unity2018.4.8項目下載網址:https://pan.baidu.com/s/1JdN62plr433NGb8KN1eCUg
這兩天交了人工智能的期末大作業,花兩天時間查閱思考算法、編寫優化程序代碼以及製作界面,做了個智能五子棋人機對下系統。思路是結合了求棋盤各點位置的權重與博弈樹的一些改進,運行效果是電腦的水平和普通的人差不多,不過有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<ScorePos> {
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;
}
}