Technical Notes on Goodnite Luddite =================================== Goodnite Luddite uses a variant of the Jet Set Willy game-engine which is substantially modified for the following three purposes: * The arrows are cyan rather than white; * Goodnite Luddite uses its own font; * Goodnite Luddite uses a 24-hour clock. Cyan Arrows ----------- The arrows in Goodnite Luddite are cyan (INK 5) rather than white (INK 7). I achieved this with POKE 37512,5 - but this is not as straightforward as it seems! The byte at 37512 is actually the operand of an OR instruction, used to set the ink-colour of a character-square without changing its paper-colour, brightness or flash. For the standard white arrows, this instruction is OR 00000111, which sets the INK to 7 regardless of the background ink-colour (here, `background' means whatever character-square the arrow is passing through). However, for cyan arrows, the instruction is OR 00000101, which sets the INK to either 5 or 7, depending on whether the middle bit of the background INK is 0 (for background INK 0, 1, 4, 5) or 1 (for background INK 2, 3, 6, 7). Note that the colour of an arrow is separate from the ink-colour of the pixels it can collide with. Goodnite Luddite's cyan arrows are still white-seeking missiles! But they don't collect items for you, because items are only collected when they are drawn on white-ink character-squares. Changing the Font ----------------- Goodnite Luddite uses a font from Journey's End, rather than the standard Spectrum font. The reader who just wants to know how to change the font, without understanding why, may skip the following indented section: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> The standard Spectrum font resides at addresses 15616-16383: the 8x8 graphics (8 bytes each) for 96 characters (ASCII codes 32-127). Thus we need to modify the routine (in the JSW engine) that looks up a character in the Spectrum font and prints it on the screen. This routine is located at address 38545. It takes, as inputs, the ASCII code of the character to print in register A {32 <= A < 128}, and the address to print it on the screen in register-pair DE. Let's examine the routine to print a character in its assembly-form, which is: 38545: LD H,7 ; HL = 256*7 = 1792 38547: LD L,A ; HL = 1792 + A 38548: SET 7,L ; HL = 1792 + A + 128 [set the MSB of L to 1] = 1920 + A 38550: ADD HL,HL ; HL = 3840 + 2*A 38551: ADD HL,HL ; HL = 7680 + 4*A 38552: ADD HL,HL ; HL = 15360 + 8*A, i.e. address of character in 15616-16383 38553: LD B,8 ; repeat the following 8 times: 38555: LD A,(HL) ; A = row of 8 pixels to print (peek HL) 38556: LD (DE),A ; draw it on the screen 38557: INC HL ; HL = address of next row of pixels to read 38558: INC D ; move down a pixel-row on the screen (DE:= DE + 256) 38559: DJNZ -6 ; B:= B-1; if B > 0 then jump back to 38555 38561: RET The first half of this routine calculates the address a of the 8x8 graphic of the character c to print as: a = (256*x + 128 + c)*8 = 2048*x + 1024 + 8*c where x is the byte that is loaded into H (the high-byte of HL) at the start (i.e. x = PEEK 38546). Let b = 2048*x + 1024, so that a = b + 8*c (address = base + offset). The base-address is fixed at b = 2048*7 + 1024 = 15360 (which means that the character-graphics are stored at 15616-16383, since {32 <= c < 128}). Thus we can relocate the character-graphics by solving the equation b = 2048*x + 1024 to determine the value of x for a given base-address b, i.e. x = (b - 1024)/2048. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< To use a different font, we must copy the character-graphics to an appropriate place in memory, and modify the character-printing routine to point to the new character-graphics. The character-printing routine is modified by: POKE 38546, x where x = (b-1024)/2048 and b is the base-address for the character-graphics, which are located at addresses b+256 to b+1023 (ASCII codes 32-127 only). Note that x must be an integer, therefore b must be divisible by 1024 and NOT divisible by 2048. For Goodnite Luddite, I chose a base-address of 48128 for the character-graphics (i.e. they are located at 48384-49151). Thus I used POKE 38546,23 to point to my character-graphics (x = (48128-1024)/2048). In fact I decided not to use characters 96-127, leaving 48896-49151 free for 16x16 graphics. This restricts Goodnite Luddite from using lower-case letters (it was always an artistic choice to use only upper-case in We Pretty and Goodnite Luddite), and the copyright symbol has ASCII code 95 instead of 127. The 24-hour Clock ----------------- The original JSW uses a 12-hour am/pm clock, and simply updates the string that is displayed on the screen every JSW-minute (256 time-frames): * The "m" is hard-wired into the text that is displayed on row 19 during play (34132-34163). * The start-time is held at 34181-34186 as a string " 7:00a", and the current time, which is updated during play, is held at 34175-34180 in the same format. * The address in video-RAM to print the leftmost character of the time-string is held in 35382/35383 - for Goodnite Luddite, I have moved it one character to the right with POKE 35382,122. The original JSW uses the following code to update the clock: 35442: LD A,(IX+0) ; if tens_of_hours 35445: CP 49 ; <> "1" 35447: JR NZ,32 ; then jump to 35481 35449: INC (IX+1) ; units_of_hours:= units_of_hours + 1 35452: LD A,(IX+1) ; if units_of_hours 35455: CP 51 ; <> "3" 35457: JR NZ,40 ; then jump to 35499 35459: LD A,(IX+5) ; if am_or_pm 35462: CP 112 ; = "p" 35464: JP Z,34762 ; then jump to 34762, i.e. the title-screen 35467: LD (IX+0),32 ; 35471: LD (IX+1),49 ; hours:= " 1" 35475: LD (IX+5),112 ; am_or_pm:= "p" 35479: JR 18 ; jump to 35499 35481: INC (IX+1) ; units_of_hours:= units_of_hours + 1 35484: LD A,(IX+1) ; if units_of_hours 35487: CP 58 ; <> ":" (i.e. the character after "9" in the ASCII set) 35489: JR NZ,8 ; then jump to 35499 35491: LD (IX+1),48 ; 35495: LD (IX+0),49 ; hours:= "10" The above code has the bug that the hour after 11am is 12am, because it changes "a" to "p" when it changes "12" to " 1". Furthermore, the game does /not/ end at midnight as advertised, but at 1am (after the hour it calls "12pm"). So I replaced the above with the following code to implement a 24-hour clock and end the game at 24:00. Furthermore it goes to the Game-Over-screen when the time runs out, whereas the above goes straight to the title-screen. 35442: INC (IX+1) ; units_of_hours:= units_of_hours + 1 35445: LD A,(IX+0) ; if tens_of_hours 35448: CP 50 ; = "2" 35450: JR Z,14 ; then jump to 35466 35452: LD A,(IX+1) ; if units_of_hours 35455: CP 58 ; <> ":" (i.e. the character after "9" in the ASCII set) 35457: JR NZ,40 ; then jump to 35499 35459: LD (IX+1),48 ; units_of_hours:= "0" 35463: INC (IX+0) ; tens_of_hours:= tens_of_hours + 1 35466: LD A,(IX+1) ; if units_of_hours 35469: CP 52 ; <> "4" 35471: JR NZ,26 ; then jump to 35499 35473: JP 35914 ; jump to 35914, i.e. GAME `OVA Addresses 35476-35498 (23 bytes) are unused now.