From 83c20a3e0f317a4c2e8addf73bd7b67701914c81 Mon Sep 17 00:00:00 2001 From: Low Jeng Lam Date: Sun, 19 May 2019 14:21:32 +0800 Subject: [PATCH] Winner and score computation --- src/Wiser.ts | 135 ++++++++++++++++-------------------- src/__tests__/Wiser.test.ts | 35 ++++++++++ src/typings.ts | 80 +++++++++++++++++++++ 3 files changed, 174 insertions(+), 76 deletions(-) create mode 100644 src/typings.ts diff --git a/src/Wiser.ts b/src/Wiser.ts index d34a12f..2693f5d 100644 --- a/src/Wiser.ts +++ b/src/Wiser.ts @@ -1,77 +1,9 @@ import deepDiff from 'deep-diff'; -import { CommandManager, ICommand, ICommandManager } from './CommandManager'; +import { CommandManager, ICommandManager } from './CommandManager'; import { BallStatus } from './Constant'; +import { IBall, IGameRules, IGameState, IRescueBall, nullifyType } from './typings'; import { isMissHittSequence, isNormalHitSequence, removeFirstTeamBall } from './utils'; -interface ISequence { - action: string; - nullified: boolean; -} - -interface IBallstate { - label: string; - status: BallStatus; - foul: number; - hits: string[]; - activeHits: string[]; - hitBy: string[]; -} - -interface ITeamState { - score: number; - pendingRescue: string[]; - balls: IBallstate[]; -} - -type TeamKeys = 'r' | 'w'; -interface IMatchInfo { - winner: string; - r: ITeamState; - w: ITeamState; - sequences: ISequence[]; -} - -interface IGameTeamInfo { - name: string; -} - -type MissHitType = 'MY' | 'WWSC'; -interface IGameRules { - name: string; - config: { - points: { - contesting: number; - firstLocked: number; - secondLocked: number; - eliminated: number; - }; - missHitType: MissHitType; - }; -} - -interface IGameInfo { - rules: IGameRules; - r: IGameTeamInfo; - w: IGameTeamInfo; -} - -interface IBall { - team: TeamKeys; - idx: number; -} - -type RescueType = 'normal' | 'missHit'; -interface IRescueBall { - type: RescueType; - ball: IBall; -} - -type nullifyType = 'rescue' | 'eliminate' | 'rescueMissHit'; - -export interface IGameState { - info: IGameInfo; - match: IMatchInfo; -} const template: IGameRules = { name: 'Malaysia', @@ -99,12 +31,24 @@ const gameState: IGameState = { match: { winner: '', r: { - score: 0, + score: { + point: 0, + contesting: 0, + firstLocked: 0, + secondLocked: 0, + eliminated: 0, + }, pendingRescue: [], balls: [], }, w: { - score: 0, + score: { + point: 0, + contesting: 0, + firstLocked: 0, + secondLocked: 0, + eliminated: 0, + }, pendingRescue: [], balls: [], }, @@ -157,6 +101,9 @@ export class Wiser { this.rescue(rescue as IRescueBall); } + // Compute score and check for winner + this.computeScore(); + // Insert the command to command manager const command = { command: 'r1r2', @@ -186,14 +133,52 @@ export class Wiser { public reset() { this.state.match.sequences = []; - this.state.match.r.score = 0; + this.state.match.r.score = { + point: 0, + contesting: 0, + firstLocked: 0, + secondLocked: 0, + eliminated: 0, + }; this.state.match.r.pendingRescue = []; - this.state.match.w.score = 0; + this.state.match.w.score = { + point: 0, + contesting: 0, + firstLocked: 0, + secondLocked: 0, + eliminated: 0, + }; this.state.match.w.pendingRescue = []; + this.state.match.winner = ''; this.initBallState(this.state.match.r.balls.length); } + private computeScore() { + const team = ['r', 'w']; + const points = this.state.info.rules.config.points; + + team.forEach(t => { + const teamScore = this.state.match[t].score; + const teamBalls = this.state.match[t].balls; + teamScore.contesting = teamBalls.filter(b => b.status === BallStatus.Contesting).length; + teamScore.firstLocked = teamBalls.filter(b => b.status === BallStatus.FirstLocked).length; + teamScore.secondLocked = teamBalls.filter(b => b.status === BallStatus.SecondLocked).length; + teamScore.eliminated = teamBalls.filter(b => b.status === BallStatus.Eliminated).length; + teamScore.point = + teamScore.contesting * points.contesting + + teamScore.firstLocked * points.firstLocked + + teamScore.secondLocked * points.secondLocked + + teamScore.eliminated * points.eliminated; + }); + + if (this.state.match.r.score.contesting === 0) { + this.state.match.winner = 'w'; + } else if (this.state.match.w.score.contesting === 0) { + this.state.match.winner = 'r'; + } + } + private initBallState(numOfBalls: number) { this.state.match.r.balls = Array(numOfBalls) .fill(0) @@ -396,8 +381,6 @@ export class Wiser { } } -// const cm = new CommandManager(); - // const wiser = new Wiser(); // wiser.state.info.rules.config.missHitType = 'WWSC'; diff --git a/src/__tests__/Wiser.test.ts b/src/__tests__/Wiser.test.ts index 3ed031f..96eca61 100644 --- a/src/__tests__/Wiser.test.ts +++ b/src/__tests__/Wiser.test.ts @@ -490,6 +490,41 @@ describe('Sequence', () => { }); }); +describe('Score Info', () => { + test('Compute Score', () => { + wiser.process('r1w3'); + + expect(wiser.state.match.r.score.point).toBe(35); + expect(wiser.state.match.w.score.contesting).toBe(6); + expect(wiser.state.match.w.score.firstLocked).toBe(1); + expect(wiser.state.match.w.score.point).toBe(32); + + wiser.process('w2r1'); + expect(wiser.state.match.w.score.point).toBe(35); + expect(wiser.state.match.r.score.contesting).toBe(6); + expect(wiser.state.match.r.score.firstLocked).toBe(1); + expect(wiser.state.match.r.score.point).toBe(32); + }); + + test('Winner', () => { + wiser.process('r1w1'); + wiser.process('r1w2'); + wiser.process('r1w3'); + wiser.process('r1w4'); + wiser.process('r1w5'); + wiser.process('r1w6'); + expect(wiser.state.match.winner).toBe(''); + wiser.process('r1w7'); + expect(wiser.state.match.winner).toBe('r'); + expect(wiser.state.match.w.score.contesting).toBe(0); + + // Throw error if the match already has winner + expect(() => { + wiser.process('r1w7'); + }).toThrow('The match already ended'); + }); +}); + describe('Undo/Redo', () => { test('Basic Undo/Redo', () => { const s0 = JSON.parse(JSON.stringify(wiser.state.match)); diff --git a/src/typings.ts b/src/typings.ts new file mode 100644 index 0000000..6071498 --- /dev/null +++ b/src/typings.ts @@ -0,0 +1,80 @@ +import { BallStatus } from './Constant'; + +interface ISequence { + action: string; + nullified: boolean; +} + +export interface IBallstate { + label: string; + status: BallStatus; + foul: number; + hits: string[]; + activeHits: string[]; + hitBy: string[]; +} + +interface IScoreInfo { + point: number; + contesting: number; + firstLocked: number; + secondLocked: number; + eliminated: number; +} + +interface ITeamState { + score: IScoreInfo; + pendingRescue: string[]; + balls: IBallstate[]; +} + +type TeamKeys = 'r' | 'w'; +interface IMatchInfo { + winner: string; + r: ITeamState; + w: ITeamState; + sequences: ISequence[]; +} + +interface IGameTeamInfo { + name: string; +} + +type MissHitType = 'MY' | 'WWSC'; + +export interface IGameRules { + name: string; + config: { + points: { + contesting: number; + firstLocked: number; + secondLocked: number; + eliminated: number; + }; + missHitType: MissHitType; + }; +} + +interface IGameInfo { + rules: IGameRules; + r: IGameTeamInfo; + w: IGameTeamInfo; +} + +export interface IBall { + team: TeamKeys; + idx: number; +} + +type RescueType = 'normal' | 'missHit'; +export interface IRescueBall { + type: RescueType; + ball: IBall; +} + +export type nullifyType = 'rescue' | 'eliminate' | 'rescueMissHit'; + +export interface IGameState { + info: IGameInfo; + match: IMatchInfo; +}