Paddle games like BreakOut and Pong are not as easy to code as they seem. Out of the dozen of QuickBASIC Arkanoid-likes released in the late '90s to early 2000s, only two of them (that I'm aware of) are truly worth playing: Arkanoid (released in 2001 by Aleksander Trojanowski) and Outbreak (released the same year by Dillon, Michael and Isaac — I covered that one already.) Everything else suffers from the same issues: lackluster gameplay with no power ups (although that's pretty much what the original BreakOut was,) slow bloated code that's impossible to compile (Pearl 5 and Outbreak both have this issue,) and terrible ball physics paired with game-breaking collisions. That being said, Outbreak is a lot of fun with very tight controls, try that one out if you can!
Why you do?
Every QuickBASIC paddle game I've played so far uses the exact same strategy to resolve collisions: POINTing video memory for pixel attribute and use special attributes to handle ball collision and resolution. Although very simple to implement, this technique also comes with a number of drawbacks. First, pixels are non-directional which means it is not possible to compute proper reflection. Second, it is not possible to have a projectile that moves more than one pixel per game tick unless you're willing to turn the code into a pixel hunt. Third, to simplify collision detection, the projectile would only travel in four directions (diagonal top-left, top-right, bottom-right or bottom-left.) Fourth, rendering errors will very likely screw collisions. So, let's use math instead, shall we?
I too, used to hate collisions
Initially, I went for a simple approach with orthogonal vectors where the ball would change direction according to the vector's alignment (vertical or horizontal.) But I didn't like the idea of doing the math for intersections only to half-ass the reflection with a "IF... THEN..." solution and so I went the extra mile and wrote a proper mirroring code. But now that I could get the proper reflection regardless of the vector's slope and ball's travel direction, why stick to orthogonal vectors? In the end, the engine is flexible enough that bricks can be any shape and any number of edges (just like the level hull,) yet I didn't do anything with it because creating levels this way would have been a pain... and after plenty of play-testing, I realized that slopes are not that fun to deal with.
The collision routines I wrote are still far from perfect though: they tend to really slow things down where there's more than one projectile on the screen and some glitches still happen now and then (especially when hitting edges or acute angles.) Internally, all solid walls (line segments) are regrouped into objects so we know what object the ball is interacting with, and we can speed up collision testing by checking if the ball movement's bounding box overlaps the object's bounding box. If the ball is located on the left side of the wall, we proceed to test collisions. A new coordinate is computed for the ball if the movement vector intersects with a line segment: a dummy line segment is created in front of the original (the distance is exactly the ball radius,) and the new coordinates are the intersection of the movement vector and the dummy wall. In theory it works. In practice, depending on the order in which line segments are listed, the ball may be pushed through a previously tested segment (hence the acute angle bug.)
But what about dynamic objects? What if the paddle moves on top of the ball, or collides with the ball during movement? To handle these cases, paddle physics are done via another routine that checks if any "edges" of the ball is located inside the paddle movement box and pushes it accordingly. The ball is destroyed if it is pushed outside level boundaries. There's also another rare bug where an unexpected reflection angle is computed after the ball hits an edge (where two vectors meet.) Edges should probably be a special case entirely.
What did I learn? I still hate collisions. And I now hate myself too apparently. At the very least, I know what to look out for next time.
What changed since the demo
At first I thought I could get away with a simple scripting method to create levels but it was a "sub-optimal choice" as they say in the banking community (or is that speed runners? I don't know,) and so I decided to write a level editor instead. The file format is slightly different from the demo (the header is different and the way cells are handled is also completely new.) If anything, it contains a decent line drawing routine using Bresenham's algorithm (since there's no LINE statement for Mode Y.) The big thing about the level editor is that converting BMP files to tiles is much easier than before.
I also fixed tons of little bugs, from faulty saved games to the score table not updating properly. Level files are very different, missing icons are finally here and the story has been shortened because nobody cares about stories in BreakOut clones, let's be honest. Since palette cycling was implemented to give the fireball its glow, I thought it would be nice to use this technique to animate the new backgrounds and bricks... and so I did. It's cheap in terms of resources and it looks nice; well worth the effort.
The menu system is an extension of what was done for Minesweeper Duo: a few more instructions were added and proper conditional branching (IF ... ELSE ... ENDIF) was added to the tokenizer. The format is still pretty awkward and lacks flexibility (for instance it is possible to test variables against INTEGERs, but not against other types of data or other variables) but it made it possible to add more content without bloating the already large code base: the story screen, along with the high-scores and settings are coded with that system. Thus, new variables and options can be added without having to modify the engine. If I ever manage to come up with a cleaner, more flexible interpreter, maybe a similar scripting system could be used to handle game rules (how power ups, bricks, balls, projectiles, etc. behave)
Crushing bytes and optimization
I'm always amazed when a compiled QBASIC program reaches 100K in size. It's really big. I usually crush my binaries using Fabrice Bellard's LZEXE before sharing, but why compress the binary when it can be reduced some other way? Depending on the instructions present in the source code, the compiler may decide to add related instructions to the executable even if these extra instructions are not present in the source (see BASIC Techniques And Utilities by Ethan Winer...) For instance, because of a SCREEN statement, the compiler also included a bunch of graphic instructions that I do not need and cannot use in Mode Y anyway. Replacing SCREEN and LOCATE with CALL INTERRUPTX 0x10 (functions 0x00 and 0x02,) reduced the executable size by about 10%. A little crush with LZEXE and presto, we spare an extra 30%!
QuickBASIC is universally known for being about as fast as a snail with a broken leg, and while I will admit that it is far from the fastest language around, I also believe this reputation was unfairly earned due to shabby code written by teenagers. To make it clear, I'm not blaming abecedarians (I'll have you know I'm quite a Connnoisseur when it comes to writing bad programs as my first games looked like cold unappetizing spaghetti code;) I'm blaming those who think the language is responsible for badly written programs. Anyway...
There are at least two things that could be done to speed up the game. First, the whole screen shouldn't be redrawn every frame like it is now. The game uses three video pages for display: one contains all the "mostly static" stuff (background, bricks, GUI) and two are used as the current and next frame. To not check each individual brick each frame, they are drawn once on the static buffer along with the background. Every frame, that static page is pasted to the next frame and sprites are added. It would be much faster to use the static buffer to erase changing elements, and only repaint sprites from one frame to the other. Second, collisions are done with single-precision floating point operations (32 bits.) Ideally, all calculations should be done with integers. After all, the whole Build Engine is working with integers (except for computing slopes,) so that's definitely possible... if only orthogonal line segments and boxes are used, a tweaked Bresenham's algorithm should be faster and more reliable to compute collisions.
Minimal system requirements would be 3000 cycles in DOSBox (5000 recommended,) and a VGA card with 256Kb of memory. The game supports keyboard, mouse and gamepad. SoundBlaster sound effects are also supported as long as 4 pages of EMS 4.0 are available (I'm not sure why I realied on EMS since there was enough memory left in the far heap to store sound there.) Download includes the game (of course) and a bunch of editing tools. Everything is sourced as always. Happy busting!