这两天交了人工智能的期末大作业,花两天时间查阅思考算法、编写优化程序代码以及制作界面,做了个智能五子棋人机对下系统。思路是结合了求棋盘各点位置的权重与博弈树的一些改进,运行效果是电脑的水平和普通的人差不多,不过有1%左右的概率落子失误(不知道是因为没想那么多呢还是因为想的太多了呢)。速度也挺快,一两秒就能计算出结果,我看它不是很卡也就没有再剪枝优化。毕竟是五子棋,不同于象棋围棋,五子相连就赢了,其实也不用怎么搜索太多步,差不多黑白来回7次就已经很好了,广度的话大概4-8个最好。下面是运行界面截图:
开发工具是Unity2018.4.8,语言是C#,项目代码量800行左右,下面是主要类的代码:
Data类:
using UnityEngine;
public static class Data {
private static int[,] map = new int[15, 15];
public static int[,] Map { get => map; set => map = value; }
public static void ResetMap() {
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
map[i, j] = 0;
}
}
}
}
GameTree类:
//博弈树结点
public class GameTree {
public GameTree[] child;
public GameTree father;
public int depth;
public int posi;
public int posj;
public int score;
public int chess;
public int solve;
public override string ToString() {
string ans = "";
ans += "深度:" + depth + "\n";
ans += "位置:" + (posi - 7) + "," + (posj - 7) + "\n";
ans += "分数:" + score + "\n";
if (chess == 1) ans += "黑棋\n";
else if (chess == 2) ans += "白棋\n";
ans += "隶属于方案:" + solve;
return ans;
}
}
Control类:(主要逻辑类)
using System;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
//状态枚举
public enum State {
Ready,
Black,
White,
BlackWin,
WhiteWin
}
public struct ScorePos {
public int score;
public int posi;
public int posj;
}
//ScorePos类比较
class ScorePosComparer : IComparer {
public int Compare(ScorePos x, ScorePos y) {
if (x.score < y.score) return 1; //从大到小排序
else if (x.score > y.score) return -1;
else return 0;
}
}
public class Control : MonoBehaviour {
#region 变量
public Transform BlackChess, WhiteChess; //棋子
public GameObject Frame; //方框
public GameObject Float; //游戏结束弹窗
public Text Message; //消息
public GameObject BlackIcon, WhiteIcon; //棋子图标
public GameObject StartButton, StartButton1;//开始按钮
public Text BlackTime, WhiteTime; //黑、白方剩余时间
public Toggle Mode; //电脑模式
private float computerTime; //电脑思考时间
private int totalTime; //总时间
private State state; //游戏状态
private float blackCount, whiteCount; //双方的计时
private float thinkCount; //思考时间计时
private bool frameBig; //方框变化方向
private bool ComputerMode; //电脑智能模式
private int width = 5; //每层向下拓展的宽度
private int depth = 7; //总层数(必须是奇数)
ScorePosComparer compare; //比较规则
#endregion
private void Start() {
Data.ResetMap();
computerTime = 0.19f;
totalTime = 1800;
ComputerMode = false;
state = State.Ready;
blackCount = whiteCount = 0;
BlackTime.text = GetTime(blackCount);
WhiteTime.text = GetTime(whiteCount);
thinkCount = 0;
frameBig = true;
compare = new ScorePosComparer();
}
private void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
OnClickReset();
}
if (Mode.isOn != ComputerMode) {
ComputerMode = Mode.isOn;
}
if (state == State.Black) {
//玩家点击下棋
blackCount += Time.deltaTime;
BlackTime.text = GetTime(blackCount);
if (Input.GetMouseButtonDown(0)) { //按下
if (PutChess(Input.mousePosition, BlackChess)) { //可下棋
state = State.White;
if (CheckWin()) { //是否胜利
state = State.BlackWin;
}
}
}
UpdateFrame();
}
else if (state == State.White) {
//电脑下棋
whiteCount += Time.deltaTime;
thinkCount += Time.deltaTime;
if (thinkCount >= computerTime) { //等待时间
//电脑计算
thinkCount = 0;
if (Calculate(WhiteChess)) { //有解
if (CheckWin()) { //是否胜利
state = State.WhiteWin;
}
else {
state = State.Black;
}
}
else {
state = State.BlackWin;
}
whiteCount += 2.5f; //算法运行时间补偿
}
WhiteTime.text = GetTime(whiteCount);
UpdateFrame();
}
else if (state == State.BlackWin) {
//玩家胜
Float.SetActive(true);
Message.text = "你赢了!";
Message.color = new Color(0, 1, 0, 1);
WhiteIcon.SetActive(false);
}
else if (state == State.WhiteWin) {
//电脑胜
Float.SetActive(true);
Message.text = "你输了!";
Message.color = new Color(1, 0, 0, 1);
BlackIcon.SetActive(false);
}
}
//生成棋
private void CreateChess(int i, int j, Transform c) {
if (c.name == "BlackChess") {
Data.Map[i, j] = 1;
BlackIcon.SetActive(false);
WhiteIcon.SetActive(true);
}
if (c.name == "WhiteChess") {
Data.Map[i, j] = 2;
WhiteIcon.SetActive(false);
BlackIcon.SetActive(true);
}
float a = 1.32f;
Vector3 pos = new Vector3();
pos.x = (i - 7) * a;
pos.y = (j - 7) * a;
pos.z = 1;
Instantiate(c, pos, new Quaternion());
Frame.SetActive(true);
Frame.transform.position = pos;
}
//玩家下棋
private bool PutChess(Vector2 pos, Transform c) {
Vector2 worldPos = Camera.main.ScreenToWorldPoint(pos);
worldPos /= 1.32f;
worldPos += new Vector2(7, 7);
int i = Mathf.RoundToInt(worldPos.x);
int j = Mathf.RoundToInt(worldPos.y);
if (i < 0 || j < 0 || i > 14 || j > 14) return false; //出界
if (Data.Map[i, j] != 0) return false; //已有棋子
CreateChess(i, j, c);
return true;
}
//电脑计算
private bool Calculate(Transform c) {
int[,] blackScore = new int[15, 15];
int[,] whiteScore = new int[15, 15];
int maxScore = -1;
int posi = 0, posj = 0;
if (!ComputerMode) { //非智能模式(计算每点权重)
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
if (Data.Map[i, j] == 0) {
GetScore(i, j, ref blackScore[i, j], ref whiteScore[i, j], Data.Map); //引用参数
if (maxScore < blackScore[i, j] + whiteScore[i, j]) {
maxScore = blackScore[i, j] + whiteScore[i, j];
posi = i;
posj = j;
}
}
}
}
if (maxScore != -1) { //有解
CreateChess(posi, posj, c);
return true;
}
}
else { //智能模式(博弈树+αβ剪枝寻最优)
GetAIScore();
return true;
}
return false;
}
//检查是否胜利
public bool CheckWin() {
int i = Mathf.RoundToInt((Frame.transform.position.x / 1.32f) + 7); //最后落子位置
int j = Mathf.RoundToInt((Frame.transform.position.y / 1.32f) + 7);
int chess = Data.Map[i, j]; //最后落子颜色(1:黑,2:白)
//横向检查
int a = 0, b = 0; //两端连续棋子数量
for (int k = i - 1; k >= 0; k--) {
if (Data.Map[k, j] == chess) {
a++;
}
else {
break;
}
}
for (int k = i + 1; k < 15; k++) {
if (Data.Map[k, j] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//纵向检查
a = b = 0;
for (int k = j - 1; k >= 0; k--) {
if (Data.Map[i, k] == chess) {
a++;
}
else {
break;
}
}
for (int k = j + 1; k < 15; k++) {
if (Data.Map[i, k] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//斜左下
a = b = 0;
for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (Data.Map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (Data.Map[m, n] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//斜右下
a = b = 0;
for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (Data.Map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (Data.Map[m, n] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
return false;
}
public bool CheckWin(int i, int j, int[,] map) {
int chess = map[i, j]; //最后落子颜色(1:黑,2:白)
//横向检查
int a = 0, b = 0; //两端连续棋子数量
for (int k = i - 1; k >= 0; k--) {
if (map[k, j] == chess) {
a++;
}
else {
break;
}
}
for (int k = i + 1; k < 15; k++) {
if (map[k, j] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//纵向检查
a = b = 0;
for (int k = j - 1; k >= 0; k--) {
if (map[i, k] == chess) {
a++;
}
else {
break;
}
}
for (int k = j + 1; k < 15; k++) {
if (map[i, k] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//斜左下
a = b = 0;
for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
//斜右下
a = b = 0;
for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
if (a + b + 1 >= 5) {
return true;
}
return false;
}
//计算分数
private void GetScore(int i, int j, ref int black, ref int white, int[,] map) {
//若放黑色
int chess = 1;
int s1 = 0, s2 = 0, s3 = 0, s4 = 0; //四个方向的棋子的数量
//横向检查
int a = 0, b = 0; //两端连续棋子数量
for (int k = i - 1; k >= 0; k--) {
if (map[k, j] == chess) {
a++;
}
else {
break;
}
}
for (int k = i + 1; k < 15; k++) {
if (map[k, j] == chess) {
b++;
}
else {
break;
}
}
s1 = a + b + 1;
//纵向检查
a = b = 0;
for (int k = j - 1; k >= 0; k--) {
if (map[i, k] == chess) {
a++;
}
else {
break;
}
}
for (int k = j + 1; k < 15; k++) {
if (map[i, k] == chess) {
b++;
}
else {
break;
}
}
s2 = a + b + 1;
//斜左下
a = b = 0;
for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
s3 = a + b + 1;
//斜右下
a = b = 0;
for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
s4 = a + b + 1;
black = GetScore(s1, -1) + GetScore(s2, -1) + GetScore(s3, -1) + GetScore(s4, -1);
//若放白色
chess = 2;
s1 = s2 = s3 = s4 = 0; //四个方向的棋子的数量
//横向检查
a = b = 0; //两端连续棋子数量
for (int k = i - 1; k >= 0; k--) {
if (map[k, j] == chess) {
a++;
}
else {
break;
}
}
for (int k = i + 1; k < 15; k++) {
if (map[k, j] == chess) {
b++;
}
else {
break;
}
}
s1 = a + b + 1;
//纵向检查
a = b = 0;
for (int k = j - 1; k >= 0; k--) {
if (map[i, k] == chess) {
a++;
}
else {
break;
}
}
for (int k = j + 1; k < 15; k++) {
if (map[i, k] == chess) {
b++;
}
else {
break;
}
}
s2 = a + b + 1;
//斜左下
a = b = 0;
for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
s3 = a + b + 1;
//斜右下
a = b = 0;
for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
if (map[m, n] == chess) {
a++;
}
else {
break;
}
}
for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
if (map[m, n] == chess) {
b++;
}
else {
break;
}
}
s4 = a + b + 1;
white = GetScore(s1, 1) + GetScore(s2, 1) + GetScore(s3, 1) + GetScore(s4, 1);
}
//智能计算分数
private void GetAIScore() {
GameTree root = new GameTree();
root.depth = 1;
root.chess = 1; //黑的刚走完
root.score = 0;
GameTree(Data.Map, root);
//递归求出分数
UpdateTreeScore(root);
int ans = 0;
int maxScore = 0;
for (int i = 0; i < 0; i++) {
if (root.child[i].score > maxScore) {
maxScore = root.child[i].score;
ans = i;
}
}
int posi = root.child[ans].posi;
int posj = root.child[ans].posj;
CreateChess(posi, posj, WhiteChess);
}
//博弈树递归
private void GameTree(int[,] map, GameTree father) {
ScorePos[] sp = new ScorePos[225];
//筛选前几个优良方案
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
if (map[i, j] == 0) {
int m = 0, n = 0;
GetScore(i, j, ref m, ref n, map); //引用参数
sp[i * 15 + j].score = m + n;
sp[i * 15 + j].posi = i;
sp[i * 15 + j].posj = j;
}
}
}
Array.Sort(sp, compare);
father.child = new GameTree[width];
for (int i = 0; i < width; i++) {
GameTree point = new GameTree();
point.child = new GameTree[width];
father.child[i] = point; //父子关系
point.father = father;
point.depth = father.depth + 1; //博弈树深度
point.posi = sp[i].posi; //落子位置
point.posj = sp[i].posj;
point.chess = father.chess % 2 + 1; //交替下棋
//计算分数
if (father.depth % 2 == 0) { //偶数层(我方)
point.score = sp[i].score;
}
else { //奇数层(对手)
point.score = -sp[i].score;
}
if (father.score < -6000 || father.score > 6000) {
point.score = father.score; //一旦一方胜利,便没有必要再算下去(防止局势扭转)
}
else {
point.score += father.score;//累加父层
}
//更新解决方案
if (point.depth == 2) { //节点隶属的解决方案
point.solve = i;
}
else {
point.solve = father.solve;
}
//拓展新地图
int[,] map1 = new int[15, 15];
for (int j = 0; j < 15; j++) {
for (int k = 0; k < 15; k++) {
map1[j, k] = map[j, k];
}
}
map1[point.posi, point.posj] = point.chess;
if (point.depth < depth) {
GameTree(map1, point);
}
//层数上限,开始收尾
else {
int maxScore = -1;
GameTree point1 = new GameTree();
for (int i1 = 0; i1 < 15; i1++) {
for (int j1 = 0; j1 < 15; j1++) {
if (map1[i1, j1] == 0) {
int m = 0, n = 0;
GetScore(i1, j1, ref m, ref n, map1); //引用参数
if (maxScore < m + n) {
maxScore = m + n;
}
}
}
}
//更新point分数
if (point.score > 6000 || point.score < -6000) {
point1.score = point.score;
}
else {
point1.score = point.score + maxScore;
}
point.score = point1.score;
//print(point.ToString());
}
}
}
//更新树所有节点的分数
private int UpdateTreeScore(GameTree p) {
if (p.depth == depth) { //末端
return p.score;
}
if (p.depth % 2 == 0) { //偶数层,求最小子分数
int minScore = 100000;
for (int i = 0; i < width; i++) {
if (p.child[i].score < minScore) {
minScore = UpdateTreeScore(p.child[i]);
}
}
return minScore;
}
else { //奇数层,求最大子分数
int maxScore = -100000;
for (int i = 0; i < width; i++) {
if (p.child[i].score > maxScore) {
maxScore = UpdateTreeScore(p.child[i]);
}
}
return maxScore;
}
}
//分数计算公式
public int GetScore(int x, int m) {
int ans = 0;
if (m == 1) {
if (x == 1) ans += 1;
else if (x == 2) ans += 10;
else if (x == 3) ans += 100;
else if (x == 4) ans += 1000;
else ans += 10000;
}
else if (m == -1) {
if (x == 1) ans += 1;
else if (x == 2) ans += 8;
else if (x == 3) ans += 64;
else if (x == 4) ans += 512;
else ans += 4096;
}
return ans;
}
//方框变大变小
private void UpdateFrame() {
if (frameBig) {
Frame.transform.localScale += new Vector3(0.003f, 0.003f, 0);
}
else {
Frame.transform.localScale -= new Vector3(0.003f, 0.003f, 0);
}
if (Frame.transform.localScale.x > 0.7f) {
frameBig = false;
}
if (Frame.transform.localScale.x < 0.5f) {
frameBig = true;
}
}
//重置
public void OnClickReset() {
SceneManager.LoadScene(0);
}
//先手游戏
public void OnClickStart() {
state = State.Black;
StartButton.SetActive(false);
StartButton1.SetActive(false);
BlackIcon.SetActive(true);
}
//后手游戏
public void OnClickStart1() {
CreateChess(7, 7, WhiteChess);
OnClickStart();
}
//获取时间
private string GetTime(float n) {
float n1 = totalTime - n;
if (n1 <= 0) { //时间到
if (state == State.Black) {
state = State.WhiteWin;
}
if (state == State.White) {
state = State.BlackWin;
}
return "00:00.00";
}
string min, sec, msec;
if ((int)n1 / 60 < 10) {
min = "0";
}
else {
min = "";
}
min += (int)n1 / 60 + "";
if ((int)n1 % 60 < 10) {
sec = "0";
}
else {
sec = "";
}
sec += (int)n1 % 60 + "";
if ((int)(n1 * 100) % 100 < 10) {
msec = "0";
}
else {
msec = "";
}
msec += (int)(n1 * 100) % 100 + "";
return min + ":" + sec + "." + msec;
}
}