Last active
October 2, 2018 08:13
-
-
Save pphetra/4365ea9ebb0e2bd10a4b21d071ea8ba8 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 { Subject, Observable, interval, merge } from "rxjs"; | |
import { map, scan, share } from 'rxjs/operators'; | |
export const MAX_LIFE = 7 | |
export const MAX_TIME = 5 | |
export type State = { | |
gameId: string, | |
status: string, | |
knownSecretWord: Array<string>, | |
selectedLetters: Array<string>, | |
secretWordLength: number, | |
lifeLeft: number, | |
timeLeft: number | |
} | |
export type HangmanAction = { | |
type: string | |
data: any | |
} | |
const guessAction = (guessCh: string) => { | |
return { | |
type: 'guess', | |
data: guessCh | |
} | |
} | |
const tickAction = () => { | |
return { | |
type: 'tick', | |
data: '' | |
} | |
} | |
export class HangmanGame { | |
private _actionSource: Subject<HangmanAction> | |
private _state: Observable<State> | |
private _currentState: Subject<State> | |
constructor(secretWord: string, gameId: string) { | |
const initState = { | |
gameId: gameId, | |
status: 'in-progress', | |
knownSecretWord: secretWord.split('').map(_ => '_'), | |
selectedLetters: [], | |
secretWordLength: secretWord.length, | |
lifeLeft: MAX_LIFE, | |
timeLeft: MAX_TIME | |
} | |
this._actionSource = new Subject<HangmanAction>() | |
this._state = merge ( | |
map(_ => tickAction())(interval(1000)), | |
this._actionSource | |
).pipe( | |
scan(this.createReducer(secretWord), initState) | |
) // cold observable | |
this._currentState = new Subject<State>() | |
this._state.subscribe(this._currentState) // make it hot | |
} | |
guess(letter: string): Observable<State> { | |
this._actionSource.next(guessAction(letter)) | |
return this.currentState() | |
} | |
currentState(): Observable<State> { | |
return this._currentState | |
} | |
private createReducer(secretWord: string): (State, HangmanAction) => State { | |
const word = secretWord.split(''); | |
return (state: State, action: HangmanAction): State => { | |
if (action.type === 'guess') { | |
const guessCh = action.data | |
const selectedLettersSet = new Set(state.selectedLetters) | |
if (selectedLettersSet.has(guessCh) || state.status === 'loss') { | |
return state | |
} | |
selectedLettersSet.add(guessCh) | |
const found = secretWord.indexOf(guessCh) >= 0 | |
const lifeLeft = state.lifeLeft - (found ? 0 : 1) | |
const knownSecretWord = word.map(ch => selectedLettersSet.has(ch) ? ch : '_') | |
const status = lifeLeft <= 0 ? 'loss' : ( | |
knownSecretWord.some(ch => ch == '_') ? 'in-progress' : 'win' | |
) | |
return { | |
...state, | |
selectedLetters: Array.from(selectedLettersSet), | |
lifeLeft, | |
timeLeft: found ? MAX_TIME : state.timeLeft, | |
knownSecretWord, | |
status | |
} | |
} else if(action.type === 'tick') { | |
if (state.status === 'loss') { | |
return state | |
} | |
let timeLeft = (state.status === 'in-progress') ? state.timeLeft - 1 : state.timeLeft | |
let lifeLeft = state.lifeLeft | |
if (timeLeft === 0) { | |
lifeLeft-- | |
timeLeft = MAX_TIME | |
} | |
const status = state.lifeLeft <= 0 ? 'loss' : state.status | |
return { | |
...state, | |
timeLeft, | |
lifeLeft, | |
status | |
} | |
} | |
} | |
} | |
} |
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 { HangmanGame, MAX_LIFE, State } from './hangman' | |
import { Test } from '@nestjs/testing'; | |
describe('Hangman', () => { | |
let hangman: HangmanGame | |
beforeEach(() => { | |
hangman = new HangmanGame('hello', '1') | |
}) | |
describe('after create a new game', () => { | |
it('state.gameId = 1', () => { | |
hangman.currentState().subscribe(state=> { | |
expect(state.gameId).toBe('1') | |
}) | |
}) | |
it('state.status should be in-progress', () => { | |
hangman.currentState().subscribe(state=> { | |
expect(state.status).toBe('in-progress') | |
}) | |
}) | |
it('state.knownSecretWord size must equal to secretWordLength', () => { | |
hangman.currentState().subscribe(state=> { | |
expect(state.knownSecretWord.length).toBe(state.secretWordLength) | |
}) | |
}) | |
it('state.leftLeft should equal to MAX_LIFE', () => { | |
hangman.currentState().subscribe(state=> { | |
expect(state.lifeLeft).toBe(MAX_LIFE) | |
}) | |
}) | |
it('state.knonwSecretWord', () => { | |
hangman.currentState().subscribe(state=> { | |
expect(state.knownSecretWord).toEqual(['_', '_', '_', '_', '_']) | |
}) | |
}) | |
}) | |
describe('when feed letters', () => { | |
it('-> h', () => { | |
hangman.guess('h') | |
hangman.currentState().subscribe(s => { | |
expect(s.selectedLetters.length).toBe(1) | |
expect(s.lifeLeft).toBe(MAX_LIFE) | |
expect(s.knownSecretWord).toEqual(['h', '_', '_', '_', '_']) | |
}) | |
}) | |
it ('-> h a', () => { | |
hangman.guess('h') | |
hangman.guess('a') | |
hangman.currentState().subscribe(s => { | |
expect(s.selectedLetters.length).toBe(2) | |
expect(s.lifeLeft).toBe(MAX_LIFE - 1) | |
expect(s.knownSecretWord).toEqual(['h', '_', '_', '_', '_']) | |
}) | |
}) | |
it ('-> h a a', () => { | |
hangman.guess('h') | |
hangman.guess('a') | |
hangman.currentState().subscribe(s => { | |
expect(s.selectedLetters.length).toBe(2) | |
expect(s.lifeLeft).toBe(MAX_LIFE - 1) | |
expect(s.knownSecretWord).toEqual(['h', '_', '_', '_', '_']) | |
}) | |
}) | |
it ('-> h a l', () => { | |
hangman.guess('h') | |
hangman.guess('a') | |
hangman.guess('l') | |
hangman.currentState().subscribe(s => { | |
expect(s.selectedLetters.length).toBe(3) | |
expect(s.lifeLeft).toBe(MAX_LIFE - 1) | |
expect(s.knownSecretWord).toEqual(['h', '_', 'l', 'l', '_']) | |
expect(s.status).toBe('in-progress') | |
}) | |
}) | |
it ('-> h a l x y z b c g', () => { | |
hangman.guess('h') | |
hangman.guess('a') | |
hangman.guess('l') | |
hangman.guess('x') | |
hangman.guess('y') | |
hangman.guess('z') | |
hangman.guess('b') | |
hangman.guess('c') | |
hangman.guess('m') | |
hangman.currentState().subscribe(s => { | |
expect(s.selectedLetters.length).toBe(9) | |
expect(s.lifeLeft).toBe(MAX_LIFE - 7) | |
expect(s.knownSecretWord).toEqual(['h', '_', 'l', 'l', '_']) | |
expect(s.status).toBe('loss') | |
}) | |
}) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment