简体   繁体   English

制作井字游戏,不能制作“ x”

[英]making a tic tack toe game, can't make an 'x'

I'm trying to teach myself no frills javascript game development. 我正在尝试自学JavaScript游戏开发。 I've chosen to hold all possible places on the board for the game to need to render an x or o as possible moves in the logic object. 我选择在游戏板上保留所有可能的位置,以使游戏需要在逻辑对象中尽可能多地绘制x或o。 I can't figure out how to draw the x inside the area of the rect it's to appear in. I want the player to eventually click or touch any space in the area of on of the possible moves object's rects. 我不知道如何在要显示的矩形区域内绘制x。我希望玩家最终单击或触摸可能的移动对象的矩形区域中的任何空间。 How do I do that? 我怎么做? How do I redo this when I need to make them instances without any idea where the player will click or touch? 当我需要使它们成为实例而又不知道播放器将在何处单击或触摸时,如何重做呢?

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

Basic Rendering and IO 基本渲染和IO

It is the basics of game development that you have assets that are rendered many times, in a variety of places, scales, orientations, etc. 游戏开发的基本原理是,您需要在各种位置,比例,方向等位置多次渲染资产。

Rendering 渲染图

So lets start with drawing a basic cross (X) and assuming you have the 2D canvas context as ctx 因此,让我们从绘制基本十字(X)开始,并假设您将2D画布上下文作为ctx

First set up the context 首先设置上下文

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

Then add some path elements, we will set the cross to be in a square 100 by 100 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(); 

And it is much the same for the circle 圈子也差不多

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

We want to make the cross and circle an entity we can draw anywhere so we will wrap each in a function definition, adding some arguments to set where and some extra details like colour. 我们希望使十字和圆圈成为可以在任何地方绘制的实体,因此我们将每个包裹在函数定义中,并添加一些参数来设置位置以及一些其他细节,例如颜色。

// 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
}

Game state 游戏状态

Now we want to create some means of storing the gameboard. 现在我们要创建一些存储游戏板的方法。 We can use a simple array with one items for each of the 9 areas. 我们可以对9个区域中的每个区域使用一个只有一个项目的简单数组。 Also some constants to define what is held in each location 还有一些常量定义每个位置中保存的内容

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

Now a function to let us set a location. 现在,一个功能让我们设置位置。 We do not want this function to just blindly set a location. 我们不希望此功能只是盲目地设置位置。 It will first check if it is empty and if so only then add the move. 它将首先检查它是否为空,如果是,则仅添加移动。 It will return true for a valid move or false if not. 有效移动将返回true,否则返回false。 That makes it easy for us to add moves without having to check the board elsewhere for valid moves. 这使我们可以轻松添加动作,而不必检查其他地方的棋盘是否有效。

// 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 
}

So now we can put these parts together to render the board 现在我们可以将这些部分放在一起来渲染电路板

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 the mouse wrangler IO鼠标牧马人

Now we have all the rendering set up we need some input so create some mouse listeners. 现在我们已经完成所有渲染设置,我们需要一些输入,因此需要创建一些鼠标侦听器。

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

Putting it all together 放在一起

To ensure we don't get in the way of the DOM we need to sync our rendering with it. 为了确保不妨碍DOM,我们需要将渲染与其同步。 To do this we create a timed rendering loop. 为此,我们创建一个定时渲染循环。 Though we well not render everything each time we can just keep it happening 60 times a second for convenience. 尽管我们并不是每次都渲染所有内容,但是为了方便起见,我们只能将它每秒发生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); 

Snippet 片段

As a snippet with code to add the 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 xy 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); 

Hope that helps.. 希望有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM