Install Circle Chain on your iPhone for free and get the source code!! 645 downloads to go - last updated: April 21, 2012

Complete Bejeweled game in less than 2KB – legible version

Do you remember the Complete Bejeweled game in less than 2KB?

Putting a Bejeweled game into only 2K made the code look a bit unclear, but Brook Jordan shares his readable version with us.

Slightly more legible now, with proper variable names and comments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
package {
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.text.TextField;
 
	public class bejewelled extends Sprite {
		private var gems_array:Array=new Array();
		private var aGem:Sprite;
		private var selectorBox:Sprite=new Sprite();
		private var selectorRow:int=-10;
		private var selectorColumn:int=-10;
		private var red:uint = 0xFF0000;
		private var green:uint = 0xFF00;
		private var blue:uint = 0xFF;
		private var yellow:uint = 0xFFFF00;
		private var cyan:uint = 0xFFFF;
		private var magenta:uint = 0xFF00FF;
		private var white:uint = 0xFFFFFF;
		private var colours_array:Array=new Array(red,green,blue,yellow,cyan,magenta,white);
		private var clickPossible:Boolean=false;
		private var score_txt:TextField=new TextField();
		private var hint_txt:TextField=new TextField();
		private var score:uint=0;
		private var inaRow:uint=0;
		private var match:Boolean = true;
 
		public function bejewelled() {
			// Game initiation
			// Create and style score text
			addChild(score_txt);
			score_txt.textColor=0xFFFFFF;
			score_txt.x=500;
			// Create and style hint text
			addChild(hint_txt);
			hint_txt.textColor=0xFFFFFF;
			hint_txt.x=550;
			// Create Gems in rows and columns
			for (var i:uint=0; i<8; i++) {
				gems_array[i]=new Array();
				for (var j:uint=0; j<8; j++) {
					do {
						gems_array[i][j]=Math.floor(Math.random()*7);
					}
						while (rowLineLength(i,j)>2 || columnLineLength(i,j)>2);
					aGem=new Sprite();
					aGem.graphics.beginFill(colours_array[gems_array[i][j]]);
					aGem.graphics.drawCircle(30,30,29);
					aGem.graphics.endFill();
					aGem.name=i+"_"+j;
					aGem.x=j*60;
					aGem.y=i*60;
					addChild(aGem);
				}
			}
			// Create and style selector box
			addChild(selectorBox);
			selectorBox.graphics.lineStyle(2,red,1);
			selectorBox.graphics.drawRect(0,0,60,60);
			selectorBox.visible=false;
			// Listen for user input
			stage.addEventListener(MouseEvent.CLICK,onClick);
 
			addEventListener(Event.ENTER_FRAME,everyFrame);
		}
 
		// Every frame...
		private function everyFrame(e:Event):void {
			//Assume that gems are not falling
			var gemsAreFalling:Boolean=false;
			// Check each gem for space below it
			for (var i:int=6; i>=0; i--) {
				for (var j:uint=0; j<8; j++) {
					// If a spot contains a gem, and has an empty space below...
					if (gems_array[i][j] != -1 && gems_array[i+1][j]==-1) {
						// Set gems falling
						gemsAreFalling=true;
						gems_array[i+1][j]=gems_array[i][j];
						gems_array[i][j]=-1;
						getChildByName(i+"_"+j).y+=60;
						getChildByName(i+"_"+j).name=(i+1)+"_"+j;
						break;
					}
				}
				// If a gem is falling
				if (gemsAreFalling) {
					// don't allow any more to start falling
					break;
				}
			}
			// If no gems are falling
			if (! gemsAreFalling) {
				// Assume no new gems are needed
				var needNewGem:Boolean=false;
				// but check all spaces...
				for (i=7; i>=0; i--) {
					for (j=0; j<8; j++) {
						// and if a spot is empty
						if (gems_array[i][j]==-1) {
							// now we know we need a new gem
							needNewGem=true;
							// pick a random color for the gem
							gems_array[0][j]=Math.floor(Math.random()*7);
							// create the gem
							aGem=new Sprite();
							aGem.graphics.beginFill(colours_array[gems_array[0][j]]);
							aGem.graphics.drawCircle(30,30,29);
							aGem.graphics.endFill();
							// ID it
							aGem.name="0_"+j;
							// position it
							aGem.x=j*60;
							aGem.y=0;
							// show it
							addChild(aGem);
							// stop creating new gems
							break;
						}
					}
					// if a new gem was created, stop checking
					if (needNewGem) {
						break;
					}
				}
				// If no new gems were needed...
				if (! needNewGem) {
					// assume no more/new lines are on the board
					var moreLinesAvailable:Boolean=false;
					// check all gems
					for (i=7; i>=0; i--) {
						for (j=0; j<8; j++) {
							// if a line is found
							if (rowLineLength(i,j)>2 || columnLineLength(i,j)>2) {
								// then we know more lines are available
								moreLinesAvailable=true;
								// creat a new array, set the gem type of the line, and where it is
								var lineGems:Array=[i+"_"+j];
								var gemType:uint=gems_array[i][j];
								var linePosition:int;
								// check t's a horizontal line...
								if (rowLineLength(i,j)>2) {
									// if so, find our how long it is and put all the line's gems into the array
									linePosition=j;
									while (sameGemIsHere(gemType,i,linePosition-1)) {
										linePosition--;
										lineGems.push(i+"_"+linePosition);
									}
									linePosition=j;
									while (sameGemIsHere(gemType,i,linePosition+1)) {
										linePosition++;
										lineGems.push(i+"_"+linePosition);
									}
								}
								// check t's a vertical line...
								if (columnLineLength(i,j)>2) {
									// if so, find our how long it is and put all the line's gems into the array
									linePosition=i;
									while (sameGemIsHere(gemType,linePosition-1,j)) {
										linePosition--;
										lineGems.push(linePosition+"_"+j);
									}
									linePosition=i;
									while (sameGemIsHere(gemType,linePosition+1,j)) {
										linePosition++;
										lineGems.push(linePosition+"_"+j);
									}
								}
								// for all gems in the line...
								for (i=0; i<lineGems.length; i++) {
									// remove it from the program
									removeChild(getChildByName(lineGems[i]));
									// find where it was in the array
									var cd:Array=lineGems[i].split("_");
									// set it to an empty gem space
									gems_array[cd[0]][cd[1]]=-1;
									// set the new score
									score+=inaRow;
									// set the score setter up
									inaRow++;
								}
								// if a row was made, stop the loop
								break;
							}
						}
						// if a line was made, stop making more lines
						if (moreLinesAvailable) {
							break;
						}
					}
					// if no more lines were available...
					if (! moreLinesAvailable) {
						// allow new moves to be made
						clickPossible=true;
						// remove score multiplier
						inaRow=0;
					}
				}
			}
			// display new score
			score_txt.text=score.toString();
		}
		// When the user clicks
		private function onClick(e:MouseEvent):void {
			// If a click is allowed
			if (clickPossible) {
				// If the click is within the game area...
				if (mouseX<480&&mouseX>0&&mouseY<480&&mouseY>0) {
					// Find which row and column were clicked
					var clickedRow:uint=Math.floor(mouseY/60);
					var clickedColumn:uint=Math.floor(mouseX/60);
					// Check if the clicked gem is adjacent to the selector
					// If not...
					if (!(((clickedRow==selectorRow+1 || clickedRow==selectorRow-1)&&clickedColumn==selectorColumn)||((clickedColumn==selectorColumn+1 || clickedColumn==selectorColumn-1) && clickedRow==selectorRow))) {
						// Find row and colum the selector should move to
						selectorRow=clickedRow;
						selectorColumn=clickedColumn;
						// Move it to the chosen position
						selectorBox.x=60*selectorColumn;
						selectorBox.y=60*selectorRow;
						// If hidden, show it.
						selectorBox.visible=true;
					}
					// If it is not next to it...
					else {
						// Swap the gems;
						swapGems(selectorRow,selectorColumn,clickedRow,clickedColumn);
						// If they make a line...
						if (rowLineLength(selectorRow,selectorColumn)>2 || columnLineLength(selectorRow,selectorColumn)>2||rowLineLength(clickedRow,clickedColumn)>2 || columnLineLength(clickedRow,clickedColumn)>2) {
							// remove the hint text
							hint_txt.text="";
							// dis-allow a new move until cascade has ended (removes glitches)
							clickPossible=false;
							// move and rename the gems
							getChildByName(selectorRow+"_"+selectorColumn).x=clickedColumn*60;
							getChildByName(selectorRow+"_"+selectorColumn).y=clickedRow*60;
							getChildByName(selectorRow+"_"+selectorColumn).name="t";
							getChildByName(clickedRow+"_"+clickedColumn).x=selectorColumn*60;
							getChildByName(clickedRow+"_"+clickedColumn).y=selectorRow*60;
							getChildByName(clickedRow+"_"+clickedColumn).name=selectorRow+"_"+selectorColumn;
							getChildByName("t").name=clickedRow+"_"+clickedColumn;
							match = true;
						}
						// If not...
						else {
							// Switch them back
							swapGems(selectorRow,selectorColumn,clickedRow,clickedColumn);
							match = false;
						}
						if (match) {
							// Move the selector position to default
							selectorRow=-10;
							selectorColumn=-10;
							// and hide it
							selectorBox.visible=false;
						}
						else {
							// Set the selector position
							selectorRow=clickedRow;
							selectorColumn=clickedColumn;
							// Move the box into position
							selectorBox.x=60*selectorColumn;
							selectorBox.y=60*selectorRow;
							match = false;
							// If hidden, show it.
							selectorBox.visible=true;
						}
					}
				}
				// If the click is outside the game area
				else {
					// For gems in all rows...
					for (var i:uint=0; i<8; i++) {
						// and columns...
						for (var j:uint=0; j<8; j++) {
							// if they're not too close to the side... 
							if (i<7) {
								// swap them horizontally
								swapGems(i,j,i+1,j);
								// check if they form a line
								if ((rowLineLength(i,j)>2||columnLineLength(i,j)>2||rowLineLength(i+1,j)>2||columnLineLength(i+1,j)>2)) {
									// if so, name the move made
									selectorBox.x = j*60;
									selectorBox.y = i*60;
									selectorBox.visible = true;
									hint_txt.text = (i+1).toString()+","+(j+1).toString()+"->"+(i+2).toString()+","+(j+1).toString();
								}
								// swap the gems back
								swapGems(i,j,i+1,j);
							}
							// then if they're not to close to the bottom...
							if (j<7) {
								// swap it vertically
								swapGems(i,j,i,j+1);
								// check if it forms a line
								if ((rowLineLength(i,j)>2||columnLineLength(i,j)>2||rowLineLength(i,j+1)>2||columnLineLength(i,j+1)>2) ) {
									// if so, name it
									selectorBox.x = j*60;
									selectorBox.y = i*60;
									selectorBox.visible = true;
									hint_txt.text = (i+1).toString()+","+(j+1).toString()+"->"+(i+1).toString()+","+(j+2).toString();
								}
								// swap the gems back
								swapGems(i,j,i,j+1);
							}
						}
					}
				}
			}
		}
		//Swap given gems
		private function swapGems(fromRow:uint,fromColumn:uint,toRow:uint,toColumn:uint):void {
			//Save the original position
			var originalPosition:uint=gems_array[fromRow][fromColumn];
			//Move original gem to new position
			gems_array[fromRow][fromColumn]=gems_array[toRow][toColumn];
			//move second gem to saved, original gem's position
			gems_array[toRow][toColumn]=originalPosition;
		}
		//Find out if there us a horizontal line
		private function rowLineLength(row:uint,column:uint):uint {
			var gemType:uint=gems_array[row][column];
			var lineLength:uint=1;
			var checkColumn:int=column;
			//check how far left it extends
			while (sameGemIsHere(gemType,row,checkColumn-1)) {
				checkColumn--;
				lineLength++;
			}
			checkColumn=column;
			//check how far right it extends
			while (sameGemIsHere(gemType,row,checkColumn+1)) {
				checkColumn++;
				lineLength++;
			}
			// return total line length
			return (lineLength);
		}
		//Find out if there us a vertical line
		private function columnLineLength(row:uint,column:uint):uint {
			var gemType:uint=gems_array[row][column];
			var lineLength:uint=1;
			var checkRow:int=row;
			//check how low it extends
			while (sameGemIsHere(gemType,checkRow-1,column)) {
				checkRow--;
				lineLength++;
			}
			//check how high it extends
			checkRow=row;
			while (sameGemIsHere(gemType,checkRow+1,column)) {
				checkRow++;
				lineLength++;
			}
			// return total line length
			return (lineLength);
		}
		private function sameGemIsHere(gemType:uint,row:int,column:int):Boolean {
			//Check there are gems in the chosen row
			if (gems_array[row]==null) {
				return false;
			}
			//If there are, check if there is a gem in the chosen slot
			if (gems_array[row][column]==null) {
				return false;
			}
			//If there is, check if it's the same as the chosen gem type
			return gemType==gems_array[row][column];
		}
	}
}

And the final size? 2.711 bytes, a 33% larger than the original code.

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (9 votes, average: 4.89 out of 5)
Loading ... Loading ...
Flash Templates provided by Template Monster are pre-made web design products developed using Flash technology.
They can be easily customized to meet the unique requirements of your project.
Be my fan on Facebook and follow me on Twitter! Exclusive content for my Facebook fans and Twitter followers

This post has 6 comments

  1. hydrobot

    on May 9, 2011 at 11:19 am

    There’s nothing clearer than a 370 loc class :D

  2. Jae

    on May 9, 2011 at 4:09 pm

    There are a couple of optimizations that would maintain readability and reduce the code size (such as referencing the color WHITE on lines 32 & 36 and reducing some of the repeated code to function calls…but that would make the game slower), however I think this is fantastic on its own merit and deserves praise. Thank you Brook for an awesome job!

  3. Vladimir

    on May 9, 2011 at 8:02 pm

    Well :) sometimes the clarity of the code is more important than the size of the code. Although I find both the last code and this new one pretty understandable I guess that in a few weeks I’ll prefer the commented one ;)

  4. vega

    on May 10, 2011 at 10:32 pm

    @Jae: why should those changes affect the performance of the game at all?(in any visible manner)?

  5. Jocuri

    on May 25, 2011 at 3:41 pm

    Anyways, I think that the result is more important than the manner the code is written. I’m a newbie so sorry if anyone more experienced thinks something else…

  6. kegogrog

    on October 6, 2011 at 3:58 pm

    It’s all nice until you embed sound and fonts.

    Tripop is
    238KB for the background loop
    10KB for the sound effects
    39KB for the game itself (including embedded fonts)

    It’s made with completely code generated graphics and the final size with mochi preloader is at 307kB. The code is not optimized and totally laymenwise overdone. So, while I enjoy challenges like this, is there any real use to it?

    It’s successor (TripleDrop) is better performancewise but even 300kB bigger.