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.ship_ops(time, prev_time)
                self.ufo_ops_back(time, prev_time)
                self.alien_ops(time, prev_time)

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...


  1. How does the game save the scores over time?

    1. If you make it to top ten, it saves the high score list into a file locally, and the list is loaded from the file when the game is launched. Indeed, having a web server to save the scores to would be much nicer.

    2. Thanks

    3. But wait, how are other players' high scores saved to the game?

    4. The scores are saved locally to your game directory only. Unfortunately, other players' scores are not shared - for that some central repository would be needed.

  2. Well done! Really fun game.

  3. I can't beat the level 16 ;-;

  4. Seems fun. As of the standalone executable, if you've already tried and failed with pyinstaller (also my first try for such cases) you should definitely give a go to cx-Freeze. It has an emphasis on numpy and could probably figure out things better / differently. https://cx-freeze.readthedocs.io/en/latest/

    1. Thank you for the tip - I tried cx-Freeze on (the much simpler) Maze Generator and, after some hiccups, it produced an .exe with some 13,000 files, 1.1 GB in total. Unfortunately, even if it had worked (which it didn't) it is simply too much...

      Of course, I may have overlooked some instructions or something, went straight to "build" without really reading the docs at all.

    2. Can you make the powerups items bigger and some other ways to pick up these items instead of shooting at them?

    3. The powerups could be bigger, but it is part of the design that they are not that easy to get. If you have good ideas on how to pick them up instead of shooting them, I could try to implement them!

  5. How do I beat the level 4 boss? I tried lots of times already but it seems the boss cannot be shot at all. Where is the point that I have to hit?

    1. The bosses are only vulnerable when hit at a specific area, as you have noticed. Otherwise, they would be too easy targets. The hit areas are in the middle.

  6. I have looked at the source code and did draw the alien size and alien hit area as rectangles on the screen. With this I have figured out that it might not be possible to hit the boss (in level 4) at all (?). I increased the hit area from (87, 300, 106, 65) to (87, 300, 106, 81). It seems that the height needs to be > 80. Now, I guess I have the intended behavior from you.
    It might be that I have a different version of the libraries compared to you and that this might cause a rounding error showing this result. (I have python 3.9.12, pygame 2.1.2, numpy 1.22.4 on a MacOS.)

    1. First of all, apologies for this bug - also to anyone else who has tried to beat the bosses with no hit area!

      And thank you for looking into the issue and presenting a fix. Frankly, I have no clue what could be causing this. Perhaps something to do with pygame.transform.scale working differently on MacOS (I have only tested on Windows), as Boss #1 image is scaled before using it? However, (87, 300, 106, 65) is the hit area for Boss #2, which is not scaled.

  7. Excellent Work! Well Done

  8. so I tried running your programs, I installed all of the things i needed to run it but after all of that its telling me file not found even though I see the file in the folder which is weird i'm not too familiar with python I was just seeing some projects that other people made to motivate myself to use this for game dvelopment.

    1. Sorry, based on such limited information it is hard to say what goes wrong. You should have all the Space Invaders files in one directory, and then, using a Python interpreter, run the spaceinv.py program. The code assumes it can find all the data files needed in that directory.