Files
tic-tac-toe/plugins/games/tic_tac_toe.go
Vishesh 'ironeagle' Bangotra bcdc5faea5 feat(core): migrate to multi-board architecture and implement per-game InitBoards
### Major changes
- Replace single-board MatchState (`Board`) with multi-board map (`Boards`)
- Update GenericMatch to initialize empty Boards and populate them via GameRules.InitBoards
- Remove legacy `newEmptyBoard` helper from match.go
- Update .gitignore to include *BKP* patterns

### GameRules interface
- Add InitBoards(players, cfg) to allow games to construct their own board sets
- Add detailed documentation explaining method responsibilities and usage
- Improve MovePayload comment clarity

### TicTacToe updates
- Implement InitBoards to produce a single `"tictactoe"` board
- Replace all old references to `state.Board` with `state.Boards["tictactoe"]`
- Make CheckGameOver, ValidateMove, and ApplyMove multi-board compatible

### Battleship updates
- Implement InitBoards generating per-player ships + shots boards:
  - p0_ships, p0_shots
  - p1_ships, p1_shots

### Match flow updates
- Boards are now created only when all players have joined
- Initial state broadcast now includes `boards` instead of `board`

This completes the backend migration for multi-board games and prepares the architecture
for Battleship and other complex board-based games.
2025-12-03 17:52:27 +05:30

170 lines
3.7 KiB
Go

package games
import (
"localrepo/plugins/structs"
)
// TicTacToeRules implements GameRules for 3x3 Tic Tac Toe.
type TicTacToeRules struct{}
// -------------------------------
// GameRules Implementation
// -------------------------------
func (t *TicTacToeRules) MaxPlayers() int {
return 2
}
func (t *TicTacToeRules) InitBoards(players []*structs.Player, cfg GameConfiguration) map[string]*structs.Board {
return map[string]*structs.Board{
"tictactoe": structs.NewBoard(cfg.Board.Rows, cfg.Board.Cols),
}
}
// Assign player symbols: X and O
func (t *TicTacToeRules) AssignPlayerSymbols(players []*structs.Player) {
if len(players) < 2 {
return
}
players[0].Metadata["symbol"] = "X"
players[1].Metadata["symbol"] = "O"
}
// ValidateMove checks bounds and empty cell.
func (t *TicTacToeRules) ValidateMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool {
rowVal, ok1 := payload.Data["row"]
colVal, ok2 := payload.Data["col"]
if !ok1 || !ok2 {
return false
}
row, ok3 := rowVal.(float64)
col, ok4 := colVal.(float64)
if !ok3 || !ok4 {
return false
}
r := int(row)
c := int(col)
b := state.Boards["tictactoe"]
if b == nil {
return false
}
// bounds
if !b.InBounds(r, c) {
return false
}
// empty?
return b.IsEmpty(r, c)
}
// ApplyMove writes X or O to the board.
func (t *TicTacToeRules) ApplyMove(
state *structs.MatchState,
playerIdx int,
payload MovePayload,
) (bool, bool, int) {
b := state.Boards["tictactoe"]
if b == nil {
return false, false, -1
}
symbol := state.Players[playerIdx].Metadata["symbol"]
r := int(payload.Data["row"].(float64))
c := int(payload.Data["col"].(float64))
b.Set(r, c, symbol)
over, winner := t.CheckGameOver(state)
return true, over, winner
}
// CheckGameOver determines win/draw state.
func (t *TicTacToeRules) CheckGameOver(state *structs.MatchState) (bool, int) {
b := state.Boards["tictactoe"]
if b == nil {
return true, -1 // fallback safety
}
winnerSymbol := t.findWinner(b)
if winnerSymbol != "" {
// find the player with this symbol
for _, p := range state.Players {
if p.Metadata["symbol"] == winnerSymbol {
return true, p.Index
}
}
return true, -1
}
if b.Full() {
return true, -1 // draw
}
return false, -1
}
// OnForfeit: whoever leaves loses instantly
func (t *TicTacToeRules) ForfeitWinner(state *structs.MatchState, leaverIndex int) int {
// If player 0 leaves, player 1 wins.
if leaverIndex == 0 && len(state.Players) > 1 {
return 1
}
// If player 1 leaves, player 0 wins.
if leaverIndex == 1 && len(state.Players) > 0 {
return 0
}
// Otherwise draw.
return -1
}
// -------------------------------
// Helper: winner detection
// -------------------------------
func (t *TicTacToeRules) findWinner(b *structs.Board) string {
lines := [][][2]int{
// rows
{{0, 0}, {0, 1}, {0, 2}},
{{1, 0}, {1, 1}, {1, 2}},
{{2, 0}, {2, 1}, {2, 2}},
// cols
{{0, 0}, {1, 0}, {2, 0}},
{{0, 1}, {1, 1}, {2, 1}},
{{0, 2}, {1, 2}, {2, 2}},
// diagonals
{{0, 0}, {1, 1}, {2, 2}},
{{0, 2}, {1, 1}, {2, 0}},
}
for _, line := range lines {
r1, c1 := line[0][0], line[0][1]
r2, c2 := line[1][0], line[1][1]
r3, c3 := line[2][0], line[2][1]
v1 := b.Get(r1, c1)
if v1 != "" &&
v1 == b.Get(r2, c2) &&
v1 == b.Get(r3, c3) {
return v1
}
}
return ""
}