package main

import "math"

// CheckersBot — бот на основе минимакса с альфа-бета отсечением.
// Оценка позиции простая: количество шашек (дамка весит дороже
// простой) плюс небольшой бонус за продвижение к последней
// горизонтали — этого достаточно для разумной, но не идеальной игры.
type CheckersBot struct {
	Depth int // глубина поиска в полуходах; 0 -> используется значение по умолчанию
}

// Уровни сложности: разница только в глубине поиска минимакса —
// более глубокий просчёт означает более сильную (но и более
// медленную) игру. На доступном железе даже "сильный" уровень
// отвечает за доли секунды.
const (
	CheckersDifficultyEasy = 3 // "Простой" — быстрый, ощутимо слабее
	CheckersDifficultyHard = 8 // "Сильный" — заметно сильнее, чуть медленнее
)

const defaultCheckersDepth = 5

// checkersMoveStep — один шаг хода (from->to), достаточный для
// вызова g.Move один раз; серии взятий бот проходит по одному шагу,
// как и человек через TUI.
type checkersMoveStep struct {
	From CheckersPos
	To   CheckersPos
}

// legalStepsForColor перечисляет все допустимые ходы (from->to) для
// стороны color при текущем состоянии g (учитывая обязательное
// взятие и середину серии, если применимо).
func legalStepsForColor(g *CheckersGameState, color CheckersColor) []checkersMoveStep {
	var out []checkersMoveStep
	if g.ChainPos != nil {
		for _, opt := range g.LegalMovesFrom(*g.ChainPos) {
			out = append(out, checkersMoveStep{From: *g.ChainPos, To: opt.To})
		}
		return out
	}
	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}
			for _, opt := range g.LegalMovesFrom(pos) {
				out = append(out, checkersMoveStep{From: pos, To: opt.To})
			}
		}
	}
	return out
}

// cloneCheckers создаёт независимую глубокую копию состояния — нужна
// минимаксу для просчёта ходов без изменения реальной партии.
func cloneCheckers(g *CheckersGameState) *CheckersGameState {
	clone := &CheckersGameState{
		Turn:              g.Turn,
		MovesSinceCapture: g.MovesSinceCapture,
	}
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			if p := g.Board[r][c]; p != nil {
				cp := *p
				clone.Board[r][c] = &cp
			}
		}
	}
	if g.ChainPos != nil {
		pos := *g.ChainPos
		clone.ChainPos = &pos
	}
	if g.Result != nil {
		res := *g.Result
		clone.Result = &res
	}
	return clone
}

// evaluateCheckers оценивает позицию с точки зрения perspective:
// положительно — хорошо для perspective, отрицательно — для
// соперника.
func evaluateCheckers(g *CheckersGameState, perspective CheckersColor) int {
	if g.Result != nil {
		if g.Result.Draw {
			return 0
		}
		if g.Result.Winner == perspective {
			return 100000
		}
		return -100000
	}
	score := 0
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			p := g.Board[r][c]
			if p == nil {
				continue
			}
			value := 10
			if p.King {
				value = 18
			} else {
				if p.Color == CheckersWhite {
					value += (7 - r) / 3
				} else {
					value += r / 3
				}
			}
			if p.Color == perspective {
				score += value
			} else {
				score -= value
			}
		}
	}
	return score
}

// minimax реализует альфа-бета отсечение. toMove — сторона, чей ход
// сейчас просчитывается; perspective — сторона, для которой ведётся
// итоговая оценка (бот).
func minimax(g *CheckersGameState, depth int, alpha, beta int, perspective CheckersColor) int {
	if g.Result != nil || depth == 0 {
		return evaluateCheckers(g, perspective)
	}

	steps := legalStepsForColor(g, g.Turn)
	if len(steps) == 0 {
		return evaluateCheckers(g, perspective)
	}

	maximizing := g.Turn == perspective
	if maximizing {
		best := math.MinInt32
		for _, step := range steps {
			child := cloneCheckers(g)
			_ = child.Move(step.From, step.To)
			val := minimax(child, depth-1, alpha, beta, perspective)
			if val > best {
				best = val
			}
			if best > alpha {
				alpha = best
			}
			if alpha >= beta {
				break
			}
		}
		return best
	}

	best := math.MaxInt32
	for _, step := range steps {
		child := cloneCheckers(g)
		_ = child.Move(step.From, step.To)
		val := minimax(child, depth-1, alpha, beta, perspective)
		if val < best {
			best = val
		}
		if best < beta {
			beta = best
		}
		if alpha >= beta {
			break
		}
	}
	return best
}

// DecideMove выбирает лучший ход для текущего хода в g согласно
// минимаксу. Возвращает (step, true), либо (_, false), если ходов
// нет (не должно происходить, если PlayFullTurn вызывается только
// когда игра не окончена).
func (b CheckersBot) DecideMove(g *CheckersGameState) (checkersMoveStep, bool) {
	steps := legalStepsForColor(g, g.Turn)
	if len(steps) == 0 {
		return checkersMoveStep{}, false
	}
	depth := b.Depth
	if depth <= 0 {
		depth = defaultCheckersDepth
	}

	perspective := g.Turn
	bestVal := math.MinInt32
	var bestStep checkersMoveStep
	alpha, beta := math.MinInt32, math.MaxInt32
	for _, step := range steps {
		child := cloneCheckers(g)
		_ = child.Move(step.From, step.To)
		val := minimax(child, depth-1, alpha, beta, perspective)
		if val > bestVal {
			bestVal = val
			bestStep = step
		}
		if bestVal > alpha {
			alpha = bestVal
		}
	}
	return bestStep, true
}

// PlayFullTurn исполняет один шаг хода бота (одно применение Move) —
// при серии взятий вызывающий код должен продолжать вызывать
// PlayFullTurn, пока не сменится очередь хода (g.Turn).
func (b CheckersBot) PlayFullTurn(g *CheckersGameState) error {
	step, ok := b.DecideMove(g)
	if !ok {
		return nil
	}
	return g.Move(step.From, step.To)
}
