Skip to content

brtrick/Disarray

 
 

Repository files navigation

DisArray

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.

Check Out the Live App!
Version 2 now live!

Game Play

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

Round Results

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!

End of Game Results

Word Entry

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" (v1) or the green checkmark (v2).

    Click 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.

    Click Word

  • To de-select the last letter selected, simply click it again (if clicking) / return to the previously selected letter (if dragging).

    Click Word

Contributors (and their primary responsibilities)

Alejandro Weil

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 Trick

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 on v1 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();
  }
}

Version 2 is also Brad's creation. Most of the changes are behind the scenes: he upgraded the frontend from React 17 class components to React 18 function components with hooks, from HashRouter in React Router v5 to createBrowserRouter in ReactRouter 6, and from create-react-app to Vite, improving the overall efficiency and maintainability of the application. As for user-facing changes, some of the game elements have been rearranged on the screen, the "Submit Word" button was changed to a green checkmark button in the word bank, and the practice / join buttons are now disabled during game play.

Brekke Andrew Green

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 Torre

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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 74.7%
  • CSS 23.9%
  • HTML 1.4%