A collection of minigames under 4 KB

As a kid, I had a cheap 8 in 1 Brick Game handheld console, which had LCD display with large dots, 4 directional buttons and a speaker. Along with a couple of Tetris variations it had games like car racing, brick shooting, brick building and a snake game.

I also like game development and fun limitations. As an exercise, I wanted to write a minimalistic game that would run in the Windows taskbar (system tray). This way you can have an inconspicuous gaming session while anyone who looks at your screen could not suspect anything.

In the spirit of the cheap handheld consoles there should definitely more than one game, so I’ve built four: Snake, Brick Shooter, Cars and Breakout.

This is how the game looks when running in the task bar:

tray game

As it’s really tiny, I magnified it by a factor of 2:

tray game

The individual games scaled by 10x:

bricks bricks bricks bricks

There’s also a little menu screen that allows the player to pick a game and a difficulty.

Tech stack

All we really need to to is to render the game canvas to a tray icon, handle input, process the game logic and produce some sound.

C# with the .NET Framework seemed like a good tool for the job, leveraging the GDI+ (System.Drawing) graphics library for rendering and the Windows Forms API to place and redraw the tray icon.

The icon in the task bar has a size of 16x16 pixels, that’s how large our canvas needs to be, so we’re literally pushed into the pixel art direction. This also means we can afford to draw every frame from scratch as modern computers are fast.

Arrow keys work as controls, space bar starts or restarts a game. Because we don’t have meaningful way to give focus to a tray icon, we hijack the global keyboard events using the RegisterHotKey API.

Sound effects (beeps) are produced with the Console.Beep() API.

The game loop is handled by a Timer that ticks at a fixed rate, and the difficulty setting adjusts how many ticks to skip.

Why Code Golfing?

Code golfing is a time-honored tradition where programmers try to achieve the shortest source code possible while still retaining the functionality.

I do that sometimes for fun and golfing a game seemed like a worthy challenge. While C# isn’t the traditional code golf language of choice due to its verbosity, I already had a working game and thought it could fit under 4 kilobytes of source code.

The original (already slightly unreadable) version had 9586 bytes of code. The final golfed size is 3727 bytes. Binary size (x64, release) is 9728 bytes.

Removing all whitespace could save around 300 more bytes, as there are 165 lines with 2-byte CRLF line endings.

If you’re interested in code golf, there’s a vibrant community at Code Golf Stack Exchange. There’s also a JS1K Javascript code golfing competition with the aim of creating something cool with a max size of 1 KB.

An example

if (mode == BRK) { //draw player g.DrawLine(Pens.Magenta, X, 15, X + 5, 15); //draw blocks for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) if (M[y][x] > 0) FR(B.Cyan, x, y); //draw bullets g.DrawLine(BP, X + 2, 15, X + 2, 0); }

_The original version, 231 characters without comments_

```csharp
if(m==0){
J(Pens.Magenta,X,Z,X+5,Z);
for(y=0;y<H;y++)
for(x=0;x<W;x++)
if(b[y][x]>0)
FR(B.Cyan,x,y);
J(BP,X+2,Z,X+2,0);
}

The golfed version, 128 characters

Code Golfing tips and tricks

Table of contents:

Here’s a bag of tricks:

DISCLAIMER: Please don’t use the following techniques in production code, it will make your colleagues unhappy.

Shorter identifiers

Single character identifiers are your friends

There are 53 valid single character identifiers:

ABCDEFGHIJKLMNOPQRSTVWXYZ
abcdefghijklmnopqrstvwxyz
_

Characters saved: varies

Tip #1: underscore is your friend

Tip #2: The readers will likely appreciate you mixing l and I

Type aliases:

I created aliases for some commonly used types, e.g.

	Brushes.Black
	Brushes.Red
	Brushes.Red
	Brushes.White

	using B=System.Drawing.Brushes;
	B.Black
	B.Red
	B.Red
	B.White

Characters saved: varies, depending on the length of the fully qualified type

Other example:

	using P=System.Drawing.Point;

using static for Math

The using static directive allowed savings for multiple repetitions of static Math methods such as Min, Max.

Example:

	spd=Max(1,Min(spd+dx,9));
	spd=Math.Max(1,Math.Min(spd+dx,9))

Characters saved: some, starting when Math methods used more than 5 times. Compare the length of the following:

using static System.Math;
Math.Math.Math.Math.Math.

Interface or DllImport method argument names don’t matter

[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr h,int i,int f,int v);

vs

[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);

Characters saved: significant for long parameters

Skipping whitespace

Variables of the same type can be declared on one line

int L=-1,R=1,U=3,D=4,_=5,W=16,H=14;

vs

int L=-1;int R=1;int U=3;int D=4;int _=5;int W=16;int H=14;

Characters saved: 4 for every sequence of int and a whitespace

Skip whitespace in class declaration

class A:ApplicationContext,IMessageFilter{

Skip whitespace in some variable declarations

C# doesn’t require a whitespace between a type and the identifier when they are already separated by square or angled brackets - arrays or generics.

	int[][]M;
	List<P>snake;

Note: Doesn’t work in regular cases, such as int M;

Skip whitespace after parentheses

	if(h.X>W||h.X<0||h.Y<0||h.Y>W)mode=OVR;

Characters saved: 1

Note: It may make more sense to squash all whitespace with a separate pass instead of doing this by hand.

Code / identifier reuse

Reuse variables for different purposes

If you have a nice List<Point> used for the body of the snake, why not reuse it as a container for blocks in the block-breaking part of the game?

Also, I reused f as the food position in the snake game and the ball position in the breakout game.

Using a global variable for a loop variable

You could define int i,j,k on the class level and then skip declaring loop variables, such as int i in every for loop.

	int i;
	for(i=0;i<9;i++)
	for(i=0;i<9;i++)
	for(i=0;i<9;i++)

vs:

	for(int i=0;i<9;i++)
	for(int i=0;i<9;i++)
	for(int i=0;i<9;i++)

Characters saved: Almost always

Beware: Be careful of overwriting the loop variable in nested loops or loops that span over method calls.

Shadowing global variables with locals when necessary

Useful when you’re running out of 1-character identifiers - but less likely as there’s a lot of them.

Code (re)organizing

Extracting methods

Pays off when the same bit of functionality is repeated and the method

Action<int,Keys>E=(k,K)=>RegisterHotKey(IntPtr.Zero,k,0,(int)K);
E(L,Keys.Left);
E(R,Keys.Right);

vs

RegisterHotKey(IntPtr.Zero,L,0,(int)Keys.Left);
RegisterHotKey(IntPtr.Zero,k,0,(int)Keys.Right);

or

filling out a 1 px rectangle:
Action<Brush,int,int> F=(b,x,y)=>g.FillRectangle(b,x,y,1,1);
F(Brushes.Black,x,y)
F(Brushes.Red,p.x,p.y)

vs 

g.FillRectangle(Brushes.Black,x,y,1,1)
g.FillRectangle(Brushes.Red,x,y,1,1)

Inlining methods

Inverse strategy to extracting - it makes sense to inline the method body if it only gets called once.

void L(){code;code;code;}
vs
code;code;code;

Character savings: 10 for void parameterless methods

Extract numerical constants

In some cases, extracting a constant that is used repeatedly saves space.

If you are going to use -1 in your code 6 times, that’s 12 characters However, if you add one more int declaration as L=-1,, then using L 6 times requires 10 characters + 5 for declaration

Compare:

-1-1-1-1-1-1
L=-1,LLLLLL

and 

123123123
N=123,NN

Characters saved: Starts to pay off at

  • 6 repetitions of two-digit constant
  • 3 repetitions of three-digit constant

Tip: You can use Visual Studio’s Find All References commands for constants, it calculates number of occurrences

Don’t declare constants for a single digit int

It always results in a net loss of characters, readability be damned. Refactoring or changing the size of something is a pain, though.

Avoid declaring variables at all

If you only need to name an expression and later use it once, then it does not need a name and can be inlined.

var vs type names

var instead of full type name for locals

In some cases var is shorter to type than the full type name, for example:

var I=new int[W];

vs

int[] I=new int[W];

Characters saved: Length of the type name - 3

Don’t use var when using array initializer

Example:

	string[] games={"BRK","SNK","CRS","ARK"};
	var games=new string[]{"BRK,"SNK","CRS","ARK"}

Conditionals

Conditionals - if vs switch

Sometimes a sequence of if statements can be shorter than a corresponding switch statement, as we have to include break statements in C#:

if(k==U){dy=-1;dx=0;}
if(k==D){dy=1;dx=0;}
if(k==L){dx=-1;dy=0;}
if(k==R){dx=1;dy=0;}

vs

switch(k){
case U:dy=-1;dx=0;break;
case D:dy=1;dx=0;break;
case L:dy=-1;dx=0;break;
case R:dy=1;dx=0;break;
}

Ternary operator vs if in an assignment

	N=y<6?MkR():new int[W];
	if(y<6)N=MkR();else N=new int[W];

	x=b?1:2;
	if(b)x=1;else x=2;

Characters saved: 10, depending on length of the target variable name as it is spelled out only once in a ternary.

Use object initializers - you save 1 character per property initialized

Q=new Timer(){Interval=W};

vs

Q=new Timer();Q.Interval=W;

Characters saved: 1 per every property initialized

Miscellaneous aka cheap tricks

Casting an int constant to the enum

This helps with almost all enum values with long names. For most enums, if the Enum value is longer than 4 characters, go for it.

g.SmoothingMode=(System.Drawing.Drawing2D.SmoothingMode)4;
F=new Font(c.Families[0],5,(GraphicsUnit)5);

vs

g.SmoothingMode=System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
F=new Font(c.Families[0],5,GraphicsUnit.Pixel);

Characters saved: Length of the enum value name - 2 - (digits of the int value)

Note: It also worked with IntPtr: IntPtr.Zero vs (IntPtr)0

Use operators to your advantage

For example, adding Size to a point is easier with the + operator of Point as it’s already built into the framework:

	var dst=P.Add(snake[0],new Size(dx,dy));
	var dst=snake[0]+new Size(dx,dy);

Drawing 1 rectangle vs 2 lines

Example: Vertical walls in the brick-breaking mode:

	g.DrawRectangle(Pens.Blue,0.5f,-1,15,18);
	vs
	g.DrawLine(Pens.Blue,0,0,0,W);
	g.DrawLine(Pens.Blue,15,0,15,W);

Characters saved: 22

Note: If you want to align GDI+ DrawRectangle with DrawLine, you need to shift it by 0.5f pixels

Pick colors with shorter names

Brushes.Red is shorter than Brushes.Green.

Shortcuts taken

There are more than a few shortcuts or hacks that should be avoided in a more serious attempt at a similar game.

First, there are no win conditions - you cannot win this game.

I didn’t clean up the resources properly as they will get disposed of eventually when you close the application. The application is also probably leaking resources like mad as a new Icon is generated for every bitmap for every frame.

The code never unregisters the hotkeys after registering them at the game start, so you need to terminate it in order to use the arrow keys or space bar for anything other than the game.

I did use an external CG pixel 4x5 font file so there is some legible text at the tiny 16x16 resolution.

Right clicking the game icon initially opened a context menu with a single Exit option - but we don’t really need that, the player can kill the executable from the Task Manager.

The code

Hosted with <3 on GitHub: https://github.com/jborza/traygame

Golfed source: https://github.com/jborza/traygame/blob/master/Program.cs

Ungolfed version: https://github.com/jborza/traygame/blob/master/Program_Ungolfed.cs