Jump to content

CPC464Kid

New Members
  • Content Count

    16
  • Joined

  • Last visited

Posts posted by CPC464Kid


  1. On 7/5/2020 at 5:19 PM, Andrew Davie said:

    Just an update on more efficient encoding...
    Use "0" bit to indicate no change.

    Then, "1" bit indicates a change - there are actually only two possibilities, as you can't go backwards, and you're already ruled out forwards.
    So, you only need another bit 0 or 1 to indicate direction left/right.
    That would be pretty efficient.

    But you could do even better, I think.

    Use "0" bit to indicate no change, and follow that by (say) 4 bits indicating a counter.  So, "no change for 10 moves" for example.

    In that case, 5 bits for 10 moves total, compared to 10 bits for the earlier example.
    In games, there are fairly long stretches of "no change"; it's uncommon for a human to move regularly/quickly. Humans tend to focus on a target and just let it go. Your mileage may vary.

    But in any case, this would probably... let's say... double the length you could store compared to the two-bit per move method.

     

    Makes sense, thanks Andrew.  I'd just need to be careful to keep the maximum length sensible to allow for more turns after the snake grows, and I guess the player will also make more turns when the snake is longer as there would then be less free screen space.  Otherwise I could run out of memory when the player tries to change direction!


  2. 12 minutes ago, Andrew Davie said:

    Perhaps there's a better way.

    MOST of the movements will be exactly the same direction as a previous movement, right?

    In other words, changes of direction are (relatively) infrequent. Perhaps an encoding scheme taking account of this would work.

    For example, assume "0" means "no change".  Then for most of the movements you need a single bit.

    Then you could have "1" mean "there's a new direction" and use the next two bits to indicate which direction it is.

     

    Or, another alternative...

    only store the (x,y) at each direction change. Nothing inbetween.

    You can calculate which direction to move by looking at which of (x) or (y) are unchanged from the previous position.

     

     

     

     

     

     

    True, most of time you won't be making many turns.  I guess the issue is also being able to support the extreme cases where somebody keeps twisting and turning.  The edge cases are always the problem 🙂

    I like the ideas though. I think I'll start by converting what I've got to use movement values and see how that goes.  I'm finding that small incremental changes are the way to got with 6502 because otherwise it's hard to figure out where you've broken something!!!


  3. 15 minutes ago, Andrew Davie said:

    Technically you don't need to store the snake at all, because it's effectively stored in the playfield. IF you have a playfield. You can keep a track of the snake tail "next position" by looking for the adjacent "on" pixel (i.e., up/down/left/right) in the screen "bitmap".  That is, I'm suggesting a 5-byte (for the PF0/PF1/PF2/PF0/PF1/PF2 as discussed above) x n lines RAM usage.  So, assuming 24 lines you would need 120 bytes :). 16 only requires 80 bytes.  Process would be to treat those bytes as a "bitmapped" display (even though the pixel positions are weird-as) and thus the snake head pointer writes a pixel in, and the tail pointer erases itself and "finds" the new tail position by looking for the adjacent set-pixel.

    Just throwing alternate ideas at you, as this is what '2600 programming is all about.

    Interesting idea, but I still think I will need to store the "move directions" (2 bits per segment) that you suggested before.  I don't think it would be possible to find the adjacent set-pixel without storing all the movements.  Or at least I don't think my brain could cope with trying to find the correct one!  That's what I meant with the trade off as I'm now planning to store movements rather than the snake itself, but if the PF gets too big I won't be able to store enough movements.

     

    I will think about this properly when I've got more free time and start refactoring the code.  Thanks for all the ideas, you definitely need to think outside the box with the 2600.  I'm more used to C and C++ applications where nobody cares about memory anymore!


  4. 7 hours ago, Andrew Davie said:

    Although there are 6 bytes/line for PF - there are only 40 actually used bits.  That is, there are 8 bits "wasted" in the 6 bytes.

    In particular, PF0 only uses 4 bits.  So you can save space (1 byte/line) by putting the left-side PF0 bits in the high nibble of a byte, and the right-side PF0 bits in the left nibble of the same byte. You just need to shift 4-times when doing the screen draw itself. This saves you, as noted, 1 byte per line (if you were using all 6).  I see you are probably using 4 bytes/line - this would give you full-screen width at the cost of just 1 more byte/line. Given the 2 bits/move + 5 bytes/line mods, I think you could get it full screen. Go on, I dare you :)


    Good job so far, btw!

    Thanks Andrew, that is true.  Although currently I'm actually only using two bytes per row (my rows are 8 scan lines deep), with a further 6 temporary variables used inside the kernel to keep track of what to load into the PF registers for the current and next row.  The values for the next row are built up during spare cycles in the current row from these two bytes etc.  Effectively I've got a 16x16 bit table that represents my playfield (excluding the side walls) and the actual PF data is temporary (changing every 8 scan lines).  I might need to think about storing the data in a format that fits the PF registers instead to get full screen width.  I guess it would be a trade off between RAM consumed for the playfield and RAM available for the snake to grow!

     

    I'll see what I can do 🙂  Perhaps just extending it from 16 PF pixels wide to 24 PF pixels would be a good trade off.  Although making it full screen would be a good technical challenge!


  5. 7 hours ago, DEBRO said:

    Really? My calculations show a value of 134 is the maximum before you spill to another scan line.

     

    A value of 134 should strobe RESP0 at cycle 63 with a 8 pixel right shift. Adding 6 more cycles for the RTS you are now at cycle 69 on the return with enough time to do a WSYNC.

     

    A value of 135 should strobe RESP0 at cycle 68 with a -6 pixel left shift. Adding 6 more cycles for the RTS you are now at cycle 74 on the return and no time to do a WSYNC without spilling to the next scan line.

    Yes, your right with 149 I do find myself at cycle 74 on the return.  I was just checking that it returned on the same scan line, but of course that doesn't leave enough time for WSYNC 😞  The calling code would have to take that into account and skip the WSYNC if the X position were greater than 134.  Another option would be to WSYNC and HMOVE within the subroutine before returning (if you don't need to move multiple things at once).  Or if using a timer during vertical sync it wouldn't matter of course.

     

    Good discussion!


  6. 56 minutes ago, ZeroPage Homebrew said:

    Just checked it out on my Light Sixer using a Harmony Encore cart, no issues! Played up to 65 points, on point 59 the dot appeared on my tail so I had to wait until it was cleared to get it. Not sure if that's a bug or a feature but it works either way. 🙂

     

    - James

    Cool, good to know thanks.  I think that is another bug as it's supposed to only put the dots on empty squares, but I'll look at that another day!

    • Like 1

  7. 4 hours ago, DEBRO said:

    Hi there,

     

    If I'm understanding correctly, your new routine hits the player reset at a minimum of cycle 22. This would give a player horizontal resolution of 60 - 224 without spilling over to the next scan line. The importance of hitting the player reset at a minimum of cycle 23 gives a player horizontal resolution of 63 - 227 before spilling over to the next scan line.

    Hi Dennis, I've just checked with Stella and when A = 0 the scan cycle (CPU cycle) is 23 when the PC is pointing at the RTS instruction.  The position is set to 6 and HM is also 6 (meaning we do eventually end up with position 0 as requested).  The timing of the reset is exactly the same, but the subroutine can return right after because HMP0 has already been set.  An adjustment is also made for balls and missiles.

     

    At the other side of the screen when I set A to 149 it just returns in time.  At 150 it starts to spill over onto an extra scan line.  Not too bad I think 🙂


  8. 6 hours ago, Armscar Coder said:

    I echo James's comments regarding the face graphics and mouth animation.  Surround and Tapeworm are some of my favorite original games.  Anything resembling a Tron Light Cycle is cool in my book.  I look forward to the final version which I plan to put on my Harmony cart for some on demand Atari therapy when I need some downtime after a tough day at work or when my lawn mower won't start.

    Thanks for the encouragement @Armscar Coder. When I get a bit further it would be useful to have somebody test on real hardware.  I've managed to resist buying a 2600 from eBay because I know it won't look great on my modern(ish) TV, and I need to stop filling my house with such things!


  9. 5 hours ago, ZeroPage Homebrew said:

    Found a small bug in the game. There are certain directions you can press on the joystick that will result in instant death:

     

    While Facing Right: Pressing Down/Up then Left IMMEDIATELY (this is more easily accomplished at the beginning before moving)

    While Facing Left: Pressing Down/Up then Right IMMEDIATELY

     

    - James

    Now fixed with replacement rom attached above!  Thanks again for spotting this James @ZeroPage Homebrew

    • Like 1

  10. 1 hour ago, ZeroPage Homebrew said:

    Found a small bug in the game. There are certain directions you can press on the joystick that will result in instant death:

     

    While Facing Right: Pressing Down/Up then Left IMMEDIATELY (this is more easily accomplished at the beginning before moving)

    While Facing Left: Pressing Down/Up then Right IMMEDIATELY

     

    - James

    Thanks James, well spotted.  I knew about moving left before you start and that was just lazy programming, but the other cases I hadn't considered.  Just had a quick look and I think it's because I am checking the joystick and updating the direction every frame, but only moving every 10 frames.  So if you change direction very quickly it gets confused.  I will fix this!

    • Like 1

  11. 5 hours ago, Andrew Davie said:

     

    In a snake game you could represent the snake by a tail position (x,y) and a head position(x,y) and the body simply as a series of "move directions". Moves are only up/down/left/right, so you only need two bits for these. Thus, 4 positions per byte.  You could probably spare about 100+ bytes for storing the body, so let's say 4 or 500 body length :). That is, if you wanted really long snakes, or perhaps a two player version with two snakes.
     

    Thanks @Andrew Davie that's a brilliant idea.  I never thought about it that way, but of course your right 🙂  I still need 1 bit of storage for every playfield square because the kernel needs to know what PF bits to set on each line (I'm currently using 32 bytes for this), but my 70 odd free bytes would still give a body length of 280.  Obviously I'll also need to make the playing area bigger but your idea gives a lot of scope for improving things.  I'll try using this technique.  A two player version would also be cool.

    • Like 1

  12. Updates

     

    • Playfield is now 1/3 wider (24x16) and the maximum snake length is more than double at 200+ segments, all thanks to @Andrew Davie's very cleaver storage optimisation idea (I am now using just 2 bits for each segment by storing movement values instead of locations).

     

    Known issues / bugs

     

    • Random number generator does not yet cover the extra screen area (it still works with a 16x16 grid).
    • The score will now wrap around if you exceed 99 🙂 

     

    Hi, I'd like to share my first Atari project which is a little snakes game.  I'm sure this has been done before, but as I wrote one years ago for Windows it seemed like a good place to start.  The major challenges were finding enough memory to maintain a long (ish) snake and having to manipulate the playfield registers on every scanline whilst also drawing a couple of sprites.

     

    I originally started using a linked list to maintain the snakes body (requiring two bytes per segment), but quickly started running out of RAM.  This is the most obvious algorithm for a computer, but clearly not for the 2600.  I now just use an ordered list (one byte per segment) which is reshuffled whenever the snake moves to maintain the order.  This is very inefficient, but the CPU has nothing else to do at the end of a frame so actually makes sense.  I'm starting to get into the mindset of programming this thing!

     

    The playfield is limited to a 16x16 grid for obvious reasons (4-bits for X and 4-bits for Y allow any position to be represented by 1 byte).  The maximum size of the snake is around 75 segments long which makes things reasonably challenging I hope.

     

    I've not added a proper "Game Over" or "You Win" sequence yet, but it will re-start if you die and stop moving if you win (by scoring 70 points).  Just push the joystick to start the game!

     

    ROM file attached and also available on my github page with the source:

     

    https://github.com/RobinSergeant/2600-Snakes

     

    Comments and suggestions welcome.  It's been a fun learning exercise for me and I've really enjoyed doing some retro coding.

     

     

    snakes.bin

    • Like 5

  13. I also used a mask for the Snakes game I'm currently writing.  Please see source code on github:

     

    https://github.com/RobinSergeant/2600-Snakes

     

    I have an 8 line kernel, but it uses so many cycles manipulating the playfield to draw the snake that I didn't have time for conditions.  Hence, the snake's head is drawn with a mask (the mask is 0 when the head should not be visible.).  Most of the RAM is needed to store the snake data as well and so I ended up storing temporary variables like the mask on top of my stack 🙂  This actually works quite well as I cannot call any subroutines in the kernel section anyway!

     

    Something else you can do is try to make use of every spare cycle towards the end of the scanline to set things up for the next line.  Pre-load all your registers etc.  I did most of my logic here because my game doesn't use much of the screen.  So using less of the horizontal real estate is another good option.  It gives you more times to set things up at the start and free time at the end of each line.

     

    Not using WSYNC also helps save 3 cycles when things get really tight.

    • Like 1
    • Thanks 1

  14. For my first post I'd like to share my version of the horizontal positioning routine, as other variations I've seen always seem to be out by a few pixels which is frustrating 😞  I started with the SetHorizPos subroutine from Steven Hugg's book, but this is out by 3 pixels for players.  He's fixed that in other projects at the 8-bit workshop, but even this is still out by one pixel for balls and missiles.  Michael Martin has a clever and well reasoned alternative that corrects the ball, but it uses more cycles and so often goes over a scanline.  Anyway, here is my updated variation of it:

    ; SetHorizPos routine
    ; A = X coordinate
    ; RESP0+X = reset register to strobe
    SetHorizPos SUBROUTINE
                cpx #2 ; carry flag will be set for balls and missiles
                adc #0 ; (adding 1 to account for different timings)
                sec
                sta WSYNC
    .loop       sbc #15
                bcs .loop
                eor #7
                asl
                asl
                asl
                asl
                sta.a HMP0,X  ; force absolute addressing for timing!
                sta RESP0,X
                rts

    The first two lines adjust for balls and missiles - credit to Michael for this, see his excellent blog post for an explanation:

     

    https://bumbershootsoft.wordpress.com/2018/08/30/an-arbitrary-sprite-positioning-routine-for-the-atari-2600/

     

    The rest is from Steven, but I've forced absolute addressing (using sta.a to consume an extra cycle!) and moved a few things around to fix the timing without having to sleep.  For comparison here this what the fixed version in the 8-bit workshop does:

    SetHorizPos
    	sta WSYNC	; start a new line
        bit 0		; waste 3 cycles
    	sec		; set carry flag
    DivideLoop
    	sbc #15		; subtract 15
    	bcs DivideLoop	; branch until negative
    	eor #7		; calculate fine offset
    	asl
    	asl
    	asl
    	asl
    	sta RESP0,x	; fix coarse position
    	sta HMP0,x	; set fine offset
    	rts		; return to caller

    Notice that in my version there is no need to waste 3 cycles and that I'm now also setting HMP0 before strobing RESP0.  This has the advantage of allowing the routine to return as soon as RESP0 has been strobed which works better for co-ordinates near the end of a scanline.  The sta.a instruction adds 5 cycles before the strobe, but moving the SEC to before WSYNC saves 2 which results in the same 3 cycle delay!

     

    Hopefully this will be useful to other people scratching their heads over this.  I've certainly found it the most difficult concept to grasp so far!

    • Like 1
×
×
  • Create New...