Play cards.
This commit is contained in:
parent
576a2dd69e
commit
45e1eeebf7
@ -2,11 +2,16 @@ package cardsim
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUncooperativeCards = errors.New("a milion cards refused to join the hand")
|
ErrUncooperativeCards = errors.New("a milion cards refused to join the hand")
|
||||||
|
ErrInvalidCard = errors.New("invalid card specified")
|
||||||
|
ErrInvalidChoice = errors.New("invalid choice specified")
|
||||||
|
ErrNotUrgent = errors.New("action not urgent when urgent card is available")
|
||||||
|
ErrNoActions = errors.New("no actions remaining")
|
||||||
|
|
||||||
WarningStalemate = errors.New("no actions can be taken")
|
WarningStalemate = errors.New("no actions can be taken")
|
||||||
)
|
)
|
||||||
@ -92,7 +97,10 @@ type Player[C StatsCollection] struct {
|
|||||||
ActionsPerTurn int
|
ActionsPerTurn int
|
||||||
ActionsRemaining int
|
ActionsRemaining int
|
||||||
|
|
||||||
// PermanentActions are an "extra hand" of cards that are not discarded when used.
|
// PermanentActions are an "extra hand" of cards that are not discarded when
|
||||||
|
// used. An Urgent PermanentAction does not block non-urgent actions and
|
||||||
|
// cards in hand from being used, but it can be used even when an urgent
|
||||||
|
// card is in the hand.
|
||||||
PermanentActions []Card[C]
|
PermanentActions []Card[C]
|
||||||
|
|
||||||
// InfoPanels lists informational views available to the player. The Prompt
|
// InfoPanels lists informational views available to the player. The Prompt
|
||||||
@ -197,7 +205,7 @@ func (p *Player[C]) Simulate() error {
|
|||||||
|
|
||||||
if p.DebugLevel > 0 && !errs.IsEmpty() {
|
if p.DebugLevel > 0 && !errs.IsEmpty() {
|
||||||
p.ChapterBreak()
|
p.ChapterBreak()
|
||||||
p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d errors and warnings:", len(errs.Errs)))
|
p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d ERRORS AND WARNINGS:", len(errs.Errs)))
|
||||||
for i, e := range errs.Errs {
|
for i, e := range errs.Errs {
|
||||||
yikes := " "
|
yikes := " "
|
||||||
if IsSeriousError(e) {
|
if IsSeriousError(e) {
|
||||||
@ -255,3 +263,128 @@ func (p *Player[C]) FillHand() error {
|
|||||||
|
|
||||||
return WarningTooFewCards
|
return WarningTooFewCards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasUrgentCards returns whether any cards in the Hand think they are Urgent.
|
||||||
|
func (p *Player[C]) HasUrgentCards() bool {
|
||||||
|
for _, c := range p.Hand {
|
||||||
|
if c.Urgent(p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnactCardUnchecked executes a card choice, removes it from the hand, and
|
||||||
|
// decrements the ActionsRemaining. It does not check for conflicting Urgent
|
||||||
|
// cards or already being out of actions. If no such card or card choice
|
||||||
|
// exists, this returns nil and ErrInvalidCard/ErrInvalidChoice. Otherwise, this returns
|
||||||
|
// the result of enacting the card. If enacting the card causes an error,
|
||||||
|
// the State becomes GameCrashed.
|
||||||
|
func (p *Player[C]) EnactCardUnchecked(cardIdx, choiceIdx int) (Message, error) {
|
||||||
|
if cardIdx < 0 || cardIdx >= len(p.Hand) {
|
||||||
|
return nil, fmt.Errorf("%w: no card #%d when %d cards in hand", ErrInvalidCard, cardIdx, len(p.Hand))
|
||||||
|
}
|
||||||
|
card := p.Hand[cardIdx]
|
||||||
|
var errs ErrorCollector
|
||||||
|
options, err := card.Options(p)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
errs.Add(err)
|
||||||
|
if choiceIdx < 0 || choiceIdx > len(options) {
|
||||||
|
errs.Add(fmt.Errorf("%w: no option #%d on card #%d with %d options", ErrInvalidCard, choiceIdx, cardIdx, len(options)))
|
||||||
|
return nil, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Hand = DeleteFrom(p.Hand, cardIdx)
|
||||||
|
p.ActionsRemaining--
|
||||||
|
|
||||||
|
ret, err := options[choiceIdx].Enact(p)
|
||||||
|
errs.Add(err)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
return ret, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = card.Then(p, options[choiceIdx])
|
||||||
|
errs.Add(err)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
}
|
||||||
|
return ret, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnactCard executes a card choice, removes it from the hand, and decrements
|
||||||
|
// the ActionsRemaining. If the card is not Urgent but urgent cards are
|
||||||
|
// available, or the player is out of actions, this returns ErrNotUrgent or
|
||||||
|
// ErrNoActions. Otherwise, this acts like EnactCardUnchecked.
|
||||||
|
func (p *Player[C]) EnactCard(cardIdx, choiceIdx int) (Message, error) {
|
||||||
|
if p.ActionsRemaining <= 0 {
|
||||||
|
return nil, ErrNoActions
|
||||||
|
}
|
||||||
|
if cardIdx < 0 || cardIdx >= len(p.Hand) {
|
||||||
|
return nil, fmt.Errorf("%w: no card #%d when %d cards in hand", ErrInvalidCard, cardIdx, len(p.Hand))
|
||||||
|
}
|
||||||
|
if !p.Hand[cardIdx].Urgent(p) && p.HasUrgentCards() {
|
||||||
|
return nil, ErrNotUrgent
|
||||||
|
}
|
||||||
|
return p.EnactCardUnchecked(cardIdx, choiceIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnactPermanentActionUnchecked executes a permanently-available action and
|
||||||
|
// decrements the ActionsRemaining. It does not check for conflicting Urgent
|
||||||
|
// cards or already being out of actions. If no such action or card option
|
||||||
|
// exists, this returns nil and ErrInvalidCard/ErrInvalidChoice. Otherwise, this returns
|
||||||
|
// the result of enacting the permanent action. If enacting the card causes an error,
|
||||||
|
// the State becomes GameCrashed.
|
||||||
|
func (p *Player[C]) EnactPermanentActionUnchecked(actionIdx, choiceIdx int) (Message, error) {
|
||||||
|
if actionIdx < 0 || actionIdx >= len(p.PermanentActions) {
|
||||||
|
return nil, fmt.Errorf("%w: no action #%d when %d permanent actions exist", ErrInvalidCard, actionIdx, len(p.PermanentActions))
|
||||||
|
}
|
||||||
|
card := p.PermanentActions[actionIdx]
|
||||||
|
var errs ErrorCollector
|
||||||
|
options, err := card.Options(p)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
errs.Add(err)
|
||||||
|
if choiceIdx < 0 || choiceIdx > len(options) {
|
||||||
|
errs.Add(fmt.Errorf("%w: no option #%d on permanent action #%d with %d options", ErrInvalidChoice, choiceIdx, actionIdx, len(options)))
|
||||||
|
return nil, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ActionsRemaining--
|
||||||
|
|
||||||
|
ret, err := options[choiceIdx].Enact(p)
|
||||||
|
errs.Add(err)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
return ret, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = card.Then(p, options[choiceIdx])
|
||||||
|
errs.Add(err)
|
||||||
|
if IsSeriousError(err) {
|
||||||
|
p.State = GameCrashed
|
||||||
|
}
|
||||||
|
return ret, errs.Emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnactPermanentAction executes a permanently-available card and decrements
|
||||||
|
// the ActionsRemaining. If the action is not Urgent but urgent cards are
|
||||||
|
// available, or the player is out of actions, this returns ErrNotUrgent or
|
||||||
|
// ErrNoActions. Otherwise, this acts like EnactPermanentActionUnchecked.
|
||||||
|
func (p *Player[C]) EnactPermanentAction(actionIdx, choiceIdx int) (Message, error) {
|
||||||
|
if p.ActionsRemaining <= 0 {
|
||||||
|
return nil, ErrNoActions
|
||||||
|
}
|
||||||
|
if actionIdx < 0 || actionIdx >= len(p.PermanentActions) {
|
||||||
|
return nil, fmt.Errorf("%w: no action #%d when %d permanent actions available", ErrInvalidCard, actionIdx, len(p.PermanentActions))
|
||||||
|
}
|
||||||
|
if !p.PermanentActions[actionIdx].Urgent(p) && p.HasUrgentCards() {
|
||||||
|
return nil, ErrNotUrgent
|
||||||
|
}
|
||||||
|
return p.EnactPermanentActionUnchecked(actionIdx, choiceIdx)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user