Juraj's Blog

22 May 2021

Code golfing tips and tricks

This is a companion article to the 4-in-1 game for taskbar post, which has four playable games for the Windows taskbar in 3727 bytes of code describing the techniques of code size reduction.

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.