Skip to content

Commit

Permalink
Keepers in Iron
Browse files Browse the repository at this point in the history
  • Loading branch information
punkstarman committed Feb 12, 2023
1 parent 17dffc9 commit 96dd2c3
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 9 deletions.
6 changes: 4 additions & 2 deletions src/action-parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RootActionClearPath, RootActionCombat, RootActionCraft, RootActionDominance, RootActionGainVP, RootActionMove, RootActionReveal, RootActionUpdateFunds, RootCard, RootCardName, RootFaction, RootFactionBoard, RootItem, RootItemState, RootPiece, RootPieceType, RootLocation, RootSuit, RootThing, RootVagabondRelationshipStatus, RootForest, RootActionPlot, RootActionType, RootVagabondItemSpecial } from './interfaces';
import { parseConspiracyAction, parseCultAction, parseDuchyAction, parseEyrieAction, parseHundredsAction, parseMarquiseAction, parseRiverfolkAction, parseVagabondAction, parseWoodlandAction } from './parsers';
import { parseConspiracyAction, parseCultAction, parseDuchyAction, parseEyrieAction, parseHundredsAction, parseKeepersAction, parseMarquiseAction, parseRiverfolkAction, parseVagabondAction, parseWoodlandAction } from './parsers';
import { splitAction } from './utils/action-splitter';
import { extendCardName } from './utils/card-name-utils';
import { formRegex } from './utils/regex-former';
Expand Down Expand Up @@ -33,7 +33,7 @@ const EXTENDED_MOVE_REGEX = formRegex('[Number|||countMoved]<ExtendedComponent||
const FACTION_BOARD_REGEX = new RegExp(`^([${ALL_FACTIONS}])?\\$$`);
const ITEM_REGEX_STRING = `^\%[${ALL_ITEMS}]$`;
const ITEM_REGEX = new RegExp(ITEM_REGEX_STRING);
const PIECE_REGEX_STRING = `^[${ALL_FACTIONS}]?[${ALL_PIECES}]_?[swrkfrmcbe]?$`;
const PIECE_REGEX_STRING = `^[${ALL_FACTIONS}]?[${ALL_PIECES}](_([jtswrkfrmcbe123](_[fjt])?)?)?$`;
const PIECE_REGEX = new RegExp(PIECE_REGEX_STRING);
const CARD_REGEX_STRING = `^[${ALL_SUITS}]?#[@a-z]*$`;
const CARD_REGEX = new RegExp(CARD_REGEX_STRING);
Expand Down Expand Up @@ -341,6 +341,8 @@ export function parseAction(action: string, faction: RootFaction): any {
case 'H':
parsedAction = parseHundredsAction(action);
break;
case 'K':
parsedAction = parseKeepersAction(action);
default:
break;
}
Expand Down
11 changes: 9 additions & 2 deletions src/interfaces/actions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { RootCard, RootCardName, RootFaction, RootItem, RootPiece, RootLocation, RootSuit } from './rootgame';

export type RootAction = RootActionGainVP | RootActionCraft | RootActionMove | RootActionDominance | RootActionCombat | RootActionReveal | RootActionClearPath | RootActionSetOutcast | RootActionSetPrices | RootActionUpdateFunds | RootActionPlot | RootActionSwapPlots;
export type RootAction = RootActionGainVP | RootActionCraft | RootActionMove | RootActionDominance | RootActionCombat | RootActionReveal | RootActionClearPath | RootActionSetOutcast | RootActionSetPrices | RootActionUpdateFunds | RootActionPlot | RootActionSwapPlots | RootActionFlipRelic;

export enum RootActionType {
FlipPlot = "flip plot",
ExposePlot = "expose plot",
SwapPlots = "swap plots",
FlipRelic = "flip relic",
}

export interface RootActionGainVP {
Expand Down Expand Up @@ -79,4 +80,10 @@ export interface RootActionPlot {
export interface RootActionSwapPlots {
type: RootActionType;
clearings: number[];
}
}

export interface RootActionFlipRelic {
type: RootActionType;
relic: string;
clearing: number;
}
16 changes: 14 additions & 2 deletions src/interfaces/rootgame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export enum RootFaction {
Riverfolk = 'O',
Duchy = 'D',
Corvid = 'P',
Hundreds = 'H'
Hundreds = 'H',
Keepers = 'K'
}

export interface RootPiece {
Expand Down Expand Up @@ -180,6 +181,12 @@ export enum RootHundredsMoodSpecial {
Wrathful = 'wrathful'
}

export enum RootKeepersSpecial {
Move = 1,
BattleDelve = 2,
MoveOrRecover = 3,
}

export enum RootCardName {

// all decks
Expand Down Expand Up @@ -271,7 +278,11 @@ export enum RootCardName {
SwapMeet = 'swap',
SwapMeetFullName = 'swapmeet',
Tun = 'tun',
TunFullName = 'tunnels'
TunFullName = 'tunnels',

// Eyrie and Keepers
LoyalVizier = 'viz',
FaithfulRetainer = 'faith',
}

export enum RootQuestCard {
Expand All @@ -295,6 +306,7 @@ export interface RootForest {

export interface RootFactionBoard {
faction: RootFaction;
sublocation?: string | number;
}

// TODO: Add decree column, Vagabond board areas, quests
Expand Down
1 change: 1 addition & 0 deletions src/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './cult';
export * from './duchy';
export * from './eyrie';
export * from './hundreds';
export * from './keepers';
export * from './marquise';
export * from './metadata';
export * from './riverfolk';
Expand Down
72 changes: 72 additions & 0 deletions src/parsers/keepers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { parseCard } from '../action-parser';
import { RootAction, RootActionMove, RootActionType, RootCardName, RootFaction, RootFactionBoard, RootThing } from '../interfaces';
import { splitAction } from '../utils/action-splitter';
import { formRegex } from '../utils/regex-former';

const FLIP_RELIC_REGEX = formRegex('t<Clearing|||relicClearing>^<Piece|||relicFlipped>');
const ADD_TO_RETINUE_REGEX = formRegex('[Number|||countAdded]<Card|||cardAdded>K->$_<Retinue|||columnAdded>')
const DISCARD_FROM_RETINUE_REGEX = formRegex('<Card|||cardDiscarded>$_<Retinue|||columnDiscarded>->')

export function parseAddToRetinue(actions: string[]): RootActionMove {

const movingComponents = [];

for (let action of actions) {
const result = action.match(ADD_TO_RETINUE_REGEX);
const component = {
number: +(result.groups.countAdded || 1),
thing: parseCard(result.groups.cardAdded),
start: RootFaction.Keepers,
destination: {
faction: RootFaction.Keepers,
sublocation: +result.groups.columnAdded,
}
};

movingComponents.push(component);
}

return {
things: movingComponents
};

}

export function parseKeepersAction(action: string): RootAction {

if (FLIP_RELIC_REGEX.test(action)) {
const result = action.match(FLIP_RELIC_REGEX);

return {
type: RootActionType.FlipRelic,
relic: result.groups.relicFlipped,
clearing: +result.groups.relicClearing
};
}

if (DISCARD_FROM_RETINUE_REGEX.test(action)) {
const result = action.match(DISCARD_FROM_RETINUE_REGEX);
const card = parseCard(result.groups.cardDiscarded);

return {
things: [{
number: 1,
thing: card,
start: {
faction: RootFaction.Keepers,
sublocation: +result.groups.columnDiscarded
},
destination: (card.cardName === RootCardName.FaithfulRetainer) ? null : 'Discard pile'
}]
}
}

const simpleActions = splitAction(action);

if (simpleActions.every(act => ADD_TO_RETINUE_REGEX.test(act))) {
return parseAddToRetinue(simpleActions);
}

return null;

}
10 changes: 7 additions & 3 deletions src/utils/regex-former.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RootCardName, RootDuchyMinisterSpecial, RootEyrieLeaderSpecial, RootEyrieSpecial, RootFaction, RootHundredsMoodSpecial, RootItem, RootItemState, RootLizardOutcastSpecial, RootPieceType, RootQuestCard, RootRiverfolkPriceSpecial, RootSpecialCardName, RootSuit, RootVagabondCharacterSpecial, RootVagabondItemSpecial, RootVagabondRelationshipStatus } from '../interfaces';
import { RootCardName, RootDuchyMinisterSpecial, RootEyrieLeaderSpecial, RootEyrieSpecial, RootFaction, RootHundredsMoodSpecial, RootKeepersSpecial, RootItem, RootItemState, RootLizardOutcastSpecial, RootPieceType, RootQuestCard, RootRiverfolkPriceSpecial, RootSpecialCardName, RootSuit, RootVagabondCharacterSpecial, RootVagabondItemSpecial, RootVagabondRelationshipStatus } from '../interfaces';

const DIVIDER_BEFORE_GROUP_NAME = '|||'; // arbitrarily chosen to be a divider that will never appear in Rootlog code

Expand All @@ -17,8 +17,8 @@ const HAND = `(${ALL_FACTIONS})`; // A facti
// In order from most-to-least specific, including the discard pile represented by *
const ALL_LOCATIONS = `(${FOREST}|${CLEARING}|${FACTION_BOARD}|${HAND}|${ALL_ITEM_STATE}|\\*)`;

// [Faction]<PieceType>[_<subtype>], subtype must be one letter
const PIECE_REGEX_STRING = `(${ALL_FACTIONS})?(${ALL_PIECE_TYPES})(_[a-z])?`;
// [Faction]<PieceType>[_<subtype>], subtypes must be one letter or digit
const PIECE_REGEX_STRING = `(${ALL_FACTIONS})?(${ALL_PIECE_TYPES})(_[a-z0-9](_[a-z])?)?`;
// [Suit]#[CardName]
const CARD_REGEX_STRING = `(${ALL_SUITS})?#${ALL_CARD_NAMES}?`;
// %<ItemType> or %_ to represent 'all items'
Expand All @@ -45,6 +45,8 @@ const DUCHY_SPECIFIC_LOCATIONS = `(0)`; // The Burrow
const DUCHY_MINISTERS = `(${Object.values(RootDuchyMinisterSpecial).join('|')})`;
// Lord of the Hundreds
const HUNDREDS_MOODS = `(${Object.values(RootHundredsMoodSpecial).join('|')})`
// Keepers in Iron
const KEEPERS_COLUMNS = `(${Object.values(RootKeepersSpecial).join('|')})`;

const EXTENDED_LOCATIONS = `(${VAGABOND_SPECIFIC_LOCATIONS}|${DUCHY_SPECIFIC_LOCATIONS}|${ALL_LOCATIONS}|\\*)`;
// includes faction-specific card names
Expand Down Expand Up @@ -99,6 +101,8 @@ const parseForRegexString = function(str: string): string {
return _parseForRegexString(DUCHY_MINISTERS, groupName);
case ('mood'):
return _parseForRegexString(HUNDREDS_MOODS, groupName);
case ('retinue'):
return _parseForRegexString(KEEPERS_COLUMNS, groupName);
case ('extendedlocation'):
return _parseForRegexString(EXTENDED_LOCATIONS, groupName);
case ('extendedcomponent'):
Expand Down
173 changes: 173 additions & 0 deletions test/keepers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import test from 'ava-ts';
import { parseAction } from '../src/action-parser';

import { RootActionMove, RootActionType, RootFaction, RootFactionBoard, RootSuit } from '../src/interfaces';

test('Correctly parses placing a Keepers waystation', t => {

const result = parseAction('b_t_j->3', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 'b_t_j',
pieceType: 'b'
},
start: null,
destination: 3
}]);
});

test('Correctly parses delving a relic', t => {

const result = parseAction('t_j1_2_5_10->10', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 't_j',
pieceType: 't'
},
start: {
clearings: [1, 2, 5, 10],
},
destination: 10
}]);
});

test('Correctly parses flipping a relic', t => {

const result = parseAction('t3^t_2_j', RootFaction.Keepers);

t.deepEqual(result, {
type: RootActionType.FlipRelic,
clearing: 3,
relic: 't_2_j'
});
});

test('Correctly parses recovering a relic', t => {

const result = parseAction('t_2_j2->$', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 't_2_j',
pieceType: 't'
},
start: 2,
destination: {
faction: RootFaction.Keepers
}
}]);
});

test('Correctly parses adding a card to the retinue', t => {

const result = parseAction('M#K->$_1', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
suit: RootSuit.Mouse,
cardName: null,
},
start: RootFaction.Keepers,
destination: {
faction: RootFaction.Keepers,
sublocation: 1
}
}]);
});

test('Correctly parses discarding a Faithful Retainer from the retinue', t => {

const result = parseAction('#faith$_1->', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
suit: null,
cardName: 'faith',
},
start: {
faction: RootFaction.Keepers,
sublocation: 1,
},
destination: null
}]);
});

test('Correctly parses discarding a deck card from the retinue', t => {

const result = parseAction('B#$_2->', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
suit: RootSuit.Bird,
cardName: null,
},
start: {
faction: RootFaction.Keepers,
sublocation: 2,
},
destination: 'Discard pile'
}]);
});

test('Correctly parses removing relic after battle', t => {

const result = parseAction('Kt_2_j3->', RootFaction.Eyrie);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 't_2_j',
pieceType: 't',
},
start: 3,
destination: null
}]);
});

test('Correctly parses placing relic in forest during setup', t => {

const result = parseAction('t_j->1_2_5_10', RootFaction.Keepers);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 't_j',
pieceType: 't',
},
start: null,
destination: {
clearings: [1, 2, 5, 10]
}
}]);
});

test('Correctly parses placing relic in forest after removal', t => {

const result = parseAction('Kt_2_j->1_2_5_10', RootFaction.Eyrie);

t.deepEqual(result.things, [{
number: 1,
thing: {
faction: RootFaction.Keepers,
piece: 't_2_j',
pieceType: 't',
},
start: null,
destination: {
clearings: [1, 2, 5, 10]
}
}]);
});

0 comments on commit 96dd2c3

Please sign in to comment.