Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Locked thread
Tann
Apr 1, 2009

I made a game called civiliz8n click here to play



I'm pretty happy with it so far, I might expand on it a bit by improving the art/adding a splash screen/high score board?

Anyway, play my game and make a game for octojam folks!

Tann fucked around with this message at 23:54 on Oct 5, 2015

Adbot
ADBOT LOVES YOU

Pollyanna
Mar 5, 2005

Milk's on them.


ExiledTinkerer posted:

The Gameduino Project guy did a (porting and such) thing apparently:

http://excamera.com/sphinx/article-chip8.html#chip8

Aw, and I was hoping I'd be the first to do this. :(

I still wanna do this, of course.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Tann posted:

I made a game called civiliz8n click here to play



I'm pretty happy with it so far, I might expand on it a bit by improving the art/adding a splash screen/high score board?

Anyway, play my game and make a game for octojam folks!

This looks cool, will give it a play when I get home.

Buffis
Apr 29, 2006

I paid for this
Fallen Rib
Making progress on my octojam game



Staying without superchip or XO chip for this jam, since I wont have time to make them justice. Limitations are good for me.

Buffis fucked around with this message at 21:30 on Oct 7, 2015

Tann
Apr 1, 2009

That's really cool buffis. The bullet hell shooter works so well with chip8's pixel perfect collision detection!

Tann
Apr 1, 2009

http://octojam.com/octojam-ii/games/civiliz8n

Just submitted my first game to the jam site. Still 20 days left people, make a game!

Fiduciary
Jan 5, 2009
Octo deserves to be pretty.

I've just put the finishing touches on an Octo syntax package for the Atom text editor.

https://github.com/james0x0A/language-octo

TomR
Apr 1, 2003
I both own and operate a pirate ship.
My music player now has a dedicated edit mode. Press 7 to enter and 8 to advance ticks.

Edit: I made a title screen.

http://johnearnest.github.io/Octo/index.html?gist=5fd018baae22420d208b

TomR fucked around with this message at 19:21 on Oct 15, 2015

Explosive Tampons
Jul 9, 2014

Your days are gone!!!
So I'm trying to do a game for the jam but I have no idea how to read and store values into RAM. Help :(

I think that load, save and unpack have something to do with it but I have no idea how to use them.

Dr. Stab
Sep 12, 2010
👨🏻‍⚕️🩺🔪🙀😱🙀
To load, you do something like this:
code:
: cool-load-function
	i := cool-memory-location
	load v1
;
...

: cool-memory-location
	1 2
This will load 1 into v0, and 2 into v1. The load operation also increments i, so that i is now cool-memory-location + 2.

To save:
code:
: cool-save-function
	v0 := 3
	v1 := 4
	i := cool-memory-location
	save v1
;
this puts 3 at cool-memory-location, 4 at cool-memory-location + 1, and leaves i at cool-memory-location + 2.

Explosive Tampons
Jul 9, 2014

Your days are gone!!!

Dr. Stab posted:

To load, you do something like this:


Thanks Dr. Stab, you're the coolest. :)

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Dr. Stab's explanation is perfect.

For the record, :unpack is for more subtle and less common situations. Basically it's a convenient syntax for saying "take the two bytes that would represent an instruction like i := NNN and store it in the registers v0 and v1." You can then write the instruction into memory with save v1. A detailed scenario where you might want to use self-modifying code like this is described in this example and the techniques guide section on multiple indirection.

It's not necessary to use a feature like this in most programs, but it was helpful in making Cave Explorer's code more straightforward.

Buffis
Apr 29, 2006

I paid for this
Fallen Rib
Making progress on danm8ku



Playable work-in-progress here:
http://octojam.com/octojam-ii/games/danm8ku

Tann
Apr 1, 2009

Nice, I love the variance in movement speeds!

Tann
Apr 1, 2009

There's still easily time to make a game for octojam. I'm just starting to think about my second game. Join the fun!

Explosive Tampons
Jul 9, 2014

Your days are gone!!!
Is there any way to inspect RAM during debugging?

edit: managed to fix my code without it but it would still be cool to have a RAM viewer in the debug mode. Am I too dumb to find it or there's nothing like that implemented?

Explosive Tampons fucked around with this message at 05:30 on Oct 23, 2015

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Currently there isn't a RAM inspector, but several people are already tinkering with it.

In the meantime, remember that you can always pause execution, drop into the debugger and then directly use your browser's Javascript console to inspect or modify any part of the emulator state. It's a little clumsy, but very flexible.

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Time for more tales of software archaeology. In the process of disassembling my large corpus of old Chip8 ROMs, I noticed a strange pattern. Quite a few programs would start with a forward jump over a chunk of random-seeming data. Static analysis showed that the data this jump avoided was never read, written or executed. What could it be?

I wrote a little program which searched for this pattern and then attempted to display the contents of the blob as a sequence of ASCII characters. Bingo. On the left is a rom title, and on the right is the string I found:

code:
Alien [Jonas Lindstedt, 1993].ch8                           : "Jonas Lindstedt\0"
Ant - In Search of Coke [Erin S. Catto].ch8                 : "[ Ant v1.0 ]\0"
Astro Dodge [Revival Studios, 2008].ch8                     : "REVIVALSTUDIOS2008\0"
Blinky [Hans Christian Egeberg, 1991].ch8                   : "2.00 C. Egeberg 18/8-'91\0"
BMP Viewer - Hello (C8 example) [Hap, 2005].ch8             : "\0"
Car [Klaus von Sengbusch, 1994].ch8                         : "1.00 K.v.Sengbusch 24/4-'94\0"
Climax Slideshow - Part 1 [Revival Studios, 2008].ch8       : "REVIVALSTUDIOS2008\0"
Climax Slideshow - Part 2 [Revival Studios, 2008].ch8       : "REVIVALSTUDIOS2008\0"
Emutest [Hap, 2006].ch8                                     : "EmuTalk`\0"
Loopz (with difficulty select) [Hap, 2006].ch8              : "(c) A.Daumann\0"
Loopz [Andreas Daumann].ch8                                 : "(c) A.Daumann\0"
Magic Square [David Winter, 1997].ch8                       : "Magic Square v1.0 by David WINTER\0"
Mines! - The minehunter [David Winter, 1997].ch8            : "MINES! 1.00 By David WINTER\0"
SC Test.ch8                                                 : " Tronix (c) 2010\0"
SCSerpinski [Sergey Naydenov, 2010].ch8                     : "C8P\0"
SCStars  [Sergey Naydenov, 2010].ch8                        : "C8P\0"
Sierpinski [Sergey Naydenov, 2010].ch8                      : "C8P`\0"
Single Dragon (Bomber Section) [David Nurser, 1993].ch8     : "(C) D.NURSER\0"
Sirpinski [Sergey Naydenov, 2010].ch8                       : "C8P`\0"
Space Invaders [David Winter] (alt).ch8                     : "SPACE INVADERS v0.9 By David WINTER`\0"
Space Invaders [David Winter].ch8                           : "SPACE INVADERS 0.91 By David WINTER`\0"
Stars [Sergey Naydenov, 2010].ch8                           : "C8P`\0"
Super Astro Dodge [Revival Studios, 2008].ch8               : "REVIVALSTUDIOS2008\0"
SuperTrip8 Demo (2008) [Revival Studios].ch8                : "REVIVALSTUDIOS2008\0"
SuperWorm V3 [RB, 1992].ch8                                 : "Worm\0"
SuperWorm V4 [RB-Revival Studios, 2007].ch8                 : "Superworm v.4, by: RB, Updated by: Martijn Wenting / Revival Studios\0"
Trip8 Demo (2008) [Revival Studios].ch8                     : "REVIVALSTUDIOS2008\0"
Worm V4 [RB-Revival Studios, 2007].ch8                      : "Worm v.4, by: RB, Chip-8 version by: Martijn Wenting / Revival Studios\0"
In most cases, this seems to be an attempt at embedding metadata about the name of the game or the creator. If any of you write an emulator in the future you might consider trying to extract data like this for display purposes, but I only found strings in 28 out of 143 binaries. Octo's disassembler will now print ascii decodings of relevant unreferenced data segments in comments so that this type of thing will stand out better.

Tann
Apr 1, 2009

Do you think this could have been supported/displayed somewhere on old chip8 emulators? It seems curious that so many ROMs would embed data an identical fashion.

Buffis
Apr 29, 2006

I paid for this
Fallen Rib
I made a quick thing yesterday, it looks pretty sweet.
http://johnearnest.github.io/Octo/index.html?gist=2b767e2162f73f6e7652
( https://github.com/buffis/misc-samples/blob/master/Octo/patterns.8o )

3D Megadoodoo
Nov 25, 2010


This program made me go cross-eyed for a second and gave me morgellons.

Dr. Stab
Sep 12, 2010
👨🏻‍⚕️🩺🔪🙀😱🙀
Hey if anyone wants to make a game, Octojam is still open for another 5 seconds.

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."




Does anyone have questions about how Eaty The Alien works?

Quaternion Cat
Feb 19, 2011

Affeline Space
I'd enjoy hearing some more about the game design overall, but in particular the challenges you faced and the solutions you came up with for making a (relatively?) balanced layout of items (and the data structure used) for this type of game - how have things changed (if any) from eg Cave Explorer? I had a brief look at the source, it's not exactly comment heavy but it's not unnavigable either - a higher level description would be cool. Also, I guess a little under half the ROM is the graphics - how big do you think a game like this could get with a simpler visual feel? (btw the graphics in Eaty are really good)

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Like I recommend in my documentation, I started this game by laying out graphics and planning a register map. I did some 128x64 mockup sketches, began slicing them and breaking out various pieces of animations. It's a little scary how quickly you chew through memory at first when you take this approach, but conservatively blocking out all the buffers and graphics you need early on makes it easy to prioritize features and get a sense of how close you are to the wire. I ended up with 153 bytes free, and I never had to sweat over carving out space to cram in something I felt was necessary.

In Cave Explorer I made use of self-modifying code in several places to keep the text drawing routines as fast as possible. In retrospect, that was serious overkill. I didn't use any self-modifying code this time around. Eaty doesn't do any general-purpose text rendering, and the only time I have to hop back and forth between two values in i was during the process of horizontal scroll screen transitions where I have more than enough cycles to work with. I didn't write any custom data-preparation tools for this project, so I limited myself to data structures I could easily write and edit by hand.

The world of Eaty is represented with two tables- rooms and loot-data. The former stores the graphical appearance of the overworld map- 64 bytes per room. The latter stores a list of item positions- roses, skittles, phone parts, etc- 8 bytes per room. With 5 rooms worth of data, that accounts for 360 bytes.

The overworld representation is a sequence of pairs of bytes which represent an offset into the overworld tile data and a y coordinate. The superchip scroll-left and scroll-right instructions scroll 4 pixels at a time, so for silky-smooth transitions I split all the overworld graphics into 4x15 strips, wasting half of the pixels in each contiguous sprite. In retrospect, I think I could've just scrolled 8 pixels at a time and halved the storage of those sprites without a noticeable degradation of quality. By storing Y positions per strip, I was able to get skewed overworld sprites "for free". Here's what that inner drawing loop looks like:

code:
v2 := 0
v3 := 62
loop
	this-room             # fetch address of the current room's tile data
	i += v3               # add offset
	load v1               # v0: sprite offset, v1: y position
	i := overworld-tiles
	iplus16               # multiply v0 by 16 and add to i
	scroll-right
	sprite v2 v1 15       # always draw at the left edge
	wait                  # delay for vblank so you can see it animate
	v3 += -2
	if v3 != -2 then
again
Unskewed and skewed pits, followed by their overworld data:


code:
1 40    2 40    3 40    4 40 # normal
...
1 40    2 41    3 42    4 43 # skewed
Generalizing the game to handle larger environments would require some careful refactoring. The first four rooms vary and the final room is hardcoded- this means all the graphics data for rooms fits in a single 256-byte "page" and indexing via an 8-bit register is easy. Similar deal for the overworld tiles, but currently the tree and pit only take up 144 bytes- I could have afforded to include a second type of tree or some other obstacle.

Logically the world is 1d- a looped linked list of rooms. Every 16-pixel wide strip of the overworld maps to one entry in the loot table, which makes displaying the item indicators very straightforward. The fifth room has fixed "loot"- the UFO pickup zone. At game initialization I then fill the lower 32 bytes of the loot table with random bytes 0-3. In pits, 1s are roses. Anywhere else (ie in an aboveground area) they'll be skittles. After placing inessentials, I do the quick-and-dirty approach for placing essential items- pick a number, check the table, make sure the location is suitable (parts go in pits, call points go on ground) and then place it:

code:
: try-place-part
	v3 := random 31
	is-v3-pit
	if v0 != 1 then jump try-place-part
	i := loot-data
	i += v3
	load v0
	if v0 > 3 then jump try-place-part
	i := loot-data
	i += v3
	v0 := v4 # v4 has the id of the part we're trying to place
	save v0
;
The biggest challenge I had in writing this game was keeping the main display loop tight. A few too many cycles and I'd get a flickery HUD. I was particularly pleased with this snippet, which draws the combination of parts you're carrying or the big countdown timer, contextually. This is the reason the countdown timer is cut off-er, I mean, composed of mysterious but strangely familiar alien glyphs:

code:
v1 := -10
i := part-tiles
i += inventory
if timer != 0 then i := bighex timer
sprite v1 v1 8
There's still room for improvement- particularly avoiding redrawing all those life counter digits every frame- but I had to prioritize and work with the time I had. For anyone else trying to write a complex game in a tight cycle budget I'd recommend trying to draw life or score as bars rather than digits, at least during gameplay.

I think making a larger game world than this one is eminently possible, even without sacrificing too many graphics- coarser scrolling for the overworld could have halved both the size of my overworld tiles and the amount of data needed per room. Loot tables could have been byte-packed; I only used a handful of distinct values for each entry. There are probably a few hundred bytes to squeeze out by aliasing work buffers with initialization code, factoring reused code more aggressively and replacing a few "empty" sprites with special cases in code. The biggest challenge is always going to be representing more variety, so tricks like sprite skewing to get more mileage out of limited graphics data are helpful. I'd really like to see more attempts at roguelikes or adventure games on the chip8 platform- with cleverness and care you may be surprised how much you can pack into 3.5kb.

Internet Janitor fucked around with this message at 02:13 on Nov 5, 2015

Quaternion Cat
Feb 19, 2011

Affeline Space
Thanks for talking about Eaty, I'm really interested in the adventure/exploration genre and wonder how far it could be taken on chip-8.

Additionally, it was suggested to me on IRC that I expound somewhat on pretty much the 2 main components of Octopeg:
1) dealing with large decimal numbers, in the order of hundreds of thousands or millions
2) Interesting looking collisions in an 8 bit math space.

This is however going to come across as more of a badly written report on my experience writing Octopeg, so, bear with me. Honestly, skip to the start of the pictures if you want to know about the collisions, rather than the inane drivel that is me talking about my bad decimal math engine.

Octopeg has a set of functions called BigDecimal for the score and the decimal mathematic trappings thereof. Naturally, I looked at the BCD function for this - it converts a 0-255 register into 3 bytes of hundreds, tens, and units, writing them into 3 bytes of memory based from I. The issues with this were that:
a) I knew I was going to want to have a number bigger than this
b) If you don't have any eg hundreds, you will still write the hundreds bytes value, so I can't easily pack them together in memory.
c) The order in which the digits are written into memory - bcd writes in Hundreds, Tens, Units. This means tracking the I register backwards if your order increases to eg thousands etc.

So I got to thinking and came up with a (not very good) solution: Using the same method you're taught as a kid to add up digits. Yeah...
I decided to lay out, in memory, individual bytes for each decimal digit 0-9 for an arbitrary number of digits, in little endian byte order, so, the first byte is the units, then tens, then hundreds etc. I eventually had 3 of these memory addresses I could refer to, but in their infancy, they were just reading/writing directly to the score. These 3 memory area are A, B and C, running through each digit of A, add each digit of B, and put the result in C and carry any overflow above 10. And then all of the code would modify each of the acquisition functions to add one to the addresses, and repeat.

If you've read that and thought about it any, I imagine you can see that it's actually kind of crap and could be done a lot better. My initial plan was to award points for every collision in real time, however, my strategy here ended up being far too slow, and I replaced it. However, this wasn't entirely a dumb solution.

One of the key elements is that if you were adding a small number to a much larger number, you can stop early rather than process all digits. I decided that B would be the small increasing value (eg 150 points), terminated with 0xff, so in memory would appear as eg 0 5 1 -1. We can conclude two rules about this process: we know that we must continue until we hit this -1 in B, and until we have no carry value. So if we were to add this to 1239850, you would continue to carry into the 10000s column, observe that you have both run out of B to add, and your carry is now empty, and do not need to look at any further digits of the number, so you simply stop processing at the '4' in your result, 1240000. If no carrying were required, we would simply stop after we hit the -1 element of B. Not only does this save you from having to actually access the memory, it saves you from having to redraw, too. This, it turns out, is not the problem.

This result worked ok for fixed bonus values, which I could prewrite into memory, and dealing with however many points you'd earnt during a turn, but, actually calculating those points in a turn was a different kettle of fish now. I decided that the scoring system for Octopeg should award you additional points the more full your combo/fever bar is, hoping to make it so trying to hit as many as possible with a full fever bar before dunking into a pit of your choosing was worth while. Tying these two things together wasn't straight forward, thus, I went back and reconsidered my score format.

Drawing a much simpler 'you hit something' effect was top of the agenda, so, a memory value was read, incremented, and used for a single line's height - gradually filling the combo bar as you hit more pegs. The 'fill' amount of this bar was left in a register, and was the perfect tool for doing the scoring; I set a register to -100 at the start of each turn, and add to this the 'fill' amount, sans the 3 least significant bits. This meant that, for every 8 lines (lined up with the indicators on the bar), you would get an extra 8 points, so, 8, 16, 24 etc points per peg hit depending on the bar, plus an extra 20 if you had the bar filled (64 + 20) - the only requirement is that it be < 100. This value was added to my register storing -100. However, since -100 isn't really a number, really it's just 156, when you eventually add a total > than 100, it would result in an overflow and vf being set to 1. When vf is 1, you subtract 100 from the register, and I used a memory read/write to increase the number of 'hundreds or more I guess' in memory. Storing this overflow in a register would be important to speed things up.

At the end of the turn, subtracting 156, or, adding 100, to the points tracker, combined with use of the previously discarded BCD command, would give access to the units and tens for your score that turn. Rearranging and copying it into my 'points this turn' memory address for BigDecimal, repeating for the 'hundreds or more I guess', followed by a -1, would offer the required format. I also did the classic 'put a fake 0 in' so you got 80 points+ instead of 8, so, the most points you can get in a turn is 255960 I guess - at the maximum bonus of a full fever bar, this would only be like 38 pegs, so, uh, that might actually be possible to hit but it's pretty unlikely.

The short of it is, If I had to do it again (and I probably will), I would design it quite differently:
Firstly, I would remember that you can do I += vn, a command I completely forgot existed until after I'd written BigDecimal. Instead of just having self modifying code that sets I to a given value and then applying a vn offset, it reloads the memory into v0 and v1, sets a working register to one, adds it to v1, handles overflows, and then resaves those memory locations, for all of A, B and C, for each digit, when it could simply store an offset value in a register and skipped 80% of that.
Secondly, I would swap to a big endian byte order and simply start my offset at eg 7 and reduce it each digit so that BCD would be easier to integrate with it.
Finally, BigDecimal was designed to minimise redraws, something it is no longer concerned with and was likely a misguided goal anyway. As a result, there's really no reason to keep it as single digits in each address - I'd pack each byte much the same way as I tracked points in a turn, and would BCD out the units and tens of each value when having to draw the score.

Ultimately I'm not very proud of it at all, but, it did get me really comfortable making tools and dealing with self modifying code, so, there's that.

What I am proud of, however, is the collision mathematics. I invested a lot of time into getting the collisions in Octopeg as reliable and as interesting to look at as I could. Collisions are of course, much more interesting with diagrams, so, there'll be some of those this time!

The first step in having things collide is to know if they are colliding or not. Of course, we use chip 8's vf register to let us know if we drew over a pixel that was already on. However, what do we do next?



I reuse the peg sprite, a single pixel, to search each pixel the ball is composed of. You may note the testing order is a little unusual, but, that's how I ended up doing it. When vf is triggered again during this 1 pixel search, you detect which pixel of the ball collided with the peg. This is just the first step in a long and arduous process, but I hoped to use the above image to draw you in~

Really though from here I need to talk about the ball, and how it moves. The ball's position is expressed to some extent by using 'subpixels', a technique where you define the position, and sort of more importantly, the velocity, with greater precision than you can actually display. There are two obvious ways you could do this: Firstly, in lores mode, the display is only 64 x 32. These only require 6 and 5 bits to define every pixel of. This means that you could store additional information in the 2 and 3 respectively unnecessary bits of the 8 bit registers you define the x and y position in - for example, 0 through 7 could all be quantised to a height of 0, 8 through 15 to 1, and so on. The main problem with this strategy in Chip-8 is that bit shifts are one bit at a time, so you'll need a lot of operations to redraw them, which is no good for a high speed ball moving game that will move every frame. The alternative is to have dedicated subpixel registers. These are registers that you add your velocity into instead of your position directly, and you modify the positions only when you eg under or overflow your subpixels. This is the strategy I used for Octopeg.

With the addition to these subpixel registers, I would need: position-x, position-y, velocity-x, velocity-y, subpixel-x, and subpixel-y. However, I found that I also required more information. Sign-x and Sign-y - easy to access sign bits of the velocities. This was because I needed to know if I should test for an overflow or an underflow of the subpixel registers, and how to modify the position register in such an event. An important factor to note is that, vx and vy are still stored as negative numbers when travelling in the reverse direction, and additionally, due to the seperate sign bits, the ball has a full range of speeds from -256 to 255. I could have really tightened up my code for applying the velocity to the ball if only I'd thought a bit more, but, I needed cycles for the scoring and collision process anyway and this didn't seem to be a big deal and it fell by the way side. If I'd done it right originally, the updates would look a little bit like this:
code:
sx += vx 				# Add velocity to subpixel register
if vf != signx begin  			# If we don't overflow and are -ve, or do overflow and are +ve
	if signx == 0 then px += 1 	# If we're travelling in a positive direction, increase by 1
	if signx == 1 then px += -1 	# If we're travelling in a negative direction, decrease by 1
	end
So, now you know a little bit about how the ball is updated, I can carry on with the collision description. The process of designing the collisions was somewhat iterative, the deeper we go, the higher quality the collisions appear. Let's start at the simplest things we could do when we collide: we could invert our horizontal, or our vertical velocity.

We have pixel information, how about if we attached horizontal bounces to two of them, and vertical bounces to the other two? What would that look like? By the way, some of these ball movement gifs are a little, weird? With how the ball moves? I messed something up and redoing it would take 30 minutes+, there's a youtube link at the end where they all look nice.



Well, honestly that isn't too bad. But, there are some problems:
1) It feels very... boxy? Bounces are all very similar, there is no understanding of the ball as a circle and the peg as a pin.
2) Sometimes, something like this happens:


What is happening here? Is it something we want? If not, how do you detect and resolve it?

Let's consider what we have in our tool box to help us with situation 1. By assigning horizontal and vertical bounces to each corner, we are pretending that each corner is one side of a a square, and that we are colliding with other squares. It's no wonder it looks very boxy!



What could we do instead? Well, really we have 'corners' instead of 'sides' - maybe we should pretend to be a diamond? How do diamonds collide?



OK that's great, but, what does the transform that we apply look like at that point of collision?



The answer in this case, is, noting that our y axis increases in the downward direction here, a reflection on the line y = x. In this case, our x velocity becomes our y velocity, and our y velocity becomes our x velocity. We do not however flip them. However, this only applies to 2 of our diamond sides, the other two sides reflect on a different line, the line y = -x. Hopefully you can guess from the diagram that if we had collided with this corner, the result will be a vector travelling in a -ve direction on both x and y, likely the vector (-4, -1), which is, x = -y, y = -x.

So, let's swap our vx and vy, negating as needed, swapping sign registers et al. What would this look like?



Well, that's... better? It does a lot of good bouncing around in addition to going around in circles a lot. Modelling ourselves as a diamond is a little better than modelling ourselves as a square, but, we're supposed to be a ball, circles can behave in both of these ways. Can we do better?

When we test our ball vs the peg, we test which of our 4 pixels the ball is touching the peg with. This really only gives us a small amount of information. Can we get more information about where the ball is or how it is behaving with relation to the peg? The answer to this question is, of course, yes - we have subpixel information. If we were to look at just the most significant bit of our subpixel registers, this would tell us which corner of a pixel our ball is in. If we combine this information with our collision pixel, we dramatically increase the amount of information we have available:



Now instead of just 4 zones to consider where the peg has collided with us, we have 16 zones to consider. With this multitude of zones, we could consider ourselves a square in the zones along the top bottom and sides, and consider ourselves a diamond in the zones in the corners. Because we can move a whole pixel in a single frame, which is now larger than a single 'zone', we can have the peg penetrate inside our ball model, so, we have to consider the 4 'internal' zones too. I chose to simply model these as diamond type collisions also. So now, instead of being a square or a diamond, we're pretending to be uh, this weird octagon shape:



Now, technically, there's a small problem with the way we pretend to be this shape - Firstly, we don't have sub pixel information for the peg. Secondly, we only actually know the peg even exists when it's already within our 2x2 body. So, sort of what we're doing here is wrong? If you consider the opposing peg in the top left corner vs the bottom right corner, the ball can only use the extra subpixel information to move itself down and to the right, which results in an imbalanced new perspective on the peg's location. Fortunately, we can't really do any better than this approximation anyway, and this will happen fast enough that no one will notice or really stop to think about what might be happening.



Anyway, having decided to just roll with it, we need to use some brain logic. Our collision pixels are numbered from 0 to 3, and we can combine our sy and sx msbs into the 2nd and 1st bit of another value, to create a similar 0 to 3 value. Observe this figure I drew:



So for example, if we are in the 00 pixel, then we want to be a corner in state 00 or 11 subpixel, and a side in 01 or 10. Corner for 01 in 01 or 10, side in 00 or 11, etc. You may quickly observe that the logic for this is: if CollisionPixel == SubPixel or CollisionPixel == (SubPixel ^ 0b11) then Corner else Side (^ in this case being XOR). This is a test we can easily do in CHIP-8, and it is in fact the exact test that I perform. You can talso perform tests on bit 2 of CollisionPixel vs Subpixel (match: top/bottom, different: side - eg, cp = 0b01 and sub = 0b11 => side) to work out if we want to present a horizontal or vertical side when we pretend to be a square.

Now, what does THIS look like?



Getting better. But, there's still some problems. We're modelling our ball a lot better now, but, a whiles back we noticed a problem. This:


What's going on here? In this case, our ball happens to move on x and y such that it sort of... catches? the peg with its corner. Back when we recorded this footage, we were only doing a horizontal inversion, so, here's a diagram of what's going on here:



How do we describe what is going wrong here? In just this case, if we were bouncing, ideally, we would be leaving the way we were already going. If you're mathematically minded at all, you could say something like the dot product of our relative entry velocity and a line perpendicular to our line of reflection vs our relative position vector & the same having the same signs means it will be bad. If you're not mathematically minded at all, then, well, this:



So... given uh, that, we need to somehow detect and resolve this. For a horizontal bounce like this, this is straight forwards and we actually don't need to do any maths at all really: If our horizontal velocity sign matches the appropriate bit of the detected pixel as 0bYX, then, don't bounce because it will look weird? This kind of filtering works ok for our 'square' type collision, but, if this is a problem here, it's also a problem with our diamond type collisions. How do we test the complicated dot product thing I said earlier when we reflect on the line y = x?

Well, this is actually remarkably simple. The relative position business is implicit to which of our pixels we collide with. We do however need to analyse our relative velocity. Or really just our velocity because the peg can't move. Let's look at this figure:



Here I've drawn an 'X' over our ball, and drawn on several velocities it might have. It's colliding with a peg in its upper right corner, and it's modelling a corner/diamond type collision. We expect it to reflect on the line y = x. If we came in with the Green velocity, we would expect a result that looked like the Blue velocity. And I'm sure if it came in with the red velocity it would look fine too. But, if it collided while travelling with either the Blue or Black velocities, how would it bounce? Remember that it CAN collide like this, even though the peg would have sort of had to have phased through part of the ball - we don't get to model that event as our collision data will only trigger on whole pixel moves, all we have is the state we've found ourselves in. In these cases, it would bounce 'towards' the peg, which is the precise case we don't like.

If we discount the impossible zone, where if we were travelling with a velocity in that zone, we could never collide with the peg unless we moved 2 pixels in a single step, then we can combine our sign information with another piece of information to detect these situations. If the X sign is Negative, we have a problem only if we are in the LEFT/RIGHT area of the X. If the Y sign is Positive, we have a problem if we're in the UP/DOWN area of the X. If we are not in the corresponding areas, then there is no problem. Additionally, since we know we can't be in the impossible zone, we actually know what kind of bounce we could do *instead* of a diamond bounce. If we were the blue line, we could do a vertical bounce, and if we were the black line, we could do a horizontal bounce. That's great!

Now, how do we know which zone of the X we are in? Well, it's actually super simple: if our X velocity is greater than our Y velocity, then, we must be in the LR zone. If it's not, then we're in the UD zone. It's that simple. I wrote all of the pixel match ups vs LR/UD states by hand, I'm sure there's some bit comparison you could do, but given Chip-8's options, I'm not sure it would work out faster. Now that we filter these 'unwanted' collisions, what does this look like?



That's not bad... but, can we do better? Currently, we present a very large 'flat' surface to our pegs. These result in horizontal or vertical reflections. These are boring reflections, and they don't add much dynamicism to our ball as it bounces around. Could we take our diamond type reflections further? What if instead of presenting as that octagonal shape from earlier, we present ourselves as this shape:



These new edges would generate reflections on the lines y=2x and y=0.5x (and the inverses there of). Having solved our problems with reflecting into pegs by comparing the ratio of y vs x, we can imagine that we will need to do the same thing if we want these fancier reflections. Fortunately, we can do a couple of bitshifts, if we shift right, then we divide by 2, so, we can compare vx with a shifted vy, and a shifted vx with vy. If 0.5 of vx is still bigger than vy, then, we must be really moving it from left to right, hence, we're in a super LR zone, and if 0.5vy is bigger than vx, then we must be in a super UD zone.



We can use these zones to filter out our unwanted reflections for these news lines of reflection, although we do need to select which one is appropriate for our collision type, so, there has to be some canoodling of data between our subpixel state and our X zone detection/selection. I opted to determine all the zone states into a single register as packed bits (so eg 0x7 would be in the UD zone for all Xs), and in a bitmask depending on subpixel state to determine which one we care about, and then tested if the X zone was 0 or not when filtering collisions.

Anyway, so, while we can adequately filter these new collision types, how do we actually reflect on these lines. Well, I thought this was easy, 'oh yeah you just add some divided by two bits around it'll be simple'. I was quite wrong; the correct equation for reflecting on the line y = 2x is, as you can see here, x = 0.8y - 0.6x, y = 0.8x + 0.6y, or 4/5s and 3/5ths respectively. The different arrangements of -2x, 0.5x etc use these same coefficients, but, juggle the signs around, so eg 0.8 + 0.6 etc. Doing division by 5 was uh, not going to be a thing I wanted to do or even something I actually know where to get started with in 8 bit so, I decided that, maybe 0.75 and 0.5 would be ok. So that's 1 shift right for 0.5, and then another for 0.25, and then adding that in as required for each value.

The first time I tried this however I ran into a problem. First of all, I completely lost track of the correct value for the separate sign bit, second of all, 255 * 0.75 + 255 * 0.5 is going to be > 255, so, there's an overflow problem to consider too. As a result, I decided that the correct course of action was to a) shift everything down by 1 and throw a bit away so that I could detect and cap the velocities when this overflow/underflow for -vs would occur and b) shift everything down by another one and use the most significant bit as a sign bit, and do real twos compliment math. This means that at the start of this routine, there's 3 bitshifts down, and then oring in 0xE0 if the sign bits are set to fill out the 3 MSBs correctly.

The result is that, when shifting back up, you first shift out your new sign bit into vf, retain this in some way (I used a logic branch) then shift again and compare vf. If the first bit was 1 and the 2nd 0, then, we are travelling -ve and have had an underflow, thus, cap at -ve vel max, if the first bit was 0 and the 2nd was 0, then, we're +ve and we haven't had an overflow. In addition, there is the special case of 0b1100000, which will be -256 when shift back up. This isn't going to work for us unfortunately, so it has to be detected and capped to -ve vel max also. The ultimate result of all of this is actually to rob quite a lot of velocity away from the ball. As it turns out though, that's actually a good thing. Slowing the ball down in some way was an important elemnt, and in fact, it's probably one of the hardest elements to implement independently, and these bounce types gave it for free.

And that's it, that's Octopeg's peg collision & reflection system in a very large and extremely longwinded nutshell. The final result looks like this:



And here's that youtube link of all the bounce types with the right fps:

https://www.youtube.com/watch?v=qY2OpMRxsGk

edit - fixed a 0x11 to 0b11 and also :3: VVV also technically I mostly meant that it would get a bad grade or something because I wrote 'I did x' a lot.

Quaternion Cat fucked around with this message at 09:15 on Nov 6, 2015

Explosive Tampons
Jul 9, 2014

Your days are gone!!!
Mastigophoran your work on OctoPeg was great and your post made me appreciate what went into the game even more, thank you for writing your "badly written report".

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Fascinating devlog, Mastigophoran.

Eaty The Alien takes a much simpler approach to score- I store life force in two bytes and cap each to a value of 99. I can then decode the value 2 digits at a time via bcd. All I needed for my mechanics was a routine for decrementing the life force (and checking if you died) and awarding extra life force. By making health pickups always come in multiples of 100 I could get away with simply adding to the high-order byte, with no need for carry logic.

Collision is extremely dirty compared to OctoPeg- I used pixel overlapping as a cheap first pass and then if that was triggered I did an AABB-style overlap check. Eaty's fairly complex animations created a lot of problems, though, because pixel overlapping varied between frames- if you fall in a pit and then come back out in the pixel position you were at just before you overlapped with the pit you could easily end up in a different frame, and already be colliding. This inadvertently recreated the most annoying feature of the original E.T. game. Since I didn't have much time to shore this up properly, the quick-and-nasty fix to this was to detect these situations and then randomly teleport Eaty until he wasn't overlapping any objects. In the future I think it may be better to avoid trying to use animated sprites in combination with vF-collision at all. If anyone is thinking about doing this in their own games, go with a dedicated "collision mask" sprite which is drawn before the real sprite and which is consistent across animation cycles.

Buffis
Apr 29, 2006

I paid for this
Fallen Rib
Danm8ku postmortem / devlog

I started off writing danm8ku as a bullet hell engine for chip-8 danm8ku games, and i built it upon this pretty simplistic game which shows off some of the features.
I tried to keep the difficulty reasonable, but looking at the octojam twitch stream, I guess people without much bullet hell game experience just can't play these games very well anyways, so I might as well have made some patterns a bit harder.

Writing this up with quite a lot of detail, but someone might find some stuff in here interesting.

First off, here's what the game looks like when played through:


== Title screen ==

The title screen is actually pretty neat.
Here is everything but the title screen removed, running at a slower pace to show off how it works:
http://johnearnest.github.io/Octo/index.html?gist=de467a09480be597df33


It first draws the text, and then increments a 8 bit binary counter for a random line.
Basically, algorithm is (for one side of the screen):

  1. Get a random line
  2. Set the X position to 0
  3. Draw a sprite at position X
  4. If I drew over an existing sprite, increment X by 4 and go to step 3
  5. Repeat

Removing the text drawing part makes it even easier to follow (less than 50 bytes):
http://johnearnest.github.io/Octo/index.html?gist=586e37d76decd37fc78f

== Bullet engine ==

Most of the effort is put into the bullet engine, which is actually rather complex, since it requires allocating and moving up to 32 bullets on screen at the same time, which gets awkward even when running the game at 1000 cycles (Octo games generally have very few moving sprites).

Each bullet takes up 8 bytes of space, containing:
  • x position (two bytes)
  • y position (two bytes)
  • x velocity (one byte)
  • y velocity (one byte)
  • x acceleration (one byte)
  • sprite offset (one byte)

In practice, very few bits of x acceleration are actually needed, and it should be possible to merge it with sprite offset, although it would require a bit more computations, and the engine is already running very close to the cpu limit, so this seemed fine.

The bullets are stored in a 256byte dedicated area (memory address 3000), and should essentially be considered an array. Un-allocated bullets will have the x velocity set to 255, which is a magic value saying that its address is free for allocation. This should probably live in sprite offset instead, but whatever.

Whenever a tick in the engine occurs, move_bullet will be done on each active bullet. This updates bullets positions, and sets a sprite offset based on the current direction. This method is heavily optimized in some non-obvious ways. One interesting difference is that x and y velocities are signed values, but they are not using ones compliment.

Position is then updated based on velocity using the following logic

code:
	
    vc := vx       # store velocity in a temp variable for later
    vx <<= vx      # multiply by two, check for neg bit             
    va := vf       # va will be 1 here if the velocity is negative
    x_f += vx      # add the velocity to the one byte fractional counter of x,
                   # if this overflows, we should move the bullet one step
    vx := vc       # restore velocity

    # Hack to avoid jumps/comparisons to do:
    # if vf != 0 begin
    #   if va == 0 then x += 1
    #   if va == 1 then x -= 1
    # end
    va &= vf
    v8 := vf
    v8 -= va
    v8 -= va
    x += v8


I tried doing something like this with "regular" signed arithmetic, but couldn't get it this fast.
One nice thing here, is that v8 will be set to 1 if x velocity is positive, -1 if negative, and 0 if not moving.
This means that we can do this for both X and Y, and then use these values to setup the sprite_offset byte to a unique value for all 9 possible movements:

code:
   # Set sprite_offset to values used to get movement sprites.
   # v8 is X direction
   # vd is Y direcation
   so := v8 
   if v8 > 1 then
     so := 2
   if vd > 1 then
     vd := 2
   vd <<= vd
   vd <<= vd
   so |= vd
Then when drawing the bullet, I do:
code:
	i := bul_nop
	so <<= so   # multiply by four, since each sprite is four bytes
	so <<= so 
	i += so
	sprite x y 4
This then fetches the next sprite data to XOR to the bullet, to make it look like its moving in that direction, without having to call clear.
Data is:
code:
    # Sprite movement data
    # ORDER OF THESE ARE IMPORTANT. DON'T REORDER.
    : bul_nop 0x00 0x00 0x00 0x00   # 0
    : bul_r 0x00 0xA0 0xA0 0x00     # 1
    : bul_l 0x00 0x50 0x50 0x00     # 2
    : bul   0x00 0x60 0x60 0x00     # 3 (not used for movement?)
    : bul_d 0x60 0x00 0x60 0x00     # 4
    : bul_dr 0xC0 0xA0 0x60 0x00    # 5
    : bul_dl 0x30 0x50 0x60 0x00    # 6
    : bullet_count 0                # 7 (unused so storing other stuff here)
    : player_position 5 15
    : UNUSED1 0
    : bul_u 0x00 0x60 0x00 0x60     # 8
    : bul_ur 0x00 0x60 0xA0 0xC0    # 9
    : bul_ul 0x00 0x60 0x50 0x30    # 10
Note that the sprite_offset can't map to values 3 or 7, so it's possible to store some other data there, as long as it's four bytes to keep the offsets correct. Here, I actually store the initial bullet sprite "bul" + some unrelated variables, just to save ROM storage.

Clear data works the same (see clr_nop and onwards). Most other stuff with the handling is pretty straight forward.

Spawrning a bullet is done by doing something like:
code:
    gogo_bullet x := 35 y := vc vx := 66 vy := 12 ax := 2 pew_pew
Subsequent bullets that should use similar attributes, with just a few changes, can just modify whats needed, like:
code:
    gogo_bullet x := 50 y := vc vx := 230 vy := 0 pew_pew
    gogo_bullet x := 54 pew_pew  # everything but X start position is the same as the first
    gogo_bullet x := 58 pew_pew  # everything but X start position is the same as the first
Technically, the lines above will only work assuming that the currently active pattern wont reach the max limit of bullets.
If the pattern should spawn lots of bullets (like pattern 2), the pattern will need to check that v8 is non-zero, after calling gogo_bullet, example:
code:
    gogo_bullet   # will set v8 to 1 if a bullet can be spawned
    if v8 == 1 begin
        vx := random 127
        vx += 127
        x := 57 y := vc  vy := 35 ax := 0 pew_pew
    end
== Pattern engine ==

Since the bullet engine has this nice interface described above for spawning bullets, the pattern engine can be pretty simple.
Each pattern is just a memoryblock, ending with a jump to "pattern_done", these patterns are then store after eachother and accessed through a jump0 call.

code:
: pattern1  # example pattern
    v0 := 0b00000111
    v0 &= vd
    if v0 == 0b00000111 begin
        vc := random 31
        gogo_bullet
        ax := 2
        vx := random 63
        vx += 170
        x := 55 y := vc pew_pew
    end
    jump pattern_done

: pattern
    jump pattern1
    jump pattern2
    jump pattern3
    jump pattern4
    jump pattern5
    jump pattern6
    jump pattern7
    jump pattern8
    jump youwin

The main game loop keep track of the number of ticks, and when a counter is reached, will increment the pattern_state by 2, and each tick, the following logic will make sure that the pattern is executed:
code:
    i := pattern_state
    load v0
    jump0 patterns
Each pattern then has a register "vd" set to the number of ticks that has triggered inside the pattern, making it possible to make certain bullets spawn at certain times, allowing for complex things!


== Ending screen ==

The ending screen is also pretty neat, although less minimalistic (and not really optimized) compared to the startup screen:
http://johnearnest.github.io/Octo/index.html?gist=d315690f033bde3188e4


Drawing it slowly should make it pretty visible that the algorithm for this is extremely simple:
http://johnearnest.github.io/Octo/index.html?gist=20c8e5265cedf41265cc


The data being drawn is the first 4 lines of the Zero in the built in Octo font.


== Retrospective ==

Looking at the jonterp twitch stream of this game, where they were unable to get past the second level, I guess this game isn't really for everyone, but I only really wrote it for myself, and I'm pretty pleased with it.

Some patterns are not very thought through, since I found the main engine the most exciting part, so I might come back to the engine later, and produce something new. We'll see.

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
New game by a non-goon- Mastermind:


http://johnearnest.github.io/Octo/index.html?gist=c4edfbcea14f960c628a

William Donnelly, the fellow who submitted "SpaceJam!", intended to make this for the Octo-ber jam as well but ran out of time. Lots of details in the source code!

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Octo-ber Jam 2 playthrough footage is now up- Thanks to Jonterp and Everdraed for helping me record and edit the video, and thanks to Doug Crawford who provided some footage of games running on real COSMAC VIP hardware!

https://www.youtube.com/watch?v=fxPoscaB8aE

Edit: whoops, first upload was missing a few seconds of footage. New version will be up shortly.

Internet Janitor fucked around with this message at 06:16 on Nov 9, 2015

TomR
Apr 1, 2003
I both own and operate a pirate ship.
Those are some really good write ups. Thanks for taken the time to go into so much detail.

Tann
Apr 1, 2009

Yeah I really enjoyed watching it, especially seeing the real hardware at the end!

duomo
Oct 9, 2007




Soiled Meat

duomo fucked around with this message at 05:54 on May 4, 2016

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Is there a benchmark function for Octo to find out how many instructions/sec or /frame it can do on a certain PC?

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Not presently. Are you volunteering to write 2DMark '77?

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Internet Janitor posted:

Not presently. Are you volunteering to write 2DMark '77?

OK, sure. :)

I was thinking of approaching it from the other direction -- that is, running a program and counting cycles in the emulator. But this was fun to make. I am not certain this is error free by any means, but it gets numbers close to what I calculate. I edited the octo cycles/frame dropdown and it works at 100000 but not 1000000.







https://johnearnest.github.io/Octo/index.html?gist=08bbd489f8f6c5bc317a2f68a1a3b378
https://github.com/jdeeny/chipmark77

I suppose that .08 is a bug.

taqueso fucked around with this message at 03:05 on Jun 13, 2016

Quaternion Cat
Feb 19, 2011

Affeline Space
I tried this on a few devices, a samsung s2, an iPad mini 4 and running from 1 to 6 instances on my laptop, all report 60.08kOps - is this technique working as expected?

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Mastigophoran posted:

I tried this on a few devices, a samsung s2, an iPad mini 4 and running from 1 to 6 instances on my laptop, all report 60.08kOps - is this technique working as expected?

If the emulator is running at 1000 cycles/frame then it should report 60 kOPS. 1000 cycles/frame * 60 frames/sec = 60000 cycles/sec = 60k cycles/sec. It is only interesting if you can run the emulator without any cycle delay.

taqueso fucked around with this message at 00:36 on Jun 13, 2016

Adbot
ADBOT LOVES YOU

Dr. Stab
Sep 12, 2010
👨🏻‍⚕️🩺🔪🙀😱🙀
I saw this thread was bumped so I had to make a program.

Like all good programs, it's a modified version of the sample person program



http://johnearnest.github.io/Octo/index.html?gist=6a3642baee4a53f8c6b2484eaa0fd3df

WASD - move cursor
V - draw
C - erase
1 2 3 - choose brush
Z X - choose layer

  • Locked thread