Jump to content


New Members
  • Content Count

  • Joined

  • Last visited

Community Reputation

46 Excellent

About NoLand

  • Rank
    Star Raider

Contact / Social Media

Profile Information

  • Gender
  • Location
    Vienna, Austria

Recent Profile Visitors

1,771 profile views
  1. Very nice! Minor idea: Have a slightly different sound for an attempt to move the cursor off the board. (This is just one of those tiny things the "brain" tends to like.) However, evaluating the game over state is probably of higher priority – and quite a challenge, given the limited amount of memory. :-)
  2. Maybe worth mentioning: The TIA is all you have (apart from the RIOT chip for a few things like timers and reading input), it's all there is. It's your only friend. Don't rush things, understand them. The TIA is much like an operating system on modern computers, strobing and setting registers is like calling OS routines. However, the TIA is also a rather tacit friend, it won't talk back regarding any errors (unlike an OS). Instead, there's immediate visual feedback. Part of the art is also, guessing from the visual feedback what may have gone wrong. Some of this may seem tedious, but it's really a quite ingenious design, given the (cost) constraints. Part of these constraints is (a) the TIA "knows" nothing about vertical images, it just does scanlines, over and over again, as currently configured. Hence, we're counting scanlines, etc. (b) It uses linear feedback shift registers internally for counting, rather than counting by ordinary numbers. These shift registers progress by unique values, like normal counters, but non-sequentially, out of sequence. This is why we have to communicate horizontal sprite positions by timing. This is too coarse for an exact position at most of the pixels, but there are also HMOVE and the corresponding horizontal movement registers. These can be used either for moving an object by a certain offset to the left or right, or for fine positioning. This is, where the more arcane code, like the one in the Battlezone routine comes to help. Thankfully, this has been figured out long ago… Combining all those things in the tiny amount of time provided by a single scanline (often, we want most of this already be set in HBLANK, which is just 22 CPU cycles) in the right order is the high art. (This is, what caught me somewhat by surprise: reading it up, it seems just simple, there are that and that constraints and that and that recipes to do it, fair enough. But then you realize, you must be ready by the end of HBLANK, if you want to show a sprite at the left side of the screen. Maybe, you'll have to tweek your game design, maybe you'll have to become inventive with your code. Maybe, reordering code helps. Again, making copied code your own by thoroughly understanding it helps enormously.) Maybe start with looser constraints, i.e. a two-line kernel, which provides you double the time at the cost of vertical resolution.
  3. Andrew Davie's advice is probably the best one: start simple, understand each of the steps, and progress. Take a very simple kernel, e.g., the one attached here, understand what it is doing. The one suggested here uses timers for VBLANK and overscan, so you've only to worry about counting the visible kernel scanlines. Also, it can be configured for NTSC and PAL (already preconfigured), showing that there really isn't much difference (it's just about the extent of the three parts, VBLANK, kernel, overscan). Try inserting code, start – as already suggested – by editing the kernel so that a full sprite (where ever it will appear on the screen) is displayed. Maybe try different patterns. Maybe, you'll want to try to move that band incrementally using HMOVE (insert this before the kernel once per frame, don't forget to rest HMOVE values). Then try to make the sprite just on a single scanline, or on a certain vertical position for a given height. Try changing the pattern for each of the scanlines it's visible. Use patterns stored in a table for a real 2D sprite. Finally, implement some code for horizontal positioning. (This is the most tricky part, since it can be done only by timing and the CPU is slower than the beam/TIA. So there'll has to be compensation for fine adjustment.) There are well known routines to achieve this, e.g., the "Skipdraw" routine for vertical drawing, or the Battlezone routine for horizontal positioning (in various flavors). Have a look at various tutorials or at http://www.qotile.net/minidig/tricks.html. Copy just the code you actual need and integrate it into what you already have. Understand it and make it your own! If you're more the reading type, read it up (again, see tutorials, etc), and, again, understand it and make it your own. So, here's a simple kernel to start with: playfieldstarter.asm
  4. Mind that using a mirrored playfield setup (as opposed to a repeated one) will give you some extra time for the last two PF-registers. (Repeated: 4 + 8 + 8 + 4 + 8 + 8 vs. mirrored: 4 + 8 + 8 + 8 + 8 + 4. Meaning, there are 5 extra cycles for setting the last two PF values, in this case PF1 and PF0.)
  5. Mind that you'll have to (a) write the first value/pattern of PF0 on the start of each scanline for the left side, then wait for PF0 having been actually displayed and then set the new value – and this for each line. Start counting your cycles at WSYNC. E.g., sta WSYNC ;strobe WSYNC to be woke up at start of next scanline lda #%00010000 ;[3 cycles] sta PF0 ;[3 cycles, now at 6] lda #%01110000 ;[3 cycles, now at 9] sta PF1 ;[3 cycles, now at 12] lda #%11111100 ;[3 cycles, now at 15] sta PF2 ;[3 cycles, now at 18] lda #00010000 ;[3 cycles, now at 21] load right-side PF0 ;now wait until PF0 has been displayed ;scanline display starts at the end of cycle 23 ;+ 4 fat pixels = 4 * 4 / 3 (pixels per CPU cycle) = 5.333 ;so we'll have to wait at least until cycle #28 has passed. ;on the other hand, we should be done before cycle #49, ;when PF0 is used by the TIA again for the right side. nop ;[2 cycles, now at 23] nop ;[2 cycles, now at 25] nop ;[2 cycles, now at 27] ;mind that the value will be actually set only at the end of ;the last cycle of the STA instruction, so we're good to go sta PF0 ;[3 cycles, now at 30] ; now wait for PF1 (compare the chart) ; and for PF2 ...
  6. The shape data would be normally stored in ROM, of which there's plenty, comparatively speaking. For a bit of understanding of what the TIA does or doesn't: The TIA just assembles a horizontal line of TV image on the fly. For this, there are background graphics (20 bits of "wide" pixels, 4 times the size of a normal pixel, repeated or mirrored for 40 wide pixels in total), two player sprites, a missile (a single dot) for each player, and a ball (another pixel). Sprites may be scaled and player sprites may be also repeated at an offset, up to three times. You may see where this goes with Space Invaders, already, as there are 6 Invaders, which may be accomplished by using the two sprites with 3 copies for each of the two sprites. We just have to change the pattern for the sprites, just at the right time, which is the tricky portion. So, where does the pattern come from? You read it from ROM and put it in the respective register. And you have to do it for each line of the screen. Mind that the TIA does horizontal lines only and has no notion/concept of a vertical extent. If we do nothing on the next line, the image will be repeated over and over, resulting in vertical stripes or bands. So we have to provide for this in the program, in a section usually called "kernel" that controls the visual image. We'll have to count the rows, as we're passing over them, and we'll have to figure out, if a certain sprite is on for the given row/line of the TV image. This is done by keeping track by counters, which are also used to read the sprite data with an increasing (or much more often, decreasing offset, as we're usually counting down to zero from a set number of screen lines). Fortunately, the 6502 processor provides some handy instructions for this. (Mind that all invader sprites are positioned at the same horizontal offsets, meaning, we haven't to change the horizontal positions of the sprites. They are either off or displaying a certain pattern. However, down the image, it becomes more tricky, as we'd have to reposition the sprites for the barriers and the player sprite. The TIA has a really as basic as possible approach to this, where you basically say "now" to the chip, when the beam is it at the right position. This is, because the TIA doesn't implement numeric counters for determining positions, but so called linear feedback shift registers, which count in unique, but non-sequential digits. Oops. But this lessens the transistor count and therefore costs dramatically. Also, the CPU runs slower than the TIA, therefore, us saying "now" is a bit too coarse in granularity and we'll have to figure out a correcting offset. Fun! The tell-tale sign of this procedure are the black "combs" seen frequently on the very left edge of the screen, which are artifacts of this fine positioning.) BTW, the TIA, being the little box of wonders that it is, does sound, as well. Yes, this is actually quite simple. While the TIA is assembling the signal, which will be eventually sent to the TV, it's rather easy to determine, if another signal source is already set high/active. If this is the case, this will be stored in the respective collision register, of which there are 8 in total to represent any of the possible combinations. To tap into the TIA's "stream of thought", this may be well like, "Now on to the next pixel, which is pixel 67 – or 123, as I call it internally, – let's see, background is off, nothing to do, get the player sprite #0, oh, its position is well out of scope, never mind, then sprite #1, oh, it's active and, as I'm shifting the bits along, as well, it's an active bit. — Let's look up the color, oh, it's a bright red, put this on the output. Then, let's move to the first missile, oh, it's active as well, let's put this one on the output. Oh, there's already an active signal from sprite 1, let's take note of this…" – As it happens, each of the "oh"s can be implemented by a simple logical gate. (These registers share addresses with those registers used to set the state of the TIA. If you write to these addresses, you set the state, if you read from them, you get a readout of the registers storing collisions or the state of the controllers. Again, this is for cost reduction, since, this way, we can save some cents on additional pins on the chip. Which is the logic behind most of the VCS/Atari 2600: Why spend money on each of the millions consoles shipped to users, if a few extra hours on the programmer's side may do the same? Still, development and production of the VCS was that expensive that Atari had to be sold to Time Warner in order to survive. It was pretty much on the verge of what was commercially viable at the time.)
  7. First, mind that this is a simplified example to illustrate the principle. In actuality, there are also explosions and maybe even explosion phases to take into account. So it will probably a byte for each invader to store an additional state and a counter. Regarding TIA collision registers, the TIA registers any of the possible combination of a set bit of any of the sprites overlapping and stores them in a few read-only registers. In theory, this would provide already all we need to determine a collision. (E.g., does the missile of the player overlap with the enemy sprite? Read the according register and check for the collision bit representing this exact condition.) However, here, we're repurposing sprites multiple times, so all the TIA could tell us is whether there was a collision at all and we would have to figure out on our own, which invader was hit, etc.
  8. Mind that not every object on the screen has to have a representation of its own in the code! E.g., having a look at the Space Invaders screen in your example, there are 6 rows of 6 invaders each. Mind that the rows move in concordance as a single block. So, you actually only need the (x/y) offset of the block and a byte a row with bits set for an active invader. The rest can be done by simple math, using a few shifts and addition for comparing objects. (However, the TIA collision registers will do some of this for us already.) Of course, we'd need some additional code to handle an entire edge (to the left of right) of the invader block becoming free, meaning, the block wouldn't reverse when reaching x=0 zero on the left side, but at x=16, where the second invader is located, etc. So we'd need another byte for an offset to add to this on the left and another one to be subtracted on the right in order to handle the desired reversal of direction properly. Considering it all, we'd need 6 bytes to store 6 bit-vectors for each of the rows of invaders, 2 bytes for the block coordinates and another two bytes for the left and right offsets. Putting this a step further, we could also store the latter in a single byte using a bit vector indicating offset shifts to be applied to the block coordinates, which would give us a storage requirement of just 9 bytes to handle 36 objects.
  9. Please do not confuse "Atari 8-bit games", generally meaning Atari 400/800 and later siblings of that family, and Atari 2600 (VCS). These are not the same platform, they are actually incompatible. However, a controller for the Atari 8-bit family should work with the Atari 2600. Instead of buying a dedicated USB joystick, you could also get one of the various Atari joystick port (DB-9) to USB adapters and use an original or compatible one. You could even build your own using the "zero delay" USB interface used by many arcade gamers (you can find these tiny boards quite cheap at ebay, Amazon, etc.) I believe, there are several tutorial videos on how to do this on YouTube, as this has gained some popularity with the New C64.
  10. So ... we now support byte orders either per row (as before) or by labeled arrays per playfield register, both for import and for export. (Reload) https://www.masswerk.at/vcs-tools/TinyPlayfieldEditor/ Not so sure about color. Setting color codes for up to 228 rows each (PAL standard), appears to be pretty exhaustive. I think, this may be best left to manual editing. (Even, if we changed the colors for any consecutive rows below as well, there's no reasonable relation of effort and achievement, both for the user and on the programming side of things.)
  11. This seems to be a viable path to go. (Alps or not. ) We may also try to preselect those semi-intelligently. (E.g., try to identify color, which is probably either the first or the last stream, assume any others are in sequence, only try to identify those, if one is missing.) Something I'd rather want to avoid is an import Wizard, like those found in Excel and similar applications. Those are generally a pain to work with and do rarely what's expected. (So you need to add a preview for the user to check the results and to evaluate consequences of any adjustments, etc, which is somewhat radically opposed to the idea of keeping things simple.) I think, regarding export, it's easy enough to edit the labels when pasting the code into the source and we may rather go with generic ones (like "DataPF1"). [Edit: If there was a previous import, we may, of course, reuse those labels.] Regarding output ordered by rows (as-is), we could go with options to put the color either first or last (always to be exported as a hex number). For a bit of background, this really started as a bigger version of the "Tiny Sprite Editor", which I found personally quite useful. Color didn't matter for sprites that much, it's probably in a separate stream and easy enough to edit manually. Most of the effort for the playfield editor went into handling the various playfield modes and doing the required conversions on the fly. (And also, to do it in a way, which allows to switch formats on the fly without losing data and still keeping things internally somewhat simple – read, reasonably responsive.) Color however was still assumed to be handled best separately. But I can see that it does matter for playfields. Regarding Batari Basic, is there a comprehensive documentation on code formats?
  12. In honor of your mountain theme and to quote Kant, "where others may see flat footpaths, I do see Alps" (or whatever is the common English translation). How may we generalize on this? (E.g., there are just 3 streams, but one label includes "color" – easy enough to recognize by a regular expression –, so one stream is missing. Assume it's all zeros? Assume it's probably PF0, or do further semantic analysis on the labels?) Edit: Is it a robust assumption that every label for playfield data will include either "PF0", "PF1", or "PF2"? Can we make this a requirement? Or, at least, a viable fallback? (E.g., what about multiple screens and there's a label like "PF2_1"?) Again, suggestions, opinions, etc, are welcome… P.S.: I've no experience with Batari BASIC and I'm not necessarily inclined to install it. What are the formats used?
  13. This may be interesting! However, it comes with a few complications, since as-is the editor takes just any stream of bytes in whatever notation as an input. With alternative sequences things are not that clear and we've got to come up with an arbitrary convention. Any suggestions? (E.g., just any label, or any including the string "PF", as a prefix to a stream of bytes?) Edit: The same is also true for color. E.g., inside the editor, we could set row colors by presenting a color picker on a right click. But, are there any conventional code formats for export and import? (Prefix the color as the first item? Have a separate label and stream for colors? Accept any label starting with "C" as a stream of color codes?) Any suggestions are welcome! The principal idea is to facilitate working directly with assembler code. (Which may be of use for interpreting any existing playfield definitions, e.g., for disassemblies, mods, etc, as well.) So what are common formats?
  14. Small update: The principal settings, like symmetry, repeat and line-height are now annotated in an additional comment in the assembler code generated by the editor, which is also used for adjusting these settings on data import. (These settings may appear in any order in a comment starting with "mode", using any kind of separator.)
  • Create New...