Update Go module path and all import references to reflect the migration from Gitea (git.t-juice.club) to Forgejo (code.t-juice.club). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
package adventure
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.t-juice.club/torjus/oubliette/internal/shell"
|
|
)
|
|
|
|
const sessionTimeout = 10 * time.Minute
|
|
|
|
// AdventureShell implements a Zork-style text adventure set in a dungeon/data center.
|
|
type AdventureShell struct{}
|
|
|
|
// NewAdventureShell returns a new AdventureShell instance.
|
|
func NewAdventureShell() *AdventureShell {
|
|
return &AdventureShell{}
|
|
}
|
|
|
|
func (a *AdventureShell) Name() string { return "adventure" }
|
|
func (a *AdventureShell) Description() string { return "Zork-style text adventure dungeon crawler" }
|
|
|
|
func (a *AdventureShell) Handle(ctx context.Context, sess *shell.SessionContext, rw io.ReadWriteCloser) error {
|
|
ctx, cancel := context.WithTimeout(ctx, sessionTimeout)
|
|
defer cancel()
|
|
|
|
dungeonName := configString(sess.ShellConfig, "dungeon_name", "THE OUBLIETTE")
|
|
game := newGame()
|
|
|
|
// Print banner and initial room.
|
|
banner := strings.ReplaceAll(adventureBanner(dungeonName), "\n", "\r\n")
|
|
fmt.Fprint(rw, banner)
|
|
|
|
// Show starting room.
|
|
startDesc := game.describeRoom(game.rooms[game.currentRoom])
|
|
startDesc = strings.ReplaceAll(startDesc, "\n", "\r\n")
|
|
fmt.Fprintf(rw, "%s\r\n\r\n", startDesc)
|
|
|
|
for {
|
|
if _, err := fmt.Fprint(rw, "> "); err != nil {
|
|
return nil
|
|
}
|
|
|
|
line, err := shell.ReadLine(ctx, rw)
|
|
if errors.Is(err, io.EOF) {
|
|
fmt.Fprint(rw, "\r\n")
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
|
|
result := game.dispatch(trimmed)
|
|
|
|
var output string
|
|
if result.output != "" {
|
|
output = result.output
|
|
output = strings.ReplaceAll(output, "\r\n", "\n")
|
|
output = strings.ReplaceAll(output, "\n", "\r\n")
|
|
fmt.Fprintf(rw, "%s\r\n", output)
|
|
}
|
|
|
|
// Log command and output to store.
|
|
if sess.Store != nil {
|
|
if err := sess.Store.AppendSessionLog(ctx, sess.SessionID, trimmed, output); err != nil {
|
|
return fmt.Errorf("append session log: %w", err)
|
|
}
|
|
}
|
|
if sess.OnCommand != nil {
|
|
sess.OnCommand("adventure")
|
|
}
|
|
|
|
if result.exit {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func adventureBanner(dungeonName string) string {
|
|
return fmt.Sprintf(`
|
|
___ _ _ ____ _ ___ _____ _____ _____
|
|
/ _ \| | | | __ )| | |_ _| ____|_ _|_ _| ___
|
|
| | | | | | | _ \| | | || _| | | | | / _ \
|
|
| |_| | |_| | |_) | |___ | || |___ | | | | | __/
|
|
\___/ \___/|____/|_____|___|_____| |_| |_| \___|
|
|
|
|
Welcome to %s.
|
|
|
|
You wake up in the dark. The air is cold and hums with electricity.
|
|
This is a place where things are put to be forgotten.
|
|
|
|
Type 'help' for commands. Type 'look' to examine your surroundings.
|
|
|
|
`, dungeonName)
|
|
}
|
|
|
|
// configString reads a string from the shell config map with a default.
|
|
func configString(cfg map[string]any, key, defaultVal string) string {
|
|
if cfg == nil {
|
|
return defaultVal
|
|
}
|
|
if v, ok := cfg[key]; ok {
|
|
if s, ok := v.(string); ok && s != "" {
|
|
return s
|
|
}
|
|
}
|
|
return defaultVal
|
|
}
|