前端基础文章打卡Day4 : 利用canvas制作五子棋小游戏 | 青训营

124 阅读2分钟

今天复习了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;
        }

image.png

三.点击落子

利用事件监听的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>