一写在开头
上期代码主要实现瀑布流功能,本期就来实现五子棋小游戏,开发久了很多功能都是通过框架组件库来完成,但是如果组件满足不了开发需求,还需要开发人员手动封装组件,专门出这样一期文章,通过原生js实现一些特定功能,功能也比较简单,也是想借助这样一个简单的功能,然后来帮助大家了解我们JavaScript,在前端中的作用,另外也培养下我们的代码思维,那我们本次就通过由简单到复杂循序渐进,这份专栏中我们还会带领大家用前端实现读心术小游戏等等有趣的小功能,纯前端语言实现,都会陆续带给大家。
实现逻辑
1.棋盘的绘制
绘制棋盘,其实就是一个 14 行 x 14 列 的 table 表格。
每个 td 会记录当前的格子是第几行第几列,我们通过自定义属性来进行记录。
data-row 记录行,data-line 记录列。
2.确定落子的坐标
当用户在棋盘上点击鼠标时,我们需要落下一颗棋子,那么如何确定这颗棋子的坐标呢?首先,通过 e.target.dataset,能够获取到用户点击的是哪一个 td,通过 e.offsetX、e.offsetY,我们就可以获取到事件发生时鼠标相对于事件源元素的坐标,所谓事件源元素就是绑定事件的那个元素。首先计算出一个格子的宽高,然后用户点击的 e.offsetX 小于格子宽度的一半,e.offsetY 小于格子高度的一半,则是左上区域。 e.offsetX 小于格子宽度的一半但是 e.offsetY 大于格子高度的一半,则是左下,依此类推。
3.绘制棋子
4.胜负判定
5.游戏已经结束,需要询问是否要重新来一局
页面创建
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>五子棋小游戏2</title>
<link rel="stylesheet" href="../css/index.css">
</head>
<body>
<!-- 最外层的容器 -->
<div class="container">
<!-- 棋盘 -->
<table class="chessboard"></table>
</div>
<script src="../js/index.js"></script>
</body>
</html>
样式编写
/* 整体容器的样式 */
.container{
border: 1px solid rgb(207, 188, 188);
width: 500px;
height: 500px;
margin: 50px auto;
/* 是一个弹性盒子,我们这边的设置代表让里面的内容水平垂直居中 */
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(247, 230, 183);
}
/* 棋盘里面的格子的样式 */
.chessboard{
width: 92%;
height: 92%;
/* 设置表格的行列之间没有间隙 */
border-collapse: collapse;
}
.chessboard td{
border: 1px solid black;
position: relative;
}
/* 棋子的公共样式 */
.chess{
border: 1px solid lightgrey;
border-radius: 50%;
position: absolute;
left: -50%;
top: -50%;
width: 90%;
height: 90%;
color: lightgrey;
font-size : 12px;
font-weight: bold;
/* 让文字居中 */
display: flex;
justify-content: center;
align-items: center;
}
/* 白色棋子 */
.white {
background-color: #fff;
}
/* 黑色棋子 */
.black {
background-color: #000;
}
/* 获胜棋子 */
.win{
border : 1px solid red;
box-shadow: 0 0 3px 2px red; /* 红色阴影 */
}
逻辑实现
// 首先还是封装两个 DOM 查询的方法
function $(selector){
return document.querySelector(selector);
}
function $$(selector){
return document.querySelectorAll(selector);
}
var chessboard = $('.chessboard'); // 获取棋盘的 table
var isGameOver = false; // 游戏是否结束
var whichOne = 'white'; // 一开始是白色的棋子
var chessArr = []; // 存储所有的棋子信息
// 初始化棋盘的方法
function initChessboard(){
// 我们要绘制一个 14x14 的棋盘格子,并且我们要把下标放入到每一个格子
var tableContent = "";
// 循环生成行
for(var i=0;i<14;i++){
var row = '<tr>';
// 循环生成列
for(var j=0;j<14;j++){
row += `<td data-row='${i}' data-line='${j}'></td>`;
}
row += '</tr>';
tableContent += row;
}
chessboard.innerHTML = tableContent;
}
// 绑定点击事件
function bindEvent(){
chessboard.onclick = function(e){
// 我们可以很轻松的知道用户点击的是哪一个 td
if(!isGameOver){
// 游戏没有结束,那么我们要做的事情就是落子
var temp = Object.assign({}, e.target.dataset); // 获取到用户点击的 td 信息
if(e.target.nodeName === 'TD'){
// 我们首先计算出每个格子的边长
var tdw = chessboard.clientWidth * 0.92 / 14;
// 接下来我们就需要确认用户落子究竟是在四个角的哪一个角
var positionX = e.offsetX > tdw / 2;
var positionY = e.offsetY > tdw / 2;
// 接下来我们来组装棋子的信息
var chessPoint = {
x : positionX ? parseInt(temp.line) + 1 : parseInt(temp.line),
y : positionY ? parseInt(temp.row) + 1 : parseInt(temp.row),
c : whichOne
}
// 绘制棋子
chessMove(chessPoint);
}
} else {
// 游戏已经结束,需要询问是否要重新来一局
if(window.confirm('是否要重新开始一局?')){
// 进行一些初始化操作
chessArr = []; // 重置棋子的数组
initChessboard(); // 重新绘制棋盘
isGameOver = false;
}
}
}
}
// 绘制棋子,接收一个参数,就是棋子的信息的对象
function chessMove(chessPoint){
// 我们在绘制之前,我们需要先判断一下,该位置有没有棋子,如果有棋子,那么就不需要再绘制
if(exist(chessPoint) && !isGameOver){
// 进入此 if,说明该位置能够绘制,并且没有游戏结束
chessArr.push(chessPoint); // 将该棋子的信息推入到数组
// 生成一个棋子,其实就是生成一个 div,然后将该 div 放入到对应的 td 里面
var newChess = `<div class="chess ${chessPoint.c}" data-row="${chessPoint.y}" data-line="${chessPoint.x}"></div>`;
// 接下来,我们需要根据不同的落子位置,调整棋子
if(chessPoint.x < 14 && chessPoint.y < 14){
var tdPos = $(`td[data-row='${chessPoint.y}'][data-line='${chessPoint.x}']`);
tdPos.innerHTML += newChess;
}
// x 等于 14,说明是最右侧那条线
if(chessPoint.x === 14 && chessPoint.y < 14){
var tdPos = $(`td[data-row='${chessPoint.y}'][data-line='13']`);
tdPos.innerHTML += newChess;
tdPos.lastChild.style.left = '50%';
}
// y 等于 14,说明是最下侧那条线
if(chessPoint.x < 14 && chessPoint.y === 14){
var tdPos = $(`td[data-row='13'][data-line='${chessPoint.x}']`);
tdPos.innerHTML += newChess;
tdPos.lastChild.style.top = '50%';
}
// x 和 y 均等于 14,说明是最右下角的那个 td
if(chessPoint.x === 14 && chessPoint.y === 14){
var tdPos = $(`td[data-row='13'][data-line='13']`);
tdPos.innerHTML += newChess;
tdPos.lastChild.style.top = '50%';
tdPos.lastChild.style.left = '50%';
}
whichOne = whichOne === 'white' ? 'black' : 'white'; // 切换棋子的颜色
}
check(); // 核对游戏是否结束
}
// 判断该棋子是否已经存在
function exist(chessPoint){
var result = chessArr.find(function(item){
return item.x === chessPoint.x && item.y === chessPoint.y;
})
return result === undefined ? true : false;
}
// 检查游戏是否结束,检查是否有符合要求的棋子
function check(){
// 其实就是遍历数组里面的每一个棋子
// 这里分为 4 种情况:横着、竖着、斜着(2 种)
for(var i=0; i< chessArr.length; i++){
var curChess = chessArr[i];
var chess2, chess3, chess4, chess5;
// 检查有没有横着的 5 个颜色一样的棋子
chess2 = chessArr.find(function(item){
return curChess.x === item.x + 1 && curChess.y === item.y && curChess.c === item.c;
})
chess3 = chessArr.find(function(item){
return curChess.x === item.x + 2 && curChess.y === item.y && curChess.c === item.c;
})
chess4 = chessArr.find(function(item){
return curChess.x === item.x + 3 && curChess.y === item.y && curChess.c === item.c;
})
chess5 = chessArr.find(function(item){
return curChess.x === item.x + 4 && curChess.y === item.y && curChess.c === item.c;
})
if(chess2 && chess3 && chess4 && chess5){
// 进入此 if,说明游戏结束
end(curChess, chess2, chess3, chess4, chess5);
}
// 检查有没有竖着的 5 个颜色一样的棋子
chess2 = chessArr.find(function(item){
return curChess.x === item.x && curChess.y === item.y + 1 && curChess.c === item.c;
})
chess3 = chessArr.find(function(item){
return curChess.x === item.x && curChess.y === item.y + 2 && curChess.c === item.c;
})
chess4 = chessArr.find(function(item){
return curChess.x === item.x && curChess.y === item.y + 3 && curChess.c === item.c;
})
chess5 = chessArr.find(function(item){
return curChess.x === item.x && curChess.y === item.y + 4 && curChess.c === item.c;
})
if(chess2 && chess3 && chess4 && chess5){
// 进入此 if,说明游戏结束
end(curChess, chess2, chess3, chess4, chess5);
}
// 检查有没有斜着的 5 个颜色一样的棋子
chess2 = chessArr.find(function(item){
return curChess.x === item.x + 1 && curChess.y === item.y + 1&& curChess.c === item.c;
})
chess3 = chessArr.find(function(item){
return curChess.x === item.x + 2 && curChess.y === item.y + 2 && curChess.c === item.c;
})
chess4 = chessArr.find(function(item){
return curChess.x === item.x + 3 && curChess.y === item.y + 3 && curChess.c === item.c;
})
chess5 = chessArr.find(function(item){
return curChess.x === item.x + 4 && curChess.y === item.y + 4 && curChess.c === item.c;
})
if(chess2 && chess3 && chess4 && chess5){
// 进入此 if,说明游戏结束
end(curChess, chess2, chess3, chess4, chess5);
}
// 检查有没有斜着的 5 个颜色一样的棋子
chess2 = chessArr.find(function(item){
return curChess.x === item.x - 1 && curChess.y === item.y + 1&& curChess.c === item.c;
})
chess3 = chessArr.find(function(item){
return curChess.x === item.x - 2 && curChess.y === item.y + 2 && curChess.c === item.c;
})
chess4 = chessArr.find(function(item){
return curChess.x === item.x - 3 && curChess.y === item.y + 3 && curChess.c === item.c;
})
chess5 = chessArr.find(function(item){
return curChess.x === item.x - 4 && curChess.y === item.y + 4 && curChess.c === item.c;
})
if(chess2 && chess3 && chess4 && chess5){
// 进入此 if,说明游戏结束
end(curChess, chess2, chess3, chess4, chess5);
}
}
}
function end(){
if(!isGameOver){
isGameOver = true; // 代表游戏结束
// 1. 把所有的棋子标记出来
for(var i=0;i<chessArr.length;i++){
$(`div[data-row='${chessArr[i].y}'][data-line='${chessArr[i].x}']`).innerHTML = i + 1;
}
// 2. 把获胜的棋子加上一个红色阴影
for(var i=0;i<arguments.length;i++){
$(`div[data-row='${arguments[i].y}'][data-line='${arguments[i].x}']`).classList.add('win');
}
}
}
// 游戏的主方法,相当于程序的入口
function main(){
// 1. 初始化棋盘
initChessboard();
// 2. 绑定对应的事件
bindEvent();
}
main();