diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 4d29575d..00000000 --- a/app/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/app/README.md b/app/README.md deleted file mode 100644 index 859d27a6..00000000 --- a/app/README.md +++ /dev/null @@ -1,68 +0,0 @@ -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment - -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment - -### `npm run build` fails to minify - -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/app/server.js b/app/server.js deleted file mode 100644 index 46e56fd7..00000000 --- a/app/server.js +++ /dev/null @@ -1,326 +0,0 @@ -const express = require("express"); -const path = require("path"); -const cookieParser = require("cookie-parser"); -const logger = require("morgan"); -const app = express(); -const server = require("http").createServer(app); -const fs = require("fs"); -const session = require("express-session")({ - secret: "my-secret", - resave: true, - saveUninitialized: true, - }), - sharedsession = require("express-socket.io-session"); -const socketio = require("socket.io"); -const io = socketio.listen(server); -const RED = "#ff6666"; -const BLU = "#4d79ff"; -io.use(sharedsession(session)); - -app.use(logger("dev")); -app.use(session); -app.use(express.json()); -app.use(express.urlencoded({extended: false})); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, "public"))); - -app.applyPort = function(port) { - server.listen(port); - let message = "listening on port: " + port; - console.log(message); -}; -app.applyPort(8080); - -module.exports = app; - -///connections and usages -var clientList = []; -var boardState = []; -let hints = []; -let roleState = { - bspymaster: "", - rspymaster: "", - bdetective: "", - rdetective: "", -}; - -let currentTurn = null; -let guessesRemaining = 0; - -function resetVars(){ - clientList = []; - boardState = []; - hints = []; - roleState = { - bspymaster: "", - rspymaster: "", - bdetective: "", - rdetective: "", - }; - - currentTurn = null; - guessesRemaining = 0; - io.sockets.emit("reset"); -} - -function turnAfter(role) { - switch (role) { - case "bdetective": - return "rspymaster"; - case "rspymaster": - return "rdetective"; - case "rdetective": - return "bspymaster"; - case "bspymaster": - return "bdetective"; - } -} - -function updateAll() { - sendBoardUpdate(); - sendClueUpdate(); -} - -function sendRoleState() { - io.sockets.emit("updateRoleState", roleState); -} - -function sendBoardUpdate() { - let redLeft = boardState.filter((card) => card.revealedColor !== card.borderColor && card.borderColor === RED).length; - let bluLeft = boardState.filter((card) => card.revealedColor !== card.borderColor && card.borderColor === BLU).length; - let detectivelist = boardState.map(card => { - return { - word: card.word, - borderColor: card.revealedColor, - revealedColor: card.revealedColor, - }; - }); - clientList.forEach(c => { - if (c.role.endsWith("spymaster")) { - io.to(c.connection).emit("updateBoardState", boardState, redLeft, bluLeft); - } else { - io.to(c.connection).emit("updateBoardState", detectivelist, redLeft, bluLeft); - } - }); -} - -function sendClueUpdate() { - //io.sockets.emit("updateCluestate", clueLog); -} - -function isTurn(id) { - console.log("Current Turn", currentTurn); - clientList.forEach((c) => { - if (c.connection === id) { - console.log(c.role, currentTurn, c.role === currentTurn); - return c.role === currentTurn; - } - }); -} - -function nextTurn() { - console.log("before", currentTurn); - currentTurn = turnAfter(currentTurn); - console.log("after", currentTurn); - clientList.forEach(function(cli) { - if (cli.role === currentTurn) { - io.to(cli.connection).emit("your turn"); - } else { - io.to(cli.connection).emit("lockup", getClient(currentTurn)); - } - }); -} - -io.on("connect", function(client) { - console.log("establishing connection with", client.id); - console.log("all clients", clientList); - client.send(client.id); -}); - -io.on("connection", function(socket) { - socket.on("disableOthers", function() { - console.log("clicked disable"); - socket.broadcast.emit( - "specMsg", - "only for " + socket.handshake.session.userdata, - ); - - //io.sockets.to(getSocketFromClient(socket.handshake.session.userdata)).emit("specMsg", 'only for ' + socket.handshake.session.userdata); - }); - - socket.on("setInitState", function(state, browserCache, playerOne) { - console.log("session for: ", browserCache, "socket", socket.id); - let user = browserCache.user; - let found = false; - clientList.forEach(function(element) { - if (element.name && element.name === user) { - console.log( - "updating " + user + " connection from ", - element.connection, - "to", - socket.id, - ); - element.connection = socket.id; - //io.sockets.emit("displayUser", user); - found = true; - } - }); - if (!found) { - console.log("adding client", user, "with socket", socket.id); - clientList.push({name: user, connection: socket.id, role: "none"}); - //io.sockets.emit("displayUser", "New User"); - } - - //The first user that connects sets the base cards - if (boardState.length < 1) { - console.log("state set to", state, playerOne); - boardState = state; - currentTurn = playerOne; - console.log("Starting turn: ", currentTurn); - } else { - sendRoleState(); - } - }); - - socket.on("button selected", function(state) { - boardState = state; - //io.sockets.emit("update hints", full_msg, msg); - //socket.broadcast.emit("update hints", full_msg, msg); - sendBoardUpdate(); - }); - - socket.on("clue sent", function(clue) { - //clueLog.push(clue); - sendClueUpdate(); - }); - - socket.on("hintSubmission", (sender, clue, amt) => { - hints.push({sender: sender, clue: clue, amt: amt}); - io.sockets.emit("hintHistory", hints); - if (amt !== 0) { - guessesRemaining = amt + 1; - } else { - nextTurn(); - } - nextTurn(); - - }); - - socket.on("send hint", function(msg) { - socket.handshake.session.userdata = msg; - clientList.push({name: msg, connection: socket.id}); - socket.handshake.session.save(); - - let full_msg = "[ " + getCurrentDate() + " ]: " + msg; - //io.sockets.emit("update hints", full_msg, msg); - socket.broadcast.emit("update hints", full_msg, msg); - }); - - socket.on("roleSelection", function(role, name) { - console.log("JUST SELECTED ROLE", role); - - setRoleAndName(socket.id, role, name); - - greyRole(role); - }); - - socket.on("guessed", word => { - if (isTurn(socket.id)) { - console.log("out of order"); - } - if (currentTurn.endsWith("spymaster")) return; - console.log(word); - boardState.forEach(card => { - if (card.word === word) { - card.revealedColor = card.borderColor; - if (currentTurn.startsWith("r") && card.borderColor === RED) { - guessesRemaining--; - } else if (currentTurn.startsWith("b") && card.borderColor === BLU) { - guessesRemaining--; - } else { - guessesRemaining = 0; - } - if (guessesRemaining === 0) { - nextTurn(); - } - } - }); - sendBoardUpdate(); - }); - - socket.on("allSelected", function() { - let allSel = - roleState["bdetective"] && - roleState["rdetective"] && - roleState["bspymaster"] && - roleState["rspymaster"]; - io.sockets.emit("allSelectedStatus", allSel); - }); - - socket.on("startGame", function() { - sendBoardUpdate(); - socket.broadcast.emit("closeModal"); - clientList.forEach(c => { - if (c.role === currentTurn) { - console.log("your turn: " + c.role); - io.to(c.connection).emit("your turn"); - } - }); - //logic to start the game - }); - - socket.on("resetRoles", () => { - roleState = { - bspymaster: "", - rspymaster: "", - bdetective: "", - rdetective: "", - }; - io.sockets.emit("resetRoles"); - }); - - socket.on("nextTurn", nextTurn); - - socket.on("resetAll", resetVars); - -}); - -function greyRole(role) { - console.log("Sending greyRole"); - io.sockets.emit("greyRole", role); -} - -function setRoleAndName(socketID, role, name) { - clientList.forEach(function(cl) { - if (cl.connection === socketID) { - cl.role = role; - cl.name = name; - roleState[role] = socketID; - } - }); -} - -function getClient(role) { - clientList.forEach(function(element) { - if (element.role === role) { - return element; - } - }); -} - -function getCurrentDate() { - let currentDate = new Date(); - let day = (currentDate.getDate() < 10 ? "0" : "") + currentDate.getDate(); - let month = - (currentDate.getMonth() + 1 < 10 ? "0" : "") + - (currentDate.getMonth() + 1); - let year = currentDate.getFullYear(); - let hour = (currentDate.getHours() < 10 ? "0" : "") + currentDate.getHours(); - let minute = - (currentDate.getMinutes() < 10 ? "0" : "") + currentDate.getMinutes(); - let second = - (currentDate.getSeconds() < 10 ? "0" : "") + currentDate.getSeconds(); - return ( - year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second - ); -} diff --git a/app/src/App.css b/app/src/App.css deleted file mode 100644 index fd8e208e..00000000 --- a/app/src/App.css +++ /dev/null @@ -1,308 +0,0 @@ -* { - box-sizing: border-box; -} - -body { - overflow: hidden; - height: 100vh; -} - -button:hover{ - cursor: pointer; -} - -#root { - height: 100%; -} - -.App { - background-color: #282c34; - display: flex; - flex-flow: column; - height: 100%; -} -.App-logo { - height: 40vmin; -} - -.App-header { - min-height: 15vh; - display: flex; - flex: 0 1 auto; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; - margin: 25px 0; -} -h1 { - text-align: center; - font-size: 75px; - margin-block-start: 0em; - margin-block-end: 0.11em; -} - -.App-link { - color: #09d3ac; -} - -.game { - flex: 1 1 auto; - display: flex; - height: 100%; -} - -.board { - margin-left: 5%; - width: 45%; - height: 70%; - display: flex; - flex-direction: column; - justify-content: space-between; - align-content: space-between; -} - -.board-row { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - align-items: stretch; - height: 15%; -} - -.card { - height: 100%; - border-radius: 10px; - border-width: 5px; - flex: 0 1 17%; - font-size: x-large; - min-width: 0; - overflow-wrap: break-word; -} - -.card.button:hover { - background-color: gray; - cursor: pointer; -} - -.chat { - display: flex; - flex-direction: column; - width: 50%; - align-items: center; - justify-content: space-between; - height: 70%; -} - -.chat-container { - position: relative; - width: 70%; - height: 100%; - color: black; - background-color: whitesmoke; - padding: 2%; - border-radius: 8px; - display: flex; - flex-direction: column; -} - -.log { - overflow-y: scroll; - flex: 1 1 auto; -} - -.hintSubmission { - padding-top: 2%; - display: flex; - flex-direction: row; - background: whitesmoke; - width: 100%; - border-top: 2px black solid; -} - -input { - background: rgba(0, 0, 0, 0) none repeat scroll 0 0; - border: medium none; - color: #4c4c4c; - font-size: x-large; - width: 50%; - padding-left: 5%; -} - -.amountInput { - width: 50%; - display: flex; - flex-wrap: wrap; -} - -.amount { - text-align: center; - align-content: center; - width: 20%; - font-size: x-large; -} - -.amount:hover { - cursor: pointer; - background: cornsilk; -} - -.amount.disabled { - color: lightgray; - pointer-events: none; -} -.amount.disabled:hover { - cursor: default; - background: whitesmoke; -} - -.msg { - resize: none; - width: 125px; -} - -.btn { - width: 65px; -} - -.btn:hover { - cursor: pointer; -} - -.game-board { - float: left; - margin-left: 5%; -} - -.status { - text-align: center; -} - -.clue { - margin-left: 10px; -} - -.modal { - font-family: verdana, "Lucida Grande", arial, sans-serif; - font-size: x-large; -} - -.modal-header, -.modal-body { - margin-left: 3%; -} - -.spyImage { - float: right; - margin-right: 50px; - margin-left: 30px; - margin-bottom: 20px; -} - -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; -} - -.modal-header-menu { - font-size: 10vh; - text-align: center; - display: block; - color: white; - margin-top: 0.7%; - margin-bottom: 2%; -} - -.modal-body-menu { - text-align: center; -} - -.modalButton { - border-radius: 50px; - font-size: 15px; - font-weight: bold; - background-color: white; -} - -.modalButton:hover { - cursor: pointer; -} - -.redrole { - border: medium; - border-color: black; - border-style: solid; - border-width: 2px; - color: black; - font-size: xx-large; - width: 20%; - text-align: center; - background-color: #ff6666; - padding: 0.5%; -} - -.bluerole { - border: medium; - border-color: black; - border-style: solid; - border-width: 2px; - color: black; - font-size: xx-large; - width: 20%; - text-align: center; - background-color: #4d79ff; - padding: 0.5%; -} - -.play { - color: black; - font-size: xx-large; - width: 30%; - margin-bottom: 1%; - padding: 0.5%; -} - -.center { - display: block; - margin-left: auto; - margin-right: auto; -} - -#menuName { - border: medium; - color: white; - border-style: solid; - border-width: 2px; - font-size: xx-large; - width: 40%; - text-align: center; - padding: 0.5%; -} - -#yourTeam{ - margin-bottom: 65px; - text-align: center; - padding: 5px; -} - -.good-to-go{ - background-color: limegreen; -} - -.disabled-events{ - pointer-events: none; - opacity: 0.4; -} - -.Reset { - font-size: x-large; - padding: 0.5%; - width: 40%; - border-radius: 8px; -} - -.test1 { - text-align: center; - margin-bottom: 1.5%; -} diff --git a/app/src/App.js b/app/src/App.js deleted file mode 100644 index 68f168a9..00000000 --- a/app/src/App.js +++ /dev/null @@ -1,676 +0,0 @@ -import React from "react"; -import Modal from "react-modal"; -import shortid from "shortid"; -import "./App.css"; - -import socketIOClient from "socket.io-client"; -import {SSL_OP_SINGLE_DH_USE} from "constants"; - -const socket = socketIOClient("localhost:8080"); - -const RED = "#ff6666"; -const BLU = "#4d79ff"; -//would normally come from database but this is for testing -//list of ALL words -let allReady = false; -let allWords = [ - "wall", - "back", - "orange", - "crash", - "hawk", - "kiwi", - "lab", - "ice cream", - "india", - "theater", - "plane", - "parachute", - "telescope", - "match", - "police", - "post", - "ray", - "kid", - "wind", - "box", - "knife", - "church", - "bell", - "lemon", - "triangle", - "cap", - "jam", - "organ", - "engine", - "agent", - "buck", - "day", - "doctor", - "ball", -]; - -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - modalOpen: true, - selectedRole: "", - }; - - console.log("App"); - socket.on("closeModal", this.closeModal.bind(this)); - } - - componentDidMount() { - } - - closeModal() { - this.setState({modalOpen: false}); - } - - selectRole(role) { - this.setState({selectedRole: role}); - } - - render() { - return ( -
-
-
-

Codenames

-
-
- -
- {this.state.modalOpen && ( - - )} - -
- -
-
- ); - } -} - -Modal.setAppElement("#root"); - -class Modals extends React.Component { - constructor() { - super(); - - this.state = { - modalIsOpen: false, - }; - - this.openModal = this.openModal.bind(this); - this.closeModal = this.closeModal.bind(this); - } - - openModal() { - this.setState({modalIsOpen: true}); - } - - closeModal() { - this.setState({modalIsOpen: false}); - } - - render() { - return ( -
- - -
-
- - × - -
-
-
-
How to Play:
-
-
-
- -

- On the board there are 25 tiles, each of which have the - codename of different secret agent. Each agent is either a - blue team agent, red team agent, a civilian, or the assassin. - The game is played with two teams, a red team and a blue team. - Both teams have one spymaster and one detective. -
-
- At the beginning of the game, only the spymasters can see the - position of the agents (displayed by the border of the cards). - The spymasters will each take turns giving clues to their - team's detective on which agents belong to their team. These - clues will include a one word hint and the amount of agents - that the hint relates to. The detective will then guess which - word the clue relates to by clicking on a codename tile. If - the detective guesses correctly then they will be able to - continue guessing until they have guessed the amount specified - by the spymaster. If the detective's guesses are all correct, - they will get a free guess which they can choose to make or - pass. If the detective guesses incorrectly it becomes the - other teams turn. If any detective guesses the assassin then - their team atomatically loses the game. -
-
A team wins the game if their detective can find all of - their team's agents or if the other team accidentally finds - the assassin. Good Luck! -

-
-
-
-
-
- ); - } -} - -class Card extends React.Component { - constructor(props) { - super(props); - this.changeStyle = this.changeStyle.bind(this); - this.state = { - word: this.props.word, - borderColor: this.props.border, - }; - } - - changeStyle(color) { - this.setState({ - color: this.state.cardColor, - }); - } - - render() { - return ( - - ); - } -} - -class Chat extends React.Component { - constructor(props) { - super(props); - this.state = { - log: [], - clue: "", - }; - socket.on("hintHistory", hints => { - this.setState({log: hints}); - }); - } - - submitHint(amount) { - if (this.state.clue === "") { - return; - } - socket.emit("hintSubmission", this.props.role, this.state.clue, amount); - } - - createAmounts() { - let table = []; - for (let i = 0; i < 10; i++) { - table.push( -
this.submitHint(i)} - className={"amount" + (i <= this.props.wordsLeft ? "" : " disabled")} - > - {i} -
, - ); - } - return table; - } - - render() { - return ( -
-
-
- {this.state.log.map((hint, index) => { - return ( -
- {hint.clue} : {hint.amt} -
- ); - })} -
- {this.props.role.endsWith("spymaster") && ( -
- this.setState({clue: e.target.value})} - id="msg" - /> -
{this.createAmounts()}
-
- )} -
-
- ); - } -} - -class Board extends React.Component { - - renderCard(card) { - return ( - - ); - } - - render() { - return ( -
- {[0, 1, 2, 3, 4].map(n => { - return ( -
- {" "} - {[0, 1, 2, 3, 4].map(m => { - return this.renderCard(this.props.cards[n * 5 + m]); - })} -
- ); - })} -
- ); - } -} - -class Game extends React.Component { - constructor(props) { - super(props); - let firstteam = Math.floor(Math.random() * 2); - this.state = { - cards: setBoard(firstteam), - redLeft: firstteam === 0 ? 9 : 8, - bluLeft: firstteam === 1 ? 9 : 8, - firstteam: firstteam, - disabled: true, - }; - socket.on("updateBoardState", (bs, redLeft, bluLeft) => { - console.log("Received update board state"); - console.log(bs); - this.setState({cards: bs, redLeft: redLeft, bluLeft: bluLeft}); - }); - socket.on("lockup", () => { - //banner to other player name - this.setState({disabled: true}); - }); - socket.on("your turn", lastTurnData => { - this.setState({disabled: false}); - }); - } - - render() { - return ( -
- - -
-
-
- ); - } -} - -class Menu extends React.Component { - constructor(props) { - super(props); - - this.selectRole = props.selectRole; - this.state = { - modalIsOpen: true, - selectedRole: null, - }; - - this.openModal = this.openModal.bind(this); - this.closeModal = this.closeModal.bind(this); - } - - openModal() { - this.setState({modalIsOpen: true}); - } - - closeModal() { - this.setState({modalIsOpen: false}); - } - - render() { - return ( -
-
- -
-
-
-
Codenames
-
-
- -
-
- Trulli -
-
- - -
- - -
-
- - -
-
-
-
-
-
- ); - } -} - -function makeGray(btn, selected) { - var redspy = document.getElementById("rspymaster"); - var reddet = document.getElementById("rdetective"); - var bluespy = document.getElementById("bspymaster"); - var bluedet = document.getElementById("bdetective"); - bluespy.disabled = true; - redspy.disabled = true; - reddet.disabled = true; - bluedet.disabled = true; - - let b = document.getElementById(btn); - let teamColor = b.style.backgroundColor; - let roleData = b.innerHTML.split("Team"); - b.style.backgroundColor = "gray"; - b.style.borderColor = "gold"; - selected.state.selectedRole = btn; - selected.selectRole(btn); - - let nameInput = document.getElementById("menuName"); - let name = nameInput.value || selected.state.selectedRole; - nameInput.disabled = true; - sessionStorage.setItem("userInfo", name); - socket.emit("roleSelection", selected.state.selectedRole, name); - - let whoYouAre = document.getElementById("yourTeam"); - console.log(teamColor); - whoYouAre.style.backgroundColor = getColor(roleData[0]); - whoYouAre.innerHTML = name + " (" + roleData[1] + " )"; -} - -function getColor(color){ - if(color === "Blue "){ - console.log('blue'); - return BLU; - }else{ - return RED; - } -} - -function resetMenu(play) { - var redspy = document.getElementById("rspymaster"); - var reddet = document.getElementById("rdetective"); - var bluespy = document.getElementById("bspymaster"); - var bluedet = document.getElementById("bdetective"); - redspy.style.backgroundColor = "#ff6666"; - redspy.style.borderColor = "black"; - redspy.disabled = false; - bluespy.style.backgroundColor = "#4d79ff"; - bluespy.style.borderColor = "black"; - bluespy.disabled = false; - reddet.style.backgroundColor = "#ff6666"; - reddet.style.borderColor = "black"; - reddet.disabled = false; - bluedet.style.backgroundColor = "#4d79ff"; - bluedet.style.borderColor = "black"; - bluedet.disabled = false; - - socket.emit("resetRoles"); -} - -function closeMenu(play) { - var u = document.getElementById("menuName"); - var redspy = document.getElementById("rspymaster"); - var reddet = document.getElementById("rdetective"); - var bluespy = document.getElementById("bspymaster"); - var bluedet = document.getElementById("bdetective"); - var username = u.value; - if (allReady) { - play.closeModal(); - socket.emit("startGame"); - } -} - -//takes full list of words and picks 25 unique for a game -function setBoard(order) { - let cards = []; - - //for setting board words - for (let i = 0; i < 25; i++) { - cards[i] = {revealedColor: "whitesmoke"}; - let useCheck = false; - let counter = Math.floor(Math.random() * allWords.length); - for (let j = 0; j < cards.length; j++) { - if (cards[j].word === allWords[counter]) { - useCheck = true; - } - } - if (!useCheck) { - cards[i].word = allWords[counter]; - } else { - i--; - } - } - //for words to teams - setTeam(RED, order); - - setTeam(BLU, order); - - setTeam("black", order); - - setTeam("tan", order); - - function setTeam(type, order) { - let amount = 7; - if (type === "black") { - amount = 1; - } - if ((order === 0 && type === RED) || (order === 1 && type === BLU)) { - amount += 2; - } else if ( - (order === 1 && type === RED) || - (order === 0 && type === BLU) - ) { - amount++; - } - - for (let j = 0; j < amount; j++) { - let counter = Math.floor(Math.random() * 25); - if (cards[counter].borderColor == null) { - cards[counter].borderColor = type; - } else { - j--; - } - } - } - - socket.emit("setInitState", cards, getBrowserData(), - order === 0 ? "rspymaster" : "bspymaster"); - return cards; -} - -//send out message to set initial state if it hasnt already -window.onload = function() { - console.log("the sessionstorage: ", sessionStorage.getItem("userInfo")); -}; - -function getBrowserData() { - return {user: sessionStorage.getItem("userInfo") || "USER" + Math.random()}; -} - -function getBoardState() { - let cards = []; - let collectedCards = document.getElementsByClassName("card"); - const NUM_CARDS_ROW = 5; - for (let i = 0; i < NUM_CARDS_ROW; i++) { - for (let j = 0; j < NUM_CARDS_ROW; j++) { - let selCard = collectedCards[i * NUM_CARDS_ROW + j]; - if (selCard) { - cards[i * NUM_CARDS_ROW + j] = { - word: selCard.innerHTML, - revealedColor: selCard.style.backgroundColor, - borderColor: selCard.style.borderColor, - }; - } - } - } - return cards; -} - -function resetAll(){ - socket.emit("resetAll"); -} - -socket.on("reset", ()=>{ - window.location.reload(); -}); - -socket.on("update hints", function(msg) { - let final_message = document.createElement("p"); - final_message.innerHTML = msg; - document.getElementsByClassName("chat-container")[0].append(final_message); -}); - -socket.on("greyRole", function(role) { - let button = document.getElementById(role); - button.style.backgroundColor = "grey"; - button.disabled = true; - socket.emit("allSelected"); -}); - -socket.on("allSelectedStatus", function(status) { - if (status) { - allReady = true; - document.getElementById("playBtn").classList.add('good-to-go'); - } -}); - -socket.on("updateRoleState", function(rs) { - for (let role in rs) { - if (rs.hasOwnProperty(role) && rs[role]) { - console.log("greying role", role); - let button = document.getElementById(role); - button.style.backgroundColor = "grey"; - button.disabled = true; - } - } -}); - -socket.on("resetRoles", () => { - var redspy = document.getElementById("rspymaster"); - var reddet = document.getElementById("rdetective"); - var bluespy = document.getElementById("bspymaster"); - var bluedet = document.getElementById("bdetective"); - - redspy.style.backgroundColor = "#ff6666"; - redspy.style.borderColor = "black"; - redspy.disabled = false; - bluespy.style.backgroundColor = "#4d79ff"; - bluespy.style.borderColor = "black"; - bluespy.disabled = false; - reddet.style.backgroundColor = "#ff6666"; - reddet.style.borderColor = "black"; - reddet.disabled = false; - bluedet.style.backgroundColor = "#4d79ff"; - bluedet.style.borderColor = "black"; - bluedet.disabled = false; - - document.getElementById("menuName").disabled = false; - - allReady = false; - document.getElementById("playBtn").classList.remove('good-to-go'); -}); - -export default App; diff --git a/app/package-lock.json b/package-lock.json similarity index 98% rename from app/package-lock.json rename to package-lock.json index 7aeae910..4335d56e 100644 --- a/app/package-lock.json +++ b/package-lock.json @@ -3532,6 +3532,145 @@ } } }, + "concurrently": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.0.0.tgz", + "integrity": "sha512-1yDvK8mduTIdxIxV9C60KoiOySUl/lfekpdbI+U5GXaPrgdffEavFa9QZB3vh68oWOpbCC+TuvxXV9YRPMvUrA==", + "requires": { + "chalk": "^2.4.2", + "date-fns": "^2.0.1", + "lodash": "^4.17.15", + "read-pkg": "^4.0.1", + "rxjs": "^6.5.2", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.2.1", + "yargs": "^12.0.5" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "requires": { + "has-flag": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "confusing-browser-globals": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", @@ -4039,6 +4178,11 @@ } } }, + "date-fns": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.4.1.tgz", + "integrity": "sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==" + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -5279,20 +5423,25 @@ } }, "express-session": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz", - "integrity": "sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", + "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", "requires": { - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", "parseurl": "~1.3.3", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.0", "uid-safe": "~2.1.5" }, "dependencies": { + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5310,6 +5459,11 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, @@ -11874,6 +12028,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -12514,6 +12673,11 @@ "punycode": "^2.1.0" } }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==" + }, "ts-pnp": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.4.tgz", diff --git a/app/package.json b/package.json similarity index 85% rename from app/package.json rename to package.json index cc0b89ed..610c8e13 100644 --- a/app/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "concurrently": "^5.0.0", "cookie-parser": "^1.4.4", "express": "latest", "express-session": "latest", @@ -17,7 +18,8 @@ "socket.io-client": "^2.3.0" }, "scripts": { - "start": "concurrently \"react-scripts start\" \"node server.js\"", + "start": "node server.js", + "dev": "concurrently \"react-scripts start\" \"nodemon server.js --inspect\"", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/proposal.md b/proposal.md deleted file mode 100644 index c6ddae90..00000000 --- a/proposal.md +++ /dev/null @@ -1,22 +0,0 @@ -# cs4241-FinalProject - -## Team Members -- Daniel Duff -- Timothy Winters -- Andrew Levy - -_Online **Codenames** Multiplayer Game_ -For our Final Project, we plan on implementing an online version of the card game _codenames_ which will allow users to play together online and create their own cards and play styles. - -## This project will feature: -- HTML static pages and validations for form input to prevent duplicate cards -- A login feature, which allows users to log in and edit/produce their own personal card decks. -- A Node.js SQLite database which hold all of the login and card deck info for all users. -- Dynamic Javascript that will implement selectors and buttons for selecting and making cards in game as well as communicating with teammates (if necessary) -- Javascript to interact (add/modify/remove) with the database. -- Card presets, which will allow users to play the game without needing to create their own deck (if they so choose) - -## Goals for this Project: -- Create a fun interactive game that people can play online. -- Utilize web sockets to allow for realtime communication between players, browser, and the server. -- Allow players customization options for the game that paying with cards in real life does not offer. diff --git a/app/public/detective.png b/public/detective.png similarity index 100% rename from app/public/detective.png rename to public/detective.png diff --git a/app/public/favicon.ico b/public/favicon.ico similarity index 100% rename from app/public/favicon.ico rename to public/favicon.ico diff --git a/app/public/index.html b/public/index.html similarity index 100% rename from app/public/index.html rename to public/index.html diff --git a/app/public/logo192.png b/public/logo192.png similarity index 100% rename from app/public/logo192.png rename to public/logo192.png diff --git a/app/public/logo512.png b/public/logo512.png similarity index 100% rename from app/public/logo512.png rename to public/logo512.png diff --git a/app/public/mag.ico b/public/mag.ico similarity index 100% rename from app/public/mag.ico rename to public/mag.ico diff --git a/app/public/manifest.json b/public/manifest.json similarity index 100% rename from app/public/manifest.json rename to public/manifest.json diff --git a/app/public/robots.txt b/public/robots.txt similarity index 100% rename from app/public/robots.txt rename to public/robots.txt diff --git a/app/public/spy.png b/public/spy.png similarity index 100% rename from app/public/spy.png rename to public/spy.png diff --git a/server.js b/server.js new file mode 100644 index 00000000..fdc2e6a5 --- /dev/null +++ b/server.js @@ -0,0 +1,416 @@ +const express = require("express"); +const path = require("path"); +const cookieParser = require("cookie-parser"); +const logger = require("morgan"); +const app = express(); +const server = require("http").createServer(app); +const fs = require("fs"); +const session = require("express-session")({ + secret: "my-secret", + resave: true, + saveUninitialized: true, + }), + sharedsession = require("express-socket.io-session"); +const io = require("socket.io")(server); +const port = process.env.PORT || 8080; +const RED = "#ff6666"; +const BLU = "#4d79ff"; +io.use(sharedsession(session)); + +app.use(logger("dev")); +app.use(session); +app.use(express.json()); +app.use(express.urlencoded({extended: false})); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, "build"))); + +app.get("/", (req, res) => { + res.sendFile(path.join(__dirname, "build/index.html")); +}); + +module.exports = app; + +let allWords = [ + "wall", + "back", + "orange", + "crash", + "hawk", + "kiwi", + "lab", + "ice cream", + "india", + "theater", + "plane", + "parachute", + "telescope", + "match", + "police", + "post", + "ray", + "kid", + "wind", + "box", + "knife", + "church", + "bell", + "lemon", + "triangle", + "cap", + "jam", + "organ", + "engine", + "agent", + "buck", + "day", + "doctor", + "ball", +]; +///connections and usages +let game = { + clientList: [], + boardState: [], + hints: [], + _roles: new Proxy({ + bspymaster: "", + rspymaster: "", + bdetective: "", + rdetective: "", + }, { + set: (obj, prop, value) => { + obj[prop] = value; + sendRoleState(); + }, + }), + get roles() { + return this._roles; + }, + set roles(roles) { + this._roles = roles; + sendRoleState(); + }, + currentTurn: null, + guessesRemaining: 0, +}; +function generateGame() { + //for setting board words + for (let i = 0; i < 25; i++) { + game.boardState[i] = {revealedColor: "whitesmoke"}; + let useCheck = false; + let counter = Math.floor(Math.random() * allWords.length); + for (let j = 0; j < game.boardState.length; j++) { + if (game.boardState[j].word === allWords[counter]) { + useCheck = true; + } + } + if (!useCheck) { + game.boardState[i].word = allWords[counter]; + } else { + i--; + } + } + //for words to teams + if (Math.floor(Math.random() * 2) === 0) { + setTeam(RED, 9); + setTeam(BLU, 8); + game.currentTurn = "rspymaster"; + } else { + setTeam(RED, 8); + setTeam(BLU, 9); + game.currentTurn = "bspymaster"; + } + setTeam("black", 1); + setTeam("tan", 7); + + function setTeam(color, amount) { + for (let j = 0; j < amount; j++) { + let index = Math.floor(Math.random() * game.boardState.length); + if (game.boardState[index].borderColor == null) { + game.boardState[index].borderColor = color; + } else { + j--; + } + } + } +} +generateGame(); + +function resetVars() { + game = { + clientList: [], + boardState: [], + hints: [], + _roles: new Proxy({ + bspymaster: "", + rspymaster: "", + bdetective: "", + rdetective: "", + }, { + set: (obj, prop, value) => { + obj[prop] = value; + sendRoleState(); + }, + }), + get roles() { + return this._roles; + }, + set roles(roles) { + this._roles = roles; + sendRoleState(); + }, + currentTurn: null, + guessesRemaining: 0, + }; + io.sockets.emit("reset"); +} + +function turnAfter(role) { + switch (role) { + case "bdetective": + return "rspymaster"; + case "rspymaster": + return "rdetective"; + case "rdetective": + return "bspymaster"; + case "bspymaster": + return "bdetective"; + } +} + +function sendRoleState() { + game.clientList.forEach((client) => { + io.to(client.connection) + // Boolean list representing available roles + .emit("updatedRoles", Object.keys(game.roles).reduce((result, key) => { + result[key] = game.roles[key] === ""; + return result; + }, {}), client.role); + }); +} + +function sendBoardUpdate() { + let redLeft = game.boardState.filter( + (card) => card.revealedColor !== card.borderColor && card.borderColor === + RED).length; + let bluLeft = game.boardState.filter( + (card) => card.revealedColor !== card.borderColor && card.borderColor === + BLU).length; + + let detectiveList = game.boardState.map(card => { + return { + word: card.word, + borderColor: card.revealedColor, + revealedColor: card.revealedColor, + }; + }); + game.clientList.forEach(c => { + console.log(c); + if (c.role.endsWith("spymaster")) { + io.to(c.connection) + .emit("updateBoardState", game.boardState, redLeft, bluLeft); + } else { + io.to(c.connection) + .emit("updateBoardState", detectiveList, redLeft, bluLeft); + } + }); +} + +function isTurn(id) { + console.log("Current Turn", game.currentTurn); + game.clientList.forEach((c) => { + if (c.connection === id) { + console.log(c.role, game.currentTurn, c.role === game.currentTurn); + debugger + return c.role === game.currentTurn; + } + }); +} + +function nextTurn() { + console.log("before", game.currentTurn); + game.currentTurn = turnAfter(game.currentTurn); + console.log("after", game.currentTurn); + game.clientList.forEach(function(cli) { + if (cli.role === game.currentTurn) { + io.to(cli.connection).emit("your turn"); + } else { + io.to(cli.connection).emit("lockup", getClient(game.currentTurn)); + } + }); +} + +io.on("connect", function(client) { + console.log("establishing connection with", client.id); + game.clientList.push({name: "", connection: client.id, role: ""}); + console.log("all clients", game.clientList); + client.send(client.id); +}); + +io.on("connection", function(socket) { + console.log(socket.id, "connected"); + sendRoleState(); + socket.on("disconnect", () => { + console.log("disconnecting", socket.id); + let client = game.clientList.filter( + (client) => client.connection === socket.id)[0]; + if (client.role) { + game.roles[client.role] = ""; + } + game.clientList = game.clientList.filter( + (client) => client.connection !== socket.id); + if (game.clientList.length === 0) { + resetVars(); + generateGame(); + } + }); + + socket.on("setInitState", function(state, browserCache, playerOne) { + console.log("session for: ", browserCache, "socket", socket.id); + let found = false; + game.clientList.forEach(function(client) { + if (client.connection === socket.id) { + found = true; + } + }); + if (!found) return; + + //The first user that connects sets the base cards + if (game.boardState.length < 1) { + console.log("state set to", state, playerOne); + game.boardState = state; + game.currentTurn = playerOne; + } + }); + + socket.on("roleSelection", function(role) { + setRole(socket.id, role); + if (Object.values(game.roles).every((role) => role !== "")) { + io.sockets.emit("allSelectedStatus"); + } + }); + + socket.on("button selected", function(state) { + game.boardState = state; + //io.sockets.emit("update hints", full_msg, msg); + //socket.broadcast.emit("update hints", full_msg, msg); + sendBoardUpdate(); + }); + + socket.on("hintSubmission", (sender, clue, amt) => { + game.hints.push({sender: sender, clue: clue, amt: amt}); + io.sockets.emit("hintHistory", game.hints); + if (amt !== 0) { + game.guessesRemaining = amt + 1; + } else { + nextTurn(); + } + nextTurn(); + + }); + + socket.on("guessed", word => { + if (isTurn(socket.id)) { + console.log("out of order"); + } + if (game.currentTurn.endsWith("spymaster")) return; + console.log(word); + game.boardState.forEach(card => { + if (card.word === word) { + card.revealedColor = card.borderColor; + if (game.currentTurn.startsWith("r") && card.borderColor === RED) { + game.guessesRemaining--; + } else if (game.currentTurn.startsWith("b") && card.borderColor === + BLU) { + game.guessesRemaining--; + } else { + game.guessesRemaining = 0; + } + if (game.guessesRemaining === 0) { + nextTurn(); + } + } + }); + sendBoardUpdate(); + }); + + socket.on("startGame", function() { + if (Object.values(game.roles).every((role) => role !== "")) { + io.sockets.emit("closeModal"); + sendBoardUpdate(); + game.clientList.forEach(c => { + if (c.role === game.currentTurn) { + console.log("your turn: " + c.role); + io.to(c.connection).emit("your turn"); + } + }); + } + }); + + socket.on("resetRoles", () => { + game.roleState = { + bspymaster: "", + rspymaster: "", + bdetective: "", + rdetective: "", + }; + io.sockets.emit("resetRoles"); + }); + + socket.on("nextTurn", nextTurn); + + socket.on("resetAll", resetVars); + +}); + +function setRole(socketID, role) { + let client = game.clientList.filter((c) => c.connection === socketID)[0]; + + if (!client) { + console.log("no client"); + return; + } + + // clear old role + if (client.role) { + game.roles[client.role] = ""; + client.role = ""; + } + + if (role === "") { + // clearing role selection for user + client.role = ""; + game.roles[role] = ""; + } else { + if (game.roles[role] === "") { + client.role = role; + game.roles[role] = socketID; + } + } +} + +function getClient(role) { + game.clientList.forEach(function(element) { + if (element.role === role) { + return element; + } + }); +} + +function getCurrentDate() { + let currentDate = new Date(); + let day = (currentDate.getDate() < 10 ? "0" : "") + currentDate.getDate(); + let month = + (currentDate.getMonth() + 1 < 10 ? "0" : "") + + (currentDate.getMonth() + 1); + let year = currentDate.getFullYear(); + let hour = (currentDate.getHours() < 10 ? "0" : "") + currentDate.getHours(); + let minute = + (currentDate.getMinutes() < 10 ? "0" : "") + currentDate.getMinutes(); + let second = + (currentDate.getSeconds() < 10 ? "0" : "") + currentDate.getSeconds(); + return ( + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second + ); +} + +server.listen(port); diff --git a/src/App.css b/src/App.css new file mode 100644 index 00000000..c54a3410 --- /dev/null +++ b/src/App.css @@ -0,0 +1,340 @@ +* { + box-sizing: border-box; +} + +body { + background-color: #282c34; +} + +button:hover { + cursor: pointer; +} + +#root { + height: 100%; +} + +.App { + background-color: #282c34; + display: flex; + flex-flow: column; + height: 100%; + align-items: center; +} + +.App-logo { + height: 40vmin; +} + +.App-header { + display: flex; + flex: 0 1 auto; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; + margin: 25px 0; +} + +h1 { + text-align: center; + font-size: 75px; + margin-block-start: 0em; + margin-block-end: 0.11em; +} + +.App-link { + color: #09d3ac; +} + + +.modal { + font-family: verdana, "Lucida Grande", arial, sans-serif; + font-size: x-large; +} + +.menu-body { + display: flex; + flex-direction: column; + align-items: center; + width: 50%; +} + +#menuName { + border: 2px solid; + color: white; + font-size: xx-large; + text-align: center; + padding: 0.5%; + margin-bottom: 5%; +} + +.menu-body img { +} + +.roleSelector { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 5%; +} + +.role { + border: 2px black solid; + font-size: xx-large; + color: white; + width: 50%; + text-align: center; + padding: 1% 0; + filter: brightness(25%); +} + +.role:nth-child(-n+2) { + margin-bottom: 1%; +} + +.role.red { + background-color: #ff6666; +} + +.role.blue { + background-color: #4d79ff; +} + +.role.selected, +.role:enabled:active { + filter: none; +} + +.role:disabled { + cursor: default; + background-color: gray; +} + +.statusButtons { + display: flex; + justify-content: space-around; + width: 100%; +} + +.status { + color: black; + font-size: xx-large; + width: 40%; +} + +.play:not(:disabled) { + background-color: limegreen; +} + +.reset { + background-color: darkred; + +} + +.game { + flex: 1 1 auto; + display: flex; + height: 100%; + width: 100%; + flex-wrap: wrap; + justify-content: center; +} + +.board { + margin-left: 5%; + width: 45%; + height: 70%; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-content: space-between; +} + +.card { + height: 17%; + border-radius: 10px; + border-width: 5px; + border-style: solid; + flex: 0 1 17%; + font-size: x-large; + min-width: 0; + overflow-wrap: break-word; + background-color: whitesmoke; +} + +.card svg { + text-align: center; +} + +.card.button:hover { + background-color: gray; + cursor: pointer; +} + +.chat { + display: flex; + flex-direction: column; + width: 50%; + align-items: center; + justify-content: space-between; + height: 70%; +} + +.chat-container { + position: relative; + width: 70%; + height: 100%; + color: black; + background-color: whitesmoke; + padding: 2%; + border-radius: 8px; + display: flex; + flex-direction: column; +} + +.log { + overflow-y: scroll; + flex: 1 1 auto; +} + +.hintSubmission { + padding-top: 2%; + display: flex; + flex-direction: row; + background: whitesmoke; + width: 100%; + border-top: 2px black solid; +} + +input { + background: rgba(0, 0, 0, 0) none repeat scroll 0 0; + border: medium none; + color: #4c4c4c; + font-size: x-large; + width: 50%; + padding-left: 5%; +} + +.amountInput { + width: 50%; + display: flex; + flex-wrap: wrap; +} + +.amount { + text-align: center; + align-content: center; + width: 20%; + font-size: x-large; +} + +.amount:hover { + cursor: pointer; + background: cornsilk; +} + +.amount.disabled { + color: lightgray; + pointer-events: none; +} + +.amount.disabled:hover { + cursor: default; + background: whitesmoke; +} + +.msg { + resize: none; + width: 125px; +} + +.btn { + width: 65px; +} + +.btn:hover { + cursor: pointer; +} + +.game-board { + float: left; + margin-left: 5%; +} + +.status { + text-align: center; +} + +.clue { + margin-left: 10px; +} + +.spyImage { + float: right; + margin-right: 50px; + margin-left: 30px; + margin-bottom: 20px; +} + +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; +} + +.modal-header-menu { + font-size: 10vh; + text-align: center; + display: block; + color: white; + margin-top: 0.7%; + margin-bottom: 2%; +} + +.modal-body-menu { + text-align: center; +} + +.modalButton { + border-radius: 50px; + font-size: 15px; + font-weight: bold; + background-color: white; +} + +.modalButton:hover { + cursor: pointer; +} + + +.center { + display: block; + margin-left: auto; + margin-right: auto; +} + + +#yourTeam { + margin-bottom: 65px; + text-align: center; + padding: 5px; +} + + +.disabled-events { + pointer-events: none; + opacity: 0.4; +} + +.Reset { + font-size: x-large; + padding: 0.5%; + width: 40%; + border-radius: 8px; +} + +.test1 { + text-align: center; + margin-bottom: 1.5%; +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 00000000..4c5d034f --- /dev/null +++ b/src/App.js @@ -0,0 +1,425 @@ +import React from "react"; +import Modal from "react-modal"; +import shortid from "shortid"; +import "./App.css"; + +import io from "socket.io-client"; + +const socket = io(); + +const RED = "#ff6666"; +const BLU = "#4d79ff"; +//would normally come from database but this is for testing +//list of ALL words + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { + menuOpen: true, + selectedRole: "", + username: "", + roleIsAvailable: { + rspymaster: true, + rdetective: true, + bspymaster: true, + bdetective: true, + }, + + }; + + console.log("App"); + socket.on("closeModal", this.closeMenu.bind(this)); + socket.on("updatedRoles", (roles, selectedRole) => { + this.setState({roleIsAvailable: roles, selectedRole: selectedRole}); + }); + } + + closeMenu() { + this.setState({menuOpen: false}); + } + + updateUsername(username) { + this.setState({username: username}); + } + + selectRole(role) { + if (this.state.selectedRole === role) { + // User is clicking their role again + socket.emit("roleSelection", ""); + } else { + socket.emit("roleSelection", role); + } + } + + render() { + return ( +
+
+
+

Codenames

+ {!this.state.menuOpen ? +
{this.state.selectedRole}
: + null} +
+ +
+ {this.state.menuOpen ? + + : } +
+ ); + } +} + +class Menu extends React.Component { + constructor(props) { + super(props); + + this.selectRole = props.selectRole; + this.state = { + modalIsOpen: true, + readyToPlay: false, + }; + + this.openModal = this.openModal.bind(this); + this.closeModal = this.closeModal.bind(this); + socket.on("allSelectedStatus", () => { + console.log("all selected"); + this.setState({readyToPlay: true}); + }); + } + + openModal() { + this.setState({modalIsOpen: true}); + } + + closeModal() { + this.setState({modalIsOpen: false}); + } + + render() { + return ( +
+ this.props.updateUsername(e.target.value)} + /> + Trulli +
+ + + + +
+
+ + +
+
+ ); + } +} + +Modal.setAppElement("#root"); + +class Modals extends React.Component { + constructor(props) { + super(props); + + this.state = { + modalIsOpen: false, + }; + + this.openModal = this.openModal.bind(this); + this.closeModal = this.closeModal.bind(this); + } + + openModal() { + this.setState({modalIsOpen: true}); + } + + closeModal() { + this.setState({modalIsOpen: false}); + } + + render() { + return ( +
+ + +
+
+ + × + +
+
+
+
How to Play:
+
+
+
+ +

+ On the board there are 25 tiles, each of which have the + codename of different secret agent. Each agent is either a + blue team agent, red team agent, a civilian, or the assassin. + The game is played with two teams, a red team and a blue team. + Both teams have one spymaster and one detective. +
+
+ At the beginning of the game, only the spymasters can see the + position of the agents (displayed by the border of the cards). + The spymasters will each take turns giving clues to their + team's detective on which agents belong to their team. These + clues will include a one word hint and the amount of agents + that the hint relates to. The detective will then guess which + word the clue relates to by clicking on a codename tile. If + the detective guesses correctly then they will be able to + continue guessing until they have guessed the amount specified + by the spymaster. If the detective's guesses are all correct, + they will get a free guess which they can choose to make or + pass. If the detective guesses incorrectly it becomes the + other teams turn. If any detective guesses the assassin then + their team atomatically loses the game. +
+
A team wins the game if their detective can find all of + their team's agents or if the other team accidentally finds + the assassin. Good Luck! +

+
+
+
+
+
+ ); + } +} + +class Card extends React.Component { + constructor(props) { + super(props); + this.state = { + word: this.props.word, + borderColor: this.props.border, + }; + } + + render() { + return ( +
socket.emit("guessed", this.props.value)}> + + {this.props.value} + +
+ ); + } +} + +class Chat extends React.Component { + constructor(props) { + super(props); + this.state = { + log: [], + clue: "", + }; + socket.on("hintHistory", hints => { + this.setState({log: hints}); + }); + } + + submitHint(amount) { + if (this.state.clue === "") { + return; + } + socket.emit("hintSubmission", this.props.role, this.state.clue, amount); + } + + createAmounts() { + let table = []; + for (let i = 0; i < 10; i++) { + table.push( +
this.submitHint(i)} + className={"amount" + (i <= this.props.wordsLeft ? "" : " disabled")} + > + {i} +
, + ); + } + return table; + } + + render() { + return ( +
+
+
+ {this.state.log.map((hint, index) => { + return ( +
+ {hint.clue} : {hint.amt} +
+ ); + })} +
+ {this.props.role.endsWith("spymaster") && ( +
+ this.setState({clue: e.target.value})} + id="msg" + /> +
{this.createAmounts()}
+
+ )} +
+
+ ); + } +} + +class Board extends React.Component { + + renderCard(card) { + return ( + + ); + } + + render() { + return ( +
+ {this.props.cards.map(card => { + return this.renderCard(card); + })} +
+ ); + } +} + +class Game extends React.Component { + constructor(props) { + super(props); + let firstteam = Math.floor(Math.random() * 2); + this.state = { + cards: [], + redLeft: firstteam === 0 ? 9 : 8, + bluLeft: firstteam === 1 ? 9 : 8, + firstteam: firstteam, + disabled: true, + }; + socket.on("updateBoardState", (bs, redLeft, bluLeft) => { + console.log("Received update board state"); + console.log(bs); + this.setState({cards: bs, redLeft: redLeft, bluLeft: bluLeft}); + }); + socket.on("lockup", () => { + //banner to other player name + this.setState({disabled: true}); + }); + socket.on("your turn", lastTurnData => { + this.setState({disabled: false}); + }); + } + + render() { + return ( +
+ + + + +
+ ); + } +} + +function getBrowserData() { + return {user: sessionStorage.getItem("userInfo") || "USER" + Math.random()}; +} + +export default App; diff --git a/app/src/App.test.js b/src/App.test.js similarity index 100% rename from app/src/App.test.js rename to src/App.test.js diff --git a/app/src/index.css b/src/index.css similarity index 100% rename from app/src/index.css rename to src/index.css diff --git a/app/src/index.js b/src/index.js similarity index 100% rename from app/src/index.js rename to src/index.js diff --git a/app/src/serviceWorker.js b/src/serviceWorker.js similarity index 100% rename from app/src/serviceWorker.js rename to src/serviceWorker.js