Background Introduction
Because I have used gdi+ in winform in c# and the java graphics package to play backgammon, I am familiar with this logical idea. However, I recently wanted to review the canvas drawing function of html5 (companies generally do not use it) These), so I made a backgammon. Of course, I did not refer to the previous client code. I only used the previous winning and losing judgment algorithm and computer AI (network borrowing) algorithm. Of course, there are many backgammon made in html5 on Baidu, but I It is always a good thing to realize one side. Okay, let’s stop talking and get to the point. ^_^
Interface function introduction and subsequent functions that can be added
Current interface functions:
Main interface Contains
1: Options for human-human and man-machine battles 2: Choice of chess piece appearance 3: Choice of chessboard background 4: Choice of chessboard line color
The game interface contains
1: Player name 2: Player piece 3: Who is currently playing chess, background positioning 4: Player score 5: Function menu area (restart and infinite regret chess) 6: Board area 7. Connect chess pieces after victory 8. The last chess position flashes and displays 9. Cursor positioning
Game end interface
1: Victory background picture 2: Victorious player name 3: Continue to the next move button
Functions can be added
1. Return to the main interface 2. Save the game and related data 3. Read the game and related data 4. Exchange Character 5. Online battle (2 machines) 6. Total thinking time record of both parties
Interface screenshot appreciation (didn’t take the time to beautify, it’s ugly)
Overall design and main code introduction
Overall design
##Chess playing process: Players or computer AI play chess---> Draw chess pieces---> Set the two-dimensional coordinate values of chess pieces----> logic (logical judgment) - ---> (Player) One side has five pieces in a row---> Winning interface
↑ |
↓
< ;------------------------------------------------- -----------------------------------------------No five sons
Repentance process (everyone plays): One player regrets the move----> Pop up the chess record stack and set it Because it is the last chess piece ---> Clear the last chess piece image ---> Clear the 2-dimensional coordinate value of the chess piece ---> Reposition to display the last chess position and flash
Repent chess process (human-machine battle): Player regrets chess ----> Pop up the chess record stack 2 times, set the last computer as the last chess ---> ; Clear the pop-up 2 recorded images ---> Clear the two-dimensional coordinate values of the two chess pieces ---> Reposition and display the last chess position and flash
Main code introduction
The main code is divided into two blocks: 1. Interface logic block 2. Game main block (interface and game code are separated, with clear logic and division of labor Clear)
Interface logic code
<script type="text/javascript"> var gb = null; var infoboj = document.getElementsByClassName("info")[0]; var pl1obj = document.getElementById("pl1"); var pl2obj = document.getElementById("pl2"); var plname1obj = document.getElementById("plname1"); var plname2obj = document.getElementById("plname2"); var chesstypeobj = document.getElementsByName("chesstype"); var chesscolorobj = document.getElementsByName("chesscolor"); var chessbgObj = document.getElementsByName("chessbg"); var winerpnl = document.getElementById("winer"); document.getElementById("startgame").addEventListener("click", function() { function initParams() { var chessTypeValue = 1; if (chesstypeobj.length > 0) { for (var i = 0; i < chesstypeobj.length; i++) { if (chesstypeobj[i].checked) { chessTypeValue = chesstypeobj[i].value; break; } } } var linevalue = ""; if (chesscolorobj.length > 0) { for (var i = 0; i < chesscolorobj.length; i++) { if (chesscolorobj[i].checked) { linevalue = chesscolorobj[i].value; break; } } } var bcorimgvalue = ""; if (chessbgObj.length > 0) { for (var i = 0; i < chessbgObj.length; i++) { if (chessbgObj[i].checked) { bcorimgvalue = chessbgObj[i].value; break; } } } return { lineColor: linevalue, chessType: chessTypeValue, //1 色彩棋子 2 仿真棋子 playAName: plname1Input.value, playBName: plname2Input.value, backColorORImg: bcorimgvalue, playAImg: "http://sandbox.runjs.cn/uploads/rs/62/nbqodq5i/playA.png", playBImg: "http://sandbox.runjs.cn/uploads/rs/62/nbqodq5i/playB.png", playerBIsComputer:openComputer.checked }; } document.getElementById("cc").style.display = "block"; gb = new gobang(initParams()); /** * 设置一些界面信息 * @param {Object} opt */ gb.info = function(opt) { infoboj.style.visibility = "visible"; document.getElementsByClassName("startpnl")[0].style.visibility = "hidden"; plname1obj.innerHTML = opt.playAName; plname2obj.innerHTML = opt.playBName; if (opt.chessType == 1) { var span1 = document.createElement("span"); pl1obj.insertBefore(span1, plname1obj); var span2 = document.createElement("span"); pl2obj.insertBefore(span2, plname2obj); } else { var img1 = document.createElement("img"); img1.src = opt.playAImg; pl1obj.insertBefore(img1, plname1obj); var img2 = document.createElement("img"); img2.src = opt.playBImg; pl2obj.insertBefore(img2, plname2obj); } } /** * 每次下棋后触发事件 * @param {Object} c2d */ gb.operate = function(opt, c2d) { if (!c2d.winer || c2d.winer <= 0) { pl1obj.removeAttribute("class", "curr"); pl2obj.removeAttribute("class", "curr"); if (c2d.player == 1) { pl2obj.setAttribute("class", "curr"); } else { pl1obj.setAttribute("class", "curr"); } document.getElementById("backChessman").innerHTML="悔棋("+c2d.canBackTimes+")"; } else { var winname = c2d.winer == 1 ? opt.playAName : opt.playBName; var str = "恭喜,【" + winname + "】赢了!" alert(str); winerpnl.style.display = "block"; document.getElementById("winerName").innerHTML = "恭喜,【" + winname + "】赢了!"; document.getElementById("pl" + c2d.winer).style.backgroundColor = "pink"; document.getElementById("scoreA").innerHTML = c2d.playScoreA; document.getElementById("scoreB").innerHTML = c2d.playScoreB; } } gb.start(); }); document.getElementById("openComputer").addEventListener("change", function() { if (this.checked) { plname2Input.value = "电脑"; plname2Input.disabled = "disabled"; } else { plname2Input.value = "玩家二"; plname2Input.disabled = ""; } }); //document.getElementById("openComputer").checked="checked"; //重新开始 function restartgui() { if (gb) { winerpnl.style.display = "none"; pl1obj.removeAttribute("class", "curr"); pl2obj.removeAttribute("class", "curr"); document.getElementById("pl1").style.backgroundColor = ""; document.getElementById("pl2").style.backgroundColor = ""; gb.restart(); } }; </script>
Game main code block (only contains function declaration Code)
// ========== // =name:gobang 游戏 // =anthor:jasnature // =last modify date:2016-04-13 // ========== (function(win) { var gb = function(option) { var self = this, canObj = document.getElementById("cc"), can = canObj.getContext("2d"); self.contextObj = canObj; self.context = can; if (!self.context) { alert("浏览器不支持html5"); return; }; self.Opt = { lineColor: "green", chessType: 1, //1 色彩棋子 2 仿真棋子 playAName: "play1", playBName: "play2", playAColor: "red", playBColor: "blue", playAImg: "img/playA.png", playBImg: "img/playB.png", backColorORImg: "default", playerBIsComputer: false }; self.operate; //合并属性 for (var a in option) { //console.log(opt[a]); self.Opt[a] = option[a]; }; //私有变量 var my = {}; my.enableCalcWeightNum = false; //显示AI分数 my.gameover = false; //棋盘相关 my.baseWidth = 30; my.lastFocusPoint = {}; //鼠标最后移动到的坐标点,计算后的 my.cw = self.contextObj.offsetWidth; //棋盘宽 my.ch = self.contextObj.offsetHeight; //高 my.xlen = Math.ceil(my.cw / my.baseWidth); //行数 my.ylen = Math.ceil(my.ch / my.baseWidth); //列 my.chessRadius = 14; //棋子半径 my.playerBIsComputer = false; //棋手B是否是电脑 my.ComputerThinking = false; //电脑是否在下棋 my.goBackC2dIsComputer = false; //最后下棋是否为电脑 my.switcher = 1; //由谁下棋了 1-a 2-b or computer my.winer = -1; //赢家,值参考my.switcher my.playScoreA = 0; my.playScoreB = 0; //x,y 正方形数量(20*20) my.rectNum = my.xlen; //存储已下的点 my.rectMap = []; my.NO_CHESS = -1; //没有棋子标识 my.goBackC2d = {}; //最后下的数组转换坐标 my.downChessmanStackC2d = []; // 记录已下棋子的顺序和位置,堆栈 my.focusFlashInterval = null; //焦点闪烁线程 my.focusChangeColors = ["red", "fuchsia", "#ADFF2F", "yellow", "purple", "blue"]; my.eventBinded = false; my.currChessBackImg = null; my.currChessAImg = null; my.currChessBImg = null; my.currDrawChessImg = null; my.ChessDownNum = 0; //2个玩家 下棋总数 /** * 开始游戏 */ self.start = function() { }; /** * 重新开始游戏 */ self.restart = function() { }; /** * 悔棋一步 ,清棋子,并返回上一次参数 */ self.back = function() { } /** * 初始化一些数据 */ function init() { } // self.paint = function() { // // //window.requestAnimationFrame(drawChessboard); // }; /** * 游戏逻辑 */ function logic(loc, iscomputer) { }; /** * 判断是否有玩家胜出 * @param {Object} c2d */ function isWin(c2d) { return false; } /** * 连接赢家棋子线 * @param {Object} points */ function joinWinLine(points) { } /** * 画棋盘 */ function drawChessboard() { }; /** * 画棋子 * @param {Object} loc 鼠标点击位置 */ function drawChessman(c2d) { } function drawRect(lastRecord, defColor) { } /** * 闪烁最后下棋点 */ function flashFocusChessman() { } /** * 清棋子 * @param {Object} c2d */ function clearChessman() { } /** * @param {Object} loc * @return {Object} I 二维数组横点(),J二维数组纵点,IX 横点起始坐标,JY纵点起始坐标,player 最后下棋玩, winer 赢家 */ function calc2dPoint(loc) { var txp = Math.floor(loc.x / my.baseWidth), typ = Math.floor(loc.y / my.baseWidth) dxp = txp * my.baseWidth, dyp = typ * my.baseWidth; loc.I = txp; loc.J = typ; loc.IX = dxp; loc.JY = dyp; return loc; } my.isChangeDraw = true; /** * 位置移动光标 * @param {Object} loc */ function moveFocus(loc) { } /** * 绑定事件 */ function bindEvent() { if (!my.eventBinded) { self.contextObj.addEventListener("touchstart", function(event) { //console.log(event); var touchObj = event.touches[0]; eventHandle({ s: "touch", x: touchObj.clientX - this.offsetLeft, y: touchObj.clientY - this.offsetTop }) }); self.contextObj.addEventListener("click", function(event) { //console.log("click event"); eventHandle({ s: "click", x: event.offsetX, y: event.offsetY }) }); self.contextObj.addEventListener("mousemove", function(event) { //console.log("mousemove event"); moveFocus({ x: event.offsetX, y: event.offsetY }); }); my.eventBinded = true; } function eventHandle(ps) { if (!my.gameover && !my.ComputerThinking) { logic(ps); if (my.playerBIsComputer && my.switcher == 2) { my.ComputerThinking = true; var pp = AI.analysis(my.goBackC2d.I, my.goBackC2d.J); logic({ I: pp.x, J: pp.y }, true); my.ComputerThinking = false; } } event.preventDefault(); event.stopPropagation(); return false; } } }; win.gobang = gb; })(window);
玩家OR电脑胜出算法
/** * 判断是否有玩家胜出 * @param {Object} c2d */ function isWin(c2d) { //四个放心计数 竖 横 左斜 右斜 var hcount = 0, vcount = 0, lbhcount = 0, rbhcount = 0, temp = 0; var countArray = []; //左-1 for (var i = c2d.I; i >= 0; i--) { temp = my.rectMap[i][c2d.J]; if (temp < 0 || temp !== c2d.player) { break; } hcount++; countArray.push({ I: i, J: c2d.J }); } //右-1 for (var i = c2d.I + 1; i < my.rectMap.length; i++) { temp = my.rectMap[i][c2d.J]; if (temp < 0 || temp !== c2d.player) { break; } hcount++; countArray.push({ I: i, J: c2d.J }); } if (countArray.length < 5) { countArray = []; //上-2 for (var j = c2d.J; j >= 0; j--) { temp = my.rectMap[c2d.I][j]; if (temp < 0 || temp !== c2d.player) { break; } vcount++; countArray.push({ I: c2d.I, J: j }); } //下-2 for (var j = c2d.J + 1; j < my.rectMap[c2d.I].length; j++) { temp = my.rectMap[c2d.I][j]; if (temp < 0 || temp !== c2d.player) { break; } vcount++; countArray.push({ I: c2d.I, J: j }); } } if (countArray.length < 5) { countArray = []; //左上 for (var i = c2d.I, j = c2d.J; i >= 0, j >= 0; i--, j--) { if (i < 0 || j < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } lbhcount++; countArray.push({ I: i, J: j }); } //右下 if (c2d.I < my.rectMap.length - 1 && c2d.I < my.rectMap[0].length - 1) { for (var i = c2d.I + 1, j = c2d.J + 1; i < my.rectMap.length, j < my.rectMap[0].length; i++, j++) { if (i >= my.rectMap.length || j >= my.rectMap.length) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } lbhcount++; countArray.push({ I: i, J: j }); } } } if (countArray.length < 5) { countArray = []; //右上 for (var i = c2d.I, j = c2d.J; i < my.rectMap.length, j >= 0; i++, j--) { if (i >= my.rectMap.length || j < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } rbhcount++; countArray.push({ I: i, J: j }); } //左下 if (c2d.I >= 1 && c2d.J < my.rectMap[0].length - 1) { for (var i = c2d.I - 1, j = c2d.J + 1; i > 0, j < my.rectMap[0].length; i--, j++) { if (j >= my.rectMap.length || i < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } rbhcount++; countArray.push({ I: i, J: j }); } } } if (hcount >= 5 || vcount >= 5 || lbhcount >= 5 || rbhcount >= 5) { my.winer = c2d.player; my.gameover = true; joinWinLine(countArray); return true; } return false; }
算法简介:主要思路是搜索最后落下棋子的位置(二维坐标)计算 米 字形线坐标,看是否有连续5个或以上棋子出现。
连接赢家棋子线
/** * 连接赢家棋子线 * @param {Object} points */ function joinWinLine(points) { points.sort(function(left, right) { return (left.I + left.J) > (right.I + right.J); }); var startP = points.shift(); var endP = points.pop(); var poffset = my.baseWidth / 2; can.strokeStyle = "#FF0000"; can.lineWidth = 2; can.beginPath(); var spx = startP.I * my.baseWidth + poffset, spy = startP.J * my.baseWidth + poffset; can.arc(spx, spy, my.baseWidth / 4, 0, 2 * Math.PI, false); can.moveTo(spx, spy); var epx = endP.I * my.baseWidth + poffset, epy = endP.J * my.baseWidth + poffset; can.lineTo(epx, epy); can.moveTo(epx + my.baseWidth / 4, epy); can.arc(epx, epy, my.baseWidth / 4, 0, 2 * Math.PI, false); can.closePath(); can.stroke(); }
算法简介:根据赢家返回的连子位置集合,做坐标大小位置排序,直接使用lineto 连接 第一个棋子和最后一个
坐标换算
/** * 坐标换算 * @param {Object} loc * @return {Object} I 二维数组横点(),J二维数组纵点,IX 横点起始坐标,JY纵点起始坐标,player 最后下棋玩, winer 赢家 */ function calc2dPoint(loc) { var txp = Math.floor(loc.x / my.baseWidth), typ = Math.floor(loc.y / my.baseWidth) dxp = txp * my.baseWidth, dyp = typ * my.baseWidth; loc.I = txp; loc.J = typ; loc.IX = dxp; loc.JY = dyp; return loc; }
算法简介:这个比较简单,根据每个格子的宽度计算出实际坐标
电脑AI主要代码(修改来源于网络)
/** * AI棋型分析 */ AI.analysis = function(x, y) { //如果为第一步则,在玩家棋周围一格随机下棋,保证每一局棋第一步都不一样 if (my.ChessDownNum == 1) { return this.getFirstPoint(x, y); } var maxX = 0, maxY = 0, maxWeight = 0, i, j, tem; for (i = BOARD_SIZE - 1; i >= 0; i--) { for (j = BOARD_SIZE - 1; j >= 0; j--) { if (my.rectMap[i][j] !== -1) { continue; } tem = this.computerWeight(i, j, 2); if (tem > maxWeight) { maxWeight = tem; maxX = i; maxY = j; } if (my.enableCalcWeightNum) { can.clearRect(i * 30 + 2, j * 30 + 2, 24, 24); can.fillText(maxWeight, i * 30 + 5, j * 30 + 15, 30); } } } return new Point(maxX, maxY); }; //下子到i,j X方向 结果: 多少连子 两边是否截断 AI.putDirectX = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, //两边是否被截断 side2 = false; for (m = j - 1; m >= 0; m--) { if (my.rectMap[i][m] === chessColor) { nums++; } else { if (my.rectMap[i][m] === my.NO_CHESS) { side1 = true; //如果为空子,则没有截断 } break; } } for (m = j + 1; m < BOARD_SIZE; m++) { if (my.rectMap[i][m] === chessColor) { nums++; } else { if (my.rectMap[i][m] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; //下子到i,j Y方向 结果 AI.putDirectY = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1; m >= 0; m--) { if (my.rectMap[m][j] === chessColor) { nums++; } else { if (my.rectMap[m][j] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1; m < BOARD_SIZE; m++) { if (my.rectMap[m][j] === chessColor) { nums++; } else { if (my.rectMap[m][j] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; //下子到i,j XY方向 结果 AI.putDirectXY = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1, n = j + 1; m < BOARD_SIZE && n < BOARD_SIZE; m++, n++) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; AI.putDirectYX = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1, n = j + 1; m >= 0 && n < BOARD_SIZE; m--, n++) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1, n = j - 1; m < BOARD_SIZE && n >= 0; m++, n--) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; /** * 计算AI下棋权重 * chessColor 玩家1为玩家2为AI */ AI.computerWeight = function(i, j, chessColor) { //基于棋盘位置权重(越靠近棋盘中心权重越大) var weight = 19 - (Math.abs(i - 19 / 2) + Math.abs(j - 19 / 2)), pointInfo = {}; //某点下子后连子信息 //x方向 pointInfo = this.putDirectX(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectX(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //y方向 pointInfo = this.putDirectY(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectY(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //左斜方向 pointInfo = this.putDirectXY(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectXY(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //右斜方向 pointInfo = this.putDirectYX(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectYX(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 return weight; }; //权重方案 活:两边为空可下子,死:一边为空 //其实还有很多种方案,这种是最简单的 AI.weightStatus = function(nums, side1, side2, isAI) { var weight = 0; switch (nums) { case 1: if (side1 && side2) { weight = isAI ? 15 : 10; //一 } break; case 2: if (side1 && side2) { weight = isAI ? 100 : 50; //活二 } else if (side1 || side2) { weight = isAI ? 10 : 5; //死二 } break; case 3: if (side1 && side2) { weight = isAI ? 500 : 200; //活三 } else if (side1 || side2) { weight = isAI ? 30 : 20; //死三 } break; case 4: if (side1 && side2) { weight = isAI ? 5000 : 2000; //活四 } else if (side1 || side2) { weight = isAI ? 400 : 100; //死四 } break; case 5: weight = isAI ? 100000 : 10000; //五 break; default: weight = isAI ? 500000 : 250000; break; } return weight; };
AI分析:这个只是最简单的算法,其实很简单,计算每个没有下棋坐标的分数,也是按照 米 字形 计算,计算格子8个方向出现的 一个棋子 二个棋子 三个棋子 四个棋子,其中还分为是否被截断,其实就是边缘是否被堵死。
其实这个AI算法后续还有很多可以优化,比如 断跳 二活 其实就是2个交叉的 活二 , 因为是断掉的所以没有纳入算法权重计算,如果加入这个算法,估计很难下赢电脑了。
如符号图:
* *
* *
空位
下这里
因为不是连续的,所有没有纳入。
The above is the detailed content of HTML5 Canvas implements code sharing for the backgammon game (pictures and text). For more information, please follow other related articles on the PHP Chinese website!