DisArray is a fast-paced, multi-player game inspired by Boggle! It is built with the MERN stack (MongoDB | Express | React | Node.js) and hosted on Heroku. Technologies include: Socket.IO, MongoDB, Mongoose, Express.js, React/ReactHooks, Redux, Node.js, HTML5, CSS3, Webpack, Heroku, Git, and Github.
Players have a minute and a half per round to find as many words as they can in the given letter tiles. At the end of each round, the words found are scored as follows:
- non-words: 0 points
- words found by more than one player: 0 points
- 3-4 letters: 1 point
- 5 letters: 2 points
- 6 letters: 3 points
- 7 letters: 5 points
- 8 or more letters: 11 points
The results at the end of each round are keyed by color:
- Green: Valid word with awarded points in brackets
- Orange: Valid word awarded no points because it was found by at least one other player
- Red (crossed off): Invalid word
The player with the highest score after 3 rounds wins!
When forming words, no letter tile can be used more than once in a single word, and consecutive letters must be adjacent in the grid. Players enter words in one of two ways:
-
Click on each tile individually, in order. When the word is fully spelled, click "Submit Word".
-
Press the mouse button down over a word's first letter and drag the cursor over the subsequent letters. Release the mouse button to submit.
-
To de-select the last letter selected, simply click it again (if clicking) / return to the previously selected letter (if dragging).
Alejandro implemented the Backend database for user information using mongoDB, mongoose, and express. Users can create an account which will allow them to have their statistics(wins, losses) tracked and if they get enough wins, displayed on the live updating leaderboard. He also implemented the modals present in the app, using react redux to manipulate state to determine when to display modals or not. Conditional update to leaderboard based on if user won or not:
//board.jsx endGame
let breadWinnerArr = []
breadWinner.forEach(i => {
breadWinnerArr.push(playerNames[i])
})
if (this.props.id) {
if (breadWinnerArr.includes(this.props.username)) {
this.props.updateUser({id: this.props.id, win: ++this.props.user.gamesWon, loss: this.props.user.gamesLost, game: ++this.props.user.gamesPlayed});
} else {
this.props.updateUser({id: this.props.id, win: this.props.user.gamesWon, loss: ++this.props.user.gamesLost, game: ++this.props.user.gamesPlayed});
}
this.props.receiveCurrentUser(this.props.user);
}
setTimeout(this.props.fetchLeaderboard, 2000);
Brad implemented the game server and socket.io sockets that coordinate the flow of game elements across multiple clients on the web. The server and clients communicate through defined message types. For example, when the server receives a chat
message from a client, it broadcasts the accompanying message to the appropriate room
, either the other players in the game or, if the user is not currently in a game, the site more broadly:
//board.jsx (Client)
this.socket.emit('chat', {
gameId: this.currentGame,
username: this.props.username,
msg: this.state.chatMessage
});
//gameServer.js
socket.on("chat", ({gameId, username, msg}) => {
if (gameId)
socket.to(gameId).emit('chat', {username, msg});
else
socket.to("site").emit('chat', {username, msg});
});
Brad's main other responsibility was enabling users to enter words from the board. Event handlers monitor mouseenter
, mouseleave
, mousedown
, and mouseup
on the board tiles and store the results in the React state. For example, the handler for mouseleave submits the current word if the mouse leaves the board while creating a word through dragging:
handleMouseLeave(e) {
const index = parseInt(e.currentTarget.dataset.index);
if (!this.mouseDown) return;
if (([0, 4, 8, 12].includes(index) && (e.nativeEvent.offsetX < 0)) || //exit left
([0, 1, 2, 3].includes(index) && (e.nativeEvent.offsetY < 0)) || //exit top
([3, 7, 11, 15].includes(index) && (e.nativeEvent.offsetX > e.currentTarget.offsetWidth)) || //exit right
([12, 13, 14, 15].includes(index) && (e.nativeEvent.offsetY > e.currentTarget.offsetHeight))) //exit bottom
{
this.submitAndReset();
}
}
Brekke constructed the game logic using OOP strategies; he created classes for the game, player, board, dice, and wordlist elements. The players' wordlists are collected on the frontend and then sent to the game class via socket.io. Once all the players' wordlists are received by the game class, a list of duplicate words is compiled and used to calculate the individual player's score (see 'Game Play' section above). Instances of the game class are stored on the game server and manipulated through the client's sockets to handle scoring between rounds and at the conclusion of the game:
// gameServer.js
socket.on("finish-round", ({id, username, foundWords}) => {
this.games[id].receiveWords({[username]: foundWords});
if (this.games[id].listsReceived === this.games[id].players.length) {
//End game if 3 rounds have been played or if this was a practice round
if (this.games[id].roundsPlayed === 3 || this.games[id].players.length === 1) {
this.io.to(id).emit("endGame", this.games[id].roundResults);
this.games[id].players.forEach(({socket}) => {
socket.join("site");
socket.leave(id);
delete this.socketsInGames[socket.id];
delete this.games[id];
});
} else {
this.io.to(id).emit("roundResults", this.games[id].roundResults[this.games[id].roundsPlayed-1]);
this.games[id].resetRoundVars();
}
}
});
Marco built custom user authorization for Disarray and created the visual style of the app using HTML5 and Cascading Style Sheets (CSS). Using various box-shadow effects to create the three-dimensional look of each die was a particularly fun challenge.
Additionally, Marco further leveraged the versatility of CSS and utilized media queries to dynamically scale the game to various screen sizes.
This included adding a custom dropdown menu and icon whenever the navigation bar is sized below a specified width.