【问题标题】:making a tic tack toe game, can't make an 'x'制作井字游戏,无法制作“x”
【发布时间】:2016-12-31 19:30:08
【问题描述】:

我正在尝试自学没有多余的 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>

【问题讨论】:

    标签: javascript html canvas frontend tic-tac-toe


    【解决方案1】:

    基本渲染和 IO

    游戏开发的基础是您拥有在各种位置、比例、方向等多次渲染的资产。

    渲染

    让我们从绘制一个基本的十字 (X) 开始,并假设您的 2D 画布上下文为 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。这使我们可以轻松添加动作,而无需在其他地方检查棋盘是否有效。

    // 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); 
    

    片段

    作为一个带有代码的sn-p来添加画布。

        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); 

    希望对您有所帮助..

    【讨论】:

    • 感谢您的精彩解释。你给出了一些严肃的见解,我非常感谢你。伟大的工作。
    猜你喜欢
    • 1970-01-01
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 2019-11-24
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多