package hacking import ( "fmt" "math/rand" "strconv" "strings" ) type LiarsDiceGame struct { playerDice []int aiDice []int currentBid LiarsBid hasBid bool playerTurn bool round int gameOver bool won bool playerLost int completed bool } type LiarsBid struct { Quantity int Face int } func NewLiarsDiceGame() *LiarsDiceGame { return &LiarsDiceGame{ playerDice: make([]int, 5), aiDice: make([]int, 5), } } func (l *LiarsDiceGame) Name() string { return "Signal Bluff" } func (l *LiarsDiceGame) Init(level int) string { l.playerDice = make([]int, 5) l.aiDice = make([]int, 5) l.round = 0 l.gameOver = false l.playerTurn = true l.hasBid = false l.playerLost = 0 l.completed = false var sb strings.Builder sb.WriteString("=== SIGNAL BLUFF ===\n") sb.WriteString("\n") sb.WriteString("You've connected to a secure channel running a bluffing protocol.\n") sb.WriteString("You and the system each have 5 signal fragments (dice). Each round,\n") sb.WriteString("fragments are scrambled. You see only yours. Take turns making claims\n") sb.WriteString("about the total count of a specific signal across ALL fragments.\n") sb.WriteString("\n") sb.WriteString("1s are wild -- they count as any signal.\n") sb.WriteString("\n") sb.WriteString("Commands:\n") sb.WriteString(" bid - Claim at least N fragments show face F\n") sb.WriteString(" (e.g., 'bid 3 4' = \"at least three 4s\")\n") sb.WriteString(" call - Challenge the last bid (reveal all)\n") sb.WriteString(" exact - Claim the bid is exactly right\n") sb.WriteString(" status - Show your fragments and current bid\n") sb.WriteString(" jack out - Disconnect (forfeit)\n") sb.WriteString("\n") sb.WriteString(l.startRound()) return sb.String() } func (l *LiarsDiceGame) HandleInput(input string) (string, bool, bool) { input = strings.TrimSpace(strings.ToLower(input)) parts := strings.Fields(input) if len(parts) == 0 { return "Commands: bid , call, exact, status, jack out", false, false } cmd := parts[0] switch cmd { case "bid", "b": if len(parts) < 3 { return "Usage: bid (e.g., bid 3 4)", false, false } qty, err1 := strconv.Atoi(parts[1]) face, err2 := strconv.Atoi(parts[2]) if err1 != nil || err2 != nil { return "Usage: bid (e.g., bid 3 4)", false, false } return l.doBid(qty, face) case "call", "c", "intercept", "liar": return l.doCall(false) case "exact", "e": return l.doExact() case "status": return l.doStatus(), false, false default: if len(parts) == 2 { qty, err1 := strconv.Atoi(parts[0]) face, err2 := strconv.Atoi(parts[1]) if err1 == nil && err2 == nil { return l.doBid(qty, face) } } return "Commands: bid , call, exact, status, jack out", false, false } } func (l *LiarsDiceGame) doBid(qty, face int) (string, bool, bool) { if face < 1 || face > 6 { return "Face must be 1-6.", false, false } if qty < 1 { return "Quantity must be at least 1.", false, false } if !l.playerTurn { return "It's not your turn to bid. Use 'call' or 'exact'.", false, false } if l.hasBid { if qty < l.currentBid.Quantity || (qty == l.currentBid.Quantity && face <= l.currentBid.Face) { return "You must bid higher (higher quantity or same quantity with higher face).", false, false } } l.currentBid = LiarsBid{Quantity: qty, Face: face} l.hasBid = true l.playerTurn = false return l.aiTurn() } func (l *LiarsDiceGame) doCall(player bool) (string, bool, bool) { if !l.hasBid { return "No bid to call yet. Make a bid first.", false, false } var sb strings.Builder if player { sb.WriteString("System intercepts!\n") } else { sb.WriteString("You intercept!\n") } sb.WriteString(l.showAllDice()) sb.WriteString(fmt.Sprintf("\nBid was: %d %s\n", l.currentBid.Quantity, faceNamePlural(l.currentBid.Face))) total := l.countFace(l.currentBid.Face) sb.WriteString(fmt.Sprintf("Actual count: %d (%d %ss + %d wild 1s)\n\n", total, l.countFaceNonWild(l.currentBid.Face), faceName(l.currentBid.Face), l.countFace(1)-l.countFaceNonWild(1))) if total >= l.currentBid.Quantity { sb.WriteString("The bid holds! ") if player { sb.WriteString("System loses a fragment.") l.aiDice = l.aiDice[:len(l.aiDice)-1] } else { sb.WriteString("You lose a fragment.") l.playerDice = l.playerDice[:len(l.playerDice)-1] l.playerLost++ } } else { sb.WriteString("Bluff called! ") if player { sb.WriteString("You lose a fragment.") l.playerDice = l.playerDice[:len(l.playerDice)-1] l.playerLost++ } else { sb.WriteString("System loses a fragment.") l.aiDice = l.aiDice[:len(l.aiDice)-1] } } return l.checkGameOver(sb) } func (l *LiarsDiceGame) doExact() (string, bool, bool) { if !l.hasBid { return "No bid to claim exact on. Make a bid first.", false, false } var sb strings.Builder sb.WriteString("You claim exact match!\n") sb.WriteString(l.showAllDice()) total := l.countFace(l.currentBid.Face) sb.WriteString(fmt.Sprintf("\nBid was: %d %s\n", l.currentBid.Quantity, faceNamePlural(l.currentBid.Face))) sb.WriteString(fmt.Sprintf("Actual count: %d\n\n", total)) if total == l.currentBid.Quantity { sb.WriteString("Exact match! You recover a fragment.\n") if len(l.playerDice) < 5 { l.playerDice = append(l.playerDice, rand.Intn(6)+1) } } else { sb.WriteString("Not an exact match. You lose a fragment.\n") l.playerDice = l.playerDice[:len(l.playerDice)-1] l.playerLost++ } return l.checkGameOver(sb) } func (l *LiarsDiceGame) checkGameOver(sb strings.Builder) (string, bool, bool) { if len(l.playerDice) == 0 { sb.WriteString("\nYou've lost all your fragments. Connection terminated.") l.gameOver = true l.won = false l.completed = true return sb.String(), true, false } if len(l.aiDice) == 0 { sb.WriteString("\nSystem has no fragments left. You win!") l.gameOver = true l.won = true l.completed = true return sb.String(), true, true } sb.WriteString(l.startRound()) return sb.String(), false, false } func (l *LiarsDiceGame) showAllDice() string { var sb strings.Builder sb.WriteString(fmt.Sprintf("Your fragments: %s\n", l.diceStr(l.playerDice))) sb.WriteString(fmt.Sprintf("System fragments: %s\n", l.diceStr(l.aiDice))) return sb.String() } func (l *LiarsDiceGame) diceStr(dice []int) string { var parts []string for _, d := range dice { parts = append(parts, fmt.Sprintf("[%d]", d)) } return strings.Join(parts, " ") } func (l *LiarsDiceGame) doStatus() string { var sb strings.Builder sb.WriteString(fmt.Sprintf("--- Cycle %d ---\n", l.round)) sb.WriteString(fmt.Sprintf("Your fragments: %s (You: %d, System: %d)\n", l.diceStr(l.playerDice), len(l.playerDice), len(l.aiDice))) if l.hasBid { who := "System" if !l.playerTurn { who = "System" } else { who = "you" } sb.WriteString(fmt.Sprintf("Current bid: %d %s (by %s)\n", l.currentBid.Quantity, faceNamePlural(l.currentBid.Face), who)) } if l.playerTurn { sb.WriteString("Your turn: make a bid.") } else { sb.WriteString("Your turn: bid higher, call, or exact.") } return sb.String() } func (l *LiarsDiceGame) startRound() string { l.round++ for i := range l.playerDice { l.playerDice[i] = rand.Intn(6) + 1 } for i := range l.aiDice { l.aiDice[i] = rand.Intn(6) + 1 } l.hasBid = false l.playerTurn = true var sb strings.Builder sb.WriteString(fmt.Sprintf("--- Cycle %d ---\n", l.round)) sb.WriteString(fmt.Sprintf("Your fragments: %s (You: %d, System: %d)\n", l.diceStr(l.playerDice), len(l.playerDice), len(l.aiDice))) sb.WriteString("You go first. Make a bid.\n") return sb.String() } func (l *LiarsDiceGame) aiTurn() (string, bool, bool) { totalDice := len(l.playerDice) + len(l.aiDice) aiCount := l.countFace(l.currentBid.Face) unknownDice := len(l.playerDice) estimated := float64(aiCount) + float64(unknownDice)/3.0 if float64(l.currentBid.Quantity) > estimated*1.5 { return l.doCall(true) } bestFace, bestCount := l.aiBestFace() newQty := l.currentBid.Quantity newFace := l.currentBid.Face if bestFace > newFace && bestCount >= newQty { newFace = bestFace } else { newQty++ } if newQty > totalDice { return l.doCall(true) } l.currentBid = LiarsBid{Quantity: newQty, Face: newFace} l.playerTurn = true var sb strings.Builder sb.WriteString(fmt.Sprintf("System broadcasts: %d %s.\n", newQty, faceNamePlural(newFace))) sb.WriteString("Your turn: bid higher, call, or exact.\n") return sb.String(), false, false } func (l *LiarsDiceGame) aiBestFace() (face int, count int) { counts := make(map[int]int) for _, d := range l.aiDice { if d == 1 { continue } counts[d]++ } wilds := 0 for _, d := range l.aiDice { if d == 1 { wilds++ } } best := 2 bestN := counts[2] + wilds for f := 3; f <= 6; f++ { n := counts[f] + wilds if n > bestN || (n == bestN && f > best) { best = f bestN = n } } return best, bestN } func (l *LiarsDiceGame) countFace(face int) int { count := 0 for _, d := range l.playerDice { if d == face || d == 1 { count++ } } for _, d := range l.aiDice { if d == face || d == 1 { count++ } } return count } func (l *LiarsDiceGame) countFaceNonWild(face int) int { count := 0 for _, d := range l.playerDice { if d == face { count++ } } for _, d := range l.aiDice { if d == face { count++ } } return count } func faceName(f int) string { names := []string{"", "one", "two", "three", "four", "five", "six"} if f >= 1 && f <= 6 { return names[f] } return fmt.Sprint(f) } func faceNamePlural(f int) string { names := []string{"", "ones", "twos", "threes", "fours", "fives", "sixes"} if f >= 1 && f <= 6 { return names[f] } return fmt.Sprint(f) } func (l *LiarsDiceGame) BonusXP() int { if l.completed && l.won && l.playerLost == 0 { return 150 } return 0 }