javascript - 制作井字游戏,无法制作 'x'

标签 javascript html canvas frontend tic-tac-toe

我正在尝试自学 JavaScript 游戏开发。我选择保留棋盘上所有可能的位置,以便游戏需要在逻辑对象中渲染 x 或 o 作为可能的移动。我不知道如何在要出现的矩形区域内绘制 x。我希望玩家最终单击或触摸可能移动对象的矩形区域中的任何空间。我怎么做?当我需要创建它们实例而不知道玩家将点击或触摸哪里时,如何重做?

// the stage object holds the HTML5 canvas, it's 2d context, and a self starting function that sizes it. (unless all ready fired, canvas is not defined.)
var stage = {
    canvas: document.getElementById('canvas'),
    context: this.canvas.getContext('2d'),
    full_screen: (function () {
        this.canvas.width = document.documentElement.clientWidth;
        this.canvas.height = window.innerHeight;
        this.canvas.style.border = '1px solid black';
        console.log(this.canvas);
        return this.canvas;
    })()
};

stage.width = stage.canvas.width;
stage.height = stage.canvas.height;


var init = function () {
// ui for the game
var button = {
    pause: document.getElementById('pause'),
    restart: document.getElementById('restart'),
    options: document.getElementById('opt')
};

// this function assigns functions the ui buttons
var functionality = function () {
    button.pause.onclick = pause;
    button.restart.onclick = restart;
    button.options.onclick = options;
};

var logic = {
    player: { score: 0 },
    cpu: { score: 0 },
    possible_moves: {
        x: 0,
        y: 0,
        top_left: {
            x: stage.width * .05,
            y: stage.height * .02,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        top_middle: {
            x: stage.canvas.width * .385,
            y: stage.canvas.height * .02,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        top_right: {
            x: stage.canvas.width * .715,
            y: stage.canvas.height * .02,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        middle_left: {
            x: stage.canvas.width * .05,
            y: stage.canvas.height * .35,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        middle_middle: {
            x: stage.canvas.width * .385,
            y: stage.canvas.height * .35,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        middle_right: {
            x: stage.canvas.width * .715,
            y: stage.canvas.height * .35,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        bottom_left: {
            x: stage.canvas.width * .05,
            y: stage.canvas.height * .68,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        bottom_middle: {
            x: stage.canvas.width * .385,
            y: stage.canvas.height * .68,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        bottom_right: {
            x: stage.canvas.width * .715,
            y: stage.canvas.height * .68,
            width: stage.width * .22,
            height: stage.height * .22,
            draw: function () {
                stage.context.beginPath();
                stage.context.lineWidth = 1;
                stage.context.rect(this.x, this.y, this.width, this.height);
                stage.context.stroke();
            }
        },
        draw_top_row: function () {
            logic.possible_moves.top_left.draw();
            logic.possible_moves.top_middle.draw();
            logic.possible_moves.top_right.draw();
        },
        draw_middle_row: function () {
            logic.possible_moves.middle_left.draw();
            logic.possible_moves.middle_middle.draw();
            logic.possible_moves.middle_right.draw();
        },
        draw_bottom_row: function () {
            logic.possible_moves.bottom_left.draw();
            logic.possible_moves.bottom_middle.draw();
            logic.possible_moves.bottom_right.draw();
        },
        draw_left_column: function () {
            logic.possible_moves.top_left.draw();
            logic.possible_moves.middle_left.draw();
            logic.possible_moves.bottom_left.draw();
        },
        draw_middle_column: function () {
            logic.possible_moves.top_middle.draw();
            logic.possible_moves.middle_middle.draw();
            logic.possible_moves.bottom_middle.draw();
        },
        draw_right_column: function () {
            logic.possible_moves.top_right.draw();
            logic.possible_moves.middle_right.draw();
            logic.possible_moves.bottom_right.draw();
        },
        draw_left_to_right_diagonal: function () {
            logic.possible_moves.top_left.draw();
            logic.possible_moves.middle_middle.draw();
            logic.possible_moves.bottom_right.draw();
        },
        draw_right_to_left_diagonal: function () {
            logic.possible_moves.top_right.draw();
            logic.possible_moves.middle_middle.draw();
            logic.possible_moves.bottom_left.draw();
        },
        draw_all_moves: function () {
            logic.possible_moves.top_left.draw();
            logic.possible_moves.top_middle.draw();
            logic.possible_moves.top_right.draw();
            logic.possible_moves.middle_left.draw();
            logic.possible_moves.middle_middle.draw();
            logic.possible_moves.middle_right.draw();
            logic.possible_moves.bottom_left.draw();
            logic.possible_moves.bottom_middle.draw();
            logic.possible_moves.bottom_right.draw();
        },
        generate_logic_map: (function () {

        })()
    }
};

// I had to add the scoreboard to the logic object as an after thought because I wanted to just reference the two individual player and cpu objects in case I need to increase complextity to those cbjects seperately. Also, jaascript won't allow me to reference these propties "inside" the object.
logic.score_board = {
    p: logic.player.score,
    c: logic.cpu.score
};

// this object holds the visual elements of the game
var assets = {
    x: {
        left_to_right: {
            x1: logic.possible_moves.top_left.x,
            y1: logic.possible_moves.top_left.y,
            x2: logic.possible_moves.top_left.width,
            y2: logic.possible_moves.top_left.height,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
                console.log(this.x1, this.x2, this.y1, this.y2);
            }
        },
        right_to_left: {
            x1: logic.possible_moves.top_left.width,
            y1: logic.possible_moves.top_left.height,
            x2: 0,
            y2: 43,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
                console.log(this.x1, this.x2, this.y1, this.y2);
            }
        },
        draw: function () {
            console.log(this.left_to_right.x1, this.left_to_right.y1, this.left_to_right.x2, this.left_to_right.y2);
            stage.context.lineWidth = 5;
            stage.context.strokeStyle = 'black';
            this.left_to_right.draw();
            //this.right_to_left.draw();
        }
    },
    o: {},
    grid: {
        x: 0,
        y: 0,
        horizontal_line_l: {
            x1: stage.canvas.width * .02,
            y1: stage.canvas.height * .33,
            x2: stage.canvas.width * .98,
            y2: stage.canvas.height * .33,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
            }
        },
        horizontal_line_r: {
            x1: stage.canvas.width * .02,
            y1: stage.canvas.height * .66,
            x2: stage.canvas.width * .98,
            y2: stage.canvas.height * .66,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
            }
        },
        vertical_line_u: {
            x1: stage.canvas.width * .33,
            y1: stage.canvas.height * .02,
            x2: stage.canvas.width * .33,
            y2: stage.canvas.height * .98,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
            }
        },
        vertical_line_d: {
            x1: stage.canvas.width * .66,
            y1: stage.canvas.height * .02,
            x2: stage.canvas.width * .66,
            y2: stage.canvas.height * .98,
            draw: function () {
                stage.context.beginPath();
                stage.context.moveTo(this.x1, this.y1);
                stage.context.lineTo(this.x2, this.y2);
                stage.context.stroke();
            }
        },
        draw: function () {
            stage.context.lineWidth = 20;
            stage.context.strokeStyle = '#0000ff';
            stage.context.lineCap = 'round';
            this.horizontal_line_l.draw();
            this.horizontal_line_r.draw();
            this.vertical_line_u.draw();
            this.vertical_line_d.draw();
        }
    },
    text: {}
};

    assets.grid.draw();
    logic.possible_moves.draw_all_moves();
    assets.x.draw();
};

window.onload = init();
<小时/>
<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>Tik Tack Toe</title>
    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.18.1/build/cssreset/cssreset-min.css">
    <link rel="stylesheet" type="text/css" href="game.css" />
</head>
<body>
    <div id="container">
        <canvas id="canvas"></canvas>
        <div id="UI" class="">
            <ul>
                <li><button id="pause">Pause</button></li>
                <li><button id="restart">Restart</button></li>
                <li><button id="opt">Options</button></li>
            </ul>
        </div>
    </div>
    <script src="game.js"></script>
</body>
</html>

最佳答案

基础渲染和IO

游戏开发的基础是您拥有在不同位置、不同比例、方向等多次渲染的 Assets 。

渲染

因此,让我们从绘制一个基本的十字 (X) 开始,并假设您的 2D Canvas 上下文为 ctx

首先设置上下文

ctx.strokeStyle = "black"; // the colour/style of the cross
ctx.lineWidth = 10; // the width of a stroke in pixels.

然后添加一些路径元素,我们将十字设置在 100 x 100 像素的正方形中。

// Very important that you use the following line whenever creating new paths
// if not you end up adding to the existing path
ctx.beginPath(); // tell the context we are starting a new path. 
ctx.moveTo(10,10); // start of first line top left
ctx.lineTo(90,90); // create a line to the bottom right
ctx.moveTo(90,10); // move to the top right
ctx.lineTo(10,90); // create a line to the bottom left

// now the path is defined we can render it
ctx.stroke();

    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = 100;
    const ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);


    ctx.strokeStyle = "black"; // the colour/style of the cross
    ctx.lineWidth = 10; // the width of a stroke in pixels.
    // Very important that you use the following line whenever creating new paths
    // if not you end up adding to the existing path
    ctx.beginPath(); // tell the context we are starting a new path. 
    ctx.moveTo(10,10); // start of first line top left
    ctx.lineTo(90,90); // create a line to the bottom right
    ctx.moveTo(90,10); // move to the top right
    ctx.lineTo(10,90); // create a line to the bottom left

    // now the path is defined we can render it
    ctx.stroke();

对于圆来说也是如此

    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = 100;
    const ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);


    ctx.strokeStyle = "black"; // the colour/style of the cross
    ctx.lineWidth = 10; // the width of a stroke in pixels.
    // Very important that you use the following line whenever creating new paths
    // if not you end up adding to the existing path
    ctx.beginPath(); // tell the context we are starting a new path. 
    ctx.arc(50,50,40,0,Math.PI * 2); // create a circle path

    // now the path is defined we can render it
    ctx.stroke();

我们希望使十字和圆圈成为可以在任何地方绘制的实体,因此我们将每个都包装在函数定义中,添加一些参数来设置位置以及一些额外的细节(例如颜色)。

// draw a cross with the top left at x,y
function drawCross(x,y,col){
    ctx.save(); // save the current canvas context state
    ctx.translate(x,y); // set where on the canvas the top left will be
    ctx.strokeStyle = col;
    ctx.lineWidth = 10; 
    ctx.beginPath(); 
    ctx.moveTo(10,10); 
    ctx.lineTo(90,90); 
    ctx.moveTo(90,10); 
    ctx.lineTo(10,90); 
    ctx.stroke();
    ctx.restore(); // now restore the canvas state
}
function drawCircle(x,y,col){
    ctx.save(); // save the current canvas context state
    ctx.translate(x,y); // set where on the canvas the top left will be
    ctx.strokeStyle = col;
    ctx.lineWidth = 10; 
    ctx.beginPath(); 
    ctx.arc(50,50,40,0,Math.PI * 2); // create a circle path
    ctx.stroke();
    ctx.restore(); // now restore the canvas state
}

游戏状态

现在我们想要创建一些存储游戏板的方法。我们可以使用一个简单的数组,其中 9 个区域中的每一个区域都有一个项目。还有一些常量来定义每个位置保存的内容

// a 2d array containing 3 arrays one for each row
var gameBoard = [[0,0,0],[0,0,0],[0,0,0]];
const empty = 0;
const cross = 1;
const circle = 2; 
var turn = circle; // whos turn it is

现在有一个函数可以让我们设置位置。我们不希望这个函数只是盲目地设置一个位置。它将首先检查它是否为空,如果是则添加移动。如果移动有效,它将返回 true,否则返回 false。这使我们可以轻松添加 Action ,而无需检查棋盘上其他地方的有效 Action 。

// set a board position x y with a type
function setBoard(x,y,type){
     if(gameBoard[y][x] === empty){ // only if empty
         gameBoard[y][x] = type;
         return true; // indicate we have set the position
     }
     return false; // could not set location 
}

现在我们可以将这些部分放在一起来渲染板

function renderBoard(){
    var x, y;
    // as we may have some stuff already drawn we need to clear the
    // board
    ctx.clearRect(0,0,300,300);
    // lets draw the horizontal and vertical lines
    // We can use fillRect as it does not need the beginPath command
    // or a line width
    ctx.fillStyle = "black";
    ctx.fillRect(97,0,6,300);
    ctx.fillRect(197,0,6,300);
    ctx.fillRect(0,97,300,6);
    ctx.fillRect(0,197,300,6);

    for(y = 0; y < 3; y ++){
        for(x = 0; x < 3; x++){
            var loc = gameBoard[y][x]; // get what is at the location
            if(loc === cross){ // is it a cross?
                 // as the area is 100 by 100 pixels we need th correct top left
                 // coordinate, so multiply the x and y by 100
                 drawCross(x * 100, y * 100, "red");
            }else if(loc === circle){ // is it a circle
                 drawCircle(x * 100, y * 100, "red");
            }
        }
    }

}

IO 鼠标牧马人

现在我们已经设置了所有渲染,我们需要一些输入,因此创建一些鼠标监听器。

 // fisrt a mouse object to hold mouse state
 const mouse = {};
 function mouseEvent(event){
     var bounds = canvas.getBoundingClientRect(); // get the canvas loc
     // get the mouse position relative to the canvas top left
     mouse.x = event.pageX - (bounds.left + scrollX);
     mouse.y = event.pageY - (bounds.top + scrollY);          
     if(event.type === "mouseup"){  // when the mouse button is up we have a click
         mouse.clicked = true;             
     }
  }  
  canvas.addEventListener("mousemove",mouseEvent);
  canvas.addEventListener("mouseup",mouseEvent);

把它们放在一起

为了确保我们不会妨碍 DOM,我们需要将渲染与其同步。为此,我们创建一个定时渲染循环。虽然我们不会每次都渲染所有内容,但为了方便起见,我们可以让它每秒发生 60 次。

 var turn = circle; // who turn it is
 function mainLoop(){
      requestAnimationFrame(mainLoop); // ask the DOM for the next convenient time to render
      // now check the mouse button
      if(mouse.clicked){ // yes a click
          mouse.clicked = false; // clear the click
          // now convert the pixel coords of mouse to game board coords
          var bx = Math.floor(mouse.x / 100);
          var by = Math.floor(mouse.y / 100);
          if(setBoard(dx,dy,turn)){ // set the location. Function returns true if a valid move
               // all good so draw the board
               renderBoard();
               // getthe next turn
               turn = turn === circle ? cross : circle;
          }
      }
  }

  // start it all going                                        
  requestAnimationFrame(mainLoop); 

代码片段

作为包含添加 Canvas 的代码的片段。

    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = 300;
    const ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);

    // draw a cross with the top left at x,y
    function drawCross(x,y,col){
        ctx.save(); // save the current canvas context state
        ctx.translate(x,y); // set where on the canvas the top left will be
        ctx.strokeStyle = col;
        ctx.lineWidth = 10; 
        ctx.beginPath(); 
        ctx.moveTo(10,10); 
        ctx.lineTo(90,90); 
        ctx.moveTo(90,10); 
        ctx.lineTo(10,90); 
        ctx.stroke();
        ctx.restore(); // now restore the canvas state
    }
    function drawCircle(x,y,col){
        ctx.save(); // save the current canvas context state
        ctx.translate(x,y); // set where on the canvas the top left will be
        ctx.strokeStyle = col;
        ctx.lineWidth = 10; 
        ctx.beginPath(); 
        ctx.arc(50,50,40,0,Math.PI * 2); // create a circle path
        ctx.stroke();
        ctx.restore(); // now restore the canvas state
    }
    // a 2d array containing 3 arrays one for each row
    var gameBoard = [[0,0,0],[0,0,0],[0,0,0]];
    const empty = 0;
    const cross = 1;
    const circle = 2; 
    // set a board position x y with a type
    function setBoard(x,y,type){
         if(gameBoard[y][x] === empty){ // only if empty
             gameBoard[y][x] = type;
             return true; // indicate we have set the position
         }
         return false; // could not set location 
    }
    function renderBoard(){
        var x, y;
        // as we may have some stuff already drawn we need to clear the
        // board
        ctx.clearRect(0,0,300,300);
        // lets draw the horizontal and vertical lines
        // We can use fillRect as it does not need the beginPath command
        // or a line width
        ctx.fillStyle = "black";
        ctx.fillRect(97,0,6,300);
        ctx.fillRect(197,0,6,300);
        ctx.fillRect(0,97,300,6);
        ctx.fillRect(0,197,300,6);

        for(y = 0; y < 3; y ++){
            for(x = 0; x < 3; x++){
                var loc = gameBoard[y][x]; // get what is at the location
                if(loc === cross){ // is it a cross?
                     // as the area is 100 by 100 pixels we need th correct top left
                     // coordinate, so multiply the x and y by 100
                     drawCross(x * 100, y * 100, "red");
                }else if(loc === circle){ // is it a circle
                     drawCircle(x * 100, y * 100, "blue");
                }
            }
        }

     }
     // fisrt a mouse object to hold mouse state
     const mouse = {};
     function mouseEvent(event){
         var bounds = canvas.getBoundingClientRect(); // get the canvas loc
         // get the mouse position relative to the canvas top left
         mouse.x = event.pageX - (bounds.left + scrollX);
         mouse.y = event.pageY - (bounds.top + scrollY);          
         if(event.type === "mouseup"){  // when the mouse button is up we have a click
             mouse.clicked = true;             
         }
      }  
      canvas.addEventListener("mousemove",mouseEvent);
      canvas.addEventListener("mouseup",mouseEvent);

     var turn = circle; // who turn it is
     function mainLoop(){
          requestAnimationFrame(mainLoop); // ask the DOM for the next convenient time to render
          // now check the mouse button
          if(mouse.clicked){ // yes a click
              mouse.clicked = false; // clear the click
              // now convert the pixel coords of mouse to game board coords
              var bx = Math.floor(mouse.x / 100);
              var by = Math.floor(mouse.y / 100);
              if(setBoard(bx,by,turn)){ // set the location. Function returns true if a valid move
                   // all good so draw the board
                   renderBoard();
                   // getthe next turn
                   turn = turn === circle ? cross : circle;
              }
          }
         
      }
      // draw the empty board
      renderBoard();
      // start it all going                                        
      requestAnimationFrame(mainLoop); 

希望有帮助..

关于javascript - 制作井字游戏,无法制作 'x',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41411183/

相关文章:

javascript - 是否可以监视 HTML 元素中的事件监听器以了解其中之一何时被删除?

css - IE9 奇怪地呈现 Bootstrap 布局,我不知道为什么

java - 如何保存 JPanel 中绘制的线条?

javascript - 将 jQuery 日期选择器中的 minDate 设置为特定日期

javascript - 无法通过 XMLHttpRequest 验证其余调用

javascript - 更改输入类型,从单选按钮到复选框,jquery

javascript - 如何将 HTML <canvas> 数据发送到服务器

javascript - 存储在数组中的 Ember.js 绑定(bind)模型

html输入框问题

javascript - 为什么 Canvas 中的文字云很模糊?