Space Invaders game example to learn how to use arsd

If you have finished the arsd tutorial then now I will walk you through a space invaders game. If you were born after 2001 then you probably have no idea what that is but it was one of the most popular types of game ever, I loved it. This code sure works on Windows, but it may not work on all Linux distros.

First you need to download the arsd library along with the necessary image and sound files, they are all here:
https://drive.google.com/open?id=1Kss5FfT4umhKwlqfovLd9VSYRIY0Nhxe

The entire source code is in the file source.d and it is shown below. I have filled it with comments to explain the entire code in details.

// This software is a space invaders game.

// import the tools we need
import arsd.image: loadImageFromFile;
import arsd.simpleaudio : AudioOutputThread;
import arsd.simpledisplay : Image, Key, KeyEvent, MemoryImage, MouseButton, MouseEvent, MouseEventType, Point, Rectangle, ScreenPainter, SimpleWindow,
                            Sprite;

// make sure the terminal will not pop up
pragma(linkerDirective, "/subsystem:windows");
pragma(linkerDirective, "/entry:mainCRTStartup");

// a struct to represent each alien
struct Alien
{
    // this is the alien's life
    int life;
    // this is the alien's position
    Point position;
    // these will be the costumes of the alien when it is alive and when it is dead
    Sprite alive, dead;
    // this boolean will tell if the alien has been destroyed
    bool destroyed;
}

void main()
{
    // create the GUI window
    SimpleWindow window = new SimpleWindow(1000, 400, "Space Invaders");
    // create and start the audio thread
    AudioOutputThread music = AudioOutputThread(true);
    // load the memory images
    MemoryImage memBackground = loadImageFromFile("images/background.png"), memShipNormal = loadImageFromFile("images/ship normal.png"),
                memShipTurbo = loadImageFromFile("images/ship turbo.png"), memPrimFire = loadImageFromFile("images/primary fire.png"),
                memSuperFire = loadImageFromFile("images/super fire.png"), memDefeat = loadImageFromFile("images/defeat.png"),
                memEnd = loadImageFromFile("images/end.png");
    // create the actual images
    Image background = Image.fromMemoryImage(memBackground), defeat = Image.fromMemoryImage(memDefeat), end = Image.fromMemoryImage(memEnd);
    // create the sprites
    Sprite shipNormal = new Sprite(window, Image.fromMemoryImage(memShipNormal, true)),
           shipTurbo = new Sprite(window, Image.fromMemoryImage(memShipTurbo, true)),
           primFire = new Sprite(window, Image.fromMemoryImage(memPrimFire, true)),
           superFire = new Sprite(window, Image.fromMemoryImage(memSuperFire, true)), ship = shipNormal;
    // create the booleans to keep track of the current state of the player and the game
    bool superShotFired, superShotOver, upgradeEarned, won, lost;
    // 'backgroundHeight' controls the position of the background, which rolls down as you play, and 'movingSpeed' controls the speed of the ship
    int backgroundHeight = -2400, movingSpeed = 1;
    // 'shipPosition' will keep track of the ship's position and 'superShotPosition' will keep track of the super shot position (in case you fire it)
    Point shipPosition = Point(400, 300), superShotPosition;
    // create an array where we will place the current position of every single primary shot you fire (not counting the super shot)
    Point[] shotsPositions;
    // create an array full of aliens that will attack you, the struct 'Alien' is defined above
    Alien[6] aliens;
    // create rectangles for the aliens, for your ship, for the super shot and for the normal shots, because the computer only understands rectangles
    Rectangle alienRect, shipRect, superShotRect, shotRect;
    // create the array with the names of the image files of them alive and then the array with the image files of them dead
    string[6] imgNamesAlive = ["images/alien0alive.png", "images/alien1alive.png", "images/alien2alive.png", "images/alien3alive.png",
                               "images/alien4alive.png", "images/alien5alive.png"],
              imgNamesDead = ["images/alien0dead.png", "images/alien1dead.png", "images/alien2dead.png", "images/alien3dead.png",
                              "images/alien4dead.png", "images/alien5dead.png"];
    // then we get the start points of each alien
    Point[6] startPoints = [Point(100, -120), Point(600, -300), Point(200, -500), Point(800, -700), Point(250, -900), Point(400, -1750)];
    // this variable will store the alien's life in the loop below
    int life;

    // use a loop to create each of the 6 aliens
    foreach (i; 0 .. 6)
    {
        // load the memory image
        MemoryImage memAlive = loadImageFromFile(imgNamesAlive[i]), memDead = loadImageFromFile(imgNamesDead[i]);
        // turn it into a sprite
        Sprite imgAlive = new Sprite(window, Image.fromMemoryImage(memAlive, true)), imgDead = new Sprite(window, Image.fromMemoryImage(memDead, true));
        // define the alien's struct, the variable life is incremented by 5 at each iteration, so that each alien is stronger than its predecessor
        aliens[i] = Alien(life += 5, startPoints[i], imgAlive, imgDead);
    }

    // start playing the background music
    music.playOgg("sounds/background.ogg", true);
    // hide the cursor because we don't want it on top of the ship
    window.hideCursor();

    // start the window's event loop
    window.eventLoop(30,
    {
        // create the screen painter
        ScreenPainter painter = window.draw();

        // if you've won
        if (won)
            // draw the end image
            painter.drawImage(Point(0, 0), end);
        // if you've lost
        else if (lost)
            // draw the defeat image
            painter.drawImage(Point(0, 0), defeat);
        // if you are still playing, then we draw everything on the screen
        else
        {
            // draw the background, it's a very big image
            painter.drawImage(Point(0, backgroundHeight), background);

            // start a loop to draw all shots (primary fire)
            foreach (i; 0 .. shotsPositions.length)
            {
                // draw the fire's sprite
                primFire.drawAt(painter, shotsPositions[i]);
                // update the fire's position for the next iteration (taking into account the player's speed)
                shotsPositions[i].y -= 15 + movingSpeed;
            }

            // if you have fired the super shot and it still hasn't gone over the window's edge
            if (superShotFired && !superShotOver)
            {
                // draw the super shot
                superFire.drawAt(painter, superShotPosition);
                // update it's position
                superShotPosition.y -= (15 + movingSpeed);
                // update the rectangle around it
                superShotRect = Rectangle(superShotPosition.x, superShotPosition.y, superShotPosition.x + superFire.width(),
                                          superShotPosition.y + superFire.height());
                // check if it has already disappeared from the window, in which case you set this boolean to true
                superShotOver = superShotPosition.y + superFire.height() < 0;
            }

            // start a loop to draw each alien on the window
            foreach (i; 0 .. 6)
            {
                // if this alien has been destroyed
                if (aliens[i].destroyed)
                    // draw it with the dead costume
                    aliens[i].dead.drawAt(painter, aliens[i].position);
                // if it is still alive
                else
                {
                    // draw it with the living costume
                    aliens[i].alive.drawAt(painter, aliens[i].position);

                    // if it's still alive and it has totally passed by your ship
                    if (aliens[i].position.y > 400)
                        // set this boolean to true, you lose the game
                        lost = true;
                }

                // update the alien's position based on your current speed (your speed changes when you use the turbo)
                aliens[i].position.y += movingSpeed;
            }

            // draw your ship
            ship.drawAt(painter, shipPosition);
        }

        // if you're still playing
        if (!(won || lost))
        {
            // update the background's height
            backgroundHeight += movingSpeed;

            // if you've finally reached the end of the game
            if (backgroundHeight > -5)
                // set this boolean to true, you've won
                won = true;
            // if you have not reached the end yet, then we damage the aliens (if you've shot them) and check if you've crashed into them
            else
                // start a loop to check, for each alien, if you have hit it or crashed into it
                foreach (i; 0 .. 6)
                {
                    // if the alien is still alive and it hasn't yet totally passed by you
                    if (!aliens[i].destroyed && aliens[i].position.y + aliens[i].alive.height() > 0 && aliens[i].position.y < 400)
                    {
                        // define a rectangle around the alien and one around your ship, because the computer only understands rectangles
                        alienRect = Rectangle(aliens[i].position.x, aliens[i].position.y, aliens[i].position.x + aliens[i].alive.width(),
                                              aliens[i].position.y + aliens[i].alive.height());
                        shipRect = Rectangle(shipPosition.x, shipPosition.y, shipPosition.x + ship.width(), shipPosition.y + ship.height());

                        // if you've used the super shot and it still hasn't disappeared from the window
                        if (superShotFired && !superShotOver)
                            // if the center of the super shot is inside the alien's rectangle then it deeply intersects it
                            if (alienRect.contains(superShotRect.center()))
                                // set the alien's life to 0, the alien dies
                                aliens[i].life = 0;

                        // start a loop to check all shots, starting from the last one
                        foreach_reverse (shotPos; shotsPositions)
                        {
                            // define a rectangle around the shot
                            shotRect = Rectangle(shotPos.x, shotPos.y, shotPos.x + primFire.width(), shotPos.y + primFire.height());

                            // if it totally enters the rectangle of the alien
                            if (alienRect.contains(shotRect))
                            {
                                 // remove it from the array (hence the reverse loop starting from the last one)
                                shotsPositions.length--;
                                // update the alien's life
                                aliens[i].life--;
                            }
                        }

                        // if your ship touches an alien
                        if (alienRect.overlaps(shipRect))
                            // update this boolean, you lose the game
                            lost = true;
                    }

                    // if the alien's life reaches 0
                    if (!aliens[i].destroyed && aliens[i].life <= 0)
                    {
                        // update the alien's state
                        aliens[i].destroyed = true;
                        // this boolean becomes true to give you an upgrade with double fire after you've destroyed 3 aliens
                        upgradeEarned = aliens[0].destroyed && aliens[1].destroyed && aliens[2].destroyed;
                        // play the sound of the alien's death
                        music.playOgg("sounds/alien death sound.ogg");
                    }
                }
        }
    },
    // register mouse events
    (MouseEvent event)
    {
        // if you are still playing, then we move the ship and shoot
        if (!(won || lost))
            // if you've moved the mouse arrow
            if (event.type == MouseEventType.motion)
                // update the ship's position, we make the ship be wherever the mouse arrow is
                shipPosition.x = event.x, shipPosition.y = event.y;
            // if you've pressed a mouse button
            else if (event.type == MouseEventType.buttonPressed)
                // if you've pressed the left mouse button
                if (event.button == MouseButton.left)
                {
                    // add the new shot to the array of shots
                    shotsPositions ~= Point(event.x, event.y);

                    // if you already have the double shot upgrade
                    if (upgradeEarned)
                        // add another shot to the array
                        shotsPositions ~= Point(event.x + 30, event.y);

                    // play the sound of shooting
                    music.playOgg("sounds/shoot sound.ogg");
                }
                // if you've pressed the right mouse button and you still have the super shot, then you fire the super shot
                else if (event.button == MouseButton.right && !superShotFired)
                {
                    // set this boolean to true
                    superShotFired = true;
                    // define the position of the super shot
                    superShotPosition = Point(event.x - 25, event.y - 90);
                    // play the super shot sound
                    music.playOgg("sounds/super shoot sound.ogg");
                }
    },
    // register key events
    (KeyEvent event)
    {
        // if you are still playing and the key was the Space, then we activate the turbo
        if (!(won || lost) && event.key == Key.Space)
            // if you've pressed the key
            if (event.pressed)
            {
                // if your speed is still normal
                if (movingSpeed == 1)
                {
                    // up the speed from 1 to 5
                    movingSpeed = 5;
                    // update the costume of the ship's sprite
                    ship = shipTurbo;
                    // play the turbo sound
                    music.playOgg("sounds/turbo sound.ogg");
                }
            }
            // if you've released the Space key
            else
            {
                // set the speed back to normal
                movingSpeed = 1;
                // set the costume back to normal
                ship = shipNormal;
            }
    });
}

Comments