From 372113c8a40af71e25335479c3642617159613ef Mon Sep 17 00:00:00 2001 From: Stevo John <4673357+stevehjohn@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:39:11 +0100 Subject: [PATCH 1/2] Teach WillyBot to adapt route after deaths --- .../ProcessorHooks/ManicMiner/RoutePlanner.cs | 45 ++++++++++++++-- .../ProcessorHooks/ManicMiner/WillyBot.cs | 52 +++++++++++++++++-- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs index 532f9c40..4bfe53be 100644 --- a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs +++ b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs @@ -9,9 +9,12 @@ public class RoutePlanner private readonly PriorityQueue<(int X, int Y, List Moves, HashSet<(int X, int Y)> Keys, HashSet<(int X, int Y)> Visited, int Steps), int> _queue = new(); - public RoutePlanner(int level, Interface @interface) + private readonly HashSet<(int X, int Y)> _deathCells; + + public RoutePlanner(int level, Interface @interface, IEnumerable<(int X, int Y)> deathCells = null) { _levelData = new LevelData(level, @interface); + _deathCells = deathCells == null ? [] : [..deathCells]; } public void Initialise() @@ -46,7 +49,9 @@ public List GetNextRoute() continue; } - _queue.Enqueue((move.X, move.Y, [..node.Moves, move.Move], node.Keys, [..node.Visited, (move.X, move.Y)], node.Steps + 1), node.Steps + 1); + var penalty = GetDeathPenalty(move.X, move.Y); + + _queue.Enqueue((move.X, move.Y, [..node.Moves, move.Move], [..node.Keys], [..node.Visited, (move.X, move.Y)], node.Steps + 1), node.Steps + 1 + penalty); } } @@ -115,7 +120,7 @@ private bool IsComplete((int X, int Y, List Moves, HashSet<(int X, int Y)> if (TryWalk(x, y, 2)) { - moves.Add((Move.Left, x + 2, y)); + moves.Add((Move.Right, x + 2, y)); } var jump = TryJump(x, y, -2); @@ -129,7 +134,7 @@ private bool IsComplete((int X, int Y, List Moves, HashSet<(int X, int Y)> if (jump.Safe) { - moves.Add((Move.UpLeft, jump.X, jump.Y)); + moves.Add((Move.UpRight, jump.X, jump.Y)); } return moves; @@ -247,6 +252,38 @@ private bool CheckSafe(int x, int y) return true; } + private int GetDeathPenalty(int x, int y) + { + if (_deathCells.Count == 0) + { + return 0; + } + + var cell = (x / 8, y / 8); + + if (_deathCells.Contains(cell)) + { + return 10000; + } + + foreach (var deathCell in _deathCells) + { + var distance = System.Math.Abs(deathCell.X - cell.Item1) + System.Math.Abs(deathCell.Y - cell.Item2); + + if (distance <= 2) + { + return 1000; + } + + if (distance <= 4) + { + return 250; + } + } + + return 0; + } + private bool CheckOpen(int x, int y) { var cell = _levelData.Map[x / 8, y / 8]; diff --git a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/WillyBot.cs b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/WillyBot.cs index f9cd2ac5..f4f2e15f 100644 --- a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/WillyBot.cs +++ b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/WillyBot.cs @@ -13,6 +13,8 @@ public class WillyBot : IProcessorHook private readonly Queue _route = new(); private Move _move; + + private readonly Dictionary> _deathsByLevel = new(); public bool Activate(State state) { @@ -37,6 +39,8 @@ public void PassiveCycle(State state, Interface @interface) case 0x88FF: // Dead + LearnFromDeath(@interface); + RestartLevel(); break; @@ -121,7 +125,7 @@ public void PassiveCycle(State state, Interface @interface) var grounded = @interface.ReadFromMemory(0x806B) == 0; - if (grounded) + if (grounded && _route.Count > 0) { _move = _route.Dequeue(); } @@ -132,7 +136,7 @@ public void PassiveCycle(State state, Interface @interface) private void StartLevel(Interface @interface) { - _routePlanner = new RoutePlanner(_level, @interface); + _routePlanner = new RoutePlanner(_level, @interface, GetDeathCells(_level)); _routePlanner.Initialise(); @@ -142,7 +146,47 @@ private void StartLevel(Interface @interface) private void RestartLevel() { _route.Clear(); - - _routePlanner.GetNextRoute().ForEach(m => _route.Enqueue(m)); + + var route = _routePlanner.GetNextRoute(); + + if (route == null) + { + return; + } + + route.ForEach(m => _route.Enqueue(m)); + } + + private void LearnFromDeath(Interface @interface) + { + var cellPointer = @interface.ReadFromMemory(0x806C) + (@interface.ReadFromMemory(0x806D) << 8); + + if (cellPointer < 0x5C00) + { + return; + } + + var cellOffset = cellPointer - 0x5C00; + + var x = cellOffset % 32; + var y = @interface.ReadFromMemory(0x8068) / 2 / 8; + + if (!_deathsByLevel.TryGetValue(_level, out var cells)) + { + cells = []; + _deathsByLevel[_level] = cells; + } + + cells.Add((x, y)); + } + + private IEnumerable<(int X, int Y)> GetDeathCells(int level) + { + if (_deathsByLevel.TryGetValue(level, out var cells)) + { + return cells; + } + + return []; } } \ No newline at end of file From 35b9a6c5d3fb7662a20256fcc2fa7aecb0470df4 Mon Sep 17 00:00:00 2001 From: Stevo John Date: Tue, 14 Apr 2026 15:41:19 +0100 Subject: [PATCH 2/2] Fix --- src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs index 4bfe53be..ef7fe43d 100644 --- a/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs +++ b/src/Zen.Desktop.Host/ProcessorHooks/ManicMiner/RoutePlanner.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Zen.Z80.Processor; @@ -268,7 +269,7 @@ private int GetDeathPenalty(int x, int y) foreach (var deathCell in _deathCells) { - var distance = System.Math.Abs(deathCell.X - cell.Item1) + System.Math.Abs(deathCell.Y - cell.Item2); + var distance = Math.Abs(deathCell.X - cell.Item1) + Math.Abs(deathCell.Y - cell.Item2); if (distance <= 2) {