太长不看:点击这里查看源代码
上一篇我们做了一个自己的蒙特卡洛 AI,今天我们给我们的游戏增添一点趣味。
我们知道五子棋游戏具有对称性,包含旋转和轴对称总计 8 种对称。纵使一个局面有这么多的对称局面,而 AI 却不见得可以利用这种对称性。由于 AI 不懂对称性,总是下相同的着法,咱们可以在游戏中把局面进行对称变换,让 AI 下出来的着法看似有所不同,从而增加一点趣味。大致思路就是,把人类的着法进行变换,然后把 AI 思考出来的着法进行反变换。
首先,咱们要有变换函数。新建 src/Symmetry.js
文件,并粘贴如下内容:
export function Symmetry(w, h, symm) {
if (w < 1 || h < 1)
throw new RangeError("width and height must be greater than 1");
if (w !== h && symm > 3)
throw new RangeError(`unsupported symmetry for shape [${w},${h}]`);
else if (symm < 0 || symm > 7) throw new RangeError(`unsupported symmetry`);
const temp = [0, 0];
/**
*
* @param {number} x
* @param {number} y
* @param {number[]} [out]
*/
function identity(x, y, out) {
out = out || temp;
out[0] = x;
out[1] = y;
return out;
}
function flipY(x, y, out) {
return identity(x, h - 1 - y, out);
}
function flipX(x, y, out) {
return identity(w - 1 - x, y, out);
}
function rotate180(x, y, out) {
return identity(w - 1 - x, h - 1 - y, out);
}
function flipPrimaryDiagonal(x, y, out) {
return identity(h - 1 - y, w - 1 - x, out);
}
function flipSecondaryDiagonal(x, y, out) {
return identity(y, x, out);
}
function rotate90cw(x, y, out) {
return identity(y, w - 1 - x, out);
}
function rotate90ccw(x, y, out) {
return identity(h - 1 - y, x, out);
}
switch (symm) {
case 0:
return { map: identity, unmap: identity };
case 1:
return { map: flipX, unmap: flipX };
case 2:
return { map: flipY, unmap: flipY };
case 3:
return { map: rotate180, unmap: rotate180 };
case 4:
return { map: flipPrimaryDiagonal, unmap: flipPrimaryDiagonal };
case 5:
return { map: flipSecondaryDiagonal, unmap: flipSecondaryDiagonal };
case 6:
return { map: rotate90cw, unmap: rotate90ccw };
case 7:
return { map: rotate90ccw, unmap: rotate90cw };
default:
throw new Error(`unsupported symmetry(${symm})`);
}
}
然后,修改 src/Game.js
文件,把对称变换加上:
import { Symmetry } from "./Symmetry";
export class Game {
constructor(/* ... */) {
/* ... */
this._symmetry = Symmetry(
BOARD_SIZE,
BOARD_SIZE,
Math.trunc(Math.random() * 8)
);
}
/* ... */
start() {
if (this._started || this._stopped) return;
this._client.subscribe((s) => {
/* ... */
if (this._aiPlayer === s.ctx.currentPlayer) {
this._mcts.exec(this._root, this._state).then((result) => {
if (this._stopped) return;
const mov = this._unmapMove(result.bestChild.a);
this._client.moves.putStone(mov);
});
}
});
}
_mapMove(id) {
let y = Math.floor(id / BOARD_SIZE);
let x = id % BOARD_SIZE;
[x, y] = this._symmetry.map(x, y);
return y * BOARD_SIZE + x;
}
_unmapMove(id) {
let y = Math.floor(id / BOARD_SIZE);
let x = id % BOARD_SIZE;
[x, y] = this._symmetry.unmap(x, y);
return y * BOARD_SIZE + x;
}
_advance(mov) {
mov = this._mapMove(mov);
this._state.makeMove(mov);
/* ... */
}
}
好了,现在每次进行游戏,都会随机选择一种对称性,来体验一下效果吧。
本文介绍了如何利用五子棋的对称性增加游戏趣味。由于对称性,在训练神经网络时,我们也会将某一局面的对称局面输入给神经网络。所以,咱们添加的变换函数今后也能用上。接下来,就可以准备模型并开始训练了。