├── .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. Don't have an acount?
12 | Sign Up Here
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 | Click to Play
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 = props.resetGame()}>Reset Game ;
11 | const playAgainButton = props.resetGame()}>Play Again ;
12 | const nextButton = props.updateGame(props.level+1)}>Next Level
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 |
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 = ;
58 | }
59 | if(status === 'success'){
60 | body = You've successfully signed up. Now log in to play!
61 | Click Here To Log In
;
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 |
85 |
86 | {links}
87 |
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 ();
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 |
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 | }
--------------------------------------------------------------------------------