This commit is contained in:
Pavel Flegr 2026-05-07 19:10:23 +02:00
commit ff5d6ab91d
7 changed files with 388 additions and 84 deletions

View file

@ -34,12 +34,17 @@ import (
// reverseDir bool — toggle turn-order direction (false = clockwise, true = counter-clockwise)
// choicePrompt string — optional UI prompt shown to current player
// choiceOptions string[] — allowed answer options for that prompt
// choiceTargetPlayerIndex number — optional target player for the choice prompt
// discardIndex number — optional index of one extra card to discard from current hand
// playerHands Card[][] — optional full replacement of all players' hands
// discardPile Card[] — optional full replacement of played/discard pile
// playedPile Card[] — alias for discardPile
// deck Card[] — optional full replacement of draw deck
// currentPlayerIndex number — optional current player override
//
// state also includes richer context for advanced rules:
//
// turnNumber, roundNumber, lastActionType, lastActorIndex, pendingChoices
const DefaultScript = `
// ── Standard Prší rules ───────────────────────────────────────────────
@ -96,30 +101,43 @@ type GameScriptState struct {
PlayerCount int
HandCounts []int // cards held by each player, in seat order
Direction int // 1 = clockwise, -1 = counter-clockwise
TurnNumber int
RoundNumber int
LastActionType string
LastActorIndex int
PendingChoices []ScriptPendingChoice
}
type ScriptPendingChoice struct {
TargetPlayerIndex int
Prompt string
Options []string
}
// OnPlayedResult carries every mutation onPlayed() can request.
// All fields are optional; the engine only applies what the script explicitly returns.
type OnPlayedResult struct {
PenaltyCards int
SkipNext bool
ActiveSuit Suit
InstantWin bool // current player wins immediately
ExtraTurn bool // current player takes another turn
SetReverseDir bool // whether to apply the ReverseDir value
ReverseDir bool // new direction: false=CW, true=CCW (only used when SetReverseDir=true)
ChoicePrompt string
ChoiceOptions []string
SetDiscardIdx bool
DiscardIdx int
SetPlayerHands bool
PlayerHands [][]Card
SetDiscardPile bool
DiscardPile []Card
SetDeck bool
Deck []Card
SetCurrentPlayer bool
CurrentPlayer int
PenaltyCards int
SkipNext bool
ActiveSuit Suit
InstantWin bool // current player wins immediately
ExtraTurn bool // current player takes another turn
SetReverseDir bool // whether to apply the ReverseDir value
ReverseDir bool // new direction: false=CW, true=CCW (only used when SetReverseDir=true)
ChoicePrompt string
ChoiceOptions []string
SetDiscardIdx bool
DiscardIdx int
SetPlayerHands bool
PlayerHands [][]Card
SetDiscardPile bool
DiscardPile []Card
SetDeck bool
Deck []Card
SetCurrentPlayer bool
CurrentPlayer int
SetChoiceTargetPlayer bool
ChoiceTargetPlayer int
}
// ScriptRuntime holds a compiled goja VM. NOT goroutine-safe.
@ -223,6 +241,18 @@ func (sr *ScriptRuntime) stateVal(s GameScriptState) goja.Value {
for i, n := range s.HandCounts {
counts[i] = n
}
pending := make([]interface{}, len(s.PendingChoices))
for i, ch := range s.PendingChoices {
opts := make([]interface{}, len(ch.Options))
for j, o := range ch.Options {
opts[j] = o
}
pending[i] = map[string]interface{}{
"targetPlayerIndex": ch.TargetPlayerIndex,
"prompt": ch.Prompt,
"options": opts,
}
}
return sr.vm.ToValue(map[string]interface{}{
"activeSuit": string(s.ActiveSuit),
"penaltyCards": s.PenaltyCards,
@ -234,6 +264,11 @@ func (sr *ScriptRuntime) stateVal(s GameScriptState) goja.Value {
"playerCount": s.PlayerCount,
"handCounts": counts,
"direction": s.Direction,
"turnNumber": s.TurnNumber,
"roundNumber": s.RoundNumber,
"lastActionType": s.LastActionType,
"lastActorIndex": s.LastActorIndex,
"pendingChoices": pending,
})
}
@ -319,6 +354,10 @@ func (sr *ScriptRuntime) parseResult(v goja.Value, defaults OnPlayedResult) OnPl
out.CurrentPlayer = int(cp.ToInteger())
out.SetCurrentPlayer = true
}
if tp := objGet(sr.vm, obj, "choiceTargetPlayerIndex"); tp != nil {
out.ChoiceTargetPlayer = int(tp.ToInteger())
out.SetChoiceTargetPlayer = true
}
return out
}