diff options
-rw-r--r-- | draughts/app.js | 32 | ||||
-rw-r--r-- | draughts/environment.js | 17 | ||||
-rw-r--r-- | draughts/game.js | 20 | ||||
-rw-r--r-- | draughts/piece.js | 73 | ||||
-rw-r--r-- | draughts/public/javascripts/game.js | 64 | ||||
-rw-r--r-- | draughts/public/javascripts/messages.js | 9 | ||||
-rw-r--r-- | draughts/public/stylesheets/splash.css | 70 | ||||
-rw-r--r-- | draughts/public/stylesheets/style.css | 26 | ||||
-rw-r--r-- | draughts/statTracker.js | 13 |
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; |