Jan 16, 2022

Space Invaders Game

While all the preceding postings have been about demos, this is actually a game. I started it as a joint project with my son  as a programming exercise - and it has indeed been a fun way to test some new methods, and of course to try building some "playability" while the graphics are very similar to a demo project.

Space Invaders is a classic game from 1978, the era of the arcades - home computers were still not quite up to handling such graphics. In the game, there's at times quite a lot going on - dozens of bullets and aliens moving, and I added a star field background of 4,000 independent stars.

The program code and the required data files can be found in my github repository, as usual. The graphics are either "free from the internet" (no copyright/licence claims) or purchased cheaply, like the sound effects. In any case you should not re-use/distribute them, but the code is free.


Above: The title screen with high scores.

Above: Game play, level 15.

Building Blocks


There are close to 2,000 lines of code. The reusable game objects - aliens, ufos, bullets, power ups, explosions, and scores all have their own classes, as has the player space ship. At the beginning of each level, a list of aliens is created. There are other modes (for showing the start page, or for saving a high score etc.) but in game mode, the main loop simply checks if control keys (space bar or left or right cursor key) are pressed, and adds a ship bullet or moves the player space ship. It then runs the following "operations":

                self.level_ops(time)
                self.ship_ops(time, prev_time)
                self.ufo_ops_back(time, prev_time)
                self.alien_ops(time, prev_time)
                self.bullet_ops(time)
                self.explosion_ops()
                self.score_ops(time)
                self.powerup_ops(time)
                self.ufo_ops_front(time)
                self.info_ops()

Each of these handles a specific part of rendering each game frame. In practice, there are lists of all the main elements - like aliens, ufos, bullets, and explosions - and each item in these lists is an independent instance of its respective class. 
  • Level_ops checks if the level has been completed, or if a game over event has occurred.
  • Ship_ops draws the player space ship and updates its power ups' statuses.
  • Ufo_ops_back adds a new ufo to the ufo list, if a random number is smaller than the level's ufo probability; the higher the level, the more ufos will appear. It then goes through each ufo in the ufo list, moving it on its trajectory, and dropping bombs (generating five new alien bullets) when in the middle of its path. The ufos start far away in the distance, then turn back by simultaneously coming closer, and they can only be shot down close to the middle of their turn. The ufos will be drawn here only if they are still far away, i.e. behind the other aliens. If the ufo has finished its run, it will be removed from the ufos list.
  • Alien_ops goes through each alien in the alien list and moves them. It also tests if they collide with the player space ship and, with level-specific probability, if they shoot - generating new alien bullets.
  • Bullet_ops goes through two lists; the alien bullets and the ship bullets. First the bullets are moved. Alien bullets are each tested whether they hit the player space ship or its shield. In the former case, the player is killed and an explosion added to the explosions list; in the latter, the bullet is removed. The test is first made on coordinates, checking if the rectangles containing the bullet and the ship overlap, and if so, then on their bit masks, to make sure the ship was really hit. 
    The ship's bullets are, in a similar fashion, tested for hitting aliens, ufos, or power ups. If they do, an explosion is added to the explosions list and a score is added to the scores list (for power ups, a power up to the ship's power up list), and the bullet and the object being hit removed from their respective lists.
    All bullets not removed are of course also drawn to the screen here.
  • Explosion_ops goes through the list of active explosions. Each explosion is built of a picture containing the explosion animation frames, and a grid defining their size. If there are animation frames left for an explosion, the next one will be drawn; if not, the explosion will be removed from the list.
  • Score_ops will draw each score for a pre-defined time, and then remove it from the scores list.
  • Powerup_ops will draw each power up on screen for a pre-defined time, and then remove it from the power ups list. These are the power ups left behind by shot ufos or bosses.
  • Ufo_ops_front simply draws the ufos which are closer than the other aliens and had to wait for other objects being drawn first.
  • Info_ops draws the game score, ship power up status, player ships left, and level number.

Video: the first level.


Game Play - Play a Game


The game starts relatively easy, but gets difficult when you proceed through the levels. The aliens get faster, they shoot more, the bosses get angrier, and there are more ufos dropping their bombs at you. The ufos are important - they often leave behind power ups, which are crucial for success in the later levels. Unfortunately, most power ups have a limited life time and must be renewed to keep the advantage. And the bosses can actually be quite difficult to beat.

Video: Levels 15 and 16, with power ups.

I have reached level 30 (later update: 44!) but that is not easy... there is a cheat mode, too, if you can find it, that will give you power ups and infinite lives.

There are a couple of other clever bits besides the actual game here. The stars in the background are done using NumPy and pygame surfarray and were already part of the demo project Sound Vision; and the rotating title text on the start page actually rotates each and every pixel, again using NumPy and surfarray. The trick for the latter is to map all the result image rectangle's pixels to the source image, even if some are mapped "outside" of it, as the far away edge of the image is narrower than the front edge. Then the pixels are filtered so that only the ones actually mapped correctly are used. These operations can be done in one single step, instead of mapping the image line by line, which is much faster.

I tried also converting the Python source code to an executable. Even though some 600+ MB of files were created, it was still missing some DLLs and, hence, did not work. In the good old days and the Amiga, 100 kB - about the same as what the source code file size now is - would have been more than enough for something like this...