If you are a regular reader, you should know sometimes I try to create Flash game in less than 2KB.

After Sokoban, Bejeweled and a math game with an original concept, it’s time to show you my Roguelike game in less than 2KB, here it is:

The roguelike is a sub-genre of role-playing video games, characterized by random level generation, tile-based graphics and permanent death. Roguelikes descend from the 1980 game Rogue, particularly mirroring Rogue’s character- or sprite-based graphics, turn-based gameplay that gives the player the time to plan each move, and high fantasy setting (source).

Let’s see what I was able to do in only 2KB:

* Random level generation

* Level progression

* Rogue style graphics

* Player energy

* Player score

* Basic enemy AI

* Game over screen

Have a look at it:

You control the green “9” (which also represent your energy) with arrow keys and must kill all red “M” (the monsters).

You kill a monster when you are in a cell adjacent to the monster and move towards it.

A monster hurts you when you end your turn in a cell adjacent to the monster.

But the most interesting thing of the script, unlike my previous 2KB experiments, it I managed to have a quite legible code.

package { import flash.display.Sprite; import flash.text.TextField; import flash.events.KeyboardEvent; [SWF(backgroundColor="0x222222")] public class Main extends Sprite { private var rows:Number=38; private var cols:Number=52; private var monsters:Number=30; private var map:Array; private var emptyRatio:Number=0.85; private var heroRow:Number; private var heroCol:Number; private var enemyRange:Number=10; private var energy:Number=9; private var score:Number=0; private var level:Number=1; private var gameOver:Boolean=false; private var gameOverSprite:Sprite; public function Main() { setup(); drawMap(); stage.addEventListener(KeyboardEvent.KEY_UP, movePlayer); } private function movePlayer(e:KeyboardEvent):void { if (! gameOver) { var keyPressed:Number=e.keyCode; switch (keyPressed) { case 37 : addTurn(-1,0); break; case 38 : addTurn(0,-1); break; case 39 : addTurn(1,0); break; case 40 : addTurn(0,1); break; } } else { gameOver=false; score=0; energy=9; level=1; setup(); drawMap(); } } private function checkMove(i:Number,j:Number,d:Number):Boolean { var deltaRow:Number=(d%2)*(2-d); var deltaCol:Number=((d+1)%2)*(d-1); if (i+deltaRow>=0 && i+deltaRow<rows && j+deltaCol>=0 && j+deltaCol<cols && map[i+deltaRow][j+deltaCol]==".") { map[i][j]="."; if (d==1||d==2) { map[i+deltaRow][j+deltaCol]="F"; } else { map[i+deltaRow][j+deltaCol]="M"; } return true; } return false; } private function addTurn(deltaCol,deltaRow):void { if (heroRow+deltaRow>=0&&heroRow+deltaRow<rows&&heroCol+deltaCol>=0&&heroCol+deltaCol<cols&&map[heroRow+deltaRow][heroCol+deltaCol]!="#") { if (map[heroRow+deltaRow][heroCol+deltaCol]==".") { map[heroRow+deltaRow][heroCol+deltaCol]=energy.toString(); map[heroRow][heroCol]="."; heroRow+=deltaRow; heroCol+=deltaCol; } else { if (map[heroRow+deltaRow][heroCol+deltaCol]=="M") { map[heroRow+deltaRow][heroCol+deltaCol]="."; score+=level; } } var monsterCount:Number=0; var enemyDir:Number=-1; for (var i:Number=0; i<rows; i++) { for (var j:Number=0; j<cols; j++) { if (map[i][j]=="M") { monsterCount++; var manhattan:Number=Math.abs(i-heroRow)+Math.abs(j-heroCol); if (manhattan==1) { energy-=level; if (energy<0) { energy=0; } map[heroRow][heroCol]=energy.toString(); } else { if (manhattan<enemyRange) { if (Math.abs(i-heroRow)>Math.abs(j-heroCol)) { if (i>heroRow) { enemyDir=3; } else { enemyDir=1; } } else { if (j>heroCol) { enemyDir=0; } else { enemyDir=2; } } } if (enemyDir==-1 || !checkMove(i,j,enemyDir)) { var secureCount=0; do { secureCount++; enemyDir=Math.floor(Math.random()*4); } while (!checkMove(i,j,enemyDir)&&secureCount<10); } } } else { if (map[i][j]=="F") { map[i][j]="M"; } } } } if (monsterCount==0) { level++; setup(); } drawMap(); } } private function setup():void { map=new Array(); for (var i:Number = 0; i < rows; i++) { map[i]=new Array(); for (var j:Number = 0; j < cols; j++) { if (Math.random()>emptyRatio) { map[i].push('#'); } else { map[i].push('.'); } } } for (i = 0; i <= monsters+5*level; i++) { do { var randomRow:Number=Math.floor(Math.random()*rows); var randomCol:Number=Math.floor(Math.random()*cols); } while (map[randomRow][randomCol]!="."); if (i==0) { heroRow=randomRow; heroCol=randomCol; map[randomRow][randomCol]=energy.toString(); } else { map[randomRow][randomCol]="M"; } } } private function drawMap():void { for (var k:Number=numChildren-1; k>=0; k--) { removeChild(getChildAt(k)); } for (var i:Number = 0; i < rows; i++) { for (var j:Number = 0; j < cols; j++) { var t:TextField=new TextField(); t.autoSize="center"; t.selectable=false; addChild(t); t.x=j*12+10; t.y=i*12+8; t.text=map[i][j]; switch (map[i][j]) { case "M" : t.textColor=0xff0000; break; case "." : t.textColor=0x666666; break; case "#" : t.textColor=0xFFFFFF; break; default : t.textColor=0x00ff00; } } } if (energy==0) { gameOver=true; gameOverSprite=new Sprite(); addChild(gameOverSprite); gameOverSprite.graphics.lineStyle(1,0x000000,0.6); gameOverSprite.graphics.beginFill(0x000000,0.6); gameOverSprite.graphics.drawRect(0,0,640,480); gameOverSprite.graphics.endFill(); t=new TextField(); t.width=500; t.text="YOUR SCORE: "+score.toString()+" - PRESS ANY KEY TO RESTART"; addChild(t); t.textColor=0x00ff00; t.selectable=false; } } } }

When looking at the source code, please consider it’s optimized for small compiled swf size.

Download the source code.