diff options
Diffstat (limited to 'internal/game/hacking/hacking_wumpus.go')
| -rw-r--r-- | internal/game/hacking/hacking_wumpus.go | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/internal/game/hacking/hacking_wumpus.go b/internal/game/hacking/hacking_wumpus.go new file mode 100644 index 0000000..91b9243 --- /dev/null +++ b/internal/game/hacking/hacking_wumpus.go @@ -0,0 +1,286 @@ +package hacking + +import ( + "fmt" + "math/rand" + "strconv" + "strings" +) + +var dodecahedron = [20][3]int{ + {1, 4, 7}, + {0, 2, 9}, + {1, 3, 11}, + {2, 4, 13}, + {0, 3, 5}, + {4, 6, 14}, + {5, 7, 16}, + {0, 6, 8}, + {7, 9, 17}, + {1, 8, 10}, + {9, 11, 18}, + {2, 10, 12}, + {11, 13, 19}, + {3, 12, 14}, + {5, 13, 15}, + {14, 16, 19}, + {6, 15, 17}, + {8, 16, 18}, + {10, 17, 19}, + {12, 15, 18}, +} + +type WumpusGame struct { + player int + wumpus int + pits [2]int + ice [2]int + probes int + gameOver bool + won bool +} + +func NewWumpusGame() *WumpusGame { + return &WumpusGame{} +} + +func (w *WumpusGame) Name() string { + return "Hunt the Wumpus" +} + +func (w *WumpusGame) Init(level int) string { + taken := make(map[int]bool) + w.player = rand.Intn(20) + taken[w.player] = true + + w.wumpus = randFree(taken) + taken[w.wumpus] = true + + w.pits[0] = randFree(taken) + taken[w.pits[0]] = true + w.pits[1] = randFree(taken) + taken[w.pits[1]] = true + + w.ice[0] = randFree(taken) + taken[w.ice[0]] = true + w.ice[1] = randFree(taken) + + w.probes = 5 + w.gameOver = false + w.won = false + + var sb strings.Builder + sb.WriteString("=== HUNT THE ROGUE AI ===\n") + sb.WriteString("\n") + sb.WriteString("You've jacked into an abandoned network. Somewhere in this maze of\n") + sb.WriteString("20 nodes, a rogue AI lurks. You have 5 probes to find and neutralize it.\n") + sb.WriteString("\n") + sb.WriteString("Hazards:\n") + sb.WriteString(" - Rogue AI: Moves to an adjacent node if you enter its node. Kills you.\n") + sb.WriteString(" - Data Traps: Fall in and you're fried. 2 in the network.\n") + sb.WriteString(" - ICE: Grabs you and dumps you in a random node. 2 in the network.\n") + sb.WriteString("\n") + sb.WriteString("Commands:\n") + sb.WriteString(" move <node> - Move to an adjacent node (1-20)\n") + sb.WriteString(" shoot <node> - Launch a probe into an adjacent node (1-20)\n") + sb.WriteString(" status - Show your current status\n") + sb.WriteString(" map - Show network map\n") + sb.WriteString(" jack out - Disconnect (forfeit)\n") + sb.WriteString("\n") + sb.WriteString(w.roomDesc()) + return sb.String() +} + +func (w *WumpusGame) HandleInput(input string) (string, bool, bool) { + input = strings.TrimSpace(strings.ToLower(input)) + parts := strings.Fields(input) + + if len(parts) == 0 { + return "Unknown command.", false, false + } + + cmd := parts[0] + + switch cmd { + case "move", "m": + if len(parts) < 2 { + return "Move where? (e.g., move 3)", false, false + } + n, err := strconv.Atoi(parts[1]) + if err != nil || n < 1 || n > 20 { + return "Invalid node. Use 1-20.", false, false + } + return w.doMove(n - 1) + case "shoot", "probe", "launch", "s": + if len(parts) < 2 { + return "Shoot where? (e.g., shoot 3)", false, false + } + n, err := strconv.Atoi(parts[1]) + if err != nil || n < 1 || n > 20 { + return "Invalid node. Use 1-20.", false, false + } + return w.doShoot(n - 1) + case "status": + return w.doStatus(), false, false + case "map": + return w.doMap(), false, false + default: + n, err := strconv.Atoi(cmd) + if err == nil && n >= 1 && n <= 20 { + return w.doMove(n - 1) + } + return "Commands: move <N>, shoot <N>, status, map, jack out", false, false + } +} + +func (w *WumpusGame) doMove(target int) (string, bool, bool) { + if !w.hasAdjacent(w.player, target) { + adj := w.adjStrings(w.player) + return fmt.Sprintf("Can't move there. Adjacent nodes: %s", strings.Join(adj, ", ")), false, false + } + w.player = target + return w.checkRoom() +} + +func (w *WumpusGame) doShoot(target int) (string, bool, bool) { + if !w.hasAdjacent(w.player, target) { + adj := w.adjStrings(w.player) + return fmt.Sprintf("Can't shoot there. Adjacent nodes: %s", strings.Join(adj, ", ")), false, false + } + w.probes-- + if target == w.wumpus { + w.gameOver = true + w.won = true + return "Your probe hits the rogue AI! It destabilizes and crashes.\n\nConnection terminated.", true, true + } + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Your probe finds nothing in node %d.", target+1)) + if w.probes == 0 { + sb.WriteString("\nYou're out of probes. The rogue AI detects your presence and terminates your connection.") + w.gameOver = true + w.won = false + return sb.String(), true, false + } + if rand.Float64() < 0.75 { + w.wumpus = w.randomAdjacent(w.wumpus) + sb.WriteString("\nYou hear something shift in the network...") + } + sb.WriteString("\n") + sb.WriteString(w.roomDesc()) + return sb.String(), false, false +} + +func (w *WumpusGame) checkRoom() (string, bool, bool) { + var sb strings.Builder + cur := w.player + if cur == w.wumpus { + if rand.Float64() < 0.75 { + newRoom := w.randomAdjacent(w.wumpus) + w.wumpus = newRoom + sb.WriteString("--- Node " + fmt.Sprint(cur+1) + " ---\n") + sb.WriteString("The rogue AI stirs and relocates to a nearby node...\n") + sb.WriteString("\n") + sb.WriteString(w.roomDesc()) + return sb.String(), false, false + } + sb.WriteString("The rogue AI devours your connection!\n\nConnection terminated.") + w.gameOver = true + w.won = false + return sb.String(), true, false + } + for _, p := range w.pits { + if cur == p { + sb.WriteString("You've fallen into a data trap! Your connection is severed.") + w.gameOver = true + w.won = false + return sb.String(), true, false + } + } + for _, i := range w.ice { + if cur == i { + sb.WriteString("ICE detected! You're relocated to a random node...\n") + w.player = rand.Intn(20) + chained, _, _ := w.checkRoom() + sb.WriteString(chained) + return sb.String(), false, false + } + } + sb.WriteString(w.roomDesc()) + return sb.String(), false, false +} + +func (w *WumpusGame) roomDesc() string { + cur := w.player + var sb strings.Builder + sb.WriteString(fmt.Sprintf("--- Node %d ---\n", cur+1)) + adj := w.adjStrings(cur) + sb.WriteString(fmt.Sprintf("Tunnels lead to: %s\n", strings.Join(adj, ", "))) + + for _, a := range dodecahedron[cur] { + if a == w.wumpus { + sb.WriteString("{196}You detect corrupted data nearby...{/}\n") + } + for _, p := range w.pits { + if a == p { + sb.WriteString("{208}You sense a void in the network...{/}\n") + } + } + for _, i := range w.ice { + if a == i { + sb.WriteString("{226}You hear static crackling...{/}\n") + } + } + } + return sb.String() +} + +func (w *WumpusGame) doStatus() string { + return fmt.Sprintf("Node: %d | Probes: %d/5", w.player+1, w.probes) +} + +func (w *WumpusGame) doMap() string { + var sb strings.Builder + sb.WriteString("=== Network Map ===\n") + for i := 0; i < 20; i++ { + marker := " " + if i == w.player { + marker = "*" + } + adj := dodecahedron[i] + sb.WriteString(fmt.Sprintf(" [%s] Node %2d -> %2d, %2d, %2d\n", + marker, i+1, adj[0]+1, adj[1]+1, adj[2]+1)) + } + sb.WriteString(fmt.Sprintf("\nYou are at Node %d.\n", w.player+1)) + return sb.String() +} + +func (w *WumpusGame) hasAdjacent(from, to int) bool { + for _, a := range dodecahedron[from] { + if a == to { + return true + } + } + return false +} + +func (w *WumpusGame) randomAdjacent(r int) int { + adj := dodecahedron[r] + return adj[rand.Intn(len(adj))] +} + +func (w *WumpusGame) adjStrings(r int) []string { + var out []string + for _, a := range dodecahedron[r] { + out = append(out, fmt.Sprint(a+1)) + } + return out +} + +func randFree(taken map[int]bool) int { + for { + r := rand.Intn(20) + if !taken[r] { + return r + } + } +} |
