Add common module stuff

This commit is contained in:
Oliver Akins 2022-12-25 17:41:07 -06:00
parent ee9d9ca6ed
commit 30042a1152
No known key found for this signature in database
GPG key ID: 3C2014AF9457AF99
2 changed files with 358 additions and 0 deletions

View 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);
};
});
};
});

View 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);
};
};