Jump to content
  • entries
    106
  • comments
    796
  • views
    139,607

Got it maid?

Sign in to follow this  
vdub_bobby

1,501 views

ELEVATOR REPAIRMAN, now kinda playable. Also rethemed a bit:

blog-6060-1160665829_thumb.png

ElevRep20061011.bin

 

Changes: I stopped trying to develop the 1K and 4K versions simultaneously and am now using a table to change the background color, rather than manipulating the line counter and plugging that into COLUBK. Also created a sprite. Also cleaned up the display a bit, tweaked the left/right boundaries.

The main changes, though: a) the score, b) the timer, and c) collisions. It's all very rough, but now all of the essential pieces are there - well, except for a game-over condition.

 

As you can see, I haven't thought of anything better than the hotel maid theme; please throw theme suggestions at me! I like what I've got alright, but if there's something better I want to know about it!

 

EDIT: Small update before the weekend:

blog-6060-1160777980_thumb.png

Just prettied up the text a little. Currently displaying about 200 visible lines, which is a bummer since I also wanted to have the title of the game and a small bitmap (~10 lines) above the play area, and ~215 visible lines is probably too many. Or is it?

ElevRep20061012.bin

Sign in to follow this  


28 Comments


Recommended Comments



So how will this game change between now and when the game is finished? How about implementing a life system, and once the timer gets to 0, the game doesn't end, but you just don't get any points for clearing the floor.

Share this comment


Link to comment

Interesting technique drawing sprites with the PF. (And I mean sprites in the more abstract sense, not P0 and P1.) Off the top of my head, I don't remember any other games that do this. The closest I can think of is Save Mary, but that game has only one PF sprite on a scanline.

Share this comment


Link to comment

So how will this game change between now and when the game is finished? How about implementing a life system, and once the timer gets to 0, the game doesn't end, but you just don't get any points for clearing the floor.

Good ideas! :lust:

 

That's exactly how Elevator Repairman is set up and I plan to use the same ideas.

 

Interesting technique drawing sprites with the PF. (And I mean sprites in the more abstract sense, not P0 and P1.) Off the top of my head, I don't remember any other games that do this. The closest I can think of is Save Mary, but that game has only one PF sprite on a scanline.

 

I used a similar technique to draw the paddles in my Pong demo: paddles were the PF, net was the ball, and the score was the players. And the ball was one of the missiles.

Share this comment


Link to comment

Very nice and pretty addicting. I hope you will also make a 1k version of it.

 

Any chances for adding the stairs? IMO the contrast should be increase (e.g. between the floors).

 

And why are the elevators striped?

Share this comment


Link to comment

Very nice and pretty addicting. I hope you will also make a 1k version of it.

I'll try, though almost certainly not until the 4K version is done.

Any chances for adding the stairs? IMO the contrast should be increase (e.g. between the floors).

And why are the elevators striped?

Taking these in order:

I assume by stairs you mean the stripes on alternating sides of the floors? Adding them would be tricky since it would be yet another asymmetrical playfield part. Maybe.

By contrast you mean the colors? I agree that the colors aren't especially good, I essentially chose them at random. They will be changed at some point.

As for why the elevators are striped, that's because I don't have time to write to all four PF registers every line.

But - I need to rewrite the kernel anyway because right now it doesn't support two independent sprites, which is kind of essential for a two-player game. So for a 4K game, especially since the graphics load is pretty small otherwise, I might go to a kernel that does this:

   lda (),Y
  ora (),Y
  sta PF1;+13

  lda (),Y
  ora (),Y
  sta PF2
  and #%10000000
  ora (),Y
  sta PF2;+23

  lda (),Y
  ora (),Y
  sta PF1;+13

I think I posted in the comments to the previous blog entry what I am doing currently. It takes about 12 cycles longer, total.

 

But still, that's 49 cycles per line; doesn't leave much time to draw the sprites - I think I've got to draw the elevators on alternating lines unless I go to 2-line-resolution sprites, which I don't want to do.

Share this comment


Link to comment

Got it Maid is clever! I like the player graphics too :lust:

 

Interesting technique drawing sprites with the PF. (And I mean sprites in the more abstract sense, not P0 and P1.) Off the top of my head, I don't remember any other games that do this. The closest I can think of is Save Mary, but that game has only one PF sprite on a scanline.

I believe the sprite for the player in Chopper Command is drawn using the playfield. I don't have the BIN on my MacBook, so I can't check it to make sure until I return home on Sunday.

Share this comment


Link to comment
But still, that's 49 cycles per line; doesn't leave much time to draw the sprites - I think I've got to draw the elevators on alternating lines unless I go to 2-line-resolution sprites, which I don't want to do.

 

What if you do something like this:

; Even scan line  (cycle counts are for END of instruction)
; Should start 9 cycles before visible display
lda temp1; -6
sta PF1
lax (ptr4),y
ora (ptr3),y
sta temp2; 10
sta PF2
txa
ora (ptr5),y; 20
sta temp3
sta PF2; Should end on cycle 26 of displayed screen
ldx temp4
stx PF1
; Odd scan line
 ; Should start 9 cycles before display
lda (ptr1),y
ora (ptr2),y
sta PF1; 1
sta temp1
lda temp2
sta PF2; 10
ldx temp3
lda (ptr6),y
ora (ptr7),y
stx PF2; 26
sta temp4
sta PF1

My cycle counts may be off, but I think that gets you down to 41 cycles/scan line. If you unrolled the loop 4X, you could save even more cycles. Note also, btw, that you may have to scramble the sprite shape data in ROM (as ET does) to get maximum benefit from unrolling loops, but doing so will also reduce the space such data requires.

 

BTW, another approach which would require a lot more code and RAM but would retain single-cycle motion of elevators would be to make a list of all the scan lines where the combination of elevators changes. That will happen at most 14 times per screen, so if each list holds a line count and the new elevator pattern, that would require 70 bytes, plus probably 5 more as a sentinel. A lot of RAM, but the kernel would save some precious cycles and would have single-line resolution:

; X counts which elevator row we're on
 lda PF1dat,x
 sta PF1
 lda PF2dat,x
 sta PF2
 lda PF4dat,x
 sta PF1
 lda PF3dat,x
 sta PF2
 tya
 cmp linecount,x
 bne skipinc ; Try to put a page crossing here
 inx
skipinc:

That's 4*(4+3)+6+2+2, or 38 cycles total.

Share this comment


Link to comment
I assume by stairs you mean the stripes on alternating sides of the floors? Adding them would be tricky since it would be yet another asymmetrical playfield part. Maybe.

 

What about using the Ball? That could be especially nice if you can spare the time for an HMOVE on every other scan line (make a mini staircase). Use Missile 1 (black) to cover up the "comb".

 

Another thing you might be able to do, btw, if you use something like the technique I just mentioned to save enough cycles and yet still keep the alternating-line elevators, would be to have a two player game with two dual-color figures per scan line.

 

Use Missile 1 (black) for the black parts of the first maid and Player 0 for the colored parts. Use Player 1 (black) for the black parts of the second maid and the Ball for the colored parts. Some slight changes to the second maid's design would be required, but nothing outrageous. Having two independently-horizontally-mobile two-color objects on a row with all those elevators would be quite a trick for the 2600.

Share this comment


Link to comment

I like it! :lust:

 

Looks like you beat me to it! I've always liked this game and Spy's Demise and was hoping to try to program a variation of it in bBasic. If I didn't have damn school always taking up my time! :D :D

 

My initial thought was to use P0 and P1 , copied 3 times each for the elevators, but I admit I haven't explored it much beyond that( no time ).

 

Good luck! :D

Share this comment


Link to comment

Nice little game! The "S" character in "MAIDS" in the last screenshot looks a bit thin - perhaps you could use a row of six maid icons instead of a number to illustrate the lives remaining?

 

Chris

Share this comment


Link to comment
BTW, another approach which would require a lot more code and RAM but would retain single-cycle motion of elevators would be to make a list of all the scan lines where the combination of elevators changes. That will happen at most 14 times per screen, so if each list holds a line count and the new elevator pattern, that would require 70 bytes, plus probably 5 more as a sentinel. A lot of RAM, but the kernel would save some precious cycles and would have single-line resolution:

This is the method I've decided to go with, as it turns out - I sure hope you're right about the max number of elevator combination changes!

 

I'm still working out the details of the kernel and the code that will populate those 70+ bytes of RAM; hopefully I'll have something soon. :lust:

 

Thanks for the idea, John.

Share this comment


Link to comment

This is the method I've decided to go with, as it turns out - I sure hope you're right about the max number of elevator combination changes!

 

Glad you like the idea. It makes the kernel simple, but is apt to severely complicate your game action processing. With efficient code you should be able to get stuff done in overscan or vblank, but it could be a little tight.

 

You have seven elevators, each with one top and one bottom. Since the playfield pattern can only change at the top or bottom of an elevator, that implies it can change at most fourteen times. There are actually fifteen zones, though the top and bottom zones don't have any elevators in them. If you special-case both top and bottom, you'll save eight bytes of RAM; if you special-case one, you'll save four. If you special-case neither, you'll need 75 bytes of RAM instead of 70.

 

The toughest part of the game is going to be managing to produce the list of scan line changes. You won't have a whole lot of extra RAM to play with, after all. I think one of the first things you'll have to answer is whether the elevators attributes should be stored in fixed order left to right, or if the elevaotrs should be maintained in a list that gets shuffled to remain in vertical order. The latter is apt to be faster but require more RAM. Given the RAM constraints, I'd suggest the former if possible.

 

If you use 1.75 bytes for each elevator (one byte for position, and 6 bits to hold speed and low position), then the sort loop might be something like:

elevator_scan:
 ldy #0
elevator_scan_lp:
; Find elevator with lowest Y position
 lda #191
 tax
 cmp pos0
 bcc not_0
 ldx #0
 lda pos0
not_0:
 cmp pos1
 bcc not_1
 ldx #1
 lda pos1
not_1:
 cmp pos2
 bcc not_2
 ldx #2
 lda pos2
not_2:
 ...
not_7:
; X indicates lowest elevator.  Are we at the top or bottom of it?
 txa
 bmi all_done
 lda pos,x
 sta temp
 lda info,x
 bmi not_top
 ora #$80
 sta info,x
 lda pos,x
 clc
 adc #$10 ; Elevator height
 sta pos1,x
 bne handle_elevator
not_top:
 ora #$C0
 sta pos,x
 lda temp
 sta info,x
handle_elevator:
 tya
 beq first_line
 lda temp
 cmp linechange,y
 beq no_new_line
 sta linechange+1,y
 lda PF0dat,y
 eor PF0change,x
 sta PF0dat+1,y
 lda PF1dat,y
 eor PF1change,x
 sta PF1dat+1,y
 lda PF2dat,y
 eor PF2change,x
 sta PF2dat+1,y
 lda PF3dat,y
 eor PF3change,x
 sta PF3dat+1,y
 iny
 bne elevator_scan_lp
no_new_line:
 lda PF0dat,y
 eor PF0change,x
 sta PF0dat,y
 lda PF1dat,y
 eor PF1change,x
 sta PF1dat,y
 lda PF2dat,y
 eor PF2change,x
 sta PF2dat,y
 lda PF3dat,y
 eor PF3change,x
 sta PF3dat,y
 jmp elevator_scan_lp
first_line:
 lda PF0change,x
 sta PF0dat
 lda PF1change,x
 sta PF1dat
 lda PF2change,x
 sta PF2dat
 lda PF3change,x
 sta PF3dat
 iny
 bne elevator_scan_lp
all_done:

The idea is that it scans for the elevator with the lowest-numbered position (must be <192). If there isn't any, it quits. When such an elevator is found, it's first checked to see if it's been done yet. If not, its "done-yet" flag is set and its position is increased by 16. This will cause it to appear again 16 lines later. Once the elevator is found the second time, its position and flags values will be swapped (making the position value now >=192).

 

Whether the elevator is found the first or second time, its scan line is checked against the last display-list item. If they match, the playfield values in the last item are altered instead of creating a new one. Otherwise a new display-list item is created.

 

The 'find the minimum' bit is a bit mungy (you need to copy it out once for each elevator) but I would expect things should still run decently.

Share this comment


Link to comment
Glad you like the idea. It makes the kernel simple, but is apt to severely complicate your game action processing. With efficient code you should be able to get stuff done in overscan or vblank, but it could be a little tight.

 

You have seven elevators, each with one top and one bottom. Since the playfield pattern can only change at the top or bottom of an elevator, that implies it can change at most fourteen times. There are actually fifteen zones, though the top and bottom zones don't have any elevators in them. If you special-case both top and bottom, you'll save eight bytes of RAM; if you special-case one, you'll save four. If you special-case neither, you'll need 75 bytes of RAM instead of 70.

My plan now is to special-case neither the top nor the bottom and use 75 bytes of RAM. That isn't really excessive, at least not for any of my other projects! Go Fish! used up around 56 bytes for the enemy fish, Reindeer Rescue used something like 60 bytes for the scrolling playfield, M-4 used 40 bytes for the walls plus I had to keep around 30 bytes free for the scoreboard. My Metroid demo uses 60 bytes for the playfield plus 3 bytes per enemy allowed on screen (in the intelligent flicker routine).

 

Every time I start a project I think, "this idea doesn't have excessive RAM requirements, this time I won't have to squeeze value out of every single stinking bit," - but every time I do. :lust: :D

 

The toughest part of the game is going to be managing to produce the list of scan line changes. You won't have a whole lot of extra RAM to play with, after all. I think one of the first things you'll have to answer is whether the elevators attributes should be stored in fixed order left to right, or if the elevaotrs should be maintained in a list that gets shuffled to remain in vertical order. The latter is apt to be faster but require more RAM. Given the RAM constraints, I'd suggest the former if possible.

I was a bit daunted by this part at first, but upon further thought I don't think it will be that hard - the key will be getting it done fast enough, as you pointed out.

Share this comment


Link to comment

My plan now is to special-case neither the top nor the bottom and use 75 bytes of RAM. That isn't really excessive, at least not for any of my other projects! Go Fish! used up around 56 bytes for the enemy fish, Reindeer Rescue used something like 60 bytes for the scrolling playfield, M-4 used 40 bytes for the walls plus I had to keep around 30 bytes free for the scoreboard. My Metroid demo uses 60 bytes for the playfield plus 3 bytes per enemy allowed on screen (in the intelligent flicker routine).

 

Well, the routine I sketched out assumes you special-case one edge. The other edge isn't affected by the sort routine so it can just be ignored. Did it make sense to you? There aren't a whole lot of comments there.

 

Every time I start a project I think, "this idea doesn't have excessive RAM requirements, this time I won't have to squeeze value out of every single stinking bit," - but every time I do. :lust: :D

 

If you're not having to squeeze, you're not using the 2600 to its full potential. Why only seven elevators--why not ten? They'd fit, and they wouldn't slow down the kernel at all. RAM would be a bit tight (116 bytes for elevators alone, assuming you special-case the top and bottom) but it'd be doable. Almost.

 

Ten elevators wouldn't be practical. But eight or nine might be. Each extra elevator would require twelve bytes of RAM (two more 5-byte display-list entries and two for motion information. Since the limit on the number of elevators is the amount of RAM, the more you save the more elevators you can have.

Share this comment


Link to comment
If you're not having to squeeze, you're not using the 2600 to its full potential. Why only seven elevators--why not ten? They'd fit, and they wouldn't slow down the kernel at all. RAM would be a bit tight (116 bytes for elevators alone, assuming you special-case the top and bottom) but it'd be doable. Almost.

1. It would not be possible to make the game work with only 8 free bytes for sprites, score, timers, etc.

 

2. It would slow the kernel down since then I would have to update PF0 twice every line.

 

3. Besides, 10 elevators would not fit. 8 free pixels on either side plus 10 elevators at 8 pixels each plus 9 blank spaces between them at 8 pixels each would be 168 pixels. Too wide. 9 elevators would fit.

Ten elevators wouldn't be practical. But eight or nine might be. Each extra elevator would require twelve bytes of RAM (two more 5-byte display-list entries and two for motion information. Since the limit on the number of elevators is the amount of RAM, the more you save the more elevators you can have.

If I used 9 elevators, besides having to then update PF0 and, consequently, probably losing a multicolored sprite and/or two sprites, it would require that either there be no L/R borders or the elevators would have to be equally spaced. Both choices would make the game look worse, IMO.

 

Also: I need more than just two bytes of RAM for motion information since I am using fractional positioning, so I need two bytes for position and one byte for speed. Though maybe...maybe I could use 1 byte for position and fractional position. Have to look into that.

 

But anyway - I've got the kernel mostly working and the routine that sets up all the elevator RAM working - but it takes too, too long. The routine that sets up all the RAM takes ~60 scanlines. :lol:

 

I'm working on optimizing the routine, but I don't think I can optimize it in half - we'll see :ponder: - so I am trying to come up with a different approach to the problem.

Share this comment


Link to comment
I'm working on optimizing the routine, but I don't think I can optimize it in half - we'll see :ponder: - so I am trying to come up with a different approach to the problem.

 

Can you post it?

Share this comment


Link to comment
I'm working on optimizing the routine, but I don't think I can optimize it in half - we'll see :ponder: - so I am trying to come up with a different approach to the problem.

 

Can you post it?

Maybe tomorrow, or tonight - I don't have it with me right now.

Share this comment


Link to comment

Also: I need more than just two bytes of RAM for motion information since I am using fractional positioning, so I need two bytes for position and one byte for speed. Though maybe...maybe I could use 1 byte for position and fractional position. Have to look into that.

 

If an object will be maintaining a constant fractional speed, there's no need to store fractional position. Instead, you can use a lookup table to determine what the object should do on each frame.

 

For example, suppose you want to allow sixteen speeds: 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, 5/6, 1, 1 1/6, 1 1/4, 1 1/3, 1 1/2, 1 2/3, 1 3/4, 1 5/6, 2. You need four bits in RAM to select a speed, and one four-bit counter which is common to all objects. The counter will count repeatedly from 0 to 11.

 

The counter is used to read a byte from a ROM table, and the lower three bits of the speed selector are used to select a bit from that byte. If the selected bit is set, the object moves. If the top bit of the speed is set, the object moves (another) pixel regardless.

 

This approach will not work well if objects can change speed during continuous motion (it's no problem if objects can change speed when they 'bounce'). Otherwise, though, it's a very nice approach since it minimizes RAM requirements.

 

But anyway - I've got the kernel mostly working and the routine that sets up all the elevator RAM working - but it takes too, too long. The routine that sets up all the RAM takes ~60 scanlines. :ponder:

 

Did you set it up anything like I illustrated above?

Share this comment


Link to comment

Also: I need more than just two bytes of RAM for motion information since I am using fractional positioning, so I need two bytes for position and one byte for speed. Though maybe...maybe I could use 1 byte for position and fractional position. Have to look into that.

 

If an object will be maintaining a constant fractional speed, there's no need to store fractional position. Instead, you can use a lookup table to determine what the object should do on each frame.

 

For example, suppose you want to allow sixteen speeds: 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, 5/6, 1, 1 1/6, 1 1/4, 1 1/3, 1 1/2, 1 2/3, 1 3/4, 1 5/6, 2. You need four bits in RAM to select a speed, and one four-bit counter which is common to all objects. The counter will count repeatedly from 0 to 11.

 

The counter is used to read a byte from a ROM table, and the lower three bits of the speed selector are used to select a bit from that byte. If the selected bit is set, the object moves. If the top bit of the speed is set, the object moves (another) pixel regardless.

 

This approach will not work well if objects can change speed during continuous motion (it's no problem if objects can change speed when they 'bounce'). Otherwise, though, it's a very nice approach since it minimizes RAM requirements.

That's an interesting idea...I'll look into that. But 16 different speeds is way, way too few. :lol:

But anyway - I've got the kernel mostly working and the routine that sets up all the elevator RAM working - but it takes too, too long. The routine that sets up all the RAM takes ~60 scanlines. :ponder:

 

Did you set it up anything like I illustrated above?

I'll post my code in a few minutes. It's similar, I think.

Share this comment


Link to comment
If an object will be maintaining a constant fractional speed, there's no need to store fractional position. Instead, you can use a lookup table to determine what the object should do on each frame.

 

For example, suppose you want to allow sixteen speeds: 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, 5/6, 1, 1 1/6, 1 1/4, 1 1/3, 1 1/2, 1 2/3, 1 3/4, 1 5/6, 2. You need four bits in RAM to select a speed, and one four-bit counter which is common to all objects. The counter will count repeatedly from 0 to 11.

 

The counter is used to read a byte from a ROM table, and the lower three bits of the speed selector are used to select a bit from that byte. If the selected bit is set, the object moves. If the top bit of the speed is set, the object moves (another) pixel regardless.

I'm having a hard time understanding this - and I need to extend it to 64 speeds. How do you set up the ROM table? And why does the counter run from 0 to 11? And what if the fastest speed is greater than 2?

 

Thanks. :ponder:

Share this comment


Link to comment
The counter is used to read a byte from a ROM table, and the lower three bits of the speed selector are used to select a bit from that byte. If the selected bit is set, the object moves. If the top bit of the speed is set, the object moves (another) pixel regardless.

I'm having a hard time understanding this - and I need to extend it to 64 speeds. How do you set up the ROM table? And why does the counter run from 0 to 11? And what if the fastest speed is greater than 2?

 

Basically, the idea is that your speed is some integer value plus one of eight fractions. All fractions must divide evenly into the size of your table. I used a 12-entry table, so allowable fractions would be 0, 1/12, 1/6, 1/4, 1/3, 5/12, 1/2, 7/12, 2/3, 3/4, 5/6, 11/12, or 1. You need to pick eight of those.

 

The simplest way to think of this approach is to imagine that you have a roller with eight columns marked on it (each column goes around the drum). Similar to the control cam on a washing machine or dishwasher. Each column has one or more pegs. To determine when to bump the elevator an extra step, put a sensor under the column and start the roller turning. If a column has one peg in it, the sensor will get tripped once per revolution. If the column has four pegs, the sensor will get tripped four times per revolution. If the drum turns 1/12 rotation per frame and a column has four equally-spaced pegs, one peg will be hit every third frame.

 

The one danger with this approach is that a significant fraction of a count may be gained or lost any time you switch columns. For example, suppose one column has two pegs and another column has three pegs. There's one spot where the pegs in both columns line up. If you were to consistently switch columns immediately before you're going to encounter a peg, you'd only count 1 peg per revolution even though you should count at least two. Likewise if you were to constently switch to a column with a peg before it goes buy, you'd count 4 pegs per revolution even though you should count at most three. If you don't allow elevators to change speeds except when reversing direction, though, that won't be a problem.

Share this comment


Link to comment

Thanks a bunch. I'll try to digest this and put it to use tonight. :ponder:

 

EDIT: And the elevators will not change speed except when reversing direction except between levels. And if it looks bad or causes other problems I can always wait until a bounce.

Share this comment


Link to comment

Guest
Add a comment...

×   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...