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
}
