The game itself is published here.
Here's the full source code. It's surprisingly consise, feel free to study it on your own.
// index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import App from '@/app'; /** * Entry point */ class Program { Main() { var app = ( <App /> ); ReactDOM.render(app, document.getElementById('guess6')); } } new Program().Main(); // App.tsx import React, { useEffect, useState } from 'react'; import Dictionary from './dictionary'; import Keyboard from './keyboard'; import WordMatch from './wordMatch'; const App = () => { const EXPECTEDLENGTH = 6; const [words, setWords] = useState<Array<string>>([]); const [secretWord, setSecretWord] = useState<string>(''); function getRandomWord(Dictionary: string[]): string { const randomIndex = Math.floor(Math.random() * (Dictionary.length)); return Dictionary[randomIndex].toUpperCase(); } function restartGame() { setWords([]); setSecretWord(getRandomWord(Dictionary)); } function onWordTyped( newWord: string ) { setWords( words => words.concat([newWord]) ); } function giveUp() { if ( secretWord.length == EXPECTEDLENGTH ) { setWords( words => words.concat( secretWord ) ); } } useEffect( () => { restartGame(); }, []); return <> <div> <button className='flatButton' onClick={() => restartGame()}>NEW GAME</button> <button className='flatButton' onClick={() => giveUp()}>GIVE UP</button> </div> <h1>Enter {EXPECTEDLENGTH}-letter word</h1> <Keyboard dictionary={Dictionary} expectedLength={EXPECTEDLENGTH} onWordTyped={onWordTyped} /> {words.map( (word, index) => <ordMatch candidate={word} secret={secretWord} key={index} />)} </> }; export default App; // Keyboard.tsx import React, { KeyboardEvent, useEffect, useState } from 'react'; const Keyboard = ({dictionary, expectedLength, onWordTyped} : {dictionary: string[] | undefined, expectedLength: number, onWordTyped: (word: string) => void}) => { const [message, setMessage] = useState<string>(''); const [word, setWord] = useState<string>(''); const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const QWERTY = "QWERTYUIOP"; const ASDF = "ASDFGHJKL"; const ZXCV = "ZXCVBNM"; function appendLetter(letter: string) { if ( word.length < expectedLength ) { setWord( w => w + letter ); setMessage(''); } } function clearWord() { setWord(''); setMessage(''); } function tryAcceptWord() { if ( word.length != expectedLength ) { setMessage(`Expected ${expectedLength} characters, got ${word.length} so far`); return; } if ( dictionary !== undefined && dictionary.map( w => w.toUpperCase() ).indexOf( word ) < 0 ) { setMessage(`Word ${word} not in dictionary`); return; } onWordTyped(word); setWord(''); } return <div> <div> <input className='keyboardInput' value={word} readOnly /> </div> <div className='firstRow'> {QWERTY.split('').map( (letter) => <button className='letterButton flatButton' onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )} </div> <div className='secondRow'> {ASDF.split('').map( (letter) => <button className='letterButton flatButton' onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )} <button className='flatButton' onClick={() => clearWord()}>DEL</button> </div> <div className='thirdRow'> {ZXCV.split('').map( (letter) => <button className='letterButton flatButton' onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )} <button className='flatButton' onClick={() => tryAcceptWord()}>ENTER</button> </div> <div>{message}</div> </div>; } export default Keyboard; // WordMatch.tsx import React from 'react'; const WordMatch = ({candidate, secret} : {candidate: string, secret: string}) => { if ( candidate.length != secret.length ) { throw new Error('candidate and secret word must have same length'); } type letterState = 'USED' | undefined; const letterStates: Array<letterState> = Array<letterState>(candidate.length); function getLetterClass( index: number ) : string { // match if ( secret[index] == candidate[index] ) { letterStates[index] = 'USED'; return 'letter letterMatch'; } // possible for ( let i=0; i<secret.length; i++ ) { if ( secret[i] == candidate[index] && letterStates[i] == undefined ) { letterStates[i] = 'USED'; return 'letter letterPossible'; } } // none return 'letter letterWrong'; } return <div> {candidate.split('').map( (letter, index) => <span className={getLetterClass(index)} key={index}>{letter}</span> )} </div>; } export default WordMatch; // Dictionary.ts // https://eslforums.com/6-letter-words/ const Dictionary: Array<string> = [ "abacus", // the rest of the dictionary here "zoning" ]; export default Dictionary;
No comments:
Post a Comment