页面设计原则
1、可配置性。比如棋盘的大小可配置,棋盘边长可配置,黑白空期的值可配置;
2、响应式。各种屏幕大小下棋盘的布局要合理;
3、面向对象。棋子、棋盘的定义都用类来封装,代码要写的好看。
开发页面
## 基础HTML骨架
代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>三黄工作室 - 五子棋</title> <style> * { margin: 0; } body{ background-image: url("img/bg.png"); } #canvas_line { box-shadow: 0 0 5px 0px rgba(0, 0, 0, .8); border-radius: 5px; box-sizing: border-box; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); border: 1px solid black; background-color: #ffbd5b; z-index: 5; } #canvas_chess { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; } .loading-message { /* background-color: #f5f5f5; */ padding: 5px; text-align: center; font-size: 18px; font-weight: bold; color:white; } .logo{ background-color: #FCFCFD; padding: 5px; text-align: center; bottom: 0px; position: fixed; width: 100%; } .button-30 { align-items: center; appearance: none; background-color: #FCFCFD; border-radius: 4px; border-width: 0; box-shadow: rgba(45, 35, 66, 0.4) 0 2px 4px,rgba(45, 35, 66, 0.3) 0 7px 13px -3px,#D6D6E7 0 -3px 0 inset; box-sizing: border-box; color: #36395A; cursor: pointer; display: inline-flex; font-family: "JetBrains Mono",monospace; height: 40px; justify-content: center; line-height: 1; list-style: none; overflow: hidden; padding-left: 16px; padding-right: 16px; position: relative; text-align: left; text-decoration: none; transition: box-shadow .15s,transform .15s; user-select: none; -webkit-user-select: none; touch-action: manipulation; white-space: nowrap; will-change: box-shadow,transform; font-size: 18px; } .button-30:focus { box-shadow: #D6D6E7 0 0 0 1.5px inset, rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; } .button-30:hover { box-shadow: rgba(45, 35, 66, 0.4) 0 4px 8px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; transform: translateY(-2px); } .button-30:active { box-shadow: #D6D6E7 0 3px 7px inset; transform: translateY(2px); } </style> </head> <body> <canvas id="canvas_line" width="600px" height="600px"></canvas> <canvas id="canvas_chess"></canvas> <div class="loading-message"><span style="display: none;">正在计算中...</span></div> <div class="loading-message"><button onclick="regreat()" class="button-30">悔棋</button></div> <div class="logo"><img src="img/logo.png" style="height: 30px;"/></div> </body> </html>
目前的页面样式如下:
## 添加页面响应式功能
现在的手机版页面如下,可以发现手机版的棋盘太小、按钮太小、下方的logo太小。
于是我们添加响应式功能,在<head>
里面添加<meta>
头,在<style>
里面追加手机页面下的css样式:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
/* 在设备宽度小于600像素时,调整div的大小 */ @media (max-width: 600px) { #canvas_line{ width: 100%; } #canvas_chess{ width: 100%; } }
添加之后,手机版页面就刚好了:
编写JS
## 获取画布对象与DOM对象
代码如下:
// 画布 var canvas_chess = document.getElementById("canvas_chess"); var context_chess = canvas_chess.getContext('2d'); var canvas_line = document.getElementById("canvas_line"); var context_line = canvas_line.getContext('2d');
这段代码主要是获取两个画布元素,并分别创建了两个2D绘图上下文对象。这些上下文对象可以用于在画布上进行绘制和操作,例如绘制图形、文本等。
获取Dom元素是为了得到元素的长宽、偏移量等;获取画布元素是为了绘制图形;获取2个一个是线条容器、一个是棋子容器。
## 定义棋子、棋盘对象.
代码如下:
// 落子状态 'z'为空 'b'为黑 'w'为白 const white_flag = '1'; const black_flag = '-1'; const blank_flag = '0'; // 棋盘边长 const len = 15; class Chess { constructor(x, y, z) { // 横坐标 this.x = x; // 纵坐标 this.y = y; // 落子状态 '0'为空 '-1'为黑 '1'为白 this.z = z; } } class Board { constructor() { // 棋盘边长 this.len = len; // 棋盘棋局状态值 value[len][len] this.value = Array.from(Array(this.len), () => new Array(this.len).fill(blank_flag)); // 棋盘棋局状态值 value[len][len] this.chessList = []; } }
上述代码定义了两个类,Chess 和 Board。
Chess 类表示一个棋子,具有横坐标 x、纵坐标 y 和落子状态 z 的属性。
Board 类表示一个棋盘,具有棋盘边长 len、棋局状态值 value 和棋子列表 chessList 的属性。value 是一个二维数组,用于存储棋盘上每个位置的落子状态。chessList 则是用于存储已下的棋子对象。
此外,代码中还定义了常量 white_flag、black_flag 和 blank_flag,分别表示白棋、黑棋和空白位置的落子状态。
## 定义绘画对象(重要!!)
代码如下:
class Draw { constructor(canvas_line, context_line, canvas_chess, context_chess) { // 样式 this.style = { // 棋盘边长 len : len, // 棋盘线条颜色 lineColor : "#555", // 棋盘线条间隔 lineWidth : 40, } // 棋盘线条居中时,需要的偏移量 this.style['offSet'] = (canvas_line.width - this.style.len * this.style.lineWidth) / 2; // dom对象 this.dom = { l : canvas_line, c : canvas_chess } // context对象 this.context ={ l : context_line, c : context_chess } // 根据线条数、间隔大小 设置棋盘宽、高 canvas_chess.height = this.style.len * this.style.lineWidth; canvas_chess.width = this.style.len * this.style.lineWidth; } // 绘制棋盘线条 drawChessBoard(){ let style = this.style; let color = style.lineColor; let w = style.lineWidth; let o = style.offSet; let len = style.len; let h = w / 2; let c = this.context.l; for(var i=0; i < len; i++){ c.strokeStyle = color; c.moveTo(h + i*w + o, h + o);//垂直方向画线 c.lineTo(h + i*w + o, h * (2 * len - 1) + o); c.stroke(); c.moveTo(h + o, h + i*w + o);//水平方向画线 c.lineTo(h * (2 * len - 1) + o , h + i*w + o); c.stroke(); } } /** * 绘制单个棋子 * @param {*} j 横坐标 * @param {*} i 纵坐标 * @param {*} k 颜色 黑or白 * @param {*} first 是否需要绘制小红点 */ drawChess(Chess, first = false){ let j = Chess.x; let i = Chess.y; let k = Chess.z; let style = this.style; let w = style.lineWidth; let h = w / 2; let c = this.context.c; c.beginPath(); c.arc(h + i*w, h+j*w, h-2, 0, 2 * Math.PI);//绘制棋子 var g=c.createRadialGradient(h+i*w,h+j*w,13,h+i*w,h+j*w,0);//设置渐变 if(k == black_flag){ g.addColorStop(0,'#0A0A0A');//黑棋 g.addColorStop(1,'#636766'); }else if(k == white_flag){ g.addColorStop(0,'#D1D1D1');//白棋 g.addColorStop(1,'#F9F9F9'); } c.fillStyle=g; c.fill(); c.closePath(); if(first){ c.fillStyle = 'red'; c.fillRect(h*0.75 + i*w, h*0.75+j*w, h/2, h/2) } } // 绘制现有棋子 drawChessAll(list) { // 清空棋子canvas let dom_c = this.dom.c; dom_c.height = dom_c.height; // 依次绘制棋子 for(let i in list){ if(i == list.length - 1) this.drawChess(list[i], true); else this.drawChess(list[i]); } } }
上述代码定义了一个名为 Draw 的类。
Draw 类具有以下属性和方法:
- 属性:
- style:包含样式信息的对象,包括棋盘边长 (len)、棋盘线条颜色 (lineColor) 和棋盘线条间隔 (lineWidth)。
- dom:包含存储 Canvas DOM 对象的属性 l 和 c。
- context:包含存储 Canvas 上下文对象的属性 l 和 c。
- 构造函数:
- 接受两个 Canvas DOM 对象和对应的上下文对象作为参数,用于初始化 Draw 对象。
- 在构造函数中,根据传入的参数设置样式、计算偏移量,并设置棋盘 Canvas 的宽度和高度。
- 方法:
- drawChessBoard():绘制棋盘线条的方法。这里有好多数学计算,再结合canvas线条绘制的api。
- drawChess(Chess, first = false):绘制单个棋子的方法。接受 Chess 对象作为参数,包含棋子的横坐标 x、纵坐标 y 和落子状态 z。可选择是否绘制小红点。
- drawChessAll(list):绘制现有棋子的方法。接受一个棋子对象列表作为参数,依次绘制列表中的棋子。
## 初始化绘制棋盘
代码如下:
// 棋盘 let board = new Board(); // 绘画器 let draw = new Draw(canvas_line, context_line, canvas_chess, context_chess); // 玩家 let currentPlayer = 1; //绘制棋盘 draw.drawChessBoard();
目前的页面样式如下:
## 添加点击事件 能够下棋落子
代码如下:
function clk (e){ $("span").css("display","block"); canvas_chess.onclick= null; var x = e.offsetY;//相对于棋盘左上角的x坐标 var y = e.offsetX;//相对于棋盘左上角的y坐标 // var i = Math.floor(x / draw.style.lineWidth); // var j = Math.floor(y / draw.style.lineWidth); var i = Math.floor(x * canvas_chess.width / canvas_chess.offsetWidth / draw.style.lineWidth); var j = Math.floor(y * canvas_chess.width / canvas_chess.offsetWidth / draw.style.lineWidth); if( board.value[i][j] == blank_flag ) { if(currentPlayer == 1){ var c = new Chess(i,j,black_flag); board.value[i][j]=black_flag; }else{ var c = new Chess(i,j,white_flag); board.value[i][j]=white_flag; } board.chessList.push(c); draw.drawChessAll(board.chessList); if(checkWin(board.chessList, currentPlayer) === true){ alert((currentPlayer == 1? "black" : "white") + "win !!"); return; } // 切换玩家 currentPlayer = (currentPlayer === 1) ? 2 : 1; }else{ $("span").css("display","none"); canvas_chess.onclick= clk; } }
这样我们就可以自己交替着下黑子和白子了。于是前端页面基本结束。