diff --git a/LICENSE b/LICENSE index 18ba6be..2081c63 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Marvin Blum +Copyright (c) 2020 Marvin Blum, Erik Schilling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/game/Game.test.ts b/src/game/Game.test.ts new file mode 100644 index 0000000..dcdd322 --- /dev/null +++ b/src/game/Game.test.ts @@ -0,0 +1,48 @@ +import "jest" +import {Game, SelectionMode} from "./Game" + +describe("GameState", () => { + const threeByThreeMatrix = [ + "00", "10", "20", + "01", "11", "21", + "02", "12", "22", + ] + + test("getting size works", () => { + expect((new Game([])).size).toEqual(0) + expect((new Game(["00"])).size).toEqual(1) + expect((new Game([ + "00", "01", + "10", "11", + ])).size).toEqual(2) + }); + + test("getting cell works", () => { + const game = new Game(threeByThreeMatrix); + expect(game.getCell(0, 2)).toEqual("02") + }); + + describe("picking", () => { + test("starts with free pick", () => { + const game = new Game([]); + expect(game.state).toEqual({selectionMode: SelectionMode.FreePick}) + }); + + test("picking cells works", () => { + const game = new Game(threeByThreeMatrix); + game.pick(0, 0); + expect(game.state).toEqual({selectionMode: SelectionMode.RowPick, column: 0}); + expect(() => game.pick(0, 2)).toThrow(); + game.pick(2, 0); + expect(game.state).toEqual({selectionMode: SelectionMode.ColumnPick, row: 2}); + }); + + test("picking outside of range fails", () => { + const game = new Game(threeByThreeMatrix); + expect(() => game.pick(-1, 0)).toThrow(); + expect(() => game.pick(0, -1)).toThrow(); + expect(() => game.pick(3, 0)).toThrow(); + expect(() => game.pick(0, 3)).toThrow(); + }); + }); +}); diff --git a/src/game/Game.ts b/src/game/Game.ts new file mode 100644 index 0000000..7c62e3b --- /dev/null +++ b/src/game/Game.ts @@ -0,0 +1,125 @@ +export enum EndState { + Won, + Lost, +} + +export enum SelectionMode { + FreePick, + RowPick, + ColumnPick, +} + +type FreeSelection = { + selectionMode: SelectionMode.FreePick, +} + +type RowSelection = { + selectionMode: SelectionMode.RowPick, + column: number; +} + +type ColumnSelection = { + selectionMode: SelectionMode.ColumnPick, + row: number; +} + +type SelectionState = FreeSelection | RowSelection | ColumnSelection; + +type State = SelectionState | EndState.Won | EndState.Lost; + +interface StateSpecificHandler { + Won(): T + Lost(): T + InProgress(selectionState: SelectionState): T +} + +function matchState(h: StateSpecificHandler): (a: State) => T { + return (a: State) => { + switch (a) { + case EndState.Won: + return h.Won(); + case EndState.Lost: + return h.Lost(); + default: + return h.InProgress(a); + } + } +} + +interface SelectionStateSpecificHandler { + Free(): T; + Row(row: number): T; + Column(column: number): T; +} + +function matchSelectionState(h: SelectionStateSpecificHandler): (a: SelectionState) => T { + return (a: SelectionState) => { + switch (a.selectionMode) { + case SelectionMode.FreePick: + return h.Free(); + case SelectionMode.RowPick: + return h.Row(a.column); + case SelectionMode.ColumnPick: + return h.Column(a.row); + } + } +} + +class IllegalMoveError extends Error { + constructor() { + super("Illegal move!") + } +} + +export class Game { + state: State = {selectionMode: SelectionMode.FreePick} + public readonly size: number + + constructor(public readonly matrix: string[]) { + this.size = Math.sqrt(matrix.length) + } + + getCell(row: number, column: number) { + return this.matrix[row + column * this.size] + } + + pick(row: number, column: number): void { + if (row < 0 || column < 0 || row >= this.size || column >= this.size) { + throw new IllegalMoveError() + } + + matchState({ + Won: () => {throw new IllegalMoveError()}, + Lost: () => {throw new IllegalMoveError()}, + InProgress: (selectionMode) => + matchSelectionState({ + Free: () => { + this.state = { + selectionMode: SelectionMode.RowPick, + column: column, + }; + }, + Column: (r) => { + if (r === row) { + this.state = { + selectionMode: SelectionMode.RowPick, + column: column, + } + } else { + throw new IllegalMoveError() + } + }, + Row: (c) => { + if (c === column) { + this.state = { + selectionMode: SelectionMode.ColumnPick, + row: row, + } + } else { + throw new IllegalMoveError() + } + }, + })(selectionMode), + })(this.state) + } +}