|
@@ -9,15 +9,11 @@ function start_game() {
|
|
h3.remove()
|
|
h3.remove()
|
|
h1.remove();
|
|
h1.remove();
|
|
button.remove();
|
|
button.remove();
|
|
-
|
|
|
|
- // game parameters
|
|
|
|
- const DELAY_COMP = 0.5; // seconds for the computer to take its turn
|
|
|
|
- const GRID_CIRCLE = 0.7; // circle size as a fraction of cell size
|
|
|
|
- const GRID_COLS = 7; // number of game columns
|
|
|
|
- const GRID_ROWS = 6; // number of game rows
|
|
|
|
- const MARGIN = 0.02; // margin as a fraction of the shortest screen dimension
|
|
|
|
-
|
|
|
|
- // colour variables
|
|
|
|
|
|
+ const DELAY_COMP = 0.5;
|
|
|
|
+ const GRID_CIRCLE = 0.7;
|
|
|
|
+ const GRID_COLS = 7;
|
|
|
|
+ const GRID_ROWS = 6;
|
|
|
|
+ const MARGIN = 0.02;
|
|
const COLOR_BACKGROUND = "mintcream";
|
|
const COLOR_BACKGROUND = "mintcream";
|
|
const COLOR_COMP = "yellow";
|
|
const COLOR_COMP = "yellow";
|
|
const COLOR_COMP_DRK = "olive";
|
|
const COLOR_COMP_DRK = "olive";
|
|
@@ -28,14 +24,10 @@ function start_game() {
|
|
const COLOR_TIE = "darkgrey";
|
|
const COLOR_TIE = "darkgrey";
|
|
const COLOR_TIE_DRK = "black";
|
|
const COLOR_TIE_DRK = "black";
|
|
const COLOR_WIN = "black";
|
|
const COLOR_WIN = "black";
|
|
-
|
|
|
|
- // text variables
|
|
|
|
const TEXT_COMP = "Computer";
|
|
const TEXT_COMP = "Computer";
|
|
const TEXT_PLAY = "YOU";
|
|
const TEXT_PLAY = "YOU";
|
|
const TEXT_TIE = "DRAW";
|
|
const TEXT_TIE = "DRAW";
|
|
const TEXT_WIN = "WON!";
|
|
const TEXT_WIN = "WON!";
|
|
-
|
|
|
|
- // cell class
|
|
|
|
class Cell {
|
|
class Cell {
|
|
constructor(left, top, w, h, row, col) {
|
|
constructor(left, top, w, h, row, col) {
|
|
this.bot = top + h;
|
|
this.bot = top + h;
|
|
@@ -57,26 +49,14 @@ function start_game() {
|
|
contains(x, y) {
|
|
contains(x, y) {
|
|
return x > this.left && x < this.right && y > this.top && y < this.bot;
|
|
return x > this.left && x < this.right && y > this.top && y < this.bot;
|
|
}
|
|
}
|
|
-
|
|
|
|
- // draw the circle or hole
|
|
|
|
draw(/** @type {CanvasRenderingContext2D} */ ctx) {
|
|
draw(/** @type {CanvasRenderingContext2D} */ ctx) {
|
|
-
|
|
|
|
- // owner colour
|
|
|
|
let color = this.owner == null ? COLOR_BACKGROUND : this.owner ? COLOR_PLAY : COLOR_COMP;
|
|
let color = this.owner == null ? COLOR_BACKGROUND : this.owner ? COLOR_PLAY : COLOR_COMP;
|
|
-
|
|
|
|
- // draw the circle
|
|
|
|
ctx.fillStyle = color;
|
|
ctx.fillStyle = color;
|
|
ctx.beginPath();
|
|
ctx.beginPath();
|
|
ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2);
|
|
ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.fill();
|
|
-
|
|
|
|
- // draw highlighting
|
|
|
|
if (this.winner || this.highlight != null) {
|
|
if (this.winner || this.highlight != null) {
|
|
-
|
|
|
|
- // colour
|
|
|
|
color = this.winner ? COLOR_WIN : this.highlight ? COLOR_PLAY : COLOR_COMP;
|
|
color = this.winner ? COLOR_WIN : this.highlight ? COLOR_PLAY : COLOR_COMP;
|
|
-
|
|
|
|
- // draw a circle around the perimeter
|
|
|
|
ctx.lineWidth = this.r / 4;
|
|
ctx.lineWidth = this.r / 4;
|
|
ctx.strokeStyle = color;
|
|
ctx.strokeStyle = color;
|
|
ctx.beginPath();
|
|
ctx.beginPath();
|
|
@@ -85,80 +65,58 @@ function start_game() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- // set up the canvas and context
|
|
|
|
var canv = document.createElement("canvas");
|
|
var canv = document.createElement("canvas");
|
|
document.body.appendChild(canv);
|
|
document.body.appendChild(canv);
|
|
var ctx = canv.getContext("2d");
|
|
var ctx = canv.getContext("2d");
|
|
-
|
|
|
|
- // game variables
|
|
|
|
var gameOver, gameTied, grid = [], playersTurn, timeComp;
|
|
var gameOver, gameTied, grid = [], playersTurn, timeComp;
|
|
-
|
|
|
|
- // dimensions
|
|
|
|
var height, width, margin;
|
|
var height, width, margin;
|
|
setDimensions();
|
|
setDimensions();
|
|
-
|
|
|
|
- // event listeners
|
|
|
|
canv.addEventListener("click", click);
|
|
canv.addEventListener("click", click);
|
|
canv.addEventListener("mousemove", highlightGrid);
|
|
canv.addEventListener("mousemove", highlightGrid);
|
|
window.addEventListener("resize", setDimensions);
|
|
window.addEventListener("resize", setDimensions);
|
|
-
|
|
|
|
- // game loop
|
|
|
|
var timeDelta, timeLast;
|
|
var timeDelta, timeLast;
|
|
requestAnimationFrame(loop);
|
|
requestAnimationFrame(loop);
|
|
|
|
|
|
function loop(timeNow) {
|
|
function loop(timeNow) {
|
|
- // initialise timeLast
|
|
|
|
if (!timeLast) {
|
|
if (!timeLast) {
|
|
timeLast = timeNow;
|
|
timeLast = timeNow;
|
|
}
|
|
}
|
|
-
|
|
|
|
- // calculate the time difference
|
|
|
|
timeDelta = (timeNow - timeLast) / 1000; // seconds
|
|
timeDelta = (timeNow - timeLast) / 1000; // seconds
|
|
timeLast = timeNow;
|
|
timeLast = timeNow;
|
|
|
|
|
|
- // update
|
|
|
|
goComputer(timeDelta);
|
|
goComputer(timeDelta);
|
|
|
|
|
|
- // draw
|
|
|
|
drawBackground();
|
|
drawBackground();
|
|
drawGrid();
|
|
drawGrid();
|
|
drawText();
|
|
drawText();
|
|
|
|
|
|
- // call the next frame
|
|
|
|
requestAnimationFrame(loop);
|
|
requestAnimationFrame(loop);
|
|
}
|
|
}
|
|
|
|
|
|
function checkWin(row, col) {
|
|
function checkWin(row, col) {
|
|
-
|
|
|
|
- // get all the cells from each direction
|
|
|
|
|
|
+
|
|
let diagL = [], diagR = [], horiz = [], vert = [];
|
|
let diagL = [], diagR = [], horiz = [], vert = [];
|
|
for (let i = 0; i < GRID_ROWS; i++) {
|
|
for (let i = 0; i < GRID_ROWS; i++) {
|
|
for (let j = 0; j < GRID_COLS; j++) {
|
|
for (let j = 0; j < GRID_COLS; j++) {
|
|
-
|
|
|
|
- // horizontal cells
|
|
|
|
|
|
+
|
|
if (i == row) {
|
|
if (i == row) {
|
|
horiz.push(grid[i][j]);
|
|
horiz.push(grid[i][j]);
|
|
}
|
|
}
|
|
|
|
|
|
- // vertical cells
|
|
|
|
if (j == col) {
|
|
if (j == col) {
|
|
vert.push(grid[i][j]);
|
|
vert.push(grid[i][j]);
|
|
}
|
|
}
|
|
|
|
|
|
- // top left to bottom right
|
|
|
|
if (i - j == row - col) {
|
|
if (i - j == row - col) {
|
|
diagL.push(grid[i][j]);
|
|
diagL.push(grid[i][j]);
|
|
}
|
|
}
|
|
|
|
|
|
- // top right to bottom left
|
|
|
|
if (i + j == row + col) {
|
|
if (i + j == row + col) {
|
|
diagR.push(grid[i][j]);
|
|
diagR.push(grid[i][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // if any have four in a row, return a win!
|
|
|
|
return connect4(diagL) || connect4(diagR) || connect4(horiz) || connect4(vert);
|
|
return connect4(diagL) || connect4(diagR) || connect4(horiz) || connect4(vert);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -167,29 +125,24 @@ function start_game() {
|
|
let winningCells = [];
|
|
let winningCells = [];
|
|
for (let i = 0; i < cells.length; i++) {
|
|
for (let i = 0; i < cells.length; i++) {
|
|
|
|
|
|
- // no owner, reset the count
|
|
|
|
if (cells[i].owner == null) {
|
|
if (cells[i].owner == null) {
|
|
count = 0;
|
|
count = 0;
|
|
winningCells = [];
|
|
winningCells = [];
|
|
}
|
|
}
|
|
|
|
|
|
- // same owner, add to the count
|
|
|
|
else if (cells[i].owner == lastOwner) {
|
|
else if (cells[i].owner == lastOwner) {
|
|
count++;
|
|
count++;
|
|
winningCells.push(cells[i]);
|
|
winningCells.push(cells[i]);
|
|
}
|
|
}
|
|
|
|
|
|
- // new owner, new count
|
|
|
|
else {
|
|
else {
|
|
count = 1;
|
|
count = 1;
|
|
winningCells = [];
|
|
winningCells = [];
|
|
winningCells.push(cells[i]);
|
|
winningCells.push(cells[i]);
|
|
}
|
|
}
|
|
|
|
|
|
- // set the lastOwner
|
|
|
|
lastOwner = cells[i].owner;
|
|
lastOwner = cells[i].owner;
|
|
|
|
|
|
- // four in a row is a win
|
|
|
|
if (count == 4) {
|
|
if (count == 4) {
|
|
for (let cell of winningCells) {
|
|
for (let cell of winningCells) {
|
|
cell.winner = true;
|
|
cell.winner = true;
|
|
@@ -217,24 +170,20 @@ function start_game() {
|
|
function createGrid() {
|
|
function createGrid() {
|
|
grid = [];
|
|
grid = [];
|
|
|
|
|
|
- // set up cell size and margins
|
|
|
|
let cell, marginX, marginY;
|
|
let cell, marginX, marginY;
|
|
|
|
|
|
- // portrait
|
|
|
|
if ((width - margin * 2) * GRID_ROWS / GRID_COLS < height - margin * 2) {
|
|
if ((width - margin * 2) * GRID_ROWS / GRID_COLS < height - margin * 2) {
|
|
cell = (width - margin * 2) / GRID_COLS;
|
|
cell = (width - margin * 2) / GRID_COLS;
|
|
marginX = margin;
|
|
marginX = margin;
|
|
marginY = (height - cell * GRID_ROWS) / 2;
|
|
marginY = (height - cell * GRID_ROWS) / 2;
|
|
}
|
|
}
|
|
|
|
|
|
- // landscape
|
|
|
|
else {
|
|
else {
|
|
cell = (height - margin * 2) / GRID_ROWS;
|
|
cell = (height - margin * 2) / GRID_ROWS;
|
|
marginX = (width - cell * GRID_COLS) / 2;
|
|
marginX = (width - cell * GRID_COLS) / 2;
|
|
marginY = margin;
|
|
marginY = margin;
|
|
}
|
|
}
|
|
|
|
|
|
- // populate the grid
|
|
|
|
for (let i = 0; i < GRID_ROWS; i++) {
|
|
for (let i = 0; i < GRID_ROWS; i++) {
|
|
grid[i] = [];
|
|
grid[i] = [];
|
|
for (let j = 0; j < GRID_COLS; j++) {
|
|
for (let j = 0; j < GRID_COLS; j++) {
|
|
@@ -252,7 +201,6 @@ function start_game() {
|
|
|
|
|
|
function drawGrid() {
|
|
function drawGrid() {
|
|
|
|
|
|
- // frame and butt
|
|
|
|
let cell = grid[0][0];
|
|
let cell = grid[0][0];
|
|
let fh = cell.h * GRID_ROWS;
|
|
let fh = cell.h * GRID_ROWS;
|
|
let fw = cell.w * GRID_COLS;
|
|
let fw = cell.w * GRID_COLS;
|
|
@@ -261,7 +209,6 @@ function start_game() {
|
|
ctx.fillStyle = COLOR_FRAME_BUTT;
|
|
ctx.fillStyle = COLOR_FRAME_BUTT;
|
|
ctx.fillRect(cell.left - margin / 2, cell.top + fh - margin / 2, fw + margin, margin);
|
|
ctx.fillRect(cell.left - margin / 2, cell.top + fh - margin / 2, fw + margin, margin);
|
|
|
|
|
|
- // cells
|
|
|
|
for (let row of grid) {
|
|
for (let row of grid) {
|
|
for (let cell of row) {
|
|
for (let cell of row) {
|
|
cell.draw(ctx);
|
|
cell.draw(ctx);
|
|
@@ -274,7 +221,6 @@ function start_game() {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- // set up text parameters
|
|
|
|
let size = grid[0][0].h;
|
|
let size = grid[0][0].h;
|
|
ctx.fillStyle = gameTied ? COLOR_TIE : playersTurn ? COLOR_PLAY : COLOR_COMP;
|
|
ctx.fillStyle = gameTied ? COLOR_TIE : playersTurn ? COLOR_PLAY : COLOR_COMP;
|
|
ctx.font = size + "px dejavu sans mono";
|
|
ctx.font = size + "px dejavu sans mono";
|
|
@@ -284,7 +230,6 @@ function start_game() {
|
|
ctx.textAlign = "center";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.textBaseline = "middle";
|
|
|
|
|
|
- // draw the text
|
|
|
|
let offset = size * 0.55;
|
|
let offset = size * 0.55;
|
|
let text = gameTied ? TEXT_TIE : playersTurn ? TEXT_PLAY : TEXT_COMP;
|
|
let text = gameTied ? TEXT_TIE : playersTurn ? TEXT_PLAY : TEXT_COMP;
|
|
if (gameTied) {
|
|
if (gameTied) {
|
|
@@ -303,7 +248,6 @@ function start_game() {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- // count down till the computer makes its selection
|
|
|
|
if (timeComp > 0) {
|
|
if (timeComp > 0) {
|
|
timeComp -= delta;
|
|
timeComp -= delta;
|
|
if (timeComp <= 0) {
|
|
if (timeComp <= 0) {
|
|
@@ -312,74 +256,61 @@ function start_game() {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- // set up the options array
|
|
|
|
let options = [];
|
|
let options = [];
|
|
- options[0] = []; // computer wins
|
|
|
|
- options[1] = []; // block the player from winning
|
|
|
|
- options[2] = []; // no significance
|
|
|
|
- options[3] = []; // give away a win
|
|
|
|
|
|
+ options[0] = [];
|
|
|
|
+ options[1] = [];
|
|
|
|
+ options[2] = [];
|
|
|
|
+ options[3] = [];
|
|
|
|
|
|
- // loop through each column
|
|
|
|
let cell;
|
|
let cell;
|
|
for (let i = 0; i < GRID_COLS; i++) {
|
|
for (let i = 0; i < GRID_COLS; i++) {
|
|
cell = highlightCell(grid[0][i].cx, grid[0][i].cy);
|
|
cell = highlightCell(grid[0][i].cx, grid[0][i].cy);
|
|
|
|
|
|
- // column full, go to the next column
|
|
|
|
if (cell == null) {
|
|
if (cell == null) {
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- // first priority, computer wins
|
|
|
|
cell.owner = playersTurn;
|
|
cell.owner = playersTurn;
|
|
if (checkWin(cell.row, cell.col)) {
|
|
if (checkWin(cell.row, cell.col)) {
|
|
options[0].push(i);
|
|
options[0].push(i);
|
|
} else {
|
|
} else {
|
|
-
|
|
|
|
- // second priority, block the player
|
|
|
|
|
|
+
|
|
cell.owner = !playersTurn;
|
|
cell.owner = !playersTurn;
|
|
if (checkWin(cell.row, cell.col)) {
|
|
if (checkWin(cell.row, cell.col)) {
|
|
options[1].push(i);
|
|
options[1].push(i);
|
|
} else {
|
|
} else {
|
|
cell.owner = playersTurn;
|
|
cell.owner = playersTurn;
|
|
|
|
|
|
- // check the cell above
|
|
|
|
if (cell.row > 0) {
|
|
if (cell.row > 0) {
|
|
grid[cell.row - 1][cell.col].owner = !playersTurn;
|
|
grid[cell.row - 1][cell.col].owner = !playersTurn;
|
|
|
|
|
|
- // last priority, let player win
|
|
|
|
if (checkWin(cell.row - 1, cell.col)) {
|
|
if (checkWin(cell.row - 1, cell.col)) {
|
|
options[3].push(i);
|
|
options[3].push(i);
|
|
}
|
|
}
|
|
|
|
|
|
- // third priority, no significance
|
|
|
|
else {
|
|
else {
|
|
options[2].push(i);
|
|
options[2].push(i);
|
|
}
|
|
}
|
|
|
|
|
|
- // deselect cell above
|
|
|
|
grid[cell.row - 1][cell.col].owner = null;
|
|
grid[cell.row - 1][cell.col].owner = null;
|
|
}
|
|
}
|
|
|
|
|
|
- // no row above, third priority, no significance
|
|
|
|
else {
|
|
else {
|
|
options[2].push(i);
|
|
options[2].push(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // cancel highlight and selection
|
|
|
|
cell.highlight = null;
|
|
cell.highlight = null;
|
|
cell.owner = null;
|
|
cell.owner = null;
|
|
}
|
|
}
|
|
|
|
|
|
- // clear the winning cells
|
|
|
|
for (let row of grid) {
|
|
for (let row of grid) {
|
|
for (let cell of row) {
|
|
for (let cell of row) {
|
|
cell.winner = false;
|
|
cell.winner = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // randomly select a column in priority order
|
|
|
|
let col;
|
|
let col;
|
|
if (options[0].length > 0) {
|
|
if (options[0].length > 0) {
|
|
col = options[0][Math.floor(Math.random() * options[0].length)];
|
|
col = options[0][Math.floor(Math.random() * options[0].length)];
|
|
@@ -391,10 +322,8 @@ function start_game() {
|
|
col = options[3][Math.floor(Math.random() * options[3].length)];
|
|
col = options[3][Math.floor(Math.random() * options[3].length)];
|
|
}
|
|
}
|
|
|
|
|
|
- // highlight the selected cell
|
|
|
|
highlightCell(grid[0][col].cx, grid[0][col].cy);
|
|
highlightCell(grid[0][col].cx, grid[0][col].cy);
|
|
|
|
|
|
- // set the delay
|
|
|
|
timeComp = DELAY_COMP;
|
|
timeComp = DELAY_COMP;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -403,10 +332,8 @@ function start_game() {
|
|
for (let row of grid) {
|
|
for (let row of grid) {
|
|
for (let cell of row) {
|
|
for (let cell of row) {
|
|
|
|
|
|
- // clear existing highlighting
|
|
|
|
cell.highlight = null;
|
|
cell.highlight = null;
|
|
|
|
|
|
- // get the column
|
|
|
|
if (cell.contains(x, y)) {
|
|
if (cell.contains(x, y)) {
|
|
col = cell.col;
|
|
col = cell.col;
|
|
}
|
|
}
|
|
@@ -417,7 +344,6 @@ function start_game() {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- // highlight the first unoccupied cell
|
|
|
|
for (let i = GRID_ROWS - 1; i >= 0; i--) {
|
|
for (let i = GRID_ROWS - 1; i >= 0; i--) {
|
|
if (grid[i][col].owner == null) {
|
|
if (grid[i][col].owner == null) {
|
|
grid[i][col].highlight = playersTurn;
|
|
grid[i][col].highlight = playersTurn;
|
|
@@ -457,12 +383,10 @@ function start_game() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // don't allow selection if no highlighting
|
|
|
|
if (!highlighting) {
|
|
if (!highlighting) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- // check for a tied game
|
|
|
|
if (!gameOver) {
|
|
if (!gameOver) {
|
|
gameTied = true;
|
|
gameTied = true;
|
|
OUTER: for (let row of grid) {
|
|
OUTER: for (let row of grid) {
|
|
@@ -474,13 +398,11 @@ function start_game() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // set game over
|
|
|
|
if (gameTied) {
|
|
if (gameTied) {
|
|
gameOver = true;
|
|
gameOver = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // switch the player if no game over
|
|
|
|
if (!gameOver) {
|
|
if (!gameOver) {
|
|
playersTurn = !playersTurn;
|
|
playersTurn = !playersTurn;
|
|
}
|
|
}
|
|
@@ -494,4 +416,4 @@ function start_game() {
|
|
margin = MARGIN * Math.min(height, width);
|
|
margin = MARGIN * Math.min(height, width);
|
|
newGame();
|
|
newGame();
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+}
|