[{"data":1,"prerenderedAt":360},["ShallowReactive",2],{"learn:invaders:04-shot":3},{"item":4,"marksAgainst":359},{"slug":5,"number":6,"title":7,"idea":8,"models":9,"body":12,"tryIt":295,"game":341,"cites":342,"program":358},"04-shot",4,"Fire one shot that rises and disappears","a 16-bit variable with DW, 0 for none, and two things moving in one loop",[10,11],3,1,[13,19,23,27,93,97,100,104,107,111,114,117,137,139,165,167,169,191,220,222,230,232,242,244,277,281,284,288,291],{"kind":14,"inlines":15},"p",[16],{"kind":17,"text":18},"text","Press the space bar and the base fires: a shot leaves from just above its middle, climbs a row each turn, and is gone past the top of the screen. There is one shot at a time; while it is in the air the space bar does nothing, and the base goes on moving under it.",{"kind":20,"id":21,"text":22},"h2","what-changed","What changed",{"kind":14,"inlines":24},[25],{"kind":17,"text":26},"Chapter 3's program, with these changes:",{"kind":28,"items":29},"ul",[30,36,50,59,68,82],[31,34],{"kind":32,"text":33},"strong","~",{"kind":17,"text":35}," The comment at the top, lines 1 to 5, now describes chapter 4.",[37,39,41,44,46,48],{"kind":32,"text":38},"+",{"kind":17,"text":40}," ",{"kind":42,"text":43},"code","MOVESHOT",{"kind":17,"text":45},", a new routine with its comment, below ",{"kind":42,"text":47},"MOVEBASE",{"kind":17,"text":49},": the shot rises a row, or is gone.",[51,52,53,55,56,57],{"kind":32,"text":38},{"kind":17,"text":40},{"kind":42,"text":54},"FIRE",{"kind":17,"text":45},{"kind":42,"text":43},{"kind":17,"text":58},": the space bar starts a shot.",[60,61,63,66],{"kind":32,"text":33},{"kind":17,"text":62}," The game loop's comment: its last line now ends ",{"kind":64,"text":65},"em","the base, then the shot",{"kind":17,"text":67},".",[69,70,71,73,75,77,79,81],{"kind":32,"text":38},{"kind":17,"text":40},{"kind":42,"text":72},"CALL MOVESHOT",{"kind":17,"text":74}," and ",{"kind":42,"text":76},"CALL FIRE",{"kind":17,"text":78}," in the loop, after ",{"kind":42,"text":80},"CALL MOVEBASE",{"kind":17,"text":67},[83,84,85,87,89,91],{"kind":32,"text":38},{"kind":17,"text":40},{"kind":42,"text":86},"SHOT",{"kind":17,"text":88},", after ",{"kind":42,"text":90},"BASEX",{"kind":17,"text":92},": the shot's screen address, a word.",{"kind":14,"inlines":94},[95],{"kind":17,"text":96},"Nothing is taken out.",{"kind":20,"id":98,"text":99},"two-things-in-one-loop","Two things in one loop",{"kind":14,"inlines":101},[102],{"kind":17,"text":103},"The base and the shot move at once, but there are no threads and no timers: each turn the loop gives each its go, one after the other.",{"kind":42,"lang":105,"text":106},"z80","LOOP:   CALL    WAIT            ; the turn's pause, so the game runs at a steady speed\n        CALL    MOVEBASE        ; the base moves if an arrow is down\n        CALL    MOVESHOT        ; the shot, if there is one, rises a row\n        CALL    FIRE            ; the space bar starts a shot, if there is none\n        JR      LOOP            ; and round again, for ever",{"kind":14,"inlines":108},[109],{"kind":17,"text":110},"In JavaScript it is the loop every browser game has:",{"kind":42,"lang":112,"text":113},"js","let baseX = 30, shot = 0;      \u002F\u002F BASEX: DB 30, SHOT: DW 0\nfunction turn() {\n  moveBase(); moveShot(); fire();\n  setTimeout(turn, 66);        \u002F\u002F CALL WAIT\n}",{"kind":20,"id":115,"text":116},"where-the-shot-is-a-word","Where the shot is: a word",{"kind":14,"inlines":118},[119,121,123,125,127,129,131,133,135],{"kind":17,"text":120},"The shot's place is a screen address, ",{"kind":42,"text":122},"3C00H",{"kind":17,"text":124}," to ",{"kind":42,"text":126},"3FFFH",{"kind":17,"text":128},", too big for a byte, so it is a word, two bytes, made by ",{"kind":42,"text":130},"DW",{"kind":17,"text":132},". 0 is not on the screen, so 0 means ",{"kind":64,"text":134},"no shot",{"kind":17,"text":136},":",{"kind":42,"lang":105,"text":138},"SHOT:   DW      0               ; the shot's screen address, or 0 for none",{"kind":14,"inlines":140},[141,143,145,147,148,150,152,154,156,158,160,161,163],{"kind":17,"text":142},"A word is stored low byte first. ",{"kind":42,"text":144},"LD HL,(SHOT)",{"kind":17,"text":146}," loads both bytes at once, the one at ",{"kind":42,"text":86},{"kind":17,"text":149}," into L and the one at ",{"kind":42,"text":151},"SHOT+1",{"kind":17,"text":153}," into H, and ",{"kind":42,"text":155},"LD (SHOT),HL",{"kind":17,"text":157}," stores them back the same way. To test HL for 0, ",{"kind":42,"text":159},"LD A,H",{"kind":17,"text":74},{"kind":42,"text":162},"OR L",{"kind":17,"text":164}," make the zero flag, as the clear does with B and C.",{"kind":20,"id":166,"text":54},"fire",{"kind":42,"lang":105,"text":168},"FIRE:   LD      A,(3840H)       ; the space bar is bit 7 of the arrows' row\n        BIT     7,A\n        RET     Z               ; not down: nothing to do\n        LD      HL,(SHOT)\n        LD      A,H\n        OR      L\n        RET     NZ              ; a shot is in the air already: one at a time",{"kind":14,"inlines":170},[171,173,175,177,178,180,182,184,186,188,190],{"kind":17,"text":172},"The space bar is bit 7 of the row at ",{"kind":42,"text":174},"3840H",{"kind":17,"text":176},", beside the arrows: as for the arrows, that order within the row is the one Fugløy's emulator uses, from the ROM's own keyboard routine. ",{"kind":42,"text":54},{"kind":17,"text":179}," has two early returns. ",{"kind":42,"text":181},"RET Z",{"kind":17,"text":183}," returns when the space bar is up. ",{"kind":42,"text":185},"RET NZ",{"kind":17,"text":187}," returns when the zero flag is clear, here when HL is not 0: a shot is already in the air. That second one is the whole of ",{"kind":64,"text":189},"one shot at a time",{"kind":17,"text":67},{"kind":14,"inlines":192},[193,195,197,199,201,203,204,206,207,209,211,213,215,217,219],{"kind":17,"text":194},"Otherwise the shot starts on row 14, just above the base's middle cell. Row 14 starts at ",{"kind":42,"text":196},"3F80H",{"kind":17,"text":198},", and the middle cell is the base's column plus one, so the address is ",{"kind":42,"text":200},"3F81H",{"kind":17,"text":202}," plus ",{"kind":42,"text":90},{"kind":17,"text":205},". ",{"kind":42,"text":90},{"kind":17,"text":208}," is at most 61, so ",{"kind":42,"text":210},"81H",{"kind":17,"text":212}," plus it is at most ",{"kind":42,"text":214},"0BEH",{"kind":17,"text":216},", one byte, and H is always ",{"kind":42,"text":218},"3FH",{"kind":17,"text":136},{"kind":42,"lang":105,"text":221},"        LD      A,(BASEX)       ; row 14 starts at 3F80H; the base's middle\n        ADD     A,81H           ; is its column plus one: 3F81H plus BASEX\n        LD      L,A\n        LD      H,3FH\n        LD      (HL),21H        ; draw the shot\n        LD      (SHOT),HL       ; and remember where it is",{"kind":14,"inlines":223},[224,226,228],{"kind":17,"text":225},"The shot is ",{"kind":42,"text":227},"21H",{"kind":17,"text":229},", the exclamation mark.",{"kind":20,"id":231,"text":43},"moveshot",{"kind":14,"inlines":233},[234,236,237,239,240],{"kind":17,"text":235},"Each turn ",{"kind":42,"text":43},{"kind":17,"text":238}," erases the shot, moves it up a row, and draws it again, as ",{"kind":42,"text":47},{"kind":17,"text":241}," does for the base:",{"kind":42,"lang":105,"text":243},"        LD      (HL),20H        ; erase the shot where it is\n        LD      DE,-64          ; up a row\n        ADD     HL,DE\n        BIT     2,H             ; still on the screen? 3C00H-3FFFH all have bit 2 of H set\n        JR      NZ,SHOTUP\n        LD      HL,0            ; off the top: no shot",{"kind":14,"inlines":245},[246,248,250,252,254,256,258,260,262,264,266,267,268,270,272,274,275],{"kind":17,"text":247},"A row is 64 cells, so the cell above is 64 bytes back. The assembler writes -64 as the word ",{"kind":42,"text":249},"0FFC0H",{"kind":17,"text":251},", and adding it to HL wraps past ",{"kind":42,"text":253},"0FFFFH",{"kind":17,"text":255}," to the same result as taking 64 away. ",{"kind":42,"text":257},"ADD HL,DE",{"kind":17,"text":259}," sets no zero flag, so the program tests where HL is with ",{"kind":42,"text":261},"BIT 2,H",{"kind":17,"text":263},": the screen's high bytes, ",{"kind":42,"text":265},"3CH",{"kind":17,"text":124},{"kind":42,"text":218},{"kind":17,"text":269},", all have bit 2 set, and above row 0 the address is ",{"kind":42,"text":271},"3BxxH",{"kind":17,"text":273},", whose bit 2 is clear. Then the shot is not drawn, and ",{"kind":42,"text":86},{"kind":17,"text":276}," goes back to 0, so the space bar works again.",{"kind":14,"inlines":278},[279],{"kind":17,"text":280},"The shot moves once a turn, about every 66 ms, so it climbs the 15 rows above the base in about a second.",{"kind":20,"id":282,"text":283},"keys-go-to-the-screen-not-the-editor","Keys go to the screen, not the editor",{"kind":14,"inlines":285},[286],{"kind":17,"text":287},"Click the TRS-80's screen before you press the arrows or the space bar, so that it has the keyboard.",{"kind":20,"id":289,"text":290},"on-the-model-i","On the Model I",{"kind":14,"inlines":292},[293],{"kind":17,"text":294},"The Model I has the same keyboard row and the same screen, so the program runs unchanged, a little slower at the Model I's 1.77 MHz.",[296,314],{"kind":14,"inlines":297},[298,300,301,303,305,306,308,310,312],{"kind":17,"text":299},"Make the shot twice as fast: in ",{"kind":42,"text":43},{"kind":17,"text":302},", change ",{"kind":42,"text":304},"LD DE,-64",{"kind":17,"text":124},{"kind":42,"text":307},"LD DE,-128",{"kind":17,"text":309},", and press Run ▶. The shot now skips a row each turn, rows 14, 12, 10 and on to 0, and from there ",{"kind":42,"text":311},"3B80H",{"kind":17,"text":313}," and up is off the screen.",{"kind":14,"inlines":315},[316,318,320,322,323,325,327,329,331,333,335,337,339],{"kind":17,"text":317},"Then put it back, set a breakpoint on the ",{"kind":42,"text":319},"SHOTUP:",{"kind":17,"text":321}," line (click its left edge), press Run ▶, click the screen and press the space bar. ",{"kind":42,"text":54},{"kind":17,"text":324}," puts the shot at ",{"kind":42,"text":326},"3F9FH",{"kind":17,"text":328},", row 14 above the base's middle, and on the next turn the program stops at ",{"kind":42,"text":330},"SHOTUP",{"kind":17,"text":332}," with HL at ",{"kind":42,"text":334},"3F5FH",{"kind":17,"text":336},", a row up. Each Continue is one more turn, and HL goes down by ",{"kind":42,"text":338},"40H",{"kind":17,"text":340},", 64, each time.",null,[343,347,351,355],{"source":344,"page":345,"about":346},"m3-trm","14","the Model III's keyboard is memory at 3800H-3BFFH, and its screen at 3C00H",{"source":348,"page":349,"about":350},"m1-trh","12","the Model I's keyboard is memory from 3800H, and its screen at 3C00H",{"source":352,"page":353,"about":354},"decker","64","the keyboard's rows are read at 3801H to 3840H, one address a row",{"source":356,"page":357,"about":354},"paay","26","; Build Space Invaders, chapter 4: the base fires. Chapter 3's base, and the\n; space bar, bit 7 of the keyboard row at 3840H, fires a shot that rises a\n; row each turn and is gone past row 0. One shot at a time: SHOT holds its\n; screen address, 0 when there is none, and FIRE does nothing while it is\n; in the air. MOVESHOT moves it each turn, as MOVEBASE moves the base.\n\n        ORG     7000H           ; the program lives at 7000H, in free memory\n\n; WAIT: a counted delay, 5,120 passes of 26 T-states, about 66 ms a turn at\n; 2.03 MHz. The routines come first, and the game itself after them, from\n; START, where END START says the program begins.\nWAIT:   PUSH    BC              ; save BC, which the loop uses\n        LD      BC,1400H        ; 1400H is 5,120 passes of 26 T-states\nDELAY:  DEC     BC\n        LD      A,B\n        OR      C\n        JR      NZ,DELAY\n        POP     BC              ; BC as the caller left it\n        RET\n\n; DRAWBASE: copy the three bytes HL points at to the base's place on row 15,\n; 3FC0H plus BASEX. BC is kept on the stack; A, DE and HL are not.\nDRAWBASE:\n        PUSH    BC              ; save BC, which LDIR counts down\n        LD      A,(BASEX)       ; the base's column ...\n        ADD     A,0C0H          ; ... plus 3FC0H, row 15: the base's left cell\n        LD      E,A\n        LD      D,3FH\n        LD      BC,3            ; three bytes\n        LDIR                    ; from (HL) to (DE), one byte at a time\n        POP     BC              ; BC as the caller left it\n        RET\n\n; MOVEBASE: with the left or right arrow down, erase the base, move it a\n; column, and draw it again. Columns 0 and 61 are the edges: there it stays.\nMOVEBASE:\n        LD      A,(3840H)       ; the arrows' row: bit 5 left, 6 right\n        AND     60H             ; keep those two bits (0110 0000)\n        RET     Z               ; neither is down: nothing to do\n        LD      C,A             ; keep the keys in C, so A is free\n        LD      HL,BLANK        ; erase the base where it is\n        CALL    DRAWBASE\n        LD      A,(BASEX)       ; the base's column, from memory\n        BIT     5,C             ; left?\n        JR      Z,NOTLT\n        CP      0               ; column 0, the left edge: stay\n        JR      Z,NOTLT\n        DEC     A\nNOTLT:\n        BIT     6,C             ; right?\n        JR      Z,NOTRT\n        CP      61              ; column 61, the right edge for three cells: stay\n        JR      Z,NOTRT\n        INC     A\nNOTRT:\n        LD      (BASEX),A       ; the new column, back to memory\n        LD      HL,BASE         ; draw the base there\n        CALL    DRAWBASE\n        RET\n\n; MOVESHOT: the shot, if there is one, moves up a row; past row 0 it is gone.\nMOVESHOT:\n        LD      HL,(SHOT)       ; where the shot is: two bytes from memory into HL\n        LD      A,H             ; 0 is no shot: test HL as the clear tests BC\n        OR      L\n        RET     Z               ; no shot: nothing to do\n        LD      (HL),20H        ; erase the shot where it is\n        LD      DE,-64          ; up a row\n        ADD     HL,DE\n        BIT     2,H             ; still on the screen? 3C00H-3FFFH all have bit 2 of H set\n        JR      NZ,SHOTUP\n        LD      HL,0            ; off the top: no shot\nSHOTUP: LD      (SHOT),HL       ; keep where it is, or 0\n        LD      A,H\n        OR      L\n        RET     Z               ; gone: nothing to draw\n        LD      (HL),21H        ; the shot is an exclamation mark\n        RET\n\n; FIRE: with the space bar down and no shot in the air, a shot starts on\n; row 14, just above the base's middle cell.\nFIRE:   LD      A,(3840H)       ; the space bar is bit 7 of the arrows' row\n        BIT     7,A\n        RET     Z               ; not down: nothing to do\n        LD      HL,(SHOT)\n        LD      A,H\n        OR      L\n        RET     NZ              ; a shot is in the air already: one at a time\n        LD      A,(BASEX)       ; row 14 starts at 3F80H; the base's middle\n        ADD     A,81H           ; is its column plus one: 3F81H plus BASEX\n        LD      L,A\n        LD      H,3FH\n        LD      (HL),21H        ; draw the shot\n        LD      (SHOT),HL       ; and remember where it is\n        RET\n\nSTART:  LD      HL,3C00H        ; HL points at the top-left cell\n        LD      BC,400H         ; BC counts the cells: 400H is 1024\nCLEAR:  LD      (HL),20H        ; a space: the cell is blank\n        INC     HL              ; on to the next cell\n        DEC     BC              ; one cell fewer to go (DEC BC sets no flags ...)\n        LD      A,B             ; ... so test BC by hand: copy B into A\n        OR      C               ; and OR in C: zero only when B and C both are\n        JR      NZ,CLEAR        ; not zero yet: clear the next cell\n        LD      HL,BASE         ; draw the base where BASEX says\n        CALL    DRAWBASE\n\n; The game loop: one turn of the game, then round again. A game's turn\n; reads the keys, moves things and draws them: the base, then the shot.\nLOOP:   CALL    WAIT            ; the turn's pause, so the game runs at a steady speed\n        CALL    MOVEBASE        ; the base moves if an arrow is down\n        CALL    MOVESHOT        ; the shot, if there is one, rises a row\n        CALL    FIRE            ; the space bar starts a shot, if there is none\n        JR      LOOP            ; and round again, for ever\n\n; The game's data: below the loop, where the processor never runs into it.\nBASE:   DB      0BCH,0BFH,0BCH  ; the base: a wing, a block, a wing\nBLANK:  DB      '   '           ; three spaces, to erase it\nBASEX:  DB      30              ; the base's column, 0 to 61\nSHOT:   DW      0               ; the shot's screen address, or 0 for none\n\n        END     START           ; the program starts at START\n","; Build Space Invaders, chapter 3: the arrow keys move the base. Chapter 2's\n; base on row 15, and each turn of the game loop now calls MOVEBASE, which\n; reads the left and right arrows, bits 5 and 6 of the keyboard row at\n; 3840H, and with one down erases the base, moves BASEX a column, and draws\n; the base again; at the screen's edges it stays where it is.\n\n        ORG     7000H           ; the program lives at 7000H, in free memory\n\n; WAIT: a counted delay, 5,120 passes of 26 T-states, about 66 ms a turn at\n; 2.03 MHz. The routines come first, and the game itself after them, from\n; START, where END START says the program begins.\nWAIT:   PUSH    BC              ; save BC, which the loop uses\n        LD      BC,1400H        ; 1400H is 5,120 passes of 26 T-states\nDELAY:  DEC     BC\n        LD      A,B\n        OR      C\n        JR      NZ,DELAY\n        POP     BC              ; BC as the caller left it\n        RET\n\n; DRAWBASE: copy the three bytes HL points at to the base's place on row 15,\n; 3FC0H plus BASEX. BC is kept on the stack; A, DE and HL are not.\nDRAWBASE:\n        PUSH    BC              ; save BC, which LDIR counts down\n        LD      A,(BASEX)       ; the base's column ...\n        ADD     A,0C0H          ; ... plus 3FC0H, row 15: the base's left cell\n        LD      E,A\n        LD      D,3FH\n        LD      BC,3            ; three bytes\n        LDIR                    ; from (HL) to (DE), one byte at a time\n        POP     BC              ; BC as the caller left it\n        RET\n\n; MOVEBASE: with the left or right arrow down, erase the base, move it a\n; column, and draw it again. Columns 0 and 61 are the edges: there it stays.\nMOVEBASE:\n        LD      A,(3840H)       ; the arrows' row: bit 5 left, 6 right\n        AND     60H             ; keep those two bits (0110 0000)\n        RET     Z               ; neither is down: nothing to do\n        LD      C,A             ; keep the keys in C, so A is free\n        LD      HL,BLANK        ; erase the base where it is\n        CALL    DRAWBASE\n        LD      A,(BASEX)       ; the base's column, from memory\n        BIT     5,C             ; left?\n        JR      Z,NOTLT\n        CP      0               ; column 0, the left edge: stay\n        JR      Z,NOTLT\n        DEC     A\nNOTLT:\n        BIT     6,C             ; right?\n        JR      Z,NOTRT\n        CP      61              ; column 61, the right edge for three cells: stay\n        JR      Z,NOTRT\n        INC     A\nNOTRT:\n        LD      (BASEX),A       ; the new column, back to memory\n        LD      HL,BASE         ; draw the base there\n        CALL    DRAWBASE\n        RET\n\nSTART:  LD      HL,3C00H        ; HL points at the top-left cell\n        LD      BC,400H         ; BC counts the cells: 400H is 1024\nCLEAR:  LD      (HL),20H        ; a space: the cell is blank\n        INC     HL              ; on to the next cell\n        DEC     BC              ; one cell fewer to go (DEC BC sets no flags ...)\n        LD      A,B             ; ... so test BC by hand: copy B into A\n        OR      C               ; and OR in C: zero only when B and C both are\n        JR      NZ,CLEAR        ; not zero yet: clear the next cell\n        LD      HL,BASE         ; draw the base where BASEX says\n        CALL    DRAWBASE\n\n; The game loop: one turn of the game, then round again. A game's turn\n; reads the keys, moves things and draws them; this one moves the base.\nLOOP:   CALL    WAIT            ; the turn's pause, so the game runs at a steady speed\n        CALL    MOVEBASE        ; the base moves if an arrow is down\n        JR      LOOP            ; and round again, for ever\n\n; The game's data: below the loop, where the processor never runs into it.\nBASE:   DB      0BCH,0BFH,0BCH  ; the base: a wing, a block, a wing\nBLANK:  DB      '   '           ; three spaces, to erase it\nBASEX:  DB      30              ; the base's column, 0 to 61\n\n        END     START           ; the program starts at START\n",1792464471258]