diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-07-24 12:04:02 -0400 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-07-24 12:22:10 -0400 |
| commit | f674e565a212b49e82b0e8f04018b5d371ad062f (patch) | |
| tree | c213d9516fd253d48dc8b7c8c511107d40ce8bb3 | |
| parent | 450f4e8df494b9f2aeba5bc47d261d4547a962ab (diff) | |
| download | muhqs-game-f674e565a212b49e82b0e8f04018b5d371ad062f.tar.gz muhqs-game-f674e565a212b49e82b0e8f04018b5d371ad062f.zip | |
improve x-effect parsing
| -rw-r--r-- | go/game/card.go | 24 | ||||
| -rw-r--r-- | go/game/effect.go | 51 | ||||
| -rw-r--r-- | go/game/effect_test.go | 46 | ||||
| -rw-r--r-- | go/game/permanent.go | 44 | ||||
| -rw-r--r-- | go/game/tile.go | 2 | ||||
| -rw-r--r-- | go/game/unit_test.go | 9 |
6 files changed, 149 insertions, 27 deletions
diff --git a/go/game/card.go b/go/game/card.go index 921830cc..5ecce64c 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -2,7 +2,6 @@ package game import ( "errors" - "fmt" "io" "log" "net/http" @@ -219,7 +218,7 @@ func retrieveCardDefinition(cardPath string) ([]byte, error) { } func (card *Card) parseDefinition(definition []byte) { - data := make(map[interface{}]interface{}) + data := make(map[any]any) err := yaml.Unmarshal(definition, &data) if err != nil { log.Panicf("parsing %s failed with: %v\n", definition, err) @@ -234,7 +233,7 @@ func (card *Card) parseDefinition(definition []byte) { switch nameYml := name.(type) { case string: card.Name = nameYml - case map[string]interface{}: + case map[string]any: cardName := nameYml["en"] card.Name = cardName.(string) default: @@ -280,14 +279,14 @@ func (card *Card) getCanonicalValues(key string) []string { } switch typedValues := untypedValues.(type) { - case map[string]interface{}: + case map[string]any: enValues := typedValues["en"] switch typedEnValues := enValues.(type) { case string: values = []string{typedEnValues} case []string: values = typedEnValues - case []interface{}: + case []any: values = []string{} for _, v := range typedEnValues { values = append(values, v.(string)) @@ -316,20 +315,19 @@ func (card *Card) hasEffect(effect string) bool { return slices.Contains(card.getEffects(), effect) } -func (card *Card) getXEffect(effect string) (int, error) { +var ( + ErrNoXEffect = errors.New("card has no xeffect") +) + +func (card *Card) getXEffect(effect string) (xEffect, error) { for _, e := range card.getEffects() { if !strings.Contains(e, effect) { continue } - tokens := strings.SplitN(e, " ", 2) - xEffect := tokens[1] - if strings.HasSuffix(xEffect, ".") { - xEffect = tokens[1][:len(tokens[1])-1] - } - return strconv.Atoi(xEffect) + return parseXEffect(e) } - return 0, fmt.Errorf("Card %s has no effect %s", card.Name, effect) + return xEffect{}, ErrNoXEffect } func (card *Card) hasPlacementConstrain(effect string) bool { diff --git a/go/game/effect.go b/go/game/effect.go index 1fd2a630..4d89eea5 100644 --- a/go/game/effect.go +++ b/go/game/effect.go @@ -1,5 +1,11 @@ package game +import ( + "errors" + "regexp" + "strconv" +) + type effect interface { apply(*LocalState) end(*LocalState) @@ -45,3 +51,48 @@ func newTmpEffect(p Permanent, effect string) effect { end := func(*LocalState) { p.removeTmpEffect(effect) } return &dynamicEffect{apply, end} } + +type xEffect struct { + // amount of the effect + x int + // does the effect set or adjust the value + set bool + // raw effect description + raw string +} + +type ErrXEffectError struct { + err error +} + +func (e ErrXEffectError) Error() string { + return e.err.Error() +} + +var ErrXEffectFormat = ErrXEffectError{errors.New("invalid format")} + +var ( + xEffectXSuffix = regexp.MustCompile(`\S+ ([\+-]?)(\d+)`) + xEffectXPrefix = regexp.MustCompile(`([\+-]?)(\d+) \S+`) +) + +func parseXEffect(desc string) (e xEffect, err error) { + e.raw = desc + m := xEffectXSuffix.FindAllStringSubmatch(desc, -1) + if len(m) == 0 { + m = xEffectXPrefix.FindAllStringSubmatch(desc, -1) + } + + if len(m) != 0 { + e.set = m[0][1] == "" + + e.x, err = strconv.Atoi(m[0][1] + m[0][2]) + if err != nil { + return e, ErrXEffectError{err} + } + return e, nil + } + + // Both regex did not match + return e, ErrXEffectFormat +} diff --git a/go/game/effect_test.go b/go/game/effect_test.go new file mode 100644 index 00000000..53058e72 --- /dev/null +++ b/go/game/effect_test.go @@ -0,0 +1,46 @@ +package game + +import ( + "errors" + "testing" +) + +func TestParseXEffect(t *testing.T) { + e, err := parseXEffect("+1 armor") + if err != nil { + t.Fatal("unexpected", err) + } + if e.set { + t.Fatal("+ xEffect reports as setting") + } + if e.x != 1 { + t.Fatal("expected x == 1") + } + + e, err = parseXEffect("crew 3") + if err != nil { + t.Fatal("unexpected", err) + } + if !e.set { + t.Fatal("xEffect reports not as setting") + } + if e.x != 3 { + t.Fatal("expected x == 3") + } + + e, err = parseXEffect("foobar") + if err == nil || !errors.Is(err, ErrXEffectFormat) { + t.Fatal("expected format error") + } + + var err2 ErrXEffectError + e, err = parseXEffect("9999999989899999999 armor") + if err == nil || !errors.As(err, &err2) { + t.Fatal("expected format error") + } + + e, err = parseXEffect("crew 9999999989899999999") + if err == nil || !errors.As(err, &err2) { + t.Fatal("expected format error") + } +} diff --git a/go/game/permanent.go b/go/game/permanent.go index 1bb1a408..278524fa 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -3,7 +3,6 @@ package game import ( "fmt" "log" - "strconv" "strings" "golang.org/x/exp/slices" @@ -120,14 +119,15 @@ func FindPermanent(s *LocalState, desc string) Permanent { func DealDamage(src, dest Permanent, damage int) { actualDamage := damage - shield, err := dest.XEffect("shield") - if err != nil { - // TODO: implement piercing - - actualDamage -= shield - if actualDamage < 0 { - actualDamage = 0 + armor, err := dest.XEffect("armor") + if err == nil { + piercing, err := src.XEffect("piercing") + if err == nil { + piercing = 0 } + + reduction := max(armor-piercing, 0) + actualDamage = max(actualDamage-reduction, 0) } dest.adjustDamage(actualDamage) } @@ -148,8 +148,11 @@ func (p *permanentBase) removeTmpEffect(tmpEffect string) { p.tmpEffects = utils.RemoveFromUnorderedSlice[string](p.tmpEffects, tmpEffect) } +// Effects returnes a slice containing all static effects of a permanent. +// The new slice returned contains the card and temporary effetcs. func (p *permanentBase) Effects() []string { - return append(p.card.getEffects(), p.tmpEffects...) + effects := make([]string, 0, len(p.card.getEffects())+len(p.tmpEffects)) + return append(append(effects, p.card.getEffects()...), p.tmpEffects...) } func (p *permanentBase) HasEffect(effect string) bool { @@ -157,19 +160,34 @@ func (p *permanentBase) HasEffect(effect string) bool { } func (p *permanentBase) XEffect(effect string) (int, error) { - x, err := p.card.getXEffect(effect) + x := 0 + xE, err := p.card.getXEffect(effect) + if err != nil { + return 0, err + } + + x = xE.x + if x < 0 { + log.Panic("initial negativ x effect") + } for _, e := range p.tmpEffects { if !strings.Contains(e, effect) { continue } - tokens := strings.SplitN(e, " ", 2) - tmpX, err := strconv.Atoi(tokens[1]) + tmpX, err := parseXEffect(e) + if err != nil { return 0, err } - x += tmpX + + // TODO: implement layers + if tmpX.set { + x = tmpX.x + } else { + x += tmpX.x + } } return x, err diff --git a/go/game/tile.go b/go/game/tile.go index d5a256d9..ea4568ca 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -194,7 +194,7 @@ func (t *Tile) IsAvailableForCard(card *Card) bool { return false } - if len(t.Permanent.Pile()) == x { + if len(t.Permanent.Pile()) == x.x { return false } diff --git a/go/game/unit_test.go b/go/game/unit_test.go index 94fd3893..26ef3700 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -129,3 +129,12 @@ symbols: t.Fatal("6 attackable permanents expected not:", is) } } + +func TestKnightArmor(t *testing.T) { + a := NewUnit(NewCard("base/archer"), NewMockTile(), NewMockPlayer()) + k := NewUnit(NewCard("base/knight"), NewMockTile(), NewMockPlayer()) + DealDamage(a, k, a.Attack.DamageInRange(2)) + if k.Damage() != 0 { + t.Fatal("knight received damage from attack 1") + } +} |
