【五子棋实战】第5章 开发五子棋前端页面
- 页面设计原则
- 开发页面
- ## 基础HTML骨架
- ## 添加页面响应式功能
- 编写JS
- ## 获取画布对象与DOM对象
- ## 定义棋子、棋盘对象
- ## 定义绘画对象(重要!!)
- ## 初始化绘制棋盘
- ## 添加点击事件 能够下棋落子
- 继续学习下一篇实战!
页面设计原则
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;
}
}
这样我们就可以自己交替着下黑子和白子了。于是前端页面基本结束。
继续学习下一篇实战!
【五子棋实战】第6章 调用接口进行联调