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