| ← BCAD | Map | BE03 → |
BCB9: Front end: title screen, input-device menu, and attract cycle
|
Runs the game's front end, and returns only when the player starts a game. It leaves the chosen input device set.
|
|||
| kstate_save | BCB9 | DEFB $00,$00,$00,$00,$00,$00,$00,$00 | Saved copy of the ROM keyboard state |
| frames_save | BCC1 | DEFB $00,$00,$00 | Saved copy of the ROM FRAMES counter: its low word first, then its high byte |
| menu_attr_ptr | BCC4 | DEFB $0A,$59 | The attribute-cell address of the input-device line the menu highlights, low byte first |
|
Setup: black border, cleared bitmap, bright white attributes, logo and menu drawn, credits scroller primed, title tune cued. Falls into the menu loop.
|
|||
| title_screen | BCC6 | XOR A | Black border |
| BCC7 | OUT ($FE),A | ||
| BCC9 | CALL clear_screen | ||
| BCCC | LD A,$47 | Paint every attribute cell bright white on black. | |
| BCCE | CALL fill_attrs | ||
| BCD1 | CALL draw_title_logo | ||
| BCD4 | CALL draw_title_menu | ||
| BCD7 | CALL credits_scroll_init | ||
| BCDA | LD HL,title_tune | Point the tune pointer at the start of the title tune. | |
| BCDD | LD (tune_ptr),HL | ||
|
One pass per loop. Poll the 1–4 device keys and pulse the selected menu line. Check the O key, play one tune note, then run one attract/frame step. An option key does nothing when its device is already current, so a second press of the active option neither beeps nor redraws. There is no option 5 on the menu, so its key is ignored.
|
|||
| title_menu_loop | BCE0 | LD A,(input_device) | Stash the current input device in A' for the re-select check below. |
| BCE3 | EX AF,AF' | ||
| BCE4 | LD BC,KBD_ROW_1_5 | Read the 1/2/3/4/5 keyboard half-row and keep the five key bits. | |
| BCE7 | IN A,(C) | ||
| BCE9 | AND $1F | ||
| BCEB | BIT 0,A | Key 1 down? If not, fall through to the key-2 test. | |
| BCED | JR NZ,title_check_key2 | ||
| BCEF | EX AF,AF' | Already keyboard? Then just keep the pulse – no reselect. | |
| BCF0 | OR A | ||
| BCF1 | JR Z,title_pulse_menu | ||
| BCF3 | LD HL,ATTR_BASE+$10A | Otherwise arm the keyboard and select the KEYBOARD line's attributes. | |
| BCF6 | XOR A | ||
| BCF7 | JR title_set_device | ||
| title_check_key2 | BCF9 | BIT 1,A | Key 2 down? If not, fall through to the key-3 test. |
| BCFB | JR NZ,title_check_key3 | ||
| BCFD | EX AF,AF' | Already Kempston? Then just keep the pulse. | |
| BCFE | CP $01 | ||
| BD00 | JR Z,title_pulse_menu | ||
| BD02 | LD HL,ATTR_BASE+$14A | Otherwise arm Kempston and select the KEMPSTON line's attributes. | |
| BD05 | LD A,$01 | ||
| BD07 | JR title_set_device | ||
| title_check_key3 | BD09 | BIT 2,A | Key 3 down? If not, fall through to the key-4 test. |
| BD0B | JR NZ,title_check_key4 | ||
| BD0D | EX AF,AF' | Already the cursor keys? Then just keep the pulse. | |
| BD0E | CP $02 | ||
| BD10 | JR Z,title_pulse_menu | ||
| BD12 | LD HL,ATTR_BASE+$18A | Otherwise arm the cursor keys and select the CURSOR line's attributes. | |
| BD15 | LD A,$02 | ||
| BD17 | JR title_set_device | ||
| title_check_key4 | BD19 | BIT 3,A | Key 4 down? If not, nothing is selected – go pulse the current line. |
| BD1B | JR NZ,title_pulse_menu | ||
| BD1D | EX AF,AF' | Already the Sinclair joystick? Then just keep the pulse. | |
| BD1E | CP $03 | ||
| BD20 | JR Z,title_pulse_menu | ||
| BD22 | LD HL,ATTR_BASE+$1CA | Otherwise arm the Sinclair/Interface II joystick and select that line's attributes; falls into the setter. | |
| BD25 | LD A,$03 | ||
|
Commit the newly chosen option. Remember the line's attribute address for the pulse and store the device. Redraw the menu to clear the old highlight, and beep.
|
|||
| title_set_device | BD27 | LD (menu_attr_ptr),HL | Save the selected line's attribute address (in the buffer just below this routine) for the pulse animation. |
| BD2A | LD (input_device),A | Store the chosen device. | |
| BD2D | CALL draw_title_menu | ||
| BD30 | CALL sfx_key_blip | ||
|
Pulse the selected option. Both character rows of the line step through the bright inks magenta, green, cyan, yellow and white once per frame. From white they wrap back to magenta. The line comes from the choice made by the option keys, the KEYBOARD line by default.
|
|||
| title_pulse_menu | BD33 | LD HL,(menu_attr_ptr) | Load the selected line's attribute address; keep a copy for the second row. |
| BD36 | PUSH HL | ||
| BD37 | LD B,$14 | Pulse every cell of the line's first character row. | |
| title_pulse_loop1 | BD39 | LD A,(HL) | Step this cell's attribute, and wrap at the end of the run. |
| BD3A | INC A | ||
| BD3B | CP $48 | ||
| BD3D | JR NC,title_pulse_wrap1 | ||
| title_pulse_set1 | BD3F | LD (HL),A | Store the stepped attribute, advance to the next cell, and repeat for all 20. |
| BD40 | INC L | ||
| BD41 | DJNZ title_pulse_loop1 | ||
| BD43 | JR title_pulse_line2 | ||
| title_pulse_wrap1 | BD45 | LD A,$43 | Past bright white – restart the cell at bright magenta. |
| BD47 | JR title_pulse_set1 | ||
| title_pulse_line2 | BD49 | POP HL | Drop to the menu line's second character row and pulse it the same way. |
| BD4A | CALL advance_buffer_row | ||
| BD4D | LD B,$14 | ||
| title_pulse_loop2 | BD4F | LD A,(HL) | Step each cell of the second row the same way. |
| BD50 | INC A | ||
| BD51 | CP $48 | ||
| BD53 | JR NC,title_pulse_wrap2 | ||
| title_pulse_set2 | BD55 | LD (HL),A | Write the stepped ink back to this cell. |
| BD56 | INC L | ||
| BD57 | DJNZ title_pulse_loop2 | ||
| BD59 | JR title_check_options | ||
| title_pulse_wrap2 | BD5B | LD A,$43 | Past bright white – restart the cell at bright magenta. |
| BD5D | JR title_pulse_set2 | ||
|
Check the O key (options), then advance the title tune by one note. At the terminator the tune loops.
|
|||
| title_check_options | BD5F | LD BC,KBD_ROW_Y_P | Read the Y/U/I/O/P half-row and keep the five key bits. |
| BD62 | IN A,(C) | ||
| BD64 | AND $1F | ||
| BD66 | BIT 1,A | O pressed? Enter the options screen. | |
| BD68 | JP Z,options_menu | ||
| BD6B | CALL play_tune_note | Play the next title-tune note. Has the tune reached its terminator? | |
| BD6E | CP $0D | ||
| BD70 | JR NZ,title_attract_loop | Not the end – skip the rewind. | |
| BD72 | LD HL,title_tune | Tune finished – rewind the tune pointer to loop it. | |
| BD75 | LD (tune_ptr),HL | ||
|
Per-frame step of the menu loop. It busy-waits to set the pace, then waits for one frame, the front end's only interrupt sync. The ROM keyboard state and frame counter are saved and restored around it, because they lie inside the sprite bank. Then scroll the credits two pixels, read the player's controls, and test the start inputs.
|
|||
| title_attract_loop | BD78 | LD BC,$05DC | Pace the loop with a busy-wait. |
| BD7B | CALL delay | ||
| BD7E | LD IY,$0000 | Park IY at zero before the frame sync, because the interrupt's ROM handler writes through it. The keyboard scan sets a flag bit in the byte after IY on each new key. The frame tick increments a byte at a fixed offset from IY each time FRAMES wraps. Parked at zero, both writes land in ROM and do nothing. After a game IY still points at a game record, where they would land in game memory. | |
| BD82 | LD HL,slot_visor_1 | Save the ROM keyboard state (KSTATE) into the buffer below this routine. Those bytes are also the head of the sprite bank, and the interrupt that ends the HALT below rewrites them. The same interrupt also writes the byte after them, which nothing restores – see Defects. | |
| BD85 | LD DE,kstate_save | ||
| BD88 | LD BC,$0008 | ||
| BD8B | LDIR | ||
| BD8D | LD HL,(frames_low) | Save the ROM frame counter (FRAMES). | |
| BD90 | LD (frames_save),HL | ||
| BD93 | LD A,(frames_high) | ||
| BD96 | LD (frames_save+$2),A | ||
| BD99 | EI | Wait for the next frame interrupt. | |
| BD9A | HALT | ||
| BD9B | DI | ||
| BD9C | LD HL,kstate_save | Restore KSTATE so the interrupt's keyboard scan does not perturb it. | |
| BD9F | LD DE,slot_visor_1 | ||
| BDA2 | LD BC,$0008 | ||
| BDA5 | LDIR | ||
| BDA7 | LD HL,(frames_save) | Restore the ROM frame counter. | |
| BDAA | LD (frames_low),HL | ||
| BDAD | LD A,(frames_save+$2) | ||
| BDB0 | LD (frames_high),A | ||
| BDB3 | CALL credits_scroll_step | Scroll the credits two pixels this frame (two steps). | |
| BDB6 | CALL credits_scroll_step | ||
| BDB9 | CP $FF | Credits exhausted? Switch to the high-score display. | |
| BDBB | JP Z,title_attract_check | ||
| BDBE | CALL read_ctrl | ||
| BDC1 | LD HL,controls | Fire held? Return to start the game. | |
| BDC4 | BIT 4,(HL) | ||
| BDC6 | RET NZ | ||
| BDC7 | LD BC,KBD_ROW_6_0 | Read the 0/9/8/7/6 half-row and keep the five key bits. | |
| BDCA | IN A,(C) | ||
| BDCC | AND $1F | ||
| BDCE | BIT 0,A | Key 0 up? Loop the menu. Key 0 (START) pressed falls through to RET and starts the game. | |
| BDD0 | JP NZ,title_menu_loop | ||
| BDD3 | RET | ||
|
Credits finished: show the high-score table and play the title tune once. Any key, the end of the tune, or fire restarts the front end. The game cannot be started from this screen – fire here only loops back to the menu.
|
|||
| title_attract_check | BDD4 | CALL draw_highscore_table | |
| BDD7 | LD HL,title_tune | Rewind the tune pointer for the high-score wait. | |
| BDDA | LD (tune_ptr),HL | ||
| title_attract_cont | BDDD | LD BC,$0BB8 | Pace the high-score wait. |
| BDE0 | CALL delay | ||
| BDE3 | CALL play_tune_note | Play the next tune note. Has the tune reached its terminator? | |
| BDE6 | CP $0D | ||
| BDE8 | JP Z,title_screen | Restart the whole front end. | |
| BDEB | LD BC,$00FE | Read all key half-rows at once and keep the five key bits. | |
| BDEE | IN A,(C) | ||
| BDF0 | AND $1F | ||
| BDF2 | XOR $1F | ||
| BDF4 | JR NZ,title_attract_end | Any key down? End the wait and restart the front end. | |
| BDF6 | CALL read_ctrl | ||
| BDF9 | LD HL,controls | Loop until fire is pressed, then restart. | |
| BDFC | BIT 4,(HL) | ||
| BDFE | JR Z,title_attract_cont | ||
| title_attract_end | BE00 | JP title_screen | Fire or any key: back to the top of the front end. |
| ← BCAD | Map | BE03 → |
