I'm trying to teach myself no frills javascript game development. 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. 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. 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>
It is the basics of game development that you have assets that are rendered many times, in a variety of places, scales, orientations, etc.
So lets start with drawing a basic cross (X) and assuming you have the 2D canvas context as 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.
// 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
}
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. 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. 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");
}
}
}
}
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);
To ensure we don't get in the way of the DOM we need to sync our rendering with it. 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.
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);
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..
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.