A full recreation of the 1978 arcade classic built as a Universal Windows Platform app in C#. Features sound effects, escalating difficulty, local high scores, controller support, and a data-driven level system.
A toggle lets the player switch between two firing modes mid-game. In single mode, only one bullet can be on screen at a time — classic arcade behavior. In interval mode, bullets fire at a fixed cooldown interval, letting multiple projectiles stack. The green charge bar shows the cooldown state in real time.
A red bonus ship periodically flies across the top of the screen. Hitting it awards a random score — keeping players guessing and chasing every appearance.
Rather than hard-coding difficulty, every attribute of the game is configured through a Level class. Speed, spacing,
cooldowns, and starting positions are all data — meaning new levels take minutes to add, not hours to
rebalance.
The real mechanic is InvaderChange: a list of
difficulty escalations that trigger at specific invader-destroyed milestones within a round. As enemies fall,
the survivors move faster, shoot more often, and stun for shorter durations — creating a natural pressure
curve that rewards quick play. Every level bottoms out at a movement interval of 1 when a single invader
remains, meaning it moves essentially every tick.
// Triggered when InvaderDestroyed count is reached mid-round public class InvaderChange { public int InvaderDestroyed; // milestone trigger public int? InvaderBulletSpeed; public int? InvaderHorizontalSpeed; public int? InvaderMovementInterval; // lower = faster movement public int? InvaderMinimumTickBeforeShoot; public int? InvaderStunDuration; } public class Level { // Invader starting attributes public int InvaderBulletSpeed, InvaderHorizontalSpeed, InvaderVerticalSpeed; public int InvaderMovementInterval, InvaderMinimumTickBeforeShoot; public int InvaderStunDuration, InvaderStartYPosition; // higher Y = closer to player public int InvaderXGap, InvaderYGap; // Player attributes public int PlayerBulletSpeed, PlayerMovementSpeed; public int PlayerBulletCooldown; // only active in interval fire mode // Escalation schedule public List<InvaderChange> InvaderChanges; }
Top scores are persisted to a local file using UWP's sandboxed ApplicationData.Current.LocalFolder
— meaning the save file is tied to the app installation and persists across sessions without the user managing
any file location.
Scores are kept in sorted order at all times via a manual insertion sort on write, so the file on disk is always pre-ranked. On read, the list loads directly into the UI with no additional sorting needed. The top 6 scores are displayed on the side panel during gameplay.
// UWP sandboxed app storage — persists across sessions, no path management needed StorageFolder localFolder = ApplicationData.Current.LocalFolder; // Creates the file on first launch, reopens it on every subsequent run ScoreFile = localFolder .CreateFileAsync("Scores.txt", CreationCollisionOption.OpenIfExists) .GetAwaiter().GetResult(); // Read all lines and map directly to FinalScore objects in one expression var fileData = FileIO.ReadLinesAsync(ScoreFile).GetAwaiter().GetResult().ToList(); FinalScoreList = fileData .Select(fd => { var splitData = fd.Split(':'); return new FinalScore(splitData[0], int.Parse(splitData[1])); }) .ToList(); // Immediately reflect persisted scores in the UI before any gameplay UpdateHighScore();
// Scores are kept sorted at all times via manual insertion sort — // so the file on disk is always pre-ranked, no sorting needed on read int points = SpaceInvaders.TotalPoints; FinalScore newScore = new FinalScore(name, points); if (FinalScoreList.Count == 0) { FinalScoreList.Add(newScore); } else if (newScore.Score > FinalScoreList.First().Score) { FinalScoreList.Insert(0, newScore); // new high score } else if (newScore.Score <= FinalScoreList.Last().Score) { FinalScoreList.Add(newScore); // lowest score } else { for (int i = 0; i < FinalScoreList.Count - 1; i++) { if (newScore.Score <= FinalScoreList[i].Score && newScore.Score > FinalScoreList[i + 1].Score) { FinalScoreList.Insert(i + 1, newScore); // sorted position break; } } } // Serialize the sorted list back to file in one call FileIO.WriteLinesAsync(ScoreFile, FinalScoreList.Select(fs => fs.ToString()) ).GetAwaiter().GetResult();
UWP's cross-device model meant the game runs natively on Xbox with a controller — no additional porting required. Both input methods were mapped to identical actions, letting players switch without any gameplay difference.