构建画布并获取canvas 2d绘图引擎
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const canvas = document.createElement("canvas");
canvas.width = 800; canvas.height = 800;
canvas.style["display"] = "block" canvas.style["margin"] = "0 auto" canvas.style["background"] = "#0a0"
document.body.append(canvas);
const ctx = canvas.getContext("2d");
|
绘制棋盘
- 棋盘由多条横线和纵线组合而成
- 由于设定宽度为800,预留100空间,实际的棋盘宽为700
- 为了好计算,棋盘切分为14格,每格宽为50
- 循环14次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const canvas = document.createElement("canvas"); ......
const ctx = canvas.getContext("2d");
for (let i = 1; i <= 15; i++) { ctx.moveTo(50, 50 * i); ctx.lineTo(750, 50 * i); ctx.stroke();
ctx.moveTo(50 * i, 50); ctx.lineTo(50 * i, 750); ctx.stroke(); }
|
鼠标点击生成棋子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ......
const ctx = canvas.getContext("2d"); ......
function drawPiece(x, y) { ctx.beginPath() ctx.arc(x, y, 20, 0, 2 * Math.PI) ctx.fill() ctx.closePath() }
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e let x = offsetX let y = offsetY drawPiece(x, y) })
|
现在点击鼠标,一个黑色的圆就显示在鼠标的位置上,我们预想的棋子是需要落在棋盘的框线上,这里就需要添加一些算法来确定棋子的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ......
const ctx = canvas.getContext("2d"); ......
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e
let x = Math.floor(offsetX / 50) * 50 let y = Math.floor(offsetY / 50) * 50 drawPiece(x, y) })
|
现在点击鼠标,一个棋子就出现在棋盘的框线位置上,我们在点格子的右下角时,预想的棋子是需要落在最近的框线上,但现在出现在左上角,这里还需要改良一下坐标算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ......
const ctx = canvas.getContext("2d"); ......
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e
let x = Math.floor((offsetX + 25) / 50) * 50 let y = Math.floor((offsetY + 25) / 50) * 50 drawPiece(x, y) })
|
现在点击鼠标,一个棋子就出现在棋盘的框线位置上,我们在点格子的右下角时,棋子是会落在最近的框线上,接下来我们美化一下棋子
绘制棋子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ......
const ctx = canvas.getContext("2d"); ......
function drawPiece(x, y) { let tx = x - 10; let ty = y - 10; ctx.beginPath(); ctx.arc(x, y, 20, 0, 2 * Math.PI); const g = ctx.createRadialGradient(tx, ty, 0, tx, ty, 30); g.addColorStop(0, "#ccc"); g.addColorStop(1, "#000"); ctx.fillStyle = g; ctx.fill(); ctx.closePath(); }
|
现在点击鼠标,一个有立体感的黑棋出现在棋盘的框线位置上,既然有黑棋,那么白棋也不能少,接下来我们添加一下棋子
绘制黑白棋
- 五子棋玩法,黑棋下一子,然后到白棋下一子,再到黑棋下一子,依次循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| ......
const ctx = canvas.getContext("2d"); ......
let isBlack = true
function drawPiece(x, y) { let tx = isBlack ? x - 10 : x + 10; let ty = isBlack ? y - 10 : y + 10; ctx.beginPath(); ctx.arc(x, y, 20, 0, 2 * Math.PI); const g = ctx.createRadialGradient(tx, ty, 0, tx, ty, 30); g.addColorStop(0, isBlack ? "#ccc" : "#666"); g.addColorStop(1, isBlack ? "#000" : "#fff"); ctx.fillStyle = g; ctx.fill(); ctx.closePath(); }
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e
let x = Math.floor((offsetX + 25) / 50) * 50 let y = Math.floor((offsetY + 25) / 50) * 50 drawPiece(x, y)
isBlack = !isBlack })
|
点击鼠标在棋盘上显示不同的黑棋、白棋,会发现有一点bug,黑白棋会互相覆盖,接下来解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| ......
const ctx = canvas.getContext("2d"); ......
let isBlack = true
let pieces = []
for (let i = 1; i <= 15; i++) { pieces[i] = [] }
......
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e
let i = Math.floor((offsetX + 25) / 50) let j = Math.floor((offsetY + 25) / 50)
if (pieces[j][i]) { return; }
pieces[j][i] = isBlack ? "black" : "white";
let x = i * 50 let y = j * 50 drawPiece(x, y)
isBlack = !isBlack })
|
这时黑白棋的交替出现基本上没有问题了,这里还有一些bug,就是点击棋盘边缘时,棋子出现在棋盘外,接下来解决这个bug
1 2 3 4 5 6 7 8 9 10 11 12 13
| ......
const ctx = canvas.getContext("2d"); ......
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e if (offsetX < 25 || offsetY < 25 || offsetX > 775 || offsetY > 775) return;
...... })
|
至此,黑白棋交替出现已经完成了,接下来添加一些提示文本
添加提示文本
- 添加在canvas元素之前,为了方便,使用js创建元素了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const tip = document.createElement("div"); tip.style["text-align"] = "center" tip.style["padding"] = "0 0 5px" tip.innerText = '请黑棋落子' document.body.append(tip);
......
const ctx = canvas.getContext("2d"); ......
canvas.addEventListener("click", (e) => { ......
if (pieces[j][i]) { tip.innerText = `这里不能重复落子,当前是${isBlack ? "黑" : "白"}子的回合`; return; }
......
isBlack = !isBlack tip.innerText = isBlack ? "请黑棋落子" : "请白棋落子"; })
|
实现五子棋游戏规则
- 连续5个相同颜色的棋子方获胜
- 加上本体,向上4个、向下4个、向左4个、向右4个、向左上4个、向右上4个、向左下4个、向右下4个,连续相同的颜色获胜
- 从上到下有连续5个相同颜色的棋子就能获胜
- 从左到右有连续5个相同颜色的棋子就能获胜
- 从左上到右下连续5个相同颜色的棋子就能获胜
- 从右上到左下连续5个相同颜色的棋子就能获胜
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
| ......
const ctx = canvas.getContext("2d"); ......
let pieces = [];
for (let i = 1; i <= 15; i++) { pieces[i] = []; }
let endGame = false;
canvas.addEventListener("click", (e) => { const { offsetX, offsetY } = e; if (offsetX < 25 || offsetY < 25 || offsetX > 775 || offsetY > 775) return;
if (endGame) { return; }
......
drawPiece(x, y);
endGame = checkVertical(j, i) || checkHorizontal(j, i) || checkNW2SE(j, i) || checkNE2SW(j, i); if (endGame) { tip.innerText = `${isBlack ? "黑棋" : "白棋"}获胜,请刷新重新开始`; return; }
isBlack = !isBlack; tip.innerText = isBlack ? "请黑棋落子" : "请白棋落子"; })
function checkVertical(row, col) { let up = 0; let down = 0; let count = 1; for (let times = 0; times < 4; times++) { let target = isBlack ? "black" : "white"; up++; if (row - up >= 1) { if (pieces[row - up][col] && pieces[row - up][col] === target) { count++; } } down++; if (row + down <= 15) { if (pieces[row + down][col] && pieces[row + down][col] === target) { count++; } }
if (count >= 5) { break; } } return count >= 5; }
function checkHorizontal(row, col) { let left = 0; let right = 0; let count = 1; for (let times = 0; times < 4; times++) { let target = isBlack ? "black" : "white"; left++; if (col - left >= 1) { if (pieces[row][col - left] && pieces[row][col - left] === target) { count++; } } right++; if (col + right <= 15) { if (pieces[row][col + right] && pieces[row][col + right] === target) { count++; } }
if (count >= 5) { break; } } return count >= 5; }
function checkNW2SE(row, col) { let lt = 0; let rb = 0; let count = 1; for (let times = 0; times < 4; times++) { let target = isBlack ? "black" : "white"; lt++; if (row - lt >= 1 && col - lt >= 1) { if (pieces[row - lt][col - lt] && pieces[row - lt][col - lt] === target) { count++; } } rb++; if (row + rb <= 15 && col + rb <= 15) { if (pieces[row + rb][col + rb] && pieces[row + rb][col + rb] === target) { count++; } }
if (count >= 5) { break; } } return count >= 5; }
function checkNE2SW(row, col) { let rt = 0; let lb = 0; let count = 1; for (let times = 0; times < 4; times++) { let target = isBlack ? "black" : "white"; rt++; if (row - rt >= 1 && col + rt <= 15) { if (pieces[row - rt][col + rt] && pieces[row - rt][col + rt] === target) { count++; } } lb++; if (row + lb <= 15 && col - lb >= 1) { if (pieces[row + lb][col - lb] && pieces[row + lb][col - lb] === target) { count++; } }
if (count >= 5) { break; } } return count >= 5; }
|
上述的算法,可能还有优化的地步,暂时未想到更好的。至此一个五子棋的极简版已完成,后续会完善
源码学习
五子棋 - CodePenhttps://codepen.io/EvilChan/pen/jOeYmZr