Pong, Breakout and Space Invaders on a shared C#/MonoGame core.
One solution, three games, no game assets: sprites are pixel patterns in source, text comes from a 5x7 glyph table, and every sound is a square wave or noise burst synthesized at load. .NET 8, MonoGame DesktopGL, runs on Windows, Linux and macOS.
| Pong | Breakout | Space Invaders |
|---|---|---|
![]() |
![]() |
![]() |
dotnet build
dotnet run --project src/AtariClassics.Pong
dotnet run --project src/AtariClassics.Breakout
dotnet run --project src/AtariClassics.SpaceInvaders
dotnet test
Menus: up/down select, enter confirms, esc backs out. P or esc pauses in game.
| Game | Move | Action |
|---|---|---|
| Pong | W/S or up/down | |
| Breakout | A/D or left/right | space launches |
| Space Invaders | A/D or left/right | space fires |
Pong: first to 11 against the machine. The ball leaves the paddle at an angle picked by where you catch it and gains speed with every return. The cpu paddle is capped slower than yours and only rethinks its target a few times a second, so it can be beaten.
Breakout: 8x5 wall, top rows pay 9 down to 1. The ball speeds up as the wall thins. Three balls, clear everything to win.
Space Invaders: the 5x11 flock steps as one and drops at the edges, faster as it shrinks. One shell in the air at a time, three shields that shots and aliens both chew away, a mystery ufo now and then, and every cleared wave restarts one row lower.
AtariClassics.Core
|- Collision RectF, swept aabb cast (tunneling-proof), paddle bounce math
|- Scenes IGameScene, SceneManager stack, shared menu/pause/game over
|- Input GameKey behind IInputProvider, keyboard impl
|- Graphics 5x7 pixel font, pattern grids -> textures
|- Audio square/noise synth, manager that goes mute without a device
|- Entities Paddle, Ball, IUpdatable/IRenderable
`- Persistence per-game high score json
AtariClassics.Pong ----------+
AtariClassics.Breakout ------+--- scenes and rules only, the rest lives in Core
AtariClassics.SpaceInvaders -+
tests/AtariClassics.Tests xunit over the logic above
build/AppBundler publishes and wraps the games into mac .app bundles
Games run a fixed 60 Hz step with dt-based movement. Collision is swept aabb
(slab method), so a fast ball hits the first surface on its path instead of
teleporting through it. Every game accepts --smoke [frames] and exits by
itself after that many steps, which is how CI boots all three under xvfb.
The macos-app workflow (Actions tab, run it manually) publishes all three
games self-contained for arm64 and x64 and wraps them into ad-hoc-signed
.app bundles; grab the zips from the run artifacts. First launch needs
right-click and Open since the apps are not notarized. The same run starts
each packaged app with --screenshot to regenerate the captures above.
The same tool runs locally:
dotnet run --project build/AppBundler -- --game pong --rid osx-arm64
Breakout and Space Invaders keep one number each under
~/.config/JoystickNET (%APPDATA%\JoystickNET on Windows), written async
on game over and shown in the menus.



