diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2024-12-27 19:10:16 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:57:13 +0200 |
| commit | bfab1b8f10ce006dd4822ac3e82125ec226597f5 (patch) | |
| tree | ff01df5992cbcfe730a88a210a746dd1a17ca5b4 | |
| parent | 5c3dbd266bbd6732088d08480bc973e112ce6d99 (diff) | |
| download | muhqs-game-bfab1b8f10ce006dd4822ac3e82125ec226597f5.tar.gz muhqs-game-bfab1b8f10ce006dd4822ac3e82125ec226597f5.zip | |
finish the shy AI
| -rw-r--r-- | go/TODO | 2 | ||||
| -rw-r--r-- | go/game/ai.go | 63 | ||||
| -rw-r--r-- | go/game/ai_test.go | 61 | ||||
| -rw-r--r-- | go/game/unit_test.go | 2 |
4 files changed, 114 insertions, 14 deletions
@@ -8,4 +8,4 @@ * implement draft * implement game log * finish AIs - * shy ai + * tyrant diff --git a/go/game/ai.go b/go/game/ai.go index ec535182..415f01e0 100644 --- a/go/game/ai.go +++ b/go/game/ai.go @@ -258,6 +258,43 @@ func moveToRandomTile(ai *UnitAI) Action { return a } +func moveAwayFromEnemies(ai *UnitAI) Action { + if ai.u.AvailMoveActions == 0 { + return nil + } + + graph := ai.s.Map().generateMapGraphFor(ai.u) + var farest *Tile + var farestEnemyDistance int64 + + for _, availableTile := range ai.u.MoveRangeTiles(ai.s.Map()) { + var nearestEnemy int64 = -1 + + for _, enemyUnit := range ai.s.EnemyUnits(ai.u.Controller()) { + p, err := findPathTo(graph, enemyUnit, availableTile.Position) + if err != nil { + continue + } + if nearestEnemy == -1 || p.Distance < nearestEnemy { + nearestEnemy = p.Distance + } + } + + if farest == nil || farestEnemyDistance < nearestEnemy { + farest = availableTile + farestEnemyDistance = nearestEnemy + } + } + + if farest == nil { + return nil + } + + a := NewMoveAction(ai.u) + a.Target().AddSelection(farest) + return a +} + func actionFromPaths(ai *UnitAI, graph *dijkstra.Graph, paths []*dijkstra.BestPath) Action { nearest := 0 for i, p := range paths { @@ -332,7 +369,7 @@ func attackAttackableEnemyPerm(ai *UnitAI) Action { return a } -func fullAction(ai *UnitAI) { +func fullAction(ai *UnitAI) Action { fullActions := ai.u.FullActions idx := 0 if len(fullActions) > 1 { @@ -340,10 +377,14 @@ func fullAction(ai *UnitAI) { } a := fullActions[idx] - err := selectRandomTargets(ai.s.Rand, a.Targets()) - if err == nil { - ai.actions <- a + if a.Targets() != nil && a.Targets().RequireSelection() { + err := selectRandomTargets(ai.s.Rand, a.Targets()) + if err == nil { + return nil + } } + + return a } // 1. If enemy Unit in attack Range then @@ -399,13 +440,17 @@ out: } } if !inEnemyAttackRange && ai.u.HasFullAction() { - fullAction(ai) + ai.actions <- fullAction(ai) + return } // 2. - // TODO: implement path finding - // if u.AvailMoveActions > 0 { - // } + if ai.u.AvailMoveActions > 0 { + a := moveAwayFromEnemies(ai) + if !ai.PostAndContinue(a) { + return + } + } // 3. if a := attackAttackableEnemyPerm(ai); a != nil { @@ -452,7 +497,7 @@ func WanderingAI(ai *UnitAI, x int) { func TargetOrientedAI(ai *UnitAI) { // 1. if ai.u.HasFullAction() { - fullAction(ai) + ai.actions <- fullAction(ai) return } diff --git a/go/game/ai_test.go b/go/game/ai_test.go index 880c1efc..2d3a6a32 100644 --- a/go/game/ai_test.go +++ b/go/game/ai_test.go @@ -65,6 +65,62 @@ symbols: } } +func TestClowFish(t *testing.T) { + mapDef := `map: |1- + WWWWW + WWWWW + WWWWW + WWWWW + WWWWW +symbols: + W: deep water +` + r := strings.NewReader(mapDef) + m, _ := readMap(r) + s := NewLocalState() + s.SetMap(m) + p1 := s.AddNewPlayer("kraken", NewDeck(), nil) + p2 := s.AddNewPlayer("player", NewDeck(), nil) + c := s.addNewUnit(NewCard("kraken/clownfish"), Position{2, 2}, p1) + s.addNewUnit(NewCard("kraken/sailfish"), Position{1, 1}, p2) + s.addNewUnit(NewCard("kraken/sailfish"), Position{3, 2}, p2) + + ai := NewUnitAI(s, c) + if ai == nil { + t.Fatal("No AI available for Bauernfeind") + } + + ai.promptAction() + a := <-ai.actions + if a == nil { + t.Fatal("No action reported") + } + + var ma *MoveAction + var ok bool + if ma, ok = a.(*MoveAction); !ok { + t.Fatal("AI did not move flee from enemies") + } + + if tile, ok := ma.targets.Cur().sel[0].(*Tile); ok { + if tile != m.TileAt(Position{1, 4}) { + t.Fatalf("Unexpected clownfish move: expected tile(1,4), got: %s", tile) + } + } else { + t.Fatal("Target is not a Tile") + } + ma.resolve(s) + + ai = NewUnitAI(s, c) + ai.promptAction() + a = <-ai.actions + if a == nil { + t.Fatal("No action reported") + } + if _, ok := a.(*FullAction); !ok { + t.Fatal("Unthreatend AI did not use a full action") + } +} func TestBauernFeind(t *testing.T) { mapDef := `map: |1- @@ -72,7 +128,6 @@ func TestBauernFeind(t *testing.T) { W WWW - symbols: W: wall ` @@ -91,7 +146,7 @@ symbols: } bAi.promptAction() - a := <- bAi.actions + a := <-bAi.actions if a == nil { t.Fatal("No action reported") } @@ -103,7 +158,7 @@ symbols: } if tile, ok := ma.targets.Cur().sel[0].(*Tile); ok { - if tile != m.TileAt(Position{3,2}) { + if tile != m.TileAt(Position{3, 2}) { t.Fatalf("Unexpected Bauernfeind move: expected tile(3,2), got: %s", tile) } } else { diff --git a/go/game/unit_test.go b/go/game/unit_test.go index fd9b95b4..68fadea4 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -19,7 +19,7 @@ symbols: ` s := NewLocalState() r := strings.NewReader(mapDef) - m, _ := readMap(r) + m, _ := readMap(r) s.SetMap(m) s.AddNewPlayer("player", NewDeck(), nil) |
