我们利用五子棋来做本次实验

先简单介绍一下五子棋吧

五子棋

两人对弈的策略型棋类游戏,是博弈游戏中较简单的一种玩法:双方分别使用黑白两色的棋子,轮流在棋盘空白交叉点上着子,先形成五子连线的一方获胜。

那很明显的,五子棋的规则、赢法都十分简单。那么我们的AI设计也是相对简单的

吗?

实现一个五子棋并不难做到,上一篇文章就已经写了,可以调回头去看看~


但是要实现AI功能的话,那是完全不一样了

反复在Google捣鼓代码,企图找到一个合适的方案,然而

全都是近千行代码啊!我顿时感觉这个AI,不太可能实现的了

他们使用了很多的逻辑机制,一个就几十行,而且还不止一个呢?

之后受到RainbowRoad1的五子棋的启发,开启了新的思路——权重

思路

我们要做一个简单&具有一定棋力的AI,这个意思就是说,他需要会选择合适的位置下子

并且逻辑需要尽可能的简单

会选择合适的位置

AI他需要找到当前局面“最优解”,那每个解的权重我们可以给每个位置评分来取得;出现了多个“最优解”的话,那么我们就随机选择其中的一个

我们可以根据连子的数量,进行对位置的评分。

头文件&变量

前面与我之前写的五子棋有蛮多可能相同的~

头文件

我们先引入必要的头文件

#include <windows.h> // 调用Windows API,防止运行时闪屏 
#include <conio.h> // 控制台输入输出,定义了通过控制台进行数据输入和数据输出的函数 
#include <stdio.h> // 标准库,定义了一些通用工具函数

变量

/* 定义解释 
 * W(Width)地图宽
 * S(Size)元素数量,也就是地图大小,长宽相乘
 * M(Mode)模式,总共分三种模式,第一种是PVP 均有玩家自行下子,第二个PVE 玩家下子后自动下子,第三个EVE 按一下自动下一子 
 * m(map)地图 
 * r(round)回合
 * c(char)字符,用于获取键盘输入 
 * z(zuobiao)坐标 
 * i,j,k,*p 临时变量 
 */ 
int W = 13, S, M = 0, *m, c = 1, r = 2, z, i, j, k, *p;

sum()

主要功能:返回一条线上遇到第一个与起点元素不同的地址

函数原型:int sum(int v, int l);

参数含义:int *v 起点地址 int l 方向/步进

返回参数:int * 终点地址

int *sum(int *v, int l) { 
/* @Main 返回一条线上遇到第一个与起点元素不同的地址
 * @Real int *sum(int *v, int l);
 */ 
    return *v - v[l] ? v + l : sum(v + l, l); 
}

down()

主要功能:下子,更新棋盘以及对局面评估

函数原型:void down(int v);

函数含义:int v 下子位置(简称目标)

void down(int v) { //定义
    for (m[v] = r ^= 3, i = 2; j = i % 3 - 1 + i / 3 * W, i < 6; ++i) { //交换回合,赋值
        p = sum(m + v, j), k = (p - sum(m + v, -j)) / j; //计算位置
        *p || (p[S * r] += 1 << k), p -= k * j, *p || (p[S * r] += 1 << k); //更新权重
        m[v + S] = m[v + S * 2] = 0, k > 5 && (r ^= 3, r += 4, i = 8); //五连判定
    }
}

交换回合,目标赋值(更新棋盘)

m[v] = r ^= 3

r ^= 3 | 使r的值在1和2之间交换

m[v] = r | 目标赋值

i < 6; ++i

对四条轴依次处理(也就是循环四次)

ai()

主要功能:模拟一次下子操作,选择权重最大的位置

void ai() {
    for (j = k = 0, i = S; i < 3 * S || (k = rand() % k, k || ++k, 0);) // 寻找对最大值
        j == m[i] && ++k, j < m[i] && (j = m[i], k = 1), ++i;
    for (i = S; k && i < 3 * S || (down(i % S), 0);)m[++i] == j && --k; // 随机选择一个值
}

我们通过i = S; i < 3 * S 遍历所有的评估值(从S到3 * S)包括两方的局面评估

(k = rand() % k, k || ++k, 0) 遍历结束时从1~k中选择一个值,那如果有多个最大值的话,选择其中的一个

如果和最大值相同,k自增1(++k) j == m[i] && ++k

j < m[i] && (j = m[i], k = 1), ++i如果大于最大值,重置j和k

之后我们又再次遍历i = S; k && i < 3 * S 寻找第k个最大值

m[++i] == j && --k 每次遇到最大值,k自减1,通过再次遍历寻找第k个最大值实现

main()

摆弄了这么久,终于,到了main的时候了!

首先初始化

for (W += 2, S = W * W, m = calloc(i = S * 3, 4), srand((int)m); i--;)
        i % W && -~i % W && !(i % S < W || (i + W) % S < W) || (m[i] = -1);

我们需要多一圈预定值-1(越界问题)所以W需要+2

m = calloc(i = S * 3, 4) 棋盘表面包括双方评估,分配3倍的空间

同时,i = S * 3 赋值给i,后面需要一次遍历

之后,请出我的老朋友,srand((int)m) 初始化随机种子,此时m的值不固定

之后主循环

for (z = S / 2, system("cls"); r < 4 && c - 27; c = _getch() & 95) {
        c - 87 || m[z - W] + 1 && (z -= W), c - 68 || m[++z] + 1 || --z;
        c - 83 || m[z + W] + 1 && (z += W), c - 65 || m[--z] + 1 || ++z;
        if (!c)M - 2 ? !m[z] && (down(z), r < 4 && M && (ai(), 0)) : ai();
        SetConsoleCursorPosition(GetStdHandle((DWORD)-11), (COORD) { 0 });
        for (i = W; i < S - W; ++i % W || _cprintf("%d\n", i / W - 1))
            SetConsoleTextAttribute(GetStdHandle((DWORD)-11), i - z ? 15 : 175),
            m[i] + 1 && printf(". \0○\0●" + m[i] * 3);
        for (i = 1, c - 81 || ++M; ++i < W;){
        _cprintf(" %c", 95 + i);
        }
        M %= 3, _cprintf("\n%s|", "PvP\0PvE\0EvE" + M * 4);
        _cputs(r & 1 ? "White" : "Black"), r < 4 || _cputs(" win!");
        printf("\nMade By Wibus & RainbowRoad1");
    }

SetConsoleCursorPosition等这里不解释,Google走起啦

r < 4达成五连时,r > 4 会跳出循环,达成5连的时候r会加上4

我们需要判断是否是EvE模式,如果是则直接执行ai()M - 2

最终代码

/*
 * 五子棋
 * Author: Wibus
 * Date: 2020.9.12
 */ 

#include <windows.h> // 调用Windows API,防止运行时闪屏 
#include <conio.h> // 控制台输入输出,定义了通过控制台进行数据输入和数据输出的函数 
#include <stdio.h> // 标准库,定义了一些通用工具函数 

/* 定义解释 
 * W(Width)地图宽
 * S(Size)元素数量,也就是地图大小,长宽相乘
 * M(Mode)模式,总共分三种模式,第一种是PVP 均有玩家自行下子,第二个PVE 玩家下子后自动下子,第三个EVE 按一下自动下一子 
 * m(map)地图 
 * r(round)回合
 * c(char)字符,用于获取键盘输入 
 * z(zuobiao)坐标 
 * i,j,k,*p 临时变量 
 */ 
int W = 13, S, M = 0, *m, c = 1, r = 2, z, i, j, k, *p;
int *sum(int *v, int l) { 
/* @Main 返回一条线上遇到第一个与起点元素不同的地址
 * @Real int *sum(int *v, int l);
 */ 
    return *v - v[l] ? v + l : sum(v + l, l); 
}

void down(int v) { //定义
    for (m[v] = r ^= 3, i = 2; j = i % 3 - 1 + i / 3 * W, i < 6; ++i) { //交换回合,赋值
        p = sum(m + v, j), k = (p - sum(m + v, -j)) / j; //计算位置
        *p || (p[S * r] += 1 << k), p -= k * j, *p || (p[S * r] += 1 << k); //更新权重
        m[v + S] = m[v + S * 2] = 0, k > 5 && (r ^= 3, r += 4, i = 8); //五连判定
    }
}

/* AI实现部分
 * @Skill 计算每格权重
 * @Time 每个棋即开始计算  
 */
void ai() {
    for (j = k = 0, i = S; i < 3 * S || (k = rand() % k, k || ++k, 0);) // 寻找对最大值
        j == m[i] && ++k, j < m[i] && (j = m[i], k = 1), ++i;
    for (i = S; k && i < 3 * S || (down(i % S), 0);)m[++i] == j && --k; // 随机选择一个值
}

int main() {
    for (W += 2, S = W * W, m = calloc(i = S * 3, 4), srand((int)m); i--;){
        i % W && -~i % W && !(i % S < W || (i + W) % S < W) || (m[i] = -1);
    }
    SetConsoleCursorInfo(GetStdHandle((DWORD)-11), &(CONSOLE_CURSOR_INFO) { 25 });
    for (z = S / 2, system("cls"); r < 4 && c - 27; c = _getch() & 95) {
        c - 87 || m[z - W] + 1 && (z -= W), c - 68 || m[++z] + 1 || --z;
        c - 83 || m[z + W] + 1 && (z += W), c - 65 || m[--z] + 1 || ++z;
        if (!c)M - 2 ? !m[z] && (down(z), r < 4 && M && (ai(), 0)) : ai();
        SetConsoleCursorPosition(GetStdHandle((DWORD)-11), (COORD) { 0 });
        for (i = W; i < S - W; ++i % W || _cprintf("%d\n", i / W - 1))
            SetConsoleTextAttribute(GetStdHandle((DWORD)-11), i - z ? 15 : 175),
            m[i] + 1 && printf(". \0○\0●" + m[i] * 3);
        for (i = 1, c - 81 || ++M; ++i < W;){
        _cprintf(" %c", 95 + i);
        }
        M %= 3, _cprintf("\n%s|", "PvP\0PvE\0EvE" + M * 4);
        _cputs(r & 1 ? "White" : "Black"), r < 4 || _cputs(" win!");
        printf("\nMade By Wibus & RainbowRoad1");
    }
}
最后修改:2020 年 10 月 05 日 05 : 28 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论

2 条评论

  1. 老弟一号

    来一发

    1. wibus
      @老弟一号

      这就来一发

域名代备案/服务器虚拟主机售卖/二级不死域名代制作/
老备案老域名/营业执照代办/QQ互联代申请/
海报宣传图设计/各类程序授权/各类业务

联系QQ:1032066668
点击联系