Compare commits

...

15 commits

7 changed files with 266 additions and 79 deletions

View file

@ -7,7 +7,10 @@
<script type="module" src="./js/main.js"></script>
</head>
<body>
<div class="board" id="board">
<div id="game">
</div>
<button class="button new-game">
New Game
</button>
</body>
</html>

193
src/js/Game.js Normal file
View file

@ -0,0 +1,193 @@
import { Constants } from "./Constants.js";
import { elt } from "./elt.js";
import { State } from "./State.js";
/**
* Represents a game.
*/
export class Game
{
/**
* The width of the board.
*/
static #width = 7;
/**
* The height of the board.
*/
static #height = 6;
/**
* The state of the game.
*
* @type {State}
*/
#state;
/**
* The actual board.
*
* @type {HTMLElement}
*/
#board;
/**
* The element containing a log message.
*
* @type {HTMLElement}
*/
#log;
/**
* The id of the element to add the board to.
*
* @type {string}
*/
id;
/**
* Initializes a new instance of the {@link Game `Game`} class.
*
* @param {string} id
* The id of the element to add the board to.
*/
constructor(id)
{
this.id = id;
this.#state = new State(Game.#width, Game.#height);
}
/**
* Gets the state of the game.
*/
get state()
{
return this.#state;
}
/**
* Gets the board of the game.
*/
get board()
{
return this.state.board;
}
/**
* Initializes the game.
*/
initialize()
{
let container = document.getElementById(this.id);
this.#board = elt(
"div",
{
class: "board"
});
this.#log = elt(
"div",
{
class: "log"
});
container.appendChild(this.#board);
container.appendChild(this.#log);
this.draw();
}
/**
* Resets the game.
*/
reset()
{
this.#state = new State(Game.#width, Game.#height);
this.draw();
}
/**
* Replaces the content of the board with the current state.
*/
draw()
{
let board = this.#board;
board.innerHTML = "";
for (let y = 0; y < Game.#height; y++)
{
for (let x = 0; x < Game.#width; x++)
{
/** @type {Node[]} */
let children = [];
let playerId = this.board[y][x];
if (playerId !== "")
{
children.push(
elt(
"div",
{
class: `piece ${Constants.PLAYER_NAMES[playerId]}`
}));
}
let field = elt(
"div",
{
class: "field"
},
...children);
field.onclick = () =>
{
if (this.addChip(x, y))
{
this.state.turnCount++;
this.draw();
}
};
board.appendChild(field);
}
}
board.appendChild(
elt(
"div",
{
style: "clear: both;"
}));
this.#log.className = "";
this.#log.classList.add(this.state.currentPlayer);
this.#log.innerHTML = "";
this.#log.textContent = `It's player "${Constants.PLAYER_NAMES[this.state.currentPlayer]}"s turn`;
}
/**
* Adds a chip to the board indicated by the {@link x `x`} and the {@link y `y`} coordinate.
*
* @param {number} x
* The x coordinate to add the chip to.
*
* @param {number} y
* The y coordinate to add the chip to.
*
* @returns {boolean}
* A value indicating whether the chip could be added.
*/
addChip(x, y)
{
for (let i = Game.#height - 1; i >= 0; i--)
{
if (this.board[i][x] === "")
{
this.board[i][x] = this.state.currentPlayer;
return true;
}
}
return false;
}
}

View file

@ -1,20 +1,55 @@
/**
* The state of the board.
* Represents the state of a game.
*/
export const State = {
turnCount: 0,
export class State
{
/**
* The board of the game.
*
* @type {Board}
*/
#board;
/**
* Gets the id of the current player.
*
* @type {CellOwner}
*/
get currentPlayer()
{
return this.turnCount % 2 === 0 ? "r" : "b";
},
/**
* The number of turns that have been played.
*/
turnCount = 0;
board: /** @type {Board} */ (
Array(6).fill("").map(
() => (Array(7).fill(""))))
};
/**
* Initializes a new instance of the {@link State `State`} class.
*
* @param {number} width
* The width of the board.
*
* @param {number} height
* The height of the board.
*/
constructor(width, height)
{
this.#board = /** @type {Board} */ (
Array(height).fill("").map(
() =>
{
return Array(width).fill(
/** @type {CellOwner} */ (""));
}));
}
/**
* Gets the id of the current player.
*
* @type {CellOwner}
*/
get currentPlayer()
{
return this.turnCount % 2 === 0 ? "r" : "b";
}
/**
* Gets the board of the game.
*/
get board()
{
return this.#board;
}
}

View file

@ -1,50 +0,0 @@
import { Constants } from "./Constants.js";
import { elt } from "./elt.js";
import { State } from "./State.js";
/**
* Initializes the game board.
*
* @param {string} id
* The id of the element to add the board to.
*/
export function initializeBoard(id)
{
let board = document.getElementById(id);
board.innerHTML = "";
for (let i = 0; i < 6; i++)
{
for (let j = 0; j < 7; j++)
{
/** @type {Node[]} */
let children = [];
let playerId = State.board[i][j];
if (playerId !== "")
{
children.push(elt(
"div",
{
class: `piece ${Constants.PLAYER_NAMES[playerId]}`
}));
}
let field = elt(
"div",
{
class: "field"
},
...children);
field.onclick = () =>
{
State.board[i][j] = State.currentPlayer;
State.turnCount++;
initializeBoard("board");
};
board.appendChild(field);
}
}
}

View file

@ -1,11 +1,24 @@
import { initializeBoard } from "./board.js";
import { Game } from "./Game.js";
/**
* The game that is being played.
*
* @type {Game}
*/
let game;
/**
* Initializes the board.
*/
function initialize()
{
initializeBoard("board");
game = new Game("game");
game.initialize();
(/** @type {HTMLElement} */ (document.querySelector(".new-game"))).onclick = () =>
{
game.reset();
};
}
initialize();

11
src/js/types.d.ts vendored
View file

@ -6,16 +6,9 @@ type CellOwner = "" | "r" | "b";
/**
* Represents a row of the game field.
*/
type Row = [CellOwner, CellOwner, CellOwner, CellOwner, CellOwner, CellOwner, CellOwner];
type Row = CellOwner[];
/**
* Represents a game board.
*/
type Board = [
Row,
Row,
Row,
Row,
Row,
Row
];
type Board = Row[];

View file

@ -4,7 +4,7 @@ div {
.board {
width: 84vw;
margin: auto;
margin: 10px auto;
outline: 1px solid black;
}