Design Patterns C# · WinForms CIS 476 Winter 2025

Coin
Collector

Memento pattern · Procedural generation · Enemy AI

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.

Coin Collector gameplay
01
Memento Pattern
Save · Load · Encapsulated state snapshots

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.

Encapsulation by design
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.
C# Caretaker.cs — save list management
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());
    }
}
02
Map Generation
Procedural · DFS validation · Rejection loop

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.

C# MapGenerator.cs — validation loop
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
Coin accessibility
A second DFS pass treats damage tiles as walls — any coin not reachable by this traversal is converted to an empty tile, guaranteeing the player can always collect every coin without being forced to take damage.
03
Enemy AI
BFS pathfinding · Probabilistic behaviour

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.

Idle
10%
BFS shortest path
60%
Random move
30%

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.

Gameplay — save, load, restart, and enemy pursuit
04
Rendering
WinForms · Double buffering · Animated sprites

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.

← Previous Space Invaders Next → TinyKart