8

I'm referencing this Space Invaders code.

Could someone explain why the PrintMessageDel routine checks for a delay value of one instead of zero?

The interrupt service routine decrements the isrDelay variable. So why not start with a value of 6 and exit on zero instead of starting with 7 and exiting on one? There isn't even a need to do a decrement in PrintMessageDel, it's a wasted instruction, right?

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 

Here's the interrupt routine where isrDelay gets decremented. Note that this specific 8080 decrement command actually decrements the isrDelay memory location.

ScanLine224:
; Interrupt brings us here when the beam is at the end of the screen (line 224) when the VBLANK begins.
0010: F5              PUSH    AF                  ; Save ...
0011: C5              PUSH    BC                  ; ...
0012: D5              PUSH    DE                  ; ...
0013: E5              PUSH    HL                  ; ... everything
0014: 3E 80           LD      A,$80               ; Flag that tells objects ...
0016: 32 72 20        LD      (vblankStatus),A    ; ... on the lower half of the screen to draw/move
0019: 21 C0 20        LD      HL,isrDelay         ; Decrement ...
001C: 35              DEC     (HL)                ; ... the general countdown (used for pauses)
001D: CD CD 17        CALL    CheckHandleTilt     ; Check and handle TILT
0020: DB 01           IN      A,(INP1)            ; Read coin switch
0022: 0F              RRCA                        ; Has a coin been deposited (bit 0)?
0023: DA 67 00        JP      C,$0067             ; Yes ... note that switch is closed and continue at 3F with A=1
0026: 3A EA 20        LD      A,(coinSwitch)      ; Switch is now open. Was it ...
0029: A7              AND     A                   ; ... closed last time?
002A: CA 42 00        JP      Z,$0042             ; No ... skip registering the credit

EDIT: I should have said that the posted code is Z80 assembly, not 8080. I was mistaken.

11
  • 4
    If this is Z80 code, then per my reading of the manual (I never programmed a Z80), LD does not change the condition codes. Commented Jun 13 at 23:54
  • Yeah, I believe 8080 and Z80 are compatible. Okay, so I guess an instruction was necessary...Might as well make it a DEC? Commented Jun 14 at 0:00
  • 2
    Seems strange: a test instruction is needed but AND A for isrDelay=0..6 would have done exactly the same as DEC A for isrDelay=1..7, both update the Zero flag for A. Can only assume it's a programmer's whim, there's no logic for choosing one over the other unless isrDelay is also used elsewhere. Commented Jun 14 at 16:09
  • 4
    @dave, the Z80 AND A instruction ANDs the accumulator A with itself, naturally leaving A unchanged. What it does do though is update the Zero flag and clear the Carry flag, so it's often used just for those functions: testing if A is zero and/or clearing Carry. Same goes for the OR A instruction. (Just to round off the set, the XOR A instruction is more useful as it loads A with zero. Being a 1-byte instruction, it's very often used instead of the 2-byte LD A,0.) Commented Jun 14 at 19:50
  • 1
    Ah, I see. Thanks. Commented Jun 14 at 19:52

1 Answer 1

7

I thought that the code looked a bit funny for Z80, and indeed, Space Invaders were written for 8080. Otherwise, the presented code is standard and does not really do anything unusual. The state of the interrupt counter is stored in the RAM cell isrDelay. The interrupt routine decrements the value in this cell using

    LD HL,isrDelay
    DEC (HL)

which is one of the standard ways of doing so (an obvious alternative of

    LD A,(isrDelay)
    DEC A
    LD (isrDelay),A

is 3 bytes longer and works slower). So, the main point of contention is the following waiting loop

    LD A,7
    LD (isrDelay),A

LOOP:
    LD A,(isrDelay)
    DEC A
    JP NZ,LOOP

You think this loop is unusual because it iterates until isrDelay reaches 1, which you find unintuitive. It probably is to some extent. However,

  • Instruction LD A,(isrDelay) does not modify any flags, so an extra operation on the contents of A would be necessary to know if the value in A is "correct".
  • What are the typical commands used for this purpose? AND A or OR A would both not modify the contents of A and raise flag Z when A is equal to zero. You are quite right that using one of these commands would be a lot more idiomatic.
  • Another pattern that often occurs in 8080/Z80 code is that increment/decrement commands, such as DEC A, preserve the state of flag C, which can be useful to preserve a one bit state through the waiting loop. However, this is definitely not the case on this occasion, as DrawChar subroutine does not depend on state of flag C and modifies it within, which can be easily verified by looking at the source code.
  • However, also note that the use of DEC A does not have any substantial disadvantages except, possibly, being slightly harder for a human reader. Indeed, DEC A occupies the same 1 byte of memory and allows the same range of 255 possible durations of the delay (any value from 2 to 0 results in the delay between 1 and 255 interrupts). @paxdiablo makes a good point in the comments that DEC A executes 1 t-state longer on 8080, but given that this is a loop waiting for the interrupt that only happens twice per frame, the timing difference is surely immaterial. (The execution time of these two commands would be identical if it was Z80.)
  • In fact, the programmer could have replaced JP NZ,LOOP command by JP P,LOOP to obtain the loop that would DEC A, but still stop at 0, as you'd prefer.
  • It all probably comes down to the personal preferences and coding habits. One thing to keep in mind is that Space Invaders was developed by a programmer who never programmed 8080 before and did not even have access to the official development tools. Pretty much any valid code in this situation would be a fair game from my perspective, and maybe we just should not expect the programmer in this situation to rely upon the set of standard solutions to standard problems, as he must have been figuring these things on the go.
2
  • 1
    @introspec Not true that Nishikado hadn't programmed 8080 before. He created Gun Fight 3 years prior, which also used the 8080. Commented Jul 8 at 17:24
  • 1
    @Mike, there are two versions of Gun Fight, see en.wikipedia.org/wiki/Gun_Fight. Nishikado programmed the original Japanese version of the game named "Western Gun". Quoting from the Wikipedia: "The game was developed using transistor–transistor logic (TTL), as game development had not yet moved to microprocessors. The game was among the most complex TTL games developed in the 1970s." You are likely thinking about a 1975 remake of the game by Midway, which did have 8080, but did not involve Nishikado. Commented Jul 8 at 20:09

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.