Last active
September 23, 2018 09:01
-
-
Save imkrish/9e1e424d091eae8280abe84b50e85b62 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Observable, BehaviorSubject, Subject } from 'rxjs' | |
import { withLatestFrom } from 'rxjs/operators' | |
import { deepEqual } from 'assert' | |
interface IGameState { | |
status: GameStatus | |
selectedLetters: string[] | |
lifeLeft: number | |
secretWordLength: number | |
knownSecretWord: string | |
} | |
enum GameStatus { | |
IN_PROGRESS = 'in-progress', | |
WON = 'won', | |
LOSE = 'lose', | |
} | |
function reactiveHangman(secretWord: string, letter$: Observable<string>) { | |
const initGameState = getInitGameState(secretWord, 7) | |
const gameState$ = new BehaviorSubject(initGameState) | |
const letterWithGameState$ = letter$.pipe(withLatestFrom(gameState$)) | |
letterWithGameState$.subscribe(([letter, gameState]) => { | |
const nextGameState = getNextGameState(gameState, secretWord, letter) | |
gameState$.next(nextGameState) | |
const { status } = nextGameState | |
const winOrLose = status === GameStatus.WON || status === GameStatus.LOSE | |
if (winOrLose) { | |
gameState$.complete() | |
} | |
}) | |
return gameState$.asObservable() | |
} | |
function getInitGameState(secretWord: string, lives: number) { | |
const secretWordLength = secretWord.length | |
const initGameState: IGameState = { | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: [], | |
lifeLeft: lives, | |
secretWordLength: secretWord.length, | |
knownSecretWord: '_'.repeat(secretWordLength), | |
} | |
return initGameState | |
} | |
function fillCorrectLetters(word: string, filledWord: string, letter: string) { | |
const newFilledWord = [...word].reduce( | |
(filedWord, char, idx) => { | |
if (char === letter) { | |
filedWord[idx] = char | |
} | |
return filedWord | |
}, | |
[...filledWord] | |
) | |
return newFilledWord.join('') | |
} | |
function isWin(knowSecretWord: string) { | |
return [...knowSecretWord].every(char => char !== '_') | |
} | |
function getNextGameState( | |
gameState: IGameState, | |
secretWord: string, | |
letter: string | |
): IGameState { | |
const { | |
selectedLetters, | |
lifeLeft, | |
secretWordLength, | |
knownSecretWord, | |
} = gameState | |
const newSelectedLetters = selectedLetters.concat(letter) | |
const correctLetter = secretWord.includes(letter) | |
const newLifeLeft = correctLetter ? lifeLeft : lifeLeft - 1 | |
const newKnownSecretWord = fillCorrectLetters( | |
secretWord, | |
knownSecretWord, | |
letter | |
) | |
const win = isWin(newKnownSecretWord) | |
const lose = newLifeLeft === 0 | |
const newStatus = win | |
? GameStatus.WON | |
: lose | |
? GameStatus.LOSE | |
: GameStatus.IN_PROGRESS | |
return { | |
status: newStatus, | |
selectedLetters: newSelectedLetters, | |
lifeLeft: newLifeLeft, | |
secretWordLength, | |
knownSecretWord: newKnownSecretWord, | |
} | |
} | |
// Test | |
function test(secretWord: string, letters: string, expected: IGameState[]) { | |
const letterSubject = new Subject<string>() | |
const letter$ = letterSubject.asObservable() | |
const gameState$ = reactiveHangman(secretWord, letter$) | |
const actual: IGameState[] = [] | |
gameState$.subscribe( | |
gameState => actual.push(gameState), | |
() => {}, | |
() => { | |
deepEqual(actual, expected) | |
} | |
) | |
;[...letters].forEach(letter => letterSubject.next(letter)) | |
} | |
// Test case 1 | |
const expected1: IGameState[] = [ | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: [], | |
lifeLeft: 7, | |
secretWordLength: 7, | |
knownSecretWord: '_______', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b'], | |
lifeLeft: 7, | |
secretWordLength: 7, | |
knownSecretWord: 'b__b___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'b__b___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'i'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'bi_b___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'i', 'g'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'bigb___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'i', 'g', 'a'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'bigb_a_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'i', 'g', 'a', 'e'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'bigbea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'i', 'g', 'a', 'e', 'y'], | |
lifeLeft: 5, | |
secretWordLength: 7, | |
knownSecretWord: 'bigbea_', | |
}, | |
{ | |
status: GameStatus.WON, | |
selectedLetters: ['b', 'o', 'i', 'g', 'a', 'e', 'y', 'r'], | |
lifeLeft: 5, | |
secretWordLength: 7, | |
knownSecretWord: 'bigbear', | |
}, | |
] | |
test('bigbear', 'boigaeyr', expected1) | |
// Test Case 2 | |
const expected2: IGameState[] = [ | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: [], | |
lifeLeft: 7, | |
secretWordLength: 7, | |
knownSecretWord: '_______', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b'], | |
lifeLeft: 7, | |
secretWordLength: 7, | |
knownSecretWord: 'b__b___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'b__b___', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'b__b_a_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e'], | |
lifeLeft: 6, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n'], | |
lifeLeft: 5, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n', 'u'], | |
lifeLeft: 4, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n', 'u', 't'], | |
lifeLeft: 3, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n', 'u', 't', 'z'], | |
lifeLeft: 2, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.IN_PROGRESS, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n', 'u', 't', 'z', 'x'], | |
lifeLeft: 1, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
{ | |
status: GameStatus.LOSE, | |
selectedLetters: ['b', 'o', 'a', 'e', 'n', 'u', 't', 'z', 'x', 'v'], | |
lifeLeft: 0, | |
secretWordLength: 7, | |
knownSecretWord: 'b__bea_', | |
}, | |
] | |
test('bigbear', 'boaenutzxv', expected2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment