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:
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.
1kRl Visual Studio Solution (zipped)
ProgramPretty.cs - pretty printed version with comments
IOPretty.cs - pretty printed IO library
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.
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();}}
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();
}
}
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;}}}
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;
}
}
}