diff options
| -rw-r--r-- | App.js | 331 | ||||
| -rw-r--r-- | index.html | 6 | 
2 files changed, 84 insertions, 253 deletions
| @@ -1,277 +1,112 @@ -const levels = []; -const LEVEL_SIZE = 20; - -for (let i=0; (LEVEL_SIZE*i) < characters.length; i++) { -   let start = (20*i) + 1; -   let end = 20 * (i+1); -   end = end > characters.length ? characters.length-1 : end; -    -   levels.push(`Level ${i+1} (${start} - ${end})`); -} - - -const gameModes = [ -   'Only Level Characters', -   'Level + Previous Four Levels', -   'All Characters up to Level', -]; - -const GAMEMODE_LEVEL_ONLY = 0; -const GAMEMODE_PREV_LEVELS = 1; -const GAMEMODE_ALL_TO_LEVEL = 2; - - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -let state = { -   level: 0, -   gameMode: GAMEMODE_LEVEL_ONLY, -   paused: false, -    -   levelCharacters: [], -   index: 0, -    -   character: '你', -   pinyin: 'ni3', - -   guessHistory: [], -   numGuesses: 0, -   correctGuesses: 0, -    -   hintLevel: 0, -    -   inputValue: undefined, -   inputShaking: false, -}; - - -function setState(update) -{ -   for (const key in update) -      state[key] = update[key]; - -   render(); -} +'use strict'; +const h = Inferno.h; -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const GlobalState = {}; -const App = function() -{ -   const { -      levelCharacters, time, -      level, gameMode, -      character, pinyin, -      numGuesses, correctGuesses,       -      hintLevel, -      inputValue, inputShaking -   } = state; - -   return h( -      'div', {}, -      [ -         h(ScoreView, -           { level: level, -             numCharacters: levelCharacters.length, -             guesses: numGuesses, -             correctGuesses, -             time, -           }), -          -         h('h1', {}, character), -          -         h(InputBox, -           { value: inputValue, -             shaking: inputShaking, -             handleKeyDown: handleInputKeyDown, -           }), - -         h('br'), - -         h(HintButton, -           { -              answer: pinyin, -              hintLevel, -              handleClick: () => setState({ hintLevel: hintLevel+1 }), -           }), - -         h('br'), - -         h(StyleDropdown, -           { options: levels, -             index: level, -             defaultText: 'Select a Level', -             onChoose: (index) => { -                setState({ level: index }); -                reset(); -             }, -           }), +// IMPURE +const updateState = (state, update) => Object.keys(update).forEach( +   key => { +      if (typeof(state[key]) === 'object') +         updateState(state[key], update[key]); +      else +         state[key] = update[key]; +   }); -         h(StyleDropdown, -           { options: gameModes, -             index: gameMode, -             defaultText: 'Select a Game Mode', -             onChoose: (index) => { -                setState({ gameMode: index }); -                reset(); -             }, -           }), -      ] +// IMPURE +const setState = update => { +   updateState(GlobalState, update); +   Inferno.render( +      h(App, {state: GlobalState, setState}), +      document.getElementById('root')     );  } -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -function handleInputKeyDown(event) -{ -   const KEY_ENTER = 13; -       -   if (event.keyCode === KEY_ENTER) { -      makeGuess(); -   } -   else { -      setTimeout(() => setState({ -         inputValue: event.target.value -      }), 300); -   } -} - - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * App + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ -function makeGuess() -{ -   const { inputValue, pinyin } = state; -    -   if (inputValue === pinyin) -         correctAnswer(); -      else -         shakeInputBox(); -} +// IMPURE +const App = ({state, setState}) => { +   const { guessInput, } = state; -function correctAnswer() -{ -   setState({ -      inputValue: undefined, -      hintLevel: 0, +   return h(GuessInput, { +      value: guessInput.value, +      isShaking: guessInput.isShaking, +      handleInput: updateStateOnEvent(state, handleGuessInputInput), +      handleKeyDown: updateStateOnEvent(state, handleGuessInputKeyDown),     }); -} +}; -function shakeInputBox() -{ -   setState({ inputShaking: true }); -   setTimeout(() => setState({ inputShaking: false }), 300); -} +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Components + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +const GuessInput = ({value, isShaking, handleInput, handleKeyDown}) => { +   const inputTag = isShaking ? 'input.shaking' : 'input'; -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +   return h(inputTag, { +      type: 'text', +      value, +      onInput: handleInput, +      onKeyDown: handleKeyDown, +   }); +}; -// fisher-yates shuffle -function shuffledArray(arr) -{ -   const shuffled = [...arr]; -   for (let i=arr.length-1; i>0; i--) { -      const j = Math.floor(Math.random() * i); -      let tmp = shuffled[j]; -      shuffled[j] = shuffled[i]; -      shuffled[i] = tmp; -   } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Event Handlers + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ -   return shuffled; -} +// IMPURE +const updateStateOnEvent = (state, handler) => +      event => setState(handler(state, event)); -function buildLevel(level, levelSize, gameMode, characters) -{ -   let start; -    -   switch (gameMode) { -   case GAMEMODE_LEVEL_ONLY: -      start = levelSize * level; -      break; +const handleGuessInputInput = (state, event) => +      ({ guessInput: { value: event.target.value } }); -   case GAMEMODE_PREV_LEVELS: -      start = levelSize * (level - 4); -      break; -   case GAMEMODE_ALL_TO_LEVEL: -      start = 0; -      break; +const handleGuessInputKeyDown = (state, event) => { +   const KEYCODE_ENTER = 13; -   default: -      start = 0; -      break; +   if (event.keyCode === KEYCODE_ENTER) { +      console.log(state.guessInput.value); +      return { guessInput: { value: '' } };     } -   start = start < 0 ? 0 : start; -    -   let end = levelSize * (level + 1); +   return {}; +}; -   const levelCharacters = []; -   for (let i=start; i<end; i++) -      levelCharacters.push(characters[i]); -   return shuffledArray(levelCharacters); +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Document load + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +// IMPURE +window.onload = () => { +   const initialState = { +      guessInput: { +         value: '', +         isShaking: false, +      }, +   }; +                +   setState(initialState);  } - -function reset() { -   const { level, gameMode } = state; - -   const levelCharacters = buildLevel(level, LEVEL_SIZE, gameMode, characters); - -   const [character, pinyin] = levelCharacters[0]; - -   clearInterval(timeInterval); -   timeInterval = setInterval(() => { -      const { paused, time } = state; -      if (!paused) -         setState({ time: time+1 }); -   }, 1000); -    -   setState({ -      levelCharacters, -      index: 0, -      paused: false, -      time: 0, -       -      character, -      pinyin, -      guessHistory: [], -      numGuesses: 0, -      correctGuesses: 0, -      hintLevel: 0, -       -      inputValue: undefined, -      inputShaking: false, -   }); -} - - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -let timeInterval; - -function ScoreView({ level, numCharacters, guesses, correctGuesses, time }) -{ -   const minutes = Math.floor(time/60); -   let seconds = time % 60; -   seconds = seconds < 10 ? '0' + seconds : seconds; - - -   const levelName = `Level ${level + 1}`; -   const percentage = -         guesses === 0 ? '100.0' : String(0.1 * Math.floor(1000 * correctGuesses / guesses)); -   const score = `${correctGuesses}/${numCharacters} (${percentage}%)`; - -   return h( -      'div', {}, -      [ -         h('h1', {}, `${levelName} - ${minutes}:${seconds}`), -         h('h1', {}, score), -      ] -   ); -} @@ -9,16 +9,12 @@      <script src="https://unpkg.com/inferno-hyperscript@7.4.8/dist/inferno-hyperscript.min.js"></script>      <!-- local scripts --> -    <script src="./main.js"></script>      <script src="./characters.js"></script> -    <script src="./InputBox.js"></script> -    <script src="./StyleButton.js"></script> -    <script src="./HintButton.js"></script> -    <script src="./StyleDropdown.js"></script>      <script src="./App.js"></script>      <!-- styling -->      <link href="./style.css" rel="stylesheet"> +    <!-- -->    </head>    <body> | 
