    Escape From New York dissected by Lasse Öörni
    ---------------------------------------------

    This is my second rant at http://covertbitops.c64.org. Here I take my Crap Game
    Compo 1999 entry and try to explain how it works, from start to finish.

    Because the game had graphical bugs deliberately included for more crappiness,
    I wanted to fix them first before beginning the dissection. Naturally, I don't
    want to give examples of buggy coding :)

    You can get the fixed versions at

    http://covertbitops.c64.org/rants/efix.zip (binary)

    http://covertbitops.c64.org/rants/efixsrc.zip (source)

    First something general about this game. EFNY is a simple platform game/
    shoot'em-up, that features scrolling in one direction (from right to left),
    maximum 8 sprites (no multiplexing), bullets done with character graphics and
    4 different kind of enemies. All sprite position calculations are done in the
    "screen" coordinates, not in "world" coordinates like in Metal Warrior games.

    EFNY uses 2 raster interrupts: one at the top of the screen to initialize the
    game screen/title picture display, and one at the bottom to initialize the
    score panel display and to play music.

    Let the dissection of EFNY.S begin! Warning: this is going to be a long rant!

    Definitions
    -----------

    The source code begins with memory location defines. Sprites, background
    character set, music data, background map data (how blocks are arranged),
    background block data and the title screen bitmap picture.

    SPRITES         = $2000
    CHARS           = $3800
    MUSIC           = $4000
    MAP             = $4800
    BLOCKS          = $5000
    PICTURE         = $6000

    These are the raster line Y-positions on which raster interrupts happen.
    "Raster0" is the bottom interrupt that displays the score panel and plays music.
    It also increments a counter called "rastercount" which is used to stabilize
    execution speed of the main program. "Raster1" displays the game screen or
    title screen bitmap picture.

    RASTER0POS      = 242
    RASTER1POS      = 20

    Here are the "actor type" defines. "Actors" are the heart of almost all my
    games, basically they are all the objects that move onscreen (player, enemies,
    explosions). Sometimes I also define the bullets as actors (Metal Warrior 1 & 3)
    but in this case it isn't done. For each actor there is a move routine that
    will be called each time the game situation is updated. Actors can transform
    from one type to another, for example the motorist turns into an explosion
    when he is destroyed. This is what makes the system very flexible.

    ACT_NONE        = 0
    ACT_PLISSKEN    = 1
    ACT_MAN1        = 2
    ACT_MAN2        = 3
    ACT_MAN3        = 4
    ACT_MOTORIST    = 5
    ACT_EXPLOSION   = 6

    Here are the screen mode defines for "raster1" interrupt. There are 3 modes;
    game graphics, title bitmap, and text display. More of these later.

    DISPGAME        = 0
    DISPTITLE       = 1
    DISPTEXT        = 2

    Joystick bit value defines for each direction and the fire button. These
    are read from the $dc00 register (joystick port 2)

    JOY_UP          = 1
    JOY_DOWN        = 2
    JOY_LEFT        = 4
    JOY_RIGHT       = 8
    JOY_FIRE        = 16

    Next come all zero-page variable definitions. The first is the display mode
    for "raster1" interrupt.

    dispmode        = $02

    The next is the X-direction hardware-scrolling (0-7) for the game screen.

    scrollx         = $03

    This is the joystick control data that has been read from $dc00 (With bits
    inverted so that a 1 bit means direction/fire button active).

    joystick        = $04

    Previous joystick control value. This is used to check that player has released
    fire button or up direction (jumping) before a new shot can be fired or new
    jump be done.

    prevjoy         = $05

    A 16-bit memory location that points at the current level's background map data.

    mapadrlo        = $06
    mapadrhi        = $07

    The position in background map data (measured from its left edge in blocks)
    that is used to draw new background graphics to the right edge of screen when
    the screen scrolls. Each level is 100 blocks wide (10 whole screens) so this
    gets values from 0 to 100.

    mapx            = $08

    Position in the background data, within a block. Each block is 4 chars wide so
    this gets the values 0-3.

    blockx          = $09

    Various temp variables, used when needed.

    temp1           = $0a
    temp2           = $0b
    temp3           = $0c
    temp4           = $0d
    temp5           = $0e

    The raster interrupt counter incremented by "raster0", used in stabilizing the
    game execution speed.

    rastercount     = $0f

    DASM assembler requires the processor to be defined, so here that is taken
    care. Program code starts at memory location $0800 (2048)

                    processor 6502
                    org $0800

    The main program
    ----------------

    First things to do are to clear the processor's decimal flag (just to be sure),
    init raster interrupts ("initraster") and initialize screen colors
    ("initscreen"). The subroutines are described in more detail when they are
    actually encountered in the source code.

    efnystart:      cld
                    jsr initraster
                    jsr initscreen

    Here we init music. EFNY's music has been done with the SadoTracker that uses
    the "standard" convention for music init/play JSR addresses: MUSIC+0 is the
    init routine (subtune number passed in accumulator) and MUSIC+3 is the play
    routine to be called each frame. Basically, EFNY uses only one subtune and
    subtune numbering starts from 0.

                    lda #$00
                    jsr MUSIC

    Title screen code starts here. We come back here whenever a game ends. The
    thing we check here is whether a new hiscore has been made. Both the score
    and hiscore are 3 bytes of binary coded decimals, with 2 digits in each
    byte. The compare is started from the most significant byte and if no
    conclusion is made, then the next most significant byte is checked.

    title:          lda score+2
                    cmp hiscore+2
                    bcc title_nohiscore
                    beq title_check2
                    bcs title_hiscore
    title_check2:   lda score+1
                    cmp hiscore+1
                    bcc title_nohiscore
                    beq title_check3
                    bcs title_hiscore
    title_check3:   lda score
                    cmp hiscore
                    bcc title_nohiscore

    If a new hiscore was made, copy the "score" contents (3 bytes) to "hiscore".

    title_hiscore:  lda score
                    sta hiscore
                    lda score+1
                    sta hiscore+1
                    lda score+2
                    sta hiscore+2

    Display the title picture ("showpic") and update the numbers in the score panel
    ("drawscores")

    title_nohiscore:jsr showpic
                    jsr drawscores

    Title screen waiting loop. Wait for the "rastercount" to increase ("waitras"),
    get joystick control value ("getjoystick") and check the fire button bit. Loop
    until fire button has been pressed.

    titleloop:      jsr waitras
                    jsr getjoystick
                    lda joystick
                    and #JOY_FIRE
                    beq titleloop

    Game starts. First thing to do is to clear the score (3 bytes), init number of
    lives and the level number.

    gamestart:      lda #$00
                    sta score
                    sta score+1
                    sta score+2
                    lda #3
                    sta lives
                    lda #1
                    sta level

    Code to move onto the next level. All sprites are turned off ($d015), score
    panel is updated and scrolling of the level background graphics is initialized
    ("initscroll")

    initlevel:      lda #$00
                    sta $d015
                    jsr drawscores
                    jsr initscroll

    At the end of a level a "kill" meter activates. Here it is deactivated at first
    by storing 0 to the variable "killactive", as well as clearing the meter itself
    (consisting of the meter length in chars "killmeter" and the "fine" component
    of the meter "killmeterd")

                    lda #0
                    sta killactive
                    sta killmeter
                    sta killmeterd

    Next we scroll the first screen "in" from the right edge, as seen in the
    beginning of each level. The scrolling speed (4 pixels) is given to "doscroll"
    routine in the accumulator. We loop until the map X-position (in blocks) has
    reached 10, which means that the entire screen has been scrolled in.

    initlevel2:     jsr waitras
                    lda #4
                    jsr doscroll
                    lda mapx
                    cmp #10
                    bcs initlevel3
                    jmp initlevel2

    The next thing to do is to set all the 8 "actors" to inactive state, which is
    done by setting the type of each actor to the inactive (zero) value. The
    actor type is stored in an indexed array called "actt", with the index going
    from 0 to 7. All other actor properties are stored in similarly indexed arrays
    as well. Note: do not confuse actor types and indexes! The player is actor
    index 0 and enemies are indexes 1-7, but they can be of any type.

    initlevel3:     ldx #7
    initlevel4:     lda #ACT_NONE
                    sta actt,x
                    dex
                    bpl initlevel4

    There's a similar array for the bullets, with "bullett" (bullet type)
    indicating if a bullet is active or not. Bullets are character graphics, so
    there's no limit for their amount, but 16 seems like a sensible value.

                    ldx #15
    initlevel5:     lda #$00
                    sta bullett,x
                    dex
                    bpl initlevel5

    Program execution goes back here also whenever a life has been lost. Here
    the time counter ("time", binary coded decimal number) is reset to the
    maximum 99. Also the time decrement delay counter ("timedl") is reset.

    initlife:       lda #$99
                    sta time
                    lda #$00
                    sta timedl

    The first actor index (index 0) is always the player. So player's position
    is reset here. An actor has its X-position stored as a 16-bit value ("actxl"
    and "actxh" and the Y-position ("acty") as 8 bits. The origin of this
    coordinate system is the first visible pixel in the top left corner of the
    screen. Note: The actor position corresponds to the Y-position of the actor's
    feet and the center in X-direction.

                    lda #128
                    sta actxl
                    lda #0
                    sta actxh
                    lda #20*8
                    sta acty

    Here a number of other actor properties are reset. "Actd" is the actor
    direction, 0 is facing right, 1 is facing left. "Actf" is the frame number
    (animation). "Actj" indicates whether an actor is jumping (1 = is jumping)
    "Actsx" and "actsy" are the X and Y speeds of an actor. "Actyd" is a delay
    counter for slowly changing the Y-speed of an actor, for acceleration due
    to gravity.

                    lda #0
                    sta actd
                    sta actf
                    sta actj
                    sta actsx
                    sta actsy
                    sta actyd

    This is the player's firing delay counter.

                    sta firedelay

    Initialize player actor type and give an immortality time of 200 game frames-
    ("actimm") Player has only 1 hitpoint ("acthp") so any hit kills the player.
    (enemies have more)

                    lda #ACT_PLISSKEN
                    sta actt
                    lda #200
                    sta actimm
                    lda #1
                    sta acthp

    Here begins the game main loop. First we get the joystick controls.

    gameloop:       jsr getjoystick

    Handle movement of all actors ("moveactors")

                    jsr moveactors

    Handle player shooting ("plrshoot")

                    jsr plrshoot

    Generate new enemies at the edges of the screen ("spawnenemies")

                    jsr spawnenemies

    Wait for the raster interrupt

                    jsr waitras

    Erase bullet characters from the screen ("erasebullets")

                    jsr erasebullets

    Move bullets ("movebullets")

                    jsr movebullets

    Update score panel.

                    jsr drawscores

    Activate and display the kill meter at the end of levels ("killmeterr")

                    jsr killmeterr

    Wait for the raster interrupt again, to get as much rastertime as possible
    for the screen scrolling (takes quite a lot of time when the screen memory
    has to be shifted to the left)

                    jsr waitras

    Check for need of scrolling (player moved far enough to the right?)
    "Checkscroll" returns the scrolling speed in accumulator, or 0 if no scrolling
    required.

                    jsr checkscroll

    Here the screen is scrolled, with speed indicated by accumulator.

                    jsr doscroll

    Draw all actors ("drawactors")

                    jsr drawactors

    Draw all bullet characters ("drawbullets")

                    jsr drawbullets

    Decrement time counter ("dectime")

                    jsr dectime

    Check player actor death ("checkdeath"). If player actor has died, this routine
    does not return but pulls the return address from stack and jumps to the
    location "initlife", if lives remain, or to "title" when game is over.

                    jsr checkdeath

    Checks completion of level ("checklevelend"). This routine also does not return
    if a level is completed.

                    jsr checklevelend

    Go back to the beginning of the gameloop.

                    jmp gameloop

    The rest of the code consists of the subroutines.

    "Checklevelend" subroutine
    --------------------------

    If the kill meter is active (nonzero value), compare the meter value to the
    limit required by each level.

    checklevelend:  lda killactive
                    beq cle2
                    lda killmeter
                    cmp killlimit
                    bcc cle2
                    pla
                    pla

    Counting time bonus at the end of the level. By using binary coded decimal
    arithmetic, increase score by 100 points until "time" is zero.

    countbonus:     lda time
                    beq countbonus2
                    sed
                    lda time
                    sec
                    sbc #$01
                    sta time
                    lda score+1
                    clc
                    adc #$01
                    sta score+1
                    bcc noextra

    Give an extra life each ten thousand points (when the most significant byte of
    the score increments).

                    inc lives
    noextra:        lda score+2
                    adc #$00
                    sta score+2
                    cld

    Update the score panel and perform some delaying.

                    jsr drawscores
                    jsr waitras
                    jsr waitras
                    jsr waitras
                    jsr waitras
                    jmp countbonus

    Move onto the next level, or if we were on level 3, do the endsequence.

    countbonus2:    lda level
                    cmp #$03
                    beq complete
                    inc level
                    jmp initlevel

    Level not completed: go back to main loop

    cle2:           rts

    Endsequence code. Initialize scrolling again, set the "text mode" to be
    displayed and clear sprites at first.

    complete:       jsr initscroll
                    lda #DISPTEXT
                    sta dispmode
                    ldx #0
                    stx $d015

    Loop to display the ending text (4 rows) on the screen.

    complete1:      lda ctext1,x
                    and #$3f
                    sta $400+4*40+8,x
                    lda ctext2,x
                    and #$3f
                    sta $400+6*40+8,x
                    lda ctext3,x
                    and #$3f
                    sta $400+8*40+8,x
                    lda ctext4,x
                    and #$3f
                    sta $400+10*40+8,x
                    lda #$01
                    sta $d800+4*40+8,x
                    sta $d800+6*40+8,x
                    sta $d800+8*40+8,x
                    sta $d800+10*40+8,x
                    inx
                    cpx #24
                    bne complete1

    Use temp1 as a delay counter to wait for a "long" time and then return to
    title screen.

                    lda #255
                    sta temp1
    complete2:      jsr waitras
                    jsr waitras
                    dec temp1
                    bne complete2
                    jmp title

    ctext1:         dc.b "     WELL DONE SNAKE    "
    ctext2:         dc.b "YOUR MISSION IS COMPLETE"
    ctext3:         dc.b "AND YOU HAVE EARNED YOUR"
    ctext4:         dc.b "        FREEDOM.        "

    "Killmeterr" subroutine
    -----------------------

    First check if kill meter is yet inactive and we have arrived at the end of the
    level (X-position in blocks 100), in which case it must be activated.

    killmeterr:     lda killactive
                    bne killmeter2
                    lda mapx
                    cmp #100
                    bcc killmeter2
                    inc killactive
                    sta killactive

    Draw the word "KILL" on the screen :)

                    lda #96
                    sta $400+42
                    lda #97
                    sta $400+43
                    lda #98
                    sta $400+44
                    lda #99
                    sta $400+45

    Get the kill meter limit corresponding to the current level.

                    ldx level
                    dex
                    lda killlimittbl,x
                    sta killlimit
                    tax

    Draw the empty kill meter bar on screen.

    drawkillloop:   lda #100
                    sta $400+45,x
                    dex
                    bne drawkillloop
    killmeter2:     rts

    killlimittbl:   dc.b 8,12,24

    "Dectime" subroutine
    --------------------

    Decrement the time counter by using decimal mode arithmetic each time the
    "timedl" variable has counted 50 game frames.

    dectime:        lda time
                    beq nodectime
                    inc timedl
                    lda timedl
                    cmp #50
                    bcc nodectime
                    lda #$00
                    sta timedl
                    sed
                    lda time
                    sec
                    sbc #$01
                    sta time
                    cld
                    bne nodectime

    If time has run out, kill the player actor by setting its hitpoints to zero.

                    lda #0
                    sta acthp
    nodectime:      rts

    "Checkdeath" subroutine
    -----------------------

    Frame number 7 of the player actor is displayed when he's dead, so first check
    for that.

    checkdeath:     lda actf
                    cmp #7
                    bne cdeath_not

    Then check if the dead player actor has reached the bottom of the screen.

                    lda acty
                    cmp #240
                    bcc cdeath_not

    If so, pull the return address from the stack and do not return with RTS;
    instead decrement lives and jump back to the "initlife" code if lives still
    remain, or to "title" when player is out of lives.

                    pla
                    pla
                    dec lives
                    lda lives
                    beq cdeath_gameover
                    jmp initlife
    cdeath_gameover:jmp title
    cdeath_not:     rts

    "Plrshoot" subroutine
    ---------------------

    Handle player's shooting attack. First check if the "firedelay" counter is
    zero, which means that firing the next bullet is allowed. If it's nonzero,
    decrement it and return.

    plrshoot:       lda firedelay
                    beq plrshootok
                    dec firedelay
                    rts

    Check for the death frame of player actor. Don't allow firing when dead.

    plrshootok:     lda actf
                    cmp #7
                    bne plrshootok2
    plrshootnot:    rts

    Check that fire button is pressed, and the fire button has been released
    previously (a semi-automatic weapon :)).

    plrshootok2:    lda joystick
                    and #JOY_FIRE
                    beq plrshootnot
                    lda prevjoy
                    and #JOY_FIRE
                    bne plrshootnot

    Then start searching for an unused bullet index (zero value in the "bullett"
    array). Bullet indexes 0-7 are reserved for player bullets. If no unused
    bullet is found, return.

                    ldx #7
    plrshootfind:   lda bullett,x
                    beq plrshootfound
                    dex
                    bpl plrshootfind
                    rts

    Copy player location & direction to bullet location & direction ("bulletxl",
    "bulletxh", "bullety" and "bulletd"). Y-coord is modified to make the bullets
    appear at the height of the player's weapon (there's a table for this,
    "actshootymod" (Y-modification) of which we use the first value, which
    corresponds to the player actor type, ACT_PLISSKEN)

    plrshootfound:  lda actxl
                    sta bulletxl,x
                    lda actxh
                    sta bulletxh,x
                    lda acty
                    sec
                    sbc actshootymod
                    sta bullety,x
                    lda actd
                    sta bulletd,x

    Set the bullet to active state and set the firing delay.

                    lda #1
                    sta bullett,x
                    lda #2
                    sta firedelay
                    rts

    "Plr": Player actor move routine
    --------------------------------

    Called by "moveactors" routine, X register contains now the current actor
    index.

    Assume that the player is in standing position; modify the values in the
    actortype's Y-size ("actsizey") and shooting Y-modification tables.

    plr:            lda #42
                    sta actsizey
                    lda #20
                    sta actshootymod

    Check for hitpoints running out.

                    lda acthp,x
                    bne plr_nodeath

    If player actor is already in the "death" frame, do not re-init the death
    sequence.

                    lda actf,x
                    cmp #7
                    beq plr_nodinit

    Set the "death" frame and give upwards Y-speed. Reset Y-speed increment
    (gravity) delay counter.

                    lda #7
                    sta actf,x
                    lda #-5
                    sta actsy,x
                    lda #0
                    sta actyd,x

    Gravity handling for the dead actor. After the Y-speed delay counter has
    increased to 3, increase the Y-speed.

    plr_nodinit:    inc actyd,x
                    lda actyd,x
                    cmp #3
                    bcc plr_d2
                    lda #$00
                    sta actyd,x
                    inc actsy,x

    After performing the acceleration, move the player actor in Y-direction.
    ("moveactory") The actor index parameter that is required is already in the
    X-register.

    plr_d2:         jsr moveactory
                    rts

    Player actor death code ends here, now we check if the actor is jumping.
    ("actj" has nonzero value when jumping)

    plr_nodeath:    lda actj,x
                    beq plr_nofly

    Set the frame 5 (jumping frame). Do a similar gravity acceleration &
    actor Y-movement like above, but with a larger delay value.

    plr_fly:        lda #5
                    sta actf,x
                    inc actyd,x
                    lda actyd,x
                    cmp #5
                    bcc plr_fly2
                    lda #$00
                    sta actyd,x
                    inc actsy,x

    Before the Y-movement, perform X-movement by a subroutine ("plisskenmovex").

    plr_fly2:       jsr plisskenmovex
                    jsr moveactory

    Now check the Y-speed. If Y-speed is positive & greater than zero, it is
    possible for the player to land on a platform (ends the jump).

                    lda actsy,x
                    beq plr_noground
                    bmi plr_noground

    Check for ground below feet ("checkground"). This subroutine returns carry 0
    if there is ground.

                    jsr checkground
                    bcs plr_noground

    Player landed on ground. Reset Y-speed, jumping indicator and align Y-position
    on a character boundary with the and operation.

                    lda #0
                    sta actsy,x
                    sta actj,x
                    lda acty,x
                    and #$f8
                    sta acty,x
    plr_noground:   rts

    "Player not jumping"-code. If player has moved into a location where there's no
    ground under feet, initiate falling (jumping without initial upwards Y-speed)

    plr_nofly:      jsr checkground
                    bcc plr_nofall

    Set jumping indicator nonzero.

                    lda #1
                    sta actj,x

    Initial Y-speed is 0, it will start to increase (downwards speed)

                    lda #0
                    sta actsy,x

    Reset Y-acceleration delay counter.

                    sta actyd,x
                    jmp plr_fly

    If player not falling, check for various joystick movements. First comes the
    check of "joystick up" to initiate a new jump. Up must not have been previously
    pressed.

    plr_nofall:     lda joystick
                    and #JOY_UP
                    beq plr_nojump
                    lda prevjoy
                    and #JOY_UP
                    bne plr_nojump
                    lda #1
                    sta actj,x
                    lda #0
                    sta actyd,x

    Give the initial upwards (negative) Y-speed.

                    lda #-4
                    sta actsy,x
                    jmp plr_fly
    plr_nojump:     lda #0
                    sta actsx,x

    Check for moving left. If moving left, set player facing left ("actd" 1),
    give X-speed of -2 pixels (left) and jump to the walk animation code.

                    lda joystick
                    and #JOY_LEFT
                    beq plr_notleft
                    lda #1
                    sta actd,x
                    lda #-2
                    sta actsx,x
                    jmp plr_walkanim

    Similar code for moving right.

    plr_notleft:    lda joystick
                    and #JOY_RIGHT
                    beq plr_notright
                    lda #0
                    sta actd,x
                    lda #2
                    sta actsx,x
                    jmp plr_walkanim

    If not either left or right, set standing frame (frame 0).

    plr_notright:   lda #0
                    sta actf,x
                    jmp plr_domove

    Walk animation. Increase animation frame delay counter, and when it has
    counted to five, increase frame. And-operation is used to limit the
    frame between 0-3, and 1 is then added (so the final frame range for
    walking animation is 1-4).

    plr_walkanim:   inc actfd,x
                    lda actfd,x
                    cmp #5
                    bcc plr_walkanim2
                    lda #$00
                    sta actfd,x
                    lda actf,x
                    and #$03
                    clc
                    adc #$01
                    sta actf,x
    plr_walkanim2:

    Finally check for down direction (crouching).

    plr_domove:     lda joystick
                    and #JOY_DOWN
                    beq plr_noduck

    Set animation frame to 6 (crouching) and make the player's Y-size now smaller
    (for correct collision detection) and modify the shooting Y-modification as
    well, as the weapon is held lower now.

                    lda #6
                    sta actf,x
                    lda #18
                    sta actsizey
                    lda #10
                    sta actshootymod
                    rts
    plr_noduck:     jsr plisskenmovex
                    rts

    This is a subroutine for player actor's movement in X-direction. It allows
    movement left (by calling the "moveactorx" subroutine) only if the actor's
    X-coordinate is greater than 10, and movement right only if the X-coordinate
    is less than 310, so the player actor isn't allowed to move outside the visible
    screen.

    plisskenmovex:  lda actsx,x
                    bmi plr_moveleft
                    lda actxh,x
                    beq plr_moveok
                    lda actxl,x
                    cmp #(310-256)
                    bcc plr_moveok
    plr_nomove:     rts
    plr_moveok:     jsr moveactorx
                    rts
    plr_moveleft:   lda actxh,x
                    bne plr_moveok
                    lda actxl,x
                    cmp #11
                    bcs plr_moveok
                    rts

    "Man": Enemy man actor move routine
    -----------------------------------

    This code is very similar to the "plr" move routine, so it isn't explained in
    as much detail. Like before, X register contains the current actor index.

    Check for enemy becoming dead.

    man:            lda acthp,x
                    bne man_notdead

    For these kind of enemies, 3 is the death animation frame. If frame wasn't
    already 3, init death animation & movement (similar upward Y-speed like
    with the player) and increase player score.

                    lda actf,x
                    cmp #3
                    beq man_nodinit
                    jsr addenemyscore
                    lda #3
                    sta actf,x
                    lda #-5
                    sta actsy,x
                    lda #0
                    sta actyd,x
    man_nodinit:    inc actyd,x
                    lda actyd,x
                    cmp #2
                    bcc man_d2
                    lda #$00
                    sta actyd,x
                    inc actsy,x
    man_d2:         jsr moveactory
                    lda acty,x
                    cmp #240
                    bcc man_noremove

    The enemy is removed (by setting the actor type to zero) when it moves off the
    bottom of screen after death.

                    lda #0
                    sta actt,x
    man_noremove:   rts

    man_notdead:    lda actj,x
                    beq man_nofly

    Enemy jumping code. 2 is the jumping animation frame for these enemies.

    man_fly:        lda #2
                    sta actf,x
                    inc actyd,x
                    lda actyd,x
                    cmp #5
                    bcc man_fly2
                    lda #$00
                    sta actyd,x
                    inc actsy,x
    man_fly2:       jsr moveactorx
                    jsr moveactory
                    lda actsy,x
                    beq man_noground
                    bmi man_noground
                    jsr checkground
                    bcs man_noground
                    lda #0
                    sta actsy,x
                    sta actj,x
                    lda acty,x
                    and #$f8
                    sta acty,x
    man_noground:   jmp man_shoot
                    rts

    Enemy walking code. If an enemy has moved to a location where it doesn't have
    ground under its feet, it jumps (this is a bit of cheating, since the player
    actor would fall in a similar situation)

    man_nofly:      jsr checkground
                    bcc man_nojump
                    lda #1
                    sta actj,x
                    lda #-4
                    sta actsy,x
                    sta actyd,x
                    jmp man_fly
    man_nojump:     lda actd,x
                    beq man_right

    The enemy's move speed depends on its type (there are 3 kinds of these
    enemies). Actually the enemy type's maximum hitpoints are used as the move
    speed.

                    ldy actt,x
                    dey
                    lda actmaxhp,y

    Here the speed needs to be negated by two's complement for moving left.

                    eor #$ff
                    clc
                    adc #$01
                    sta actsx,x
                    jmp man_walkanim
    man_right:      ldy actt,x
                    dey
                    lda actmaxhp,y
                    sta actsx,x
    man_walkanim:   inc actfd,x
                    lda actfd,x
                    cmp #6
                    bcc man_walkanim2
                    lda #0
                    sta actfd,x

    The enemy actor uses only frames 0 & 1 for walking animation, so it looks
    quite primitive :)

                    lda actf,x
                    eor #$01
                    and #$01
                    sta actf,x
    man_walkanim2:  jsr moveactorx

    Enemy shooting routine. Call "random" subroutine to get a pseudorandom number
    for shooting decision.

    man_shoot:      jsr random
                    sta temp1

    Compare the level number against the random value. If it's smaller then do not
    shoot. This has the effect that enemies start to shoot more frequently in later
    levels.

                    lda level
                    cmp temp1
                    bcc man_noshoot

    Now search for a free enemy bullet (nonzero value in "bullett" array), using
    the Y register as an index. Bullet indexes 8-15 are reserved for enemies.

                    ldy #8
    manshootfind:   lda bullett,y
                    beq manshootfound
                    iny
                    cpy #$10
                    bcc manshootfind

    No bullet found, just exit

    man_noshoot:    rts

    Copy the actor location & direction to bullet location & direction, and perform
    Y-position modification, like in the player actor's shoot routine.

    manshootfound:  lda actxl,x
                    sta bulletxl,y
                    lda actxh,x
                    sta bulletxh,y
                    lda acty,x
                    sec
                    sbc #20
                    sta bullety,y
                    lda actd,x
                    sta bulletd,y
                    lda #1
                    sta bullett,y
                    rts

    "Mc": Enemy motorist actor move routine
    ---------------------------------------

    When the motorist is killed, he turns into an explosion.

    mc:             lda acthp,x
                    bne mc_notdead
                    jsr addenemyscore

    Initialize animation frame for the explosion, and change actor type.

                    lda #0
                    sta actf,x
                    lda #ACT_EXPLOSION
                    sta actt,x
                    rts

    Rest of the code is very similar to the player & enemy move routines seen
    before.

    mc_notdead:     lda actj,x
                    beq mc_nofly
    mc_fly:         lda #0
                    sta actf,x
                    inc actyd,x
                    lda actyd,x
                    cmp #5
                    bcc mc_fly2
                    lda #$00
                    sta actyd,x
                    inc actsy,x
    mc_fly2:        jsr moveactorx
                    jsr moveactory
                    lda actsy,x
                    beq mc_noground
                    bmi mc_noground
                    jsr checkground
                    bcs mc_noground
                    lda #0
                    sta actsy,x
                    sta actj,x
                    lda acty,x
                    and #$f8
                    sta acty,x
    mc_noground:    rts
    mc_nofly:       jsr checkground
                    bcc mc_nojump
                    lda #1
                    sta actj,x
                    lda #-3
                    sta actsy,x
                    sta actyd,x
                    jmp mc_fly
    mc_nojump:      lda actd,x
                    beq mc_right
                    lda #-4
                    sta actsx,x
                    jmp mc_walkanim
    mc_right:       lda #4
                    sta actsx,x
    mc_walkanim:    lda actf,x
                    eor #$01
                    and #$01
                    sta actf,x
                    jsr moveactorx

    Here is the difference: The motorist can kill the player actor by colliding.
    Check collision between the enemy actor (index in X register) and player
    (Y register loaded with 0, player actor's index) with the "actactcoll"
    (actor-actor collision) subroutine.

                    ldy #0
                    jsr actactcoll
                    bcc mc_nocoll

    Carry 1 indicates collision. Decrease player actor hitpoints (player actor has
    only one so he's killed)

                    lda acthp,y
                    sec
                    sbc #$01
                    sta acthp,y
    mc_nocoll:      rts

    "Expl": Explosion actor move routine
    ------------------------------------

    This is a simple "move" routine because it only involves animation. Count
    the frame delay up to 6 and after that increase the animation frame. The
    explosion has 4 animation frames, after all these have been shown the
    explosion is removed by putting zero value to actor type ("actt").

    expl:           inc actfd,x
                    lda actfd,x
                    cmp #6
                    bcc expl2
                    lda #0
                    sta actfd,x
                    inc actf,x
                    lda actf,x
                    cmp #4
                    bcc expl2
                    lda #0
                    sta actt,x
    expl2:          rts

    "Addenemyscore" subroutine
    --------------------------

    Add score according to the actor type of the dead enemy (use a value from a
    table), using decimal arithmetic.

    addenemyscore:  ldy actt,x
                    dey
                    sed
                    lda score
                    clc
                    adc actscorelo,y
                    sta score
                    lda score+1
                    adc actscorehi,y
                    sta score+1
                    bcc noextra2

    When tens of thousands increase, give an extra life.

                    inc lives

    noextra2:       lda score+2
                    adc #$00
                    sta score+2
                    cld

    If the kill meter is active, lenghten the kill meter bar on screen. "Killmeterd"
    goes through values 0-4 to specify what char is drawn at the end of the meter,
    to make it increase smoothly. "Killmeter" is the meter length in whole chars
    (indicates the position of the meter's endpoint on screen). The chars used for
    the meter are 100-104.

                    lda killactive
                    beq addenemyscore2
                    inc killmeterd
                    ldy killmeter
                    lda #100
                    clc
                    adc killmeterd
                    sta $400+46,y
                    lda killmeterd
                    cmp #4
                    bcc addenemyscore2
                    lda #$00
                    sta killmeterd
                    inc killmeter
    addenemyscore2: rts

    "Moveactorx" subroutine
    -----------------------

    Moves an actor (index indicated by the X-register) horizontally, according
    to its speed ("actsx" array). "Actsx" is only 8 bits so to perform the 16-bit
    position addition correctly we must check its sign before adding.

    moveactorx:     clc
                    lda actsx,x
                    bmi negmovex
                    adc actxl,x
                    sta actxl,x
                    lda actxh,x
                    adc #$00
                    sta actxh,x
                    rts
    negmovex:       adc actxl,x
                    sta actxl,x
                    lda actxh,x
                    adc #$ff
                    sta actxh,x
                    rts

    "Moveactory" subroutine
    -----------------------

    Moves an actor (index indicated by the X-register) vertically, according to its
    speed ("actsy" array). Y-position is only 8 bits like the speed so this
    movement is easy to do.

    moveactory:     lda actsy,x
                    clc
                    adc acty,x
                    sta acty,x
                    rts

    "Checkground" subroutine
    ------------------------

    Checks for ground under the actor's (index indicated by X-register) feet. First
    divide the actor's Y-position by 8 to get the screen row where we must check.

    checkground:    lda acty,x
                    lsr
                    lsr
                    lsr

    If it's over the gamescreen portion of the screen, limit the row number.

                    cmp #24
                    bcc cg_notover1
                    lda #23

    Fetch the screen row's memory address from a table.

    cg_notover1:    tay
                    lda rowtbllo,y
                    sta temp1
                    lda rowtblhi,y
                    sta temp2

    Now divide the X-position by 8 to get the char column we need.

                    lda actxh,x
                    sta temp3
                    lda actxl,x
                    lsr temp3
                    ror
                    lsr temp3
                    ror
                    lsr temp3
                    ror

    Move the column to Y-register, limit it to the visible screen boundaries (0-39)
    if it's less or greater.

                    tay
                    bpl cg_notoverleft
                    ldy #0
                    jmp cg_notoverright
    cg_notoverleft: cpy #39
                    bcc cg_notoverright
                    ldy #39

    Then read the char from that location at screen memory. Characters 0-31 can be
    walked on, so carry will be 0 in that case, otherwise 1.

    cg_notoverright:lda (temp1),y
                    cmp #32
                    rts

    This is the table of memory addresses of the 24 gamescreen rows.

    rowtbllo:
    N               SET 0
                    REPEAT 24
                    dc.b #<($400+N*40)
    N               SET N+1
                    REPEND
    rowtblhi:
    N               SET 0
                    REPEAT 24
                    dc.b #>($400+N*40)
    N               SET N+1
                    REPEND

    "Random" subroutine
    -------------------

    Pseudorandom generator. Reads a byte from a certain memory range ($0b00-$bff,
    the program code), then adds the previous random number and $d012 (raster line
    Y-position) to it, returning the new random number in the accumulator. Code is
    selfmodified to get the next byte to read on the next execution of this
    subroutine.

    random:         lda $b00
                    adc randseed
                    adc $d012
                    sta randseed
                    inc random+1
                    rts
    randseed:       dc.b $73

    "Bullactcoll" subroutine
    ------------------------

    Bullet-actor collision. Parameters and return value (carry flag) are shown
    below.
            ;X = bullet number (bullet must exist!)
            ;Y = actor number
            ;C=1 collision happened

    The collision routines are all based on coordinate range checking. The bullets
    use the same coordinate system as the actors.

    Check that the actor exists and it doesn't have immortality time left.

    bullactcoll:    lda actt,y
                    beq bsc_nocoll
                    lda actimm,y
                    bne bsc_nocoll

    Also, if an actor doesn't have hitpoints left, it doesn't participate in the
    collision checking.

                    lda acthp,y
                    bne bsc1
    bsc_nocoll:     clc
                    rts

    Get the actor X & Y size from a table, based on the actor type (modify the
    CMP instructions directly). Note that registers have to be saved to temporary
    locations.

    bsc1:           sty temp1
                    lda actt,y
                    tay
                    dey
                    lda actsizex,y
                    sta bsc_xcmp+1
                    lda actsizey,y
                    sta bsc_suby+1

    Compare Y coordinate ranges first. The bullet is considered a point-like
    object. If bullet is outside the actor's Y-size range, no collision has
    happened.

                    ldy temp1
                    lda acty,y              ;Check against bottom of actor
                    cmp bullety,x
                    bcc bsc_nocoll
                    sec
    bsc_suby:       sbc #$00                ;Check against top of actor
                    cmp bullety,x
                    bcs bsc_nocoll

    Then compare X-coordinates. Get X distance between bullet & actor by
    subtraction, and its absolute value (negate it if it's negative)

                    lda actxl,y             ;Get X distance between bullet&actor
                    sec
                    sbc bulletxl,x
                    sta temp1
                    lda actxh,y
                    sbc bulletxh,x
                    sta temp2
                    bpl bsc_posofs
                    eor #$ff
                    sta temp2
                    lda temp1
                    eor #$ff
                    clc
                    adc #$01
                    sta temp1
                    lda temp2
                    adc #$00
                    sta temp2

    Then check that bullet is not farther away than the actor's X size, or
    collision hasn't happened.

    bsc_posofs:     lda temp2                       ;X distance must not be
                    bne bsc_nocoll                  ;greater than X size
                    lda temp1
    bsc_xcmp:       cmp #$00
                    bcs bsc_nocoll
                    sec
                    rts

    "Actactcoll" subroutine
    -----------------------

    Actor-actor collision. This is similar to the subroutine above, but both
    actors have a size, and that must be taken into account in the coordinate
    comparisions.

            ;X = actor number (must exist)
            ;Y = actor number
            ;C=1 collision happened

    Check for actor existing, being not immortal and having hitpoints, like
    before.

    actactcoll:     lda actt,y
                    beq ssc_nocoll
                    lda actimm,y
                    bne ssc_nocoll
                    lda acthp,y
                    bne ssc1
    ssc_nocoll:     clc
                    rts

    Get the X & Y sizes of actors. Again, register saving to temporary locations
    must happen, because of the need to use many table indexes.

    ssc1:           sty temp1
                    stx temp2
                    lda actt,y
                    tay
                    dey
                    lda actt,x
                    tax
                    dex
                    lda actsizex,y
                    clc
                    adc actsizex,x
                    sta ssc_xcmp+1
                    lda actsizey,y
                    sta ssc_ycmp+1
                    ldy temp1
                    ldx temp2
                    lda acty,y              ;Check against bottom of actor
                    sec
                    sbc acty,x
                    bpl ssc_ypos
                    eor #$ff
                    clc
                    adc #$01
    ssc_ypos:
    ssc_ycmp:       cmp #$00
                    bcs ssc_nocoll
                    lda actxl,y             ;Get X distance between actors
                    sec
                    sbc actxl,x
                    sta temp1
                    lda actxh,y
                    sbc actxh,x
                    sta temp2
                    bpl ssc_posofs
                    eor #$ff
                    sta temp2
                    lda temp1
                    eor #$ff
                    clc
                    adc #$01
                    sta temp1
                    lda temp2
                    adc #$00
                    sta temp2
    ssc_posofs:     lda temp2                       ;X distance must not be
                    bne ssc_nocoll                  ;greater than X size
                    lda temp1
    ssc_xcmp:       cmp #$00
                    bcs ssc_nocoll
                    sec
                    rts

    "Spawnenemies" subroutine
    -------------------------

    Creates enemy actors to the left & right borders of the screen. Enemy appearance
    frequency and enemy actor types that will be spawned depends on the level we're
    on.

    Multiply level number by 16 to get an index to the spawn table.

    spawnenemies:   lda level
                    sec
                    sbc #$01
                    asl
                    asl
                    asl
                    asl
                    sta spawnadd+1

    Decision for spawning, if random number is in the range $10-$3f then don't
    spawn.

                    jsr random
                    and #$3f
                    cmp #$10
                    bcc spawnok1
                    rts
    spawnok1:       clc

    Add the random number to the spawn table index, to get the final position
    in the table.

    spawnadd:       adc #$00
                    tax

    Get enemy actor type from the spawn table. If it's 0, don't spawn.

                    lda levelspawntable,x
                    bne spawnok2
                    rts

    Search for a free actor (actor type zero) in the actor index range 1-7 (enemies).

    spawnok2:       ldx #1
    spawnsearch:    ldy actt,x
                    beq spawnfound
                    inx
                    cpx #7
                    bcc spawnsearch
                    rts
    spawnfound:     sta actt,x

    Enemy actor index is now in the X-register. Decision whether the enemy appears
    on left or right. Give the corresponding X-coordinate to the enemy.

                    jsr random
                    and #$01
                    tay
                    sta actd,x
                    lda spawnxlo,y
                    sta actxl,x
                    lda spawnxhi,y
                    sta actxh,x

    Random decision for the Y position of the actor.

                    jsr random
                    and #$03
                    clc
                    adc #$02

    Multiply Y-coordinate by 32; enemies' initial Y-coordinates are aligned to the
    blocks (4 chars high) on screen.

                    asl
                    asl
                    asl
                    asl
                    asl
                    sta acty,x

    If there is no ground under the feet of an enemy, it will not be spawned. In
    that case, simply zero the actor type and exit the routine.

                    jsr checkground
                    bcc spawnground
                    lda #0
                    sta actt,x
                    rts

    Reset jumping, speed, animation frame, frame delay, immortality.

    spawnground:    lda #0
                    sta actj,x
                    sta actsx,x
                    sta actsy,x
                    sta actf,x
                    sta actfd,x
                    sta actyd,x
                    sta actimm,x

    Give the initial hitpoints according to enemy actor type. (from a table)

                    ldy actt,x
                    dey
                    lda actmaxhp,y
                    sta acthp,x
                    rts

    The types of enemies that will be spawned in each level.

                    ;level1
    levelspawntable:dc.b 0,2,0,2,0,3,0,2,0,2,0,3,0,2,0,2
                    ;level2
                    dc.b 0,2,2,3,0,2,3,4,0,3,4,0,2,3,2,5
                    ;level3
                    dc.b 2,2,3,3,2,3,4,4,3,3,4,4,3,2,5,5

    Initial X-coordinates are either 0 or 320

    spawnxlo:       dc.b 0,<320
    spawnxhi:       dc.b 0,>320

    "Checkscroll" subroutine
    ------------------------

    Checks if the player actor's X-position is on the right side of the screen and
    gives the scrolling speed in the accumulators (zero if no scrolling.)

    checkscroll:    ldx #$00
                    lda actxh
                    cmp #1
                    bcs needscroll
                    lda actxl
                    cmp #200
                    bcc noneedscroll
    needscroll:     ldx #$02
    noneedscroll:   txa
                    rts

    "Drawbullets" subroutine
    ------------------------

    Saves the chars that are underneath the bullet positions and draws the bullet
    chars. There are 16 bullets (possibly) to draw.

    The bullet index (X-register) will go from last to first.

    drawbullets:    ldx #15

    Check if bullet is active. Skip if not.

    dbloop:         lda bullett,x
                    beq dbnext

    Divide bullet Y-pos by 8 to get character row number.

                    lda bullety,x
                    lsr
                    lsr
                    lsr

    If outside the visible gamescreen, skip.

                    cmp #23
                    bcs dbnext

    Get the corresponding screen memory row address.

                    tay
                    lda rowtbllo,y
                    sta temp1
                    lda rowtblhi,y
                    sta temp2

    Divide bullet X-pos by 8 to get column number.

                    lda bulletxh,x
                    sta temp3
                    lda bulletxl,x
                    lsr temp3
                    ror
                    lsr temp3
                    ror
                    lsr temp3
                    ror

    If outside the visible gamescreen, skip.

                    cmp #40
                    bcs dbnext

    Add the column to the row address to get the final memory location. Store
    also the memory location to the bullet array for fast retrieval later.

                    clc
                    adc temp1
                    sta temp1
                    sta bulletlo,x
                    lda temp2
                    adc #$00
                    sta temp2
                    sta bullethi,x
                    ldy #0

    Save the char under the bullet position and draw the bullet (char number 255)

                    lda (temp1),y
                    sta bulletunder,x
                    lda #255
                    sta (temp1),y

    Set bullet type to 2 to indicate the bullet has been drawn on screen.

                    lda #2
                    sta bullett,x

    Loop until all bullets done.

    dbnext:         dex
                    bpl dbloop
                    rts

    "Erasebullets" subroutine
    -------------------------

    Restores the chars that were overwritten by bullets on screen. To ensure
    correct restore, the index must go in reverse direction (from first to last)
    than in the "drawbullets" routine.

    Start from bullet index 0 (in X-register)

    erasebullets:   ldx #0

    Skip if the bullet hasn't been drawn

    ebloop:         lda bullett,x
                    cmp #2
                    bne ebnext
                    ldy #0

    The screen memory position has already been calculated.

                    lda bulletlo,x
                    sta temp1
                    lda bullethi,x
                    sta temp2

    Restore the char that was under the bullet.

                    lda bulletunder,x
                    sta (temp1),y

    Reset the bullet type to 1 to indicate it has been erased from the screen

                    lda #1
                    sta bullett,x

    Loop until all 16 bullets have been checked.

    ebnext:         inx
                    cpx #16
                    bcc ebloop
                    rts

    "Movebullets" subroutine
    ------------------------

    Moves the bullets, if they exist, and checks their collisions to player
    & enemies.

    Start from the last bullet (X-register as index).

    movebullets:    ldx #15

    Does the bullet exist?

    mbloop:         lda bullett,x
                    beq mbnext

    Is it a player or enemy bullet?

                    cpx #8
                    bcs checkenemybull

    It's a player bullet, loop through the enemy actor indexes 1-7 to check
    collisions.

                    ldy #1
    checkplayerbull:
                    jsr bullactcoll
                    bcc cpb_nocoll

    If a collision happened, reduce the enemy's hitpoints by one and remove the
    bullet that collided.

                    lda acthp,y
                    sec
                    sbc #$01
                    sta acthp,y
                    lda #$00
                    sta bullett,x
                    jmp mbnext
    cpb_nocoll:     iny
                    cpy #8
                    bcc checkplayerbull
                    jmp bullcheckdone

    It's an enemy bullet, check collision to player actor and reduce player actor's
    hitpoints (kill player actor!) + remove bullet if collided

    checkenemybull: ldy #0
                    jsr bullactcoll
                    bcc bullcheckdone
                    lda acthp,y
                    sec
                    sbc #$01
                    sta acthp,y
                    lda #$00
                    sta bullett,x
                    jmp mbnext

    Collision checking has been done, and the bullet wasn't removed. Next, move
    the bullet. Check direction ("bulletd" array); move the bullet 8 pixels either
    left or right depending on that.

    bullcheckdone:  lda bulletd,x
                    bne mbleft

    Movement right.

    mbright:        lda bulletxl,x
                    clc
                    adc #8
                    sta bulletxl,x
                    lda bulletxh,x
                    adc #0
                    sta bulletxh,x
                    beq mbnext

    If the bullet goes outside the screen, remove it.

                    lda bulletxl,x
                    cmp #(320-256)
                    bcc mbnext
    mberase:        lda #0
                    sta bullett,x
                    jmp mbnext

    Movement left.

    mbleft:         lda bulletxl,x
                    sec
                    sbc #8
                    sta bulletxl,x
                    lda bulletxh,x
                    sbc #0
                    sta bulletxh,x

    If the bullet goes outside the screen, remove it.

                    bmi mberase

    Loop until all bullets have been moved.

    mbnext:         dex
                    bpl mbloop
                    rts

    "Moveactors" subroutine
    -----------------------

    Calls the move routine of each actor (0-7) and decreases their immortality
    counter (used only for the player). Also, for enemy actors (1-7) a check
    is made to see if they have gone are outside the screen; in this case they
    are removed.

    Loop from last actor to first, with X register as index.

    moveactors:     ldx #7

    Does the actor exist?

    mactloop:       lda actt,x
                    beq mactnext

    If it has immortality left, decrease the immortality counter.

                    lda actimm,x
                    beq mact_noimm
                    dec actimm,x

    If it's the player, skip the removal check.

    mact_noimm:     cpx #0
                    beq mact_noremove
                    lda actxh,x
                    beq mact_noremove
                    bmi mact_rleft

    Check for X-coordinates greater/equal to 330 or less than -10, and remove
    the actor in that case.

    mact_rright:    lda actxl,x
                    cmp #<(330)
                    bcc mact_noremove
                    lda #0
                    sta actt,x
                    jmp mactnext
    mact_rleft:     lda actxl,x
                    cmp #(256-10)
                    bcs mact_noremove
                    lda #0
                    sta actt,x
                    jmp mactnext

    Make sure X is preserved for the next actor (although no actor move routine
    should modify the X register)

    mact_noremove:  stx mact_restx+1

    Get the move routine JSR address corresponding to the actor type's move
    routine and call the move routine.

                    lda actt,x
                    tay
                    dey
                    lda actroutlo,y
                    sta mactjsr+1
                    lda actrouthi,y
                    sta mactjsr+2
    mactjsr:        jsr $0000

    Go to next actor, loop until all have been done.

    mact_restx:     ldx #$00
    mactnext:       dex
                    bpl mactloop
                    rts

    "Drawactors" subroutine
    -----------------------

    Transforms the actors' position and animation frame into actual sprite data
    put to the sprite registers.

    Init a few "virtual" sprite registers for the X-coordinate MSB, sprite on
    bits and X & Y expansion. This is to prevent flicker.

    drawactors:     lda #$00
                    sta virtd010
                    sta virtd015
                    sta virtd017
                    sta virtd01d

    Loop through all actors (X is the index).

                    ldx #7

    Check that the actor exists, and move its type to the Y register, for use in
    actortype properties (color, expansion etc.) table lookups.

    dactloop:       lda actt,x
                    bne dactok
                    jmp dactnext
    dactok:         tay
                    dey

    If the actor is immortal, it flashes at the rate given by the 3th bit of the
    immortality counter. When that bit is on, don't draw the actor.

                    lda actimm,x
                    and #$08
                    beq dact_noflash
                    jmp dactnext

    Set the corresponding $d015 bit (sprite is on)

    dact_noflash:   lda virtd015
                    ora bittable,x
                    sta virtd015

    Get the actor X-coordinate and subtract the actor's hotspot (X-center).

                    lda actxl,x
                    sec
                    sbc acthotx,y
                    sta temp1
                    lda actxh,x
                    sbc #$00
                    sta temp2

    Do same for Y-coordinate.

                    lda acty,x
                    sec
                    sbc acthoty,y
                    sta temp3

    Because the actor coordinate system's origin was (0,0) but the top-left edge is
    (24,50) for the actual sprite coordinates, add those values.

                    lda temp3
                    clc
                    adc #50
                    sta temp3
                    lda temp1
                    clc
                    adc #24
                    sta temp1
                    lda temp2
                    adc #0
                    sta temp2

    Get actor's "base frame" depending on its direction.

                    lda actd,x
                    beq dact_right
    dact_left:      lda actbaseframel,y
                    jmp dact_frame
    dact_right:     lda actbaseframer,y
    dact_frame:     clc

    Add the animation frame to the base frame, and store the frame number to the
    spriteframe pointers (last 8 bytes of screen memory)

                    adc actf,x
                    sta 2040,x

    Get actor's color and store it to the sprite color register

                    lda actcolor,y
                    sta $d027,x

    If actor is expanded in X or Y direction, set the corresponding expand bits.

                    lda actmagx,y
                    beq dact_nomagx
                    lda virtd01d
                    ora bittable,x
                    sta virtd01d
    dact_nomagx:    lda actmagy,y
                    beq dact_nomagy
                    lda virtd017
                    ora bittable,x
                    sta virtd017

    Multiply actor (sprite) number by 2 to get the index to the X/Y coordinate
    registers.

    dact_nomagy:    txa
                    asl
                    tay

    Store the X-coordinate least significant byte and Y-coordinate.

                    lda temp1
                    sta $d000,y
                    lda temp3
                    sta $d001,y

    Then handle X-coordinate most significant bit; set the $d010 bit if X-
    coordinate is in the range 256-511

                    lda temp2
                    beq dactnext
                    lda virtd010
                    ora bittable,x
                    sta virtd010

    Loop until all actors done.

    dactnext:       dex
                    bmi dactdone
                    jmp dactloop

    Then dump the virtual sprite bit registers to the actual video registers.

    dactdone:       lda virtd010
                    sta $d010
                    lda virtd015
                    sta $d015
                    lda virtd017
                    sta $d017
                    lda virtd01d
                    sta $d01d
                    rts

    Powers of two for the corresponding bits of each sprite

    bittable:       dc.b 1,2,4,8,16,32,64,128
    virtd010:       dc.b 0
    virtd015:       dc.b 0
    virtd017:       dc.b 0
    virtd01d:       dc.b 0

    "Waitras" subroutine
    --------------------

    Waits until the raster interrupt counter increased by "raster0" has changed.
    Resets the counter afterwards.

    waitras:        lda rastercount
                    cmp #$01
                    bcc waitras
                    lda #$00
                    sta rastercount
                    rts

    "Initscroll" subroutine
    -----------------------

    Clears the screen and sets the correct color memory value (multicolor white)
    for gamescreen displaying. Resets the map & block positions ("mapx", "blockx")
    as well as the fine scrolling ("scrollx") and sets the map data pointer based
    on the level we're on. The background graphics map data for the levels is
    organized in the memory as follows:

    1st block-row of 1st level (100 blocks = 100 bytes)
       ...
    5th block-row of 1st level (100 blocks = 100 bytes)

    1st block-row of 2nd level (100 blocks = 100 bytes)
       ...
    5th block-row of 2nd level (100 blocks = 100 bytes)

    1st block-row of 3rd level (100 blocks = 100 bytes)
       ...
    5th block-row of 3rd level (100 blocks = 100 bytes)

    initscroll:
                    ldx #39
    iscr1:
    N               SET 0
                    REPEAT 24
                    lda #$20
                    sta $400+N*40,x
                    lda #$09
                    sta $d800+N*40,x
    N               SET N+1
                    REPEND
                    dex
                    bmi iscrdone1
                    jmp iscr1
    iscrdone1:      lda #$00
                    sta mapx
                    sta blockx
                    lda #$07
                    sta scrollx
                    lda level
                    sec
                    sbc #$01
                    asl
                    tax
                    lda levelmaptbl,x
                    sta mapadrlo
                    lda levelmaptbl+1,x
                    sta mapadrhi

    ;Finally, set the display mode used by "raster1" interrupt.

                    lda #DISPGAME
                    sta dispmode
                    rts
    levelmaptbl:    dc.w MAP, MAP+500, MAP+1000

    "Doscroll" subroutine
    ---------------------

    Performs X-scrolling. Amount of pixels to scroll (scrolling speed) is given
    in the accumulator.

    If already at the right edge of a level, do not scroll further.

    doscroll:       ldx mapx
                    cpx #100
                    bcc doscrollok
                    rts
    doscrollok:     sta scrsub+1
                    sta sprsub+1

    Move all actors to the left by the amount of pixels to scroll.

                    ldx #7
    doscrollspr:    lda actxl,x
                    sec
    sprsub:         sbc #$00
                    sta actxl,x
                    lda actxh,x
                    sbc #$00
                    sta actxh,x
                    dex
                    bpl doscrollspr

    Then subtract the scrolling amount from the X fine-scroll. If it goes to
    negative, screen data must be shifted.

                    lda scrollx
                    sec
    scrsub:         sbc #$00
                    bmi scrshift
                    sta scrollx
                    rts
    scrshift:       and #$07
                    sta scrollx

    First shift the top 10 rows of gamescreen (screen rows 4-13) one char to the
    left (all rows not done at once to eliminate tearing effects on NTSC machines,
    that have less rastertime).

                    ldx #$00
    scrshiftloop1:
    N               SET 4
                    REPEAT 10
                    lda $400+N*40+1,x
                    sta $400+N*40,x
    N               SET N+1
                    REPEND
                    inx
                    cpx #39
                    bne scrshiftloop1

    Then shift the bottom 10 rows of gamescreen (screen rows 14-23)

                    ldx #$00
    scrshiftloop2:
    N               SET 14
                    REPEAT 10
                    lda $400+N*40+1,x
                    sta $400+N*40,x
    N               SET N+1
                    REPEND
                    inx
                    cpx #39
                    bne scrshiftloop2

    Next it's time to draw new background graphics to the edge of the screen.
    The mapdata tells what numbered blocks must be drawn, and the blocks (4x4
    char sized) tell what chars must be drawn on screen.

    Add the map x-position to the left edge address of map.

                    lda mapadrlo
                    clc
                    adc mapx
                    sta temp1
                    lda mapadrhi
                    adc #$00
                    sta temp2

    This is the destination screen pointer, starting from the rightmost column
    of screen row 4 (first gamescreen row).

                    lda #<($400+4*40+39)
                    sta temp3
                    lda #>($400+4*40+39)
                    sta temp4

    This is the row counter (20 rows to do)

                    lda #20
                    sta temp5

                    ldy #$00

    Data for each 4x4 block is stored in the following way:
    0 1 2 3
    4 5 6 7
    8 9 a b
    c d e f

    So, to get on the next row in a block, 4 must be added to the memory address
    from where fetching the block data. To get the horizontal position within a
    block, the block-x position (0-3) can just be added to that address.

    scrblockloop:   ldx blockx

    Get the block number from the map data.

                    lda (temp1),y
                    tay

    Modify the LDA instruction to fetch block data, to point to the address of just
    that block.

                    lda blocktbllo,y
                    sta scrblockget+1
                    lda blocktblhi,y
                    sta scrblockget+2

    To get onto the next map-row, increase the map address by 100 bytes (done here
    already)

                    lda temp1
                    clc
                    adc #100
                    sta temp1
                    lda temp2
                    adc #$00
                    sta temp2
                    ldy #$00

    Now get the chars from the blockdata and put them on the screen. X register
    is the position within the block.

    scrblockget:    lda $1000,x
                    sta (temp3),y

    Add 40 to the destination screen address to get on the next row.

                    lda temp3
                    clc
                    adc #40
                    sta temp3
                    lda temp4
                    adc #0
                    sta temp4

    Increase position within block with 4 to get on the next block row (as told
    earlier)

                    txa
                    adc #4
                    tax

    All 20 rows done?

                    dec temp5
                    beq scrblockready

    If the block-position went to 16 or over that it's time to fetch the next
    block from the map data.

                    cpx #$10
                    bcc scrblockget
                    jmp scrblockloop

    New data has been drawn. Now increase the block & map-positions, so that the
    next column of background graphics will be drawn next time.

    scrblockready:  inc blockx
                    lda blockx
                    cmp #$04
                    bcc scrblockready2
                    lda #$00
                    sta blockx
                    inc mapx
    scrblockready2: rts

    This is a table for the addresses of all background graphics blocks.

    blocktbllo:
    N               SET 0
                    REPEAT 128
                    dc.b #<(BLOCKS+N*16)
    N               SET N+1
                    REPEND
    blocktblhi:
    N               SET 0
                    REPEAT 128
                    dc.b #>(BLOCKS+N*16)
    N               SET N+1
                    REPEND

    "Getjoystick" subroutine
    ------------------------

    First set all bits of $dc00 to 1 to be able to read them correctly, then
    save the current joystick control status to the previous status, then get
    new status from $dc00, negating all the bits.

    getjoystick:    lda #$ff
                    sta $dc00
                    lda joystick
                    sta prevjoy
                    lda $dc00
                    eor #$ff
                    sta joystick
                    rts

    "Showpic" subroutine
    --------------------

    Displays the title bitmap picture. Transfers the screen & color data that has
    been stored after the bitmap to their correct locations (for bitmap display,
    the screen memory resides at $5c00).

    showpic:        ldx #$00
    showpicloop:    lda $8000,x
                    sta $5c00,x
                    lda $8100,x
                    sta $5d00,x
                    lda $8200,x
                    sta $5e00,x
                    lda $8400,x
                    sta $d800,x
                    lda $8500,x
                    sta $d900,x
                    lda $8600,x
                    sta $da00,x
                    inx
                    bne showpicloop
    showpic2:       lda $8300,x
                    sta $5f00,x
                    lda $8700,x
                    sta $db00,x
                    inx
                    cpx #192
                    bne showpic2

    Set titlescreen display mode for the "raster1" interrupt.

                    lda #DISPTITLE
                    sta dispmode
                    rts

    "Initscreen" subroutine
    -----------------------

    Sets color registers (background graphics multicolors and sprite multicolors),
    turns all sprites multicolored and makes them be display over the background.
    Draws also the initial scorepanel display and turns it yellow.

    initscreen:     lda #$00
                    sta $d020
                    sta $d021
                    lda #$0e
                    sta $d022
                    lda #$06
                    sta $d023
                    lda #$ff
                    sta $d01c
                    lda #$00
                    sta $d01b
                    lda #$0a
                    sta $d025
                    lda #$00
                    sta $d026
                    ldx #39
    ip_loop:        lda paneltext,x
                    and #$3f
                    sta $400+24*40,x
                    lda #$07
                    sta $d800+24*40,x
                    dex
                    bpl ip_loop
                    rts

    "Drawscores" subroutine
    -----------------------

    Draws all the elements of the status bar, like score, lives, level, time &
    hiscore.

    drawscores:     ldy #$02
                    ldx #$02
    ds1:            lda score,x

    Get the binary coded decimal at the high 4 bits of the score byte.

                    lsr
                    lsr
                    lsr
                    lsr

    Add 48 - character code of '0'.

                    clc
                    adc #48

    Store to screen.

                    sta $400+24*40,y
                    iny

    Then, get the binary coded decimal at the low 4 bits of the score byte.

                    lda score,x
                    and #$0f
                    clc
                    adc #48
                    sta $400+24*40,y
                    iny

    Loop for all 3 bytes of the score.

                    dex
                    bpl ds1
                    ldx #$02
                    ldy #34

    Display the hiscore in a similar fashion.

    ds2:            lda hiscore,x
                    lsr
                    lsr
                    lsr
                    lsr
                    clc
                    adc #48
                    sta $400+24*40,y
                    iny
                    lda hiscore,x
                    and #$0f
                    clc
                    adc #48
                    sta $400+24*40,y
                    iny
                    dex
                    bpl ds2

    Display lives. This is just one digit.

                    lda lives
                    clc
                    adc #48
                    sta $400+24*40+14

    Display time, that has 2 binary coded digits (similar to what was done for
    the score & hiscore).

                    lda time
                    lsr
                    lsr
                    lsr
                    lsr
                    clc
                    adc #48
                    sta $400+24*40+20
                    lda time
                    and #$0f
                    clc
                    adc #48
                    sta $400+24*40+21

    Display level number (only one digit)

                    lda level
                    clc
                    adc #48
                    sta $400+24*40+28
                    rts

    paneltext:      dc.b "SC         MEN    TI     LEV    HI      "

    "Initraster" subroutine
    -----------------------

    Activates raster interrupts. "Raster0" interrupt is to be executed first.

    initraster:     sei
                    lda #<raster0                   ;Set main IRQ vector
                    sta $0314
                    lda #>raster0
                    sta $0315
                    lda #$7f                        ;Set timer interrupt off
                    sta $dc0d
                    lda #$01                        ;Set raster interrupt on
                    sta $d01a
                    lda $d011
                    and #$7f
                    sta $d011
                    lda #RASTER0POS                 ;Set low bits of position
                    sta $d012                       ;for first raster interrupt
                    lda $dc0d                       ;Acknowledge timer interrupt
                    cli                             ;(for safety)
                    rts

    "Raster0" interrupt
    -------------------

    Sets video registers for the display of the score panel (X-scrolling is
    stationary and singlecolor, screen memory at $0400-$07ff, videobank is at
    $0000-$3fff), plays music and increases "rastercount", then sets up "raster1"
    to be executed next.

    raster0:        cld
                    lda #27
                    sta $d011
                    lda #$03
                    sta $dd00
                    lda #21
                    sta $d018
                    lda #8
                    sta $d016
                    inc $d019
                    jsr MUSIC+3
                    lda #<raster1
                    sta $0314
                    lda #>raster1
                    sta $0315
                    lda #RASTER1POS
                    sta $d012
                    inc rastercount
                    jmp $ea81

    "Raster1" interrupt
    -------------------

    Sets video registers according to the displaymode ("dispmode"). The game
    screen & textscreen both are at $0400-$07ff, videobank at $0000-$3fff, but the
    difference is the X-scrolling: gamescreen has variable X-finescrolling
    ("scrollx") while the textscreen has stationary X-scrolling. The bitmap screen
    is at videobank $4000-$7fff, screen memory $5c00-$5fff, with multicolor bitmap
    graphics. Finally, "raster0" is set to be executed next to form a loop.

    raster1:        cld
                    lda dispmode
                    beq r1_gamemode
    r1_titlemode:   cmp #DISPTEXT
                    bne r1_pic
                    lda #3
                    sta $dd00
                    lda #27
                    sta $d011
                    lda #$18
                    sta $d016
                    lda #21
                    sta $d018
                    jmp r1_end
    r1_pic:         lda #2
                    sta $dd00
                    lda #59
                    sta $d011
                    lda #24
                    sta $d016
                    lda #$78
                    sta $d018
                    lda #$00
                    sta $d015
                    jmp r1_end
    r1_gamemode:    lda #$03
                    sta $dd00
                    lda #27
                    sta $d011
                    lda #30
                    sta $d018
                    lda scrollx
                    and #$07
                    ora #$10
                    sta $d016
    r1_end:         inc $d019
                    lda #<raster0
                    sta $0314
                    lda #>raster0
                    sta $0315
                    lda #RASTER0POS
                    sta $d012
                    jmp $ea81

    The variables
    -------------

    General variables:

    score:          dc.b 0,0,0
    hiscore:        dc.b 0,0,0
    lives:          dc.b 3
    level:          dc.b 1
    time:           dc.b $99
    timedl:         dc.b 0
    firedelay:      dc.b 0
    killactive:     dc.b 0
    killmeter:      dc.b 0
    killmeterd:     dc.b 0
    killlimit:      dc.b 0

    Actor variable arrays:

    actxl:          ds.b 8,0
    actxh:          ds.b 8,0
    acty:           ds.b 8,0
    actf:           ds.b 8,0
    actfd:          ds.b 8,0
    actd:           ds.b 8,0
    actsx:          ds.b 8,0
    actsy:          ds.b 8,0
    actyd:          ds.b 8,0
    actj:           ds.b 8,0
    actt:           ds.b 8,0
    actimm:         ds.b 8,0
    acthp:          ds.b 8,0

    Tables for properties of different actor types:

    X- and Y-hotspots (centers within the sprite):

    acthotx:        dc.b 12,12,12,12,24,24
    acthoty:        dc.b 40,40,40,40,40,40

    X- and Y-magnification:

    actmagx:        dc.b 0,0,0,0,1,1
    actmagy:        dc.b 1,1,1,1,1,1

    X- and Y-sizes:

    actsizex:       dc.b 12,12,12,12,30,48
    actsizey:       dc.b 42,42,42,42,32,42

    Colors:

    actcolor:       dc.b 11,9,2,4,12,7

    Base frames facing left and right:

    actbaseframer:  dc.b 128,144,144,144,152,156
    actbaseframel:  dc.b 136,148,148,148,154,156

    Addresses of move routines:

    actroutlo:      dc.b <plr,<man,<man,<man,<mc,<expl
    actrouthi:      dc.b >plr,>man,>man,>man,>mc,>expl

    Shooting Y-coord modification:

    actshootymod:   dc.b 20,20,20,20,20,0

    Initial hitpoints:

    actmaxhp:       dc.b 1,1,2,3,5,0

    Score for killing an enemy:

    actscorelo:     dc.b $00,$50,$50,$00,$00,$00
    actscorehi:     dc.b $00,$02,$04,$06,$10,$00

    Bullet variable arrays:

    bulletxl:       ds.b 16,0
    bulletxh:       ds.b 16,0
    bullety:        ds.b 16,0
    bulletd:        ds.b 16,0
    bullett:        ds.b 16,0
    bulletlo:       ds.b 16,0
    bullethi:       ds.b 16,0
    bulletunder:     ds.b 16,0

    Included binary data
    --------------------

    The sprites:

                    org SPRITES
                    incbin efny.spr

    The chars: (the char-collision data saved by BGEDIT is unused)

                    org CHARS-$100
                    incbin efny.chr

    The music, made with SadoTracker:

                    org MUSIC
                    incbin music.bin

    The background map data (map-header saved by BGEDIT is unused)

                    org MAP-2

                    incbin efny.map

    The blocks (block-color data saved by BGEDIT is unused)

                    org BLOCKS-$80
    blocks:         incbin efny.blk

    The title bitmap picture:

                    org PICTURE
                    incbin plissken.pic

    So, there we have reached the end of the Escape From New York sourcecode, and
    almost the end of this rant. But for a closing I'll explain how the music
    was extracted, and the commands in the makefile.

    The music was saved with the pack/relocate option of SadoTracker on a D64
    image (EFNYMUS.D64), starting from address $4000. Then, that .PRG file was
    extracted from the disk image (don't remember what utility I used back then,
    today I would use D642PRG in my commandline-utility collection). The
    EFNY+PLAYER.PRG file was then converted to a raw binary file MUSIC.BIN
    (without start address) with the PRG2BIN utility.

    The makefile commands:

    - EFNY.PRG depends on the source code, on the IFF/LBM title picture and the
      music binary:

    efny.prg: efny.s efny.lbm music.bin

    - Execute the BENTON64 picture conversion utility, save the picture as a raw
      binary file PLISSKEN.PIC with bitmap data (8kb) followed by screen data (1kb)
      and color memory data (1kb)

            benton64 efny.lbm plissken.pic -r

    - Assemble the source code with DASM, output file is EFNY.PRG. Use verbose mode
      and maximum of 3 passes.

            dasm efny.s -oefny.prg -v3 -p3

    - Compress the output file with PUCRUNCH (get it at
      http://www.cs.tut.fi/~albert/Dev/pucrunch/) with execution start address
      set at 2048.

            pucrunch -x2048 efny.prg efny.prg

    If you are interested, you can examine the sprite file EFNY.SPR with SPREDIT
    and the background data with BGEDIT (fastest is to press F9 to "load all
    leveldata" and type EFNY)

    Ok, now this rant is finished.

                                                      Lasse Öörni
                                                      loorni@student.oulu.fi