开篇导读:最近面试遇到一个笔试题使用js和dom实现一个五子棋游戏,包含悔棋和撤销悔棋功能,对人机对战不做要求,这里分享一下我个人的实现方案
HTML部分(未使用原型的封装方式)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.container{
width: 500px;
height: 500px;
border: 1px solid black;
}
.container button{
background: transparent;
height: 50px;
width: 50px;
margin: 0;
padding: 0;
outline: none;
border: 1px solid #000000;
float: left;
font-size: 26px;
}
#bank,#revokeBank{
position: fixed;
width: 80px;
height: 30px;
top: 200px;
left: 700px;
}
#revokeBank{
top: 240px;
}
</style>
</head>
<body>
<div class="container"></div>
<button id="bank">悔棋</button>
<button id="revokeBank">撤销悔棋</button>
</body>
//js逻辑部分
<script src="index.js"></script>
</html>
JS部分(未使用原型的封装方式)
//创建一个容器
let dom = document.createDocumentFragment();
let container = document.querySelector(".container");
for(let i=1;i<=100;i++){
let btn = document.createElement("button");
btn.className = i;
dom.appendChild(btn);
}
container.appendChild(dom);
//红方选手
let redSquare = "O";
// 黑方选手
let blackSquare = "X";
//判断轮到谁下
let flag = true;
// 每次落子的位置
let array = [];
// 棋子的历史记录
let historys = [];
// 悔棋的记录
let unhistorys = [];
//选择撤销一步悔或撤销所有悔棋
let revocationType = false;
//调用事件委派
delegate(container,'click','button',function(e){
//如果此处已下棋子,则不执行方法
if(e.target.innerText) return;
flag?isRedOrblack(e,redSquare):isRedOrblack(e,blackSquare);
});
//判断哪方下棋
function isRedOrblack(e,square){
// 保存当前事件的对象
let nowDom = e.target;
nowDom.innerText = square;
// 记录在数组的对应类名序号的下标下
array[nowDom.className] = square;
historys.push(nowDom.className);
//将悔棋记录清空
unhistorys.length = 0;
action(parseInt(nowDom.className),square);
flag=!flag;
}
//判断胜负循环检索的方法
function decide(value,Operator,str,count){
for(i=1;i<5;i++){
if(array[value+Operator*i] === str)
count++;
else break;
}
return count;
}
//判断胜负
function action(value,str){
//垂直方向上的棋子
let Hcount=1;
//水平方向上的棋子
let Vcount=1;
//左对角方向上的棋子
let LTacr = 1;
//右方向上的棋子
let RTacr = 1;
//垂直方向
Hcount = decide(value,-10,str,Hcount);
Hcount = decide(value,10,str,Hcount);
// 水平方向
Vcount = decide(value,-1,str,Vcount);
Vcount = decide(value,1,str,Vcount);
//上左边对角方向
LTacr = decide(value,-11,str,LTacr);
LTacr = decide(value,11,str,LTacr);
//上右边对角方向
RTacr = decide(value,-9,str,RTacr);
RTacr = decide(value,9,str,RTacr);
(Hcount === 5 || Vcount === 5 || LTacr === 5 || RTacr === 5) && alert(str + "胜利");
};
// 悔棋
let BankDom = document.querySelector("#bank");
let containerBtn = document.querySelectorAll("button");
BankDom.onclick = function (){
// 可以悔棋多次
if(historys.length){
let number = setBank();
// 对应的button按钮元素下标需要类名值减一
containerBtn[number].innerText= "";
}
}
//设置悔棋的相应数据
function setBank(){
//拿到记录中的棋子下标
let number = parseInt(historys.pop());
// 记录需要悔棋棋子的下标
unhistorys.push(number);
// 记录棋子值的数组中的下标对应按钮的类名的值
array[number] = "";
// 更改下棋的选手
flag = !flag;
// 返回棋子位置的数组在末尾拿掉对应的值减去一就是该btn按钮的下标
return number-1;
}
//将悔掉的棋重新赋值
function setValue(number,square,isAction){
let target = containerBtn[number-1];
target.innerText= square;
array[number] = square;
isAction && action(parseInt(target.className),square);
// 更改下棋的选手
flag = !flag;
}
//撤销悔棋
let revokeBank = document.querySelector("#revokeBank");
revokeBank.onclick = function (){
if(!unhistorys.length) return;
if(revocationType){
for(let i=unhistorys.length-1;i>=0;i--){
historys.push(unhistorys[i]);
flag?setValue(unhistorys[i],redSquare,false):setValue(unhistorys[i],blackSquare,false)
}
//将悔棋记录清空
unhistorys.length = 0;
}
else{
let add = unhistorys.pop();
historys.push(add);
flag?setValue(add,redSquare,true):setValue(add,blackSquare,true);
}
}
上述JS部分调用了一个封装好了的delegate事件委派的函数,详细注释js实现事件委派
//事件委派方法
function delegate (element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target;
while (!el.matches(selector)) {
if (element === el) {
el = null;
break;
}
el = el.parentNode;
}
el && fn.call(el, e, el)
});
return element
};
HTML部分(使用原型的封装方式)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.container{
width: 500px;
height: 500px;
border: 1px solid black;
}
.container button{
background: transparent;
height: 50px;
width: 50px;
margin: 0;
padding: 0;
outline: none;
border: 1px solid #000000;
float: left;
font-size: 26px;
}
#bank,#revokeBank{
position: fixed;
width: 80px;
height: 30px;
top: 200px;
left: 700px;
}
#revokeBank{
top: 240px;
}
</style>
</head>
<body>
<div class="container"></div>
<button id="bank">悔棋</button>
<button id="revokeBank">撤销悔棋</button>
</body>
//js逻辑部分
<script src="index.js"></script>
<script>
//创建游戏并初始化
new Game().initGame();
</script>
</html>
JS部分(使用原型的封装方式)
// 声明一个游戏函数
function Game(){
//红方选手
this.redSquare = "O";
// 黑方选手
this.blackSquare = "X";
//判断轮到谁下
this.flag = true;
// 每次落子的位置
this.array = [];
// 棋子的历史记录
this.historys = [];
// 悔棋的记录
this.unhistorys = [];
//选择撤销一步悔或撤销所有悔棋
this.revocationType = false;
//获取到.container元素
this.container = document.querySelector(".container");
//保存所有button元素
let containerBtn = null;
}
//初始化游戏
Game.prototype.initGame = function(){
this.createBoard();
this.addEventListener();
this.needBank();
this.needRevokeBank();
}
//在游戏函数原型上增加方法,创建棋盘
Game.prototype.createBoard = function(){
//创建一个容器
let dom = document.createDocumentFragment();
for(let i=1;i<=100;i++){
let btn = document.createElement("button");
btn.className = i;
dom.appendChild(btn);
}
// 将棋盘渲染
this.container.appendChild(dom);
//获到所有button元素
this.containerBtn = document.querySelectorAll("button");
}
//为棋盘每个格子绑定点击事件
Game.prototype.addEventListener = function(){
//调用事件委派
this.delegate(this.container,'click','button',(e) =>{
//如果此处已下棋子,则不执行方法
if(e.target.innerText) return;
this.flag?this.isRedOrblack(e,this.redSquare):this.isRedOrblack(e,this.blackSquare);
});
}
//判断哪方下棋
Game.prototype.isRedOrblack = function(e,square){
// 保存当前事件的对象
let nowDom = e.target;
nowDom.innerText = square;
// 记录在数组的对应类名序号的下标下
this.array[nowDom.className] = square;
this.historys.push(nowDom.className);
//将悔棋记录清空
this.unhistorys.length = 0;
this.action(parseInt(nowDom.className),square);
this.flag = !this.flag;
}
//判断胜负循环检索的方法
Game.prototype.decide = function(value,Operator,str,count){
for(i=1;i<5;i++){
if(this.array[value+Operator*i] === str)
count++;
else break;
}
return count;
}
//判断胜负
Game.prototype.action = function(value,str){
//垂直方向上的棋子
let Hcount=1;
//水平方向上的棋子
let Vcount=1;
//左对角方向上的棋子
let LTacr = 1;
//右方向上的棋子
let RTacr = 1;
//垂直方向
Hcount = this.decide(value,-10,str,Hcount);
Hcount = this.decide(value,10,str,Hcount);
// 水平方向
Vcount = this.decide(value,-1,str,Vcount);
Vcount = this.decide(value,1,str,Vcount);
//上左边对角方向
LTacr = this.decide(value,-11,str,LTacr);
LTacr = this.decide(value,11,str,LTacr);
//上右边对角方向
RTacr = this.decide(value,-9,str,RTacr);
RTacr = this.decide(value,9,str,RTacr);
(Hcount === 5 || Vcount === 5 || LTacr === 5 || RTacr === 5) && alert(str + "胜利");
}
// 悔棋
Game.prototype.needBank = function(){
let BankDom = document.querySelector("#bank");
//设置悔棋的相应数据
let setBank = () =>{
//拿到记录中的棋子下标
let number = parseInt(this.historys.pop());
// 记录需要悔棋棋子的下标
this.unhistorys.push(number);
// 记录棋子值的数组中的下标对应按钮的类名的值
this.array[number] = "";
// 更改下棋的选手
this.flag = !this.flag;
// 返回棋子位置的数组在末尾拿掉对应的值减去一就是该btn按钮的下标
return number-1;
}
BankDom.onclick = () =>{
if(this.historys.length){
let number = setBank();
// 对应的button按钮元素下标需要类名值减一
this.containerBtn[number].innerText= "";
}
}
}
//撤销悔棋
Game.prototype.needRevokeBank = function(){
let revokeBank = document.querySelector("#revokeBank");
//将悔掉的棋重新赋值
function setValue(number,square,isAction){
let target = this.containerBtn[number-1];
target.innerText= square;
this.array[number] = square;
isAction && this.action(parseInt(target.className),square);
// 更改下棋的选手
this.flag = !this.flag;
}
//绑定事件
revokeBank.onclick = () =>{
if(!this.unhistorys.length) return;
if(this.revocationType){
for(let i=this.unhistorys.length-1;i>=0;i--){
this.historys.push(this.unhistorys[i]);
this.flag?setValue.call(this,this.unhistorys[i],this.redSquare,false):setValue.call(this,this.unhistorys[i],this.blackSquare,false)
}
//将悔棋记录清空
this.unhistorys.length = 0;
}
else{
let add = this.unhistorys.pop();
this.historys.push(add);
this.flag?setValue.call(this,add,this.redSquare,true):setValue.call(this,add,this.blackSquare,true);
}
}
}
//事件委派方法
Game.prototype.delegate = function(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target;
while (!el.matches(selector)) {
if (element === el) {
el = null;
break;
}
el = el.parentNode;
}
el && fn.call(el, e, el)
});
return element
};
如果发现这个方案中有问题,请在下方留言,欢迎随时指点