go_games_collection/tictactoe_game.go

130 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import "errors"
// TicTacToeMark — что стоит в клетке: пусто, крестик или нолик.
type TicTacToeMark int
const (
TicTacToeEmpty TicTacToeMark = iota
TicTacToeX
TicTacToeO
)
// TicTacToePhase — фаза партии.
type TicTacToePhase int
const (
TicTacToePhasePlay TicTacToePhase = iota
TicTacToePhaseOver
)
// TicTacToeResult — итог завершённой партии.
type TicTacToeResult struct {
Winner TicTacToeMark // TicTacToeEmpty при ничьей
Line [3]int // индексы выигрышной линии — для подсветки; не имеет значения при ничьей
Draw bool
}
// TicTacToeGameState — состояние партии в крестики-нолики (доска
// 3×3, классические правила: три подряд по горизонтали, вертикали
// или диагонали).
type TicTacToeGameState struct {
Board [9]TicTacToeMark
CurrentTurn TicTacToeMark // чей сейчас ход
HumanMark TicTacToeMark // каким знаком играет человек
Phase TicTacToePhase
Result *TicTacToeResult
MovesPlayed int
}
var (
ErrTicTacToeGameOver = errors.New("партия уже завершена")
ErrTicTacToeInvalidPosition = errors.New("недопустимый номер клетки")
ErrTicTacToeCellOccupied = errors.New("клетка уже занята")
)
// NewTicTacToeGame создаёт новую партию. humanMark задаёт, каким
// знаком играет человек — крестики всегда ходят первыми (классическое
// правило), так что если человек играет ноликами, первым автоматически
// должен сходить бот.
func NewTicTacToeGame(humanMark TicTacToeMark) *TicTacToeGameState {
if humanMark != TicTacToeX && humanMark != TicTacToeO {
humanMark = TicTacToeX
}
return &TicTacToeGameState{
HumanMark: humanMark,
CurrentTurn: TicTacToeX,
Phase: TicTacToePhasePlay,
}
}
// ticTacToeWinLines — все восемь способов выиграть: 3 строки,
// 3 столбца, 2 диагонали (индексы клеток по порядку 0..8 слева
// направо, сверху вниз).
var ticTacToeWinLines = [8][3]int{
{0, 1, 2}, {3, 4, 5}, {6, 7, 8},
{0, 3, 6}, {1, 4, 7}, {2, 5, 8},
{0, 4, 8}, {2, 4, 6},
}
// ticTacToeCheckWin проверяет, есть ли на доске выигрышная линия.
func ticTacToeCheckWin(board [9]TicTacToeMark) (winner TicTacToeMark, line [3]int, ok bool) {
for _, l := range ticTacToeWinLines {
a, b, c := board[l[0]], board[l[1]], board[l[2]]
if a != TicTacToeEmpty && a == b && b == c {
return a, l, true
}
}
return TicTacToeEmpty, [3]int{}, false
}
// ticTacToeOpponent возвращает противоположный знак.
func ticTacToeOpponent(mark TicTacToeMark) TicTacToeMark {
if mark == TicTacToeX {
return TicTacToeO
}
return TicTacToeX
}
// ticTacToeEmptyCells — индексы всех свободных клеток.
func ticTacToeEmptyCells(board [9]TicTacToeMark) []int {
var out []int
for i, m := range board {
if m == TicTacToeEmpty {
out = append(out, i)
}
}
return out
}
// PlaceMark — текущий игрок ставит свой знак в клетку pos (0..8,
// слева направо, сверху вниз).
func (g *TicTacToeGameState) PlaceMark(pos int) error {
if g.Phase != TicTacToePhasePlay {
return ErrTicTacToeGameOver
}
if pos < 0 || pos >= 9 {
return ErrTicTacToeInvalidPosition
}
if g.Board[pos] != TicTacToeEmpty {
return ErrTicTacToeCellOccupied
}
g.Board[pos] = g.CurrentTurn
g.MovesPlayed++
if winner, line, ok := ticTacToeCheckWin(g.Board); ok {
g.Result = &TicTacToeResult{Winner: winner, Line: line}
g.Phase = TicTacToePhaseOver
return nil
}
if g.MovesPlayed == 9 {
g.Result = &TicTacToeResult{Draw: true}
g.Phase = TicTacToePhaseOver
return nil
}
g.CurrentTurn = ticTacToeOpponent(g.CurrentTurn)
return nil
}