Merge mancala.js repo
This commit is contained in:
commit
58ad1561ac
2
mancala.js/.eslintignore
Normal file
2
mancala.js/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
12
mancala.js/.eslintrc
Normal file
12
mancala.js/.eslintrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
2
mancala.js/.gitignore
vendored
Normal file
2
mancala.js/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
6
mancala.js/.prettierrc
Normal file
6
mancala.js/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80
|
||||||
|
}
|
||||||
3
mancala.js/README.md
Normal file
3
mancala.js/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# mancala.js
|
||||||
|
|
||||||
|
mancala.js is a javascript library for mancala game.
|
||||||
6
mancala.js/jest.config.js
Normal file
6
mancala.js/jest.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
transform: {'^.+\\.ts?$': 'ts-jest'},
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testRegex: '/tests/.*\\.(test|spec)?\\.(ts|tsx)$',
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
|
||||||
|
};
|
||||||
9294
mancala.js/package-lock.json
generated
Normal file
9294
mancala.js/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
mancala.js/package.json
Normal file
27
mancala.js/package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "mancala.js",
|
||||||
|
"version": "0.0.2-beta.3",
|
||||||
|
"description": "Mancala game library",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "yarn tsc -w",
|
||||||
|
"test": "yarn jest",
|
||||||
|
"lint": "eslint . --ext .ts",
|
||||||
|
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||||
|
"build": "tsc",
|
||||||
|
"prepublish": "tsc"
|
||||||
|
},
|
||||||
|
"author": "Halit Aksoy",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^27.4.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"jest": "^27.5.1",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"ts-jest": "^27.1.4",
|
||||||
|
"typescript": "^4.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
mancala.js/src/common/CommonBoard.ts
Normal file
10
mancala.js/src/common/CommonBoard.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Board } from '../core/Board';
|
||||||
|
|
||||||
|
const pitCountByUser = 6;
|
||||||
|
const initialStoneCountInPit = 4;
|
||||||
|
|
||||||
|
export class CommonBoard extends Board {
|
||||||
|
constructor() {
|
||||||
|
super(pitCountByUser, initialStoneCountInPit);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
mancala.js/src/common/CommonMancalaGame.ts
Normal file
25
mancala.js/src/common/CommonMancalaGame.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { MancalaGame } from '../core/MancalaGame';
|
||||||
|
import { CommonBoard } from './CommonBoard';
|
||||||
|
import { GRClearBoardAtEnd } from './game_rules/GRClearBoardAtEnd';
|
||||||
|
import { GRDoubleStoneInPit } from './game_rules/GRDoubleStoneInPit';
|
||||||
|
import { GRLastStoneInBank } from './game_rules/GRLastStoneInBank';
|
||||||
|
import { GRLastStoneInEmptyPit } from './game_rules/GRLastStoneInEmptyPit';
|
||||||
|
|
||||||
|
export class CommonMancalaGame extends MancalaGame {
|
||||||
|
constructor(id: string, player1Id: string, player2Id: string) {
|
||||||
|
super(
|
||||||
|
id,
|
||||||
|
new CommonBoard(),
|
||||||
|
player1Id,
|
||||||
|
player2Id,
|
||||||
|
player1Id,
|
||||||
|
[
|
||||||
|
new GRLastStoneInEmptyPit(),
|
||||||
|
new GRDoubleStoneInPit(),
|
||||||
|
new GRLastStoneInBank(),
|
||||||
|
new GRClearBoardAtEnd()
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
mancala.js/src/common/game_rules/GRClearBoardAtEnd.ts
Normal file
52
mancala.js/src/common/game_rules/GRClearBoardAtEnd.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Board } from '../../core/Board';
|
||||||
|
import { GameRule } from '../../core/GameRule';
|
||||||
|
import { GameStep } from '../../core/HistoryItem';
|
||||||
|
import { MancalaGame } from '../../core/MancalaGame';
|
||||||
|
|
||||||
|
export const GAME_STEP_BOARD_CLEARED = 'GAME_STEP_BOARD_CLEARED';
|
||||||
|
|
||||||
|
export type ClearBoardAtEndData = { pitIndexesThatHasStone: number[] };
|
||||||
|
|
||||||
|
export class GRClearBoardAtEnd implements GameRule {
|
||||||
|
onGameMoveStart(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMove(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMoveEnd(game: MancalaGame, index: number): void {
|
||||||
|
if (game.getPlayer1StoneCountInPits() === 0) {
|
||||||
|
const clearBoardAtEndData = {
|
||||||
|
pitIndexesThatHasStone: this.getPitIndexesThatHasStone(game.board)
|
||||||
|
};
|
||||||
|
game.board.player1Bank.stoneCount += game.getPlayer2StoneCountInPits();
|
||||||
|
game.board.clearPlayer2Pits();
|
||||||
|
this.addGameStep(game, index, clearBoardAtEndData);
|
||||||
|
} else if (game.getPlayer2StoneCountInPits() === 0) {
|
||||||
|
const clearBoardAtEndData = {
|
||||||
|
pitIndexesThatHasStone: this.getPitIndexesThatHasStone(game.board)
|
||||||
|
};
|
||||||
|
game.board.player2Bank.stoneCount += game.getPlayer1StoneCountInPits();
|
||||||
|
game.board.clearPlayer1Pits();
|
||||||
|
this.addGameStep(game, index, clearBoardAtEndData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPitIndexesThatHasStone(board: Board): number[] {
|
||||||
|
let index = 0;
|
||||||
|
const indexList = [];
|
||||||
|
for (const stoneCount of board.getStoneArray()) {
|
||||||
|
if (stoneCount > 0 && board.checkPitTypeIsNormalPitByIndex(index)) {
|
||||||
|
indexList.push(index);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return indexList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addGameStep(
|
||||||
|
game: MancalaGame,
|
||||||
|
index: number,
|
||||||
|
clearBoardAtEndData: ClearBoardAtEndData
|
||||||
|
) {
|
||||||
|
game.addGameStep(
|
||||||
|
new GameStep(index, GAME_STEP_BOARD_CLEARED, clearBoardAtEndData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
mancala.js/src/common/game_rules/GRDoubleStoneInPit.ts
Normal file
44
mancala.js/src/common/game_rules/GRDoubleStoneInPit.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { GameRule } from '../../core/GameRule';
|
||||||
|
import { GameStep } from '../../core/HistoryItem';
|
||||||
|
import { MancalaGame } from '../../core/MancalaGame';
|
||||||
|
|
||||||
|
export const GAME_STEP_DOUBLE_STONE_IN_PIT = 'GAME_STEP_DOUBLE_STONE_IN_PIT';
|
||||||
|
|
||||||
|
export type DoubleStoneInPitData = { pitIndex: number; bankIndex: number };
|
||||||
|
|
||||||
|
export class GRDoubleStoneInPit implements GameRule {
|
||||||
|
onGameMoveStart(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMove(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMoveEnd(game: MancalaGame, index: number): void {
|
||||||
|
index = game.board.getPitIndexCircularly(index);
|
||||||
|
const pit = game.board.pits[index];
|
||||||
|
const pitType = game.board.getPitTypeByIndex(index);
|
||||||
|
if (pit.stoneCount % 2 === 0) {
|
||||||
|
if (pitType === 'player1Pit' && game.isTurnPlayer2()) {
|
||||||
|
const pitIndex = index;
|
||||||
|
const bankIndex = game.board.player2BankIndex();
|
||||||
|
game.board.player2Bank.stoneCount +=
|
||||||
|
game.board.getPitCircularly(pitIndex).stoneCount;
|
||||||
|
game.board.getPitCircularly(pitIndex).stoneCount = 0;
|
||||||
|
this.addGameStep(game, index, { pitIndex, bankIndex });
|
||||||
|
} else if (pitType === 'player2Pit' && game.isTurnPlayer1()) {
|
||||||
|
const pitIndex = index;
|
||||||
|
const bankIndex = game.board.player1BankIndex();
|
||||||
|
game.board.player1Bank.stoneCount +=
|
||||||
|
game.board.getPitCircularly(pitIndex).stoneCount;
|
||||||
|
game.board.getPitCircularly(pitIndex).stoneCount = 0;
|
||||||
|
this.addGameStep(game, index, { pitIndex, bankIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addGameStep(
|
||||||
|
game: MancalaGame,
|
||||||
|
index: number,
|
||||||
|
doubleStoneInPitData: DoubleStoneInPitData
|
||||||
|
) {
|
||||||
|
game.addGameStep(
|
||||||
|
new GameStep(index, GAME_STEP_DOUBLE_STONE_IN_PIT, doubleStoneInPitData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
mancala.js/src/common/game_rules/GRLastStoneInBank.ts
Normal file
22
mancala.js/src/common/game_rules/GRLastStoneInBank.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { GameRule } from '../../core/GameRule';
|
||||||
|
import { GameStep } from '../../core/HistoryItem';
|
||||||
|
import { MancalaGame } from '../../core/MancalaGame';
|
||||||
|
|
||||||
|
export const GAME_STEP_LAST_STONE_IN_BANK = 'GAME_STEP_LAST_STONE_IN_BANK';
|
||||||
|
|
||||||
|
export class GRLastStoneInBank implements GameRule {
|
||||||
|
onGameMoveStart(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMove(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMoveEnd(game: MancalaGame, index: number): void {
|
||||||
|
index = game.board.getPitIndexCircularly(index);
|
||||||
|
const pitType = game.board.getPitTypeByIndex(index);
|
||||||
|
if (
|
||||||
|
(pitType === 'player1Bank' && game.isTurnPlayer1()) ||
|
||||||
|
(pitType === 'player2Bank' && game.isTurnPlayer2())
|
||||||
|
) {
|
||||||
|
game.addGameStep(new GameStep(index, GAME_STEP_LAST_STONE_IN_BANK));
|
||||||
|
} else {
|
||||||
|
game.changePlayerTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
mancala.js/src/common/game_rules/GRLastStoneInEmptyPit.ts
Normal file
53
mancala.js/src/common/game_rules/GRLastStoneInEmptyPit.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { GameRule } from '../../core/GameRule';
|
||||||
|
import { GameStep } from '../../core/HistoryItem';
|
||||||
|
import { MancalaGame } from '../../core/MancalaGame';
|
||||||
|
|
||||||
|
export const GAME_STEP_LAST_STONE_IN_EMPTY_PIT =
|
||||||
|
'GAME_STEP_LAST_STONE_IN_EMPTY_PIT';
|
||||||
|
|
||||||
|
export type LastStoneInEmptyPitData = { oppositeIndex: number };
|
||||||
|
|
||||||
|
export class GRLastStoneInEmptyPit implements GameRule {
|
||||||
|
onGameMoveStart(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMove(game: MancalaGame, index: number): void {}
|
||||||
|
onGameMoveEnd(game: MancalaGame, index: number): void {
|
||||||
|
index = game.board.getPitIndexCircularly(index);
|
||||||
|
const pit = game.board.pits[index];
|
||||||
|
const pitType = game.board.getPitTypeByIndex(index);
|
||||||
|
if (pit.stoneCount === 1) {
|
||||||
|
if (pitType === 'player1Pit' && game.isTurnPlayer1()) {
|
||||||
|
const oppositeIndex = game.board.getOppositePitIndex(index);
|
||||||
|
const oppositePit = game.board.pits[oppositeIndex];
|
||||||
|
if (oppositePit.stoneCount > 0) {
|
||||||
|
const player1BankIndex =
|
||||||
|
game.board.pits[game.board.player1BankIndex()];
|
||||||
|
player1BankIndex.stoneCount += 1 + oppositePit.stoneCount;
|
||||||
|
oppositePit.stoneCount = 0;
|
||||||
|
pit.stoneCount = 0;
|
||||||
|
this.addGameStep(game, index, { oppositeIndex });
|
||||||
|
}
|
||||||
|
} else if (pitType === 'player2Pit' && game.isTurnPlayer2()) {
|
||||||
|
const oppositeIndex = game.board.getOppositePitIndex(index);
|
||||||
|
const oppositePit = game.board.pits[oppositeIndex];
|
||||||
|
if (oppositePit.stoneCount > 0) {
|
||||||
|
const player2BankIndex =
|
||||||
|
game.board.pits[game.board.player2BankIndex()];
|
||||||
|
player2BankIndex.stoneCount += 1 + oppositePit.stoneCount;
|
||||||
|
oppositePit.stoneCount = 0;
|
||||||
|
pit.stoneCount = 0;
|
||||||
|
this.addGameStep(game, index, { oppositeIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addGameStep(
|
||||||
|
game: MancalaGame,
|
||||||
|
index: number,
|
||||||
|
data: LastStoneInEmptyPitData
|
||||||
|
) {
|
||||||
|
game.addGameStep(
|
||||||
|
new GameStep(index, GAME_STEP_LAST_STONE_IN_EMPTY_PIT, data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
216
mancala.js/src/core/Board.ts
Normal file
216
mancala.js/src/core/Board.ts
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { Bank, Pit } from './Pit';
|
||||||
|
|
||||||
|
const bankCount = 2;
|
||||||
|
|
||||||
|
export type PitType =
|
||||||
|
| 'player1Pit'
|
||||||
|
| 'player1Bank'
|
||||||
|
| 'player2Pit'
|
||||||
|
| 'player2Bank';
|
||||||
|
|
||||||
|
export class Board {
|
||||||
|
pits: Pit[];
|
||||||
|
playerPitCount: number;
|
||||||
|
initialStoneCountInPits: number;
|
||||||
|
|
||||||
|
onGameMoveStart: (index: number) => void = () => {};
|
||||||
|
onGameMove: (index: number) => void = () => {};
|
||||||
|
onGameMoveEnd: (index: number) => void = () => {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
playerPitCount: number,
|
||||||
|
initialStoneCountInPits: number,
|
||||||
|
pits: Pit[] | null = null
|
||||||
|
) {
|
||||||
|
this.playerPitCount = playerPitCount;
|
||||||
|
this.initialStoneCountInPits = initialStoneCountInPits;
|
||||||
|
this.pits = pits
|
||||||
|
? pits
|
||||||
|
: this.createPits(playerPitCount, initialStoneCountInPits);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPits(playerPitCount: number, initialStoneCountInPits: number): Pit[] {
|
||||||
|
const totalPitCount = playerPitCount + playerPitCount + bankCount;
|
||||||
|
const pitArray = new Array<Pit>(totalPitCount);
|
||||||
|
for (let index = 0; index < this.totalPitCount(); index++) {
|
||||||
|
const pitType = this.getPitTypeByIndex(index);
|
||||||
|
if (pitType === 'player1Pit' || pitType === 'player2Pit') {
|
||||||
|
pitArray[index] = new Pit(index, initialStoneCountInPits);
|
||||||
|
} else if (pitType === 'player1Bank' || pitType === 'player2Bank') {
|
||||||
|
pitArray[index] = new Bank(index, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public player1PitStartIndex = () => 0;
|
||||||
|
public player1PitStopIndex = () =>
|
||||||
|
this.player1PitStartIndex() + this.playerPitCount - 1;
|
||||||
|
public player1BankIndex = () => this.player1PitStopIndex() + 1;
|
||||||
|
public player2PitStartIndex = () => this.player1BankIndex() + 1;
|
||||||
|
public player2PitStopIndex = () =>
|
||||||
|
this.player2PitStartIndex() + this.playerPitCount - 1;
|
||||||
|
public player2BankIndex = () => this.player2PitStopIndex() + 1;
|
||||||
|
public totalPitCount = () => this.player2BankIndex() + 1;
|
||||||
|
|
||||||
|
public checkIndex(index: number): boolean {
|
||||||
|
return (
|
||||||
|
index >= this.player1PitStartIndex() && index <= this.player2BankIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIndeAndMaybeThrowError(index: number): void {
|
||||||
|
if (!this.checkIndex(index)) {
|
||||||
|
throw new Error(
|
||||||
|
`IndexOutOfRange => index : ${index} [start, stop] = [${this.player1PitStartIndex()}, ${this.player2BankIndex()}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPitTypeByIndex(index: number): PitType {
|
||||||
|
this.checkIndeAndMaybeThrowError(index);
|
||||||
|
if (
|
||||||
|
index >= this.player1PitStartIndex() &&
|
||||||
|
index <= this.player1PitStopIndex()
|
||||||
|
) {
|
||||||
|
return 'player1Pit';
|
||||||
|
} else if (index === this.player1BankIndex()) {
|
||||||
|
return 'player1Bank';
|
||||||
|
} else if (index <= this.player2PitStopIndex()) {
|
||||||
|
return 'player2Pit';
|
||||||
|
} else {
|
||||||
|
return 'player2Bank';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkPitTypeIsNormalPitByIndex(index: number): boolean {
|
||||||
|
this.checkIndeAndMaybeThrowError(index);
|
||||||
|
const pitType = this.getPitTypeByIndex(index);
|
||||||
|
return pitType === 'player1Pit' || pitType === 'player2Pit';
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkPitTypeIsBankByIndex(index: number): boolean {
|
||||||
|
this.checkIndeAndMaybeThrowError(index);
|
||||||
|
const pitType = this.getPitTypeByIndex(index);
|
||||||
|
return pitType === 'player1Bank' || pitType === 'player2Bank';
|
||||||
|
}
|
||||||
|
|
||||||
|
public move(index: number) {
|
||||||
|
this.checkIndeAndMaybeThrowError(index);
|
||||||
|
const pitType = this.getPitTypeByIndex(index);
|
||||||
|
if (pitType === 'player1Bank' || pitType === 'player2Bank') {
|
||||||
|
throw new Error(`InCorrectPit => index : ${index} pitType = ${pitType}`);
|
||||||
|
}
|
||||||
|
const pit = this.pits[index];
|
||||||
|
if (pit.stoneCount <= 0) {
|
||||||
|
throw new Error(
|
||||||
|
`StoneNotFound => index : ${index} stoneCount = ${pit.stoneCount}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const stepCount = pit.stoneCount;
|
||||||
|
this.pits[index].stoneCount = 0;
|
||||||
|
const startIndex = stepCount === 1 ? index + 1 : index;
|
||||||
|
this.moveInternal(startIndex, stepCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveInternal(startIndex: number, stepCount: number) {
|
||||||
|
this.fireOnGameMoveStart(startIndex);
|
||||||
|
for (let i = startIndex; i < startIndex + stepCount; i++) {
|
||||||
|
this.onGameMoveStep(i);
|
||||||
|
}
|
||||||
|
const stopIndex = startIndex + stepCount - 1;
|
||||||
|
this.fireOnGameMoveEnd(stopIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onGameMoveStep(index: number) {
|
||||||
|
const pit = this.pits[this.getPitIndexCircularly(index)];
|
||||||
|
pit.increaseStone();
|
||||||
|
this.fireOnGameMove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextPitCircularly(currentPitIndex: number) {
|
||||||
|
return this.pits[this.getNextPitIndexCircularly(currentPitIndex)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextPitIndexCircularly(currentPitIndex: number) {
|
||||||
|
this.checkIndeAndMaybeThrowError(currentPitIndex);
|
||||||
|
return this.getPitIndexCircularly(currentPitIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPitCircularly(currentPitIndex: number) {
|
||||||
|
return this.pits[this.getPitIndexCircularly(currentPitIndex)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPitIndexCircularly(currentPitIndex: number) {
|
||||||
|
return currentPitIndex % this.totalPitCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
fireOnGameMoveStart(index: number): void {
|
||||||
|
this.onGameMoveStart(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
fireOnGameMove(index: number): void {
|
||||||
|
this.onGameMove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
fireOnGameMoveEnd(index: number): void {
|
||||||
|
this.onGameMoveEnd(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStoneArray(): number[] {
|
||||||
|
return [...this.pits.map((pit) => pit.stoneCount)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get player1Pits(): Pit[] {
|
||||||
|
return this.pits.slice(
|
||||||
|
this.player1PitStartIndex(),
|
||||||
|
this.player1BankIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get player2Pits(): Pit[] {
|
||||||
|
return this.pits.slice(
|
||||||
|
this.player2PitStartIndex(),
|
||||||
|
this.player2BankIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get player1Bank(): Bank {
|
||||||
|
return this.pits[this.player1BankIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
get player2Bank(): Bank {
|
||||||
|
return this.pits[this.player2BankIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOppositePitIndex(index: number) {
|
||||||
|
if (index > this.player1BankIndex()) {
|
||||||
|
return this.player2PitStopIndex() - index;
|
||||||
|
} else {
|
||||||
|
return this.player1BankIndex() + this.playerPitCount - index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.player1Bank.stoneCount = 0;
|
||||||
|
this.player2Bank.stoneCount = 0;
|
||||||
|
this.clearPlayer1Pits();
|
||||||
|
this.clearPlayer2Pits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearPlayer1Pits() {
|
||||||
|
this.fillPlayer1Pits(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearPlayer2Pits() {
|
||||||
|
this.fillPlayer2Pits(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fillPlayer1Pits(stoneCount: number) {
|
||||||
|
this.player1Pits.forEach((pit) => (pit.stoneCount = stoneCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public fillPlayer2Pits(stoneCount: number) {
|
||||||
|
this.player2Pits.forEach((pit) => (pit.stoneCount = stoneCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
7
mancala.js/src/core/GameRule.ts
Normal file
7
mancala.js/src/core/GameRule.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MancalaGame } from './MancalaGame';
|
||||||
|
|
||||||
|
export interface GameRule {
|
||||||
|
onGameMoveStart(game: MancalaGame, index: number): void;
|
||||||
|
onGameMove(game: MancalaGame, index: number): void;
|
||||||
|
onGameMoveEnd(game: MancalaGame, index: number): void;
|
||||||
|
}
|
||||||
34
mancala.js/src/core/HistoryItem.ts
Normal file
34
mancala.js/src/core/HistoryItem.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export class HistoryItem {
|
||||||
|
boardSnapshot: number[];
|
||||||
|
gameSteps: GameStep[];
|
||||||
|
constructor(boardSnapshot: number[], gameSteps: GameStep[]) {
|
||||||
|
this.boardSnapshot = boardSnapshot;
|
||||||
|
this.gameSteps = gameSteps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MoveHistoryItem extends HistoryItem {
|
||||||
|
playerId: string;
|
||||||
|
moveIndex: number;
|
||||||
|
constructor(
|
||||||
|
playerId: string,
|
||||||
|
moveIndex: number,
|
||||||
|
boardSnapshot: number[],
|
||||||
|
gameSteps: GameStep[]
|
||||||
|
) {
|
||||||
|
super(boardSnapshot, gameSteps);
|
||||||
|
this.playerId = playerId;
|
||||||
|
this.moveIndex = moveIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GameStep {
|
||||||
|
index: number;
|
||||||
|
type: string;
|
||||||
|
data: any | null;
|
||||||
|
constructor(index: number, type: string, data: any = null) {
|
||||||
|
this.index = index;
|
||||||
|
this.type = type;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
239
mancala.js/src/core/MancalaGame.ts
Normal file
239
mancala.js/src/core/MancalaGame.ts
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import { Board, PitType } from './Board';
|
||||||
|
import { GameRule } from './GameRule';
|
||||||
|
import { HistoryItem, MoveHistoryItem, GameStep } from './HistoryItem';
|
||||||
|
|
||||||
|
export type GameState = 'initial' | 'playing' | 'ended';
|
||||||
|
|
||||||
|
export const GAME_STEP_GAME_MOVE = 'GAME_STEP_GAME_MOVE';
|
||||||
|
|
||||||
|
export class MancalaGame {
|
||||||
|
id: string;
|
||||||
|
board: Board;
|
||||||
|
player1Id: string;
|
||||||
|
player2Id: string;
|
||||||
|
turnPlayerId: string;
|
||||||
|
state: GameState;
|
||||||
|
gameRules: GameRule[];
|
||||||
|
history: HistoryItem[];
|
||||||
|
currentHistoryItem: HistoryItem | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
board: Board,
|
||||||
|
player1Id: string,
|
||||||
|
player2Id: string,
|
||||||
|
turnPlayerId: string,
|
||||||
|
gameRules: GameRule[],
|
||||||
|
history: HistoryItem[],
|
||||||
|
state: GameState = 'initial'
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.board = board;
|
||||||
|
this.player1Id = player1Id;
|
||||||
|
this.player2Id = player2Id;
|
||||||
|
this.turnPlayerId = turnPlayerId;
|
||||||
|
this.state = state;
|
||||||
|
this.gameRules = gameRules;
|
||||||
|
this.history = history;
|
||||||
|
this.listenBoardMoveEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
listenBoardMoveEvents() {
|
||||||
|
this.board.onGameMoveStart = (index: number) => {
|
||||||
|
this.gameRules.forEach((gameRule) => {
|
||||||
|
gameRule.onGameMoveStart(this, index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.board.onGameMove = (index: number) => {
|
||||||
|
this.addGameStep(new GameStep(index, GAME_STEP_GAME_MOVE));
|
||||||
|
this.gameRules.forEach((gameRule) => {
|
||||||
|
gameRule.onGameMove(this, index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.board.onGameMoveEnd = (index: number) => {
|
||||||
|
this.gameRules.forEach((gameRule) => {
|
||||||
|
gameRule.onGameMoveEnd(this, index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
changePlayerTurn() {
|
||||||
|
if (this.turnPlayerId === this.player1Id) {
|
||||||
|
this.turnPlayerId = this.player2Id;
|
||||||
|
} else {
|
||||||
|
this.turnPlayerId = this.player1Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isTurnPlayer1() {
|
||||||
|
return this.player1Id === this.turnPlayerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTurnPlayer2() {
|
||||||
|
return this.player2Id === this.turnPlayerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayerIdByPitType(pitType: PitType): string {
|
||||||
|
if (pitType === 'player1Pit' || pitType === 'player1Bank') {
|
||||||
|
return this.player1Id;
|
||||||
|
} else if (pitType === 'player2Pit' || pitType === 'player2Bank') {
|
||||||
|
return this.player2Id;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown pit type : ' + pitType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayerIdByIndex(index: number): string {
|
||||||
|
const pitType = this.board.getPitTypeByIndex(index);
|
||||||
|
return this.getPlayerIdByPitType(pitType);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIsPlayerTurnByIndex(index: number): boolean {
|
||||||
|
const playerId = this.getPlayerIdByIndex(index);
|
||||||
|
return this.checkIsPlayerTurn(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBoardIndexByPlayerId(playerId: string, pitIndex: number) {
|
||||||
|
if (this.player1Id === playerId) {
|
||||||
|
return this.board.player1PitStartIndex() + pitIndex;
|
||||||
|
} else if (this.player2Id === playerId) {
|
||||||
|
return this.board.player2PitStartIndex() + pitIndex;
|
||||||
|
} else {
|
||||||
|
return -1; // throwing an error might be better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkIsPlayerTurn(playerId: string) {
|
||||||
|
return playerId === this.turnPlayerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkPitIndexForPlayerId(playerId: string, pitIndex: number) {
|
||||||
|
const foundPlayerId = this.getPlayerIdByIndex(
|
||||||
|
this.getBoardIndexByPlayerId(playerId, pitIndex)
|
||||||
|
);
|
||||||
|
return playerId === foundPlayerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkIsPitIndexBank(playerId: string, pitIndex: number) {
|
||||||
|
const pitType = this.board.getPitTypeByIndex(
|
||||||
|
this.getBoardIndexByPlayerId(playerId, pitIndex)
|
||||||
|
);
|
||||||
|
return pitType === 'player1Bank' || pitType === 'player2Bank';
|
||||||
|
}
|
||||||
|
|
||||||
|
public canPlayerMove(playerId: string, pitIndex: number) {
|
||||||
|
const isPitIndexCorrect = this.checkPitIndexForPlayerId(playerId, pitIndex);
|
||||||
|
const isPitIndexBank = this.checkIsPitIndexBank(playerId, pitIndex);
|
||||||
|
const isPlayerTurn = this.checkIsPlayerTurn(playerId);
|
||||||
|
return isPitIndexCorrect && !isPitIndexBank && isPlayerTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public moveByPlayerPit(playerId: string, pitIndex: number) {
|
||||||
|
if (this.state === 'ended') return;
|
||||||
|
if (this.state === 'initial') {
|
||||||
|
this.state = 'playing';
|
||||||
|
}
|
||||||
|
if (this.canPlayerMove(playerId, pitIndex)) {
|
||||||
|
const moveIndex = this.getBoardIndexByPlayerId(playerId, pitIndex);
|
||||||
|
this.currentHistoryItem = new MoveHistoryItem(
|
||||||
|
playerId,
|
||||||
|
moveIndex,
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
this.board.move(moveIndex);
|
||||||
|
this.currentHistoryItem.boardSnapshot = this.board.getStoneArray();
|
||||||
|
this.history.push(this.currentHistoryItem);
|
||||||
|
if (this.checkGameIsEnded()) {
|
||||||
|
this.state = 'ended';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isPitIndexCorrect = this.checkPitIndexForPlayerId(
|
||||||
|
playerId,
|
||||||
|
pitIndex
|
||||||
|
);
|
||||||
|
const isPlayerTurn = this.checkIsPlayerTurn(playerId);
|
||||||
|
throw new Error(
|
||||||
|
`Player cannot move reason : isPitIndexCorrect = ${isPitIndexCorrect} isPlayerTurn = ${isPlayerTurn}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayer1StoneCountInPits(): number {
|
||||||
|
return this.getPlayerStoneCountInPits(
|
||||||
|
this.board.player1PitStartIndex(),
|
||||||
|
this.board.player1BankIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayer2StoneCountInPits(): number {
|
||||||
|
return this.getPlayerStoneCountInPits(
|
||||||
|
this.board.player2PitStartIndex(),
|
||||||
|
this.board.player2BankIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayerStoneCountInPits(
|
||||||
|
playerPitStartIndex: number,
|
||||||
|
playerBankIndex: number
|
||||||
|
): number {
|
||||||
|
const player2Pits = this.board.pits.slice(
|
||||||
|
playerPitStartIndex,
|
||||||
|
playerBankIndex
|
||||||
|
);
|
||||||
|
return player2Pits
|
||||||
|
.map((pit) => pit.stoneCount)
|
||||||
|
.reduce(
|
||||||
|
(previousStoneCount, stoneCount) => previousStoneCount + stoneCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkGameIsEnded(): boolean {
|
||||||
|
if (
|
||||||
|
this.getPlayer1StoneCountInPits() === 0 ||
|
||||||
|
this.getPlayer2StoneCountInPits() === 0
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWonPlayerId(): string | undefined {
|
||||||
|
const player1StoneCount = this.board.player1Bank.stoneCount;
|
||||||
|
const player2StoneCount = this.board.player2Bank.stoneCount;
|
||||||
|
if (this.checkGameIsEnded()) {
|
||||||
|
if (player1StoneCount === player2StoneCount) {
|
||||||
|
return undefined;
|
||||||
|
} else if (player1StoneCount > player2StoneCount) {
|
||||||
|
return this.player1Id;
|
||||||
|
} else {
|
||||||
|
return this.player2Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createFromMancalaGame(mancalaGame: MancalaGame): MancalaGame {
|
||||||
|
return new MancalaGame(
|
||||||
|
mancalaGame.id,
|
||||||
|
new Board(
|
||||||
|
mancalaGame.board.playerPitCount,
|
||||||
|
mancalaGame.board.initialStoneCountInPits,
|
||||||
|
mancalaGame.board.pits
|
||||||
|
),
|
||||||
|
mancalaGame.player1Id,
|
||||||
|
mancalaGame.player2Id,
|
||||||
|
mancalaGame.turnPlayerId,
|
||||||
|
mancalaGame.gameRules,
|
||||||
|
mancalaGame.history,
|
||||||
|
mancalaGame.state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentHistoryItem(): HistoryItem | null {
|
||||||
|
return this.currentHistoryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addGameStep(gameStep: GameStep) {
|
||||||
|
this.getCurrentHistoryItem()?.gameSteps.push(gameStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
mancala.js/src/core/Pit.ts
Normal file
31
mancala.js/src/core/Pit.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export class Pit {
|
||||||
|
index: number;
|
||||||
|
stoneCount: number;
|
||||||
|
|
||||||
|
constructor(index: number, stoneCount = 0) {
|
||||||
|
this.index = index;
|
||||||
|
this.stoneCount = stoneCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isBank(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public increaseStone(count = 1) {
|
||||||
|
this.stoneCount += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public decreaseStone(count = 1) {
|
||||||
|
this.stoneCount -= count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Bank extends Pit {
|
||||||
|
constructor(index: number, stoneCount = 0) {
|
||||||
|
super(index, stoneCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
override get isBank(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
mancala.js/src/index.ts
Normal file
11
mancala.js/src/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export * from './core/MancalaGame';
|
||||||
|
export * from './core/Board';
|
||||||
|
export * from './core/Pit';
|
||||||
|
export * from './core/GameRule';
|
||||||
|
export * from './core/HistoryItem';
|
||||||
|
export * from './common/CommonBoard';
|
||||||
|
export * from './common/CommonMancalaGame';
|
||||||
|
export * from './common/game_rules/GRLastStoneInBank';
|
||||||
|
export * from './common/game_rules/GRLastStoneInEmptyPit';
|
||||||
|
export * from './common/game_rules/GRDoubleStoneInPit';
|
||||||
|
export * from './common/game_rules/GRClearBoardAtEnd';
|
||||||
125
mancala.js/tests/Board.test.ts
Normal file
125
mancala.js/tests/Board.test.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { Board } from '../src/core/Board';
|
||||||
|
|
||||||
|
describe('Board Test', () => {
|
||||||
|
test('test pit index', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.pits[0].index).toBe(0);
|
||||||
|
expect(board.pits[1].index).toBe(1);
|
||||||
|
expect(board.pits[2].index).toBe(2);
|
||||||
|
expect(board.pits[3].index).toBe(3);
|
||||||
|
expect(board.pits[4].index).toBe(4);
|
||||||
|
expect(board.pits[5].index).toBe(5);
|
||||||
|
expect(board.pits[6].index).toBe(6);
|
||||||
|
expect(board.pits[7].index).toBe(7);
|
||||||
|
expect(board.pits[8].index).toBe(8);
|
||||||
|
expect(board.pits[9].index).toBe(9);
|
||||||
|
expect(board.pits[10].index).toBe(10);
|
||||||
|
expect(board.pits[11].index).toBe(11);
|
||||||
|
expect(board.pits[12].index).toBe(12);
|
||||||
|
expect(board.pits[13].index).toBe(13);
|
||||||
|
});
|
||||||
|
test('test getPitTypeByIndex', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.getPitTypeByIndex(0)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(1)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(2)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(3)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(4)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(5)).toBe('player1Pit');
|
||||||
|
expect(board.getPitTypeByIndex(6)).toBe('player1Bank');
|
||||||
|
expect(board.getPitTypeByIndex(7)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(8)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(9)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(10)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(11)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(12)).toBe('player2Pit');
|
||||||
|
expect(board.getPitTypeByIndex(13)).toBe('player2Bank');
|
||||||
|
expect(() => board.getPitTypeByIndex(-1)).toThrowError();
|
||||||
|
expect(() => board.getPitTypeByIndex(14)).toThrowError();
|
||||||
|
});
|
||||||
|
test('test checkIndex', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.checkIndex(0)).toBe(true);
|
||||||
|
expect(board.checkIndex(1)).toBe(true);
|
||||||
|
expect(board.checkIndex(2)).toBe(true);
|
||||||
|
expect(board.checkIndex(4)).toBe(true);
|
||||||
|
expect(board.checkIndex(5)).toBe(true);
|
||||||
|
expect(board.checkIndex(6)).toBe(true);
|
||||||
|
expect(board.checkIndex(7)).toBe(true);
|
||||||
|
expect(board.checkIndex(8)).toBe(true);
|
||||||
|
expect(board.checkIndex(9)).toBe(true);
|
||||||
|
expect(board.checkIndex(10)).toBe(true);
|
||||||
|
expect(board.checkIndex(11)).toBe(true);
|
||||||
|
expect(board.checkIndex(12)).toBe(true);
|
||||||
|
expect(board.checkIndex(13)).toBe(true);
|
||||||
|
expect(board.checkIndex(14)).toBe(false);
|
||||||
|
expect(board.checkIndex(-1)).toBe(false);
|
||||||
|
});
|
||||||
|
test('test getPitIndexCircularly', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.getPitIndexCircularly(0)).toBe(0);
|
||||||
|
expect(board.getPitIndexCircularly(1)).toBe(1);
|
||||||
|
expect(board.getPitIndexCircularly(2)).toBe(2);
|
||||||
|
expect(board.getPitIndexCircularly(3)).toBe(3);
|
||||||
|
expect(board.getPitIndexCircularly(4)).toBe(4);
|
||||||
|
expect(board.getPitIndexCircularly(5)).toBe(5);
|
||||||
|
expect(board.getPitIndexCircularly(6)).toBe(6);
|
||||||
|
expect(board.getPitIndexCircularly(7)).toBe(7);
|
||||||
|
expect(board.getPitIndexCircularly(8)).toBe(8);
|
||||||
|
expect(board.getPitIndexCircularly(9)).toBe(9);
|
||||||
|
expect(board.getPitIndexCircularly(10)).toBe(10);
|
||||||
|
expect(board.getPitIndexCircularly(11)).toBe(11);
|
||||||
|
expect(board.getPitIndexCircularly(12)).toBe(12);
|
||||||
|
expect(board.getPitIndexCircularly(13)).toBe(13);
|
||||||
|
expect(board.getPitIndexCircularly(14)).toBe(0);
|
||||||
|
expect(board.getPitIndexCircularly(15)).toBe(1);
|
||||||
|
expect(board.getPitIndexCircularly(16)).toBe(2);
|
||||||
|
});
|
||||||
|
test('test move', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
const initialBoard = [4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0];
|
||||||
|
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
||||||
|
board.move(0);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
1, 5, 5, 5, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0
|
||||||
|
]);
|
||||||
|
board.move(0);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 6, 5, 5, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0
|
||||||
|
]);
|
||||||
|
board.move(1);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 1, 6, 6, 5, 5, 1, 4, 4, 4, 4, 4, 4, 0
|
||||||
|
]);
|
||||||
|
board.move(5);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 1, 6, 6, 5, 1, 2, 5, 5, 5, 4, 4, 4, 0
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('test move 2', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
board.pits[5].stoneCount = 15;
|
||||||
|
const initialBoard = [4, 4, 4, 4, 4, 15, 0, 4, 4, 4, 4, 4, 4, 0];
|
||||||
|
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
||||||
|
board.move(5);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
5, 5, 5, 5, 5, 2, 1, 5, 5, 5, 5, 5, 5, 1
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test getOppositePitIndex', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.getOppositePitIndex(0)).toBe(12);
|
||||||
|
expect(board.getOppositePitIndex(1)).toBe(11);
|
||||||
|
expect(board.getOppositePitIndex(2)).toBe(10);
|
||||||
|
expect(board.getOppositePitIndex(3)).toBe(9);
|
||||||
|
expect(board.getOppositePitIndex(4)).toBe(8);
|
||||||
|
expect(board.getOppositePitIndex(5)).toBe(7);
|
||||||
|
expect(board.getOppositePitIndex(7)).toBe(5);
|
||||||
|
expect(board.getOppositePitIndex(8)).toBe(4);
|
||||||
|
expect(board.getOppositePitIndex(9)).toBe(3);
|
||||||
|
expect(board.getOppositePitIndex(10)).toBe(2);
|
||||||
|
expect(board.getOppositePitIndex(11)).toBe(1);
|
||||||
|
expect(board.getOppositePitIndex(12)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
176
mancala.js/tests/MancalaGame.test.ts
Normal file
176
mancala.js/tests/MancalaGame.test.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import { GameStep, MoveHistoryItem } from '../src/core/HistoryItem';
|
||||||
|
import { GAME_STEP_GAME_MOVE, MancalaGame } from '../src/core/MancalaGame';
|
||||||
|
import { createGame } from './TestUtil';
|
||||||
|
|
||||||
|
describe('Game Test', () => {
|
||||||
|
test('test getPlayerIdByIndex', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.getPlayerIdByIndex(0)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(1)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(2)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(3)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(4)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(5)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(6)).toStrictEqual(player1Id);
|
||||||
|
expect(game.getPlayerIdByIndex(7)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(8)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(9)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(10)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(11)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(12)).toStrictEqual(player2Id);
|
||||||
|
expect(game.getPlayerIdByIndex(13)).toStrictEqual(player2Id);
|
||||||
|
});
|
||||||
|
test('test canPlayerMove', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.canPlayerMove(player1Id, 0)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 1)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 2)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 3)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 4)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 5)).toBe(true);
|
||||||
|
expect(game.canPlayerMove(player1Id, 6)).toBe(false);
|
||||||
|
expect(game.canPlayerMove(player2Id, 0)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test moveByPlayerPit', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.state).toBe('initial');
|
||||||
|
expect(game.turnPlayerId).toBe(player1Id);
|
||||||
|
|
||||||
|
game.moveByPlayerPit(player1Id, 0);
|
||||||
|
expect(game.state).toBe('playing');
|
||||||
|
expect(game.turnPlayerId).toBe(player2Id);
|
||||||
|
|
||||||
|
game.moveByPlayerPit(player2Id, 0);
|
||||||
|
expect(game.state).toBe('playing');
|
||||||
|
expect(game.turnPlayerId).toBe(player1Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test game end test', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.state).toBe('initial');
|
||||||
|
game.board.pits[0].stoneCount = 0;
|
||||||
|
game.board.pits[1].stoneCount = 0;
|
||||||
|
game.board.pits[2].stoneCount = 0;
|
||||||
|
game.board.pits[3].stoneCount = 0;
|
||||||
|
game.board.pits[4].stoneCount = 0;
|
||||||
|
game.board.pits[5].stoneCount = 1;
|
||||||
|
game.moveByPlayerPit(player1Id, 5);
|
||||||
|
expect(game.state).toBe('ended');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test last stone in bank', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.state).toBe('initial');
|
||||||
|
expect(game.turnPlayerId).toBe(player1Id);
|
||||||
|
|
||||||
|
game.moveByPlayerPit(player1Id, 3);
|
||||||
|
expect(game.state).toBe('playing');
|
||||||
|
expect(game.turnPlayerId).toBe(player1Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test empty pit 1', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
expect(game.state).toBe('initial');
|
||||||
|
game.board.pits[0].stoneCount = 1;
|
||||||
|
game.board.pits[1].stoneCount = 0;
|
||||||
|
game.moveByPlayerPit(player1Id, 0);
|
||||||
|
expect(game.state).toBe('playing');
|
||||||
|
expect(game.turnPlayerId).toBe(player2Id);
|
||||||
|
expect(game.board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 0, 4, 4, 4, 4, 5, 4, 4, 4, 4, 0, 4, 0
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test game history', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
game.moveByPlayerPit(player1Id, 0);
|
||||||
|
game.moveByPlayerPit(player2Id, 0);
|
||||||
|
game.moveByPlayerPit(player1Id, 1);
|
||||||
|
game.moveByPlayerPit(player2Id, 1);
|
||||||
|
const historyItem1 = new MoveHistoryItem(
|
||||||
|
player1Id,
|
||||||
|
0,
|
||||||
|
[1, 5, 5, 5, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0],
|
||||||
|
[
|
||||||
|
new GameStep(0, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(1, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(2, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(3, GAME_STEP_GAME_MOVE)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
const historyItem2 = new MoveHistoryItem(
|
||||||
|
player2Id,
|
||||||
|
7,
|
||||||
|
[1, 5, 5, 5, 4, 4, 0, 1, 5, 5, 5, 4, 4, 0],
|
||||||
|
[
|
||||||
|
new GameStep(7, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(8, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(9, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(10, GAME_STEP_GAME_MOVE)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
const historyItem3 = new MoveHistoryItem(
|
||||||
|
player1Id,
|
||||||
|
1,
|
||||||
|
[1, 1, 6, 6, 5, 5, 0, 1, 5, 5, 5, 4, 4, 0],
|
||||||
|
[
|
||||||
|
new GameStep(1, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(2, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(3, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(4, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(5, GAME_STEP_GAME_MOVE)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
const historyItem4 = new MoveHistoryItem(
|
||||||
|
player2Id,
|
||||||
|
8,
|
||||||
|
[1, 1, 6, 6, 5, 5, 0, 1, 1, 6, 6, 5, 5, 0],
|
||||||
|
[
|
||||||
|
new GameStep(8, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(9, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(10, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(11, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(12, GAME_STEP_GAME_MOVE)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
expect(game.history[0]).toStrictEqual(historyItem1);
|
||||||
|
expect(game.history[1]).toStrictEqual(historyItem2);
|
||||||
|
expect(game.history[2]).toStrictEqual(historyItem3);
|
||||||
|
expect(game.history[3]).toStrictEqual(historyItem4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test getWonPlayerId', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
|
||||||
|
game.board.clear();
|
||||||
|
|
||||||
|
game.board.pits[game.board.player1BankIndex()].stoneCount = 1;
|
||||||
|
game.board.pits[game.board.player2BankIndex()].stoneCount = 0;
|
||||||
|
expect(game.getWonPlayerId()).toBe(player1Id);
|
||||||
|
|
||||||
|
game.board.pits[game.board.player1BankIndex()].stoneCount = 0;
|
||||||
|
game.board.pits[game.board.player2BankIndex()].stoneCount = 1;
|
||||||
|
expect(game.getWonPlayerId()).toBe(player2Id);
|
||||||
|
|
||||||
|
game.board.pits[game.board.player1BankIndex()].stoneCount = 0;
|
||||||
|
game.board.pits[game.board.player2BankIndex()].stoneCount = 0;
|
||||||
|
expect(game.getWonPlayerId()).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
mancala.js/tests/TestUtil.ts
Normal file
27
mancala.js/tests/TestUtil.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { GRClearBoardAtEnd } from '../src/common/game_rules/GRClearBoardAtEnd';
|
||||||
|
import { GRLastStoneInBank } from '../src/common/game_rules/GRLastStoneInBank';
|
||||||
|
import { GRLastStoneInEmptyPit } from '../src/common/game_rules/GRLastStoneInEmptyPit';
|
||||||
|
import { GRDoubleStoneInPit } from '../src/common/game_rules/GRDoubleStoneInPit';
|
||||||
|
import { Board } from '../src/core/Board';
|
||||||
|
import { MancalaGame } from '../src/core/MancalaGame';
|
||||||
|
|
||||||
|
export function createGame(): MancalaGame {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
const player1Id = '0';
|
||||||
|
const player2Id = '1';
|
||||||
|
const game = new MancalaGame(
|
||||||
|
'0',
|
||||||
|
board,
|
||||||
|
player1Id,
|
||||||
|
player2Id,
|
||||||
|
player1Id,
|
||||||
|
[
|
||||||
|
new GRLastStoneInEmptyPit(),
|
||||||
|
new GRDoubleStoneInPit(),
|
||||||
|
new GRLastStoneInBank(),
|
||||||
|
new GRClearBoardAtEnd()
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
return game;
|
||||||
|
}
|
||||||
46
mancala.js/tests/game_rules/GRClearBoardAtEnd.test.ts
Normal file
46
mancala.js/tests/game_rules/GRClearBoardAtEnd.test.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
GAME_STEP_LAST_STONE_IN_BANK,
|
||||||
|
GRLastStoneInBank
|
||||||
|
} from '../../src/common/game_rules/GRLastStoneInBank';
|
||||||
|
import {
|
||||||
|
GAME_STEP_LAST_STONE_IN_EMPTY_PIT,
|
||||||
|
GRLastStoneInEmptyPit
|
||||||
|
} from '../../src/common/game_rules/GRLastStoneInEmptyPit';
|
||||||
|
import {
|
||||||
|
GAME_STEP_BOARD_CLEARED,
|
||||||
|
GRClearBoardAtEnd
|
||||||
|
} from '../../src/common/game_rules/GRClearBoardAtEnd';
|
||||||
|
import { Board } from '../../src/core/Board';
|
||||||
|
import { GAME_STEP_GAME_MOVE, MancalaGame } from '../../src/core/MancalaGame';
|
||||||
|
import { GameStep } from '../../src/core/HistoryItem';
|
||||||
|
import { createGame } from '../TestUtil';
|
||||||
|
|
||||||
|
describe('GRClearBoardAtEnd Test', () => {
|
||||||
|
test('test GRClearBoardAtEnd 1', () => {
|
||||||
|
const game = createGame();
|
||||||
|
game.board.fillPlayer1Pits(0);
|
||||||
|
game.board.fillPlayer2Pits(0);
|
||||||
|
game.board.player1Pits[5].stoneCount = 1;
|
||||||
|
game.board.player2Pits[5].stoneCount = 1;
|
||||||
|
game.moveByPlayerPit('0', 5);
|
||||||
|
expect(game.board.player1Bank.stoneCount).toBe(2);
|
||||||
|
expect(game.board.player2Bank.stoneCount).toBe(0);
|
||||||
|
expect(
|
||||||
|
game.board.player1Pits
|
||||||
|
.map((pit) => pit.stoneCount)
|
||||||
|
.reduce((sum, stoneCount) => sum + stoneCount, 0)
|
||||||
|
).toBe(0);
|
||||||
|
expect(
|
||||||
|
game.board.player2Pits
|
||||||
|
.map((pit) => pit.stoneCount)
|
||||||
|
.reduce((sum, stoneCount) => sum + stoneCount, 0)
|
||||||
|
).toBe(0);
|
||||||
|
expect(game.history[0].gameSteps).toStrictEqual([
|
||||||
|
new GameStep(6, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(6, GAME_STEP_LAST_STONE_IN_BANK),
|
||||||
|
new GameStep(6, GAME_STEP_BOARD_CLEARED, {
|
||||||
|
pitIndexesThatHasStone: [12]
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
43
mancala.js/tests/game_rules/GRDoubleStoneInPit.test.ts
Normal file
43
mancala.js/tests/game_rules/GRDoubleStoneInPit.test.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { GameStep } from '../../src/core/HistoryItem';
|
||||||
|
import { GAME_STEP_GAME_MOVE } from '../../src/core/MancalaGame';
|
||||||
|
import { createGame } from '../TestUtil';
|
||||||
|
import { GAME_STEP_DOUBLE_STONE_IN_PIT } from '../../src/common/game_rules/GRDoubleStoneInPit';
|
||||||
|
|
||||||
|
describe('GRDoubleStoneInPit Test', () => {
|
||||||
|
test('test GRDoubleStoneInPit 1', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const board = game.board;
|
||||||
|
const initialBoard = [3, 4, 4, 4, 4, 3, 0, 3, 4, 4, 4, 4, 3, 0];
|
||||||
|
game.board.pits[5].stoneCount = 3;
|
||||||
|
game.board.pits[7].stoneCount = 3;
|
||||||
|
game.board.pits[12].stoneCount = 3;
|
||||||
|
game.board.pits[0].stoneCount = 3;
|
||||||
|
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
||||||
|
game.moveByPlayerPit('0', 5);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
3, 4, 4, 4, 4, 1, 5, 0, 4, 4, 4, 4, 3, 0
|
||||||
|
]);
|
||||||
|
expect(game.history[0].gameSteps).toStrictEqual([
|
||||||
|
new GameStep(5, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(6, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(7, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(7, GAME_STEP_DOUBLE_STONE_IN_PIT, {
|
||||||
|
pitIndex: 7,
|
||||||
|
bankIndex: 6
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
game.moveByPlayerPit('1', 5);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 4, 4, 4, 4, 1, 5, 0, 4, 4, 4, 4, 1, 5
|
||||||
|
]);
|
||||||
|
expect(game.history[game.history.length - 1].gameSteps).toStrictEqual([
|
||||||
|
new GameStep(12, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(13, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(14, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(0, GAME_STEP_DOUBLE_STONE_IN_PIT, {
|
||||||
|
pitIndex: 0,
|
||||||
|
bankIndex: 13
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
mancala.js/tests/game_rules/GRLastStoneInEmptyPit.test.ts
Normal file
27
mancala.js/tests/game_rules/GRLastStoneInEmptyPit.test.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { GAME_STEP_GAME_MOVE } from '../../src/core/MancalaGame';
|
||||||
|
import { GameStep } from '../../src/core/HistoryItem';
|
||||||
|
import { createGame } from '../TestUtil';
|
||||||
|
import { GAME_STEP_LAST_STONE_IN_EMPTY_PIT } from '../../src/common/game_rules/GRLastStoneInEmptyPit';
|
||||||
|
|
||||||
|
describe('GRClearBoardAtEnd Test', () => {
|
||||||
|
test('test GRClearBoardAtEnd 1', () => {
|
||||||
|
const game = createGame();
|
||||||
|
const board = game.board;
|
||||||
|
const initialBoard = [4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0];
|
||||||
|
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
||||||
|
game.board.player1Pits[0].stoneCount = 1;
|
||||||
|
game.board.player1Pits[1].stoneCount = 0;
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
1, 0, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0
|
||||||
|
]);
|
||||||
|
game.moveByPlayerPit('0', 0);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
0, 0, 4, 4, 4, 4, 5, 4, 4, 4, 4, 0, 4, 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(game.history[0].gameSteps).toStrictEqual([
|
||||||
|
new GameStep(1, GAME_STEP_GAME_MOVE),
|
||||||
|
new GameStep(1, GAME_STEP_LAST_STONE_IN_EMPTY_PIT, { oppositeIndex: 11 })
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
110
mancala.js/tsconfig.json
Normal file
110
mancala.js/tsconfig.json
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||||
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "dist", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
//"sourceRoot": "src", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"**/__tests__/*",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
3095
mancala.js/yarn.lock
Normal file
3095
mancala.js/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user