The order is a bit backwards, but before I go onto documenting the guts of the game, here's a post about how I created the splash screen.
There are several different "splash" screens, and for each screen, various interesting elements like typewriting the text and aliens moving across the screen. Much of it can probably be created without looking at the original code, but let's look at it anyway, since it's interesting to see how the original system printed text onto the screen.
I spent quite a bit of time delving into the original code since..it's interesting. I will document some of my findings here, but before I do that, a quick review of how the screen is structured in the original hardware and how that relates to Javascript world.
Converting Midway 8080 screen coordinate into Javascript world x, y coordinates
I tend to spend an inordinate amount of time trying to figure out where to position things in the Javascript screen when looking at the original code. As such I ended up using EXCEL to to convert a video RAM address to the rotated Javascript screen. It's nothing more than subtracting $2400 from the screen address, first calculate the column and row in the unrotated space (column = rounddown((address - $2400)/32), row (in pixels = MOD((address - $2400), 32) * 8 (for the 0th bit). Then the x,y coordinate in the rotated Javascript world is simply:
x = column;
y = 248 - row (for the top left hand corner pixel of the image)
Overall flow of the most basic splash screen
As mentioned above, there are various different splash screens, which play one after the other, until the player inserts coin and starts the game. As far as I can tell, the original game cycled through the following splash screens.
Basic "Play Space Invaders" Screen
Self-playing demo game (self-playing game with no sound)
"INSERT COIN <1 OR 2 PLAYERS>" Screen
Inverted Y "Play Space Invaders Screen", with animated alien correcting the Y
Self-playing demo game
Duplicate C "INSERT COIN <1 OR 2 PLAYERS>" Screen, with animated alien shooting away the extra C
In this post I delve into the first of the above splash screens.
Basic "Play Space Invaders" Screen
The most basic of the splash screens prints the following 2 lines in the top half of the screen, "type-writing" them:
PLAY
SPACE INVADERS
Then *SCORE ADVANCE TABLE* together with the images of the aliens are displayed (not type-written).
Then the text explaining the points of each alien are again, type-written.
The overall code that controls the above process appears to be below.
; After initialization ... splash screens
0AEA: AF XOR A ; Make a 0
0AEB: D3 03 OUT (SOUND1),A ; Turn off sound
0AED: D3 05 OUT (SOUND2),A ; Turn off sound
0AEF: CD 82 19 CALL $1982 ; Turn off ISR splash-task
0AF2: FB EI ; Enable interrupts (using them for delays)
0AF3: CD B1 0A CALL OneSecDelay ; One second delay
0AF6: 3A EC 20 LD A,(splashAnimate) ; Splash screen type
0AF9: A7 AND A ; Set flags based on type
0AFA: 21 17 30 LD HL,$3017 ; Screen coordinates (middle near top)
0AFD: 0E 04 LD C,$04 ; 4 characters in "PLAY"
0AFF: C2 E8 0B JP NZ,$0BE8 ; Not 0 ... do "normal" PLAY
0B02: 11 FA 1C LD DE,$1CFA ; The "PLAy" with an upside down 'Y'
0B05: CD 93 0A CALL PrintMessageDel ; Print the "PLAy"
0B08: 11 AF 1D LD DE,$1DAF ; "SPACE INVADERS" message
0B0B: CD CF 0A CALL $0ACF ; Print to middle-ish of screen
0B0E: CD B1 0A CALL OneSecDelay ; One second delay
0B11: CD 15 18 CALL DrawAdvTable ; Draw "SCORE ADVANCE TABLE" with print delay
0B14: CD B6 0A CALL TwoSecDelay ; Two second delay
0B17: 3A EC 20 LD A,(splashAnimate) ; Do splash ...
0B1A: A7 AND A ; ... animations?
0B1B: C2 4A 0B JP NZ,$0B4A ; Not 0 ... no animations
Now, let's take a look at the specific pieces that draws the various elements making up this splash screen.
Drawing the "Advance Score Table"
In the above code, the location to print "PLAY" is first loaded $3017 (translates to 96, 64), the number of characters (=4) loaded to register C and the following code is called.
0BE8: 11 AB 1D LD DE,$1DAB ; "PLAY" with normal 'Y'
0BEB: CD 93 0A CALL PrintMessageDel ; Print it
0BEE: C3 0B 0B JP $0B0B ; Continue with splash (HL will be pointing to next message)
The text string is held at $1DAB, and "PrintMessageDel" (as we will see below) is a routine that "type-writes" text.
Then the following piece of code is called to print SPACE INVADERS.
; Message to center of screen.
; Only used in one place for "SPACE INVADERS"
0ACF: 21 14 2B LD HL,$2B14 ; Near center of screen
0AD2: 0E 0F LD C,$0F ; 15 bytes in message
0AD4: C3 93 0A JP PrintMessageDel ; Print and out
The code starts by loading register-HL with the coordinate; $2B14 (this would be (56,88) for positioning text in Phaser). Then the number of characters to be printed is loaded into register-C and PrintMessageDel is called.
Typewriter text
The typewriter effect is achieved by calling PrintMessageDel, which is reproduced below. The "delay" between each text is created simply by looping through an empty loop 7 times, but the key to actually draw text on the screen is DrawChart.
PrintMessageDel:
; Print message from DE to screen at HL (length in C) with a
; delay between letters.
0A93: D5 PUSH DE ; Preserve
0A94: 1A LD A,(DE) ; Get character
0A95: CD FF 08 CALL DrawChar ; Draw character on screen
0A98: D1 POP DE ; Preserve
0A99: 3E 07 LD A,$07 ; Delay between letters
0A9B: 32 C0 20 LD (isrDelay),A ; Set counter
0A9E: 3A C0 20 LD A,(isrDelay) ; Get counter
0AA1: 3D DEC A ; Is it 1?
0AA2: C2 9E 0A JP NZ,$0A9E ; No ... wait on it
0AA5: 13 INC DE ; Next in message
0AA6: 0D DEC C ; All done?
0AA7: C2 93 0A JP NZ,PrintMessageDel ; No ... do all
0AAA: C9 RET ; Out
"Drawing Text" to the screen
First thing this code does is to load register DE with the location of the bitmap information of characters, which is $1E00. What's stored there is in essence is not much different from sprite images. When looked at in combination with the data sitting at $1E00, it becomes clear that it's pretty similar to the way player sprite is drawn.
DrawChar:
; Get pointer to 8 byte sprite number in A and
; draw sprite on screen at HL
08FF: 11 00 1E LD DE,$1E00 ; Character set
0902: E5 PUSH HL ; Preserve
0903: 26 00 LD H,$00 ; MSB=0
0905: 6F LD L,A ; Character number to L
0906: 29 ADD HL,HL ; HL = HL *2
0907: 29 ADD HL,HL ; *4
0908: 29 ADD HL,HL ; *8 (8 bytes each)
0909: 19 ADD HL,DE ; Get pointer to sprite
090A: EB EX DE,HL ; Now into DE
090B: E1 POP HL ; Restore HL
090C: 06 08 LD B,$08 ; 8 bytes each
090E: D3 06 OUT (WATCHDOG),A ; Feed watchdog
0910: C3 39 14 JP DrawSimpSprite ; To screen
BitMap Image of each text character stored from $1E00
Each character comprise of 8 bytes, or 8x8 pixels, like below.
1E00: 00 1F 24 44 24 1F 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........
1E08: 00 7F 49 49 49 36 00 00 ; *****... *******. .*****.. *******. *******. *******. .*****.. *******.
1E10: 00 3E 41 41 41 22 00 00 ; ..*..*.. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.....*. ...*....
1E18: 00 7F 41 41 41 3E 00 00 ; ..*...*. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.....*. ...*....
1E20: 00 7F 49 49 49 41 00 00 ; ..*..*.. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.*...*. ...*....
1E28: 00 7F 48 48 48 40 00 00 ; *****... .**.**.. .*...*.. .*****.. *.....*. ......*. ***...*. *******.
1E30: 00 3E 41 41 45 47 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........
1E38: 00 7F 08 08 08 7F 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........
It's very confusing since it's rotated, but when rotated 90 degrees anti-clockwise, the above looks like this.
Armed with this knowledge, it is relatively straightforward to figure out how the actual score table is drawn
Advance Score Table
The code starts by loading register-HL with the coordinate (in rows and column) of the first letter of text to be printed. In this case, it is $2810, which is, as the CA commentary explains (32,16) in the rotated screen (bottom left hand side being 0,0). In the Javascript world, this would be (32,120).
DrawAdvTable:
; Draw "SCORE ADVANCE TABLE"
1815: 21 10 28 LD HL,$2810 ; 0x410 is 1040 rotCol=32, rotRow=16
1818: 11 A3 1C LD DE,$1CA3 ; "*SCORE ADVANCE TABLE*"
181B: 0E 15 LD C,$15 ; 21 bytes in message
181D: CD F3 08 CALL PrintMessage ; Print message
1820: 3E 0A LD A,$0A ; 10 bytes in every "=xx POINTS" string
1822: 32 6C 20 LD (temp206C),A ; Hold the count
1825: 01 BE 1D LD BC,$1DBE ; Coordinate/sprite for drawing table
1828: CD 56 18 CALL ReadPriStruct ; Get HL=coordinate, DE=image
182B: DA 37 18 JP C,$1837 ; Move on if done
182E: CD 44 18 CALL $1844 ; Draw 16-byte sprite
1831: C3 28 18 JP $1828 ; Do all in table
;
1834: CD B1 0A CALL OneSecDelay ; One second delay
1837: 01 CF 1D LD BC,$1DCF ; Coordinate/message for drawing table
183A: CD 56 18 CALL ReadPriStruct ; Get HL=coordinate, DE=message
183D: D8 RET C ; Out if done
183E: CD 4C 18 CALL $184C ; Print message
1841: C3 3A 18 JP $183A ; Do all in table
;
1844: C5 PUSH BC ; Hold BC
1845: 06 10 LD B,$10 ; 16 bytes
1847: CD 39 14 CALL DrawSimpSprite ; Draw simple
184A: C1 POP BC ; Restore BC
184B: C9 RET ; Out
;
184C: C5 PUSH BC ; Hold BC
184D: 3A 6C 20 LD A,(temp206C) ; Count of 10 ...
1850: 4F LD C,A ; ... to C
1851: CD 93 0A CALL PrintMessageDel ; Print the message with delay between letters
1854: C1 POP BC ; Restore BC
1855: C9 RET ; Out
In the second line, register DE is loaded with a pointer to the text to be printed. The "string" is stored like so.
MessageAdv:
; "*SCORE ADVANCE TABLE*"
1CA3: 28 12 02 0E 11 04 26 00
1CAB: 03 15 00 0D 02 04 26 13
1CB3: 00 01 0B 04 28
In the third line, register C is loaded with the number of characters to be printed (in the above case $15 or 21 characters).
Then PrintMessage code is called. PrintMessage code loads the actual character (essentially the "text code" which indicates where to look for a particular character data beyond $1E00, so in the case of A, the first letter, that would be 0), loops through incrementing register DE - i.e. the string text) calling DrawChar code - the engine that prints text - below.
Now draw the sprites for the advance score table
The code (highlighted in RED) now goes onto print the actual alien sprites in the score table. $1DBE loaded into register-BC holds the information to tell which sprite to draw where. Specifically, the first 2 bytes tells the location on the screen, and the latter 2 bytes the sprite image. ReadPriStruct actually reads the 4-byte data, loading HL with the screen coordinate, and DE with the address for the image.
; Tables used to draw "SCORE ADVANCE TABLE" information
1DBE: 0E 2C 68 1D ; Flying Saucer
1DC2: 0C 2C 20 1C ; Alien C, sprite 0
1DC6: 0A 2C 40 1C ; Alien B, sprite 1
1DCA: 08 2C 00 1C ; Alien A, sprite 0
1DCE: FF ; End of list
For information the locations above are as follows. The rotated Javascript coordinate is the bottom left hand corner.
Rotatated JS
saucer: 2C0E = (64, 143)
squid: 2C0C = (64, 159)
crab: 2C0A = (64, 175)
octopus: 2C08 = (64, 194)
To make matters a little bit complicated in positioning the sprites, whereas the original code all the alien sprites are 16 pixels wide and 8 pixels high (except for the saucer which is 24 pixels wide I think), the image textures I have created have no extra spaces hence vary in size in the x-axis; hence the x-coordinate needs adjusting (specifically: saucer=16 px wide, squid=8px, crab=11 px, octopus= 12px).
Typewriter text the actual score table
Once the image of the aliens have been printed, there is then a delay of 1 second, before the actual scores for each alien is printed. First, register BC is loaded with the address of the text to be printed. Then ReadPriStruct is called to read the 4-byte data necessary to print the information.
AlienScoreTable:
1DCF: 0E 2E E0 1D ; "=? MYSTERY"
1DD3: 0C 2E EA 1D ; "=30 POINTS"
1DD7: 0A 2E F4 1D ; "=20 POINTS"
1DDB: 08 2E 99 1C ; "=10 POINTS"
1DDF: FF ; End of list
For information the locations above are as follows:
2E0E: (80, 136)
2E0C: (80, 152)
2E0A: (80, 168)
2E08: (80, 184)
Then $184C is called, which loops through the text above (10 characters each).
184C: C5 PUSH BC ; Hold BC
184D: 3A 6C 20 LD A,(temp206C) ; Count of 10 ...
1850: 4F LD C,A ; ... to C
1851: CD 93 0A CALL PrintMessageDel ; Print the message with delay between letters
1854: C1 POP BC ; Restore BC
1855: C9 RET ; Out
Feeding the watchdog
There is this intriguing comment "Feed watchdog" next to the code that feeds register A to the port called WATCHDOG. This is apparently an internal timer that is designed to prevent accidents from crashing the game. It is necessary to "feed the watchdog" every so often; otherwise the watchdog will think the code has gone into some kind of infinite loop. In the Midway 8080, the watchdog lives at I/O port 6 and you can "feed" this dog by writing anything value to that port. Strange stuff!
Creating the Advance Score Table screen in Javascript Phaser
In essence I have created a separate Phaser scene which displays the various sprites and the relevant text, and waits for the player to push the ENTER button, before moving onto the next scene.
The TAITO font
The little bit of creativity was spent on utilising the fonts from the original arcade game. If anything, using "modern day" fonts did not look at all right given the very low resolution of the game - put simply, modern day fonts looked "too thick".
The original fonts are 8x8 "sprites", far simpler than modern day fonts. There are lots of different ways of displaying text in Phaser 3. Somewhat remarkably, there are built-in functions to parse such png files and then display so called bitmap fonts based on such parsed data.
The official documentation on its use is rather sparse but is easy enough to use.
You first load the image in preload function.
this.load.image('TAITO', 'TAITOFonts.png');
Then you parse the loaded image as follows from the create function.
this.cache.bitmapFont.add('TAITO', Phaser.GameObjects.RetroFont.Parse(this, {
image: 'TAITO',
width: 8,
height: 8,
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<>=*?-^ ',
charsPerRow: 8,
spacing: { x: 2, y: 2 }
}));
}
Typewriting Text
Below is the new Phaser Scene created just for the purpose of displaying this advance score table splash screen.
Frankly, I struggled in figuring out how best to achieve the typewriting text effect, given the nature of Javascript.
class SplashScreen extends Phaser.Scene {
constructor() {
super('SplashScreen');
this.status;
}
static Status = {
ANIMATE: 0,
WAIT: 1,
READY: 2,
}
create() {
this.add.bitmapText(36, 8, 'TAITO', "SCORE<1>").setOrigin(0.5);
this.add.bitmapText(SCREEN_WIDTH *0.5, 8, 'TAITO', "HI-SCORE").setOrigin(0.5);
this.add.bitmapText(SCREEN_WIDTH-36, 8, 'TAITO',"SCORE<2>").setOrigin(0.5);
this.add.bitmapText(SCREEN_WIDTH*0.5, 60, 'TAITO',"PLAY").setOrigin(0.5);
this.add.bitmapText(56, 88, 'TAITO',"SPACE INVADERS");
this.add.bitmapText(32, 120, 'TAITO',"*SCORE ADVANCE TABLE*");
this.add.image(64-8,136,"saucer").setOrigin(0.5,0);
this.add.image(64-8,152,"squid").setOrigin(0.5,0);
this.add.image(64-8,168,"crab").setOrigin(0.5,0);
this.add.image(64-8,184,"octopus").setOrigin(0.5,0);
this.AdvScoreTable = new ScoreTable(this);
this.enterKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.ENTER);
this.setStatus(SplashScreen.Status.WAIT);
}
update() {
switch (this.status) {
case SplashScreen.Status.ANIMATE:
this.AdvScoreTable.update();
if (this.AdvScoreTable.complete()) this.setStatus(SplashScreen.Status.READY);
break;
case SplashScreen.Status.READY:
if (Phaser.Input.Keyboard.JustDown(this.enterKey)) this.scene.start('PlayScreen');
break;
}
}
setStatus(newStatus) {
this.status = newStatus;
switch (this.status) {
case SplashScreen.Status.WAIT:
this.time.delayedCall(1000, this.setStatus,[SplashScreen.Status.ANIMATE], this);
break;
case SplashScreen.Status.ANIMATE:
this.AdvScoreTable.setStatus(ScoreTable.Status.ANIMATE)
break;
}
}
}
In the end I maintained the use of counters based on the screen refresh cycle, which made the code ridiculously complex, to achieve such a simple effect. To make the code a little bit easier to read I have created a separate object, with its own method of typewriting the score table with a delay of 7 frame count and a status flag to indicate when the process is complete. This object is repeatedly called by the Splash scene update function until the typewriting is complete, at which time the Splash scene changes its own status to READY and waits for the user to press the ENTER key, before going onto the main game.
Because of the complexity, I only type-write the advance score table I do not type-write the "PLAY" or "SPACE INVADERS" text, as in the original.
I would be most grateful if someone could give me some pointers as to how to achieve this in a more elegant manner.
class ScoreTable {
constructor(scene) {
this.scene = scene;
this.status;
this.typeCounter;
this.delayCounter;
this.setStatus(ScoreTable.Status.INIT)
}
static Status = {
INIT: 0,
ANIMATE: 1,
COMPLETE: 2
}
static alienScoreTable = [
{y: 136, text: "=? MYSTERY"},
{y: 152, text: "=30 POINTS"},
{y: 168, text: "=20 POINTS"},
{y: 184, text: "=10 POINTS"}
];
static textLength = 10;
static delayCount = 7;
update() {
switch (this.status) {
case ScoreTable.Status.ANIMATE:
this.typeWrite();
break;
}
}
setStatus(newStatus) {
this.status = newStatus;
switch (this.status) {
case ScoreTable.Status.INIT:
this.typeCounter = 0;
this.delayCounter = 0;
break;
}
}
typeWrite() {
this.delayCounter ++;
if (this.delayCounter >= ScoreTable.delayCount) {
const line = Math.floor(this.typeCounter / ScoreTable.textLength); // there are 10 characters in each line;
const scoreText = ScoreTable.alienScoreTable[line].text.substring(0,this.typeCounter % ScoreTable.textLength +1)
this.scene.add.bitmapText(80, ScoreTable.alienScoreTable[line].y, 'TAITO', scoreText);
this.delayCounter = 0;
this.typeCounter ++;
if (this.typeCounter >= ScoreTable.textLength * 4) this.setStatus(ScoreTable.Status.COMPLETE)
}
}
complete() {
return (this.status === ScoreTable.Status.COMPLETE)
}
}
The above code is available in this CODEPEN
Comments