Add common module stuff
This commit is contained in:
parent
ee9d9ca6ed
commit
30042a1152
2 changed files with 358 additions and 0 deletions
157
common/src/algorithms/movement.spec.ts
Normal file
157
common/src/algorithms/movement.spec.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { Board } from "../types/GameBoard";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import "mocha";
|
||||||
|
|
||||||
|
import { countShips, shipLocation } from "./movement";
|
||||||
|
|
||||||
|
function stringifyBoard(b: Board) {
|
||||||
|
return (
|
||||||
|
b.singularity.join(`\t`)
|
||||||
|
+ ` > `
|
||||||
|
+ [...b.path].map(x => x != null ? x : `-`).join(`\t`)
|
||||||
|
+ ` > `
|
||||||
|
+ b.warpgate.join(`\t`)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
describe("The shipLocation function", () => {
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: `Should correctly identify the singularity location`,
|
||||||
|
args: ["a"],
|
||||||
|
input: {
|
||||||
|
singularity: ["a"],
|
||||||
|
path: [],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
expect: "singularity",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Should correctly identify the path location`,
|
||||||
|
args: ["a"],
|
||||||
|
input: {
|
||||||
|
singularity: [],
|
||||||
|
path: ["a"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
expect: "path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Should correctly identify the warpgate location`,
|
||||||
|
args: ["a"],
|
||||||
|
input: {
|
||||||
|
singularity: [],
|
||||||
|
path: [],
|
||||||
|
warpgate: ["a"],
|
||||||
|
},
|
||||||
|
expect: "warpgate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Should correctly identify the ship doesn't exist`,
|
||||||
|
args: ["a"],
|
||||||
|
input: {
|
||||||
|
singularity: [],
|
||||||
|
path: [],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
expect: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
it(test.name, () => {
|
||||||
|
let b = JSON.parse(JSON.stringify(test.input));
|
||||||
|
|
||||||
|
let location = shipLocation(b, test.args[0]);
|
||||||
|
expect(location).to.equal(test.expect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("The countShips function", () => {
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: `shouldn't could ships in the singularity or warpgate`,
|
||||||
|
args: [3],
|
||||||
|
input: {
|
||||||
|
singularity: ["a"],
|
||||||
|
path: [null, null, null, "b", null, null, null],
|
||||||
|
warpgate: ["c"],
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
expect: { left: 0, right: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `should count the ships in the path correctly when location is in the middle`,
|
||||||
|
args: [3],
|
||||||
|
input: {
|
||||||
|
singularity: ["a"],
|
||||||
|
path: ["d", null, "c", "b", null, null, "e"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
expect: { left: 2, right: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `should count the ships in the path correctly when location is on the low edge`,
|
||||||
|
args: [0],
|
||||||
|
input: {
|
||||||
|
singularity: ["a"],
|
||||||
|
path: ["d", null, "c", "b", null, null, "e"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
expect: { left: 0, right: 3 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `should count the ships in the path correctly when location is on the high edge`,
|
||||||
|
args: [6],
|
||||||
|
input: {
|
||||||
|
singularity: ["a"],
|
||||||
|
path: ["d", null, "c", "b", null, null, "e"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
expect: { left: 3, right: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `should error when location is negative`,
|
||||||
|
args: [-1],
|
||||||
|
input: {
|
||||||
|
singularity: [],
|
||||||
|
path: ["d", null, "c", "b", null, null, "e"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
expect: new Error("location can't be negative"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `should error when location is too high`,
|
||||||
|
args: [100],
|
||||||
|
input: {
|
||||||
|
singularity: [],
|
||||||
|
path: ["d", null, "c", "b", null, null, "e"],
|
||||||
|
warpgate: [],
|
||||||
|
},
|
||||||
|
expect: new Error("location can't be larger than the path"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
it(test.name, () => {
|
||||||
|
let b = JSON.parse(JSON.stringify(test.input));
|
||||||
|
if (test.expect instanceof Error) {
|
||||||
|
expect(countShips(b, test.args[0])).to.throw(test.expect);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let count = countShips(b, test.args[0]);
|
||||||
|
expect(count).to.have.all.keys(Object.keys(test.expect));
|
||||||
|
expect(count.left).to.equal(test.expect.left);
|
||||||
|
expect(count.right).to.equal(test.expect.right);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
201
common/src/algorithms/movement.ts
Normal file
201
common/src/algorithms/movement.ts
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
import { FuelCard } from "../index";
|
||||||
|
import { GamePiece, Board } from "../index";
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function shipLocation(board: Board, ship: GamePiece) {
|
||||||
|
if (board.path.includes(ship)) {
|
||||||
|
return "path";
|
||||||
|
};
|
||||||
|
if (board.singularity.includes(ship)) {
|
||||||
|
return "singularity";
|
||||||
|
};
|
||||||
|
if (board.warpgate.includes(ship)) {
|
||||||
|
return "warpgate";
|
||||||
|
};
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function countShips(board: Board, location: number) {
|
||||||
|
|
||||||
|
if (location < 0) {
|
||||||
|
throw new Error("location can't be negative");
|
||||||
|
};
|
||||||
|
if (location > board.path.length) {
|
||||||
|
throw new Error("location can't be larger than the path");
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = 0;
|
||||||
|
if (location > 0) {
|
||||||
|
left = board.path.slice(0, location - 1).filter(x => x != null).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
right: board.path.slice(location + 1).filter(x => x != null).length,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function determineDirection(board: Board, location: number): number {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Edge-Case check for when the board only has a single ship on it because any
|
||||||
|
ships that hit the warp gate get removed from the game, and ships in the
|
||||||
|
singularity don't create gravity wells, not affecting other ships.
|
||||||
|
*/
|
||||||
|
let shipCount = 0;
|
||||||
|
for (const location of board.path) {
|
||||||
|
if (location != null) {
|
||||||
|
shipCount++;
|
||||||
|
if (shipCount >= 2) {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (shipCount <= 1) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let delta = 1;
|
||||||
|
while (true) {
|
||||||
|
if (location - delta < 0) {
|
||||||
|
return 1;
|
||||||
|
} else if (location + delta >= board.path.length) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
let left = board.path[location - delta];
|
||||||
|
let right = board.path[location + delta];
|
||||||
|
|
||||||
|
if (left != null && right != null) {
|
||||||
|
let sum = countShips(board, location)
|
||||||
|
return Math.sign(sum.right - sum.left);
|
||||||
|
} else if (left != null) {
|
||||||
|
return -1;
|
||||||
|
} else if (right != null) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
delta++;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function tractorBeam(board: Board, ship: GamePiece, card: FuelCard) {
|
||||||
|
let delta = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
delta++;
|
||||||
|
|
||||||
|
let behind = origin - delta;
|
||||||
|
let ahead = origin + delta;
|
||||||
|
|
||||||
|
let negativeNewPosition = behind;
|
||||||
|
let positiveNewPosition = ahead;
|
||||||
|
let behindShip = board[behind];
|
||||||
|
let aheadShip = board[ahead];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check for a ship behind the origin, if there is a ship there then pull
|
||||||
|
it towards the origin (forwards). If it were to collide with another
|
||||||
|
ship, then drift until it no longer collides.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
behind >= 0
|
||||||
|
&& behindShip != null
|
||||||
|
) {
|
||||||
|
let localMagnitude = magnitude;
|
||||||
|
while (board[behind + localMagnitude] != null) {
|
||||||
|
localMagnitude++;
|
||||||
|
};
|
||||||
|
negativeNewPosition = behind + localMagnitude;
|
||||||
|
board[behind] = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check for a ship ahead of the origin, if there is a ship there then
|
||||||
|
pull it towards the origin (backwards). If it were to collide with
|
||||||
|
another ship, then drift until it no longer collides.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
ahead < board.length
|
||||||
|
&& aheadShip
|
||||||
|
) {
|
||||||
|
let localMagnitude = magnitude;
|
||||||
|
while (board[ahead - localMagnitude] != null) {
|
||||||
|
localMagnitude++;
|
||||||
|
};
|
||||||
|
positiveNewPosition = ahead - localMagnitude;
|
||||||
|
board[ahead] = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simultaneous movement
|
||||||
|
if (aheadShip) {
|
||||||
|
board[positiveNewPosition] = aheadShip;
|
||||||
|
};
|
||||||
|
if (behindShip) {
|
||||||
|
board[negativeNewPosition] = behindShip;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (behind < 0 && ahead >= board.length) {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function moveShip(board: Board, ship: GamePiece, card: FuelCard) {
|
||||||
|
let locale = shipLocation(board, ship);
|
||||||
|
if (locale == "singularity") {
|
||||||
|
let newPos = card.magnitude;
|
||||||
|
|
||||||
|
// Used a purple card for some reason...
|
||||||
|
if (newPos < 0) { return };
|
||||||
|
board.path[card.magnitude] = ship;
|
||||||
|
}
|
||||||
|
else if (locale == "path") {
|
||||||
|
let oldPos = board.path.indexOf(ship);
|
||||||
|
let direction = determineDirection(board, oldPos)
|
||||||
|
let newPos = oldPos * direction * card.magnitude;
|
||||||
|
|
||||||
|
if (newPos < 0) {
|
||||||
|
board.singularity.push(ship);
|
||||||
|
board.path[oldPos] = null;
|
||||||
|
} else if (newPos >= board.path.length) {
|
||||||
|
board.warpgate.push(ship);
|
||||||
|
board.path[oldPos] = null;
|
||||||
|
} else {
|
||||||
|
// Drift the ship
|
||||||
|
while (board.path[newPos] != null) {
|
||||||
|
newPos += direction;
|
||||||
|
};
|
||||||
|
board.path[oldPos] = null;
|
||||||
|
board.path[newPos] = ship;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a card to the board as it was played by the ship. This modifies the
|
||||||
|
* board in-place, if wanting to not modify the board in place, pass a deep-copy
|
||||||
|
* of the board into the function.
|
||||||
|
*
|
||||||
|
* @param board The game board
|
||||||
|
* @param ship The ship that is playing the card
|
||||||
|
* @param card The card that is being played
|
||||||
|
*/
|
||||||
|
export function processCard(board: Board, ship: GamePiece, card: FuelCard) {
|
||||||
|
if (card.type == "movement") {
|
||||||
|
moveShip(board, ship, card);
|
||||||
|
} else {
|
||||||
|
tractorBeam(board, ship, card);
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue