Angry Birds Tech Stuff
This writeup came into being during my work on the Angry Birds project and should document the ups and downs of the development phase - also as a document for later times to read over it again and reproduce many of the design decisions of the game.
Are you Angry?
Pixel-8-Studios in cooperation with 8-Bit-Shack are trying to do a tribute port of Angry Birds to the Apple II in Double Lores.
Basically this project is driven by an advancement of the DOLORES library. The version behind this project will be V.14.x which consists of a library that includes the Double Lores features of the last versions with the sprite engine as well as an Ampersand interface for communication with AppleSoft.
Furthermore the library contains a music player for Mockingboards that is completely running in AUX memory and its own joystick driver that does not interfere with the music player. The library is designed to give the user a full working AppleSoft environment, as well as normal access to ProDOS. Hence it is possible to write the game code in AppleSoft and/or assembler and use ProDOS for file operations.
We believe that games in Double Lores are fun and should be more in the focus of game development. With the DOLORES framework the programmer has the possibility to easily combine AppleSoft and assembler routines in order to realize also a complex game concept.
We will keep you updated about the further development progress.
Angry Birds - Teaser
Another short teaser video of the upcoming DOLORES game engine project Angry Birds port. We have come a bit further down the road with the menu system in AppleSoft basic.
Now the music won't be interrupted when switching screens since no load operatvions are necessary anymore in the menu section. The hi-score system was set up reading hi-score data from a binary file and displaying it using the new &PRINT-function from the DOLORES library. The library now contains a small font (3x5 besides W and M are 5x5) which is used by &PRINT to display the string content on the screen.
The character generator itself uses the basic DOLORES plot function to put the pixel on the screen which is fast enough allowing a nice color animation effect on the scoreboard. Next will be the basic game mechanics like background scrolling and a small "physics" engine for calculating the projectiles trajectory. This will for sure cause quite a headache - but it is lots of fun!
Menu System and Game Engine Integration
Current achievements in the development of the Angry Birds game port to the Apple II is on one hand the finalisation of the High-Score management system by Daniel and on the other hand the basic game routine integration into the AppleSoft framework by myself.
In the attached video you can see the seamless collaboration of AppleSoft and assembly routines. If you press PLAY the main game routine starts and at the moment only the background scroller is implemented which will be used when the birds are flying from the slingshot to the targets.
By pressing Q you leave the game routine and get back to basic where - at the moment - a random score is generated and sorted into the high-score list. You can select your initials with the joystick if your score is under the best 5 scores achieved so far. If not you will get a sorry-message.
Upon quitting the game, the high-score list will be saved to disk and reloaded again on the next game startup.
Now we need to implement the game mechanics and some physics which I fear the worst. This will take some time but we hope you enjoy what we have achieved so far...
Let's look at some physics today...
Currently I am working on the motion of the bird when it gets fired from the slingshot at different angles and velocities according to the tension of the sling. But before I am diving into physical details I want to discuss another point shown in the video below.
I added double buffering for the bird's trajectory display algorithm in order to make the display of the bird's flight smoother and suppress the flickering of the sprite being deleted and redrawn. The user can toggle the double buffering with the key "D" while being in the slingshot control part of the game.
The first part of the video is recorded without double buffering and the second part with double buffering switched on. The flight's speed is slowed down with double buffering but the visual appearance is superior from my point of view. I would be happy to receive feedback from you on this feature. Maybe I leave the option key in the game so each user can decide how he/she wants to play the game.
Now a bit to the physics and how the motion equations are solved. Basically we have an oblique shot of an object with an angle and a starting velocity as a vector. For the sake of simplicity we avoid air friction effects. We also try to avoid any sine or cosine calculations which are CPU cycle intense.
The velocity vector v can be decomposed in its components vx and vy which can be evaluated independently from each other which makes life much easier now. The movement in x-direction with vx is a steady motion. This means vx = const during the whole bird's flight. The value of vx is determined from the distance the sling is stretched by the user.
The distance travelled in x-direction can be easily calculated by x = vx * t, however, since I do not want to multiply I am quantising the time and summing up the distances travelled. So I only need a single addition for that.
The movement in y-direction is an accelerated motion by the influence of gravity. Initial vy0 is also given by the initial sling stretch. The current speed in y-direction can be calculated by vy = vy0 - g * t. We quantise time again and set t = 1 which yields vy(t) = vy(t-1) - g. The gravity constant g needs to be chosen as required to yield a nice looking trajectory result. Here the value 1 shows the best results.
We use this simple iterative sum function to calculate the y-position of the bird at every timestep by y(t+1) = y(t) + vy(t) * t (with t = 1 as quantised time) which approximates the regularly required motion integrals well enough to result in a nice bird flight's parabola. This is not 100% exact but for the game it will answer the purpose intended.
Hence calculating the motion equations simplifies in a simple iterative algorithm by quantising the time steps (which is set here to 1). The simple algorithm is shown in the image below (remember that the positive direction of the y-axis is downwards on the graphics screen!).
The Target Parser
For the first version of the Angry Bird clone we decided to keep the target rather simple. We have a block and a pig and put these objects on a 5 x 5 pixels grid divided into cells. The pig itself occupies 4 cells in this approach.
In order to store level data with little memory expense I decided to stuff the content of four cells into a single byte leaving two bits per cell for defining its contents. At the moment we can have a block, a pig or an empty space. This enables to store the level data in a nice compact way.
The target parser reads these bytes and translates the bit string into sprites drawn into the appropriate cells and thus building the target. For visual effects the target buildup was slowed down to improve the user experience as shown at the beginning of the video below. Note that the bird does not yet collide with the target since collision detection has not yet been implemented.
For the collision detection algorithm - which is by far not yet implemented - a target matrix is set up simultaneously while parsing the bit string so the algorithm can quickly check the bird's position against any cell which may or may not be occupied and hence detect a collision of the objects or not. I don't know yet if this idea will work out at the end but for sake of simplicity regarding code size and execution speed it might be useful.
We want to study if a nice game mechanics can already be achieved by this rather simple layout and underlying physics model which only takes gravitation into account. We decided to try if we can make it work essentially and may add more features along the way when the first game engine works as we expect it to do.
Will the game be already fun when we are done with our simple approach? Honestly we don't know. But we already know that we will try to improve things once we are done with the first release. However, we hope you will have fun playing it - if we really manage to finish this project!
Accuracy, Accuracy!
After developing the target construction algorithm I tried a simple collision detection if it is easily possible to detect collisions with target objects while the bird is flying on its trajectory. It turned out, that at certain speeds the timing interval of the parametrised motion equations lead to too large leaps of the bird sprite bypassing target cells which have been chosen to be 5x5 pixels at the moment.
That was the end of my 8-bit only dream...
I was calculating the whole trajectory with an 8-bit value in X- and Y-direction assuming the gravity top be 1 and the time interval being defined to be 1. This yielded nice parabolas as you could see from the earlier video, well with large leaps when the velocities in X- and Y-direction were high.
What could be done to improve this? For the game mechanics in order to work it would require to make target collision detection possible even when the whole animation speed will slow down at smaller time steps. Due to the extra timesteps the bird would be drawn on the screen moreoften to also improve the bird's flight trajectory animation.
Another idea was to get rid of the time as the parameter in the vx(t)- and vy(t)-equations by combining the equations and work out something like y(x)=... that is the explicit parabola equation which would yield the screen position y at every coordinate x. To make this work I would have to implement a fairly amount of extra maths for evaluating the parabola equation or use precalculated data tables or both which I could not be bothered to try (yet). Another drawback of the y(x)-method would be the lack of momentum i.e. the effect on the bird's velocity due to the acting accelerations depending on the current flight time t which gets lost in the y(x)-formulation since the parameter t has been eliminated from the equation. This would yield an unnatural flight velocity behaviour.
Since the timestep in the 8-bit formulation was set to 1, it showed sine qua non that a smaller timestep like 0.5 was more favourable to get more points on the trajectory which can be on the one hand all displayed and on the other hand be used for checking a collision.
In the 8-bit setting I was interpreting an 8-bit value as a signed integer with values from -128 to 127. I need that interval for getting along with negative velocities and screen coordinates in double lores. Hence it was necessary to take the path form 8-bit to 16-bit maths which offers some more elbowroom regarding accuracy.
At the moment I am using 9 bits for calculating the trajectory and am able to calculate t = 1/2 timesteps for the trajectory. The trade-off is a bit more code for shifting the 8-bit maths to 16-bit and to implement the two's complement calculations for dealing with negative numbers. The image below shows a debug output of the calculated bird's x-positions (red bars indicate the former x-positions of the bird).
So far so good. It seems to work out - for now. I need to check if the accuracy is really enough to yield reliable collision detection results since the bird is not shifted pixel-wise as it would be possible with a y(x)-formulation where each pixel could be investigated if we touch any of the targets (as has been recommended by some group users here).
Next in the row will be checking if a simple box-approach will be sufficient for detecting collisions reliably enough - which means that the user also has the feeling it works properly ;-) If it will not work I out I have the chance to increase the accuracy up to 10 bits (drawing every other bird position but checking each t=1/4 timestep). If that will not work out - well I still have some bits to spend on accuracy, or will need to come back and think about the y(x)-formulation again...
First Collision Detection Algorithm
As written in the last post the collision detection algorithm was next to be developed. After increasing the accuracy of the bird's flight by doubling the time steps that are calculated the flight trajectory resolution seemed convenient for detecting a possible collision with the target.
A first solution was using a single "hot" pixel at the bird's lower right edge and checking if it was in the range of any of the target objects. For achieving this the target is divided into a grid of 5x5 pixels. A brick itself is 6x6 pixels but they are stacked to a 5x5 raster. A brick occupies one "cell" of the target table in memory and a pig occupies four cells.
A short deviation to the target array in memory. I wrote a target parser which reads target definitions from memory and draws the target and while doing that builds a data table (matrix) in memory with cell content which is empty (=0), brick (=1) or pig (=2,3,4,5).
When the bird is flying the algorithm checks if the detection pixel is inside a cell of the target matrix at each time step and if yes, it is checked if the cell is empty or contains an object. If an object is found we have a "hit" and we need to do some actions in handling this event (see below).
A problem of this cell detection algorithm is the side scrolling of the screen in X-direction which means that the target array scrolls in the visible screen area while the bird's x-position is kept constant while the screen scrolling occurs. This is solved by using a scroll step counter which keeps track of the current number of scroll steps that have been done so far correcting the X-offset of the target array at every time step.
Now if a collision is detected we need to react to this event. First the birds X-velocity is set to zero (vx=0) and the bird simply falls down to the ground. This is simple. Furthermore we need to do something with the object which has been hit. Simplest solution so far is that the object simply disappears. But that needs some thinking.
The solution here is that the original background of the hit target cell gets restored as the simplest solution. Here I am storing the original background image on LORES screen 2 and using the DOLORES SCRNXY and PLOTXY function to restore relevant target areas. It could be done faster by directly copying memory parts back and forth but as I have already seen in other projects this procedure is fast enough for small screen areas up to 256 pixels to yield a satisfactory visual effect - and it is easy and short to code since you just call available DOLORES library functions.
But there is a catch! A cell is 5x5 pixels since the bricks get stacked but a brick is 6x6 pixels. However, if we only restore 5x5 pixels every time a brick gets hit, we sometimes leave the black outer border of a brick unrestored which give a really odd look. Hence it is necessary to extend the algorithm towards a next neighbor routine checking neighboring cells if they are containing an object and especially if it is a brick or a pig. Depending on the content of the neighbors the outer borders get deleted or not. Why the hassle? I tried putting the target in a 6x6-raster but the bricks looked not well stacked with having a two pixel wide black outer border so a stacked 5x5-raster yields a more convenient visual result in my opinion.
The pig consists of four cells and hence if we hit any of the pig's cells we need to remove all four. To achieve this I am assigning the pigs cells four different values 2, 3, 4 and 5 and with this approach I always know which cell of the pig the bird has hit and I can clear out the others regarding the borders of the neighboring bricks if any.
And yes, not to forget the target cell matrix. We need to delete all entries of hit objects. If not, the bird would maybe collide with invisible objects on its next flight which is undesirable. Hence changing the target cell matrix appropriately is essential.
I have seen that one detection pixel basically works but it happens quite often at higher vx- or vy-velocities even with the doubled time step the pixelwise step size of the bird is that large that the bird seems to fly through bricks hitting objects behind walls etc. which is definitely not realistic. The basic concept is working but it needs to be extended. Hence I decided to add more "hot" pixels to the bird which improved that behavior significantly.
At the moment detection is done with four "hot" pixels but as experiments have shown this number needs to be increased. The bird still sometimes flies through walls of bricks hitting objects behind a wall which makes no real sense so I will extend that and try to fix that issue next.
One of the next tasks is also to implement some action when a brick gets hit like let the brick more slowly disappear with a small animation. And also move bricks that are above the hit brick downwards like it would be expected in the real world under gravity.
Let's see where that ideas will lead to next....
Target Animation
As it turned out collision detection with the pixel matrix works acceptable in most use cases hence next task was animation of the target or in other words: what happens when the target gets hit?
If the bird directly hits a brick or a pig the target "implodes" right away. Parallel to the implosion the bird continues its trajectory but at a remarkably reduced x-velocity and falls down on the ground. Both actions are done in parallel by the algorithm for best visual appearance.
This should simulate the difference in momentum after the bird hits an object. In my first implementations the bird directly fell to the ground with vx = 0 but group members suggested to me that the bird should partly continue on its trajectory for a better visual appearance - so here you go!
Since an effect on only the direct hit object would lead to an almost impossible to achieve goal of destroying the pigs, Daniel came up with the idea that the impact of the bird should have an effect also on neighboring objects and hence some of the neighboring objects also implode on a hit. This gives nice side effects and more variations regarding the course of the game.
-
Free bricks: After an implosion sequence some bricks remain hanging "freely" in the air. Due to gravitation these free bricks or columns collaps which is simply done by moving free hanging bricks downwards until they fall onto some populated target cell matrix. This process is done iteratively in order to catch bricks that were on "solid ground" before a free brick was moved and are in free air after the first iteration (collapsing columns).
-
Ceilings: It is possible to build ceilings of bricks over pigs to protect them like a roof. Prerequisite is that a brick that is "hanging" in free air has contact to two other bricks on the left and the right side. These bricks keep hanging and are not moved downwards. If bricks of a ceiling get destroyed the ceiling collapses. However, it is possible with the algorithmic assumptions of two neighbors that new ceilings can be formed when the remaining bricks fall to the ground.
-
Pigs on pillars: What if a pig has its supporting bricks removed? A pig needs at least one brick below it in order to support it. If both supporting bricks get removed the pig will implode. This is another possibility to remove a pig. There is currently a suggestion that the pig shall be moved downwards first and then implode when it hits another object on its way down. That is a good point and shall be considered in the next releases (I hope).
With these simple (but not easy to code) rules a real game play is feasible and it is possible to build levels. For this purpose I have extended the target parser with a level switcher. This allows to select a level from 0..255 and the target will be built according to the included table data base. Each target consists of 21 bytes of information about the locations of bricks and pigs. This allows for a 12 x 7 cells target in the game.
At the moment I have implemented three targets for testing purposes of different aspects of the target hit animation. And yes, I already have found another bug that I will need to adress now ;-)
There is also some blend-in and blend-out effect now for some more show effects.
The video below illustrates the current state of development with bugs included but gives a good impression what is possible now. Next will be the introduction of a double lores font with user feedback about score, lives etc... and then maybe we get the game engine done and need to define lots of levels for lots of playing fun.
I will release a DSK-image with the current engine so that everybody can try at home and hopefully find some bugs to report (and of course in order to further develop the keyboard joystick routine...).
Putting Things Together - a first Beta Version of the Game
As we made further progress with the development of the game engine we decided to put things we have so far together and try to create a first beta version of the game.
The reintegration of the game engine into the AppleSoft menu environment works seamlessly giving the user a complete Double LORES experience of all parts of the game.
The game engine with music player is now approximately 25 kB large without music, sprites and background graphics. Double LORES management is done via the routines developed for the DOLORES library - which allows interaction with Double LORES from AppleSoft Basic and FFedit which enables fast data passing from and to the Double LORES screen as well as a fast character generator for Double LORES.
The beta version of the game engine features:
- 23 levels of play, still very extendable
- 3 different types of birds inflicting different grades of damage to a target
- user choosable bird which to shoot next as part of a strategy to knock a target down effectively
- different point values for different types of birds when hitting pigs
- different point values for direct and indirect hits of pigs
- different point values for remaining birds when a level was completely cleared of pigs while some birds are still remaining
- 4-digit score counter for scores up to 9999
At the moment for test reasons the user gets 3 birds of each of the 3 different types to shoot at the target trying to knock-out all of the pigs with as little birds as possible. The yellow bird inflicts the least damage and gets the most points when directly hitting a pig. The red and the blue birds inflict more damage in ascending order but yielding less points compared to the yellow bird.
The game requires strategy regarding the use of the different types of birds e.g. the order of their use trying to save most birds as possible while using perfect marksman's technique operating the slingshot in order to knock out the pigs with as little birds as possible.
At the moment the engine still contains some hot keys in order to replay or switch levels which will be disabled in the final version. It also still requires a button press when a shot is finished to reset the bird in the slingshot. This will be fixed in the final version as well as the secret debugging mode will be gone then. The game also still runs endless now restarting with level 1 after level 23. The idea is that the user plays all levels trying to get as many points as possible in one go. Then the game exits back to the menu and a high-score entry is awarded - or not.
The following video gives a short sneak-peek of the current engine's performance. We still work on the level design and order, as well as the number of birds per level in order to balance the game regarding fun and demand so that users won't get frustrated since the game is too hard or find it too easy. Furthermore we chase bugs in the assembler code as well as the AppleSoft menu.
So still a way to go but we are getting to it...
A Fast Double LORES Character Generator
For a game in most cases the output of text in any form of a user interface is necessary. Here in this game we need at least feedback about the current level and scores.
For being the most convenient it would be desirable to have a routine that could print out any given string on the Double LORES screen. In order to achieve this we need to define a font dataset, a routine that can print a single character of that font onto the screen and a routine that retrieves the single characters out of a given string and feeds them to the print out routine.
Let's start with the font definition and do this with keeping the input string in mind. Characters of the string are encoded in ASCII-format hence we need to organize our font table accordingly (Check the Assembly Lines book Appendix E for a comprehensive Apple II ASCII chart). The "interesting" characters start with ASCII hex code $30 for a zero (0). However, since we need other characters like SPACE, ! or + in the game we need to start our table from hex code $20 as ASCII value for SPACE. In our font table ASCII-value $20 will be mapped to $00 and so on, $30 (0) will be on position $10 then. The string decoder routine will hand over the appropriate index value which will be converted to the appropriate pointer into the font table to retrieve the pixel bitmap of the desired character.
A character is stored in 8 data bytes in the table this allows up to 8 x 8 pixel large characters since each bit in a byte resembles a pixel of the character. I am using 8 bytes per character since 8 is a power of 2 which makes calculation of the index into the table easier. I am using a 16-bit pointer which is calculated as follows:
POINTER = CHARACTER_INDEX * 8 + FONT_BASE_ADRESS
One of the images shows the calculation of the pointer by shifting the index value three times to the left which resembles a multiplication by 8 and adding the font base adress. With the pointer it is easy to extract the desired character bitmap byte by byte. For displaying the character on the screen each bit in each byte is scanned if it is set or not. If the bit is set a pixel is plotted on the screen at the appropriate position. Doing this for all 8 x 8 = 64 bit yields the desired character.
Plotting of the single pixels is handed over to the DOLORES PLOTXY-function which does a fast pixel output on the Double LORES screen. Since PLOTXY is as fast as the blink of an eye we don't need a specialized output function for the font. The routine plotting a single character is shown in one of the photos added to this post.
The absolute adress of the 8 bytes region is calculated outside this routine and forwarded via self-modification into the character output routine. The X-register cycles through the 8 character bytes starting from the transferred adress.
Excursus: If you compare the character output here with the HIRES-routine described in Assembly Lines Chapter 31 the bitmap pattern is also stored bytewise. However, a byte of the font table is directly copied to the screen memory for faster output of the characters where one byte resembles 7 pixels of the HIRES screen. This yields character sizes of 7 x 8 pixels and due to the fact that a single character byte is copied to a screen byte the font output on the X-axis can only be done in 7-pixel-steps whereas on the Double LORES screen there is no restriction regarding the X-position.
The character output routine here seems to be a bit bloated but as you might have already discovered it is able to handle fonts with variable character spacing which is needed here to display the edgy bird font. In order to manage the different widths of the single characters we also supplied a width definition table to the font definiton table. Narrowest character is the letter I with a width of 3 pixels compared to the widest character W with 8 pixels and the other character sizes scattered in between. The routine PRINTLARGEFONT (see image) reads the input string from DATA and calculates the index value by subtracting $20 and putting the result in FINDEX and FBADR. The latter is then calculated to the absolute adress of the character byte array in the font table (which must be page-aligned to work in this context as a short incidental remark) in the subroutine SETFONTADR as described above. Calling PLOTCHAR8 outputs the current character at the position (X1,Y1) which is initially set by the calling function. X1 propagation for the next letter is done by PLOTCHAR8. Note: there is no automatic line break here!
Finally the only thing remaining is how string data gets into the DATA array? This can be done by a simple copy operation of the string data to DATA or self-modification or with a indirect zero page pointer to a string data field in memory. Here I am using a simple copy & paste method to DATA.
This is also used by the Ampersand-routine &PRINT included in the engine which enables printing any AppleSoft string variable content to the Double LORES screen which is used by the menu system when displaying the HI-SCORE-table. I know there might be way more elegant ways in doing this but this solution fully satisfies our needs.
A remaining topic is the conversion of hexadecimal values like the score to decimal and displaying those on the screen. But this needs ot be covered in one of the next articles.
Sound Effects
I do not want elaborate much here on the PT3-music player which is playing the background music. Basically the music player does most of its work in AUX memory and is similar to the player we used for the Witch Trial game which needed a development of a player that leaves AppleSoft Basic and ProDOS intact while playing a music tracker file via the MockingBoard.
What I want to focus here is the problem with non-MockingBoard sound. An important part of the game - from my point of view - is the integration of proper sound effects to support the experience of the game. Since not everybody has a MockingBoard or gets stressed out by the MockingBoard tune quickly some additional sound effect features are needed using the built-in Apple speaker. As the Apple //e has no built-in sound chip sound effects played over the speaker need to be handled 100% by the 6502 CPU. The speaker is connected to adress $C030. Toggling that adress will make the speaker click once. Generating a tone requires "clicking" the speaker with correct timings in order to get the desired tone frequencies by moving the speaker membrane with the current impulses. Hence sound generation routines normally consist of speaker clicks combined with more or less complex in-between wait loops.
In general the waiting loops are rather sophisticated in order to generate the desired sound effect. In the code cases I have reviewed so far while waiting for the next click, the CPU worked on loops for spending cycles and does not do anything else in the program. So playing a sound over the speaker normally leads to a delay of the animation process if a sound is significantly longer. Solution to this would be to use very short sounds or split up the sound generation in smaller chunks which leads to more complex program codes and timings. For this game I am using sound routines I found while searching the internet. By reviewing loads of different routines I was sometimes awestruck on how complicated sound routines were designed with multiple nested loops etc. in order to mimick different sounds.
I decided to add a sound to the slingshot when pulling the sling clicking sounds are played to support the sling movement. When the bird is shot another sound is played which should emphasize the shot. Hitting a brick gives a short noise and when a pig is vanishing you can hear a longer crumbling sound. Daniel was pleased with the sounds since he always keeps playing Angry Birds with the music turned off. However, he said that he would like the birds to tweet as it is in the original game. So I got into troubles.
For the other sounds I reviewed sound routines and listened to their output, picking routines that would sound nicely and added those routines directly to the code. However, I could not find any appropriate routine for an adequate bird tweet. So I started to fiddle with some of the routines I reviewed trying to change their parameters to squeeze out a bird-like tweet sound out of one of them. I finally found a routine that should sound like a starting UFO where I could change the parameters in a way that the speaker output sounded to me like a tweeting bird. I then had the idea to add some pseudo randomness to the sound generation in terms of the timing between single bird tweets and the pitch of a bird tweet (in a certain range). Randomness pairs here perfectly with the sound effect to create the illusion of tweeting birds in the background from my point of view. A nice little extra feature of the game!
The attached video was recorded using Virtual II emulator which has some problems in sound generation with Version 11 as it seems but it resembles most aspects of the added sound effects also in addition with the MockingBoard music playback at the end of the video which gets mixed in the sound output with the emulated speaker sounds. On real systems this is different.
The attached images show different sound routines as well as the pseudo random number generator which is an 8-bit linear shift register which "draws" a random number out of 256 which sequence will also repeat all 256 numbers so it is a very simple approach for generating pseudo random numbers but for the sake of the effect it is sufficient.
Another image shows the integration of the bird chirp in the main slingshot code routine. The counter for a chirp is also an 8-bit number which gets incremented every time the slingshot routine cycles through the detection of joystick or keyboard input. In order to prevent that every cycle generates a chirp we detect for every 31st cycle to occur to play a chirp. However, this would lead to constant chirp intervals and hence we add a pseudo random number between 0 and 15 to the chirp counter at every cycle which desynchronizes counting and lets chirping occur irregularly. Furthermore the pitch is set in a certain range that makes the sound routine play bird like tweeting sounds (in my opinion).
From my point of view the sound routines enhance the game experience significantly. Of course the chosen routines might not be optimal for the desired sound effect but I could not yet dive deeper in the development of Apple II speaker sound routines yet. Maybe in the future...
Addendum
After I have written this last development log post, I got some more sound routine input from my vibtage book library and the community. In one of my assembler books I found a disassembly of the programmer's aid #1 Integer ROM sound routine which can play single voice melodies which I added to the game engine to play the "no bonus" sound in the form of a "lose" fanfare.
A member of the CALL -151 Facebook community - Sellam Abraham - came up with his own stunning sound synthesizer routine which he dedicated to the game engine and now delivers three really advanced sound effects to the game.
See some of the code in the images of this post!