A WinForms game built to demonstrate the Memento design pattern. The course asked for a pattern demonstration — this is a fully playable coin collector with procedurally generated maps, a BFS-driven enemy, and a save/load system built entirely around Memento.
The Memento pattern separates the responsibility of saving state from the object that owns it. Game knows how to create a snapshot of itself via Save() and restore from one via Load(). Caretaker holds the list of snapshots and decides which one to restore — but
never reads or modifies the snapshot's contents directly.
Form1 triggers saves and loads through Caretaker without ever touching Game's internal
state. Three save operations are available: Q to manually save the current state,
C to load the last save, and R to jump back to the very first save —
the state of the map when it was generated. If the player runs out of health the game automatically
loads the last save.
Memento is a nested class inside Game with internal constructors and getters — only Game can
create or read a snapshot. Caretaker holds references to them but cannot
inspect their contents, enforcing the pattern at the language level.
public class Caretaker { private Game _game; private List<Game.Memento> _savePoints; public Caretaker(Game game) { _game = game; _savePoints = new List<Game.Memento>(); // First save — the initial map state Save(); } public void Save() { _savePoints.Add(_game.Save()); } public void LoadLastSave() { if (_savePoints.Count > 0) _game.Load(_savePoints.Last()); } public void LoadFirstSave() { if (_savePoints.Count > 0) _game.Load(_savePoints.First()); } }
Each new game generates a 20x20 grid with configurable densities for walls, coins, and damage tiles. After generation the map goes through a validation pass using DFS from the origin — checking that the accessible open space meets a minimum threshold, that at least the required number of coins are reachable, and that a valid enemy spawn position exists at least 15 tiles from the player start.
If any condition fails the map is discarded and a new one is generated. The loop repeats until a valid map is produced. A second DFS pass — this time avoiding damage tiles — ensures every coin can be reached without crossing damage, and any coin that fails this check is converted to an empty tile rather than becoming unreachable.
do { map = GenerateMap(rows, cols, wallDensity, coinDensity, damageDensity); enemyPosition = PlaceEnemyBot(map, rows, cols); } while (!ValidateMap( map, rows, cols, minAccessibleOpenSpaces, minCoins, enemyPosition )); // ValidateMap: DFS from (0,0) counts reachable // open spaces and coins, verifies enemy is // reachable and at least 15 tiles from player
Each time the player moves the enemy takes a turn. The enemy's behaviour is probabilistic — giving it occasional unpredictability rather than perfect mechanical pursuit.
When pathfinding the enemy uses BFS to find the shortest route to the player through the current map, reconstructing the path via a predecessor dictionary and taking only the first step. Contact with the enemy deals damage and triggers immunity turns for the player and inactivity turns for the enemy — giving the player a brief window to escape.
The game panel extends Panel with double buffering enabled — without it
WinForms redraws would cause visible flicker on every frame. A gameTimer at 10ms
calls Invalidate() continuously, triggering the Paint event which redraws the full grid each frame using GDI+.
Each animated GIF — player, enemy, and coins — has its own independent Timer
calling ImageAnimator.UpdateFrames() to advance the frame. Player and enemy
sprites swap between left and right facing versions based on the last direction of movement. The map is
drawn cell by cell: walls as sandy brown rectangles, damage tiles as red rectangles, coins as the animated
GIF scaled to cell size.