<?php

/**
 * 文件功能：五子棋 AI 对战算法服务
 *
 * 实现四个难度的 AI 决策逻辑：
 * - 简单：随机有效落点 + 单步威胁阻挡
 * - 普通：Minimax 深度 3
 * - 困难：Minimax + Alpha-Beta 剪枝 深度 5
 * - 专家：Minimax + Alpha-Beta 剪枝 深度 7 + 进攻优先调整
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Services;

class GomokuAiService
{
    /** @var int 棋盘尺寸 */
    private const BOARD_SIZE = 15;

    /** @var int 黑棋（玩家先手） */
    private const BLACK = 1;

    /** @var int 白棋/AI */
    private const WHITE = 2;

    /** @var int 无穷大分数 */
    private const INF = 999999;

    /**
     * 根据当前棋盘和 AI 难度，返回最优落点坐标。
     *
     * @param  array  $board  当前棋盘状态（15×15）
     * @param  int  $aiLevel  AI 难度：1=简单 2=普通 3=困难 4=专家
     * @return array{row: int, col: int} 最优落点
     */
    public function think(array $board, int $aiLevel): array
    {
        return match ($aiLevel) {
            1 => $this->thinkMinimax($board, 2),  // 简单：深度2
            2 => $this->thinkMinimax($board, 3),  // 普通：深度3
            3 => $this->thinkMinimax($board, 4),  // 困难：深度4
            default => $this->thinkMinimax($board, 5),  // 专家：深度5
        };
    }

    // ─── 简单难度：随机 + 单步阻挡 ─────────────────────────────────

    /**
     * 简单 AI：先检查是否有必须阻挡的威胁，否则随机落子。
     *
     * @param  array  $board  棋盘
     * @return array{row: int, col: int}
     */
    private function thinkSimple(array $board): array
    {
        // 先检查 AI 自己是否能一步胜利
        $win = $this->findImmediateWin($board, self::WHITE);
        if ($win !== null) {
            return $win;
        }

        // 再检查玩家是否要连成五，必须阻挡
        $block = $this->findImmediateWin($board, self::BLACK);
        if ($block !== null) {
            return $block;
        }

        // 随机选择有棋子附近的空位（增加合理性）
        $candidates = $this->getCandidates($board, 1);

        // 随机选一个候选点
        if (! empty($candidates)) {
            return $candidates[array_rand($candidates)];
        }

        // 棋盘全空时走中心
        return ['row' => 7, 'col' => 7];
    }

    /**
     * 寻找能立即获胜（连成五子）的落点。
     *
     * @param  array  $board  棋盘
     * @param  int  $color  检查哪方颜色
     * @return array{row: int, col: int}|null
     */
    private function findImmediateWin(array $board, int $color): ?array
    {
        for ($r = 0; $r < self::BOARD_SIZE; $r++) {
            for ($c = 0; $c < self::BOARD_SIZE; $c++) {
                if ($board[$r][$c] !== 0) {
                    continue;
                }
                $board[$r][$c] = $color;
                if ($this->checkWinAt($board, $r, $c, $color)) {
                    $board[$r][$c] = 0;

                    return ['row' => $r, 'col' => $c];
                }
                $board[$r][$c] = 0;
            }
        }

        return null;
    }

    // ─── Minimax + Alpha-Beta 剪枝 ──────────────────────────────────

    /**
     * 使用 Minimax 算法（含 Alpha-Beta 剪枝）找最优落点。
     *
     * @param  array  $board  棋盘
     * @param  int  $depth  搜索深度
     * @return array{row: int, col: int}
     */
    private function thinkMinimax(array $board, int $depth): array
    {
        $bestScore = -self::INF;
        $bestMove = ['row' => 7, 'col' => 7];

        // 先检查即时胜利（避免算法绕过）
        $win = $this->findImmediateWin($board, self::WHITE);
        if ($win !== null) {
            return $win;
        }
        $block = $this->findImmediateWin($board, self::BLACK);
        if ($block !== null) {
            return $block;
        }

        // 获取候选点（半径1，避免候选点过多导致超时）
        $candidates = $this->getCandidates($board, 1);

        if (empty($candidates)) {
            return ['row' => 7, 'col' => 7];
        }

        // 对候选点预排序（快速评分优先，提升剪枝效率）
        usort($candidates, function ($a, $b) use ($board) {
            return $this->evaluatePoint($board, $b['row'], $b['col'], self::WHITE)
                - $this->evaluatePoint($board, $a['row'], $a['col'], self::WHITE);
        });

        // 只取前 20 个高分候选点，进一步减少搜索空间
        $candidates = array_slice($candidates, 0, 20);

        foreach ($candidates as $move) {
            $board[$move['row']][$move['col']] = self::WHITE;
            $score = $this->minimax($board, $depth - 1, -self::INF, self::INF, false, $move['row'], $move['col']);
            $board[$move['row']][$move['col']] = 0;

            if ($score > $bestScore) {
                $bestScore = $score;
                $bestMove = $move;
            }
        }

        return $bestMove;
    }

    /**
     * Minimax 递归搜索。
     *
     * @param  array  $board  棋盘
     * @param  int  $depth  剩余深度
     * @param  int  $alpha  Alpha 值（剪枝用）
     * @param  int  $beta  Beta 值（剪枝用）
     * @param  bool  $isMaximize  是否为最大化层（AI 落子）
     * @param  int  $lastRow  上一步落子行（用于快速胜负检测）
     * @param  int  $lastCol  上一步落子列
     */
    private function minimax(
        array $board,
        int $depth,
        int $alpha,
        int $beta,
        bool $isMaximize,
        int $lastRow,
        int $lastCol
    ): int {
        $lastColor = $isMaximize ? self::BLACK : self::WHITE;

        // 终止条件：上一步是否已经胜利
        if ($this->checkWinAt($board, $lastRow, $lastCol, $lastColor)) {
            return $isMaximize ? -self::INF : self::INF;
        }

        // 深度耗尽：评估当前局面
        if ($depth === 0) {
            return $this->evaluateBoard($board);
        }

        // 递归层同样限制候选点范围（半径1，最多15个），防止指数爆炸
        $candidates = array_slice($this->getCandidates($board, 1), 0, 15);
        if (empty($candidates)) {
            return $this->evaluateBoard($board);
        }

        if ($isMaximize) {
            // AI 落子（最大化）
            $best = -self::INF;
            foreach ($candidates as $move) {
                $board[$move['row']][$move['col']] = self::WHITE;
                $score = $this->minimax($board, $depth - 1, $alpha, $beta, false, $move['row'], $move['col']);
                $board[$move['row']][$move['col']] = 0;
                $best = max($best, $score);
                $alpha = max($alpha, $best);
                if ($beta <= $alpha) {
                    break; // Beta 剪枝
                }
            }

            return $best;
        } else {
            // 玩家落子（最小化）
            $best = self::INF;
            foreach ($candidates as $move) {
                $board[$move['row']][$move['col']] = self::BLACK;
                $score = $this->minimax($board, $depth - 1, $alpha, $beta, true, $move['row'], $move['col']);
                $board[$move['row']][$move['col']] = 0;
                $best = min($best, $score);
                $beta = min($beta, $best);
                if ($beta <= $alpha) {
                    break; // Alpha 剪枝
                }
            }

            return $best;
        }
    }

    // ─── 棋盘评估 ────────────────────────────────────────────────────

    /**
     * 整体棋盘评估：AI 得分 - 玩家得分（正值对 AI 有利）。
     *
     * @param  array  $board  棋盘
     */
    private function evaluateBoard(array $board): int
    {
        return $this->evaluateColor($board, self::WHITE) - $this->evaluateColor($board, self::BLACK);
    }

    /**
     * 评估指定颜色在棋盘上的总得分。
     *
     * @param  array  $board  棋盘
     * @param  int  $color  棋子颜色
     */
    private function evaluateColor(array $board, int $color): int
    {
        $score = 0;
        $opponent = $color === self::WHITE ? self::BLACK : self::WHITE;

        // 四个方向
        $directions = [[0, 1], [1, 0], [1, 1], [1, -1]];

        for ($r = 0; $r < self::BOARD_SIZE; $r++) {
            for ($c = 0; $c < self::BOARD_SIZE; $c++) {
                foreach ($directions as [$dr, $dc]) {
                    $score += $this->evaluateLine($board, $r, $c, $dr, $dc, $color, $opponent);
                }
            }
        }

        return $score;
    }

    /**
     * 评估从 (r, c) 出发沿 (dr, dc) 方向的连子得分。
     *
     * @param  array  $board  棋盘
     * @param  int  $r  起始行
     * @param  int  $c  起始列
     * @param  int  $dr  行方向步长
     * @param  int  $dc  列方向步长
     * @param  int  $color  我方颜色
     * @param  int  $opponent  对方颜色
     */
    private function evaluateLine(
        array $board,
        int $r, int $c,
        int $dr, int $dc,
        int $color,
        int $opponent
    ): int {
        // 统计连续同色棋子数
        $count = 0;
        $open = 0; // 两端开口数

        for ($i = 0; $i < 5; $i++) {
            $nr = $r + $dr * $i;
            $nc = $c + $dc * $i;
            if ($nr < 0 || $nr >= self::BOARD_SIZE || $nc < 0 || $nc >= self::BOARD_SIZE) {
                return 0; // 越界，无效
            }
            $cell = $board[$nr][$nc];
            if ($cell === $opponent) {
                return 0; // 被对方截断，无价值
            }
            if ($cell === $color) {
                $count++;
            }
        }

        // 检测前端开口
        $prevR = $r - $dr;
        $prevC = $c - $dc;
        if ($prevR >= 0 && $prevR < self::BOARD_SIZE && $prevC >= 0 && $prevC < self::BOARD_SIZE) {
            if ($board[$prevR][$prevC] === 0) {
                $open++;
            }
        }

        // 检测后端开口
        $nextR = $r + $dr * 5;
        $nextC = $c + $dc * 5;
        if ($nextR >= 0 && $nextR < self::BOARD_SIZE && $nextC >= 0 && $nextC < self::BOARD_SIZE) {
            if ($board[$nextR][$nextC] === 0) {
                $open++;
            }
        }

        // 根据连子数和开口数评分
        return match ($count) {
            5 => 10000,                    // 五连：胜利
            4 => $open >= 1 ? 5000 : 500,  // 四连活四/眠四
            3 => $open === 2 ? 500 : 50,   // 活三/眠三
            2 => $open === 2 ? 50 : 10,    // 活二/眠二
            default => 0,
        };
    }

    /**
     * 评估在指定点落子后的局部得分（用于候选点预排序）。
     *
     * @param  array  $board  棋盘
     * @param  int  $row  行
     * @param  int  $col  列
     * @param  int  $color  落子颜色
     */
    private function evaluatePoint(array $board, int $row, int $col, int $color): int
    {
        $board[$row][$col] = $color;
        $score = $this->evaluateColor($board, $color);

        return $score;
    }

    // ─── 辅助工具 ─────────────────────────────────────────────────────

    /**
     * 获取棋盘上已有棋子周边 $range 格内的所有空位（候选落点）。
     *
     * @param  array  $board  棋盘
     * @param  int  $range  搜索半径（格数）
     * @return array<array{row: int, col: int}>
     */
    private function getCandidates(array $board, int $range = 1): array
    {
        $candidates = [];
        $visited = [];
        $hasStone = false;

        for ($r = 0; $r < self::BOARD_SIZE; $r++) {
            for ($c = 0; $c < self::BOARD_SIZE; $c++) {
                if ($board[$r][$c] === 0) {
                    continue;
                }
                $hasStone = true;
                // 在该棋子周边 $range 格内寻找空位
                for ($dr = -$range; $dr <= $range; $dr++) {
                    for ($dc = -$range; $dc <= $range; $dc++) {
                        $nr = $r + $dr;
                        $nc = $c + $dc;
                        if ($nr < 0 || $nr >= self::BOARD_SIZE || $nc < 0 || $nc >= self::BOARD_SIZE) {
                            continue;
                        }
                        $key = "{$nr},{$nc}";
                        if (! isset($visited[$key]) && $board[$nr][$nc] === 0) {
                            $candidates[] = ['row' => $nr, 'col' => $nc];
                            $visited[$key] = true;
                        }
                    }
                }
            }
        }

        // 棋盘全空时返回中心点
        if (! $hasStone) {
            return [['row' => 7, 'col' => 7]];
        }

        return $candidates;
    }

    /**
     * 检查指定位置落子后是否连成五子。
     *
     * @param  array  $board  棋盘（已包含该子）
     * @param  int  $row  行
     * @param  int  $col  列
     * @param  int  $color  棋子颜色
     */
    private function checkWinAt(array $board, int $row, int $col, int $color): bool
    {
        $directions = [[0, 1], [1, 0], [1, 1], [1, -1]];

        foreach ($directions as [$dr, $dc]) {
            $count = 1;

            for ($i = 1; $i <= 4; $i++) {
                $r = $row + $dr * $i;
                $c = $col + $dc * $i;
                if ($r < 0 || $r >= self::BOARD_SIZE || $c < 0 || $c >= self::BOARD_SIZE) {
                    break;
                }
                if (($board[$r][$c] ?? 0) !== $color) {
                    break;
                }
                $count++;
            }

            for ($i = 1; $i <= 4; $i++) {
                $r = $row - $dr * $i;
                $c = $col - $dc * $i;
                if ($r < 0 || $r >= self::BOARD_SIZE || $c < 0 || $c >= self::BOARD_SIZE) {
                    break;
                }
                if (($board[$r][$c] ?? 0) !== $color) {
                    break;
                }
                $count++;
            }

            if ($count >= 5) {
                return true;
            }
        }

        return false;
    }
}
