Complete Roguelike Flash game in less than 2KB

Read all posts about "" game

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers