diff --git a/AppSettings.js b/AppSettings.js index c5d63de..906dc13 100644 --- a/AppSettings.js +++ b/AppSettings.js @@ -1,7 +1,8 @@ const SETTINGS = { GRID_WIDTH: 6, GRID_HEIGHT: 5, - LIVES: 3 + LIVES: 3, + LOCAL_STORAGE_KEY: 'number-munchers-high-scores' }; export default SETTINGS; diff --git a/actions/welcome/new-game.actions.js b/actions/welcome/new-game.actions.js deleted file mode 100644 index 8622a33..0000000 --- a/actions/welcome/new-game.actions.js +++ /dev/null @@ -1,8 +0,0 @@ -// New game component actions and action creators. -export const NEWGAME_ACTION = 'NEWGAME_ACTION'; -export const BLINK = 'BLINK'; - -export const blink = () => ({ - type: NEWGAME_ACTION, - action: BLINK -}); diff --git a/actions/welcome/welcome.actions.js b/actions/welcome/welcome.actions.js new file mode 100644 index 0000000..521bcbc --- /dev/null +++ b/actions/welcome/welcome.actions.js @@ -0,0 +1,15 @@ +export const WELCOME_ACTION = 'WELCOME_ACTION'; +export const UPDATE_SCORES = 'WELCOME_UPDATE_SCORES'; +export const UPDATE_INITIALS = 'WELCOME_UDPATE_INITIALS'; + +export const updateScores = (scores) => ({ + type: WELCOME_ACTION, + action: UPDATE_SCORES, + scores: scores +}); + +export const updateInitials = (initials) => ({ + type: WELCOME_ACTION, + action: UPDATE_INITIALS, + initials: initials +}); diff --git a/components/welcome/high-score-entry.component.js b/components/welcome/high-score.component.js similarity index 100% rename from components/welcome/high-score-entry.component.js rename to components/welcome/high-score.component.js diff --git a/components/welcome/high-scores.component.js b/components/welcome/high-scores.component.js deleted file mode 100644 index 19868fc..0000000 --- a/components/welcome/high-scores.component.js +++ /dev/null @@ -1,26 +0,0 @@ -require('../../sass/welcome/highscores.scss'); - -import { Component } from 'react'; -import HighScoreEntry from './high-score-entry.component'; - -export default class HighScores extends Component { - render() { - const vals = [ - { initials: "ABA", score: "219283", rank: "1" }, - { initials: "ABA", score: "107112", rank: "2" }, - { initials: "ABA", score: "81091", rank: "3" }, - { initials: "ABA", score: "67747", rank: "4" }, - { initials: "ABA", score: "9283", rank: "5" }, - { initials: "ABA", score: "928", rank: "6" } - ]; - - var entries = []; - vals.map(function(v, i) { - entries.push(); - }); - - return ( -
{entries}
- ); - } -}; diff --git a/components/welcome/initials.component.js b/components/welcome/initials.component.js new file mode 100644 index 0000000..d108c4c --- /dev/null +++ b/components/welcome/initials.component.js @@ -0,0 +1,42 @@ +import { Component } from 'react'; +import { connect } from 'react-redux'; + +import InitialsCtrl from '../../controllers/welcome/initials.controller'; + +export class Initials extends Component { + componentDidMount() { + InitialsCtrl.setDispatch(this.props.dispatch); + }; + + render() { + const class1 = ['initial']; + const class2 = ['initial']; + const class3 = ['initial']; + + if (this.props.initials[0].active === true) { + class1.push('blink'); + } + if (this.props.initials[1].active === true) { + class2.push('blink'); + } + if (this.props.initials[2].active === true) { + class3.push('blink'); + } + + return ( +
+
{this.props.initials[0].initial}
+
{this.props.initials[1].initial}
+
{this.props.initials[2].initial}
+
+ ); + }; +}; + +const select = (state) => { + return { + initials: state.welcome.initials + } +}; + +export default connect(select)(Initials); diff --git a/components/welcome/new-game.component.js b/components/welcome/new-game.component.js deleted file mode 100644 index 768cbac..0000000 --- a/components/welcome/new-game.component.js +++ /dev/null @@ -1,51 +0,0 @@ -import { Component } from 'react'; -import { connect } from 'react-redux'; - -import * as ModeActions from '../../actions/mode.actions'; -import * as NewGameActions from '../../actions/welcome/new-game.actions'; - -let blinkTimer = null; -let newgameListener = null; - -const toggleTimeout = function() { - var hidden = this.props.blink; - this.props.dispatch(NewGameActions.blink()); - blinkTimer = setTimeout(toggleTimeout.bind(this), 600); -}; - -export default class NewGame extends Component { - componentDidMount() { - newgameListener = this.handleKeydown.bind(this); - window.addEventListener('keydown', newgameListener); - toggleTimeout.call(this); - }; - - componentWillUnmount() { - window.removeEventListener('keydown', newgameListener); - clearTimeout(blinkTimer); - }; - - handleKeydown(e) { - if (e.keyCode === 32) { - this.props.dispatch(ModeActions.options()); - } - }; - - render() { - const classname = ['newgame']; - - if (this.props.hidden === true) { - classname.push('hidden'); - } - - return ( -
Press Space Bar To Play
- ); - }; -}; - -const select = (state) => { - return state.newgame; -}; - -export default connect(select)(NewGame); diff --git a/components/welcome/welcome.component.js b/components/welcome/welcome.component.js index c6158cc..2385d95 100644 --- a/components/welcome/welcome.component.js +++ b/components/welcome/welcome.component.js @@ -1,18 +1,50 @@ require('../../sass/welcome/welcome.scss'); import { Component } from 'react'; +import { connect } from 'react-redux'; +import Initials from './initials.component'; -import NewGame from './new-game.component'; -import HighScores from './high-scores.component'; +import * as ModeActions from '../../actions/mode.actions'; +import WelcomeCtrl from '../../controllers/welcome/welcome.controller'; + +let listener; + +export class Welcome extends Component { + componentDidMount() { + listener = WelcomeCtrl.keydown.bind(WelcomeCtrl); + window.addEventListener('keydown', listener); + + // HighScoreCtrl.setDispatch(this.props.dispatch); + // HighScoreCtrl.retrieveScores(); + // HighScoreCtrl.updateScores(ScorebarCtrl.currentScore); + }; + + componentWillUnmount() { + window.removeEventListener('keydown', listener); + }; -export default class Welcome extends Component { render() { + // var entries = []; + // this.props.values.map(function(v, i) { + // entries.push(); + // }); + return (
- - +
Press Spacebar for new game
+
New high score!
+
Enter your initials:
+
); }; }; + +const select = (state) => { + return { + values: state.welcome.scores + } +}; + +export default connect(select)(Welcome); diff --git a/controllers/board/board.controller.js b/controllers/board/board.controller.js index d743e4c..a3b97e8 100644 --- a/controllers/board/board.controller.js +++ b/controllers/board/board.controller.js @@ -6,6 +6,7 @@ import MuncherCtrl from './muncher.controller'; import GridCtrl from './grid.controller'; import TitlebarCtrl from './titlebar.controller'; import ScorebarCtrl from './scorebar.controller'; +import WelcomeCtrl from '../welcome/welcome.controller'; import ModeCtrl from '../mode.controller'; let level = -1; @@ -51,13 +52,14 @@ const BoardCtrl = { } else if (ScorebarCtrl.isGameOver()) { level = -1; + WelcomeCtrl.gameOver(ScorebarCtrl.getCurrentScore()); ScorebarCtrl.reset(); MessageCtrl.hide(); TroggleCtrl.unfreeze(); ModeCtrl.welcome(); } else if (ScorebarCtrl.getLives() === 0) { - ScorebarCtrl.flagGameOver(); + ScorebarCtrl.gameOver(); MessageCtrl.show("Game over!"); } else if (GridCtrl.isCompleted() === true) { diff --git a/controllers/board/grid.controller.js b/controllers/board/grid.controller.js index 821ccbb..7be27b6 100644 --- a/controllers/board/grid.controller.js +++ b/controllers/board/grid.controller.js @@ -1,5 +1,5 @@ import * as GridActions from '../../actions/board/grid.actions'; -import SubtractionModel from '../../models/subtraction.model'; +import FactorsModel from '../../models/factors.model'; import SETTINGS from '../../AppSettings'; let values; @@ -10,12 +10,12 @@ const GridCtrl = { getValues: () => values, generateValues: (level) => { - values = SubtractionModel.generate(SETTINGS.GRID_WIDTH * SETTINGS.GRID_HEIGHT, level); + values = FactorsModel.generate(SETTINGS.GRID_WIDTH * SETTINGS.GRID_HEIGHT, level); dispatch(GridActions.update(values)); }, isCompleted: (level) => { - return SubtractionModel.checkComplete(values, level) + return FactorsModel.checkComplete(values, level) }, hideValue: (index) => { diff --git a/controllers/board/scorebar.controller.js b/controllers/board/scorebar.controller.js index dc2f703..23e46df 100644 --- a/controllers/board/scorebar.controller.js +++ b/controllers/board/scorebar.controller.js @@ -1,21 +1,32 @@ import * as ScorebarActions from '../../actions/board/scorebar.actions'; import SETTINGS from '../../AppSettings'; +const stored = localStorage.getItem(SETTINGS.LOCAL_STORAGE_KEY) || [{ score: 0 }]; + let dispatch; let lives = SETTINGS.LIVES; let currentScore = 0; -let highScore = 7; +let previousHighScore = stored[0].score; +let highScore = stored[0].score; let gameOver = false; const ScorebarCtrl = { setDispatch: d => dispatch = d, getLives: () => lives, + getCurrentScore: () => currentScore, - flagGameOver: () => gameOver = true, isGameOver: () => gameOver, update: () => { + // Note that loss of points may cause new high to drop below previous. + if (currentScore > previousHighScore) { + highScore = currentScore; + } + else { + highScore = previousHighScore; + } + dispatch(ScorebarActions.update(lives, currentScore, highScore)); }, @@ -37,9 +48,18 @@ const ScorebarCtrl = { levelUp: (level) => { currentScore += 25; + + if (level % 3 === 0) { + lives++; + } + ScorebarCtrl.update(); }, + gameOver: () => { + gameOver = true; + }, + reset: () => { lives = SETTINGS.LIVES; currentScore = 0; diff --git a/controllers/board/titlebar.controller.js b/controllers/board/titlebar.controller.js index 9d29caa..6197ef9 100644 --- a/controllers/board/titlebar.controller.js +++ b/controllers/board/titlebar.controller.js @@ -1,5 +1,5 @@ import * as TitlebarActions from '../../actions/board/titlebar.actions'; -import SubtractionModel from '../../models/subtraction.model'; +import FactorsModel from '../../models/factors.model'; let dispatch; @@ -7,7 +7,7 @@ const TitlebarCtrl = { setDispatch: d => dispatch = d, setTitle: (level) => { - const title = SubtractionModel.getTitle(level); + const title = FactorsModel.getTitle(level); dispatch(TitlebarActions.update(title)); } }; diff --git a/controllers/options/options.controller.js b/controllers/options/options.controller.js index 2e05fbd..c69e2aa 100644 --- a/controllers/options/options.controller.js +++ b/controllers/options/options.controller.js @@ -16,6 +16,10 @@ const OptionsCtrl = { keyListener(e) { if (e.keyCode === 32) { + let model = + // switch (selected) { + // case 0: + // } ModeCtrl.board(); } else if (e.keyCode === 38) { diff --git a/controllers/welcome/initials.controller.js b/controllers/welcome/initials.controller.js new file mode 100644 index 0000000..4dd736c --- /dev/null +++ b/controllers/welcome/initials.controller.js @@ -0,0 +1,91 @@ +import * as WelcomeActions from '../../actions/welcome/welcome.actions'; + +let dispatch; + +let initials = [ + { initial: 'A', active: true }, + { initial: 'A', active: false }, + { initial: 'A', active: false } +]; + +let active = 0; +let code; + +const InitialsCtrl = { + setDispatch: (d) => dispatch = d, + + keydown: function(e) { + switch (e.keyCode) { + case 13: + case 32: + break; + case 37: + active--; + if (active === -1) { + active = initials.length - 1; + } + this.updateInitials(); + break; + case 38: + code = initials[active].initial.charCodeAt(0); + code--; + if (code === 64) { + code = 90; + } + initials[active].initial = String.fromCharCode(code); + this.updateInitials(); + break; + case 39: + active++; + if (active === initials.length) { + active = 0; + } + this.updateInitials(); + break; + case 40: + code = initials[active].initial.charCodeAt(0); + code++; + if (code === 91) { + code = 65; + } + initials[active].initial = String.fromCharCode(code); + this.updateInitials(); + break; + } + + if (e.keyCode >= 65 && e.keyCode <= 90) { + initials[active].initial = String.fromCharCode(e.keyCode); + + if (active < initials.length - 1) { + active++; + } + this.updateInitials(); + } + }, + + updateInitials: () => { + initials = initials.map((v) => { + v.active = false; + return v; + }); + + initials[active].active = true; + + dispatch(WelcomeActions.updateInitials(initials)); + }, + + updateScores: (scores) => { + // [ + // { initials: "ABA", score: "219283", rank: "1" }, + // { initials: "ABA", score: "107112", rank: "2" }, + // { initials: "ABA", score: "81091", rank: "3" }, + // { initials: "ABA", score: "67747", rank: "4" }, + // { initials: "ABA", score: "9283", rank: "5" }, + // { initials: "ABA", score: "928", rank: "6" } + // ]; + + // localStorage.setItem(SETTINGS.LOCAL_STORAGE_KEY, scores); + } +}; + +export default InitialsCtrl; diff --git a/controllers/welcome/welcome.controller.js b/controllers/welcome/welcome.controller.js new file mode 100644 index 0000000..50f5a9f --- /dev/null +++ b/controllers/welcome/welcome.controller.js @@ -0,0 +1,43 @@ +import * as WelcomeActions from '../../actions/welcome/welcome.actions'; +import InitialsCtrl from './initials.controller'; +import SETTINGS from '../../AppSettings'; + +let dispatch; +let update = true; + +const HighScoreCtrl = { + setDispatch: (d) => dispatch = d, + + keydown: (e) => { + if (update === true) { + InitialsCtrl.keydown(e); + } + else if (e.keyCode === 32) { + this.props.dispatch(ModeActions.options()); + } + }, + + getHighScore: () => { + const scores = localStorage.getItem(SETTINGS.LOCAL_STORAGE_KEY); + + if (scores !== null) { + return scores[0].score; + } + + return 0; + }, + + gameOver: (score) => { + + }, + + retrieveScores: () => { + const scores = localStorage.getItem(SETTINGS.LOCAL_STORAGE_KEY); + + if (scores !== null) { + dispatch(WelcomeActions.updateScores(scores)); + } + } +}; + +export default HighScoreCtrl; diff --git a/index.js b/index.js index 86198cc..fe96ca2 100644 --- a/index.js +++ b/index.js @@ -6,25 +6,28 @@ import { Provider } from 'react-redux'; import App from './App'; import modeReducer from './reducers/mode.reducer'; -import newgameReducer from './reducers/welcome/new-game.reducer'; +import welcomeReducer from './reducers/welcome/welcome.reducer'; +import optionsReducer from './reducers/options/options.reducer'; import gridReducer from './reducers/board/grid.reducer'; import muncherReducer from './reducers/board/muncher.reducer'; import scorebarReducer from './reducers/board/scorebar.reducer'; import messageReducer from './reducers/board/message.reducer'; import troggleReducer from './reducers/board/troggle.reducer'; import titlebarReducer from './reducers/board/titlebar.reducer'; -import optionsReducer from './reducers/options/options.reducer'; const reducers = combineReducers({ mode: modeReducer, - newgame: newgameReducer, + + welcome: welcomeReducer, + + options: optionsReducer, + muncher: muncherReducer, scorebar: scorebarReducer, message: messageReducer, troggles: troggleReducer, grid: gridReducer, - titlebar: titlebarReducer, - options: optionsReducer + titlebar: titlebarReducer }); const store = createStore(reducers); diff --git a/reducers/mode.reducer.js b/reducers/mode.reducer.js index 862a058..d1e9045 100644 --- a/reducers/mode.reducer.js +++ b/reducers/mode.reducer.js @@ -1,6 +1,6 @@ import * as ModeActions from '../actions/mode.actions'; -const initial = ModeActions.OPTIONS; +const initial = ModeActions.WELCOME; const reducer = (state = initial, action) => { if (action.type !== ModeActions.MODE_ACTION) { diff --git a/reducers/welcome/high-scores.reducer.js b/reducers/welcome/high-scores.reducer.js deleted file mode 100644 index e69de29..0000000 diff --git a/reducers/welcome/new-game.reducer.js b/reducers/welcome/new-game.reducer.js deleted file mode 100644 index f14042e..0000000 --- a/reducers/welcome/new-game.reducer.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as NewGameActions from '../../actions/welcome/new-game.actions'; - -const initial = { hidden: false }; -const reducer = (state = initial, action) => { - if (action.type !== NewGameActions.NEWGAME_ACTION) { - return state; - } - - if (action.action === NewGameActions.BLINK) { - return (state.hidden ? { hidden: false } : { hidden: true }); - } - - return state; -}; - -export default reducer; diff --git a/reducers/welcome/welcome.reducer.js b/reducers/welcome/welcome.reducer.js new file mode 100644 index 0000000..79a1ddc --- /dev/null +++ b/reducers/welcome/welcome.reducer.js @@ -0,0 +1,40 @@ +const Immutable = require('immutable'); + +import * as WelcomeActions from '../../actions/welcome/welcome.actions'; + +const initial = { + scores: [ + { initials: "AAA", score: "0", rank: "1" }, + { initials: "AAA", score: "0", rank: "1" }, + { initials: "AAA", score: "0", rank: "1" }, + { initials: "AAA", score: "0", rank: "1" }, + { initials: "AAA", score: "0", rank: "1" }, + { initials: "AAA", score: "0", rank: "1" } + ], + + initials: [ + { initial: 'A', active: true }, + { initial: 'A', active: false }, + { initial: 'A', active: false } + ] +}; + +const reducer = (state = initial, action) => { + if (action.type !== WelcomeActions.WELCOME_ACTION) { + return state; + } + + // if (action.action === WelcomeActions.UPDATE_SCORES) { + // return action.scores; + // } + switch (action.action) { + case WelcomeActions.UPDATE_INITIALS: + return Immutable.Map(state) + .set('initials', action.initials) + .toObject(); + } + + return state; +}; + +export default reducer; diff --git a/sass/board/scorebar.scss b/sass/board/scorebar.scss index cea5df1..4aa627d 100644 --- a/sass/board/scorebar.scss +++ b/sass/board/scorebar.scss @@ -18,25 +18,25 @@ } .current-score { - width:30%; + width:35%; } .high-score { text-align:center; - width:40%; + width:30%; } .lives { text-align:right; - width:30%; + width:35%; } .life { background:lime; display:inline-block; - height:36px; - margin-left:10px; + height:30px; + margin-left:5px; vertical-align:middle; - width:36px; + width:30px; } } diff --git a/sass/reset.scss b/sass/reset.scss index 79a9e07..8c02784 100644 --- a/sass/reset.scss +++ b/sass/reset.scss @@ -2,6 +2,5 @@ box-sizing:border-box; font-family:Emulogic; margin: 0; - outline:none; padding: 0; } diff --git a/sass/welcome/highscores.scss b/sass/welcome/highscores.scss deleted file mode 100644 index 6434667..0000000 --- a/sass/welcome/highscores.scss +++ /dev/null @@ -1,25 +0,0 @@ -.highscores { - font-size: 0; - - .entry { - font-size:16px; - line-height:40px; - } - - .rank { - display:inline-block; - text-align:left; - width:50px; - } - - .initials { - display:inline-block; - width:100px; - } - - .score { - display:inline-block; - text-align:right; - width:200px; - } -} diff --git a/sass/welcome/welcome.scss b/sass/welcome/welcome.scss index 88cc3b0..a36a045 100644 --- a/sass/welcome/welcome.scss +++ b/sass/welcome/welcome.scss @@ -1,22 +1,100 @@ .welcome { + $w: 350px; + padding:20px; text-align:center; img { - width:350px; + width:$w; } - .highscores { - margin:0 auto; - width:350px; + .line { + line-height:30px; } - .newgame { - margin:20px 0; - transition:opacity 0.1s ease; + @keyframes blink { + 50% { + opacity: 0.6; + } + } - &.hidden { - opacity: 0.5; + @-webkit-keyframes blink { + 50% { + opacity: 0.6; } } + + .blink { + animation: blink 1s step-start 0s infinite; + -webkit-animation: blink 1s step-start 0s infinite; + } + + .initial { + display:inline-block; + } + + // .highscores { + // margin:0 auto; + // width:350px; + // } + // + // .newgame { + // margin:20px 0; + // transition:opacity 0.1s ease; + // + // &.hidden { + // opacity: 0.5; + // } + // } } +// +// .highscores { +// $w: 350px; +// +// font-size: 0; +// +// hr { +// border:0; +// border-top:1px solid #ccc; +// margin:20px auto; +// width: $w; +// } +// +// .blink { +// animation: blinker 1s none infinite; +// } +// +// @keyframes blinker { +// 50% { opacity: 0.6; } +// } +// +// .line { +// font-size:16px; +// line-height:30px; +// margin: 10px auto; +// text-align:center; +// width:$w; +// }; +// +// .entry { +// font-size:16px; +// line-height:40px; +// } +// +// .rank { +// display:inline-block; +// text-align:left; +// width:50px; +// } +// +// .initials { +// display:inline-block; +// width:100px; +// } +// +// .score { +// display:inline-block; +// text-align:right; +// width:200px; +// } +// }