package games import ( "fmt" "encoding/json" "localrepo/plugins/structs" ) var Fleet = map[string]int{ "carrier": 5, "battleship": 4, "cruiser": 3, "submarine": 3, "destroyer": 2, } // // BATTLESHIP RULES IMPLEMENTATION // // NOTES: // - 2 players // - Each player has 2 boards: // 1. Their own ship board (state.Board is not reused here) // 2. Their "shots" board (hits/misses on opponent) // - We store boards in Player.Metadata as JSON strings // (simplest method without changing your structs). // // ShipBoard and ShotBoard are encoded inside Metadata: // // Metadata["ship_board"] = JSON string of [][]string // Metadata["shot_board"] = JSON string of [][]string // // ------------------------------ // Helpers: encode/decode // ------------------------------ func encodeBoard(b [][]string) string { out := "[" for i, row := range b { out += "[" for j, col := range row { out += fmt.Sprintf("%q", col) if j < len(row)-1 { out += "," } } out += "]" if i < len(b)-1 { out += "," } } out += "]" return out } func decodeBoard(s string) [][]string { var out [][]string // should never fail; safe fallback _ = json.Unmarshal([]byte(s), &out) return out } // ------------------------------ // BattleshipRules // ------------------------------ type BattleshipRules struct{} func (b *BattleshipRules) MaxPlayers() int { return 2 } func (b *BattleshipRules) InitBoards(players []*structs.Player, cfg GameConfiguration) map[string]*structs.Board { boards := make(map[string]*structs.Board) // One ships board and one shots board per player for _, p := range players { pid := fmt.Sprintf("p%d", p.Index) // Player's fleet board (ships placement) boards[pid+"_ships"] = structs.NewBoard(cfg.Board.Rows, cfg.Board.Cols) // Player's attack tracking board (shots fired at opponent) boards[pid+"_shots"] = structs.NewBoard(cfg.Board.Rows, cfg.Board.Cols) } return boards } // ------------------------------ // Assign player boards // ------------------------------ func (b *BattleshipRules) AssignPlayerSymbols(players []*structs.Player) { // nothing needed for battleship } // ------------------------------ // Attach Game Metadata // ------------------------------ func (b *BattleshipRules) AttachGameMetadata(state *structs.MatchState) { state.Metadata["phase"] = "placement" state.Metadata["p0_ready"] = false state.Metadata["p1_ready"] = false } // ------------------------------ // ValidateMove // payload.data = { "row": int, "col": int } // ------------------------------ func (b *BattleshipRules) ValidateMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool { switch payload.Action { case "place": return b.ValidatePlacementMove(state, playerIdx, payload) case "shoot": return b.ValidateShotMove(state, playerIdx, payload) default: return false } } func (b *BattleshipRules) ValidatePlacementMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool { // Allow placement until player placed all ships if state.Metadata["phase"] != "placement" { return false } key := fmt.Sprintf("p%d_placed", playerIdx) placed := 0 if state.Metadata[key] != nil { placed = state.Metadata[key].(int) } return placed < len(Fleet) } func (b *BattleshipRules) ValidateShotMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool { if state.Metadata["phase"] != "battle" { return false } rf, ok1 := payload.Data["row"].(float64) cf, ok2 := payload.Data["col"].(float64) if !ok1 || !ok2 { return false } r := int(rf) c := int(cf) shotKey := fmt.Sprintf("p%d_shots", playerIdx) shotBoard := state.Boards[shotKey] if shotBoard == nil { return false } if !shotBoard.InBounds(r, c) { return false } if !shotBoard.IsEmpty(r, c) { // already shot return false } return true } // ----------------------------- // APPLY MOVE (MODE B — CLASSIC) // ----------------------------- func (b *BattleshipRules) ApplyShot( state *structs.MatchState, playerIdx int, payload MovePayload, ) (bool, bool, int, bool) { if !b.bothPlayersReady(state) { return false, false, -1, false } r := int(payload.Data["row"].(float64)) c := int(payload.Data["col"].(float64)) shotKey := fmt.Sprintf("p%d_shots", playerIdx) shipKey := fmt.Sprintf("p%d_ships", 1-playerIdx) shots := state.Boards[shotKey] ships := state.Boards[shipKey] hit := false if ships.Get(r, c) == "S" { // hit hit = true shots.Set(r, c, "H") ships.Set(r, c, "X") // mark destroyed section } else { shots.Set(r, c, "M") } // check game over over, winner := b.CheckGameOver(state) // keepTurn = hit (classic rule) return true, over, winner, hit } func (b *BattleshipRules) ApplyMove( state *structs.MatchState, playerIdx int, payload MovePayload, ) (bool, bool, int, bool) { switch payload.Action { case "place": return b.ApplyPlacement(state, playerIdx, payload) case "shoot": return b.ApplyShot(state, playerIdx, payload) default: return false, false, -1, false } } func (b *BattleshipRules) ApplyPlacement( state *structs.MatchState, playerIdx int, payload MovePayload, ) (bool, bool, int, bool) { shipName, _ := payload.Data["ship"].(string) rf, _ := payload.Data["row"].(float64) cf, _ := payload.Data["col"].(float64) dir, _ := payload.Data["dir"].(string) r := int(rf) c := int(cf) size, ok := Fleet[shipName] if !ok { return false, false, -1, false // invalid ship name } shipKey := fmt.Sprintf("p%d_ships", playerIdx) shipBoard := state.Boards[shipKey] // Validate placement if !b.validatePlacement(shipBoard, r, c, size, dir) { return false, false, -1, false } // Place the ship if dir == "h" { for i := 0; i < size; i++ { shipBoard.Set(r, c+i, "S") } } else { // vertical for i := 0; i < size; i++ { shipBoard.Set(r+i, c, "S") } } // Track ships placed by player placedCountKey := fmt.Sprintf("p%d_placed", playerIdx) count := state.Metadata[placedCountKey] if count == nil { state.Metadata[placedCountKey] = 1 } else { state.Metadata[placedCountKey] = count.(int) + 1 } // If all 5 ships placed → ready if state.Metadata[placedCountKey].(int) == len(Fleet) { readyKey := fmt.Sprintf("p%d_ready", playerIdx) state.Metadata[readyKey] = true } // Check if both players are ready if b.bothPlayersReady(state) { state.Metadata["phase"] = "battle" } return true, false, -1, false } func (b *BattleshipRules) validatePlacement(board *structs.Board, r, c, size int, dir string) bool { rows, cols := board.Rows, board.Cols if dir == "h" { if c+size > cols { return false } for i := 0; i < size; i++ { if board.Get(r, c+i) != "" { return false } } } else { if r+size > rows { return false } for i := 0; i < size; i++ { if board.Get(r+i, c) != "" { return false } } } return true } func (b *BattleshipRules) bothPlayersReady(state *structs.MatchState) bool { r0 := state.Metadata["p0_ready"] r1 := state.Metadata["p1_ready"] return r0 == true && r1 == true } // ------------------------------ // CheckGameOver // ------------------------------ func (b *BattleshipRules) CheckGameOver(state *structs.MatchState) (bool, int) { for i := range state.Players { shipKey := fmt.Sprintf("p%d_ships", i) ships := state.Boards[shipKey] alive := false for r := 0; r < ships.Rows; r++ { for c := 0; c < ships.Cols; c++ { if ships.Get(r, c) == "S" { alive = true break } } if alive { break } } if !alive { // this player has no ships left → opponent wins return true, 1 - i } } return false, -1 } // ------------------------------ // Forfeit Winner // ------------------------------ func (b *BattleshipRules) ForfeitWinner(state *structs.MatchState, leaverIndex int) int { // If player leaves, opponent automatically wins. if leaverIndex == 0 { return 1 } if leaverIndex == 1 { return 0 } return -1 }