Jump to content

Recommended Posts

Hm. Maybe that's the secret - I'm not sure how exactly to look at BACKTAB in IntyBASIC without PEEKs. Back to the drawing board. Care to elaborate on the magic numbers in your example PEEK? :)

 

Also part of my problem is that I'm not using an 8x8 MOB, so it makes calculations a bit more complicated - but I think I have that kinda-sorta squared away with another trick - just a matter of isolating what I'm interested in. Fortunately I'm already doing a lot of what you describe, so at least I managed to muddle my way mostly down the right path. Almost feels like I have a clue what I'm doing sometimes.

 

 

Magic numbers?

  • 512 is the address of BACKTAB. ($200 hex)
  • 20 columns in a row
  • 8 pixels per card in both X and Y direction

The MOB coordinate field starts 8 pixels above and 8 pixels to the left of the screen. An 8x8 MOB at location (1,1) will have one pixel exposed at the top left of the screen. (More will be visibile, if you've scrolled the screen down or to the right with the scroll registers.)

 

So, to map MOB coordinates to a card in BACKTAB:

  • Subtract 8 from X and Y to map upper left corner of MOB to a BACKTAB square. The BACKTAB square at the top left of the screen resides at (8,8) - (15,15) in MOB coordinates.
  • Divide by 8, as there are 8 pixels per BACKTAB card in both X and Y directions

The rest is just address computation:

  • 512: base address of BACKTAB. (512 = $200)
  • plus column
  • plus 20*row, as there are 20 columns in each row

That's it.

 

So what's the issue with using PEEK? Got something against it? I used PEEK/POKE all the time to manipulate the display in the 8-bit era.

intvnut explained it rather well, so I'll just elaborate on the specific technical details of Christmas Carol's collisions. As has been well established already, Christmas Carol uses the game engine and framework I originally built for a Pac-Man port, and therefore most of the inner workings take advantage of techniques I developed for that game.

 

Specifically, the maze walkable grid, which I call the "virtual tile map," and the sprite collisions are taken directly from the code I created for Pac-Man. Like intvnut said, I design the mazes in ASCII files and use a Perl script I wrote to rip GRAM/GROM cards and determine the virtual tile map and walkable paths. The result of this script is a data structure, stored in ROM that contains a four-bit vector for every virtual tile in the effective map, that specify which open exits are available for each tile. Virtual tiles are 4x4 pixels large, and sit at the center of a regular BACKTAB card, and fan out from it.

 

Here's a typical map definition source, this one for "Haunted Hollow":

; -------------------------
; The following symbols are
; available to all mazes:
; -------------------------
; .: Open path
; *: Candy
; @: Snowflake
; X: Present
; #: Solid ice-cube
; =: Side tunnel
; -------------------------

; -------------------------
; Stage 1: Haunted Hollow
; -------------------------
; 0 - 6: Solid ice-cube assortment
; -------------------------
:M1
.#####...###.##....
#..X**###...#.@####
#.*.#@..#.#.*.#.#X#
.#..###...##*.**.*#
###.#..X.......##.#
=...*...*#.*#...X*=
##*.......*##X...##
#*.##...*...###.##.
#.X*.#.###....*..##
.###.*.#X..**#@*..#
#.@.*..*.###.*.X###
#########...####.#.

TITLE:      Haunted Hollow
CUBE_SET:   ICE_CUBE_0
VIGNETTE:   ( 10,  0)
@@elf       (  1,  6)   >
@@snowman   ( 20,  6)   <
@@ghost     ( 18, 10)   <
; -------------------------

Since the state of having eaten items is mutable, it needs to be stored in RAM. I discovered, much to my convenience, that using Color Stack leaves four bits unused on each BACKTAB cell, which I then use to keep the state of candy and snowflakes. Again, this mechanism came directly from my Pac-Man code, where I could store up to three pills on each BACKTAB card at one time, or two pills and one energizer. From my in-code comments:

; ===================================================================
; We are using "Color Stack" mode for drawing the background, this
; frees up four bits from each 16-bit BACKTAB word, which we can use
; for game state information.
;
; The unused bits of each 16-bit word are co-opted to store the state
; of each bonus item in RAM.
;
; Each "natural" tile may contain up to three items, one of which
; can be a Magical Snowflake, the others are pieces of candy.
; The available item positions within a natural tile are as follows:
;
;   8x8 Natural Tile:
;     ...2....
;     ........
;     ........
;     ........
;     ...1...0  <- #1 can be either Candy or a Magical Snowflake.
;     ........
;     ........
;     ........
;
; The BACKTAB data for the Christmas Carol maze is pre-computed below
; for faster loading.  Each word defines a 16-bit vector in the
; following format:
;             ,-----------------------> Stack Advance Flag
;             |
;             v
;     F   E   D   C   B   A   9   8   7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
;   | @ | * | - | C | M | * | * |    GRAM/GROM Card #   |  FG Color |
;   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
;     ^   ^       ^   ^   ^   ^
;     |   |       |   |   |   |
;     |   |       |   |   |   `-------> Candy #1
;     |   |       |   |   `-----------> Candy #2
;     |   |       |   `---------------> GRAM=1, GROM=0
;     |   |       `-------------------> FG Color High Bit
;     |   |
;     |   `---------------------------> Candy #3
;     `-------------------------------> Magical Snowflake
;
;
; Copyright (c) 2010-2012, James Pujals (DZ-Jay)
; ===================================================================

To determine a collision with a background item, I not only need to convert the sprite's coordinates into a BACKTAB address, I also need to determine on which virtual tile within that card the sprite is. Candy, presents, and Snowflakes are constrained to exist exclusively at the center of a virtual tile, so a collision occurs only when the sprite is determined to be at the center of a virtual tile.

 

Here's a description of the algorithm, taken again from the in-code comments:

;;  DESCRIPTION OF ALGORITHM                                                ;;
;;  Although an 8x8 BACKTAB Tile can be divided into four Virtual Map       ;;
;;  quadrants, due to the VMAP offset from the screen origin there are      ;;
;;  actually 9 possible quadrants within Tile:                              ;;
;;                                                                          ;;
;;        Virtual Tile Quadrants:                                           ;;
;;                                                                          ;;
;;            . . | . @ . . | . .                                           ;;
;;          0 . . | . . . . | . .                                           ;;
;;            ----+---------+----                                           ;;
;;            . . | . . . . | . .                                           ;;
;;            . . | . . . . | . .                                           ;;
;;          1 . . | . @ . . | . @       @ = Virtual Tile Origin             ;;
;;            . . | . . . . | . .                                           ;;
;;            ----+---------+----                                           ;;
;;          2 . . | . . . . | . .                                           ;;
;;            . . | . . . . | . .                                           ;;
;;             0       1       2                                            ;;
;;                                                                          ;;
;;  The origin of each Virtual Tile is in offset (1, 1).  Since the items   ;;
;;  occur only at the origin of tiles and the Items Vector is encoded       ;;
;;  within a BACKTAB word, we need to adjust the screen coordinates by the  ;;
;;  origin offset in order to retrieve the word for a given Virtual Tile.   ;;
;;                                                                          ;;
;;  Thus, if the MOB is on one of the left-most quadrants (X = 0), we need  ;;
;;  to point to the tile on the left and rollover the quadrant offset to    ;;
;;  the right (X = 2).                                                      ;;
;;                                                                          ;;
;;  Likewise, if the MOB is on one of the bottom-most quadrants (Y = 2), we ;;
;;  need to point to the tile below and rollover the quadrant offset to the ;;
;;  top (Y = 0).                                                            ;;

The function that calculates the virtual tile quadrant, returns the offset of the sprite position within that quadrant as well. If the sprite is at the center of the virtual tile, we may have a collision. We'll have to check if there is an item in that quadrant by testing the items vector in the BACKTAB word as described above.

 

That takes care of the candy and snowflakes. Presents are done in a different way. Each maze definition data structure in ROM contains an array of its presents' positions, in both BACKTAB address and Virtual Tile Map coordinates. The former is to know where to draw/erase them, the second is to test collision. There is also an eight-bit vector that keeps the status of each present: a "1" means the present is still there, a "0" means it has been picked up. Each bit corresponds to one of the presents in the ROM array, in sequence, starting from the least-significant bit.

 

To test for a present collision, we iterate through the array and compare the virtual tile map coordinates of the present with those of the sprite already decoded. If they match, we have a collision! Er, well we could have a collision, we take the index of the present and test that bit in the Presents Status Vector. If set: collision! The Elf just picked up a present. We erase the present from the BACKTAB by replacing its card with a blank one*, we clear the status bit in the vector, and we decrement the presents counter.

 

* Well not quite a blank card. We use the items status bits taken from the BACKTAB and convert it into a four-bit vector which is then used as an index into the GRAM card set of various candy combinations. This has the effect of removing a present and leaving any candy adjacent in the same background card. This is the same mechanism used to "eat" candy. Below is the full candy tile set, taken from the ASCII source file.

@@CANDY:
:0001    :0010    :0011    :0100    :0101    :0110    :0111
........ ........ ........ ...#.... ...#.... ...#.... ...#....
........ ........ ........ ........ ........ ........ ........
........ ........ ........ ........ ........ ........ ........
........ ........ ........ ........ ........ ........ ........
.......# ...#.... ...#...# ........ .......# ...#.... ...#...#
........ ........ ........ ........ ........ ........ ........
........ ........ ........ ........ ........ ........ ........
........ ........ ........ ........ ........ ........ ........

By the way, if it seems that the candy collision detection is more clever, sophisticated, and efficient than the boring table search for the presents, that's not a coincidence. I spent two years, on and off, designing my Pac-Man implementation, working on all the theory of the Virtual Tile Map, as a mental exercise. By the time I started writing code, I had a fairly robust understanding of how it would work, and implemented everything in a rather elegant way. All that theory and code were re-purposed for Christmas Carol. However, Pac-Man had no presents, so in a pinch, I fell back on the simpler linear table search.

 

(It's important for me to point out that I was trying to implement Pac-Man from scratch, using only the Pac-Man Dossier's player-level description of behaviour and mechanics, with absolutely no knowledge of how the original game works; so all the techniques I developed to handle collisions, movements, and the virtual tile map were my own.)

 

The original incarnation of Christmas Carol had only one maze, so eight presents seemed more than enough. When I expanded it to have multiple stages, I rewrote most of the code, and added support for multiple tunnels, unlimited Snowflakes and Candy, and plenty of other new features; but I never changed the presents management mechanism. Consequently, each maze requires eight presents, and the boring table search is still there. It works rather effectively, though. :)

 

-dZ.

Edited by DZ-Jay
  • Like 2

So what's the issue with using PEEK? Got something against it? I used PEEK/POKE all the time to manipulate the display in the 8-bit era.

 

There's no issue with using PEEK, I just haven't had to until now. IntyBASIC abstracts so much that this is the first time I've had to consider addressing memory directly. So much so that I didn't even know what $200 referred to (hence my comment about magic numbers). I'm already doing the rest of the math described, but after-the-fact once a collision with a BACKTAB card is detected. Which is... non-optimal.

 

I also used to use PEEK/POKE all over the place, because you had to :) With IntyBASIC, I've been able to write damned near everything I want without POKEing. But it doesn't have any higher-level command for reading memory this way.

 

Thanks for the explanations and tips, guys (including DZ on this comment). I thought I was over-thinking and over-complicating things with what I figured had to be done. Sounds like I'll just be replicating the standard practices here. And DZ - you've also answered an unasked question. I was trying to figure out how to efficiently build a maze-ish type scenario, where a MOB couldn't move through walls etc. It just seemed ridiculously onerous to calculate every possible up/down/left/right boundary on every single border tile (the ice blocks in your case). You've given me food for thought on how to better implement this.

Edited by freeweed

No worries, mate. If you want, I wouldn't mind sharing the information, algorithms, and code I have on building and managing the virtual map. You shouldn't have to start from scratch. :)

Thanks, but sadly I'm gonna insist on doing it in IntyBASIC. Which means I'm reinventing the wheel anyway. However as always I'll keep the offer in mind and will likely ask more stupid questions as time goes on. Ironic that it may end up being a little more of a challenge to use a HLL.

 

I can understand why GroovyBee got hisself a bit overloaded. I've now got 4 games in various stages of development (3 of which could be described as "releasable" save for a shit-ton of bug testing and balancing). It's fun to come up with new ideas and never polish off the old ones. Or get stuck on some subtle thing and start an entire new game from scratch instead of doing the hard work. :D

 

I also used to use PEEK/POKE all over the place, because you had to :) With IntyBASIC, I've been able to write damned near everything I want without POKEing. But it doesn't have any higher-level command for reading memory this way.

 

It seems like a natural feature request to have IntyBASIC declare BACKTAB as an array of 16-bit words, to save the PEEK and POKE. Does IntyBASIC support 2-D arrays or just 1-D? Either way, that'd bring BACKTAB into the fold w/out PEEK/POKE.

 

It seems like a natural feature request to have IntyBASIC declare BACKTAB as an array of 16-bit words, to save the PEEK and POKE. Does IntyBASIC support 2-D arrays or just 1-D? Either way, that'd bring BACKTAB into the fold w/out PEEK/POKE.

 

1-D only as far as I'm aware. However that is a FANTASTIC feature request. In the same way we access things like FRAME and RAND and whatnot - I'd love to be able to use BACKTAB "natively" within IntyBASIC.

 

It wouldn't consume the available variable space, correct? Because you'd simply have the compiler re-map the array addresses to the actual BACKTAB addresses? Sounds like an absolute win.

 

While we're on the subject (and before I waste a lot of time reverse engineering it) - correct me if I'm wrong, but BACKTAB consists of 240 addresses, yeah? One for each card position. And they'd be addressed in a left-to-right, then move to the next row fashion? It's too late and there's been too much hockey tonight, and my brain is stumbling on the simplest things. The more complicated parts are apparently obvious :P

While we're on the subject (and before I waste a lot of time reverse engineering it) - correct me if I'm wrong, but BACKTAB consists of 240 addresses, yeah? One for each card position. And they'd be addressed in a left-to-right, then move to the next row fashion?

 

Yep. left-to-right, top-to-bottom, just like English text. One location per card, 240 total.

Gracias. It's what I figured based on how nanochess implemented the PRINT command - I suspect it's just a POKE $200+index,value. Which means it lines up with most everything else I've coded - now I just have to convert my "collision detected, figure out which card you're on" to just do both at the same time.

Gracias. It's what I figured based on how nanochess implemented the PRINT command - I suspect it's just a POKE $200+index,value. Which means it lines up with most everything else I've coded - now I just have to convert my "collision detected, figure out which card you're on" to just do both at the same time.

 

Approximately. You know an easy way to find out, right? :)

 

(Hint: He outputs an assembly file, with your source code in a comment right next to the assembly it produced.)

 

 

In any case: There's a little more bookkeeping going on with PRINT, such as remembering the current print color and current print cursor location, so that multiple PRINT statements in a row concatenate their output. PRINT also makes slightly more optimized use of the Intellivision's addressing modes than, say, POKE in a FOR loop, and it adjusts ASCII to the correct range of GROM cards for display, if I'm not mistaken.

Edited by intvnut

 

Approximately. You know an easy way to find out, right? :)

 

(Hint: He outputs an assembly file, with your source code in a comment right next to the assembly it produced.)

 

 

In any case: There's a little more bookkeeping going on with PRINT, such as remembering the current print color and current print cursor location, so that multiple PRINT statements in a row concatenate their output. PRINT also makes slightly more optimized use of the Intellivision's addressing modes than, say, POKE in a FOR loop, and it adjusts ASCII to the correct range of GROM cards for display, if I'm not mistaken.

 

Pf. Looking at source is cheating :P

 

And yeah, there's a bunch more stuff that it does - ie: if you don't specify a location or color, or if you're printing text. But the basic "PRINT AT 20,value" pretty much just POKEs whatever value you want into $200+20. Which means I'm already very much accustomed to writing to BACKTAB directly. It's just convenient as all hell to have a command such as this.

 

I also just realized: "READ AT 20,value" or a more BASIC-like "value=READ AT 20" could be a decent syntax for what you were suggesting above.

 

I also just realized: "READ AT 20,value" or a more BASIC-like "value=READ AT 20" could be a decent syntax for what you were suggesting above.

 

 

I actually think it'd be cleaner if it looked and acted like any other BASIC array rather than having special syntax. Maybe it's my years of C-programming minimalism speaking.

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