Online example

Today we will use WebGL to polish a backgammon, the effect picture is as follows:

This article is for those unfamiliar with the basic processes and concepts of WebGL

It is mainly divided into the following steps:

  1. Initialize the checkerboard style
  2. Pick up screen coordinates and render on canvas
  3. Draw round pieces
  4. Convert the coordinates on the canvas to the grid coordinates of the checkerboard
  5. Save the status of the pieces on the board and judge the winner

Pre – work

Auxiliary function

Vec2, VEC4 and flatten functions of Mv.js are introduced to facilitate calculation. Their functions are to store 2 data, 4 data and convert continuous arrays to 32-bit floating point values respectively.

shader

The vertex shader colors were obtained using an IMAGINATIVE global variable and passed into the slice shader by the Varyings variable

<script id="vertex-shader" type="x-shader/x-vertex">
  attribute vec4 vPosition;
    uniform vec4 vColor;
    varying vec4 fColor;
    void
    main() {
      gl_Position = vPosition;
      fColor = vColor;
    }
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
  precision mediump float;
  varying vec4 fColor;
  void main()
  {
    gl_FragColor = fColor;
  }
</script>
Copy the code

Initialize the checkerboard style

Start by declaring some variables and setting the checkerboard to 20×20

let gl;
const canvasSize = Math.min(window.innerHeight, window.innerWidth);
// The number of blocks is 20*20
const boardSegment = 20;
// The number of vertices in the checkerboard, if 'gl.lines' is used to draw the checkerboard. 21 horizontal and vertical lines, 2 vertices for each line, 21x4 vertices in total.
const boardVertexNumber = (boardSegment + 1) * 4;
// Board vertex coordinates
const boardVertex = [];
// The number of vertices (segments) per piece, set to 1 and represented by a single point, will be segmented into multiple vertices when rendering round pieces
const chessSegment = 1;
// Maximum number of pieces
constMaxChess = (boardSegment +1) * *2;
// Maximum number of vertices, board vertices + chess vertices
const maxPoints = boardVertexNumber + maxChess * chessSegment;
// The vertex shader should have the location of the global variable vColor
let colorLoc;
Copy the code

Get checkerboard vertices

The value range of the clipping coordinate X and y axis is [-1, 1], and the length is 2. It is divided into 20 pieces, and 21 lines are needed in total. The interval of each line is 2 / boardSegment = 0.1. When drawn horizontally on the X-axis, the x-value is fixed and the y-value is -1 + I * 0.2. When a vertical line is drawn along the x axis, the y value is fixed and the x value is -1 + I * 0.2.

function initBoard() {
  const rowLineNumber = boardSegment + 1;
  const columnLineNumber = boardSegment + 1;
  const rowHeight = 2 / boardSegment;
  const columnWidth = 2 / boardSegment;
  for (let i = 0; i < rowLineNumber + columnLineNumber; i++) {
    if (i < rowLineNumber) {
      boardVertex.push(
        vec2(1.0.- 1 + i * rowHeight),
        vec2(1.0.- 1 + i * rowHeight),
      );
    } else {
      boardVertex.push(
        vec2(- 1 + (i % rowLineNumber) * columnWidth, - 1),
        vec2(- 1 + (i % rowLineNumber) * columnWidth, 1)); }}}Copy the code

WebGL program

Let’s start the WebGL section

The usual way to draw all the vertices at once is with bufferData(), which passes data directly to buffer:

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
Copy the code

But gobang requires each click of the checkerboard dynamic incoming data. Here’s another use of bufferData, which tells the buffer how much space it needs, and then uses bufferSubData to pass values into it each time:

gl.bufferData(gl.ARRAY_BUFFER, verticesSize, gl.STATIC_DRAW)
// Pass values to the buffer
gl.bufferSubData(
    gl.ARRAY_BUFFER,
    offset,
     new Float32Array(vertices),
  );
Copy the code

BufferSubData The second parameter specifies the offset in bytes to start data replacement, similar to splice(offset, 0, new Float32Array(vertices)).

window.onload = function() {
  const canvas = document.getElementById("gl-canvas");
  canvas.width = canvasSize;
  canvas.height = canvasSize;
  gl = canvas.getContext("webgl");
  if(! gl)return false;
  gl.viewport(0.0, canvas.width, canvas.height);
  gl.clearColor(204 / 255.161 / 255.129 / 255.1.0); // Set the canvas color to something like wood
  
  // The shader
  const vertexShaderSource = document.getElementById("vertex-shader").text;
  const fragmentShaderSource = document.getElementById("fragment-shader").text;
  const program = createProgram(gl, vertexShaderSource, fragmentShaderSource);
  gl.useProgram(program);
  
  / / the vertices
  const vBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    8 * (maxPoints), // Buffer size, each vertex is 2 32-bit floating-point numbers, total 8 bytes
    gl.STATIC_DRAW,
  );
  const vPosition = gl.getAttribLocation(program, "vPosition");
  gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false.0.0);
  gl.enableVertexAttribArray(vPosition);
  // Get the position of the global variable vColor from the vertex shader
  colorLoc = gl.getUniformLocation(program, 'vColor')
  
  // Initialize the checkerboard
  initBoard(); // Get the vertices of the board
  gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer); // Bind the current buffer
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, flatten(boardVertex)); // Pass values to the buffer
  render()
}
Copy the code

The clear canvas, set vertex color, and draw functions are wrapped separately into render functions for subsequent reuse.

function render() {
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.uniform4fv(colorLoc, vec4(0.0.0.0.0.0.1.0)); // The checkerboard grid is black
  gl.drawArrays(gl.LINES, 0, boardVertexNumber); // Using gl.LINES, draw each two points as a separate line segment
}
Copy the code

So the pattern of the checkerboard is drawn

Pick up screen coordinates and render them on WebGL

Screen coordinates are converted to clipping coordinates

First we need a function to convert the mouse-picked screen coordinates to the clipping coordinates needed by WebGL, i.e

X-axis value range [0, canvas.width]=>[-1, 1]

[0, canvas.height]=>[1, -1]

X/width = [0, 1], (x/width) * 2-1 = [-1, 1], (x/width) * 2-1 = [-1, 1], (x/width) * 2-1 = [-1, 1], (x/width) * 2-1 = [-1, 1], (y/height) * 2 Here is the conversion function:

/** * Screen coordinates to WebGL clipping coordinates * @param {Number} x screen coordinates x coordinates * @param {Number} y screen coordinates Y coordinates * @param {Number} width Canvas width * @param {Number} height Height of the canvas */
function vertex2Gl(x, y, width, height) {
  return {
    x: (x / width) * 2 - 1.y: 1 - (y / height) * 2}; }Copy the code

Click on the canvas to add a piece

Next, you need to add a click event to the canvas

window.onload = function() {
    //...
    render()
+ canvas.addEventListener("click", evt => {
+ const { clientX: x, clientY: y } = evt;
+ const { width, height } = canvas;
+ addChess(vBuffer, x, y, width, height);
+});
    
}
Copy the code

Each time you click on the canvas to perform the addChess() function, you need to execute the bufferSubData() function to pass the data to the buffer, because the offset is set. So we need a variable to record the total number of vertices currently rendered

// ...
let colorLoc;
+ // Number of vertices
+ let chessVertexCount = 0;
Copy the code
// Add pieces
function addChess(vBuffer, x, y, width, height) {
  // Get the clipping coordinates
  const { x: x_gl, y: y_gl } = vertex2Gl(x, y, width, height);
  gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
  gl.bufferSubData(
    gl.ARRAY_BUFFER,
    (boardVertexNumber + chessVertexCount) * 8.// Offeset offset is the size in bytes of the board vertices plus the vertices of the drawn pieces
    flatten(vec2(x_gl, y_gl)),
  );
  // Add the number of vertices one at a time
  chessVertexCount += chessSegment;
  render();
}
Copy the code

Render function also needs to add logic to draw pieces, loop drawArrays() function, draw starting position is the number of board vertices ➕ number of drawn pieces vertices, number of pieces vertices drawn each time

function render() { gl.clear(gl.COLOR_BUFFER_BIT); Gl. uniform4FV (colorLoc, vec4(0.0, 0.0, 0.0, 1.0)); // The board grid is black GL.drawarrays (gl.lines, 0, boardVertexNumber); // Using gl.LINES, draw each two points as a separate line segment+ for (let i = 0; i < chessVertexCount; i += chessSegment) {
+ gl.drawArrays(gl.POINTS, boardVertexNumber + i, chessSegment);
+}
}
Copy the code

Click on the canvas to get a piece:

Set chess styles

You can see that each point is small and difficult to see: we can use the vertex shader gl_PointSize to make the point larger

<script id="vertex-shader" type="x-shader/x-vertex">
  attribute vec4 vPosition;
    uniform vec4 vColor;
    varying vec4 fColor;
    void
    main() {
+ gl_PointSize = 20.0;
      gl_Position = vPosition;
      fColor = vColor;
    }
</script>
Copy the code

That’s better

All black doesn’t work either. It needs to be black and white

ChessSegment = 1; chessSegment = 1; I/chessSegment = 1

function render() { gl.clear(gl.COLOR_BUFFER_BIT); Gl. uniform4FV (colorLoc, vec4(0.0, 0.0, 0.0, 1.0)); // The board grid is black GL.drawarrays (gl.lines, 0, boardVertexNumber); // Use gl.LINES to draw a separate line segment for (let I = 0; i < chessVertexCount; i += chessSegment) {+ const color = i / chessSegment % 2 === 0 ? Vec4 (0.0, 0.0, 0.0, 1.0) : vec4 (1.0, 1.0, 1.0, 1.0)
+ gl.uniform4fv(colorLoc, color);gl.drawArrays(gl.POINTS, boardVertexNumber + i, chessSegment); }}Copy the code

That’s it

Draw round pieces using coordinates

So how do you draw a circle? After filtering the side drawing types, we find that gl.triangle_fan is relatively easy to implement. We only need to find the vertices that form a circle with several equal distances

P of r cosine theta, r sine theta.
P(Ox + r * cos(θ), Oy + r * sin(θ))

Let’s form a circle of 20 points for the moment. Of course, the more dots, the closer to the circle it will render.

- const chessSegment = 1;
+ const chessSegment = 20;
Copy the code

The following can be used to circle the Angle of each point, using the trigonometric function to figure out the coordinates of the chess pieces:

@param {Number} x x coordinates @param {Number} y y coordinates */
function vertexChess(x, y) {
  // The chessman radius is half the length of the grid
  const chessRadius = 2 / boardSegment / 2;
  const chessVertex = [];
  for (let i = 0; i < chessSegment; i++) {
    const x_chess =
      x + chessRadius * Math.cos(((Math.PI * 2) / chessSegment) * i);
    const y_chess =
      y + chessRadius * Math.sin(((Math.PI * 2) / chessSegment) * i);
    chessVertex.push(vec2(x_chess, y_chess));
  }
  return chessVertex;
}
Copy the code

Next you need to modify some code:

Function addChess(vBuffer, x, y, width, height) {const {x: x_gl, y: y_gl } = vertex2Gl(x, y, width, height);+ const chessVertex = vertexChess(x_gl, y_gl);  
  gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
  gl.bufferSubData(
    gl.ARRAY_BUFFER,
    (boardVertexNumber + chessVertexCount) * 8,
- flatten(vec2(x_gl, y_gl)),
+ flatten(chessVertex),); ChessVertexCount += chessSegment; render(); } function render() { gl.clear(gl.COLOR_BUFFER_BIT); Gl. uniform4FV (colorLoc, vec4(0.0, 0.0, 0.0, 1.0)); // The board grid is black GL.drawarrays (gl.lines, 0, boardVertexNumber); // Use gl.LINES to draw a separate line segment for (let I = 0; i < chessVertexCount; i += chessSegment) {- gl.drawArrays(gl.POINTS, boardVertexNumber + i, chessSegment);
+ gl.drawArrays(gl.TRIANGLE_FAN, boardVertexNumber + i, chessSegment); // Change the drawing type<script id="vertex-shader" type="x-shader/ x-shader" > attribute vec4 vPosition; uniform vec4 vColor; varying vec4 fColor; void main() {- gl_PointSize = 20.0;
      gl_Position = vPosition;
      fColor = vColor;
    }
</script>
Copy the code

So every time you click on the screen, the previous dot becomes a dot

Convert WebGL coordinates to checkerboard grid coordinates

At present, the coordinates of our pieces are the click position of the mouse, which is obviously wrong. The pieces should be at the intersection of the two lines of the grid. The coordinates of each intersection are a multiple of 2/boardSegment 0.1(2/20). Click on the range near the intersection to convert it to the intermediate intersection.

The function of the chess piece coordinates obtained from the clipping coordinates can be expressed as:

/** * webGL clipping coordinates are converted to piece coordinates * @param {Number} x webGL x coordinates * @param {Number} y webGL y coordinates */
function vertex2Board(x, y) {
  // The size of each grid
  const diameter = 2 / boardSegment;
  return {
    x: Math.round(x / diameter) * diameter,
    y: Math.round(y / diameter) * diameter,
  };
}
Copy the code

Then modify some code:

Function addChess(vBuffer, x, y, width, height) {const {x: x_gl, y: y_gl } = vertex2Gl(x, y, width, height);+ const { x: x_board, y: y_board } = vertex2Board(x_gl, y_gl);
- const chessVertex = vertexChess(x_gl, y_gl);
+ const chessVertex = vertexChess(x_board, y_board); // Get all vertex coordinates of the chess piece from the center coordinatesgl.bindBuffer(gl.ARRAY_BUFFER, vBuffer); gl.bufferSubData( gl.ARRAY_BUFFER, (boardVertexNumber + chessVertexCount) * 8, flatten(vec2(x_gl, y_gl)), flatten(chessVertex), ); ChessVertexCount += chessSegment; render(); }Copy the code

So every time you click on the board, the position of the piece is always at the intersection

Save the status of the pieces on the board and judge the winner

Initialize the checkerboard state

To know whether to win or lose, we need to express the states of the pieces in data. We can use the following array to represent the states of the board in the picture below:

[[0.0.1.2.0.0],
[0.0.1.2.0.0],
[0.0.1.2.0.0],
[0.0.0.0.0.0],
[0.0.0.0.0.0],
[0.0.0.0.0.0]]Copy the code

0
1
2

Start by adding a checkerboard state variable and encapsulating a function that initializes the checkerboard state data

/ /... let chessVertexCount = 0; // checkerboard state+ const boardState = [];
Copy the code
/ * * * initializes the board move later state 0 no pieces, 1 sunspots, 2 albino * [*] [0, 0,..., * [0, 0,...), * *]... * /
function initBoardState() {
  for (let i = 0; i < boardSegment + 1; i++) {
    boardState[i] = [];
    for (let j = 0; j < boardSegment + 1; j++) {
      boardState[i][j] = 0; }}}Copy the code

Checkerboard state data coordinate function

Second, you need a function that converts the checkerboard coordinates to the coordinates in the checkerboard state data.

For example, 🌰, in the middle of a 5×5 board where the pieces are clipped at coordinates (0, 0), we need to figure out that (2,2) represents the third column in the third row

This is similar to how screen coordinates were converted to clipping coordinates, except in reverse, the clipping coordinates are now converted to rows and columns of the array (like screen coordinates, the minimum values are in the upper left). Take a look at the picture below for comparison:

Screen coordinates to cropping coordinates look like this

function vertex2Gl(x, y, width, height) {
  return {
    x: (x / width) * 2 - 1.y: 1 - (y / height) * 2}; }Copy the code

You just invert the checkerboard state coordinates and you get it, but here width and height become boardSegment which is the number of rows and columns on the checkerboard.

{
    x: ((x + 1) * width) / 2.y: ((1 - y) * height) / 2,}Copy the code

Because of the accuracy of floating-point calculation, the result will be biased. The index of the array must be an integer, so it needs to be rounded

@param {Number} x x coordinates @param {Number} y y coordinates */
function vertex2State(x, y) {
  const width = boardSegment;
  const height = boardSegment;
  return {
    x: Math.round(((x + 1) * width) / 2),
    y: Math.round(((1 - y) * height) / 2),}; }Copy the code

Add a chess piece to modify the checkerboard state

Finally, let’s modify some code.

Add a piece color identifier to determine the current piece color.

let const boardState = [];
+ // Pawn color identifier
let colorFlag = true;
Copy the code
Function addChess(vBuffer, x, y, width, height) {const {x: x_gl, y: y_gl } = vertex2Gl(x, y, width, height); const { x: x_board, y: y_board } = vertex2Board(x_gl, y_gl);+ const { x: x_state, y: y_state } = vertex2State(x_board, y_board);// This position has a piece return+ if (boardState[y_state][x_state]) return;// 1 is black,2 is white+ boardState[y_state][x_state] = colorFlag ? 1:2;const chessVertex = vertexChess(x_board, y_board); Gl.bindbuffer (gl.array_buffer, vBuffer); gl.bufferSubData( gl.ARRAY_BUFFER, (boardVertexNumber + chessVertexCount) * 8, flatten(vec2(x_gl, y_gl)), flatten(chessVertex), ); ChessVertexCount += chessSegment; render();+ colorFlag = ! colorFlag; // Change the color of the pieces after rendering
}
Copy the code

You can see that each time you add a piece, the board state changes accordingly:

After the board state changes, judge the winner

Finally the last step, how to win? Transverse, longitudinal, two bevel directions. You win if you have five identical pieces in one of the four directions.

Take A lateral judgment for example 🌰 : after the chess piece A falls, the first left side of the search, refers to the encounter and A color is not the same as the chess piece or reach the left border stop. Then search to the right of A until you encounter A piece with A different color or stop at the right boundary. During the recording period, there are several pieces that are the same. The same goes for all the other directions. You look in one direction first, and then in the other direction. Here are four examples.

/** ** @param {Number} x X coordinates of the dropped piece in the checkerboard state * @param {Number} y X coordinates of the dropped piece in the checkerboard state * @param {Number} chessType Type of the piece 1 or 2 */
function check(x, y, chessType){
  // Same number of pieces
  let connected = 1;
  // Left is index decrement direction
  let decreaseDirection = 1;
  // On the right is the index increment direction
  let increaseDirection = 1;
  // It only takes four searches to remove dropped pieces
  for (let i = 0; i < 4; i++) {
    // First look to the left
    // The left piece state is equal to the color of the dropped piece
    if (x - decreaseDirection >= 0 && boardState[y][x - decreaseDirection] === chessType) {
        // The same number of pieces increase
        connected ++ ;
         // Continue to the next position on the leftDecreaseDirection + +;// The state of the right piece is equal to the color of the fallen piece
    } else if (x + increaseDirection <= boardSegment && boardState[y][x + increaseDirection] === chessType) {
         // The same number of pieces increase
        connected ++ ;
         // Proceed to the next location on the right sideIncreaseDirection + +; }}return connected >= 5;
}
Copy the code

Let’s modify some code:

Function addChess(vBuffer, x, y, width, height) {const {x: x_gl, y: y_gl } = vertex2Gl(x, y, width, height); const { x: x_board, y: y_board } = vertex2Board(x_gl, y_gl); const { x: x_state, y: y_state } = vertex2State(x_board, y_board); Return if (boardState[y_state][x_state]) return; BoardState [y_state][x_state] = colorFlag? 1:2; const chessVertex = vertexChess(x_board, y_board); Gl.bindbuffer (gl.array_buffer, vBuffer); gl.bufferSubData( gl.ARRAY_BUFFER, (boardVertexNumber + chessVertexCount) * 8, flatten(vec2(x_gl, y_gl)), flatten(chessVertex), ); ChessVertexCount += chessSegment; render(); // Check to see if you win+ checkFinished(x_state, y_state, colorFlag);colorFlag = ! colorFlag; // Change the color of the pieces after rendering.Copy the code
/** ** @param {Number} x X coordinates of the dropped pawn in the board state * @param {Number} y X coordinates of the dropped pawn in the board state * @param {Boolean} colorFlag The current color of the pawn is true black False white * /
function checkFinished(x, y, colorFlag) {
  const chessType = colorFlag ? 1 : 2;
  if(check(x, y, chessType)){
      console.log('win')}}Copy the code

Looks good

Top right. - Bottom left

// Upper right - lower left
// (x does not reach the right boundary && y does not reach the upper boundary) && the color of the pieces in the upper right direction is the color of the pieces dropped
if ((x + decreaseDirection <= boardSegment && y - decreaseDirection >= 0) && 
        boardState[y - decreaseDirection][x + decreaseDirection] === chessType) {
          connected += 1;
          decreaseDirection++;
        // (x does not reach the left boundary && y does not reach the bottom boundary) && the color of the lower left direction is the color of the falling piece
        } else if ((x - increaseDirection >= 0 && y + increaseDirection <= boardSegment) &&
        boardState[y + increaseDirection][x - increaseDirection] === chessType) {
          connected += 1;
          increaseDirection++;
    }
Copy the code

All types of check condition functions are listed below. The judgment conditions for the four directions are basically the same: the next coordinate is within the checkerboard range && the chess pieces are of the same color:

/** ** @param {Number} x X coordinates of the dropped piece in the checkerboard state * @param {Number} y X coordinates of the dropped piece in the checkerboard state * @param {Number} chessType Type of the piece 1 or 2 * @param {String} checkType checkType row,col,diagonal_left(upper left - lower right),diagonal_right(upper right - lower left) */
function check(x, y, chessType, checkType) {
  let connected = 1;
  // index decrement directions are left, up, left, and right
  let decreaseDirection = 1;
  // index adds directions right, down, right, and left
  let increaseDirection = 1;
  for (let i = 0; i < 4; i++) {
    // Add and subtract direction coordinates and drop state checks for each check type
    switch (checkType) {
      case 'row':
        if (x - decreaseDirection >= 0 && boardState[y][x - decreaseDirection] === chessType) {
          connected += 1;
          decreaseDirection++;
        } else if (x + increaseDirection <= boardSegment && boardState[y][x + increaseDirection] === chessType) {
          connected += 1;
          increaseDirection++;
        }
        break;
      case 'col':
        if (y - decreaseDirection >= 0 && boardState[y - decreaseDirection][x] === chessType) {
          connected += 1;
          decreaseDirection++;
        } else if (y + increaseDirection <= boardSegment && boardState[y + increaseDirection][x] === chessType) {
          connected += 1;
          increaseDirection++;
        }
        break;
      case 'diagonal_left':
        if ((x - decreaseDirection >= 0 && y - decreaseDirection >= 0)
        && boardState[y - decreaseDirection][x - decreaseDirection] === chessType) {
          connected += 1;
          decreaseDirection++;
        } else if((x + increaseDirection <= boardSegment && y + increaseDirection <= boardSegment) && boardState[y + increaseDirection][x  + increaseDirection] === chessType) { connected +=1;
          increaseDirection++;
        }
        break;
      case 'diagonal_right':
        if ((x + decreaseDirection <= boardSegment && y - decreaseDirection >= 0) && 
        boardState[y - decreaseDirection][x + decreaseDirection] === chessType) {
          connected += 1;
          decreaseDirection++;
        } else if ((x - increaseDirection >= 0 && y + increaseDirection <= boardSegment) &&
        boardState[y + increaseDirection][x - increaseDirection] === chessType) {
          connected += 1;
          increaseDirection++;
        }
        break;
      default:
        break; }}return connected >= 5;
}
Copy the code

Modify the checkFinished() function to display the current winner with a label:

/** ** @param {Number} x X coordinates of the dropped pawn in the board state * @param {Number} y X coordinates of the dropped pawn in the board state * @param {Boolean} colorFlag The current color of the pawn is true black False white * /
function checkFinished(x, y, colorFlag) {
  const chessType = colorFlag ? 1 : 2;
  if (check(x, y, chessType, "row")) {
    winHandle(colorFlag);
    return;
  } else if (check(x, y, chessType, "col")) {
    winHandle(colorFlag);
    return;
  } else if (check(x, y, chessType, "diagonal_left")) {
    winHandle(colorFlag);
    return;
  } else if (check(x, y, chessType, "diagonal_right")) {
    winHandle(colorFlag);
    return; }}function winHandle(colorFlag) {
  winner = colorFlag ? "Spot" : "白子";
  winnerEl.innerText = winner + "胜"
}
Copy the code
/ / winner+ let winner;
+ const winnerEl = document.getElementById('winner')/ /... // Canvas. AddEventListener ("click", evt => {+ if (winner) return;
    const { clientX: x, clientY: y } = evt;
    const { width, height } = canvas;
    addChess(vBuffer, x, y, width, height);
});
Copy the code

Take a look at the effect:

Experience optimization

At this point, the function is basically finished, but there is still an experience to optimize, that is, every time there is no mark, easy to forget where the last time. So we need to put a mark on each piece. This uses vertex2State() to get the coordinate function in the checkerboard state from the coordinates of the pieces. This function is also converted from the coordinates of the pieces to the screen coordinate function. They all work the same way. To avoid confusion, declare a new function:

@param {Number} x x coordinates @param {Number} y y coordinates */
function vertex2Screen(x, y, width, height) {
  return {
    x: (x + 1) / 2 * width,
    y: (1 - y) / 2 * height
  };
}
Copy the code

Finally, reset the marker position after each render

// Last piece mark+ const lastEl = document.getElementById('last');Function addChess(vBuffer, x, y, width, height) {//... render(); // Get the screen coordinates of the pieces and mark the position of the last piece+ const { x: x_screen, y: y_screen } = vertex2Screen(x_board, y_board,width, height);
+ lastEl.style.display = `block`; // Hide by default
+ lastEl.style.left = `${x_screen}px`;
+ lastEl.style.top = `${y_screen}px`;CheckFinished (x_state, y_state, colorFlag); colorFlag = ! colorFlag; }Copy the code

Done 👏👏👏👏👏

reference

  • Interactive Computer Graphics: A Top-down Approach based on WebGL (7th edition)