WIP: Wizard of Wor

A place where you can keep others updated about your NES-related projects through screenshots, videos or information in general.

Moderator: Moderators

User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: WIP: Wizard of Wor

Post by Myask » Tue Sep 05, 2017 9:37 pm

How is it generating stamps[STAMP_Y(i)]? The thing I suspect is happening is that that equality stops being true after a WORRIOR has moved one pixel up, so it tries to handle their movement as though normally in-bounds instead of walking into the playfield, and says "hey you're outside where you should be" and clips the WORRIOR's Y, causing the other 23 to happen all at once.

This would happen if you're doing a modulo-24 to get "what's the current cell", as one guess.
rainWORRIOR wrote: The only thing that's maybe weird to me is if I press up just a little bit then stop, now pressing either left or right will move me upwards until the next grid, which feels a little weird, but maybe not something that needs to be addressed at all. (It's easy to learn this quirk, and it resolves itself pretty quickly anyway.)
I guess he took my suggestion.
Last edited by Myask on Tue Sep 05, 2017 10:25 pm, edited 1 time in total.

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Tue Sep 05, 2017 10:20 pm

The macros that generate the appropriate data are here:

Code: Select all

/**
 * 8 objects on screen, two players and 6 enemies.
 */
#define STAMP_NUM_FIELDS     9                    // Number of fields in each stamp slot
#define STAMP_NUM_SLOTS      8                    // Number of slots in stamp structure
#define STAMP_CENTER_BIAS_X  12                   // Offset to apply to box multiply to center sprite (X)
#define STAMP_CENTER_BIAS_Y  10                   // Offset to apply to box multiply to center sprite (Y)

#define RADAR_SPR_OFFSET_X   88                   // Radar sprite top-left offset X
#define RADAR_SPR_OFFSET_Y   176                  // Radar sprite top-left offset Y

#define STAMP_NUM(x)         (x*STAMP_NUM_FIELDS) // Stamp Number
#define STAMP_X(x)           (STAMP_NUM(x)+0)     // Stamp Field: X pixel position
#define STAMP_Y(x)           (STAMP_NUM(x)+1)     // Stamp Field: Y pixel position
#define STAMP_TYPE(x)        (STAMP_NUM(x)+2)     // Stamp Field: Type
#define STAMP_STATE(x)       (STAMP_NUM(x)+3)     // Stamp Field: state (which frames to use).
#define STAMP_LAST_STATE(x)  (STAMP_NUM(x)+4)
#define STAMP_FRAME(x)       (STAMP_NUM(x)+5)     // Stamp Field: Current frame
#define STAMP_DELAY(x)       (STAMP_NUM(x)+6)     // Stamp Field: Delay
#define STAMP_XTRA_A(x)      (STAMP_NUM(x)+7)     // Stamp Field: Extra A (Player Timer)
#define STAMP_XTRA_B(x)      (STAMP_NUM(x)+8)     // Stamp Field: Extra B (Player Pad Data)

#define PLAYER_PAD(x)        (stamps[STAMP_XTRA_B(x)])    // Alias for reading stored player pad value.
#define PLAYER_PAD_RIGHT(x)  (PLAYER_PAD(x)&1<<0) // is player pressing right?
#define PLAYER_PAD_LEFT(x)   (PLAYER_PAD(x)&1<<1) // is player pressing left?
#define PLAYER_PAD_DOWN(x)   (PLAYER_PAD(x)&1<<2) // is player pressing down?
#define PLAYER_PAD_UP(x)     (PLAYER_PAD(x)&1<<3) // is player pressing up?
#define PLAYER_PAD_IDLE(x)   (PLAYER_PAD(x)==0x00)      // is player idle?

#define PIXEL_BOX_X(x)       ((x*24)+STAMP_CENTER_BIAS_X)             // Convert Box X coordinates to pixels
#define PIXEL_BOX_Y(x)       ((x*24)+STAMP_CENTER_BIAS_Y)             // Convert Box Y coordinates to pixels
#define BOX_PIXEL_X(x)       (div24(x-STAMP_CENTER_BIAS_X))           // Convert Stamp X coordinates to Box X
#define BOX_PIXEL_Y(x)       (div24(x-STAMP_CENTER_BIAS_Y))           // Convert Stamp Y coordinates to Box Y

#define STAMP_X_TO_RADAR(x)  RADAR_SPR_OFFSET_X+BOX_PIXEL_X(x)*8          // Convert box position to radar sprite position
#define STAMP_Y_TO_RADAR(x)  RADAR_SPR_OFFSET_Y+BOX_PIXEL_Y(x)*8          // Convert box position to radar sprite position

#define BOX_WALL_RIGHT(x)    (x&1<<4)            // Box has right wall
#define BOX_WALL_DOWN(x)     (x&1<<5)            // Box has down wall
#define BOX_WALL_LEFT(x)     (x&1<<6)            // Box has left wall
#define BOX_WALL_UP(x)       (x&1<<7)            // Box has up wall

User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: WIP: Wizard of Wor

Post by Myask » Tue Sep 05, 2017 10:30 pm

if handle_player_in_box(); is supposed to handle the entire walk out of the box, it's not got the right condition on it; it should be something more like if (stamps[STAMP_Y(i)]>PIXEL_BOX_Y(5)) (with a +1 in there on the RHS if ejection is how you handle walls rather than prevention)

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Tue Sep 05, 2017 10:32 pm

handle_player_in_box is the routine that handles when the player is about to be ejected onto the playfield (with the countdown).

handle_player_in_field is used for everything else.

I may rename the former routine, if I can think of a better name for it... I was thinking "penalty box" like hockey :P ;)

-Thom

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Tue Sep 05, 2017 10:39 pm

to expand a bit, the macro PIXEL_BOX_Y takes a box # and converts it to a pixel offset (for sprite coordinates). Y=6 is a special box location that is only shared by the two penalty boxes.

STAMP_X and STAMP_Y are sprite pixel coordinates.

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Tue Sep 05, 2017 10:56 pm

other bits of interesting code, handle_pad_idle() which handles when player motion stops:

Code: Select all

/**
 * handle_pad_idle()
 */
void handle_pad_idle(void)
{
  // Change to idle state if pad is idle.
  switch(stamps[STAMP_LAST_STATE(i)])
    {
    case STATE_PLAYER_RIGHT:
      stamps[STAMP_STATE(i)]=STATE_PLAYER_RIGHT_IDLE;
      break;
    case STATE_PLAYER_LEFT:
      stamps[STAMP_STATE(i)]=STATE_PLAYER_LEFT_IDLE;
      break;
    case STATE_PLAYER_UP:
      stamps[STAMP_STATE(i)]=STATE_PLAYER_UP_IDLE;
      break;
    case STATE_PLAYER_DOWN:
      stamps[STAMP_STATE(i)]=STATE_PLAYER_DOWN_IDLE;
      break;
    }
}
and handle_player_in_box():

Code: Select all

/**
 * handle_player_in_box()
 * Handle when player is in box.
 */
void handle_player_in_box(void)
{
  if (stamps[STAMP_XTRA_A(i)]>0)
    {
      if (stamps[STAMP_XTRA_B(i)] != 0)
        {
          stamps[STAMP_XTRA_A(i)]=0;
        }
      else
        {
          stamps[STAMP_Y(i)]=PIXEL_BOX_Y(6)-1; // 6 is the Y for the box.
          if (sec==0) // 0 means approximately 1 second elapsed.
            stamps[STAMP_XTRA_A(i)]--;         // Decrement timer
        }
    }
  else
    {
      stamps[STAMP_Y(i)]=PIXEL_BOX_Y(5); // Pop out of box.
      if (i==0)
        {
          yellow_door_state=CLOSED;
        }
      else
        {
          blue_door_state=CLOSED;
        }
    }
}
sec is a variable that simply counts from 49 to 0 (I am using shiru's drop-frame consistent PAL tick rate).

get_current_box() is a function that sets a quad of variables, a,b,c,d:

Code: Select all

/**
 * get_current_box()
 * Get the current dungeon box for player
 * i = the stamp to return in a,b,c,d
 * a = the X box
 * b = the Y box
 * c = the dungeon box #
 * d = the box data.
 */
void get_current_box(void)
{
  a=div24(stamps[STAMP_X(i)]+8);
  b=div24(stamps[STAMP_Y(i)]+8);
  c=(b*10)+a; // C is now the box #
  d=dungeon[c];
  score1[0]=div24(stamps[STAMP_X(0)]+8)+1;
  score1[1]=div24(stamps[STAMP_Y(0)]+8)+1;
}
These are then used for collision detection. Since the whole maze is on a grid, and everything is bound to move along that grid, full bounding box collision detection is unnecessary for traversing the maze. (player to player/enemy detection will be another story)

I will also change the move_monsters() code to utilize the same code (with the exception that directions will be picked randomly), so that monster movement isn't so pedestrian. (Right now, monster movement is very rank and file).

It will also make computer AI easier, as all it needs to do is pick the closest monster, and attempt a straight line to it, while shooting when it's in the same lateral position on the grid.

Yes, I KNOW this code is really weird. I've never written C code that uses this many globals or goto's (and I've written CODECs!)... but you can't really write clean C code for a 6502 and have it fit or be somewhat efficient.

-Thom

User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: WIP: Wizard of Wor

Post by Myask » Tue Sep 05, 2017 11:14 pm

oh, so the WORRIOR *is* supposed to instantly pop out of the box into the middle of the adjacent map cell? I thought they were supposed to walk in.

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Tue Sep 05, 2017 11:24 pm

Myask wrote:oh, so the WORRIOR *is* supposed to instantly pop out of the box into the middle of the adjacent map cell? I thought they were supposed to walk in.
You can witness the behavior by watching the arcade longplays on YouTube.

-Thom

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: WIP: Wizard of Wor

Post by thefox » Wed Sep 06, 2017 9:17 am

tschak909 wrote:

Code: Select all

#define STAMP_NUM(x)         (x*STAMP_NUM_FIELDS) // Stamp Number
Side note: It's a good idea to get into habit of adding extra parenthesis around macro variables:

Code: Select all

#define STAMP_NUM(x)         ((x)*STAMP_NUM_FIELDS) // Stamp Number
Otherwise you can run into really nasty bugs with things like STAMP_NUM(1+2) (expands to 1+2*STAMP_NUM_FIELDS).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Wed Sep 06, 2017 9:56 am

Ah yes, good idea. :)

-Thom

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Fri Sep 08, 2017 10:40 pm

Still debugging the glitch that Kasumi found, this one is a quantum bug, as it hides when being explicitly observed! :P

It's definitely a problem, and I'm suspecting that my i variable is being reset intermittently (and I mean very infrequently, I have yet to find a consistently reproducible pattern that causes this behavior!)

-Thom

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Sat Sep 09, 2017 9:23 pm

Narrowed it down to the following bit of conditions:

* pressing up or down
* in a horizontal corridor (last state = left | right)
* state goes from not aligned to aligned

this one is weiird. I wonder what i'm doing to cause this funkiness?

-Thom

User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: WIP: Wizard of Wor

Post by Myask » Mon Sep 11, 2017 2:18 pm

tschak909 wrote:
Myask wrote:oh, so the WORRIOR *is* supposed to instantly pop out of the box into the middle of the adjacent map cell? I thought they were supposed to walk in.
You can witness the behavior by watching the arcade longplays on YouTube.

-Thom
Having looked at such…
The arcade game slides the WORRIOR into the playfield over ~0.1s (at least, 3 @youtube's 30FPS*, each with a new interpolated Y-value, so 6 frames on NTSC NES). Your version does not; it waits three frames after an input and then instantly moves the WORRIOR from inside the bullpen to the adjacent maze square.

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Mon Sep 11, 2017 11:08 pm

Noted. I will try to refine this once I figure out WTF is going on with player 2 affecting player 1 motion.

-Thom

tschak909
Posts: 138
Joined: Mon Jul 03, 2017 4:37 pm
Contact:

Re: WIP: Wizard of Wor

Post by tschak909 » Sat Sep 16, 2017 3:08 pm

To anyone here, could use a second pair of eyes.

Man, this is bonkers, have been trying to debug the errant motion, and while I can reproduce it, I can't narrow down the errant code...

(for reference, code is here: http://github.com/tschak909/wow.git)

If I do a diagonal motion, then the moment that I enter alignment into a given box, the yellow player will turn and idle in the selected vertical direction. This is downright goofy, as I do not modify the current player index at any point inside the loop, and yet, somehow i becomes 0 even when i is 1, and suddenly snaps back to 1.

I know this is because I am abusing globals, I'm trying to keep code generation down by not putting things in the stack frame...

-Thom

Post Reply