Release: Quarkus backend, Keycloak login, game history, clock, bot, tutorial and rematch #3
@ -64,14 +64,11 @@ export function describeMove(moves: PlayedMove[], moveNumber: number): MoveDescr
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Board indexes the stones of the given pit are dropped into, in order, as Board.move sows them: a single stone goes
|
// Board indexes the stones of the given pit are dropped into, in order, as Board.move sows them (see
|
||||||
// to the next pit; otherwise the first stone goes back into the played pit. Every pit and store is passed, the
|
// Board.getSowingPath): the opponent's store is skipped. The last index is where the last stone lands. Empty for an
|
||||||
// opponent's store too. The last index is where the last stone lands. Empty for an empty pit or a store.
|
// empty pit or a store.
|
||||||
export function getSowingPath(stones: number[], pitIndex: number): number[] {
|
export function getSowingPath(stones: number[], pitIndex: number): number[] {
|
||||||
const stoneCount = stones[pitIndex];
|
return referenceBoard.getSowingPath(pitIndex, stones[pitIndex] ?? 0);
|
||||||
if (!stoneCount || referenceBoard.checkPitTypeIsBankByIndex(pitIndex)) return [];
|
|
||||||
const start = stoneCount === 1 ? pitIndex + 1 : pitIndex;
|
|
||||||
return Array.from({ length: stoneCount }, (_, step) => (start + step) % stones.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The game as it was after the given move, for showing or animating that position (e.g. with PitAnimator,
|
// The game as it was after the given move, for showing or animating that position (e.g. with PitAnimator,
|
||||||
|
|||||||
@ -145,12 +145,12 @@ describe("getSowingPath", () => {
|
|||||||
expect(getSowingPath(stones, 4)).toEqual([5]);
|
expect(getSowingPath(stones, 4)).toEqual([5]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes both stores and wraps around the board", () => {
|
it("passes the player's own store, skips the opponent's and wraps around the board", () => {
|
||||||
const stones = getInitialBoard();
|
const stones = getInitialBoard();
|
||||||
stones[12] = 4;
|
stones[12] = 4;
|
||||||
expect(getSowingPath(stones, 12)).toEqual([12, 13, 0, 1]);
|
expect(getSowingPath(stones, 12)).toEqual([12, 13, 0, 1]);
|
||||||
stones[5] = 10;
|
stones[5] = 10;
|
||||||
expect(getSowingPath(stones, 5)).toEqual([5, 6, 7, 8, 9, 10, 11, 12, 13, 0]);
|
expect(getSowingPath(stones, 5)).toEqual([5, 6, 7, 8, 9, 10, 11, 12, 0, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is empty for an empty pit or a store", () => {
|
it("is empty for an empty pit or a store", () => {
|
||||||
|
|||||||
@ -107,19 +107,34 @@ export class Board {
|
|||||||
`StoneNotFound => index : ${index} stoneCount = ${pit.stoneCount}`
|
`StoneNotFound => index : ${index} stoneCount = ${pit.stoneCount}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const stepCount = pit.stoneCount;
|
const path = this.getSowingPath(index);
|
||||||
this.pits[index].stoneCount = 0;
|
pit.stoneCount = 0;
|
||||||
const startIndex = stepCount === 1 ? index + 1 : index;
|
this.fireOnGameMoveStart(path[0]);
|
||||||
this.moveInternal(startIndex, stepCount);
|
path.forEach((pathIndex) => this.onGameMoveStep(pathIndex));
|
||||||
|
this.fireOnGameMoveEnd(path[path.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private moveInternal(startIndex: number, stepCount: number) {
|
// Indexes the stones of the pit are dropped into, in order. A single stone goes to the next pit; otherwise the first
|
||||||
this.fireOnGameMoveStart(startIndex);
|
// stone goes back into the played pit. The player's own bank gets a stone, the opponent's bank is skipped. The last
|
||||||
for (let i = startIndex; i < startIndex + stepCount; i++) {
|
// index is where the last stone lands. Empty for a bank or an empty pit.
|
||||||
this.onGameMoveStep(i);
|
public getSowingPath(
|
||||||
|
index: number,
|
||||||
|
stoneCount = this.pits[index].stoneCount
|
||||||
|
): number[] {
|
||||||
|
this.checkIndeAndMaybeThrowError(index);
|
||||||
|
if (stoneCount <= 0 || this.checkPitTypeIsBankByIndex(index)) return [];
|
||||||
|
const opponentBankIndex =
|
||||||
|
this.getPitTypeByIndex(index) === 'player1Pit'
|
||||||
|
? this.player2BankIndex()
|
||||||
|
: this.player1BankIndex();
|
||||||
|
const path: number[] = [];
|
||||||
|
let current =
|
||||||
|
stoneCount === 1 ? this.getNextPitIndexCircularly(index) : index;
|
||||||
|
while (path.length < stoneCount) {
|
||||||
|
if (current !== opponentBankIndex) path.push(current);
|
||||||
|
current = this.getNextPitIndexCircularly(current);
|
||||||
}
|
}
|
||||||
const stopIndex = startIndex + stepCount - 1;
|
return path;
|
||||||
this.fireOnGameMoveEnd(stopIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGameMoveStep(index: number) {
|
public onGameMoveStep(index: number) {
|
||||||
|
|||||||
@ -103,9 +103,33 @@ describe('Board Test', () => {
|
|||||||
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
expect(board.getStoneArray()).toStrictEqual(initialBoard);
|
||||||
board.move(5);
|
board.move(5);
|
||||||
expect(board.getStoneArray()).toStrictEqual([
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
5, 5, 5, 5, 5, 2, 1, 5, 5, 5, 5, 5, 5, 1
|
5, 5, 5, 5, 5, 2, 2, 5, 5, 5, 5, 5, 5, 0
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
test('move skips the opponent bank for both players', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
board.pits[12].stoneCount = 10;
|
||||||
|
board.move(12);
|
||||||
|
expect(board.getStoneArray()).toStrictEqual([
|
||||||
|
5, 5, 5, 5, 5, 5, 0, 5, 5, 4, 4, 4, 1, 1
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('test getSowingPath', () => {
|
||||||
|
const board = new Board(6, 4);
|
||||||
|
expect(board.getSowingPath(0)).toStrictEqual([0, 1, 2, 3]);
|
||||||
|
expect(board.getSowingPath(0, 1)).toStrictEqual([1]);
|
||||||
|
expect(board.getSowingPath(5, 2)).toStrictEqual([5, 6]);
|
||||||
|
expect(board.getSowingPath(5, 9)).toStrictEqual([
|
||||||
|
5, 6, 7, 8, 9, 10, 11, 12, 0
|
||||||
|
]);
|
||||||
|
expect(board.getSowingPath(12, 3)).toStrictEqual([12, 13, 0]);
|
||||||
|
expect(board.getSowingPath(11, 10)).toStrictEqual([
|
||||||
|
11, 12, 13, 0, 1, 2, 3, 4, 5, 7
|
||||||
|
]);
|
||||||
|
expect(board.getSowingPath(0, 0)).toStrictEqual([]);
|
||||||
|
expect(board.getSowingPath(6)).toStrictEqual([]);
|
||||||
|
expect(board.getSowingPath(13)).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
test('test getOppositePitIndex', () => {
|
test('test getOppositePitIndex', () => {
|
||||||
const board = new Board(6, 4);
|
const board = new Board(6, 4);
|
||||||
|
|||||||
@ -33,7 +33,7 @@ describe('GRDoubleStoneInPit Test', () => {
|
|||||||
expect(game.history[game.history.length - 1].gameSteps).toStrictEqual([
|
expect(game.history[game.history.length - 1].gameSteps).toStrictEqual([
|
||||||
new GameStep(12, GAME_STEP_GAME_MOVE),
|
new GameStep(12, GAME_STEP_GAME_MOVE),
|
||||||
new GameStep(13, GAME_STEP_GAME_MOVE),
|
new GameStep(13, GAME_STEP_GAME_MOVE),
|
||||||
new GameStep(14, GAME_STEP_GAME_MOVE),
|
new GameStep(0, GAME_STEP_GAME_MOVE),
|
||||||
new GameStep(0, GAME_STEP_DOUBLE_STONE_IN_PIT, {
|
new GameStep(0, GAME_STEP_DOUBLE_STONE_IN_PIT, {
|
||||||
pitIndex: 0,
|
pitIndex: 0,
|
||||||
bankIndex: 13
|
bankIndex: 13
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user