大家好!五子棋是我们在学习生活中比较常见的棋类游戏之一。两人对弈,一黑一白,下在棋盘的交叉点上,横竖斜形成五子连珠即可获胜。也许有的同学编程的入门之作就是做一款五子棋,我刚好在项目开发过程中,有涉及到相关玩法的开发。今天我就来做一个系统的、分层次性的概述,跟大家探讨一下五子棋中AI博弈算法。
一、五子棋玩法。
五子棋玩法的实现没什么难度,无非就是一个用一个状态机控制战斗逻辑,然后就是简单的交互和显示逻辑。值得探讨的部分,还是AI博弈的部分。想要实现一个(简单/一般/困难/地域)不同难度的AI,作为我们本篇主要讨论的重点。
二、AI博弈算法。
- 最简单的随机策略。即每次随机选择一个合法的位置进行下子。此算法更多是用在一方超时,触发的算法。这种算法不太聪明,下子毫无章法,玩家与之对弈,也毫无竞技性和体验性可言。
- 基础算法。积分算法(比较简单,只考虑当前步的情况,只取当前步的最优位置)
这里给一下算法思路。下子时,不仅需要考虑连珠情况,也要考虑对手棋子的影响,我们分别称之为积极因素和消极因素。
- 分别设定一个积极因素评估数组score1 = { 7, 35, 800, 15000, 800000 },一个消极因素评估数组score2 = { 7, 15, 400, 2500, 100000 }。(为啥是这些数值,我未做深入探究,有同学感兴趣可做进一步研究)
- 定义一个方法count_point,用于计算棋盘中各个点的评估分数。从棋盘的初始点方向,依次计算每个点位的积极因素和消极因素。然后筛选出空白可下的点位,进行一个分数排序。然后在分数最高的队列里,随机一个点位,即为最佳位置。
如果想要调整不同水平的难度,可以考虑从前n个空白点位中随机一个。比如可以在分数排序之后,从前1/4个数中随机一个。这样AI的博弈水平就会降到一定程度。只是一个思路,大家也可以有不同的做法。
- 高阶算法。α-β剪枝算法 (是一种经典的博弈树搜索算法,它可以在搜索树中找到最优的下一步棋,并且具有很高的效率和搜索深度。它可以考虑接下来n步之后的棋面情形,来找出当下最优的下一步棋,比上面的积分算法更加的准确。)这里给出一个简单的α-β剪枝算法实例。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class RandomAITest : MonoBehaviour
{
private const int WINNING_SCORE = 1000000;
private const int THREE_IN_A_ROW = 100;
private const int TWO_IN_A_ROW = 10;
private const int ONE_IN_A_ROW = 1;
// 获取当前玩家的颜色
int GetCurrentPlayerColor(int[,] board)
{
int count = 0;
for (int i = 0; i < board.GetLength(0); i++)
{
for (int j = 0; j < board.GetLength(1); j++)
{
if (board[i, j] != 0)
{
count++;
}
}
}
return count % 2 == 0 ? 1 : 2;
}
// 使用α-β剪枝算法进行决策
Tuple<int, int> AlphaBetaSearch(int[,] board, int depth)
{
Tuple<int, int> bestMove = new Tuple<int, int>(-1, -1);
int alpha = int.MinValue;
int beta = int.MaxValue;
int playerColor = GetCurrentPlayerColor(board);
Tuple<int, int>[,] actions = GenerateActions(board);
foreach (Tuple<int, int> action in Shuffle(actions))
{
int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, playerColor);
int score = MinValue(newBoard, alpha, beta, depth - 1, playerColor);
if (score > alpha)
{
alpha = score;
bestMove = action;
}
}
return bestMove;
}
bool IsGameOver(int[,] board)
{
//TODO:判断游戏是否结束
return false;
}
// MaxValue函数
int MaxValue(int[,] board, int alpha, int beta, int depth, int playerColor)
{
if (depth == 0 || IsGameOver(board))
{
return Evaluate(board, playerColor);
}
int v = int.MinValue;
Tuple<int, int>[,] actions = GenerateActions(board);
foreach (Tuple<int, int> action in Shuffle(actions))
{
int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, playerColor);
int score = MinValue(newBoard, alpha, beta, depth - 1, playerColor);
if (score > v)
{
v = score;
}
if (v >= beta)
{
return v;
}
alpha = Math.Max(alpha, v);
}
return v;
}
// MinValue函数
int MinValue(int[,] board, int alpha, int beta, int depth, int playerColor)
{
if (depth == 0 || IsGameOver(board))
{
return Evaluate(board, playerColor);
}
int v = int.MaxValue;
Tuple<int, int>[,] actions = GenerateActions(board);
foreach (Tuple<int, int> action in Shuffle(actions))
{
int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, 3 - playerColor);
int score = MaxValue(newBoard, alpha, beta, depth - 1, playerColor);
if (score < v)
{
v = score;
}
if (v <= alpha)
{
return v;
}
beta = Math.Min(beta, v);
}
return v;
}
// 评估函数
int Evaluate(int[,] board, int player)
{
int score = 0;
// Check horizontal
score += EvaluateDirection(board, player, 0, 1); // right
score += EvaluateDirection(board, player, 0, -1); // left
// Check vertical
score += EvaluateDirection(board, player, 1, 0); // down
// Check diagonal
score += EvaluateDirection(board, player, 1, 1); // diagonal down-right
score += EvaluateDirection(board, player, 1, -1); // diagonal down-left
return score;
}
private int EvaluateDirection(int[,] board, int player, int deltaRow, int deltaCol)
{
int score = 0;
for (int row = 0; row < board.Rows; row++)
{
for (int col = 0; col < board.Columns; col++)
{
int consecutiveCount = 0;
int emptyCount = 0;
bool blocked = false;
for (int i = 0; i < 4; i++)
{
int currentRow = row + i * deltaRow;
int currentCol = col + i * deltaCol;
if (!board.IsValidPosition(currentRow, currentCol))
{
blocked = true;
break;
}
if (board[currentRow, currentCol] == player)
{
consecutiveCount++;
}
else if (board[currentRow, currentCol] == Player.None)
{
emptyCount++;
}
else
{
blocked = true;
break;
}
}
if (!blocked)
{
if (consecutiveCount == 4)
{
return WINNING_SCORE;
}
else if (consecutiveCount == 3 && emptyCount == 1)
{
score += THREE_IN_A_ROW;
}
else if (consecutiveCount == 2 && emptyCount == 2)
{
score += TWO_IN_A_ROW;
}
else if (consecutiveCount == 1 && emptyCount == 3)
{
score += ONE_IN_A_ROW;
}
}
}
}
return score;
}
// 生成所有可行的下子位置
Tuple<int, int>[,] GenerateActions(int[,] board)
{
int size = board.GetLength(0);
Tuple<int, int>[,] actions = new Tuple<int, int>[size, size];
int count = 0;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
if (IsValidMove(board, i, j))
{
actions[i, j] = new Tuple<int, int>(i, j);
count++;
}
}
}
Tuple<int, int>[,] result = new Tuple<int, int>[count, 1];
int index = 0;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
if (actions[i, j] != null)
{
result[index, 0] = actions[i, j];
index++;
}
}
}
return result;
}
// 更新棋盘
int[,] UpdateBoard(int[,] board, int x, int y, int color)
{
int size = board.GetLength(0);
int[,] newBoard = new int[size, size];
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
newBoard[i, j] = board[i, j];
}
}
newBoard[x, y] = color;
return newBoard;
}
// 洗牌算法
Tuple<int, int>[,] Shuffle(Tuple<int, int>[,] array)
{
System.Random rng = new System.Random();
int n = array.GetLength(0);
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
Tuple<int, int>[,] value = array[k, 0];
array[k, 0] = array[n, 0];
array[n, 0] = value;
}
return array;
}
}
它可以根据你调用AlphaBetaSearch方法时,传入depth深度的值,用于找出对于接下来depth步之内最有利的下一步位置。这种博弈算法在对AI水平要求比较高的情况下才使用,AI难度已经非常的高,且对性能要求也比较高。如果不是专门研究五子棋棋法的,不必使用这么高端的算法。用上面第二点的积分算法即可。
三、总结。
以上即时,从不同层次结构对五子棋AI算法的探讨和实现。希望给大家带来一定系统性的认识。