package main

import "errors"

// ПРИМЕЧАНИЕ О ПРАВИЛАХ: реализованы русские шашки (не международные
// и не английские): доска 8х8, взятие обязательно (но правило
// "бить максимально" НЕ действует — подходит любое доступное взятие,
// в отличие от международных шашек), простая шашка бьёт в любом из
// четырёх диагональных направлений (включая назад), дамка ходит и
// бьёт "летающим" ходом на любое расстояние по диагонали. Официальные
// правила ничьей (15/30-ходовые ограничения для эндшпилей) не
// реализованы — вместо них простая защита: если долго (60 полуходов)
// не было взятий, партия принудительно объявляется ничьей.

// CheckersColor — цвет стороны.
type CheckersColor int

const (
	CheckersWhite CheckersColor = iota
	CheckersBlack
)

func (c CheckersColor) Opponent() CheckersColor {
	if c == CheckersWhite {
		return CheckersBlack
	}
	return CheckersWhite
}

// CheckersPiece — шашка на доске.
type CheckersPiece struct {
	Color CheckersColor
	King  bool
}

// CheckersPos — координаты клетки (0-7, 0-7).
type CheckersPos struct {
	Row, Col int
}

func (p CheckersPos) inBounds() bool {
	return p.Row >= 0 && p.Row < 8 && p.Col >= 0 && p.Col < 8
}

var (
	ErrCheckersWrongTurn    = errors.New("сейчас не ваш ход")
	ErrCheckersNoPiece      = errors.New("на этой клетке нет вашей шашки")
	ErrCheckersMustContinue = errors.New("нужно продолжать бить той же шашкой")
	ErrCheckersInvalidMove  = errors.New("такой ход недопустим")
	ErrCheckersMustCapture  = errors.New("доступно взятие — простой ход запрещён")
	ErrCheckersGameOver     = errors.New("партия уже завершена")
)

// checkersMoveOption — один вариант хода для конкретной шашки: куда
// встать и (если это взятие) какая клетка при этом освобождается.
type checkersMoveOption struct {
	To      CheckersPos
	Capture *CheckersPos
}

// CheckersResult — итог завершившейся партии.
type CheckersResult struct {
	Winner CheckersColor
	Draw   bool
}

// maxCheckersMovesWithoutCapture — защита от бесконечных ничейных
// партий без взятий (упрощение официальных правил ничьей).
const maxCheckersMovesWithoutCapture = 60

// CheckersGameState — полное состояние партии в шашки.
type CheckersGameState struct {
	Board             [8][8]*CheckersPiece
	Turn              CheckersColor
	ChainPos          *CheckersPos // не nil, если середина серии взятий одной шашкой
	MovesSinceCapture int
	Result            *CheckersResult
}

// NewCheckersGame создаёт новую партию с начальной расстановкой:
// белые внизу (строки 5-7), чёрные наверху (строки 0-2), только на
// тёмных клетках. Первый ход — за белыми.
func NewCheckersGame() *CheckersGameState {
	g := &CheckersGameState{Turn: CheckersWhite}
	for row := 0; row < 3; row++ {
		for col := 0; col < 8; col++ {
			if (row+col)%2 == 1 {
				g.Board[row][col] = &CheckersPiece{Color: CheckersBlack}
			}
		}
	}
	for row := 5; row < 8; row++ {
		for col := 0; col < 8; col++ {
			if (row+col)%2 == 1 {
				g.Board[row][col] = &CheckersPiece{Color: CheckersWhite}
			}
		}
	}
	return g
}

var checkersDirections = []CheckersPos{{Row: -1, Col: -1}, {Row: -1, Col: 1}, {Row: 1, Col: -1}, {Row: 1, Col: 1}}

// simpleMoveOptions возвращает некапитальные ходы для шашки в pos.
func (g *CheckersGameState) simpleMoveOptions(pos CheckersPos) []CheckersPos {
	piece := g.Board[pos.Row][pos.Col]
	if piece == nil {
		return nil
	}
	var out []CheckersPos
	if piece.King {
		for _, d := range checkersDirections {
			for step := 1; ; step++ {
				np := CheckersPos{Row: pos.Row + d.Row*step, Col: pos.Col + d.Col*step}
				if !np.inBounds() || g.Board[np.Row][np.Col] != nil {
					break
				}
				out = append(out, np)
			}
		}
		return out
	}
	forward := 1
	if piece.Color == CheckersWhite {
		forward = -1
	}
	for _, dc := range []int{-1, 1} {
		np := CheckersPos{Row: pos.Row + forward, Col: pos.Col + dc}
		if np.inBounds() && g.Board[np.Row][np.Col] == nil {
			out = append(out, np)
		}
	}
	return out
}

// captureMoveOptions возвращает доступные варианты взятия для шашки
// в pos (без учёта того, обязательно ли вообще бить в этот ход —
// это проверяется на уровне выше).
func (g *CheckersGameState) captureMoveOptions(pos CheckersPos) []checkersMoveOption {
	piece := g.Board[pos.Row][pos.Col]
	if piece == nil {
		return nil
	}
	var out []checkersMoveOption
	if piece.King {
		for _, d := range checkersDirections {
			var enemyAt *CheckersPos
			for step := 1; ; step++ {
				np := CheckersPos{Row: pos.Row + d.Row*step, Col: pos.Col + d.Col*step}
				if !np.inBounds() {
					break
				}
				occupant := g.Board[np.Row][np.Col]
				if enemyAt == nil {
					if occupant == nil {
						continue
					}
					if occupant.Color == piece.Color {
						break
					}
					captured := np
					enemyAt = &captured
					continue
				}
				if occupant != nil {
					break
				}
				out = append(out, checkersMoveOption{To: np, Capture: enemyAt})
			}
		}
		return out
	}
	for _, d := range checkersDirections {
		mid := CheckersPos{Row: pos.Row + d.Row, Col: pos.Col + d.Col}
		land := CheckersPos{Row: pos.Row + d.Row*2, Col: pos.Col + d.Col*2}
		if !land.inBounds() {
			continue
		}
		midPiece := g.Board[mid.Row][mid.Col]
		if midPiece == nil || midPiece.Color == piece.Color {
			continue
		}
		if g.Board[land.Row][land.Col] != nil {
			continue
		}
		out = append(out, checkersMoveOption{To: land, Capture: &mid})
	}
	return out
}

// anyCaptureAvailable проверяет, есть ли у стороны color хоть одно
// доступное взятие где-либо на доске.
func (g *CheckersGameState) anyCaptureAvailable(color CheckersColor) bool {
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			p := g.Board[r][c]
			if p != nil && p.Color == color && len(g.captureMoveOptions(CheckersPos{Row: r, Col: c})) > 0 {
				return true
			}
		}
	}
	return false
}

// hasAnyLegalMove проверяет, есть ли у стороны color вообще хоть
// один легальный ход (взятие или простой).
func (g *CheckersGameState) hasAnyLegalMove(color CheckersColor) bool {
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			p := g.Board[r][c]
			if p == nil || p.Color != color {
				continue
			}
			pos := CheckersPos{Row: r, Col: c}
			if len(g.captureMoveOptions(pos)) > 0 || len(g.simpleMoveOptions(pos)) > 0 {
				return true
			}
		}
	}
	return false
}

// LegalMovesFrom возвращает список допустимых клеток назначения для
// шашки в pos с учётом текущей фазы хода (продолжение серии взятий,
// обязательное взятие или свободный ход).
func (g *CheckersGameState) LegalMovesFrom(pos CheckersPos) []checkersMoveOption {
	if g.Result != nil {
		return nil
	}
	piece := g.Board[pos.Row][pos.Col]
	if piece == nil || piece.Color != g.Turn {
		return nil
	}
	if g.ChainPos != nil {
		if pos != *g.ChainPos {
			return nil
		}
		return g.captureMoveOptions(pos)
	}
	if g.anyCaptureAvailable(g.Turn) {
		return g.captureMoveOptions(pos)
	}
	simples := g.simpleMoveOptions(pos)
	out := make([]checkersMoveOption, len(simples))
	for i, s := range simples {
		out[i] = checkersMoveOption{To: s}
	}
	return out
}

func isPromotionRow(color CheckersColor, row int) bool {
	if color == CheckersWhite {
		return row == 0
	}
	return row == 7
}

// Move выполняет ход шашкой из from в to, если он допустим по
// текущим правилам (см. LegalMovesFrom).
func (g *CheckersGameState) Move(from, to CheckersPos) error {
	if g.Result != nil {
		return ErrCheckersGameOver
	}
	piece := g.Board[from.Row][from.Col]
	if piece == nil || piece.Color != g.Turn {
		return ErrCheckersNoPiece
	}
	if g.ChainPos != nil && from != *g.ChainPos {
		return ErrCheckersMustContinue
	}

	options := g.LegalMovesFrom(from)
	var chosen *checkersMoveOption
	for i := range options {
		if options[i].To == to {
			chosen = &options[i]
			break
		}
	}
	if chosen == nil {
		if g.ChainPos == nil && g.anyCaptureAvailable(g.Turn) {
			simples := g.simpleMoveOptions(from)
			for _, s := range simples {
				if s == to {
					return ErrCheckersMustCapture
				}
			}
		}
		return ErrCheckersInvalidMove
	}

	g.Board[from.Row][from.Col] = nil
	if chosen.Capture != nil {
		g.Board[chosen.Capture.Row][chosen.Capture.Col] = nil
	}
	g.Board[to.Row][to.Col] = piece

	if !piece.King && isPromotionRow(piece.Color, to.Row) {
		piece.King = true
	}

	if chosen.Capture != nil {
		g.MovesSinceCapture = 0
		if len(g.captureMoveOptions(to)) > 0 {
			g.ChainPos = &to
			return nil
		}
		g.ChainPos = nil
	} else {
		g.MovesSinceCapture++
		g.ChainPos = nil
	}

	g.Turn = g.Turn.Opponent()
	g.checkGameOver()
	return nil
}

func (g *CheckersGameState) checkGameOver() {
	if g.MovesSinceCapture >= maxCheckersMovesWithoutCapture {
		g.Result = &CheckersResult{Draw: true}
		return
	}
	if !g.hasAnyLegalMove(g.Turn) {
		g.Result = &CheckersResult{Winner: g.Turn.Opponent()}
	}
}
