144 lines
3.2 KiB
Go
144 lines
3.2 KiB
Go
package games
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"localrepo/plugins/structs"
|
|
)
|
|
|
|
// TicTacToeRules implements GameRules for 3x3 Tic Tac Toe.
|
|
type TicTacToeRules struct{}
|
|
|
|
// -------------------------------
|
|
// GameRules Implementation
|
|
// -------------------------------
|
|
|
|
func (t *TicTacToeRules) MaxPlayers() int {
|
|
return 2
|
|
}
|
|
|
|
// Assign player symbols: X and O
|
|
func (t *TicTacToeRules) AssignPlayerSymbols(players []*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)
|
|
|
|
// bounds
|
|
if !state.Board.InBounds(r, c) {
|
|
return false
|
|
}
|
|
|
|
// empty?
|
|
return state.Board.IsEmpty(r, c)
|
|
}
|
|
|
|
// ApplyMove writes X or O to the board.
|
|
func (t *TicTacToeRules) ApplyMove(state *structs.MatchState, playerIdx int, payload MovePayload) {
|
|
|
|
symbol := state.Players[playerIdx].Metadata["symbol"]
|
|
|
|
r := int(payload.Data["row"].(float64))
|
|
c := int(payload.Data["col"].(float64))
|
|
|
|
state.Board.Set(r, c, symbol)
|
|
}
|
|
|
|
// CheckGameOver determines win/draw state.
|
|
func (t *TicTacToeRules) CheckGameOver(state *structs.MatchState) (bool, int) {
|
|
|
|
winnerSymbol := t.findWinner(state.Board)
|
|
|
|
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 state.Board.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 ""
|
|
}
|