├── .gitignore ├── src ├── assets │ ├── num-boss.png │ ├── num-muncher.png │ ├── num-muncher-happy.png │ └── num-muncher-sick.png ├── reducers │ ├── index.js │ └── gameReducer.js ├── store.js ├── components │ ├── Box.jsx │ ├── UserScore.jsx │ ├── Muncher.jsx │ ├── NumGen.jsx │ ├── ScoreBoard.jsx │ ├── Rules.jsx │ ├── Game.jsx │ ├── Login.jsx │ ├── GameStats.jsx │ ├── Signup.jsx │ └── GameContainer.jsx ├── index.js ├── index.html ├── constants │ └── actionTypes.js ├── actions │ └── actions.js ├── App.js └── stylesheets │ └── style.css ├── public ├── index.html └── bundle.js.LICENSE.txt ├── server ├── models │ ├── sessionModel.js │ └── userModel.js ├── controllers │ ├── cookieController.js │ ├── sessionController.js │ └── userController.js └── server.js ├── LICENSE ├── webpack.config.js ├── README.md ├── package.json └── html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | server/data 3 | -------------------------------------------------------------------------------- /src/assets/num-boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toopham/numbermunchers/HEAD/src/assets/num-boss.png -------------------------------------------------------------------------------- /src/assets/num-muncher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toopham/numbermunchers/HEAD/src/assets/num-muncher.png -------------------------------------------------------------------------------- /src/assets/num-muncher-happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toopham/numbermunchers/HEAD/src/assets/num-muncher-happy.png -------------------------------------------------------------------------------- /src/assets/num-muncher-sick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toopham/numbermunchers/HEAD/src/assets/num-muncher-sick.png -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers } from 'redux'; 2 | 3 | import gameReducer from './gameReducer'; 4 | 5 | const reducers = combineReducers({ 6 | game: gameReducer, 7 | 8 | }); 9 | 10 | export default reducers; -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import {createStore} from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import reducers from './reducers/index'; 4 | 5 | const store = createStore( 6 | reducers, 7 | composeWithDevTools() 8 | ); 9 | 10 | export default store; -------------------------------------------------------------------------------- /src/components/Box.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | 4 | const Box = (props) => { 5 | let color = 'green'; 6 | if(props.name) color = 'white'; 7 | 8 | return
{props.name}
9 | }; 10 | 11 | 12 | export default Box; -------------------------------------------------------------------------------- /src/components/UserScore.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UserScore = (props)=>{ 4 | 5 | return (
6 |
{props.userName}
7 |
{props.score}
8 |
); 9 | }; 10 | 11 | export default UserScore; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import {Provider} from 'react-redux'; 4 | import App from './App'; 5 | import store from './store'; 6 | 7 | render( 8 | 9 | 10 | , 11 | document.getElementById('app'), 12 | ); 13 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | Num Munchers
-------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Num Munchers 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /server/models/sessionModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const sessionSchema = new Schema({ 6 | cookieID: {type:String, required: true, unique: true}, 7 | userID: {type:String, required: true,}, 8 | createdAt: {type:String, expires: 3000, default: Date.now} 9 | }); 10 | 11 | const sessionModel = mongoose.model('session', sessionSchema); 12 | 13 | module.exports = sessionModel; -------------------------------------------------------------------------------- /src/components/Muncher.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {SIZE} from '../constants/actionTypes.js'; 3 | 4 | const Muncher = (props) => { 5 | const leftPos = String(props.left*SIZE) + 'px'; 6 | const topPos = String(props.top*SIZE) + 'px'; 7 | return
; 8 | 9 | }; 10 | 11 | export default Muncher; -------------------------------------------------------------------------------- /src/components/NumGen.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {SIZE} from '../constants/actionTypes.js'; 3 | 4 | const NumGen = (props) => { 5 | const leftPos = String(props.Pos[1]*SIZE) + 'px'; 6 | const topPos = String(props.Pos[0]*SIZE) + 'px'; 7 | 8 | return
; 9 | 10 | }; 11 | 12 | 13 | export default NumGen; -------------------------------------------------------------------------------- /src/constants/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const MOVE_LEFT = 'MOVE_LEFT'; 2 | export const MOVE_RIGHT = 'MOVE_RIGHT'; 3 | export const MOVE_UP = 'MOVE_UP'; 4 | export const MOVE_DOWN = 'MOVE_DOWN'; 5 | export const EAT_NUM = 'EAT_NUM'; 6 | export const UPDATE_GAME = 'UPDATE_GAME'; 7 | export const UPDATE_USER = 'UPDATE_USER'; 8 | export const MOVE_NUM = 'MOVE_NUM'; 9 | export const SCORE_BOARD = 'SCORE_BOARD'; 10 | export const CHECK_STATE = 'CHECK_STATE'; 11 | export const SIZE = 80; -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema({ 6 | userName: {type: String, required: true, unique: true}, 7 | firstName: {type: String, required: true}, 8 | lastName: {type: String, required: true}, 9 | password: {type: String, required: true}, 10 | currentLevel: {type: Number, default: 1}, 11 | score: {type: Number, default: 0}, 12 | }); 13 | 14 | const User = mongoose.model('user', userSchema); 15 | 16 | module.exports = User; -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | const cookieController = {}; 2 | 3 | 4 | cookieController.setCookie = (req, res, next) => { 5 | res.cookie('nummucher', '1'); 6 | return next(); 7 | }; 8 | 9 | cookieController.setSSIDCookie = (req, res, next) => { 10 | console.log('INSIDE SET COOKIE SSID'); 11 | res.cookie('muncher', res.locals.session, {httpOnly: true}); 12 | next(); 13 | }; 14 | 15 | cookieController.removeCookie = (req, res, next) => { 16 | res.clearCookie('muncher'); 17 | next(); 18 | } 19 | 20 | module.exports = cookieController; -------------------------------------------------------------------------------- /src/components/ScoreBoard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UserScore from './UserScore'; 3 | 4 | const ScoreBoard = (props) => { 5 | 6 | const scores = [
7 |
USERNAME
8 |
SCORE
9 |
]; 10 | 11 | props.scoreboard.forEach(user =>{ 12 | scores.push() 13 | }); 14 | 15 | return (
16 | Score Leaders 17 | {scores} 18 |
); 19 | }; 20 | 21 | export default ScoreBoard; -------------------------------------------------------------------------------- /src/components/Rules.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Rules = (props) =>{ 4 | 5 | const rules = ['As a number Muncher, your objective is to eat all numbers multiple of the given number.', 6 | 'But don\'t eat the wrong multiple or you die!', 7 | 'The numBoss is a very juicy number that you cannot resist to eat if you ever land on the same square as he is in. Use this to your advantage!']; 8 | 9 | const goals = []; 10 | for(let i = 0; i< rules.length; i++){ 11 | goals.push(

{i+1} ) {rules[i]}

); 12 | } 13 | 14 | return (
15 | Objective 16 | {goals} 17 |
); 18 | }; 19 | 20 | export default Rules; -------------------------------------------------------------------------------- /src/components/Game.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Box from './Box'; 3 | import Muncher from'./Muncher'; 4 | import NumGen from './NumGen'; 5 | 6 | const Game = (props) =>{ 7 | 8 | const boxArray = []; 9 | 10 | for(let i = 0; i < 8; i++){ 11 | for(let j = 0; j < 8; j++){ 12 | boxArray.push(); 13 | } 14 | } 15 | 16 | const numGens = []; 17 | props.numGens.forEach(num => { 18 | if(num.active){ 19 | numGens.push(); 20 | } 21 | }); 22 | 23 | let gameStatus =[]; 24 | 25 | if(props.status === 0 ) gameStatus.push(
Game Over
); 26 | if(props.status === 2 ) gameStatus.push(
You Win!
); 27 | 28 | return (
29 | {boxArray} 30 | 31 | {numGens} 32 | {gameStatus} 33 |
); 34 | }; 35 | 36 | 37 | export default Game; -------------------------------------------------------------------------------- /src/components/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const Login = (props) =>{ 5 | let page =
Welcome, {props.user.firstName}. Login to play.
6 |
7 |
8 |
9 |
10 |
11 |
Don't have an acount? 12 | 13 |
; 14 | 15 | if(props.user.userName !== 'Guest'){ 16 | page =
Welcome, {props.user.firstName}. 17 |

username: {props.user.userName}

18 |

Level: {props.user.level}

19 |

Score: {props.user.score}

20 | 21 | 22 |
23 | } 24 | 25 | 26 | return
{page}
; 27 | } 28 | 29 | 30 | export default Login; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tu Pham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: process.env.NODE_ENV, 7 | entry: './src/index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'public'), 10 | filename: 'bundle.js', 11 | }, 12 | 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: './src/index.html', 16 | }), 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?/, 22 | exclude: /node_modules/, 23 | use: { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['@babel/preset-env', '@babel/preset-react'] 27 | } 28 | }, 29 | }, 30 | { 31 | test: /\.css$/, 32 | exclude: /node_modules/, 33 | use: ['style-loader', 'css-loader', 'sass-loader'], 34 | } 35 | ] 36 | 37 | }, 38 | devServer: { 39 | static: { 40 | publicPath: '/public', 41 | }, 42 | port: 8080, 43 | proxy: { 44 | '/api/**' : 'https://localhost:3000/', 45 | }, 46 | }, 47 | resolve: { 48 | //Enable importing js or jsx without specifying type 49 | extensions: ['.js', '.jsx'], 50 | }, 51 | }; -------------------------------------------------------------------------------- /src/actions/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/actionTypes'; 2 | 3 | export const moveLeftActionCreator = () => ({ 4 | type: types.MOVE_LEFT, 5 | payload: 'muncher' 6 | }); 7 | 8 | export const moveRightActionCreator = () => ({ 9 | type: types.MOVE_RIGHT, 10 | payload: 'muncher' 11 | }); 12 | 13 | export const moveUpActionCreator = () => ({ 14 | type: types.MOVE_UP, 15 | payload: 'muncher' 16 | }); 17 | 18 | export const moveDownActionCreator = () => ({ 19 | type: types.MOVE_DOWN, 20 | payload: 'muncher' 21 | }); 22 | 23 | export const eatNumActionCreator = () => ({ 24 | type: types.EAT_NUM, 25 | payload: 'eat' 26 | }); 27 | 28 | export const updateGameActionCreator = (level) => ({ 29 | type: types.UPDATE_GAME, 30 | payload: level 31 | }); 32 | 33 | export const updateUserActionCreator = (user) => ({ 34 | type: types.UPDATE_USER, 35 | payload: user 36 | }); 37 | 38 | export const moveNumActionCreator = (pos) => ({ 39 | type: types.MOVE_NUM, 40 | payload: pos 41 | }); 42 | 43 | export const updateScoreBoardActionCreator = (users) => ({ 44 | type: types.SCORE_BOARD, 45 | payload: users, 46 | }); 47 | 48 | export const checkStateBoardActionCreator = () => ({ 49 | type: types.CHECK_STATE, 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /src/components/GameStats.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | 4 | const GameStats = (props) =>{ 5 | 6 | const gameOn = 'Eat all numbers multiple of '+(props.level+1); 7 | const gameOver = 'GAME OVER! Try again?'; 8 | const gameWon = 'You Won! Awesome Job!'; 9 | 10 | const resetButton = ; 11 | const playAgainButton = ; 12 | const nextButton = 13 | 14 | let message = gameOver; 15 | let options = playAgainButton; 16 | 17 | if(props.status===1){ 18 | message = gameOn; 19 | options = resetButton; 20 | } 21 | else if(props.status==2){ 22 | message = gameWon; 23 | options = nextButton 24 | } 25 | 26 | return
27 |
User: {props.userName}
28 |
Score: {props.score}
29 |
Current level: {props.level}
30 |
lives:
{props.lives}
31 |
{message}
32 |
{options}
33 |
; 34 | }; 35 | 36 | 37 | export default GameStats; -------------------------------------------------------------------------------- /server/controllers/sessionController.js: -------------------------------------------------------------------------------- 1 | const Session = require('../models/sessionModel'); 2 | const bcrypt = require('bcryptjs'); 3 | const sessionController = {}; 4 | 5 | sessionController.isLoggedIn = (req, res, next) => { 6 | console.log('COOKIES MUNCHER :', req.cookies.muncher); 7 | Session.find({ cookieID: req.cookies.muncher}) 8 | .then( session => { 9 | if(session[0]){ 10 | console.log('IS LOGGED IN: ', session[0]) 11 | res.locals.userId = session[0].userID; 12 | return next(); 13 | } 14 | else{ 15 | console.log('NOT LOGGED IN'); 16 | return next(); 17 | } 18 | }) 19 | .catch(err => next('Error in sessionController isLoggedIn:'+JSON.stringify(err))); 20 | }; 21 | 22 | 23 | sessionController.startSession = (req, res, next) =>{ 24 | console.log('INSIDE START SESSION'); 25 | res.locals.session = bcrypt.hashSync(res.locals.userName, 10); 26 | 27 | Session.create({cookieID: res.locals.session, userID: res.locals.userId}) 28 | .then(session => next()) 29 | .catch(err => { 30 | console.log('ERROR IN SESSION'); 31 | next('Error in sessionController startSession'+JSON.stringify(err));}); 32 | }; 33 | 34 | sessionController.endSession = (req, res, next) =>{ 35 | Session.deleteOne({cookieID: res.locals.userId}) 36 | .then(session => next()) 37 | .catch( err => next('Error in ending session :'+JSON.stringify(err))); 38 | } 39 | 40 | module.exports = sessionController; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nummunchers 2 | Number Munchers game 3 | www.nummunchers.com 4 | 5 | ## Description 6 | A classic game that I played when I was a kid in the 90's. This is an open source project with the goal of reinventing the game with a few new rules. My hope is to get the younger generation to be excited about math and have fun at the same time. 7 | 8 | **Technology Stack** 9 | 10 | 11 | **Status** : Beta 0.0.5 12 | 13 | ## Installation 14 | 15 | To install, fork and then clone to your local machine. 16 | ``` 17 | git clone https://github.com/toopham/numbermunchers.git 18 | ``` 19 | install all dependencies within folder 20 | ``` 21 | cd numbermunchers 22 | npm install 23 | ``` 24 | 25 | **Database** : 26 | You will need a MongoDB URI. You can go to www.mongodb.com and register for an account. 27 | Create a file secret.js at the path server/data/secret.js and include in the URI you want to use. 28 | Your secret.js file should look something like this 29 | ``` 30 | const URI = 'YOUR MONGODB URI HERE'; 31 | 32 | module.exports = URI; 33 | ``` 34 | 35 | bundle react app with webpack by using the command 36 | ``` 37 | npm run build 38 | ``` 39 | 40 | 41 | Start server with game running on localhost:3000 42 | ``` 43 | npm start 44 | ``` 45 | ---- 46 | #Num Munchers 47 | 48 | 49 | 50 | ## Getting involved 51 | I welcome all feedback/contributions from other developers. If you are interested in this project you can reach out to me through Github, Linkedin, or email me at toopham at gmail dot com. 52 | 53 | 54 | ## Open source licensing info 55 | [MIT License](https://github.com/toopham/numbermunchers/blob/main/LICENSE) 56 | -------------------------------------------------------------------------------- /public/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** 8 | * React Router DOM v6.0.2 9 | * 10 | * Copyright (c) Remix Software Inc. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE.md file in the root directory of this source tree. 14 | * 15 | * @license MIT 16 | */ 17 | 18 | /** 19 | * React Router v6.0.2 20 | * 21 | * Copyright (c) Remix Software Inc. 22 | * 23 | * This source code is licensed under the MIT license found in the 24 | * LICENSE.md file in the root directory of this source tree. 25 | * 26 | * @license MIT 27 | */ 28 | 29 | /** @license React v0.20.2 30 | * scheduler.production.min.js 31 | * 32 | * Copyright (c) Facebook, Inc. and its affiliates. 33 | * 34 | * This source code is licensed under the MIT license found in the 35 | * LICENSE file in the root directory of this source tree. 36 | */ 37 | 38 | /** @license React v16.13.1 39 | * react-is.production.min.js 40 | * 41 | * Copyright (c) Facebook, Inc. and its affiliates. 42 | * 43 | * This source code is licensed under the MIT license found in the 44 | * LICENSE file in the root directory of this source tree. 45 | */ 46 | 47 | /** @license React v17.0.2 48 | * react-dom.production.min.js 49 | * 50 | * Copyright (c) Facebook, Inc. and its affiliates. 51 | * 52 | * This source code is licensed under the MIT license found in the 53 | * LICENSE file in the root directory of this source tree. 54 | */ 55 | 56 | /** @license React v17.0.2 57 | * react.production.min.js 58 | * 59 | * Copyright (c) Facebook, Inc. and its affiliates. 60 | * 61 | * This source code is licensed under the MIT license found in the 62 | * LICENSE file in the root directory of this source tree. 63 | */ 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "numbermunchers", 3 | "version": "0.0.5", 4 | "description": "Number Munchers Game", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node server/server.js", 8 | "build": "cross-env NODE_ENV=production webpack", 9 | "dev": "cross-env NODE_ENV=development concurrently \"nodemon server/server.js\" \"webpack serve --open --hot\"" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/toopham/numbermunchers.git" 14 | }, 15 | "keywords": [ 16 | "Number", 17 | "Math", 18 | "Game" 19 | ], 20 | "author": "Tu Pham", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/toopham/numbermunchers/issues" 24 | }, 25 | "homepage": "https://github.com/toopham/numbermunchers#readme", 26 | "dependencies": { 27 | "axios": "^0.24.0", 28 | "bcryptjs": "^2.4.3", 29 | "body-parser": "^1.19.0", 30 | "cookie-parser": "^1.4.5", 31 | "express": "^4.17.1", 32 | "js-cookie": "^3.0.1", 33 | "mongoose": "^6.0.10", 34 | "react": "^17.0.2", 35 | "react-dom": "^17.0.2", 36 | "react-redux": "^7.2.5", 37 | "react-router": "^6.0.2", 38 | "react-router-dom": "^6.0.2", 39 | "redux": "^4.1.1", 40 | "universal-cookie": "^4.0.4" 41 | }, 42 | "devDependencies": { 43 | "@babel/core": "^7.15.8", 44 | "@babel/preset-env": "^7.15.8", 45 | "@babel/preset-react": "^7.14.5", 46 | "babel-loader": "^8.2.2", 47 | "concurrently": "^6.3.0", 48 | "cross-env": "^7.0.3", 49 | "css-loader": "^6.4.0", 50 | "html-webpack-plugin": "^5.3.2", 51 | "nodemon": "^2.0.13", 52 | "redux-devtools-extension": "^2.13.9", 53 | "sass": "^1.42.1", 54 | "sass-loader": "^12.2.0", 55 | "style-loader": "^3.3.0", 56 | "webpack": "^5.58.1", 57 | "webpack-cli": "^4.9.0", 58 | "webpack-dev-server": "^4.3.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/userModel'); 2 | const bcrypt = require('bcryptjs'); 3 | const { isValidObjectId } = require('mongoose'); 4 | const userController ={}; 5 | 6 | userController.getUsers = (req, res, next) =>{ 7 | 8 | User.find({}).sort({score: -1}).limit(10) 9 | .then(users => { 10 | res.locals.users = users; 11 | return next(); 12 | }) 13 | .catch(err => next('Error in get users: '+JSON.stringify(err))); 14 | 15 | }; 16 | 17 | userController.createUser = (req, res, next) => { 18 | const encrypted = bcrypt.hashSync(req.body.password, 10); 19 | const newUser = {... req.body, password: encrypted}; 20 | 21 | User.create(newUser) 22 | .then((user) => { 23 | res.locals.userId = user._id.toString(); 24 | res.locals.userName = user.userName; 25 | return next(); 26 | }) 27 | .catch((err) => { 28 | return next(err); 29 | }); 30 | }; 31 | 32 | userController.getUser = (req, res, next) => { 33 | if(res.locals.userId){ 34 | User.findById(res.locals.userId) 35 | .then((user) => { 36 | user._id = user._id.toString(); 37 | res.locals.user = user; 38 | return next(); 39 | }) 40 | .catch((err) => next('ERROR in get user : '+JSON.stringify(err))); 41 | } 42 | else{ 43 | res.locals.user ={ 44 | firstName: 'Guest', 45 | lastName: 'Player', 46 | userName: 'Guest', 47 | currentLevel: 1, 48 | lives: 3, 49 | score: 0, 50 | status: 1, 51 | userID: '', 52 | }; 53 | return next(); 54 | } 55 | }; 56 | 57 | userController.updateUser = (req, res, next) => { 58 | User.findByIdAndUpdate(res.locals.userId, {currentLevel: req.body.level, score: req.body.score}, (err, res)=>{ 59 | if(err){ 60 | next(err); 61 | } 62 | else{ 63 | next(); 64 | } 65 | }); 66 | }; 67 | 68 | userController.verifyUser = (req, res, next) => { 69 | User.find({userName: req.body.userName}) 70 | .then((user) =>{ 71 | if(user[0]){ 72 | if(bcrypt.compareSync(req.body.password, user[0].password)){ 73 | res.locals.userId = user[0]._id.toString(); 74 | res.locals.userName = user[0].userName; 75 | return next(); 76 | } 77 | else{ 78 | return next('Incorrect password') 79 | } 80 | } 81 | else{ 82 | return next('Incorrect username'); 83 | } 84 | }) 85 | .catch((err) => next(err)); 86 | }; 87 | 88 | module.exports = userController; 89 | 90 | -------------------------------------------------------------------------------- /src/components/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | 5 | const Signup = (props) => { 6 | 7 | const initialState = { 8 | firstName: '', 9 | lastName: '', 10 | userName: '', 11 | password: '', 12 | }; 13 | 14 | const [status, setStatus] = useState('signup'); 15 | const [state, setState] = useState(initialState); 16 | const [color, setColor] = useState('black-border'); 17 | const [message, setMessage] = useState(''); 18 | 19 | const createAccount = (e)=> { 20 | axios.post('/api/signup', state) 21 | .then(res => res.config.data) 22 | .then((user)=> { 23 | props.updateUser(user); 24 | setStatus('success'); 25 | }) 26 | .catch((err) => console.log('Error creating user: ', err)); 27 | }; 28 | 29 | const updateChange = (e) => { 30 | const newState = {...state}; 31 | newState[e.target.name] = e.target.value; 32 | setState(newState); 33 | }; 34 | 35 | const checkPassword = (e) =>{ 36 | if(e.target.value != state.password){ 37 | setColor('red-border'); 38 | setMessage('Password does not match.'); 39 | } 40 | else{ 41 | setColor('black-border'); 42 | setMessage(''); 43 | } 44 | }; 45 | 46 | let body = ''; 47 | 48 | if(status === 'signup'){ 49 | body =
50 |
51 |
52 |
53 |
54 |
55 |
{message}
56 |
57 |
; 58 | } 59 | if(status === 'success'){ 60 | body =
You've successfully signed up. Now log in to play! 61 |
; 62 | } 63 | 64 | return
65 | SIGN UP TO PLAY
66 | {body} 67 |
; 68 | 69 | } 70 | 71 | 72 | export default Signup; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const mongoose = require('mongoose'); 3 | const app = express(); 4 | const path = require('path'); 5 | const bodyParser = require('body-parser'); 6 | const cookieParser = require('cookie-parser'); 7 | const userController = require('./controllers/userController'); 8 | const sessionController = require('./controllers/sessionController'); 9 | const cookieController = require('./controllers/cookieController'); 10 | 11 | const MONGO_URI = require('./data/secret.js') 12 | 13 | mongoose.connect(MONGO_URI, { 14 | useNewUrlParser: true, 15 | useUnifiedTopology: true, 16 | dbName: 'numdb' 17 | }) 18 | .then(() => console.log("Connected to Mongo DB")) 19 | .catch((err) => console.log('ERROR CANNOT CONNECT TO MONGO DB :', err)); 20 | 21 | 22 | app.use(express.json()); 23 | app.use(bodyParser.urlencoded({extended: true})); 24 | app.use(cookieParser()); 25 | 26 | app.use('/', express.static(path.join(__dirname, '../public/'))); 27 | 28 | app.use('/assets', express.static(path.join(__dirname, '../src/assets/'))); 29 | 30 | 31 | 32 | // serve index.html on the route '/' 33 | app.post('/api/signup', userController.createUser, sessionController.startSession, cookieController.setSSIDCookie, (req, res) => { 34 | return res.status(200).json(res.locals.user); 35 | }); 36 | 37 | app.post('/api/login', userController.verifyUser, sessionController.startSession, 38 | cookieController.setSSIDCookie, (req, res) => { 39 | return res.redirect(303, '/'); 40 | }); 41 | 42 | //serve user info if user is logged in 43 | app.get('/api', sessionController.isLoggedIn, userController.getUser, (req, res) => res.status(200).json(res.locals.user)); 44 | app.post('/api', sessionController.isLoggedIn, userController.updateUser, (req, res) => res.status(200).json({})); 45 | 46 | app.get('/api/scores', userController.getUsers, (req, res) => res.status(200).json(res.locals.users)); 47 | 48 | 49 | app.get('/api/logout', sessionController.isLoggedIn, sessionController.endSession, cookieController.removeCookie, (req,res)=> res.redirect(303,'/')); 50 | 51 | 52 | // app.get('/', sessionController.isLoggedIn, (req, res) => { 53 | // return res.status(200).sendFile(path.join(__dirname, '../public/index.html')); 54 | // }); 55 | 56 | 57 | app.use((req,res) => res.status(404).sendFile(path.join(__dirname, '../html/index.html'))); 58 | 59 | app.use( (err, req, res, next) => { 60 | return res.redirect(500,'/'); 61 | }); 62 | 63 | 64 | app.listen(3000); //listens on port 3000 -> http://localhost:3000/ 65 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import GameContainer from "./components/GameContainer"; 3 | import Signup from "./components/Signup"; 4 | import Login from "./components/Login"; 5 | import axios from "axios"; 6 | import "./stylesheets/style.css"; 7 | import { 8 | BrowserRouter as Router, 9 | Routes, 10 | Route, 11 | Link, 12 | } from "react-router-dom"; 13 | import { connect } from 'react-redux'; 14 | import * as actions from './actions/actions'; 15 | 16 | //mapState 17 | const mapStateToProps = (state) => ({ 18 | firstName: state.game.firstName, 19 | lastName: state.game.lastName, 20 | userName: state.game.userName, 21 | level: state.game.level, 22 | score: state.game.score, 23 | }); 24 | 25 | //mapDispatch 26 | const mapDispatchToProps = (dispatch) => ({ 27 | updateUser: (user) => { 28 | dispatch(actions.updateUserActionCreator(user)); 29 | }, 30 | updateScoreBoard: (users) => { 31 | dispatch(actions.updateScoreBoardActionCreator(users)); 32 | } 33 | }); 34 | 35 | 36 | class App extends Component { 37 | 38 | constructor(props){ 39 | super(props); 40 | 41 | } 42 | 43 | componentDidMount(){ 44 | axios.get('/api').then(res => res.data) 45 | .then(res => { 46 | this.props.updateUser(res); 47 | }) 48 | .catch(err => console.log('ERROR ACCESSING GET API ', err)); 49 | 50 | axios.get('/api/scores') 51 | .then(res => res.data) 52 | .then(data => this.props.updateScoreBoard(data)) 53 | .catch(err => console.log('ERROR GETTING SCORES: ', err)); 54 | } 55 | 56 | 57 | 58 | 59 | 60 | 61 | render(){ 62 | axios.get('/api').then(res => res.data) 63 | .then(res => { 64 | this.props.updateUser(res); 65 | }) 66 | .catch(err => console.log('ERROR ACCESSING GET API ', err)); 67 | 68 | let links = (
    69 |
  • Play As Guest
  • 70 |
  • Login
  • 71 |
  • Signup
  • 72 |
); 73 | 74 | if(this.props.firstName!='Guest'){ 75 | links = (); 79 | } 80 | 81 | return ( 82 | 83 |
84 | 88 | 89 | {/* A looks through its children s and 90 | renders the first one that matches the current URL. */} 91 | 92 | } /> 93 | } /> 94 | } /> 95 | 96 |
97 |
98 | ); 99 | } 100 | } 101 | 102 | 103 | 104 | export default connect(mapStateToProps, mapDispatchToProps)(App); -------------------------------------------------------------------------------- /src/components/GameContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Game from './Game'; 3 | import GameStats from './GameStats'; 4 | import ScoreBoard from './ScoreBoard'; 5 | import Rules from './Rules'; 6 | import { connect } from 'react-redux'; 7 | import * as actions from '../actions/actions'; 8 | 9 | 10 | //mapState 11 | const mapStateToProps = (state) => ({ 12 | gridState: state.game.gridState, 13 | firstName: state.game.firstName, 14 | lastName: state.game.lastName, 15 | userName: state.game.userName, 16 | level: state.game.level, 17 | lives: state.game.lives, 18 | score: state.game.score, 19 | status: state.game.status, 20 | muncherPos: state.game.muncherPos, 21 | muncherImg: state.game.muncherImg, 22 | numGens: state.game.numGens, 23 | scoreboard: state.game.scoreboard, 24 | }); 25 | 26 | 27 | //mapDispatch 28 | const mapDispatchToProps = (dispatch) => ({ 29 | moveLeft: (e) => { 30 | dispatch(actions.moveLeftActionCreator()); 31 | }, 32 | moveRight: (e) => { 33 | dispatch(actions.moveRightActionCreator()); 34 | }, 35 | moveUp: (e) => { 36 | dispatch(actions.moveUpActionCreator()); 37 | }, 38 | moveDown: (e) => { 39 | dispatch(actions.moveDownActionCreator()); 40 | }, 41 | eatNum: (e) => { 42 | dispatch(actions.eatNumActionCreator()); 43 | }, 44 | updateGame: (level) => { 45 | dispatch(actions.updateGameActionCreator(level)); 46 | }, 47 | resetGame: () => { 48 | dispatch(actions.updateGameActionCreator()); 49 | }, 50 | moveNum: () => { 51 | dispatch(actions.moveNumActionCreator()); 52 | }, 53 | checkState: () => { 54 | dispatch(actions.checkStateBoardActionCreator()); 55 | } 56 | }); 57 | 58 | 59 | class GameContainer extends Component{ 60 | constructor(props){ 61 | super(props); 62 | this.gameRef = React.createRef(); 63 | 64 | //Is numGen moving? 65 | this.genMoving = false; 66 | } 67 | 68 | genMover(){ 69 | this.props.moveNum(); 70 | this.gameRef.current.focus(); 71 | //Once this function is called we set it to true. 72 | this.genMoving = true; 73 | 74 | const speed = 6000 - 5000*(1/(1+Math.exp(0-this.props.level))); 75 | 76 | if(this.props.status===1) setTimeout(this.genMover.bind(this), speed); 77 | else{ 78 | this.genMoving = false; 79 | } 80 | } 81 | 82 | componentDidMount(){ 83 | this.props.resetGame(); 84 | this.gameRef.current.focus(); 85 | 86 | //If numGEn is not moving then make it move. 87 | if(this.genMoving === false) this.genMover(); 88 | 89 | } 90 | 91 | componentDidUpdate(){ 92 | this.gameRef.current.focus(); 93 | 94 | //if numGen is not moving then make it move. 95 | if(this.genMoving === false) this.genMover(); 96 | } 97 | 98 | handleKey = e => { 99 | 100 | 101 | //If LEFT_ARROW IS PRESS 102 | if(e.keyCode === 37) this.props.moveLeft(); 103 | //If UP_ARROW IS PRESS 104 | if(e.keyCode === 38) this.props.moveUp(); 105 | //If RIGHT_ARROW IS PRESS 106 | if(e.keyCode === 39) this.props.moveRight(); 107 | //If DOWN_ARROW IS PRESS 108 | if(e.keyCode === 40) this.props.moveDown(); 109 | 110 | //IF SPACE_BAR IS PRESS 111 | if(e.keyCode === 32) this.props.eatNum(); 112 | 113 | const i = this.props.muncherPos[0]; 114 | const j = this.props.muncherPos[1]; 115 | } 116 | 117 | render(){ 118 | return (
119 | 120 | 121 |
); 122 | } 123 | } 124 | 125 | export default connect(mapStateToProps, mapDispatchToProps)(GameContainer); 126 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | Cannot Access Page 12 | 178 | 179 | 180 |
181 | 187 | 205 |
206 | 207 |
208 |
404 Not Found
209 |
210 |
211 |
212 |
213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/reducers/gameReducer.js: -------------------------------------------------------------------------------- 1 | import { updateUserActionCreator } from '../actions/actions'; 2 | import * as types from '../constants/actionTypes'; 3 | 4 | 5 | const initialState = { 6 | gridState: [['-','-','-','-','-','-','-','-'], 7 | ['-','-','-','-','-','-','-','-'], 8 | ['-','-','-','-','-','-','-','-'], 9 | ['-','-','-','-','-','-','-','-'], 10 | ['-','-','-','-','-','-','-','-'], 11 | ['-','-','-','-','-','-','-','-'], 12 | ['-','-','-','-','-','-','-','-'], 13 | ['-','-','-','-','-','-','-','-']], 14 | firstName: 'Guest', 15 | lastName: 'Player', 16 | userName: 'Guest', 17 | level: 1, 18 | lives: 3, 19 | score: 0, 20 | status: 1, 21 | userID: '', 22 | muncherPos: [0,0], 23 | muncherImg: 'url("assets/num-muncher.png")', 24 | numGens: [{color: 'red', Pos: [7,7], active: true, value: ''}, 25 | {color: 'blue', Pos: [7,7], active: false, value: ''}, 26 | {color: 'orange', Pos: [7,7], active: false}], 27 | scoreboard: [{}], 28 | } 29 | 30 | 31 | const saveUser =(level, score) =>{ 32 | fetch('/api', { 33 | method: 'POST', 34 | headers: { 'Content-Type': 'application/json',}, 35 | body: JSON.stringify({level: level, score: score}) 36 | }) 37 | .then(res => res.json()) 38 | .then(data => console.log('UPDATE user DB successfully :', data)) 39 | .catch(err => console.log('Error updating user: ',err)); 40 | } 41 | 42 | const gameReducer = (state = initialState, action) => { 43 | let newPos = [...state.muncherPos]; 44 | let status = state.status; 45 | let mult = state.level+1; 46 | let level = state.level; 47 | let lives = state.lives; 48 | let score = state.score; 49 | 50 | const newGrid = state.gridState.map(el => [...el]); 51 | const newGens = state.numGens.map(obj => Object.assign({},obj)); 52 | 53 | const updateGens = () =>{ 54 | let muncherStatus = muncherSRC[0]; 55 | newGens.forEach(num=>{ 56 | if(newPos[0]===num.Pos[0] && newPos[1]===num.Pos[1] && num.active === true){ 57 | if(newGrid[newPos[0]][newPos[1]]%mult===0 && newGrid[newPos[0]][newPos[1]]!=''){ 58 | num.active = false; 59 | muncherStatus = muncherSRC[1]; //Ate the numBoss at a multiple of mult 60 | num.value = newGrid[newPos[0]][newPos[1]]; 61 | } 62 | else{ 63 | lives-=1; 64 | muncherStatus = muncherSRC[2]; //Ate numBoss at the wrong multiple 65 | num.active = false; 66 | } 67 | newGrid[newPos[0]][newPos[1]] = ''; 68 | } 69 | }); 70 | 71 | return muncherStatus; 72 | }; 73 | 74 | 75 | const muncherSRC = ['url("assets/num-muncher.png")', 'url("assets/num-muncher-happy.png")', 'url("assets/num-muncher-sick.png")']; 76 | switch (action.type){ 77 | case types.MOVE_LEFT: 78 | if(status!=1) return {...state}; 79 | if(state.muncherPos[1]>0){ 80 | newPos[1]-=1; 81 | } 82 | 83 | muncherStatus = updateGens(); 84 | 85 | if(lives===0) status=0; 86 | 87 | return { 88 | ...state, 89 | lives: lives, 90 | status: status, 91 | gridState: newGrid, 92 | muncherPos: newPos, 93 | numGens: newGens, 94 | muncherImg: muncherStatus, 95 | }; 96 | case types.MOVE_RIGHT: 97 | if(status!=1) return {...state}; 98 | if(state.muncherPos[1]<7){ 99 | newPos[1]+=1; 100 | } 101 | 102 | muncherStatus = updateGens(); 103 | 104 | if(lives===0) status=0; 105 | 106 | return { 107 | ...state, 108 | lives: lives, 109 | status: status, 110 | gridState: newGrid, 111 | muncherPos: newPos, 112 | numGens: newGens, 113 | muncherImg: muncherStatus, 114 | }; 115 | case types.MOVE_UP: 116 | if(status!=1) return {...state}; 117 | if(state.muncherPos[0]>0){ 118 | newPos[0]-=1; 119 | } 120 | 121 | muncherStatus = updateGens(); 122 | 123 | if(lives===0) status=0; 124 | 125 | return { 126 | ...state, 127 | lives: lives, 128 | status: status, 129 | gridState: newGrid, 130 | muncherPos: newPos, 131 | numGens: newGens, 132 | muncherImg: muncherStatus, 133 | }; 134 | case types.MOVE_DOWN: 135 | if(status!=1) return {...state}; 136 | if(state.muncherPos[0]<7){ 137 | newPos[0]+=1; 138 | } 139 | 140 | muncherStatus = updateGens(); 141 | 142 | if(lives===0) status=0; 143 | 144 | return { 145 | ...state, 146 | lives: lives, 147 | status: status, 148 | gridState: newGrid, 149 | muncherPos: newPos, 150 | numGens: newGens, 151 | muncherImg: muncherStatus, 152 | }; 153 | case types.EAT_NUM: 154 | if(status!=1) return {...state}; 155 | const top = state.muncherPos[0]; 156 | const left = state.muncherPos[1]; 157 | 158 | const num = newGrid[top][left]; 159 | newGrid[top][left]=''; 160 | 161 | //check if num ate is a correct multiple 162 | if(num%mult===0){ 163 | 164 | //if you eat a 0 then increase lives by 1 165 | if(num===0) lives+=1; 166 | 167 | //assume game is over 168 | status = 2; 169 | 170 | //check if game is really over 171 | for(let i = 0; i < 8; i++){ 172 | for(let j = 0; j< 8; j++){ 173 | //if we find a number that is still a multiple then set game = 1 174 | if(typeof newGrid[i][j]==='number' && newGrid[i][j]%mult===0){ 175 | status = 1; 176 | } 177 | } 178 | } 179 | 180 | return { 181 | ...state, 182 | gridState: newGrid, 183 | lives: lives, 184 | status: status, 185 | muncherImg: muncherSRC[1], 186 | } 187 | } 188 | else{ 189 | lives = state.lives-1; 190 | if(lives===0) status=0; 191 | return { 192 | ...state, 193 | lives: lives, 194 | gridState: newGrid, 195 | status: status, 196 | muncherImg: muncherSRC[2], 197 | } 198 | } 199 | case types.UPDATE_GAME: //Reset game or move game to next level 200 | if(action.payload){ 201 | mult = action.payload+1; 202 | level = action.payload; 203 | //scoring algorithm 204 | score = score + lives*10*mult; 205 | 206 | saveUser(level,score); 207 | } 208 | 209 | lives = 3; 210 | if(level > 5) lives = 4; 211 | if(level > 10) lives = 5; 212 | 213 | for(let i = 0; i < 8; i++){ 214 | for(let j = 0; j< 8; j++){ 215 | // console.log('AT row i = ', i); 216 | // console.log('AT col j = ', j); 217 | // console.log('INJECTING NUM ', newGrid[i][j]); 218 | newGrid[i][j] = Math.floor(Math.random()*20)*(mult+Math.floor(Math.random()*3)); 219 | if(newGrid[i][j]%mult === 0) newGrid[i][j]+=1; 220 | } 221 | } 222 | 223 | //Inject in at most 20 numbers that are correct multiples 224 | for(let k = 0; k < 20;k++){ 225 | let i = Math.floor(Math.random()*8); 226 | let j = Math.floor(Math.random()*8); 227 | newGrid[i][j] = Math.floor(Math.random()*40)*(mult); 228 | // console.log("INJECTING NUMBER ", newGrid[i][j]); 229 | } 230 | 231 | //reset numBoss position 232 | newGens.forEach(num=> { 233 | num.Pos = [7,7]; 234 | if(num.color === 'red') num.active = true; 235 | if(num.color === 'blue' && level > 4) num.active = true; 236 | if(num.color === 'orange' && level > 8) num.active = true; 237 | }); 238 | 239 | return { 240 | ...state, 241 | gridState: newGrid, 242 | level: level, 243 | lives: lives, 244 | score: score, 245 | muncherPos: [0,0], 246 | status: 1, 247 | numGens: newGens, 248 | muncherImg: muncherSRC[0], 249 | } 250 | case types.UPDATE_USER: 251 | 252 | 253 | return { 254 | ...state, 255 | firstName: action.payload.firstName, 256 | lastName: action.payload.lastName, 257 | userName: action.payload.userName, 258 | userID: action.payload._id, 259 | level: action.payload.currentLevel, 260 | score: action.payload.score, 261 | } 262 | case types.MOVE_NUM: 263 | let muncherStatus =state.muncherImg; 264 | let nonActive = true; //Set to be 1 265 | newGens.forEach(num=> { 266 | if(num.active){ 267 | nonActive = false; // If there is an active numBoss then turn off trigger; 268 | //create vector to Muncher and move each numBoss towards Muncher 269 | const x = state.muncherPos[1] - num.Pos[1]; 270 | const y = state.muncherPos[0] - num.Pos[0]; 271 | 272 | let numPosx = num.Pos[1]; 273 | let numPosy = num.Pos[0]; 274 | 275 | if(Math.abs(y) > Math.abs(x)){ 276 | //move UP 277 | if(y<0) numPosy-=1; 278 | //move DOWN 279 | if(y>0) numPosy+=1; 280 | 281 | 282 | } 283 | else{ 284 | //move LEFT 285 | if(x<0) numPosx-=1; 286 | //move RIGHT 287 | if(x>0) numPosx+=1; 288 | } 289 | 290 | const top = state.muncherPos[0]; 291 | const left = state.muncherPos[1]; 292 | 293 | if(top === numPosy && left === numPosx){ 294 | if(newGrid[top][left]%mult===0 && newGrid[newPos[0]][newPos[1]]!=''){ 295 | //If the square the Muncher is in is a multiple of mult then don't move in 296 | //The numBoss is smart to not move into the same square as muncher. 297 | return {...state}; 298 | } 299 | else{ 300 | newGrid[top][left]=''; 301 | lives -=1; 302 | if(lives===0) status=0; 303 | muncherStatus = muncherSRC[2]; 304 | num.active = false; 305 | } 306 | } 307 | 308 | num.Pos[1]=numPosx; 309 | num.Pos[0]=numPosy; 310 | } 311 | }); 312 | 313 | if(nonActive){ 314 | newGens[1].active = true; 315 | newGens[1].Pos = [7,7]; 316 | } 317 | 318 | return { 319 | ...state, 320 | lives: lives, 321 | status: status, 322 | numGens: newGens, 323 | gridState: newGrid, 324 | muncherImg: muncherStatus, 325 | }; 326 | case types.SCORE_BOARD: 327 | const users = action.payload; 328 | const scoreboard = []; 329 | users.sort((a,b) => b.score - a.score); 330 | 331 | users.forEach(user => { 332 | scoreboard.push({...user}); 333 | }); 334 | 335 | return { 336 | ...state, 337 | scoreboard: scoreboard, 338 | } 339 | case types.CHECK_STATE: 340 | newGens.forEach(num=>{ 341 | if(newPos[0]===num.Pos[0] && newPos[1]===num.Pos[1]){ 342 | if(newGrid[newPos[0]][newPos[1]]%mult===0){ 343 | num.active = false; 344 | } 345 | else{ 346 | lives-=1; 347 | } 348 | newGrid[newPos[0]][newPos[1]] = ''; 349 | } 350 | }); 351 | 352 | if(lives <= 0) status=0; 353 | 354 | return { 355 | ...state, 356 | lives: lives, 357 | status: status, 358 | gridState: newGrid, 359 | } 360 | default: { 361 | return state; 362 | } 363 | 364 | } 365 | 366 | } 367 | 368 | export default gameReducer; -------------------------------------------------------------------------------- /src/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Cabin&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=Love+Ya+Like+A+Sister&display=swap'); 3 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap'); 4 | @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); 5 | 6 | body { 7 | background: rgb(255, 255, 255); 8 | padding: 0px; 9 | } 10 | 11 | h1 { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | } 16 | 17 | h2 { 18 | padding: 0px; 19 | margin: 5px; 20 | } 21 | 22 | h3 { 23 | font-family: 'Press Start 2P', cursive; 24 | font-size: 1.8em; 25 | color:rgb(0, 255, 179); 26 | } 27 | 28 | h4 { 29 | font-family: 'Press Start 2P', cursive; 30 | font-size: 1.5em; 31 | color:rgb(0, 255, 179); 32 | } 33 | 34 | h5 { 35 | font-family: 'Press Start 2P', cursive; 36 | font-size: 1.2em; 37 | color:rgb(0, 255, 179); 38 | } 39 | 40 | button { 41 | display:inline-block; 42 | padding: 0.5em 1.2em; 43 | border-radius: 0.5em; 44 | box-sizing: border-box; 45 | text-decoration: none; 46 | font-family: 'Love Ya Like A Sister', cursive; 47 | font-weight: bold; 48 | color:black; 49 | background-color:#adadad; 50 | border: 1px solid gainsboro; 51 | text-align: center; 52 | position: relative; 53 | margin: 1em 2em; 54 | padding: 0.1em 0; 55 | border-radius: 0.5em; 56 | box-shadow: 0 0.2em 0.3em 0.1em rgba(0,0,0,0.2); 57 | } 58 | 59 | button:hover{ 60 | background-color:rgb(0, 255, 179); 61 | color: black; 62 | } 63 | 64 | 65 | 66 | nav { 67 | width: 100%; 68 | background:black; 69 | color: white; 70 | display: flex; 71 | flex-direction: row; 72 | justify-content: space-between; 73 | font-family: 'Roboto', sans-serif; 74 | font-weight: bold; 75 | top: 0; 76 | left: 0; 77 | margin: 0; 78 | } 79 | 80 | nav ul{ 81 | list-style-type: none; 82 | display: flex; 83 | flex-direction: row; 84 | justify-content: flex-end; 85 | align-items: flex-end; 86 | } 87 | 88 | .title { 89 | display: flex; 90 | flex-direction: row; 91 | justify-content: flex-start; 92 | font-family: 'Press Start 2P', cursive; 93 | color:rgb(0, 255, 179); 94 | } 95 | 96 | nav ul li { 97 | margin: 0px 5px 0px 5px; 98 | width: 100px; 99 | height: 20px; 100 | background: black; 101 | color: white; 102 | display: flex; 103 | justify-content: space-around; 104 | text-decoration: none; 105 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 106 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 107 | box-sizing: border-box; 108 | } 109 | 110 | nav ul li:hover{ 111 | border: 1px solid white; 112 | font-weight: bold; 113 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 114 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 115 | box-sizing: border-box; 116 | } 117 | 118 | 119 | nav ul li a:active{ 120 | color: gray; 121 | text-decoration: none; 122 | } 123 | 124 | 125 | .container { 126 | width: 100%; 127 | display: flex; 128 | flex-direction: column; 129 | align-items: center; 130 | 131 | } 132 | 133 | .play { 134 | width: 100%; 135 | display: flex; 136 | flex-direction: row; 137 | justify-content: space-around; 138 | align-items: flex-start; 139 | } 140 | 141 | #scoreboard{ 142 | width: 400px; 143 | height: 600px; 144 | margin: 20px; 145 | padding: 10px; 146 | display: flex; 147 | flex-direction: column; 148 | align-items: center; 149 | justify-content: flex-start; 150 | background-color: #2a9d8f; 151 | font-family: 'Roboto', sans-serif; 152 | } 153 | 154 | .userscore{ 155 | width: 300px; 156 | height: 30px; 157 | margin: 0px; 158 | padding: 2px; 159 | display: flex; 160 | background-color: #2a9d8f; 161 | flex-direction: row; 162 | } 163 | 164 | .score-header { 165 | background-color: #2a9d8f; 166 | width: 150px; 167 | color: #e76f51; 168 | font-size: 1.2em; 169 | font-family: 'Roboto', sans-serif; 170 | font-weight: bold; 171 | text-align: left; 172 | padding: 2px 10px 2px 10px; 173 | margin: 0px; 174 | } 175 | 176 | .score-col { 177 | width: 150px; 178 | color: #e9c46a; 179 | font-size: 1.2em; 180 | font-family: 'Roboto', sans-serif; 181 | font-weight: bold; 182 | text-align: left; 183 | padding: 2px 10px 2px 10px; 184 | margin: 0px; 185 | } 186 | 187 | 188 | #rules { 189 | width: 400px; 190 | height: 600px; 191 | margin: 20px; 192 | padding: 10px; 193 | display: flex; 194 | flex-direction: column; 195 | align-items: center; 196 | justify-content: flex-start; 197 | background-color: #2a9d8f; 198 | color: white; 199 | font-size: 1.5em; 200 | font-family: 'Roboto', sans-serif; 201 | } 202 | 203 | .gamecontainer { 204 | width: 640px; 205 | height: 640px; 206 | display: flex; 207 | margin: 10px; 208 | flex-direction: column; 209 | align-items: center; 210 | font-family: 'Cabin', sans-serif; 211 | border-radius: 0.5em; 212 | box-shadow: 0 0.1em 0.2em 0.1em rgba(0,0,0,0.2); 213 | } 214 | 215 | .gamestats{ 216 | width: 640px; 217 | height: 150px; 218 | background-color: black; 219 | border: 1px solid rgba(255, 255, 255, 0.726); 220 | border-radius: 5px 5px 0px 0px; 221 | font-family: 'Love Ya Like A Sister', cursive; 222 | color: white; 223 | font-size: 1em; 224 | display: grid; 225 | grid-template-columns: 1fr 1fr; 226 | padding: 10px 10px 10px 10px; 227 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 228 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 229 | box-sizing: border-box; /* Opera/IE 8+ */ 230 | } 231 | .gamegrid { 232 | display: grid; 233 | grid-template-columns: repeat(8, 1fr); 234 | width: 640px; 235 | height: 640px; 236 | position: relative; 237 | background-color: rgb(34, 34, 34); 238 | border: 1px white solid; 239 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 240 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 241 | box-sizing: border-box; 242 | } 243 | 244 | .gamebox { 245 | width: 80px; 246 | height: 80px; 247 | font-size: 2em; 248 | font-weight: bold; 249 | color: white; 250 | display: flex; 251 | flex-direction: column-reverse; 252 | justify-content: space-around; 253 | align-items: center; 254 | } 255 | 256 | .gameinfo { 257 | grid-column-start: 1; 258 | grid-column-end: 3; 259 | display: flex; 260 | flex-direction: column; 261 | align-items: center; 262 | font-size: 1.5em; 263 | color:rgb(0, 255, 179); 264 | } 265 | 266 | .statinfo { 267 | width: 100%; 268 | display: flex; 269 | flex-direction: row; 270 | justify-content: flex-start; 271 | } 272 | 273 | .muncher { 274 | /*border: 1px solid rgb(81, 255, 168);*/ 275 | width: 80px; 276 | height: 80px; 277 | position: absolute; 278 | color: rgb(0, 255, 179); 279 | font-size: 1em; 280 | left: 0px; 281 | top: 0px; 282 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 283 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 284 | box-sizing: border-box; 285 | } 286 | 287 | .numGen { 288 | /*border: 1px solid #e63946;*/ 289 | width: 80px; 290 | height: 80px; 291 | position: absolute; 292 | color: rgb(0, 255, 179); 293 | font-size: 1em; 294 | left: 560px; 295 | top: 560px; 296 | display: flex; 297 | flex-direction: column-reverse; 298 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 299 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 300 | box-sizing: border-box; 301 | } 302 | 303 | .liveBar { 304 | background-color: #e63946; 305 | height: 15px; 306 | display: flex; 307 | flex-direction: column; 308 | align-items: center; 309 | } 310 | 311 | #signup { 312 | width: 800px; 313 | height: 500px; 314 | display: flex; 315 | flex-direction: column; 316 | align-items: center; 317 | margin: 10px; 318 | color: #e63946; 319 | background-color: #fff; 320 | border: none; 321 | margin: 1em 2em; 322 | padding: 0.1em 0; 323 | border-radius: 0.5em; 324 | box-shadow: 0 0.2em 0.3em 0.1em rgba(0,0,0,0.2); 325 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 326 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 327 | box-sizing: border-box; 328 | } 329 | 330 | input { 331 | border: 1px rgb(78, 78, 78) solid; 332 | border-radius: 5px; 333 | width: 250px; 334 | height: 35px; 335 | font-size: 1.5em; 336 | font-family: 'Roboto', sans-serif; 337 | color: #4CAF50; 338 | margin: 5px; 339 | } 340 | 341 | input:hover { 342 | border-color: rgb(0, 255, 179); 343 | } 344 | 345 | 346 | input[type=submit], input[type=button] { 347 | background-color: rgb(0, 255, 179);; 348 | color: black; 349 | font-size: 1.5em; 350 | width: 100px; 351 | height: 35px; 352 | display:inline-block; 353 | box-sizing: border-box; 354 | text-decoration: none; 355 | font-family: 'Roboto', sans-serif; 356 | font-weight: 300; 357 | text-align: center; 358 | margin: 1em 2em; 359 | padding: 0.1em 0; 360 | border-radius: 0.5em; 361 | box-shadow: 0 0.2em 0.3em 0.1em rgba(0,0,0,0.2); 362 | } 363 | 364 | input[type=submit]:hover { 365 | border: 0.15em solid gainsboro; 366 | background-color: rgb(0, 255, 179); 367 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 368 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 369 | box-sizing: border-box; 370 | } 371 | 372 | fieldset { 373 | font-family: 'Roboto', sans-serif; 374 | background-color: #fff; 375 | display: flex; 376 | flex-direction: column; 377 | align-items: center; 378 | border: none; 379 | margin: 1em 2em; 380 | padding: 0.1em 0; 381 | border-radius: 0.5em; 382 | box-shadow: 0 0.2em 0.3em 0.1em rgba(0,0,0,0.2); 383 | } 384 | 385 | fieldset legend { 386 | padding: 0.3em; 387 | margin-left: 1em; 388 | background-color: black; 389 | color:rgb(0, 255, 179); 390 | border-radius: 0.5em; 391 | box-shadow: 0 0.1em 0.2em 0.1em rgba(0,0,0,0.2); 392 | font-size: 1.2em; 393 | font-family: 'Press Start 2P', cursive; 394 | } 395 | 396 | .black-border { 397 | border: 1px rgb(78, 78, 78) solid; 398 | } 399 | 400 | .red-border { 401 | border: 2px #e63946 solid; 402 | } 403 | 404 | .red-border:hover { 405 | border: 2px #e63946 solid !important; 406 | } 407 | 408 | .red-border:focus { 409 | border: 2px #e63946 solid !important; 410 | } 411 | 412 | #welcome { 413 | background-color: #caffbf; 414 | color: #1d3557; 415 | font-size: 1.2em; 416 | font-family: 'Roboto', sans-serif; 417 | font-weight: bold; 418 | 419 | } 420 | 421 | #gameover { 422 | width: 640px; 423 | height: 240px; 424 | position: absolute; 425 | color: #e63946; 426 | font-size: 5em; 427 | font-weight: bold; 428 | left: 160px; 429 | top: 160px; 430 | font-family: 'Press Start 2P', cursive; 431 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 432 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 433 | box-sizing: border-box; 434 | } 435 | 436 | #nextlevel { 437 | width: 640px; 438 | height: 240px; 439 | position: absolute; 440 | color: rgb(0, 255, 179); 441 | font-size: 5em; 442 | font-weight: bold; 443 | left: 0px; 444 | top: 160px; 445 | font-family: 'Press Start 2P', cursive; 446 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ 447 | -moz-box-sizing: border-box; /* Firefox, other Gecko */ 448 | box-sizing: border-box; 449 | } --------------------------------------------------------------------------------