Vue + websocket + ES6 + canvas to make [draw and guess] games

Time:2021-8-22

Project address:https://github.com/jrainlau/draw-something

Download & run

git clone [email protected]:jrainlau/draw-something.git
cd draw-something

Node ws-server.js // start the websocket server

NPM run dev // run the client program

Then the browser opens localhost: 8080

Effect preview:

Vue + websocket + ES6 + canvas to make [draw and guess] games

Overall architecture

Because I’m busy, I’ve been playing small games such as you draw and I guess with my friends. I suddenly thought of whether I could make one myself. Anyway, I’m also idle. At the same time, I can learn the usage of websocket.

First, analyze the overall architecture:

Vue + websocket + ES6 + canvas to make [draw and guess] games

As you can see, the overall architecture is very simple, just one server and two clients.

  • Websocket server: provides data synchronization and content distribution functions, written in nodejs.

  • Drawing canvas: the area for drawing. At the same time, keywords can be obtained, and the drawn content will be synchronized to the guessing canvas.

  • Guess canvas: synchronize the self drawing canvas. The input box can submit keywords to check whether the answer is correct.

Let’s look at the specific code implementation.

Websocket server

Server adoptionnode.jsBuilt and usedwslibraryImplement websocket function. Create a new one namedws-socket.jsThe code of the file is as follows:

/*** ws-socket.js ***/

'use strict'
//Instantiate the websocketserver object and listen on port 8090
const WebSocketServer = require('ws').Server
  , wss = new WebSocketServer({port: 8090})

//Define keyword array
let wordArr = ['Monkey', 'Dog', 'Bear', 'Flower', 'Girl']

wss.on('connection', (ws) => {
    console.log('connected.')
    
    //Get a keyword at random
    let keyWord = ((arr) => {
            let num = Math.floor(Math.random()*arr.length)
            return arr[num]
        })(wordArr)
        
    //When the server receives a message from the client
    //Judge whether the message content is equal to the keyword
    //Send messages to all clients at the same time
    ws.on('message', (message) => {
        console.log('received: %s', message)
        if (message == keyWord) {
            console.log('correct')
            wss.clients.forEach((client) => {
                Client. Send ('correct!! ')
            })
        } else {
            console.log('wrong')
            wss.clients.forEach((client) => {
                client.send(message)
            })
        }
    })
    
    //When the server initializes, a keyword is provided to the client
    wss.clients.forEach((client) => {
        client.send('keyword:' + keyWord)
    })
})

The use method is basically in accordance withwslibraryJust use the document. amongws.on('message', (message) => { .. })Method will be executed when receiving a message from the client. Using this method, we can continuously send the coordinates of the drawing site from the drawing canvas to the server, and then.send()Method to distribute the coordinates, obtain the coordinates in the guessing canvas, and realize the synchronization of drawing data.

Client structure

As a client, I chosevueDevelopment becausevueEasy and fast to use. In advance, this project is only used for daily learning and hand training, not Vue, so there are many places I want to facilitate violent use, such asdocument.getElementById()And so on. I’ll change it to conform to it when I have a chance in the futurevueAesthetic code~

The client structure is as follows:

|
|-- script
|       |-- components
|       |        |-- drawing-board.vue
|       |        |-- showing-board.vue
|       |
|       |-- App.vue
|       |
|       |-- index.js
|
|-- index.html

Please browse the detailed code directlyproject, only the key part of the code is analyzed here.

Drawing canvas

be located./script/components/Yesdrawing-board.vueThe file is the drawing canvas component. First, we define aDrawClass, which contains all drawing related functions.

/*** drawing-board.vue ***/


'use strict'

class Draw {
    constructor(el) {
        this.el = el
        this.canvas = document.getElementById(this.el)
        this.cxt = this.canvas.getContext('2d')
        this.stage_info = canvas.getBoundingClientRect()
        //Record the coordinates of the drawing site
        this.path = {
            beginX: 0,
            beginY: 0,
            endX: 0,
            endY: 0
        }
    }
    //Initialization
    init(ws, btn) {
        this.canvas.onmousedown = () => {
            this.drawBegin(event, ws)
        }
        this.canvas.onmouseup = () => {
            this.drawEnd()
            ws.send('stop')
        }
        this.clearCanvas(ws, btn)
    }
    
    drawBegin(e, ws) {
        window.getSelection() ? window.getSelection().removeAllRanges() : document.selection.empty()
        this.cxt.strokeStyle = "#000"
        
        //Start a new path
        this.cxt.beginPath()
        this.cxt.moveTo(
            e.clientX - this.stage_info.left,
            e.clientY - this.stage_info.top
        )
        //Record starting point
        this.path.beginX = e.clientX - this.stage_info.left
        this.path.beginY = e.clientY - this.stage_info.top

        document.onmousemove = () => {
            this.drawing(event, ws)
        }
    }
    
    drawing(e, ws) {
        this.cxt.lineTo(
            e.clientX - this.stage_info.left,
            e.clientY - this.stage_info.top
        )
        //Record end point
        this.path.endX = e.clientX - this.stage_info.left
        this.path.endY = e.clientY - this.stage_info.top
        //Send bitmap coordinates to the server
        ws.send(this.path.beginX + '.' + this.path.beginY + '.' + this.path.endX + '.' + this.path.endY)

        this.cxt.stroke()
    }
    
    drawEnd() {
        document.onmousemove = document.onmouseup = null
    }
    
    clearCanvas(ws, btn) {
        //Click the button to empty the canvas
        btn.onclick = () => {
            this.cxt.clearRect(0, 0, 500, 500)
            ws.send('clear')
        }
    }
}

Well, I believe it’s easy to understand the logic in the code. The key isdrawing()The coordinates should be continuously sent to the server.

Well definedDrawClass, inreadyStage use:

ready: () => {
        const ws = new WebSocket('ws://localhost:8090')
        let draw = new Draw('canvas')
        //Empty canvas button
        let btn = document.getElementById('btn')
        //Execute after establishing a connection with the server
        ws.onopen = () => {
            draw.init(ws, btn)
        }
        //Determine the message from the server and operate
        ws.onmessage = (msg) => {
            msg.data.split(':')[0] == 'keyword' ?
                document.getElementById('keyword').innerHTML = msg.data.split(':')[1] :
                false
        }
    }

Guess canvas

The guessing canvas is very simple. You only need to define a canvas canvas, and then receive the coordinates sent by the server and draw it. Look at the code:

ready: () => {
            'use strict'
            const ws = new WebSocket('ws://localhost:8090');
            const canvas = document.getElementById('showing')
            const cxt = canvas.getContext('2d')
            //Reset path start point
            //In order to avoid repeatedly defining the starting point of the path in the same place
            let moveToSwitch = 1
            ws.onmessage = (msg) => {
              let pathObj = msg.data.split('.')
              cxt.strokeStyle = "#000"
              
              if (moveToSwitch && msg.data != 'stop' && msg.data != 'clear') {
                  cxt.beginPath()
                  cxt.moveTo(pathObj[0], pathObj[1])
                  moveToSwitch = 0
              } else if (!moveToSwitch && msg.data == 'stop') {
                  cxt.beginPath()
                  cxt.moveTo(pathObj[0], pathObj[1])
                  moveToSwitch = 1
              } else if (moveToSwitch && msg.data == 'clear') {
                  cxt.clearRect(0, 0, 500, 500)
              }Else if (MSG. Data = = 'correct!!'){
                  Alert ('congratulations on your correct answer!! ')
              }

              cxt.lineTo(pathObj[2], pathObj[3])
              cxt.stroke()
            }

            ws.onopen = () => {
                let submitBtn = document.getElementById('submit')
                //Send answer to server
                submitBtn.onclick = () => {
                    let keyword = document.getElementById('answer').value
                    ws.send(keyword)
                }
            }
        }

Here, the game is ready to play! However, there are still many details to be strengthened and modified, such as choosing the color of the brush, scoring by multiple users, and so on.

Postscript

Although they are rough, they have learned a lot, especially in the fields of websocket and canvas, which I am not familiar with.

Choosing ES6 can really greatly improve work efficiency,ClassThe emergence of grammar can’t be better. It’s just learning as a teacherjQueryFor me, ES6 is really small and fresh.

Welcome to continue to pay attention to my column and will continue to deliver dry goods. Please look forward to it!