aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--draughts/app.js32
-rw-r--r--draughts/environment.js17
-rw-r--r--draughts/game.js20
-rw-r--r--draughts/piece.js73
-rw-r--r--draughts/public/javascripts/game.js64
-rw-r--r--draughts/public/javascripts/messages.js9
-rw-r--r--draughts/public/stylesheets/splash.css70
-rw-r--r--draughts/public/stylesheets/style.css26
-rw-r--r--draughts/statTracker.js13
9 files changed, 234 insertions, 90 deletions
diff --git a/draughts/app.js b/draughts/app.js
index e255308..b275534 100644
--- a/draughts/app.js
+++ b/draughts/app.js
@@ -37,9 +37,6 @@ wss.on("connection", ws => {
* to join.
*/
let game = env.games.filter(g => !g.ongoing)[0]
- stats.ongoingGames++
- stats.totalGames++
-
if (!game) {
game = new Game()
env.games.push(game)
@@ -49,7 +46,13 @@ wss.on("connection", ws => {
game.redPlayer = ws
game.ongoing = true
game.messageClient({ head: Messages.WELCOME, body: Color.RED }, ws)
+
+ /* Once the red player joins the game, we can officially start this game and inform the blue
+ * player that he can make his opening move.
+ */
game.messageOpponent({ head: Messages.START }, ws)
+ stats.ongoingGames++
+ stats.totalGames++
game.nextTurn()
}
@@ -70,6 +73,9 @@ wss.on("connection", ws => {
ws.on("message", msg => {
msg = JSON.parse(msg)
switch (msg.head) {
+ /* When a player resigns we check to see if the game is still ongoing and if it is we
+ * decrement the `ongoingGames' and `totalGames' counters.
+ */
case Messages.RESIGN:
if (game.ongoing && env.games.includes(game)) {
game.messageOpponent(msg, ws)
@@ -78,6 +84,11 @@ wss.on("connection", ws => {
}
env.removeGame(game)
break
+
+ /* When the client makes a move, we tell the opponent about the move that was made so that
+ * they can display the moved pieces on their end. We also tell the game object about the
+ * move so that it can calculate legal moves for the next turn.
+ */
case Messages.MOVED:
game.messageOpponent({
head: Messages.MOVED,
@@ -90,7 +101,11 @@ wss.on("connection", ws => {
}
}, ws)
game.move(msg.body)
-
+
+ /* If this returns false, it means that one of the players won the game. In this case we
+ * need to update statistics regarding the amount of moves it takes to win a game and
+ * such. We also need to remove the game.
+ */
if (!game.nextTurn()) {
const totalMoves = game.history.length
@@ -98,15 +113,14 @@ wss.on("connection", ws => {
stats.minimumMoves = Math.min(totalMoves, stats.minimumMoves)
/* Update average amount of moves in stat tracker */
- if (stats.averageMoves != Infinity)
- stats.averageMoves = (stats.averageMoves * (stats.totalGames - 1) + totalMoves) / stats.totalGames
- else
- stats.averageMoves = totalMoves
+ stats.averageMoves = stats.averageMoves == Infinity
+ ? totalMoves
+ : (stats.averageMoves * (stats.totalGames - 1) + totalMoves) / stats.totalGames
/* Remove ongoing game */
stats.ongoingGames--
env.removeGame(game)
- }
+ }
break
}
})
diff --git a/draughts/environment.js b/draughts/environment.js
index 7518185..5f99d4d 100644
--- a/draughts/environment.js
+++ b/draughts/environment.js
@@ -1,9 +1,22 @@
-/* A class representing the complete environment. It holds all the games currently being played. */
+/*
+ * Signature:
+ * this.games :: Game[]
+ *
+ * Description:
+ * An admittedly useless class that mostly exists as a result of code evolution and rewriting. It
+ * holds an array containing all of the games that are currently ongoing.
+ */
const Environment = function() {
this.games = []
}
-/* Function to remove a game from the environment */
+/*
+ * Signature:
+ * (Game) => Nothing
+ *
+ * Description:
+ * Removes the game `game' from the array `this.games'
+ */
Environment.prototype.removeGame = function(game) {
this.games = this.games.filter(g => g != game)
}
diff --git a/draughts/game.js b/draughts/game.js
index d500c9e..d181016 100644
--- a/draughts/game.js
+++ b/draughts/game.js
@@ -359,8 +359,28 @@ Game.prototype.move = function(msg) {
this.history[this.history.length - 1].red = moveToHistory(msg.old, msg.new, msg.captures)
}
+/*
+ * Signature:
+ * (x: Number, y: Number) => Number
+ *
+ * Description:
+ * Take a set of coordinates and translate them to the corresponding square number. Draughts
+ * rules state that the upper-left square is square "1", and each square from left to right,
+ * up to down, increments by one. The bottom right square is therefor square "50".
+ */
const coordToSquare = (x, y) => 51 - Math.ceil((x + 1) / 2 + 5 * y)
+/*
+ * Signature:
+ * ({ x: Number, y: Number }, { x: Number, y: Number }, Piece[]) => String
+ *
+ * Description:
+ * Take an old position specified by `o', a new position specified by `p', and a list of captures
+ * specified by `captures' and return a string representation of the move that was played. In
+ * draughts a move is shown by the notation "F-T" where 'F' is the square the piece moved from
+ * and 'T' is the square the piece moved to. If a capture was made during the move the notation
+ * changes to "FxT".
+ */
const moveToHistory = (o, n, c) =>
`${coordToSquare(o.x, o.y)}${c.length == 0 ? "-" : "x"}${coordToSquare(n.x, n.y)}`
diff --git a/draughts/piece.js b/draughts/piece.js
index 62a221f..e0885fb 100644
--- a/draughts/piece.js
+++ b/draughts/piece.js
@@ -1,6 +1,14 @@
const Color = require("./public/javascripts/color")
-/* Return all of the potential non-capturing moves that the piece can make */
+/*
+ * Signature:
+ * (Piece) => Number[2][]
+ *
+ * Description:
+ * Return all of the possible non-capturing moves that the piece `p' could theoretically make
+ * assuming that it is not a king piece. The moves are represented as arrays of 2 numbers, an `x'
+ * and a `y' position.
+ */
const nonCapturingMovesStandard =
p => p.color == Color.BLUE
? [[p.position.x + 1, p.position.y + 1],
@@ -8,11 +16,29 @@ const nonCapturingMovesStandard =
: [[p.position.x + 1, p.position.y - 1],
[p.position.x - 1, p.position.y - 1]]
+/*
+ * Signature:
+ * (Piece) => Number[2][]
+ *
+ * Description:
+ * The same as above except we assume that `p' is a king piece.
+ */
const nonCapturingMovesKing =
p => [1, 2, 3, 4, 5, 6, 7, 8, 9].map(n => [[n, n], [n, -n], [-n, n], [-n, -n]])
.flat()
.map(([x, y]) => [p.position.x + x, p.position.y + y])
+/*
+ * Signature:
+ * (Piece) => { skipped: { x: Number, y: Number }, landed: { x: Number, y: Number } }[]
+ *
+ * Description:
+ * Return all of the possible capturing moves that the piece `p' could theoretically make
+ * assuming that it is not a king piece. The moves are represented as objects with a `skipped'
+ * and a `landed' attribute. The `skipped' attribute represents the location on the board that
+ * the piece skipped over in the capture, and the `landed' attribute is where the piece actually
+ * landed.
+ */
const capturingMovesStandard = p => [
{ skipped: { x: p.position.x + 1, y: p.position.y + 1 },
landed: { x: p.position.x + 2, y: p.position.y + 2 } },
@@ -24,6 +50,13 @@ const capturingMovesStandard = p => [
landed: { x: p.position.x - 2, y: p.position.y - 2 } }
]
+/*
+ * Signature:
+ * (Piece) => { skipped: { x: Number, y: Number }, landed: { x: Number, y: Number } }[]
+ *
+ * Description:
+ * The same as above except we assume that `p' is a king piece.
+ */
const capturingMovesKing =
p => [1, 2, 3, 4, 5, 6, 7, 8].map(
n => [{ skipped: { x: p.position.x + n, y: p.position.y + n },
@@ -36,8 +69,17 @@ const capturingMovesKing =
landed: { x: p.position.x - n - 1, y: p.position.y - n - 1 } }
]).flat()
-/* A class representing a piece on a draughts board. It has a position within the board, a color, an
- * ID which corresponds to its ID in the HTML DOM, and whether or not its a kinged piece.
+/*
+ * Signature:
+ * this.position :: { x: Number, y: Number }
+ * this.id :: Number
+ * this.color :: Color
+ * this.isKing :: Boolean
+ *
+ * Description:
+ * A class representing a draughts piece. It has a position within the board as well as an ID
+ * that corresponds to its ID in the HTML DOM, a color, and a boolean that determines if it's a
+ * king or not.
*/
const Piece = function(x, y, id, color) {
this.position = {
@@ -49,14 +91,39 @@ const Piece = function(x, y, id, color) {
this.isKing = false
}
+/*
+ * Signature:
+ * () => Number[2][]
+ *
+ * Description:
+ * Return an array of coordinates (each coordinate is an array with an `x' and `y' value) for all
+ * of the possible moves that the piece `this' could theoretically make in any given scenario
+ * which do *not* capture any pieces.
+ */
Piece.prototype.nonCapturingMoves = function() {
return this.isKing ? nonCapturingMovesKing(this) : nonCapturingMovesStandard(this)
}
+/*
+ * Signature:
+ * () => { skipped: { x: Number, y: Number }, landed: { x: Number, y: Number } }[]
+ *
+ * Description:
+ * This is the same as the above function but returns the moves which *do* capture pieces. The
+ * output is just an array of moves in the format described by the `capturingMovesKing()' and
+ * `capturingMovesStandard()' functions.
+ */
Piece.prototype.capturingMoves = function() {
return this.isKing ? capturingMovesKing(this) : capturingMovesStandard(this)
}
+/*
+ * Signature:
+ * (Piece) => Boolean
+ *
+ * Description:
+ * Return `true' or `false' depending on if `this' and `other' are on the same team or not.
+ */
Piece.prototype.sameTeam = function(other) {
return this.color == other.color
}
diff --git a/draughts/public/javascripts/game.js b/draughts/public/javascripts/game.js
index 21bf6f5..741a0ce 100644
--- a/draughts/public/javascripts/game.js
+++ b/draughts/public/javascripts/game.js
@@ -1,10 +1,3 @@
-/*
- * Description:
- * This file contains all of the client-side code of the game. It's not as pretty as the server
- * code, but it gets the job done. The code in this file is responsible for all of the graphical
- * updates on the game screen as well as handling click events and whatnot.
- */
-
/* This is basically the enumeration we have in the `public/javascripts/color.js' file, except I
* cannot for the life of me get it to import properly, so I suppose we are doing it this way for
* now.
@@ -65,35 +58,63 @@ const removeMarkers = () => Array
.forEach(pos => pos.remove())
/* Add a position marker to the position specified by the given coordinates */
+/*
+ * Signature:
+ * ({ x: Number, y: Number, captures: Piece[], king: Boolean }) => Nothing
+ *
+ * Description:
+ * Add a position marker to the board (those little grey dots) at the position specified by `x'
+ * and `y'. Then add an event listener to listen for the click event on the position marker which
+ * will handle moving the selected piece, removing captured pieces and piece promotion.
+ */
const addMarker = ({ x, y, captures, king }) => {
- /* Create and add the marker */
- let img = document.createElement("img") // Create a new image element
- img.className = "piece position" // Add it to the correct classes
- img.style.transform = `translate(${x * 100}%, ${y * 100}%)` // Move it to the correct position
- img.setAttribute("src", "images/position.svg") // Assign the actual image to it
- document.querySelector("#board-container").append(img) // Add it to the board container
+ /* Create and add the marker by first creating a new image element, giving it the correct CSS
+ * classes, applying a transformation to it, setting the correct source image, and then adding it
+ * to the board container.
+ */
+ let img = document.createElement("img")
+ img.className = "piece position"
+ img.style.transform = `translate(${x * 100}%, ${y * 100}%)`
+ img.setAttribute("src", "images/position.svg")
+ document.querySelector("#board-container").append(img)
/* Add an event listener to move the selected piece when the marker is clicked */
img.addEventListener("click", () => {
- /* Grab the old positions of the piece */
+ /* Grab the old positions of the piece by getting the transformation of the pieces style
+ * attribute. This will return a string that looks somewhat like this:
+ *
+ * "transform: translate(300%, 400%);"
+ *
+ * We can then use the `match()' method to extract all the numbers with the regex `[0-9]+'
+ * and then divide them by 100 to get numbers we can index the board with.
+ */
let [ox, oy] = selectedPiece
- .style // Get the style attributes
- .transform // Get the transformation style attribute
- .match(/[0-9]+/g) // Get the percentage offsets in the translation
- .map(n => n / 100) // Divide them by 100 to get board indicies
+ .style
+ .transform
+ .match(/[0-9]+/g)
+ .map(n => n / 100)
/* Get the ID of the selected piece so we can include it in the message to the server */
let id = selectedPiece.id.slice(1)
+ /* Add the move to the game history. If the game history is empty or the `red' attribute of
+ * the last entry is non-null, it means that we are the blue player and need to push a new
+ * entry to the history. Otherwise we are red and need to modify the last entry with our move
+ */
let [o, n] = [{ x: ox, y: oy }, { x: x, y: y }]
if (gameHistory.length == 0 || gameHistory[gameHistory.length - 1].red)
gameHistory.push({ blue: moveToHistory(o, n, captures) })
else
gameHistory[gameHistory.length - 1].red = moveToHistory(o, n, captures)
drawHistory()
+
+ /* Remove all the captured pieces */
removeCaptures(captures, opponentPrefix())
- /* Move the selected piece, unselect it, remove the markers, and end our turn */
+ /* If the piece was promoted during it's move, we make it a king piece. Then we move it to
+ * the location of the position marker, deselect the piece, end our turn, and remove all of
+ * the position markers.
+ */
if (king)
selectedPiece.src = selectedPiece.src.replace("piece.svg", "piece-king.svg")
selectedPiece.style.transform = img.style.transform
@@ -102,7 +123,8 @@ const addMarker = ({ x, y, captures, king }) => {
removeMarkers();
/* Inform the server that we have moved a piece, and tell it where the piece used to be and
- * where it has moved to
+ * where it has moved to. We also send whether or not we promoted our piece and what the new
+ * game history is.
*/
ws.send(JSON.stringify({
head: Messages.MOVED,
@@ -208,9 +230,11 @@ const drawHistory = () => {
red: (typeof(h.red) == "undefined" ? "" : h.red)
}))
+ /* If the shortlist doesn't have 8 entries, we can start by adding some blank entries */
let html = ""
for (let i = 7; i >= shortList.length; i--)
html += `<tr><td class="turn-no">${i + 1}</td><td></td><td></td></tr>`
+
for (let i = shortList.length - 1; i >= 0; i--)
html += `<tr><td class="turn-no">${i + 1 + gameHistory.length - shortList.length}</td><td>
${shortList[i].blue}</td><td>${shortList[i].red}</td></tr>`
diff --git a/draughts/public/javascripts/messages.js b/draughts/public/javascripts/messages.js
index 04a96e9..2ae34da 100644
--- a/draughts/public/javascripts/messages.js
+++ b/draughts/public/javascripts/messages.js
@@ -1,9 +1,16 @@
+/* A basic enumeration of different messages that can be sent via websockets between the client(s)
+ * and the server. Each message has a comment explaining what it represents.
+ *
+ * S --> C :: The server sends the message to the client
+ * C <-- S :: The client sends the message to the server
+ * S <-> C :: The server sends the message to the client and vise versa
+ */
(function (exports) {
exports.COMMENCE = 0 // S --> C :: Inform the client that they may commence their turn
exports.RESIGN = 1 // S <-> C :: Inform the server/opponent that the client resigned
exports.DISCONNECT = 2 // S --> C :: Inform the client that the opponent disconnected
exports.MOVED = 3 // S <-> C :: Inform the server/opponent that the client has moved a piece
exports.WELCOME = 4 // S --> C :: Inform the client of their color when they join the game
- exports.START = 5 // s --> C :: Inform the blue player that they can make the first move
+ exports.START = 5 // S --> C :: Inform the blue player that they can make the first move
exports.GAMEOVER = 6 // S --> C :: Inform the clients that the game is over
})(typeof(exports) == "undefined" ? (this.Messages = {}) : exports)
diff --git a/draughts/public/stylesheets/splash.css b/draughts/public/stylesheets/splash.css
index 6cce3ab..53f5680 100644
--- a/draughts/public/stylesheets/splash.css
+++ b/draughts/public/stylesheets/splash.css
@@ -4,13 +4,13 @@
body {
/* Set the default font */
font-family: "Saira Condensed";
- background-color: var(--dark-blue);
+ background-color: var(--dark-blue);
color: white;
/* Remove the margin on the sides of the screen */
margin: 0;
}
-/* Resizes body to fit the viewport and ensures position is absolute*/
+/* Resizes body to fit the viewport and ensures position is absolute */
body > div {
width: 95%;
height: 90vh;
@@ -18,7 +18,7 @@ body > div {
position: absolute;
}
-/* Adjusts standard rendering of our images. Adds drop shadow and ensures it fits the container*/
+/* Adjusts standard rendering of our images. Adds drop shadow and ensures it fits the container */
div > img {
object-fit: contain;
width: 100%;
@@ -26,25 +26,25 @@ div > img {
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
}
-/* set the container class to a 4x3 grid with named areas*/
+/* Set the container class to a 4x3 grid with named areas */
.container {
display: grid;
grid-template-columns: repeat(4, 25%);
grid-template-rows: repeat(3, calc(100% / 3));
grid-template-areas:
- "header header how-to-1 church"
- "stats stats how-to-2 raadhuis"
- "players button how-to-3 windmill";
+ "header header how-to-1 church"
+ "stats stats how-to-2 raadhuis"
+ "players button how-to-3 windmill";
}
-/* Sets the header class to the header grid-area.*/
+/* Sets the header class to the header grid-area */
.header {
grid-area: header;
justify-self: left;
margin-left:10%;
}
-/* Formats the title according to our wireframe*/
+/* Formats the title according to our wireframe */
h1 {
justify-content: start;
font-family: Saira;
@@ -52,15 +52,15 @@ h1 {
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
}
-/* Adds line under title*/
-h1::after{
+/* Adds line under title */
+h1::after {
display: block;
- content: "";
+ content: "";
align-content: left;
- border-top: .4rem solid white;
- width: 13rem;
- margin: 0 0.7rem;
- transform: translateY(-2rem);
+ border-top: .4rem solid white;
+ width: 13rem;
+ margin: 0 0.7rem;
+ transform: translateY(-2rem);
}
/* Sets the stats class to the stats grid-area. */
@@ -82,29 +82,29 @@ div > p {
grid-area: players;
}
-/* Alligns the icon image within the icon div*/
+/* Alligns the icon image within the icon div */
.icon > img {
margin-left: 15%;
height: 75%;
width: 90%;
}
-/* Sets button class to button grid area*/
+/* Sets button class to button grid area */
.button {
grid-area: button;
}
-/* Play button fade in animation*/
+/* Play button fade in animation */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
- opacity: 1;
+ opacity: 1;
}
- }
+}
-/* Formats our button using the button element id selector*/
+/* Formats our button using the button element id selector */
#button {
background-color: var(--light-blue);
color: inherit;
@@ -119,18 +119,18 @@ div > p {
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
}
-/* change text to bold when clicked*/
+/* Change text to bold when clicked */
#button:active {
font-weight: bold;
}
-/* changes background color of play button when hovered*/
+/* Changes background color of play button when hovered */
#button:hover {
cursor: pointer;
background-color: var(--darker-blue)
}
-/* configures the standard setting for our how-to section headers*/
+/* Configures the standard setting for our how-to section headers */
h2 {
font-family: inherit;
font-size: 30px;
@@ -147,14 +147,14 @@ h2 {
margin-left: 20%;
}
-/* Adds labeling and formattnig for the header*/
+/* Adds labeling and formattnig for the header */
.how-to-1 > h2::before {
content: "1 - ";
display: list-item-inline;
font-weight: lighter;
}
-/* some specific adjustments for each image within the how-to section to ensure proper allignment*/
+/* Some specific adjustments for each image within the how-to section to ensure proper allignment */
.how-to-1 > img {
justify-self: center;
width: fit-content;
@@ -169,14 +169,14 @@ h2 {
margin-left: 20%;
}
-/* Adds labeling and formattnig for the header*/
+/* Adds labeling and formattnig for the header */
.how-to-2 > h2::before {
content: "2 - ";
display: list-item-inline;
font-weight: lighter;
}
-/* some specific adjustments for each image within the how-to section to ensure proper allignment*/
+/* Some specific adjustments for each image within the how-to section to ensure proper allignment */
.how-to-2 > img {
justify-self: center;
height: 85%;
@@ -190,31 +190,31 @@ h2 {
margin-left: 20%;
}
-/* some specific adjustments for each image within the how-to section to ensure proper allignment*/
+/* Some specific adjustments for each image within the how-to section to ensure proper allignment */
.how-to-3> img {
justify-self: center;
height: 45%;
opacity: 0.8;
}
-/* Adds labeling and formattnig for the header*/
+/* Adds labeling and formattnig for the header */
.how-to-3 > h2::before {
display: list-item-inline;
content: "3 - ";
font-weight: lighter;
}
-/* Sets church class to church grid area*/
+/* Sets church class to church grid area */
.church {
grid-area: church;
}
-/* Sets raadhuis class to raadhuis grid area*/
+/* Sets raadhuis class to raadhuis grid area */
.raadhuis {
grid-area: raadhuis;
}
-/* Sets windmill class to windmill grid area*/
+/* Sets windmill class to windmill grid area */
.windmill {
grid-area: windmill;
-} \ No newline at end of file
+}
diff --git a/draughts/public/stylesheets/style.css b/draughts/public/stylesheets/style.css
index e9f8e90..9813ae0 100644
--- a/draughts/public/stylesheets/style.css
+++ b/draughts/public/stylesheets/style.css
@@ -13,17 +13,17 @@
/* Ensures a minimum screen resolution of 800 x 800*/
@media (max-width: 799px), (max-height: 799px) {
body::before {
- position: absolute;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- font-family: Saira;
- font-size: 20px;
- color: white;
- padding: 20px;
- content: "Your window is too small! Please zoom out or use a machine with a larger screen resolution";
- background-color: var(--red);
- z-index: 100;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ font-family: Saira;
+ font-size: 20px;
+ color: white;
+ padding: 20px;
+ content: "Your window is too small! Please zoom out or use a machine with a larger screen resolution";
+ background-color: var(--red);
+ z-index: 100;
}
-} \ No newline at end of file
+}
diff --git a/draughts/statTracker.js b/draughts/statTracker.js
index c301d9f..9d863d1 100644
--- a/draughts/statTracker.js
+++ b/draughts/statTracker.js
@@ -1,12 +1,11 @@
-/**
- * The gameStatus object tracks our statics to be displayed on our splash screen.
+/* An object that holds all sorts of statistics regarding games that have completed and such. These
+ * stats get displayed on the splash screen.
*/
-
var gameStatus = {
- minimumMoves: Infinity, /* The minimum amount of moves a game was completed*/
- averageMoves: Infinity, /* The average amount of moves of all games completed*/
- ongoingGames: 0, /*How many games are ongoing*/
- totalGames: 0 /* How many games were initialized (used for calculations) */
+ minimumMoves: Infinity, // The minimum amount of moves a game was completed in
+ averageMoves: Infinity, // The average amount of moves games are completed in
+ ongoingGames: 0, // The number of ongoing games
+ totalGames: 0 // The number of total games started (used for calculations)
}
module.exports = gameStatus;