aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-07-24 12:04:02 -0400
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:57:29 +0200
commitd7b595d3d116429d442f6b03d6a1a19ab58486ad (patch)
tree3272a6422c1b6b2b7b3e13cb32025e3c1d504c71
parent9b16d9cf5fb258d188f4cda74eca90535064054a (diff)
downloadmuhqs-game-d7b595d3d116429d442f6b03d6a1a19ab58486ad.tar.gz
muhqs-game-d7b595d3d116429d442f6b03d6a1a19ab58486ad.zip
improve x-effect parsing
-rw-r--r--go/game/card.go24
-rw-r--r--go/game/effect.go51
-rw-r--r--go/game/effect_test.go46
-rw-r--r--go/game/permanent.go44
-rw-r--r--go/game/tile.go2
-rw-r--r--go/game/tile_test.go5
-rw-r--r--go/game/unit_test.go9
7 files changed, 154 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/tile_test.go b/go/game/tile_test.go
new file mode 100644
index 00000000..a8a68360
--- /dev/null
+++ b/go/game/tile_test.go
@@ -0,0 +1,5 @@
+package game
+
+func NewMockTile() *Tile {
+ return &Tile{}
+}
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")
+ }
+}