一、前言
实现一个五子棋游戏, 简要分析其原理, 页面并没有很花哨, 原理搞懂了, 后面的就是很轻松的事了,无非是加一个棋盘背景,然后每个棋子改成圆形, 然后跟棋盘的十字中心交汇处对好。
二、分析
首先先分析一下,实现一个五子棋需要几步, 首先需要确定用什么样的数据结构, 鉴于对数组的使用更熟悉, 我们选用二维数组, 第一层数组代表行,内部代表列 然后是判断怎么样判断收集的点击坐标是横向或者纵向连续 然后是有点击位置后,不能重复点击
三、实现
1、生成棋盘二维数组
[
[[],[]],
[[],[]]
]
这个就代表两行两列了, 先写个方法实现根据传入数值计算行列
function generator(rlength, clength) {
let array = [];
for (let i = 0; i < rlength; i++) {
array[i] = [];
for (let j = 0; j < clength; j++) {
array[i].push([]);
}
}
return array;
}
const piecesArr = generator(8, 8);
2、渲染到页面上
接下来, 我们就是渲染到页面上了,再写个渲染的方法
function renderPieces() {
const pieces_box = document.querySelector("#pieces_box");
let renderText = "";
piecesArr.map((items, rindex) => {
renderText += '<div class="outer">';
items.map((item, cindex) => {
renderText += `<div class="inner" id="${[rindex, cindex]}"></div>`;
});
renderText += "</div>";
});
pieces_box.innerHTML = renderText;
}
我们的页面结构和css是这样的
<style>
.outer {
display: flex;
}
.inner {
width: 100px;
height: 100px;
background-color: #ff0;
border: 1px solid #f00;
cursor: pointer;
}
</style>
<body>
<h2>实现一个五子棋游戏</h2>
<div id="pieces_box"></div>
</body>
3、增加点击事件
然后我们的页面视觉就出来了, 然后就是处理点击了。 我们通过事件委托的方式,将最外层的元素进行绑定
let currentColor = "#fff";
pieces_box.addEventListener("click", (e) => {
if (e.target.closest(".inner") && !e.target.style.background) {
currentColor = currentColor === "#fff" ? "#000" : "#fff";
e.target.style.background = currentColor;
const currentPos = e.target.id.split(",").map(Number);
checkResult(currentPos, currentColor);
}
});
我们通过绑定后, 根据点击的元素是哪个,获取位置坐标和颜色背景, 和黑白颜色的切换
5、实现横纵方向上连成五子
接下来,我们就要去checkResult中去识别横纵坐标,不同颜色的棋子是否存在5个连续的点了, 我们先实现简单的,横向或者纵向是不是存在5个连续点,我们先写一个储存结果的集合,用来储存每个点击点, 然后我们通过对象key的唯一性, 让x为key, value 为数组, 去判断横坐标上是不是存在 value 中是否有连续5个点, 然后同理,在让y坐标为key,检查value种是否有5个点连续
const savePos = {
white: {
row: {},
col: {},
},
black: {
row: {},
col: {},
},
};
如这个数据结构, 我们标注了黑色白色的落子,标注了横和纵, 然后把坐标写入进去
// 最重要的是如何判断赢, 连成五子,在各个方向上, 判断时机就是每个子落下的时候
function checkResult(currentPos, currentColor) {
const color = currentColor === "#fff" ? "white" : "black";
const [x, y] = currentPos;
// 思路判断 横竖比较简单, 可以通过横坐标为key, 数组值为纵坐标,检查是否连续成五个
Array.isArray(savePos[color].row[x])
? savePos[color].row[x].push(y)
: (savePos[color].row[x] = [y]);
Array.isArray(savePos[color].col[y])
? savePos[color].col[y].push(x)
: (savePos[color].col[y] = [x]);
console.log(savePos, "savePos");
// 检测白子
checkISLinkSuccess(savePos["white"], "white");
// 检测黑子
checkISLinkSuccess(savePos["black"], "black");
}
如图我们就构建了这样的数据结构, 然后现在我们要做的就是去检查 value的数组中是不是有连续五个数就行
方法如下:
// 检查是否存在5个连续的数字存在
function isConsecutive(arr) {
// 先对数组进行排序
arr.sort(function (a, b) {
return a - b;
});
// 检查数组中的每个元素是否 存在依次递增 5个 的连续数字
for (let i = 0; i <= arr.length - 5; i++) {
if (
arr[i] + 1 === arr[i + 1] &&
arr[i + 1] + 1 === arr[i + 2] &&
arr[i + 2] + 1 === arr[i + 3] &&
arr[i + 3] + 1 === arr[i + 4]
) {
return true;
}
}
return false;
}
到此, 我们的横向和纵向检查是否连成五个棋子的逻辑就完美实现了。
6、 实现横向、纵向、 斜角方向、反斜角方向连成五子
上面的实现是比较好理解的, 不需要判断落子的位置和棋盘的位置关系, 只要发现不管是横向还是纵向,满足数组中包含连续5个数字时我们就认为连成了五子。
但是有一个问题, 横向纵向比较容易,但是斜角方向上确实比较难实现, 所以我们还是要重新思考一种更通用的方式。 那就是抽离一下业务, 我们通过落子的坐标去不断循环查询几个方向的其他坐标点, 然后给其他的坐标点判断是否在棋盘内,并且是否内部有颜色值, 当棋子的颜色值和落子的颜色一致时才算是有效的,然后不断通过调整+-1 去进行搜索。
const isWin = (board, point, color) => {
return (
// 判断横向
horJudgement(board, point, color) ||
// 判断纵向
verJudegment(board, point, color) ||
// 判断斜角
slashJudgement(board, point, color) ||
// 判断反斜角
backslashJudgement(board, point, color)
);
};
于是我们的落子的判断改为调用 isWin 函数, 然后调用方法去查询不同方向是否存在 连成五子
const horJudgement = createJudgement(
([i, j]) => [i, j - 1],
([i, j]) => [i, j + 1]
);
const verJudegment = createJudgement(
([i, j]) => [i - 1, j],
([i, j]) => [i + 1, j]
);
const slashJudgement = createJudgement(
([i, j]) => [i + 1, j - 1],
([i, j]) => [i - 1, j + 1]
);
const backslashJudgement = createJudgement(
([i, j]) => [i - 1, j - 1],
([i, j]) => [i + 1, j + 1]
);
// 通过闭包函数,简化代码,在调用方向判断前,先传入两个坐标移动函数
function createJudgement(p1Movement, p2Movement) {
return function (point) {
let count = 1;
let p1 = p1Movement(point);
let p2 = p2Movement(point);
while (1) {
let p1Changed = false,
p2Chanhed = false;
if (isValid(p1)) {
count++;
p1 = p1Movement(p1);
p1Changed = true;
}
if (isValid(p2)) {
count++;
p2 = p2Movement(p2);
p2Chanhed = true;
}
if (count >= 5) {
return true;
}
if (!p1Changed && !p2Chanhed) {
return false;
}
}
};
}
我们通过抽象函数, 在执行方向判断时,只需要传入 board、point、color, 去循环查看, 计算count符合五次要求及返回true
那么接下来就是实现 isValid 函数
function isValid(board, point, color) {
const ROWS = board.length;
const COLS = board[0].length;
const [x, y, c] = point;
const cur = board[point[0]]?.[point[1]];
// point在棋盘之内 && point 的颜色和落子颜色一致
if (color === cur?.[0] && x >= 0 && y >= 0 && x < ROWS && y < COLS) {
return true;
} else {
return false;
}
}
这个函数就比较简单了, 主要是判断point在棋盘之内 && point 的颜色和落子颜色一致, 但是注意要在点击时将棋盘的落子位置增加上颜色, 才可以在这里进行判断
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
.outer {
display: flex;
}
.inner {
width: 100px;
height: 100px;
background-color: #ff0;
border: 1px solid #f00;
cursor: pointer;
}
</style>
<body>
<h2>实现一个五子棋游戏</h2>
<div id="pieces_box"></div>
</body>
<script>
// 创建一个五子棋的类, 控制五子棋的 长款
// 1、 选择数据类型 数组 =》 比如 生成 2行5列的二维数组
// [
// [], [], [], [], []
// [], [], [], [], []
// ]
const pieces_box = document.querySelector("#pieces_box");
function generator(rlength, clength) {
let array = [];
for (let i = 0; i < rlength; i++) {
array[i] = [];
for (let j = 0; j < clength; j++) {
array[i].push([]);
}
}
return array;
}
const piecesArr = generator(8, 8);
function renderPieces() {
let renderText = "";
piecesArr.map((items, yindex) => {
renderText += '<div class="outer">';
items.map((item, xindex) => {
renderText += `<div class="inner" id="${[xindex, yindex]}"></div>`;
});
renderText += "</div>";
});
pieces_box.innerHTML = renderText;
}
renderPieces();
let currentColor = "#fff";
pieces_box.addEventListener("click", (e) => {
if (e.target.closest(".inner") && !e.target.style.background) {
currentColor = currentColor === "#fff" ? "#000" : "#fff";
e.target.style.background = currentColor;
const currentPos = e.target.id.split(",").map(Number);
piecesArr[currentPos[0]][currentPos[1]].push(currentColor);
setTimeout(() => {
const result = isWin(currentPos);
if (result) {
alert(`恭喜${currentColor}获得了胜利`);
}
}, 100);
}
});
// ------------------------------------------------------- // 重构。。。。。。。。。。
// 重新更改逻辑。 改为判断坐标位置,
const isWin = (point) => {
return (
horJudgement(point) ||
verJudegment(point) ||
slashJudgement(point) ||
backslashJudgement(point)
);
};
const horJudgement = createJudgement(
([i, j]) => [i, j - 1],
([i, j]) => [i, j + 1]
);
const verJudegment = createJudgement(
([i, j]) => [i - 1, j],
([i, j]) => [i + 1, j]
);
const slashJudgement = createJudgement(
([i, j]) => [i + 1, j - 1],
([i, j]) => [i - 1, j + 1]
);
const backslashJudgement = createJudgement(
([i, j]) => [i - 1, j - 1],
([i, j]) => [i + 1, j + 1]
);
function createJudgement(p1Movement, p2Movement) {
return function (point) {
let count = 1;
let p1 = p1Movement(point);
let p2 = p2Movement(point);
while (1) {
let p1Changed = false,
p2Chanhed = false;
if (isValid(p1)) {
count++;
p1 = p1Movement(p1);
p1Changed = true;
}
if (isValid(p2)) {
count++;
p2 = p2Movement(p2);
p2Chanhed = true;
}
if (count >= 5) {
return true;
}
if (!p1Changed && !p2Chanhed) {
return false;
}
}
};
}
function isValid(point) {
const ROWS = piecesArr.length;
const COLS = piecesArr[0].length;
const [x, y, c] = point;
const cur = piecesArr[x]?.[y];
// point在棋盘之内 && point 的颜色和落子颜色一致
if (
cur &&
currentColor === cur[0] &&
x >= 0 &&
y >= 0 &&
x < ROWS &&
y < COLS
) {
return true;
} else {
return false;
}
}
</script>
</html>