Jump to content
Jet Set Willy & Manic Miner Community

Free space and code optimisation in "JSW"


jetsetdanny

Recommended Posts

GO_ROOM_LEFT ;L948A

LD BC,$FE1E ;

Given that the AND now comes before the OR, it's possibly more elegant, logical and consistent to use LD BC, $E01E when moving into the room to the left. i.e. For both 'Exit Left' and 'Exit Right' (for which BC=$E000), pass the bits which represent Willy's (unchanged) vertical position through the AND B gate, and then set the bits which determine his (new) horizontal position via the OR C gate.

 

Although it makes no difference to the final outcome.  And of course, it's another excellent bit of optimisation that you've come up with!

Edited by IRF
Link to comment
Share on other sites

Just to expand on my point about 'consistent logic', when Willy is moving into a vertically adjacent room, the AND B filters the bits which represent Willy's unchanged horizontal position, then the OR C sets the appropriate bits for his new vertical position.

 

So for 'Exit Up', BC=$1FA0.

For 'Exit Down', BC=$1F00.

Edited by IRF
Link to comment
Share on other sites

The following idea only saves a single byte, but I consider it to be a most satisfactory arrangement:

 

- Replace the 'INC HL' at #96C6 with a 'DEC HL';

- Take all the notes of the Title Screen tune, reverse their order, and insert them into the range of spare addresses starting at #8141, such that the last note of the tune occupies the address #8141;

- Edit the operand of the LD HL, $0000 command at #88A8, to point at the new position of the first byte of the Title Screen tune (the first note now occupying the highest used address in page #81 of the code).

 

The Guardian Buffer Terminator Byte (value #FF) at #8140 now serves a dual purpose, as it also serves to terminate the Title Screen tune!

 

N.B. Under this arrangement, the Title Screen tune now has a maximum length of 191 bytes (not including the Terminator Byte), because page #82 of the code is used as the Screen Buffer Address look-up table).

Link to comment
Share on other sites

[Norman Sword's code for Willy's horizontal movement] handles both directions by having one routine, which has variables passed to it. The testing is the same in both directions and..... [is much] shorter than Matthews version.

 

 

I wonder if a similar process could be used to optimise the code which moves horizontal guardians, so that the code which moves them left and right is shared?

 

IX would already be in use by the guardian loop, but perhaps you could use the IY indexed register-pair to assign variables depending on a horizontal guardian's direction of travel?

 

Or else use the B/C/D/E registers to set the variables?  Geoff Mode takes that approach for the code which moves ropes - see #90D7 to #911D in the disassembly of his game engine:

https://web.archive.org/web/20030701143111/http://www.cix.co.uk/~morven/jsw/geoff_dis.html

 

Mind you, the existing code for moving horizontal guardians is much shorter than the equivalent Willy-handling code, so it might not be worth optimising/consolidating in the same way, even if it were possible - the table of variables, plus the code which sets up the combined left/right movement regime, might be as long as one half of the current code anyway!

 

I am pleased to report that I've managed to achieve the above (my suggestion in bold).

 

The following code will handle horizontal guardians moving both leftwards and rightwards, and it saves 21 bytes compared with Matthew's original code:

 

Starting at #9133:

 

      PUSH IX   ; copy the address of Byte 0 of the guardian definition

      POP HL    ; to the HL register-pair

 

      LD DE,#E0FF     ; parameters for a

      LD B A,(IX+$06)    ; guardian moving left

      BIT 7,(HL)

      JR Z, left

      LD DE,#2001     ; parameters for a 

      LD B A,(IX+$07)    ; guardian moving right

 

left:

      LD B,A

 

      LD A,(HL)

      ADD A,D                 ; increment animation-frame of guardian

      BIT 7,(HL)               ; sets the Zero Flag for a left-moving guardian

      LD (HL),A                ; update guardian definition Byte 0 with correct animation-frame

      JR Z,carry_okay

      CCF                        ; Complement the Carry Flag for a right-moving guardian

 

carry_okay:

      JR C,#91B6            ; not time to update the guardian's x-coordinate; proceed to Move the next guardian

 

      XOR #80

      LD (HL),A               ; time to update the guardian's x-coordinate, so reset the animation-frame to the first one for the next cell-column

 

      LD A,(IX+$02)        ; guardian definition byte 2

      LD C,A                   ; temporarily save in the C register

      AND #1F                ; extract guardian's current x-coordinate

      CP B                       ; has the guardian reached the extremity of its range in the current direction of travel?

      JR Z,turn_around   ; if so, jump to reverse its direction of travel

      LD A,C                   ; retrieve guardian definition byte 2

      ADD A,E                ; adjust x-coordinate

      LD (IX+$02),A        ; and save

      JR #91B6               ; proceed to Move the next guardian

 

turn_around:

      LD A,(HL)               ; we're not updating the x-coordinate after all,

      XOR #E0                ; instead we are switching to consider the other set of four animation-frames

      LD (HL),A               ; [assuming it's an eight-frame bidirectional sprite; if not, then filtering out of unwanted frames is done at the 'Draw the guardians' stage]

      JR #91B6               ; proceed to Move the next guardian

 

[#916A to #917E is now free; this may be sufficient to implement Norman Sword's 'mid-sprite bounce' - that's my next area for investigation...]

Edited by IRF
Link to comment
Share on other sites

Nice piece of code.

 

My only editing of the original code was to stop the type of guardian being changed whenever the original horizontal code was called. This was because my homing sprite called the horizontal movement whenever it wanted to move horizontally. The original code changed the guardian type.

 

It is noted that the guardian type in your code is not modified.

 

 

 

For clarity on your code it would benefit from a few additions regarding the usage of bit 7 (direction). This bit is being toggled in several places, with no mention as to why.  e.g.

 

          XOR #80        ;restore direction (bit 7)  which was inverted

 

         XOR #E0         ; invert direction (bit 7) and invert animation bits (6 and 5)

 

and finally for clarity , an explanation of what the carry bit is being used for.

 

; the direction bit (bit 7 = #80) must be taken into account
;animation right adding #20      #80, #a0, #c0, #e0    carry clear with each addition but not  #eo+#20  = #00
;                left   adding #e0       #60, #40, #20, #00   carry set with each addition but not      #00+#eo  = #eo

 

 

Link to comment
Share on other sites

I ran out of time last night. I intend to provide more commentary when I have the time (particularly in relation to the need to complement the Carry Flag), but I think you've grasped the gist of it.

 

I've also come up with a further optimisation which negates the need to take the Carry Flag into account in that way (and saves another four bytes), by replacing the JR C with a JP PO (responding to the Overflow Flag, with the conditionality the same for both left- and right-moving guardians). However, it's a bit of a fiddle as it involves swapping the half-pages of each bidirectional sprite (otherwise they walk backwards!), amongst other changes. It is possible to do it whilst preserving all the original behaviour though.

Link to comment
Share on other sites

;very little difference to the other version.
;
;but this has
;1) the addition of cell blocking to stop up and down movement via stairs into blocks
;2) changes in logic for jumping.
;3) removal of some data from the data table

;
; some of the data tables entries can be calculated by code. But the overhead of the code is bigger than the data table entry

;e.g  entry for (ix+ramp_final_adjust)= (ix+ramp_where_1)-(ix+ramp_step)

;
;set variables for these offsets
ramp_step                    equ 0*2
ramp_where_1             equ 1*2
ramp_where_2             equ 2*2
ramp_adjust                 equ 3*2
ramp_final_adjust        equ 4*2
ramp_animation_reset equ 5*2
;

;left data followed by right data
left_data
  DB -1,    1      ;0 STEP TO NEXT animation FRAME - replaces inc or dec
  DB $1f, $22   ;1 POSITION OF RAMP 1 - ramp walk up
  DB $41, $40  ;2 POSITION OF RAMP 2 - ramp walk down
  DB -1,     2     ;3 DIRECTION TO ADJUST HL - where to check vertically
  db $20, $21   ;4 FINAL ADJUST - final adjust of willy to his new moved to position
  DB 3,     0      ;5 ANIMATION RESET for next frame
;-----------------------------------

; ;8FBC: Move Willy (3)
; Used by the routines at 8DD3 and 8ED4. This routine moves Willy
; left or right as necessary.
RAMP_LOGIC                     ;L8FBC
    LD A,(ROPE_STATUS)   ;L85D6;rope status indicator
    DEC A                             ;Is Willy on a rope?
    RET P                             ;Return if so (Willy's movement along a rope is handled at 935E)

    LD A,(L85D0)                 ;L85D0;Willy's direction and movement flags
    BIT 1,A                           ;Is Willy moving?
    RET Z                             ;Return if not
;not on a rope, but moving
    ld ix,left_data
    AND $01                        ;Is Willy facing left?
    JR nz,set_left                 ;go if so 1=LEFT, 0=RIGHT
    inc ix                              ;else move IX to right data
set_left:
    ld c,a                              ;save direction facing
    LD  HL,(WILLY_ATT)    ;L85D3; Willy's attribute buffer coordinates
; is the animation still within the same ATT squares
    ld de,WILLY_ANM

    ld a,(de)                        ;(WILLY_ANM);L85D2 ;Willy's animation frame at 85D2  
    add a,(ix+ramp_step)    ;-1 OR +1 step to next animation
;if it was 0 then and now -1
;or if it was 3 then now 4    ; in both cases bit 2 is set
    BIT 2,A                          ;check limits
    JR NZ,RAMP_CHK         ;L8FDC ;move Willy's across a cell boundary
;if here still contained within the same animation (att) square, but different phase of animation
    LD (DE),A                     ;WILLY_ANM
    RET

 

; Willy now needs to move left or right and move into new attribyte squares
; this movement also needs to handle the stairs and check for their presence
RAMP_CHK:
; three final outcomes for bc
;                                            going up   bc=-32
;                                            going down    bc=+32
;                                            no ramp/blocked  bc=0
    push hl                             ;save for later

; all de increments are positive so preload D
    ld d,0
; if jumping ignore ramp checks
    LD  A,(AIRBORNE)                   ;L85D1
    OR A                                        ;Is Willy jumping?
    JR  NZ,null_offset                     ;Jump if so
;not jumping so check for stairs
    ld a,(RAMP_DIR)                     ;L80da ;either 0 or 1
; in the original JSW this value is 0 or 1 (my versions use other bits. so get rid the extra bits)
    and 1                                       ;get rid of the other bits
; this condition swaps on left and right movement
    cp c                                         ;set the condition for where to check
;assume going up ramp
    ld bc,-32
    ld e,(ix+ramp_where_1)
    jr nz,ramp_decided
; if here going down
    ld bc,+32
    ld e,(ix+ramp_where_2)
ramp_decided:
    add hl,de                                ; add offset to find ramp/stair square
    ld a,(RAMP)                           ;L80C4
    sub (hl)                                  ; is a ramp here ?
;if ramp not found then no offset needed
    jr nz,null_offset
;on a ramp is it going down?
    or b                                         ;the direction, taken from BC=+32/-32,  "A" must have been 0
    jr nz,found_                            ;branch if going up
;going down so locate the possible blocking tile
    LD A,L
    ADD A,(ix+ramp_step)             ;step across to possible wall
    LD L,A
;is the wall blocking descent
    ld a,(WALL)                            ;L80B2
    cp (hl)
    jr nz,found_
;a wall is blocking going down ramp- so remove ramp offset - and walk horizontally along  ramp/wall

:
:
; come here for no offset-or no ramp action
null_offset:
    ld b,d                                       ;
    ld c,d                                       ;bc=0, no ramp so no offset/shift
found_:
    pop hl                                      ;retrieve  WILLY_ATT

    add hl,bc                                 ; add the possible ramp displacement
    ld a,(WALL)
    LD B,A                                     ;
    ld e,(ix+ramp_adjust)              ;d is still 0
    ld a,l
   and 31
   add a,e                                    ; move across
;if negative (-1) then
    jp m,GO_ROOM_LEFT           ;L948A
; if 32, then also off screen
    bit 5,a
    jp nz,GO_ROOM_RIGHT         ;L949E
    ld a,l                                        ; restore full x position (which has part y position embeded)
    add a,e
    ld l,a                                        ;still on screen
    bit 1,h
    jr nz,ignore_this
; additional check if moving up a ramp; which checks out of line for willies head
;c can be -32 +32 or 0
    bit  7,c
    jr z,hor_or_dwn
;going up find the tile to check
    ld e,a
    sub (ix+ramp_step)                ; the direction we are moving
    ld l,a                                       ;hl adjusted to head tile
    ld a,b                                      ; the wall tile value
    cp (hl)                                     ;wall tile?
    ret z                                        ;return path is blocked
;adjust back
    ld l,e
hor_or_dwn:
; move vertically and check the squares
; after the initial add hl,bc and possible head check
; this checks downwards in a line

;

;change logic if jumping downward, willies head can pass through wall block
    call jump_fix                                ;z=moving down
    ld a,b
    jr z,ignore_this                            ;zero indicates logic change-- ignore this tile
    cp (hl)                              ;head  ;wall tile?
    ret z                                            ;return path is blocked
; resume checking vertically down
ignore_this:
    ld e,$20                                      ;D is still 0
    ADD HL,DE                                 ;move hl down one row
;                                                      ;(y+0)  if Willy is on or about to up a ramp
;                                                      ;(y+1) if just walking or
;                                                      ;(y+2) if Willy is walking down a ramp
    CP (HL)                            ;foot    ;wall tile?
    RET Z                                         ;Return path is blocked
    LD A,(WILLY_Y)                         ;L85CF; y-coordinate (Y)
; bc held the ramp shift (b has been re-used but c is still the ramp shift)
    SRA C                                         ; halve the value from
                                                        ;c 32>16
                                                        ;c  0>0
                                                        ;c-32>-16  to get ramp y-shift
    ADD A,C                                      ; y-position + ramp y shift
    LD C,A                                         ; save in C
; this addition is changing logic depending on movement (jumping)
call jump_fix                                     ;z=moving down
; if jumping downward  this square MUST be checked
    jr z,force_this
; otherwise check if willy occupies 3 squares
    ld a,c
    AND $0F   ;
    JR Z,ONLY_TWO                         ;Jump if not
force_this:
    LD A,b                                          ;ATT wall tile
    ADD HL,DE                                  ;point hil at the cell one lower
; Addendum for the bottom of the screen... Jumping downward and cell aligned can process of the screen
; it is easier to capture/stop problems before they happen
    BIT 1,H
    JR NZ,offscreen
;                                                       ;(y+1)  if Willy is on or about to up a ramp
;                                                       ;(y+2) if just walking or
;                                                       ;(y+3) if Willy is walking down a ramp
    CP (HL)                        ;beneath ;Is there a wall tile down there?

    RET Z                                          ;return path is blocked
offscreen:
   OR A   ;
   SBC HL,DE                                     ; move back to feet position

;if here then Willy has been allowed to move up or down a ramp- walked along or jumped into a new attribute square.
; so save all the new parameters 
ONLY_TWO
    OR A
    ld e,(ix+ramp_final_adjust)
    SBC  HL,DE                                  ; back to final head position (free to move here)
    LD  (WILLY_ATT),HL                    ;L85D3;Save Willy's new attribute buffer coordinates

    LD A,(IX+ramp_animation_reset)  ; the animation frame
    LD  (WILLY_ANM),A                      ;L85D2

    LD  A,C                                         ;Willy's new pixel y-coordinate
    LD  (WILLY_Y),A ;L85CF

    RET
;-----------------------------------------
jump_fix:
    ld a,(JUMP)                                   ;L85d5
    sub 12
    ret c
    ld a,(AIRBORNE)
    dec a
    ret 
 

Edited by Norman Sword
Link to comment
Share on other sites

I have compiled various bits of code to try and achieve the same thing (in relation to the movement along ramps in the vicinity of wall tiles), but I haven't yet had a chance to sit down and put it all together into a working test file.

 

It will be interesting to see if it achieves the same thing as your code (assuming mine works!)

 

EDIT: Having now studied Norman's code, it seems to be addressing the same issues. However, I also had a notion to amend the routine at #95C8. Otherwise when Willy walks down a ramp and steps onto an adjacent wall/Earth block ( a la 'On top of the house'), and he's in a position where his front foot is on the wall but his back foot is still on the ramp, then his sprite will be drawn progressively 'sunken down' into the wall as his animation-frame progresses within the same pair of cell-columns - until he transcends the next cell-column boundary and moves beyond the influence of the ramp tile (at which point his sprite will appear to suddenly 'spring back up' onto the top of the wall tile, because the downwards vertical displacement normally caused by the ramp has been cancelled out in this case).

 

Therefore, I would propose to edit the start of the routine at #95C8 as follows (new elements in bold, relocated elements in italics):

 

LD HL, (#85D3) ; Willy's attribute coordinates

LD B, #00 ; default vertical offset (in case Willy is not on a ramp)

LD A, (#85D1) ; might as well place this earlier - if Willy is jumping then no need to test for underfoot ramp cell

OR A

JR NZ, vert_offset_dealt_with

 

LD A, (#80DA) ; Ramp direction byte

AND #01

LD C, A

ADD A, #40

LD E, A

LD D, B ; incidental one-byte efficiency here, compared with original code

ADD HL, DE

LD A, (#80C4) ; ramp attribute byte

CP (HL). ; Is Willy's 'up-slope' foot on a ramp cell?

JR NZ, vert_offset_dealt_with. ; If not, keep B=0

 

OR A

SBC HL, DE. ; restore Willy's attribute coordnates in HL

LD A,C ; A now holds either 0 or 1

XOR #01

ADD A, #40

XOR #41 ; I think this will do the same as the two operations struck-through above, but in one two-byte operation? ; (given the limited possible input values of A)

 

LD E,A ; D already holds 00

ADD HL,DE

LD A, (#80B2) ; Earth/wall attribute byte

CP (HL). ;Is Willy's 'down-slope' foot on an Earth/wall block?

JR Z, vert_offset_dealt_with. ; If so, keep B=0

 

 

That then leads into the code originally at #95E4, which calculates the vertical offset when Willy is standing on a ramp.

 

#95F8 is the jump destination labelled as vert_offset_dealt_with in the above.

Edited by IRF
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.