C# <1kB RogueLike v4.1

openSUSE Linux

screenshot of C# 1kB RougueLike running in openSUSE

Windows

screenshot of C# 1kB RougueLike running in Windows

What is it?

A RogueLike game written in less than 1kB of C# source. Versions 1 through 3 were written for the rec.games.roguelike.development <1kB RL Challenge:

First <1kb RL Challenge

A year or so after the challenge a <512byte Roguelike was posted to the group. This brought about a renewed interest, with others posting new 1kB RogueLikes. I decided to revisit mine and see if any further optimizations could be made, allowing me to add more features.

Note that for the challenge we were allowed to use a separate IO library, such as curses. I used a custom IO lib, which is included below.

Features:

Download:

1kRl Visual Studio Solution (zipped)

1kRl.exe

Program.cs

ProgramPretty.cs - pretty printed version with comments

IO.cs

IOPretty.cs - pretty printed IO library

Instructions:

Numpad keys to move. The current dungeon level, your health and your kills are shown at the bottom. If your health reaches 0 then the game is over, you will need to press Enter to quit.

Source:

using System;using System.Collections.Generic;using S=System.String;partial class L{Dictionary<S,S>d=new Dictionary<S,S>();int p=9,b=999,x,y,g,u,v,i,j,c;S a="@",w="#",f=".",s=">",m="M",t;Random r=new Random();L(int l,int h,int k){var e=new List<S>();while(g++<b*l*2){t=(r.Next(b)-499)+a+(r.Next(b)-499);e.Add(t);d[t]=m;}d[P]=a;for(;;){u=x;v=y;g=c-96;x+=(g+2)%3==0?-1:g%3==0?1:0;y+=g>6&&g<10?-1:g>0&&g<4?1:0;t=T(P);if(t==m){e.Remove(P);d[P]=f;k++;}R();if(t==s){new L(l+1,h,k);return;}if(t!=f){x=u;y=v;}d[u+a+v]=f;d[P]=a;for(g=0;g<e.Count;g++){i=V(e[g],0);j=V(e[g],1);u=i<x?i+1:i>x?i-1:i;v=j<y?j+1:j>y?j-1:j;t=u+a+v;if(T(t)==f){e[g]=t;d[t]=m;d[i+a+j]=f;}if(T(t)==a)h--;}if(h<1)return;g=p*2-1;for(j=0;j++<g;){for(i=0;i++<g;){t=T((i-p+x)+a+(j-p+y));u=t==w?l:7;while(u>15)u-=15;F(u<1?7:u);W(t);}N("");}F(7);N("L"+l+f+"H"+h+f+"K"+k);c=I;}}S P{get{return x+a+y;}}S T(S c){return d.ContainsKey(c)?d[c]:d[c]=r.Next(b*9)<9?s:r.Next(9)<7?f:w;}int V(S c,int i){return int.Parse(c.Split('@')[i]);}static void Main(){new L(1,9,0);E();}}

Pretty printed source:

using System;

using System.Collections.Generic;

using S = System.String;

// Naming guide:

//

// CLASSES:

// S = (S)tring

// C = (C)onsole

// L = (L)evel

//

// int:

// d = (d)ungeon

// p = view(p)ort radius

// b = (b)ig number

// x = player (x) position

// y = player (y) position

// g = (g)eneral temporary storage

// c = last (c)haracter pressed

// l = (l)evel

// h = (h)ealth

// k = (k)ills

//

// string:

// a = (a)t

// w = (w)all

// f = (f)loor

// s = (s)tairs

// m = (m)onster

// t = (t)ile OR (t)ile location

//

// other variables:

// r = (r)andom number provider

// e = (e)nemy list

//

// properties:

// P = current player (P)osition

//

// methods:

// S T( S c ) = (S)tring representing dungeon (T)ile at (S)tring (c)oordinate

// int V( S c, int i ) = con(V)ert (S)tring (c)oordinate to an int x or y coordinate using (i)ndex, 0 for x, 1 for y

 

/// <summary>

/// represents a level of the dungeon

/// </summary>

partial class L {

  // d stores the tiles that the player has seen, the Key is a

  // string representing the X,Y coordinates for a tile in the format "X@Y" ie

  // "4@12" and the Value is the string to use to draw the tile

  Dictionary<S, S> d = new Dictionary<S, S>( );

 

  // p is used to set the viewport size (viewport size is p*2-1)

  // b is used in the calculations for number of enemies and also in the

  // dungeon generation

  // x and y store the player's current location, these shouldn't be changed

  // unless the player moves

  // g is used as a general purpose variable for counters and temporary storage

  // u & v and i & j (no real meaning) are used to store temporary X and Y

  // values and also as general purpose variables when needed

  // c stores an integer representation of the last key pressed

  int p = 9, b = 999, x, y, g, u, v, i, j, c;

 

  // a is the string to use for the player, also used as seperator in string

  // representations of X,Y locations

  // w is the string to use for walls

  // f is the string to use for floors

  // s is the string to use for stairs

  // m is the string to use for monsters

  // t is used to store a given tile from the dungeon, for example to test if

  // the player can move onto that square, or a location in the form described

  // above

  S a = "@", w = "#", f = ".", s = ">", m = "M", t;

 

  // random seed uses default value of system clock

  Random r = new Random( );

 

  /// <summary>

  /// Main game loop in the guise of a constructor - allows recursive calls

  /// when going up a level

  /// </summary>

  /// <param name="l">the current level</param>

  /// <param name="h">the player's health</param>

  /// <param name="k">the player's number of kills</param>

  L( int l, int h, int k ) {

    // e is a list of enemy locations, using the "X@Y" format, which allows us

    // to update the enemy on the map by changing the dungeon tiles using the

    // enemy location as the lookup Key into d

    var e = new List<S>( );

 

    //add enemies - at this point g hasn't been initialized so will start

    //with default value for an int, 0. Number of enemies is b * the level * 2

    //so gets harder as you descend

    while ( g++ < b * l * 2 ) {

      //get a random location within ~500 squares of the player

      t = ( r.Next( b ) - 499 ) + a + ( r.Next( b ) - 499 );

 

      //add an enemy at this location - might be same location as an existing

      //enemy, oh well

      e.Add( t );

 

      //set the dungeon tile at the newly created enemy's position to m

      d[ t ] = m;

    }

 

    //set dungeon tile at current player position to player string - "@"

    d[ P ] = a;

 

    //game loop

    for ( ; ; ) {

      //store player's current x and y location

      u = x;

      v = y;

 

      //change c from being a number from 97-105 to being a number 1-9

      //

      //value before:

      //NumPad1 = 97  = down + left

      //NumPad2 = 98  = down

      //NumPad3 = 99  = down + right

      //NumPad4 = 100 = left

      //NumPad6 = 102 = right

      //NumPad7 = 103 = up + left

      //NumPad8 = 104 = up

      //NumPad9 = 105 = up + right

      //

      //value after:

      //NumPad1 = 1  = down + left

      //NumPad2 = 2  = down

      //NumPad3 = 3  = down + right

      //NumPad4 = 4 = left

      //NumPad6 = 6 = right

      //NumPad7 = 7 = up + left

      //NumPad8 = 8 = up

      //NumPad9 = 9 = up + right

      g = c - 96;

 

      //numbers for left are 1,4,7 - as these are all 3 apart we can add 2

      //to get 3,6,9 then mod 3 this value to see if it's a left key

      //otherwise, numbers for right are 3,6,9 so we can mod 3 these to see

      //if they're a right key

      x += ( g + 2 ) % 3 == 0 ? -1 : g % 3 == 0 ? 1 : 0;

      //up is 7,8,9 and down is 1,2,3

      y += g > 6 && g < 10 ? -1 : g > 0 && g < 4 ? 1 : 0;

 

      //get the string at the new location

      t = T( P );

 

      //if player is hitting an enemy

      if ( t == m ) {

        //kill the enemy

        e.Remove( P );

 

        //set the tile where the enemy used to be to floor

        d[ P ] = f;

 

        //Increment kills

        k++;

      }

 

      //reset the cursor position

      R();

 

      //if the player moved onto stairs

      if ( t == s ) {

        //new dungeon level, increment the current level

        new L( l + 1, h, k );

        //if we get here player has died, exit

        return;

      }

 

      //if the player tried to move onto something that isn't a floor, put

      //them back where they were

      if ( t != f ) {

        x = u;

        y = v;

      }

 

      //set the last tile that the player was on to floor

      d[ u + a + v ] = f;

      //set the tile that the player is currently on to @

      d[ P ] = a;

 

      //iterate through the remaining enemies

      for ( g = 0; g < e.Count; g++ ) {

        //get the current enemy's location and store their X position in i and

        //their Y position in j

        i = V( e[ g ], 0 );

        j = V( e[ g ], 1 );

 

        //move towards the player

        u = i < x ? i + 1 : i > x ? i - 1 : i;

        v = j < y ? j + 1 : j > y ? j - 1 : j;

 

        //get the potential new enemy coordinate

        t = u + a + v;

 

        //if the dungeon tile is floor, move the enemy onto the new coordinate

        if ( T( t ) == f ) {

          //change the enemy's location in the enemy list

          e[ g ] = t;

          //set the dungeon tile at the new location

          d[ t ] = m;

          //set the dungeon tile at the old location to floor

          d[ i + a + j ] = f;

        }

 

        //if the dungeon tile the enemy tried to move to contains the player,

        //decrement the player's health

        if ( T( t ) == a ) h--;

      }

 

      //exit if player is dead

      if ( h < 1 ) return;

 

      //calculate the viewport size using p

      g = p * 2 - 1;

 

      //draw visible dungeon tiles to viewport

      for ( j = 0; j++ < g; ) {

        for ( i = 0; i++ < g; ) {

          //get tile at location X = i, Y = j

          t = T( ( i - p + x ) + a + ( j - p + y ) );

 

          //if t is a wall then assign u the level number, otherwise assign it

          //7 (gray)

          u = t == w ? l : 7;

 

          //we are using u for the color, but only colors in range 0-15 are

          //valid, so if u is higher than 15, keep subtracting 15 until it

          //isn't anymore

          while ( u > 15 )

            u -= 15;

 

          //Set foreground color to u, or 7 (gray) if u is 0 

          F( u < 1 ? 7 : u );

 

          //draw the current dungeon tile

          W( t );

        }

 

        //start a new row

        N( "" );

      }

 

      //set color to grayx

      F( 7 );

      //separate level, health and kills with a floor tile and draw below

      //viewport

      N( "L" + l + f + "H" + h + f + "K" + k );

 

      //get keyboard input

      c = I;

    }

  }

 

  /// <summary>

  /// returns the player's coordinates as a string in the format "X@Y" ie "4@12"

  /// </summary>

  S P {

    get { return x + a + y; }

  }

 

  /// <summary>

  /// get the dungeon tile at coordinate c, if no tile exists there create it

  /// </summary>

  /// <param name="c">a dungeon coordinate in the form "X@Y" ie "4@12"</param>

  /// <returns>a string representing a dungeon tile</returns>

  S T( S c ) {

    return d.ContainsKey( c ) ? d[ c ] : d[ c ] = r.Next( b * 9 ) < 9 ? s : r.Next( 9 ) < 7 ? f : w;

  }

 

  /// <summary>

  /// converts a string coordinate to either an X or Y coordinate

  /// </summary>

  /// <param name="c">the string coordinate to convert</param>

  /// <param name="i">whether to return X or Y, 0 = X, 1 = Y</param>

  /// <returns>the X or Y location as an int</returns>

  int V( S c, int i ) { return int.Parse( c.Split( '@' )[ i ] ); }

 

  static void Main( ) {

    //start a new game on level 1 with 9 health and 0 experience

    new L( 1, 9, 0 );

    //wait for newline before exiting

    E();

  }

}

IO source:

using C=System.Console;using System;partial class L{static void R(){C.SetCursorPosition(0,0);}static void W(string s){C.Write(s);}static void N(string s){W(s+"\n");}static void F(int c){C.ForegroundColor=(System.ConsoleColor)c;}static string E(){return C.ReadLine();}static int I{get{if(Type.GetType("Mono.Runtime")!=null){int i=(int)C.ReadKey(true).Key;return i>48&&i<58?i+48:i;}return(int)C.ReadKey(true).Key;}}}

IO pretty printed source:

using C = System.Console;

using System;

partial class L {

  static void R(){

    C.SetCursorPosition( 0, 0 );

  }

 

  static void W( string s ) {

    C.Write( s );

  }

 

  static void N( string s ) {

    W( s + "\n" );

  }

 

  static void F( int c ) {

    C.ForegroundColor = ( System.ConsoleColor ) c;

  }

 

  static string E() {

    return C.ReadLine();

  }

 

  static int I {

    get {

      //Mono fix: numpad keys don't return correct scancodes

      if( Type.GetType ("Mono.Runtime") != null ) {

        int i = ( int ) C.ReadKey( true ).Key;

        return i > 48 && i < 58 ? i + 48 : i;

      }

 

      return ( int ) C.ReadKey( true ).Key;

    }

  }

}

Historic versions:

  1. v1
  2. v2
  3. v3
  4. v4