diff --git a/src/common.ts b/src/common.ts index dc3acf5..947e4d7 100644 --- a/src/common.ts +++ b/src/common.ts @@ -120,6 +120,8 @@ export const CardStringSuits: CardStringSuit[] = [ CardStringSuit.Choice ] +export type ShuffleFunction = (cards: CardString[]) => CardString[] + export { MoveTag } from "./MoveTag" export { Role } from "./Role" export { StatusCode } from "./StatusCode" diff --git a/src/doCombat.ts b/src/doCombat.ts index 0a22f1c..2950f99 100644 --- a/src/doCombat.ts +++ b/src/doCombat.ts @@ -1,5 +1,6 @@ -import { shuffle } from "@samual/lib/shuffle" -import type { CardString, CardStringSuit, CardStringFace, Lane, State } from "./common" +import type { LaxPartial } from "@samual/lib" +import { shuffle as defaultShuffleFunction } from "@samual/lib/shuffle" +import type { CardString, CardStringFace, CardStringSuit, Lane, ShuffleFunction, State } from "./common" import { CardStringFaceModifier, Role, StatusCode } from "./common" export type CombatData = { @@ -20,7 +21,11 @@ export type CombatData = { export const PowersOfTwo = [ 2, 4, 8, 16, 32, 64, 128, 256 ] -export function doCombat(state: State, lane: Lane): { status: StatusCode.Okay | StatusCode.AttackerWin } & CombatData { +export function doCombat( + state: State, + lane: Lane, + { shuffleFunction: shuffle = defaultShuffleFunction }: LaxPartial<{ shuffleFunction: ShuffleFunction }> = {} +): { status: StatusCode.Okay | StatusCode.AttackerWin } & CombatData { const roleTurn: Role = (state.turn % 2) + 1 const laneDeck = state.laneDecks[lane] const laneDiscardPile = state.laneDiscardPiles[lane] @@ -129,7 +134,7 @@ export function doCombat(state: State, lane: Lane): { status: StatusCode.Okay | const attackerBouncesDiscarded: `?${CardStringSuit}`[] = [] if ( - attackerBounceIndexes.length || defenderBounceIndexes.length || (!attackerAttackPower && !defenderAttackPower) + attackerBounceIndexes.length || defenderBounceIndexes.length || !(attackerAttackPower || defenderAttackPower) ) { for (const index of defenderBounceIndexes.reverse()) { const bounceDiscarded = defenderStack.splice(index, 1)[0]! diff --git a/src/doMove.ts b/src/doMove.ts index 5e6d2b4..1c1cce9 100644 --- a/src/doMove.ts +++ b/src/doMove.ts @@ -1,3 +1,6 @@ +import type { LaxPartial } from "@samual/lib" +import type { CardString, Lane, Move, ShuffleFunction, State } from "./common" +import { AttackerDeck, MoveTag, StatusCode } from "./common" import type { CombatData } from "./doCombat" import { doMoveCombat } from "./doMoveCombat" import { doMoveDiscard } from "./doMoveDiscard" @@ -5,10 +8,8 @@ import { doMoveDraw } from "./doMoveDraw" import { doMovePass } from "./doMovePass" import { doMovePlay } from "./doMovePlay" import { doMovePlayFaceUp } from "./doMovePlayFaceUp" -import type { CardString, Lane, Move, State } from "./common" -import { AttackerDeck, MoveTag, StatusCode } from "./common" -export function doMove(state: State, move: Move): { +export function doMove(state: State, move: Move, options?: LaxPartial<{ shuffleFunction: ShuffleFunction }>): { status: StatusCode.Okay | StatusCode.AttackerWin | StatusCode.DefenderWin binlog: string[] } | { status: Exclude } { @@ -18,7 +19,7 @@ export function doMove(state: State, move: Move): { switch (move.tag) { case MoveTag.Draw: { const deckIsEmpty = !(move.deck == AttackerDeck ? state.attackerDeck : state.laneDecks[move.deck]).length - const result = doMoveDraw(state, move.deck) + const result = doMoveDraw(state, move.deck, options) const deck = move.deck == AttackerDeck ? `a` : move.deck if (result.status == StatusCode.AttackerWin) { @@ -63,7 +64,7 @@ export function doMove(state: State, move: Move): { } case MoveTag.PlayFaceUp: { - const result = doMovePlayFaceUp(state, move.card, move.lane) + const result = doMovePlayFaceUp(state, move.card, move.lane, options) if (result.status == StatusCode.Okay || result.status == StatusCode.AttackerWin || result.status == StatusCode.DefenderWin) { const binlog = [ @@ -84,7 +85,7 @@ export function doMove(state: State, move: Move): { } case MoveTag.Combat: { - const result = doMoveCombat(state, move.lane) + const result = doMoveCombat(state, move.lane, options) if (result.status == StatusCode.Okay || result.status == StatusCode.DefenderWin || result.status == StatusCode.AttackerWin) { const binlog = [ @@ -104,7 +105,7 @@ export function doMove(state: State, move: Move): { } case MoveTag.Discard: { - const result = doMoveDiscard(state, move.card, move.discardPile) + const result = doMoveDiscard(state, move.card, move.discardPile, options) if (result.status == StatusCode.Okay || result.status == StatusCode.DefenderWin) { const discardPile = move.discardPile == AttackerDeck ? `a` : move.discardPile diff --git a/src/doMoveCombat.ts b/src/doMoveCombat.ts index 9ceb75d..cd0245c 100644 --- a/src/doMoveCombat.ts +++ b/src/doMoveCombat.ts @@ -1,9 +1,10 @@ +import type { LaxPartial } from "@samual/lib" +import type { Lane, ShuffleFunction, State } from "./common" +import { Role, StatusCode } from "./common" import type { CombatData } from "./doCombat" import { doCombat } from "./doCombat" -import type { Lane, State } from "./common" -import { Role, StatusCode } from "./common" -export function doMoveCombat(state: State, lane: Lane): ( +export function doMoveCombat(state: State, lane: Lane, options?: LaxPartial<{ shuffleFunction: ShuffleFunction }>): ( { status: StatusCode.Okay | StatusCode.DefenderWin | StatusCode.AttackerWin } & CombatData ) | { status: @@ -22,7 +23,7 @@ export function doMoveCombat(state: State, lane: Lane): ( if (!state.attackerStacks[lane].length) return { status: StatusCode.AttackerInitiatedCombatWithEmptyStack } - const combatResult = doCombat(state, lane) + const combatResult = doCombat(state, lane, options) if (combatResult.status == StatusCode.AttackerWin) return combatResult diff --git a/src/doMoveDiscard.ts b/src/doMoveDiscard.ts index 60ae71c..1ba8a0c 100644 --- a/src/doMoveDiscard.ts +++ b/src/doMoveDiscard.ts @@ -1,8 +1,14 @@ -import { shuffle } from "@samual/lib/shuffle" -import type { CardString, CardStringFace, Lane, State } from "./common" +import type { LaxPartial } from "@samual/lib" +import { shuffle as defaultShuffleFunction } from "@samual/lib/shuffle" +import type { CardString, CardStringFace, Lane, ShuffleFunction, State } from "./common" import { AttackerDiscardPile, Role, StatusCode } from "./common" -export function doMoveDiscard(state: State, card: CardString | CardStringFace, discardPile: Lane | AttackerDiscardPile): { +export function doMoveDiscard( + state: State, + card: CardString | CardStringFace, + discardPile: Lane | AttackerDiscardPile, + { shuffleFunction: shuffle = defaultShuffleFunction }: LaxPartial<{ shuffleFunction: ShuffleFunction }> = {} +): { status: StatusCode.Okay | StatusCode.DefenderWin cardDiscarded: CardString cardsDrawn: [ CardString, CardString ] | undefined diff --git a/src/doMoveDraw.ts b/src/doMoveDraw.ts index 1ec12cc..4c4519f 100644 --- a/src/doMoveDraw.ts +++ b/src/doMoveDraw.ts @@ -1,8 +1,13 @@ -import { shuffle } from "@samual/lib/shuffle" -import type { CardString, Lane, State } from "./common" +import type { LaxPartial } from "@samual/lib" +import { shuffle as defaultShuffleFunction } from "@samual/lib/shuffle" +import type { CardString, Lane, ShuffleFunction, State } from "./common" import { AttackerDeck, AttackerDiscardPile, Role, StatusCode } from "./common" -export function doMoveDraw(state: State, deckToDrawFrom: Lane | AttackerDeck): { +export function doMoveDraw( + state: State, + deckToDrawFrom: Lane | AttackerDeck, + { shuffleFunction: shuffle = defaultShuffleFunction }: LaxPartial<{ shuffleFunction: ShuffleFunction }> = {} +): { status: StatusCode.Okay | StatusCode.DefenderWin cardDrawn: CardString } | { diff --git a/src/doMovePlayFaceUp.ts b/src/doMovePlayFaceUp.ts index 1ceffb5..03a5cf3 100644 --- a/src/doMovePlayFaceUp.ts +++ b/src/doMovePlayFaceUp.ts @@ -1,10 +1,16 @@ +import { LaxPartial } from "@samual/lib" import { assert } from "@samual/lib/assert" +import type { CardString, CardStringFace, Lane, ShuffleFunction, State } from "./common" +import { CardStringFaceModifier, Role, StatusCode } from "./common" import type { CombatData } from "./doCombat" import { doCombat } from "./doCombat" -import type { CardString, CardStringFace, Lane, State } from "./common" -import { CardStringFaceModifier, Role, StatusCode } from "./common" -export function doMovePlayFaceUp(state: State, card: CardString | CardStringFace, lane: Lane): { +export function doMovePlayFaceUp( + state: State, + card: CardString | CardStringFace, + lane: Lane, + options?: LaxPartial<{ shuffleFunction: ShuffleFunction }> +): { status: StatusCode.Okay | StatusCode.DefenderWin | StatusCode.AttackerWin cardPlayed: CardString combat: CombatData | undefined @@ -43,7 +49,7 @@ export function doMovePlayFaceUp(state: State, card: CardString | CardStringFace let status - ({ status, ...combat } = doCombat(state, lane)) + ({ status, ...combat } = doCombat(state, lane, options)) if (status == StatusCode.AttackerWin) return { status, cardPlayed, combat } @@ -71,7 +77,7 @@ export function doMovePlayFaceUp(state: State, card: CardString | CardStringFace let status - ({ status, ...combat } = doCombat(state, lane)) + ({ status, ...combat } = doCombat(state, lane, options)) if (status == StatusCode.AttackerWin) return { status, cardPlayed, combat } @@ -81,7 +87,7 @@ export function doMovePlayFaceUp(state: State, card: CardString | CardStringFace let status - ({ status, ...combat } = doCombat(state, lane)) + ({ status, ...combat } = doCombat(state, lane, options)) assert(status != StatusCode.AttackerWin, `attacker won when playing a face up bounce`) } else return { status: StatusCode.PlayedCardFacedWrongWay } diff --git a/src/simulateGame.ts b/src/simulateGame.ts index f6b9a52..c811782 100644 --- a/src/simulateGame.ts +++ b/src/simulateGame.ts @@ -4,7 +4,7 @@ import type { BinmatArgs } from "./generateArgs" import { generateArgsForAttacker, generateArgsForDefender } from "./generateArgs" import { makeState } from "./makeState" import { parseMove } from "./parseMove" -import type { MoveString, State } from "./common" +import type { MoveString, ShuffleFunction, State } from "./common" import { MoveTag, Role, StatusCode, StatusCodeMessages } from "./common" export type SimulateGameOptions = { @@ -23,6 +23,8 @@ export type SimulateGameOptions = { /** Intial state of binlog from attacker's last turn (should be odd numbered turn) @default [] */ attackerBinlog: string[] + + shuffleFunction: ShuffleFunction } export type CLIContext = { @@ -56,7 +58,8 @@ export function simulateGame( onMove, state = makeState(), defenderBinlog = [], - attackerBinlog = [] + attackerBinlog = [], + shuffleFunction }: LaxPartial = {} ): Role { let endTime: number @@ -157,7 +160,7 @@ export function simulateGame( throw error } - const result = doMove(state, move) + const result = doMove(state, move, { shuffleFunction }) switch (result.status) { case StatusCode.Okay: break @@ -187,7 +190,7 @@ export function simulateGame( } function doDefaultMove() { - const result = doMove(state, { tag: MoveTag.Pass }) + const result = doMove(state, { tag: MoveTag.Pass }, { shuffleFunction }) switch (result.status) { case StatusCode.Okay: break