### Core Engine - Updated `GameRules.ApplyMove` to return `(changed, gameOver, winnerIdx, keepTurn)` - Added keepTurn handling in `MatchLoop` to support Battleship mode B (classic rules) - Removed old single-board handling from MatchState and MatchInit - Cleaned go.mod by marking protobuf dependency as indirect ### Battleship - Implemented board-based state tracking using MatchState.Boards: - `p0_ships`, `p0_shots`, `p1_ships`, `p1_shots` - Removed legacy metadata-based ship/shot board encoding - Rewrote ValidateMove to use structured boards - Rewrote ApplyMove for classic Battleship rules (mode B): - Hits allow the attacker to keep their turn - Miss switches turn - Destroyed ship sections marked `X` - Improved CheckGameOver using structured boards ### TicTacToe - Updated ApplyMove signature to match new interface - Ensured TicTacToe always returns `keepTurn = false` - Updated code paths to use MatchState.Boards instead of Board ### Summary This commit completes the migration from a single-board architecture to a multi-board architecture across the engine, TicTacToe, and Battleship, enabling support for more complex games and multiple modes such as Battleship Mode B.
170 lines
3.7 KiB
Go
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, bool) {
|
|
b := state.Boards["tictactoe"]
|
|
if b == nil {
|
|
return false, false, -1, false
|
|
}
|
|
|
|
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, false
|
|
}
|
|
|
|
|
|
// 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 ""
|
|
}
|