Jump to content

Pas de Deux (was: Duet) anyone?

Recommended Posts

I did not know this mobile game until lately. It soon becomes really hard, but I think it might be a good candidate for a 2600 game.

A bit easier, without the bells and whistles and probably rotated by 90°, it seems quite doable.

Duet (V0.1) (Thomas Jentzsch).bin

Duet (V0.2) (Thomas Jentzsch).bin

Duet (V0.3) (Thomas Jentzsch).bin

Pas De Deux (V0.4).bin

Pas de Deux (V0.5) (PAL60).bin Pas de Deux (V0.5) (NTSC).bin

Edited by Thomas Jentzsch
  • Like 12
  • Thanks 1
Link to comment
Share on other sites

24 minutes ago, DeafAtariFromKansas said:

Thanks for pointing me at it. I must have missed (or forgotten?) it. 


Anyway, I think there is room for another version. Maybe with DC support. :) 

Edited by Thomas Jentzsch
  • Like 2
Link to comment
Share on other sites

So I started wondering how one could do this game on the 2600. For doing that, I analyzed the original (mostly by watching YouTube videos). It is obvious that the 2600 cannot do the nice background animation and phosphor effects, so these bells and whistles have to go. We will have to concentrate on the pure gameplay instead (maybe with some music, we'll see). I noted all the obstacles and how they behave. There are not that many obstacle types, just 7 non-rotating and two 2 rotating rectangles. However there can be up to 8 on the screen at the same time (all non-rotating types). Since I didn't want to use flicker, this soon ruled out horizontal scrolling, even though that would have fit better to the 2600 screen orientation. So I decided for vertical scrolling, which also has a better resolution than horizontal scrolling. 


Now, how should I display them? At the bottom the obstacles are vertically spaced, else you couldn't get past them. But at the top they are not. Also I wanted to use colors for the player's rotating orbs. So obstacles cannot be sprites (or can they?😉). Fortunately the non-rotating obstacles can be nicely represented by using the playfield. This will allow multiple obstacles overlapping. I decided for a 15 playfield pixel wide playfield at the center and scaled everything accordingly. This size can be done with a reflected playfield and just 2, well timed writes to PF2. 


The code for this is pretty simple, you just OR the data together, separately for left and right PF2:

  lda (pfPtr0L),y  ; 5
  ora (pfPtr1L),y  ; 5
  ora (pfPtr2L),y  ; 5
  sta PF2          ; 3
  lda (pfPtr0R),y  ; 5
  ora (pfPtr1R),y  ; 5
  ora (pfPtr2R),y  ; 5
  sta PF2          ; 3	@48!

The non-rotating obstacles can be moving down at different speeds, which is no problem with this kernel code. However, in later levels, some can also move horizontally! This would look ugly when using the playfield. And then we have the rotating obstacles. So I have to mix sprites into it too. Later...


The next problem to overcome is the amount of space used for the obstacles data. Since I simply OR the data together, no matter where the obstacles are vertically placed, this needs padding 0s below and above the actually graphics data. For a 200 pixel tall kernel, I would need 199 0s before and after each obstacle data. This would eat up a lot of space and also cause page faults. For now I decided to split the kernel into 3 sections, with the bottom kernel containing the player's orbs. I know this will cause me quite some trouble with pointer handling (I hope I am not running out of CPU time or RAM there), but currently it seems like the best solution/compromise.


Now, since I have to use sprites, how can the player's orbs have a different color? There cannot be white obstacle sprites and red or blue orb missiles at the same scanline. Also, there seems to be no time for repositioning sprites during the kernel. The first decision to solve the problem was to draw the orbs using missiles. Also I will limit the number of horizontally moving obstacles on screen to 2. This completely solves the repositioning problem. And for allowing horizontally moving obstacles besides the orbs, I plan to split the bottom kernel into three kernels. Since the orbs can only overlap when they are (mostly) horizontal positioned, the top and the bottom areas will always have only one orb. This will allow for one horizontally moving obstacle (sprite). And in the middle area, where no sprites are allowed, I will make sure that the obstacles are not moving. If you look closely to the original game, it also pauses horizontal movement for a moment when an obstacle reaches its leftmost or rightmost position. So that's what I am copying here, just a bit more restricted.


The original game has a nice circle, which visualizes the movement area of the two orbs. I am not exactly sure if and how I should replicate that. Currently I have implemented two solutions (one is flickering and one is just a dot), which you can switch using left difficulty. Both use the ball, which is nice because it shares its color with the obstacles. 


Now to the orbs:


I wanted the orbs to be not square, therefore using the missiles for them requires writing to ENAMx at the right spot plus extra NUSIZx and HMMx updates. Since I still have to update the playfield for at least two obstacles, the missile code has to be fast. Reading the data separately would require three large tables (again with a lot of padding bytes) and 5 cycles per read. But I knew that all the bits required for the 3 TIA registers would fit into byte. So I had to come up with some fast code here. Here is how it looks for one orb:

    lda (dot0Ptr),y  ; 5 
    sta NUSIZ0       ; 3
    adc #$e7         ; 2
    sta HMM0         ; 3
    sta ENAM0        ; 3 = 16   

So that's 16 vs. 24 cycles required when reading the data individually. Good.


And this is the data:

    ds      PADDING_H, $1d	; + $e7 = $04
    .byte   $17 		; + $e7 = $fe   
    .byte   $27 		; + $e7 = $0e   
    .byte   $27 		; + $e7 = $0e
    .byte   $27 		; + $e7 = $0e
    .byte   $27 		; + $e7 = $0e
    .byte   $2f 		; + $e7 = $16   
    .byte   $1f 		; + $e7 = $06   
    ds      PADDING_H, $1d	; + $e7 = $04

The orb is 2 pixel wide at top and bottom, and 4 pixel wide in the middle 5 rows. 


But how does it work and why do I use these really odd numbers? Well, I had to consider quite some constraints:

- avoid CLC for the ADC

- avoid values for NUSIZx which create repeated sprites (and missiles!)

- have bit 2 always set for ENAMx while the orb is drawn, but have the bit always clear while the orb is not drawn

- get the correct HMMx values while the orb is drawn, but do not move while it is not drawn


If you check each value for NUSIZx and the resulting value for HMMx and ENAMx, you will find that all constraints are met.


Since none of the TIA registers are delayed, the timing is also critical. Due to the narrow area I am using for the game, I have some extra CPU time after the orbs have been drawn. That's when I update their TIA registers. However I must not update the right orb too early, else it will look garbled. Therefore my code makes sure that the rightmost orb is always drawn using missile 1 (which is updated last). This only barely works, but who cares? :) 


You may notice that the orbs are a bit smaller than in the original. This is (also) because I want to use pixel perfect hardware collisions. The original game is not that strict, therefore the slightly smaller orbs should compensate that.


As of now, I have only done the bottom kernel. It is not split yet, so it cannot do horizontally moving or rotating obstacles. I first want to figure out how to organize the pointers at the top kernels. Anyway, here is the current result. There is no collision detection, just movable orbs (joystick left and right) and randomly created obstacles moving down at 3 different speeds. Every ~4 seconds, the game speed increases by 1/32 (it will become insane eventually).


There are only ~1750 bytes free with top kernels and lots of code still missing. Therefore I already strongly doubt that I can do the game in 4K, but I will keep trying. :) 


BTW: If you use high phosphor (~75%) in Stella, you can get a bit closer to the original look. But that's cheating. :D 

Duet (V0.01) (Thomas Jentzsch).bin

Edited by Thomas Jentzsch
  • Like 5
Link to comment
Share on other sites

I made some progress over the last days and created version 0.1 (see 1st post):

- all kernels are done

- the code can handle up to 6 obstacles at a time

- the obstacle generation follows some rules (there is still room for improvement)

- added collision detection with some basic sound


So there now is at least some gameplay.


Next planned steps:

- add falling obstacles (they move into their final vertical position close to middle of the orb area)

- allowing disappearing obstacles (I have some working code, but it needs refinement)

- make use of the sprites (rotating and horizontally moving obstacles)

- create rounds, where the player has to master a number of obstacles without any collisions to get to the next round. each round will become a bit harder (longer, more difficult obstacles...)


Edited by Thomas Jentzsch
  • Like 6
Link to comment
Share on other sites

Slow progress:


- implemented the delayed obstacles, which fall fast into their positions.

- also revised the obstacle data after realizing hwo graceful the original game is to collisions. Now the gaps are wider.

- this also allowed me to switch to a fully symmetric playfield, so I can reuse the data from the left side at the right side too.

- which freed almost 1K of data! Now I am much more optimistic for a 4K version.

- using sprites has been added too, you can spot them with debug colors enabled.

- had to refactor the bottom kernel, because I a had an error in my original plan on how to use them in parallel with the dots.


Next is to make use of the sprites for fading and horizontally moving obstacles.

Edited by Thomas Jentzsch
  • Like 2
Link to comment
Share on other sites

With version 0.3 (see first post), the game finally gets some structure. 


- added fading and horizontally moving obstacles

- the game is now played in stages of increasing difficulty


You start a game with 3 lives and gain an extra live for each finished stage, up to 3 lives. To start and to continue the game after a stage or a crash, press FIRE.



- The difficulty ramp up is far from being finished. Currently each stage only gets one obstacle longer and a bit faster. I plan to have much easier early stages, where the various obstacle types are introduced slowly. As of now, the game starts with everything thrown at you at once. :evil: 

- Some obstacle combinations fall too late, I will fix that with the next version

- Also the collision detection of faded obstacles at the very bottom of the screen doesn't exist. Another planned fix.


BTW: The code already counts the stages and even the score, but there is no code to display them. :) 

Edited by Thomas Jentzsch
  • Like 6
Link to comment
Share on other sites

ZeroPage Homebrew is playing Duet on tomorrow's ZPH stream LIVE on Twitch, hope you can join us!





  • Like 2
Link to comment
Share on other sites

1 hour ago, alex_79 said:

Did I hear Trackball? :ponder:

Nope. :) The game play relies on constant turning speeds.


E.g. when there are multiple bars in a sequence, you just hold left (or right) permanently. That's because the dot rotation speed is in sync with the obstacle gaps and movement speed. With a trackball and its variable speeds, the game would become much harder.

Edited by Thomas Jentzsch
  • Like 1
Link to comment
Share on other sites

55 minutes ago, MarcoJ said:

Wow, that’s some precision for the 2600.



To sync the rotation speed with the obstacle intervals (usually 60 pixel) easily, a multiple of 60 ist very helpful. That synchronization is very important for the game play. Using 120 positions avoids 2 pixel jumps in X direction. And it just requires two tables (for X and Y) with 30 bytes each only.


Therefore it seems like the best solution.

  • Like 5
Link to comment
Share on other sites

  • 2 months later...
  • 2 months later...

After I had lost motivation (also due to the latest events), it has been slowly coming back. I now have enhanced the new ROM (V0.4, see 1st post) quite a bit. 


Major changes:

  • The game is played in stages. Four stages from a group. The current progress is displayed as G-S (Group-group Stage). The first group (starting with 1-1) is more or less a tutorial for the game's obstacle forms. With each stage, the difficulty increases (faster movement, longer stages, more difficult obstacles). And with each group, a new behavior of the obstacles is introduced. So far I have added four different behaviors, most likely more will follow. 
  • You can select between 4 starting stage groups (=difficulties) by rotating the orbs. Every quarter of the circle equals a difficulty displayed as 1-1 .. 4-1. For now, everything is based on a fixed seed. But I plan to have optional, random seeds too.
  • When the game is not running, the current group and its stage (G-S), the score and your health are displayed at the top. Each time you hit an obstacle you lose ~33% health. You can regain health by successfully passing an obstacles. Up to ~33% per stage.

There is still work to be done. So please have mercy ;). But as always: Any feedback is welcome!


BTW: Albert will demo Pas de Deux at PRGE. :) 

Edited by Thomas Jentzsch
  • Like 9
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.

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.

  • Recently Browsing   0 members

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