Added fuzzy tester.

This commit is contained in:
Daniel Schmidt 2023-05-01 11:24:08 +02:00
parent 0f05b9e052
commit 3eec5bfc7d
7 changed files with 271 additions and 11 deletions

View File

@ -14,7 +14,7 @@ register_status_effect("WEAKEN", {
decay = DECAY_ALL,
rounds = 1,
callbacks = {
on_damage_calc = function()
on_damage_calc = function(ctx)
if ctx.source == ctx.owner then
return ctx.damage - ctx.stacks * 2
end

View File

@ -103,7 +103,7 @@ register_story_teller("STAGE_2", {
-- BOSS
end
return nil
return GAME_STATE_FIGHT
end
})
@ -125,6 +125,6 @@ register_story_teller("STAGE_3", {
-- BOSS
end
return nil
return GAME_STATE_FIGHT
end
})

92
cmd/fuzzy-tester/main.go Normal file
View File

@ -0,0 +1,92 @@
package main
import (
"flag"
"fmt"
"github.com/BigJk/end_of_eden/game"
"github.com/samber/lo"
"math/rand"
"os"
"runtime/debug"
"strings"
"sync"
"time"
)
var endTime int64
var seed int64
var mods []string
func main() {
fmt.Println("End Of Eden :: Fuzzy Tester")
fmt.Println("The fuzzy tester hits a game session with a random number of operations and tries to trigger a panic.")
fmt.Println()
routines := flag.Int("n", 0, "number of goroutines")
timeout := flag.Duration("timeout", time.Minute, "length of testing")
baseSeed := flag.Int64("seed", 0, "random seed")
modsString := flag.String("mods", "", "mods to load and test, separated by ',' (e.g. mod1,mod2,mod3)")
flag.Parse()
if *routines == 0 {
flag.PrintDefaults()
return
}
seed = *baseSeed
endTime = time.Now().Add(*timeout).Unix()
if len(*modsString) > 0 {
mods = strings.Split(*modsString, ",")
}
if *baseSeed == 0 {
seed = rand.Int63()
}
fmt.Println("N :", *routines)
fmt.Println("Seed :", seed)
fmt.Println("\nWorking...")
wg := &sync.WaitGroup{}
for i := 0; i < *routines; i++ {
wg.Add(1)
tester(i, wg)
}
wg.Wait()
}
func tester(index int, wg *sync.WaitGroup) {
rnd := rand.New(rand.NewSource(seed + int64(index)))
opKeys := lo.Keys(Operations)
stack := [][]string{}
defer func() {
if r := recover(); r != nil {
fmt.Println(stack)
fmt.Println(r)
fmt.Println(string(debug.Stack()))
os.Exit(-1)
}
}()
for time.Now().Unix() < endTime {
s := game.NewSession(game.WithMods(mods))
ops := 5 + rand.Intn(100)
stack = [][]string{}
s.SetOnLuaError(func(file string, line int, callback string, typeId string, err error) {
fmt.Println("File :", file)
fmt.Println("Line :", line)
fmt.Println("Callback :", callback)
fmt.Println("TypeId :", typeId)
fmt.Println("Err :", err)
panic("lua error")
})
for i := 0; i < ops; i++ {
next := lo.Shuffle(opKeys)[0]
stack = append(stack, []string{next, Operations[next](rnd, s)})
}
}
}

View File

@ -0,0 +1,126 @@
package main
import (
"fmt"
"github.com/BigJk/end_of_eden/game"
"github.com/samber/lo"
"math/rand"
)
func Shuffle[T any](rnd *rand.Rand, collection []T) []T {
rnd.Shuffle(len(collection), func(i, j int) {
collection[i], collection[j] = collection[j], collection[i]
})
return collection
}
var Operations = map[string]func(rnd *rand.Rand, s *game.Session) string{
"FinishPlayerTurn": func(rnd *rand.Rand, s *game.Session) string {
s.FinishPlayerTurn()
return "Finish player turn"
},
"FinishFight": func(rnd *rand.Rand, s *game.Session) string {
s.FinishFight()
return "Finish fight"
},
"CastCard": func(rnd *rand.Rand, s *game.Session) string {
guid := Shuffle(rnd, lo.Flatten([][]string{{""}, s.GetInstances(), s.GetActors()}))[0]
target := Shuffle(rnd, lo.Flatten([][]string{{""}, s.GetInstances(), s.GetActors()}))[0]
s.CastCard(guid, target)
return fmt.Sprintf("Cast card with guid '%s' on '%s'", guid, target)
},
"AddActorFromEnemy": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
enemyId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Enemies)}))[0]
s.AddActorFromEnemy(enemyId)
return fmt.Sprintf("Added enemy '%s'", enemyId)
},
"SetEvent": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
eventId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Events)}))[0]
s.SetEvent(eventId)
return fmt.Sprintf("Set event '%s'", eventId)
},
"SetGameState": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
eventId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Events)}))[0]
s.SetGameState(Shuffle(rnd, []game.GameState{game.GameStateGameOver, game.GameStateMerchant, game.GameStateRandom, game.GameStateEvent, game.GameStateFight, game.GameState("")})[0])
return fmt.Sprintf("Set event '%s'", eventId)
},
"FinishEvent": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
eventId := Shuffle(rnd, lo.Flatten([][]string{lo.Keys(res.Events)}))[0]
event := res.Events[eventId]
choice := rnd.Intn(len(event.Choices) + 1)
s.FinishEvent(choice)
return fmt.Sprintf("Finish event '%s' with choice %d", eventId, choice)
},
"CleanUpFight": func(rnd *rand.Rand, s *game.Session) string {
s.CleanUpFight()
return "Clean up fight"
},
"SetupFight": func(rnd *rand.Rand, s *game.Session) string {
s.SetupFight()
return "Setup fight"
},
"SetupMerchant": func(rnd *rand.Rand, s *game.Session) string {
s.SetupMerchant()
return "Setup merchant"
},
"LeaveMerchant": func(rnd *rand.Rand, s *game.Session) string {
s.LeaveMerchant()
return "Leave merchant"
},
"GivePlayerGold": func(rnd *rand.Rand, s *game.Session) string {
gold := rnd.Intn(100)
s.GivePlayerGold(gold)
return fmt.Sprintf("Give %d gold to player", gold)
},
"PlayerGiveActionPoints": func(rnd *rand.Rand, s *game.Session) string {
actionPoints := rnd.Intn(5)
s.PlayerGiveActionPoints(actionPoints)
return fmt.Sprintf("Give %d action points to player", actionPoints)
},
"AddCard": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
cardId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Cards)}))[0]
s.GiveCard(cardId, game.PlayerActorID)
return fmt.Sprintf("Give '%s' card to player", cardId)
},
"AddArtifact": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
artifactId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Artifacts)}))[0]
s.GiveArtifact(artifactId, game.PlayerActorID)
return fmt.Sprintf("Give '%s' artifact to player", artifactId)
},
"PlayerBuyCard": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
cardId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Cards)}))[0]
s.PlayerBuyCard(cardId)
return fmt.Sprintf("Buy '%s' card as player", cardId)
},
"PlayerBuyArtifact": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
artifactId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.Artifacts)}))[0]
s.PlayerBuyArtifact(artifactId)
return fmt.Sprintf("Buy '%s' artifact as player", artifactId)
},
"AddStatusEffect": func(rnd *rand.Rand, s *game.Session) string {
res := s.GetResources()
effectId := Shuffle(rnd, lo.Flatten([][]string{{""}, lo.Keys(res.StatusEffects)}))[0]
stacks := rnd.Intn(10)
s.GiveStatusEffect(effectId, game.PlayerActorID, stacks)
return fmt.Sprintf("Give '%s' status effect with %d stacks to player", effectId, stacks)
},
"BuyUpgradeCard": func(rnd *rand.Rand, s *game.Session) string {
cardId := Shuffle(rnd, lo.Flatten([][]string{{""}, s.GetInstances()}))[0]
s.BuyUpgradeCard(cardId)
return fmt.Sprintf("Buy upgrading card '%s'", cardId)
},
"BuyRemoveCard": func(rnd *rand.Rand, s *game.Session) string {
cardId := Shuffle(rnd, lo.Flatten([][]string{{""}, s.GetInstances()}))[0]
s.BuyRemoveCard(cardId)
return fmt.Sprintf("Buy removing card '%s'", cardId)
},
}

View File

@ -35,7 +35,7 @@ register_artifact(
func TestArtifact(t *testing.T) {
s := lua.NewState()
man := NewResourcesManager(s, log.New(io.Discard, "", 0))
man := NewResourcesManager(s, nil, log.New(io.Discard, "", 0))
// Evaluate lua
if !assert.NoError(t, s.DoString(TestArtifactLua)) {

View File

@ -201,6 +201,10 @@ func (man *ResourcesManager) luaDeleteEvent(l *lua.LState) int {
}
func (man *ResourcesManager) defineDocs(docs *ludoc.Docs) {
if docs == nil {
return
}
docs.Category("Content Registry", "These functions are used to define new content in the base game and in mods.", 100)
docs.Function("register_artifact", fmt.Sprintf("Registers a new artifact.\n\n```lua\n%s\n```", `register_artifact("REPULSION_STONE",

View File

@ -264,6 +264,10 @@ func (s *Session) GobDecode(data []byte) error {
return nil
}
func (s *Session) GetResources() *ResourcesManager {
return s.resources
}
//
// Internal
//
@ -574,7 +578,7 @@ func (s *Session) FinishFight() bool {
return false
}
// FinishEvent finishes a event with the given choice. If the game state is not in the EVENT state this
// FinishEvent finishes an event with the given choice. If the game state is not in the EVENT state this
// does nothing.
func (s *Session) FinishEvent(choice int) {
if len(s.currentEvent) == 0 || s.state != GameStateEvent {
@ -849,7 +853,7 @@ func (s *Session) GetInstances() []string {
return lo.Keys(s.instances)
}
// GetInstance returns a instance by guid. An instance is a CardInstance or ArtifactInstance.
// GetInstance returns an instance by guid. An instance is a CardInstance or ArtifactInstance.
func (s *Session) GetInstance(guid string) any {
return s.instances[guid]
}
@ -983,6 +987,13 @@ func (s *Session) GiveStatusEffect(typeId string, owner string, stacks int) stri
}
status := s.resources.StatusEffects[typeId]
if status == nil {
return ""
}
if _, ok := s.actors[owner]; !ok {
return ""
}
// TODO: This should always be either 0 or 1 len, so the logic down below is a bit meh.
same := lo.Filter(s.actors[owner].StatusEffects.ToSlice(), func(guid string, index int) bool {
@ -995,7 +1006,7 @@ func (s *Session) GiveStatusEffect(typeId string, owner string, stacks int) stri
})
if len(same) > 1 {
log.Println("Error: status effect duplicate!")
panic("Error: status effect duplicate!")
}
// If it can't stack we delete all existing instances
@ -1035,7 +1046,11 @@ func (s *Session) GiveStatusEffect(typeId string, owner string, stacks int) stri
// RemoveStatusEffect removes a status effect by guid.
func (s *Session) RemoveStatusEffect(guid string) {
instance := s.instances[guid].(StatusEffectInstance)
instance, ok := s.instances[guid].(StatusEffectInstance)
if !ok {
return
}
if _, err := s.resources.StatusEffects[instance.TypeID].Callbacks[CallbackOnStatusRemove].Call(CreateContext("type_id", instance.TypeID, "guid", guid, "owner", instance.Owner)); err != nil {
s.logLuaError(CallbackOnStatusRemove, instance.TypeID, err)
}
@ -1056,7 +1071,11 @@ func (s *Session) GetActorStatusEffects(guid string) []string {
// AddStatusEffectStacks increases the stacks of a certain status effect by guid.
func (s *Session) AddStatusEffectStacks(guid string, stacks int) {
instance := s.instances[guid].(StatusEffectInstance)
instance, ok := s.instances[guid].(StatusEffectInstance)
if !ok {
return
}
instance.Stacks += stacks
if instance.Stacks <= 0 {
s.RemoveStatusEffect(guid)
@ -1067,7 +1086,11 @@ func (s *Session) AddStatusEffectStacks(guid string, stacks int) {
// SetStatusEffectStacks sets the stacks of a certain status effect by guid.
func (s *Session) SetStatusEffectStacks(guid string, stacks int) {
instance := s.instances[guid].(StatusEffectInstance)
instance, ok := s.instances[guid].(StatusEffectInstance)
if !ok {
return
}
instance.Stacks = stacks
if instance.Stacks <= 0 {
s.RemoveStatusEffect(guid)
@ -1136,6 +1159,10 @@ func (s *Session) GetArtifact(guid string) (*Artifact, ArtifactInstance) {
// GiveArtifact gives an artifact to an actor.
func (s *Session) GiveArtifact(typeId string, owner string) string {
if _, ok := s.resources.Artifacts[typeId]; !ok {
return ""
}
instance := ArtifactInstance{
TypeID: typeId,
GUID: NewGuid("ARTIFACT"),
@ -1186,6 +1213,10 @@ func (s *Session) GetCard(guid string) (*Card, CardInstance) {
}
func (s *Session) GiveCard(typeId string, owner string) string {
if _, ok := s.resources.Cards[typeId]; !ok {
return ""
}
instance := CardInstance{
TypeID: typeId,
GUID: NewGuid("CARD"),
@ -1353,6 +1384,10 @@ func (s *Session) UpgradeCard(guid string) bool {
//
func (s *Session) DealDamage(source string, target string, damage int, flat bool) int {
if _, ok := s.actors[source]; !ok {
return 0
}
val, ok := s.actors[target]
if !ok {
return 0
@ -1499,7 +1534,10 @@ func (s *Session) GetActors() []string {
}
func (s *Session) GetActor(id string) Actor {
return s.actors[id]
if val, ok := s.actors[id]; ok {
return val
}
return NewActor("")
}
func (s *Session) UpdateActor(id string, update func(actor *Actor) bool) {