注:本文思路源於幕客網JS實現人機大戰五子棋
0. 效果
1. 代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Chess</title>
</head>
<body>
<style>
canvas {
display: block;
margin: 5px auto;
box-shadow: -2px -2px 2px #efefef, 5px 5px 5px #b9b9b9;
}
.information {
text-align: center;
}
button {
background: dodgerblue;
color: white;
border: 0;
}
</style>
<canvas id="chess" width="450px" height="450px"></canvas>
<div class="information">
<div class="operator">
<button id="order" onclick="changeOrder()">先後</button>
<button id="mode" onclick="changeMode()">模式</button>
<button id="replay" onclick="replay()">重玩</button>
</div>
<div id="records">勝 0 負 0</div>
</div>
<script>
/* 遊戲數據配置 */
const size = 450; // 棋盤尺寸
const lineCounts = 15; // 棋盤行數
const rowWidth = size / lineCounts; // 棋盤行寬
const margin = rowWidth / 2; // 棋盤邊界
const pieceRadius = rowWidth / 2; // 棋子半徑
const offset = 2; // 漸變偏移
const conJunctions = 5; // 五子棋,五子連珠
var order = true; // 黑先; 白後
var mode = false; // Player Vs Player; Player Vs Computer;
var player = order;
var ord = {
true: "先手執黑",
false: "後手執白"
}
var mod = {
true: "人人對戰",
false: "人機對戰"
}
var records = {
win: 0,
lose: 0
};
// winMatrix;
var winMatrix = [];
var counts = 0;
// black and white
var black = [];
var white = [];
// pieceMatrix;
var pieceMatrix = [];
// 棋盤,canvas繪製棋盤和棋子
var chess = document.getElementById("chess");
var context = chess.getContext("2d");
/* ----- */
document.getElementById("order").innerText = ord[order];
document.getElementById("mode").innerText = mod[mode];
start();
/* ----- */
function changeOrder() {
order = !order;
document.getElementById("order").innerText = ord[order];
start();
}
function changeMode() {
mode = !mode;
// 清零記錄
records = {
win: 0,
lose: 0
};
updateRecords();
if (mode) { // 人人對戰不計分
document.getElementById("records").style.display = "none";
} else {
document.getElementById("records").style.display = "block";
}
document.getElementById("mode").innerText = mod[mode];
start();
}
function initData() {
winMatrix = [];
counts = 0;
black = [];
white = [];
pieceMatrix = [];
// 初始化
for (let i = 0; i < lineCounts; i++) {
winMatrix[i] = [];
for (let j = 0; j < lineCounts; j++) {
winMatrix[i][j] = [];
}
}
// 橫向
for (let i = 0; i < lineCounts; i++) {
for (let j = 0; j <= lineCounts - conJunctions; j++) {
// 選擇到合適的起始點,加5次即可
for (let k = 0; k < conJunctions; k++) {
winMatrix[i][j + k][counts] = true;
}
counts++;
}
}
// 豎向
for (let i = 0; i <= lineCounts - conJunctions; i++) {
for (let j = 0; j < lineCounts; j++) {
// 選擇到合適的起始點,加5次即可
for (let k = 0; k < conJunctions; k++) {
winMatrix[i + k][j][counts] = true;
}
counts++;
}
}
// 正斜向[\]
for (let i = 0; i <= lineCounts - conJunctions; i++) {
for (let j = 0; j <= lineCounts - conJunctions; j++) {
// 選擇到合適的起始點,加5次即可
for (let k = 0; k < conJunctions; k++) {
winMatrix[i + k][j + k][counts] = true;
}
counts++;
}
}
// 反斜向[/]
for (let i = 0; i <= lineCounts - conJunctions; i++) {
for (let j = conJunctions - 1; j < lineCounts; j++) {
// 選擇到合適的起始點,加5次即可
for (let k = 0; k < conJunctions; k++) {
winMatrix[i + k][j - k][counts] = true;
}
counts++;
}
}
// 黑白棋贏法統計
for (let i = 0; i < counts; i++) {
black[i] = 0;
white[i] = 0;
}
// 落子矩陣
for (let i = 0; i < lineCounts; i++) {
pieceMatrix[i] = [];
for (let j = 0; j < lineCounts; j++) {
pieceMatrix[i][j] = true;
}
}
}
// 開始遊戲
function start() {
// 初始化
initData();
// 繪盤
drawChessBoard();
// 電腦先手隨機內部落子
if (!mode && !order) { // 人機且電腦先手
let x = Math.floor(Math.random() * conJunctions) + conJunctions;
let y = Math.floor(Math.random() * conJunctions) + conJunctions;
oneStep(x, y, true);
player = false; // 白
}
// 人落子
chess.addEventListener("click", clickChessBoard);
}
// 重玩
function replay() {
// 清盤
context.clearRect(0, 0, size, size);
start();
}
// 繪製棋盤
function drawChessBoard() {
// 背景色
context.fillStyle = "#CDAA7D";
context.fillRect(0, 0, size, size);
// 棋盤線條色
context.strokeStyle = "#bfbfbf";
// 劃線
context.beginPath();
for (let i = 0; i < lineCounts; i++) {
let position = margin + i * rowWidth;
// row lines
context.moveTo(margin, position);
context.lineTo(size - margin, position);
context.stroke();
// column lines
context.moveTo(position, margin);
context.lineTo(position, size - margin);
context.stroke();
}
context.closePath();
}
// 繪製棋子
function drawPiece(i, j, side) {
let x = margin + i * rowWidth;
let y = margin + j * rowWidth;
// arc
context.beginPath();
context.arc(x, y, pieceRadius, 0, 2 * Math.PI);
// gradient
let gradient = context.createRadialGradient(
x + offset, y - offset, pieceRadius, x + offset, y - offset, 0);
if (side) { // 黑
gradient.addColorStop(0, "#0a0a0a");
gradient.addColorStop(1, "#636766");
} else { // 白
gradient.addColorStop(0, "#d1d1d1");
gradient.addColorStop(1, "#f9f9f9");
}
context.fillStyle = gradient;
context.fill();
context.closePath();
}
// 遊戲結束
function gameOver(side) {
let str = side ? "黑" : "白";
if (order == side) { // 先手執黑勝 或者 後手執白勝
records.win++;
} else {
records.lose++;
}
updateRecords();
// 設置字體
context.font = "4em bold 黑體";
// 設置水平對齊方式
context.textAlign = "center";
// 設置垂直對齊方式
context.textBaseline = "middle";
// 設置顏色
context.strokeStyle = "red";
context.fillStyle = "green";
// 繪製文字(參數:要寫的字,x座標,y座標)
context.strokeText(str + " 勝!", size / 2, size / 2);
context.fillText(str + " 勝!", size / 2, size / 2);
forbidClick();
}
// 更新記錄
function updateRecords() {
document.getElementById("records").innerText = `勝 ${records.win} 負 ${records.lose}`;
}
// 禁止落子,移除事件監聽
function forbidClick() {
chess.removeEventListener("click", clickChessBoard);
}
// 落子
function oneStep(i, j, side) {
// 落子
drawPiece(i, j, side);
// 更新棋子矩陣
pieceMatrix[i][j] = false;
// 勝負判斷邏輯
for (let k = 0; k < counts; k++) {
if (winMatrix[i][j][k]) {
if (side) { // 黑
white[k] = 6; // 被黑棋落子的贏法,白棋永遠不可能贏了
if (++black[k] == 5) {
gameOver(true);
return false;
};
} else { // 白
black[k] = 6;
if (++white[k] == 5) {
gameOver(false);
return false;
};
}
}
}
return true;
}
// 點擊棋盤事件
function clickChessBoard(e) {
let x = e.offsetX; // 相對於chess的偏移
let y = e.offsetY;
// 尋找點擊範圍
let i = Math.floor(x / rowWidth);
let j = Math.floor(y / rowWidth);
// 有子不得落子
if (pieceMatrix[i][j]) {
// 落子未終局(沒有一方勝利結束遊戲)
if (oneStep(i, j, player)) {
// 黑白交替走子
if (mode) {
player = !player;
} else {
computerAI(player);
}
}
}
}
/**
* 計算機AI實現
*/
// 攻防策略分數定義
function deffence(num) {
let pts = {
0: 0,
1: 200,
2: 400,
3: 2000,
4: 10000
}
if (num > 4) {
return 0;
} else {
return pts[num];
}
}
function attack(num) {
let pts = {
0: 0,
1: 210,
2: 420,
3: 2100,
4: 20000
}
if (num > 4) {
return 0;
} else {
return pts[num];
}
}
// AI
function computerAI(player) {
// 判斷黑白方
let opponent = black, self = white;
if (!player) {
opponent = white;
self = black;
}
// 得分數組
let person = [];
let com = [];
// 初始化
for (let i = 0; i < lineCounts; i++) {
person[i] = [];
com[i] = [];
for (let j = 0; j < lineCounts; j++) {
person[i][j] = 0;
com[i][j] = 0;
}
}
// 判斷各個落點得分
for (let i = 0; i < lineCounts; i++) {
for (let j = 0; j < lineCounts; j++) {
if (pieceMatrix[i][j]) { // 可落子
for (let k = 0; k < counts; k++) {
if (winMatrix[i][j][k]) { // 可贏
person[i][j] += deffence(opponent[k]); // 防守
com[i][j] += attack(self[k]); // 進攻
}
}
}
}
}
// 尋找最優點
var max = 0; // 最高分
var u = 0, v = 0; // 最優落子點
for (let i = 0; i < lineCounts; i++) {
for (let j = 0; j < lineCounts; j++) {
// 防守最高分
if (person[i][j] > max) {
max = person[i][j];
u = i;
v = j;
}
// 進攻最高分
if (com[i][j] > max) {
max = com[i][j];
u = i;
v = j;
}
}
}
// 電腦落子
oneStep(u, v, !player);
}
</script>
</body>
</html>
2. 後記
clearRect()
清除畫布fill
stroke
填充和描邊createRadialGradient(x1,y1,x2,y2)
addColorStop(0, color)
徑向漸變beginPath()
closePath
開閉路徑