今天复习了canvas的内容,尝试着通过canvas做出一个简单的小游戏,五子棋是我想到的第一选择,就立马行动起来。
一.准备工作
首先先创建一个canvas标签,大小可以自定义
<canvas width="850px" height="850px"></canvas>
然后获取画笔
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
这样我们可以正式开始制作了
二.绘制棋盘
五子棋的棋盘都是直线,所以我们使用moveTo和lineTo进行绘制,根据棋盘大小,我们规定一个线间距,以50px为例,可以使用for循环把棋盘绘制出来
// 绘制棋盘
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50,50*i)
ctx.lineTo(800,50*i)
ctx.stroke()
}
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50*i,50)
ctx.lineTo(50*i,800)
ctx.stroke()
}
也为棋盘添加一个合适的背景颜色
canvas{
margin: 0 auto;
margin-top: 30px;
display: block;
background-color: burlywood;
}
三.点击落子
利用事件监听的click事件,和event的offsetX和offsetY的属性来进行点击落子
canvas.addEventListener('click',Game)
function Game(e){
ctx.beginPath()
let x = e.offsetX
let y = e.offsetY
ctx.arc(x,y,20,0,Math.PI*2)
}
但是这样点击的棋子不会落在棋盘的十字处,所以我们需要对下x,y进行处理
function Game(e){
ctx.beginPath()
let x = e.offsetX
let y = e.offsetY
x = parseInt((x + 25) / 50) * 50
y = parseInt((y + 25) / 50) * 50 //四舍五入原理
if(x > 0 && y > 0 && x < 850 && y < 850){//防止落在棋盘外面
ctx.arc(x,y,20,0,Math.PI*2)
}
}
四.棋子颜色和添加文字
在棋盘上方添加文字提示什么颜色的棋子落棋<h1 align="center">点击下棋</h1>
在js中获取过来const text = document.querySelector('h1')
下面我们来控制棋子的颜色,定义一个全局的变量isBlack
// 判断颜色
let isBlack = true
// 点击绘制棋子
canvas.addEventListener('click',Game)
function Game(e){
ctx.beginPath()
let x = e.offsetX
let y = e.offsetY
// 落棋子位置
x = parseInt((x + 25) / 50) * 50
y = parseInt((y + 25) / 50) * 50
if(x > 0 && y > 0 && x < 850 && y < 850){
ctx.arc(x,y,20,0,Math.PI*2)
// 文字改变
if(isBlack) text.innerHTML = '白子落棋'
else text.innerHTML = '黑子落棋'
// 不同颜色
ctx.fillStyle = isBlack ? '#000' : '#fff'
ctx.fill()
isBlack = !isBlack
ctx.stroke()
}
}
五.解决重复落子
我们需要一个二维数组来存储棋子的位置,这样我们每次点击就记录相应的位置,通过判断当前位置是否已经有棋子,再进行落子
// 判断重复落子
let circl = []
for(let j = -4 ; j <= 20 ; j++){
circl[j] = []
}
// 判断颜色
let isBlack = true
// 点击绘制棋子
canvas.addEventListener('click',Game)
function Game(e){
ctx.beginPath()
let x = e.offsetX
let y = e.offsetY
// 判断是否重复
if(circl[parseInt((x + 25) / 50)][parseInt((y + 25) / 50)])
{
text.innerHTML = `已经有棋子,请${isBlack? '黑子' : '白子'}落棋`
return
}
// 洛棋子位置
x = parseInt((x + 25) / 50) * 50
y = parseInt((y + 25) / 50) * 50
if(x > 0 && y > 0 && x < 850 && y < 850){
ctx.arc(x,y,20,0,Math.PI*2)
// 记录棋子位置
circl[parseInt((x + 25) / 50)][parseInt((y + 25) / 50)] = isBlack ? 'black' : 'white'
// 文字改变
if(isBlack) text.innerHTML = '白子落棋'
else text.innerHTML = '黑子落棋'
// 不同颜色
ctx.fillStyle = isBlack ? '#000' : '#fff'
ctx.fill()
isBlack = !isBlack
ctx.stroke()
}
}
六.胜负逻辑
可以利用上面的二维数组来对是否五子连线进行判断,以竖方向的胜负逻辑为例,我们定义棋子上方相同颜色的计数和下方相同颜色的计数,当遇到不同颜色的棋子时计数清零,其他方向类似同理
// 胜负逻辑
function SifWin(row,col){
let up = 1
let down = 1
let target = circl[row][col]
let countU = 0
let countD = 0
while(up <= 4 || down <= 4){
if(circl[row][col - up] === target) countU++
else countU = 0
up++
if(circl[row][col + down] === target) countD++
else countD = 0
down++
if(countU + countD + 1 >= 5) break
}
return countU + countD + 1 >= 5 ? true : false
}
七.胜利弹框和重新开始
当游戏有一方胜利之后我们会出现一个弹框,确认是否重新开始游戏。
<div class="alert">
<div class="button">
<div class="text">
</div>
<button class="button1">重新开始</button>
<button class="button2">取消</button>
</div>
</div>
.alert{
display: none;
width: 500px;
height: 300px;
position: absolute;
background-color: #eff;
top: 50px;
left: 50%;
transform: translate(-50%);
}
.button{
position: relative;
width: 100%;
height: 100%;
text-align: center;
font-size: 50px;
line-height: 250px;
font-weight: 600;
}
.button1{
width: 70px;
height: 30px;
position: absolute;
bottom: 10px;
left: 35%;
}
.button2{
width: 70px;
height: 30px;
position: absolute;
bottom: 10px;
left: 55%;
}
button{
background-color: #09F;
border: none;
border-radius: 5px;
color: #fff;
font-weight: 400;
cursor: pointer;
}
下面是重新开始游戏的逻辑,我们要清空棋盘,同时清空数组,然后绘制新的棋盘
document.querySelector('.button1').addEventListener('click',() => {
regame()
document.querySelector('.alert').style.display = 'none'
})
document.querySelector('.button2').addEventListener('click',( ) => {
document.querySelector('.alert').style.display = 'none'
})
function regame(){
ctx.clearRect(0,0,850,850)
ctx.beginPath()
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50,50*i)
ctx.lineTo(800,50*i)
ctx.stroke()
}
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50*i,50)
ctx.lineTo(50*i,800)
ctx.stroke()
}
canvas.addEventListener('click',Game)
// 判断重复落子
circl = []
for(let j = -4 ; j <= 20 ; j++){
circl[j] = []
}
text.innerHTML = '黑子落棋'
// 判断颜色
isBlack = true
}
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas{
margin: 0 auto;
margin-top: 30px;
display: block;
background-color: burlywood;
}
.alert{
display: none;
width: 500px;
height: 300px;
position: absolute;
background-color: #eff;
top: 50px;
left: 50%;
transform: translate(-50%);
}
.button{
position: relative;
width: 100%;
height: 100%;
text-align: center;
font-size: 50px;
line-height: 250px;
font-weight: 600;
}
.button1{
width: 70px;
height: 30px;
position: absolute;
bottom: 10px;
left: 35%;
}
.button2{
width: 70px;
height: 30px;
position: absolute;
bottom: 10px;
left: 55%;
}
button{
background-color: #09F;
border: none;
border-radius: 5px;
color: #fff;
font-weight: 400;
cursor: pointer;
}
</style>
</head>
<body>
<h1 align="center">点击下棋</h1>
<canvas width="850px" height="850px"></canvas>
<div class="alert">
<div class="button">
<div class="text">
</div>
<button class="button1">重新开始</button>
<button class="button2">取消</button>
</div>
</div>
<script>
const alert = document.querySelector('.text')
const text = document.querySelector('h1')
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// 绘制棋盘
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50,50*i)
ctx.lineTo(800,50*i)
ctx.stroke()
}
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50*i,50)
ctx.lineTo(50*i,800)
ctx.stroke()
}
// 判断重复落子
let circl = []
for(let j = -4 ; j <= 20 ; j++){
circl[j] = []
}
// 判断颜色
let isBlack = true
// 点击绘制棋子
canvas.addEventListener('click',Game)
function Game(e){
ctx.beginPath()
let x = e.offsetX
let y = e.offsetY
// 判断是否重复
if(circl[parseInt((x + 25) / 50)][parseInt((y + 25) / 50)])
{
text.innerHTML = `已经有棋子,请${isBlack? '黑子' : '白子'}落棋`
return
}
// 洛棋子位置
x = parseInt((x + 25) / 50) * 50
y = parseInt((y + 25) / 50) * 50
if(x > 0 && y > 0 && x < 850 && y < 850){
ctx.arc(x,y,20,0,Math.PI*2)
// 记录棋子位置
circl[parseInt((x + 25) / 50)][parseInt((y + 25) / 50)] = isBlack ? 'black' : 'white'
// 文字改变
if(isBlack) text.innerHTML = '白子落棋'
else text.innerHTML = '黑子落棋'
// 不同颜色
ctx.fillStyle = isBlack ? '#000' : '#fff'
ctx.fill()
isBlack = !isBlack
ctx.stroke()
// 判断获胜
let S = SifWin(parseInt((x + 25) / 50),parseInt((y + 25) / 50))
let H = HifWin(parseInt((x + 25) / 50),parseInt((y + 25) / 50))
let XL = XLifWin(parseInt((x + 25) / 50),parseInt((y + 25) / 50))
let XR = XRifWin(parseInt((x + 25) / 50),parseInt((y + 25) / 50))
if(S || H || XL || XR){
alert.innerHTML = `${isBlack? '白子' : '黑子'}获胜`
canvas.removeEventListener('click',Game)
document.querySelector('.alert').style.display = 'block'
return
}
}
}
// 胜负逻辑
function SifWin(row,col){
let up = 1
let down = 1
let target = circl[row][col]
let countU = 0
let countD = 0
while(up <= 4 || down <= 4){
if(circl[row][col - up] === target) countU++
else countU = 0
up++
if(circl[row][col + down] === target) countD++
else countD = 0
down++
if(countU + countD + 1 >= 5) break
}
return countU + countD + 1 >= 5 ? true : false
}
function HifWin(row,col){
let left = 1
let right = 1
let target = circl[row][col]
let countL = 0
let countR = 0
while(left <= 4 || right <= 4){
if(circl[row - left][col] === target) countL++
else countL = 0
left++
if(circl[row + right][col] === target) countR++
else countR = 0
right++
if(countR + countL + 1>= 5) break
}
return countR + countL + 1>= 5 ? true : false
}
function XLifWin(row,col){
let left = 1
let right = 1
let target = circl[row][col]
let countL = 0
let countR = 0
while(left <= 4 || right <= 4){
if(circl[row - left][col - left] === target) countL++
else countL = 0
left++
if(circl[row + right][col + right] === target) countR++
else countR = 0
right++
if(countR + countL + 1>= 5) break
}
return countR + countL + 1>= 5 ? true : false
}
function XRifWin(row,col){
let left = 1
let right = 1
let target = circl[row][col]
let countL = 0
let countR = 0
while(left <= 4 || right <= 4){
if(circl[row + left][col - left] === target) countL++
else countL = 0
left++
if(circl[row - right][col + right] === target) countR++
else countR = 0
right++
if(countR + countL + 1>= 5) break
}
return countR + countL + 1>= 5 ? true : false
}
// 按钮
document.querySelector('.button1').addEventListener('click',() => {
regame()
document.querySelector('.alert').style.display = 'none'
})
document.querySelector('.button2').addEventListener('click',( ) => {
document.querySelector('.alert').style.display = 'none'
})
function regame(){
ctx.clearRect(0,0,850,850)
ctx.beginPath()
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50,50*i)
ctx.lineTo(800,50*i)
ctx.stroke()
}
for(let i = 1 ; i <= 16 ; i++){
ctx.moveTo(50*i,50)
ctx.lineTo(50*i,800)
ctx.stroke()
}
canvas.addEventListener('click',Game)
// 判断重复落子
circl = []
for(let j = -4 ; j <= 20 ; j++){
circl[j] = []
}
text.innerHTML = '黑子落棋'
// 判断颜色
isBlack = true
}
</script>
</body>
</html>