The following user tutorial was created by Arseniy B., for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.
IMPORTANT NOTE: I DON'T NAME SURFACES LIKE TIM DOES. WHEN HE CALLS IT Surf_Display, MINE IS SIMPLY SurfDisplay, WITHOUT THE UNDERSCORE. MAKE SURE YOU KNOW THIS, AND ADAPT TO THE TUTORIAL ACCORDINGLY. ALSO, WHEN I MAKE PROTOTYPES IN CLASS DEFINITIONS, THE ONLY STUFF I WRITE IN THE PARAMETERS AREA (I.E. THE PARENTHESES) IS THE TYPE OF DATA TAKEN, NOT THE NAME OF THE DATA TAKEN. THAT NAME IS ONLY IN THE ACTUAL FUNCTION DEFINITION, AT LEAST FOR ME.

This tutorial is based on everything you've learned by now, if you've been following the tutorials in order and have just finished the Maps tutorial. Get ready to get a glimpse of creating your first real game! (Not counting that fabulous tic-tac-toe.) Similarly, the code this comes from is from all the code you've written up to the Maps tutorial, and including it. It would be wise to make a new Code::Blocks project (or a new DevC++ project, or whatever), because this tutorial will delve a different way for a bit, and you don't want to mess up your existing code for the upcoming Collision Detection tutorial. Speaking of that, this tutorial will introduce you to very simple collision detection, on a tile-based board.

If you're all ready, open up your [new] project and let's begin!

First, we need an introduction to Sokoban. For those of you who have never played this unusually fun and addicting puzzle game, here's how it goes. There's a player that you control with the arrow keys, and he pushes boxes on tiles to their 'home' locations. Those locations are special tiles on the board. Once all the boxes are at 'home', the level ends. The challenge, though, is that the boxes are positioned initially in such a way that makes it a puzzle. A box can't be pushed into a wall, or out of a corner, etc., so it becomes harder. That's all there is to it!

Here we go. This tutorial, like the last few, will only instruct you as to where to put the code I write; there will be no full documents, unless they're new. First and foremost, we need to modify CTile. Add another element to the typedef enum statement at the top.

typedef enum {TILE_TYPE_NONE = 0, TILE_TYPE_NORMAL, TILE_TYPE_BLOCK, TILE_TYPE_HOMEOFBLOCK};
You'll see why soon.

Also, we need to make a new function in the CMap class that checks what the TypeID of a given tile is. Add the appropriate prototype in the class definition, and add this function in CMap.cpp:

int CMap::TypeOfTileAt(int X, int Y) {
    int CheckID = (X / TILE_SIZE) + ((Y / TILE_SIZE) * MAP_WIDTH);
    return TileList[CheckID].TypeID;
}
Simple enough.

Sokoban needs blocks. Let's make a block! The CBlock class is going to extend CEntity. We're going to make this new class for the block inherit all the attributes (variables) and methods (functions) of the CEntity class. It also has its own attributes and methods that don't exist in the CEntity class alone. A good example to illustrate this is geometry. The 'parent' class is the Shape class. It has the attributes of area, color, and perimeter. A child is Square, which has those same attributes due to inheritance, but also has side (because it's a square), and Shape doesn't have side because it could be a shape with no straight lines. Circle is also a child, because it inherits area, color, and perimeter, but it has a radius attribute for itself; etc.



So open up two new files, CBlock.h and CBlock.cpp.

#ifndef CBLOCK_H_INCLUDED
#define CBLOCK_H_INCLUDED
#include "CEntity.h"
#include "CArea.h"
#include <vector>

using namespace std;

class CBlock : public CEntity {
    public:
        static vector<CBlock*> BlockList;
        bool OnHomeTile;
        CBlock();
        void CheckForHome();
        void OnMove(int, int);
};

#endif // CBLOCK_H_INCLUDED
And...
#include "CBlock.h"

vector<CBlock*> CBlock::BlockList;

CBlock::CBlock() {
    OnHomeTile = false;
    SurfEntity = NULL;
    X = Y = 0.0f;
    Width = Height = 32;
    AnimState = 0;
}

void CBlock::CheckForHome() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y) == 3) {
        OnHomeTile = true;
    } else {
        OnHomeTile = false;
    }
}

void CBlock::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}


After the class definition, we have " : public CEntity." This is what tells the compiler that this new class extends and inherits the attributes and methods of the CEntity class. This class, much like the CEntity class, has its own vector of CBlocks. Sure, it makes sense to just use the CEntity::EntityList vector in its place, but we're going to need a vector of just CBlocks (not including the CEntity that will be CPlayer).

Next, CBlock has an attribute that says whether or not it's located on its 'home' tile. The constructor, CBlock(), sets that value to false, and the rest of the values which it inherits from its parent class, CEntity, are set to their default values, as well. The next function, CheckForHome(), sets the OnHomeTile boolean value to true, if the TypeID of the tile under the block is TILE_TYPE_HOMEOFBLOCK. Otherwise, it's false. There isn't a return statement because this function just deals with CBlock's personal variables. The final function, OnMove, is very simple. It takes in two integer variables, MoveX and MoveY, and increments the block's X and the block's Y by those amounts, respectively. (Remember, even though the class didn't set an X or a Y variable, CBlock inherits those two from CEntity, so they're allowed to be manipulated. And they exist.)

Next, we need to implement a player, in kinda the same way.

CPlayer, a child of CEntity, will inherit every one of its attributes and methods, and we will define our own and maybe even override the inherited methods. Make two new files, CPlayer.cpp and CPlayer.h, and open up CPlayer.h.

#ifndef CPLAYER_H_INCLUDED
#define CPLAYER_H_INCLUDED
#include "CEntity.h"
#include "CBlock.h"
#include "CArea.h"

typedef enum {YES = 0, NO, BLOCK};

class CPlayer : public CEntity {
    public:
        CPlayer();
        void OnMove(int MoveX, int MoveY);

        int CanPlayerMoveLeft();
        int CanPlayerMoveUp();
        int CanPlayerMoveRight();

        int CanPlayerMoveDown();
};

#endif CPLAYER_H_INCLUDED
Then, open CPlayer.cpp and let's define the functions.


#include "CPlayer.h"

CPlayer::CPlayer() {
    SurfEntity = NULL;
    X = Y = 0.0f;
    Width = Height = 0;
    AnimState = 0;
}

void CPlayer::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}

int CPlayer::CanPlayerMoveLeft() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X - 32, Y) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->X == X - 32 && CBlock::BlockList[i]->Y == Y) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X - 64, Y) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y && CBlock::BlockList[k]->X == X - 64) return NO;
            }

            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveUp() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y - 32) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->Y == Y - 32 && CBlock::BlockList[i]->X == X) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y - 64) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y - 64 && CBlock::BlockList[k]->X == X) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveRight() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X + 32, Y) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->X == X + 32 && CBlock::BlockList[i]->Y == Y) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X + 64, Y) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y && CBlock::BlockList[k]->X == X + 64) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveDown() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y + 32) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->Y == Y + 32 && CBlock::BlockList[i]->X == X) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y + 64) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y + 64 && CBlock::BlockList[k]->X == X) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}

A little explanation. Just like with CBlock, " : public CEntity" is to extend the CEntity class.

In the CPlayer class, we're defining five new functions, OnMove and the four "can he move in a certain direction" functions. OnMove is simple: two integers are passed, and the CPlayer's X and Y coordinates increment by exactly that much. (Remember, CPlayer has the X and Y attributes, even though they weren't defined in CPlayer.h, because he INHERITED them from CEntity.) Here's where it gets a little trickier. Those four other functions are the collision detection functions. They check whether the player can move left, up, right, or down, and return what the result of the check was. So let's examine CanPlayerMoveLeft() closely.

First, look at the top of CPlayer.h. The typedef enum statement, like others before, makes YES equal to 0, NO equal to 1, and BLOCK equal to 2; this is because it's much easier to remember YES, NO, and BLOCK, than it is to remember that 0 is the same thing as YES, etc.

The function first checks if the TypeID of the tile to the left of the player is TILE_TYPE_BLOCK. If so, then the function returns NO, meaning the player cannot move left (because a wall is blocking his movement). Then, the function checks if there's a block there. It does this by looping through every one of the CBlocks in the static BlockList (which are automatically added when the CBlocks are loaded...we'll get to that a bit later). Every time it loops, it checks the X and Y coordinates of the block, and if a block is to the left of the player, then the function returns BLOCK, which will tell the program that the player has a block to the left of him. There are two caveats, though. First, if there is a block to the left of the player, but there is also a TILE_TYPE_BLOCK (i.e. a wall) to the left of that block, the player cannot move, and the function returns NO. Similarly, second, if there are two blocks to the left of him, the player can't move (he's not THAT strong!), and the function returns NO. Otherwise, it returns BLOCK. However, if NONE of those are true, then the function returns a simple YES, because the player has a free spot to his left.

That repeats for each direction. The purpose of this function is so that, when the OnKeyDown() function is called (in CApp), before moving the character we test if he can move. That said, let's move on to the implementation of these functions.

We need a way to load the blocks. Just like with the maps and areas, we're going to have a directory next to your exe called blocks, with (so far) one file in it: 1.blocks. Make that file, position it accordingly in the directory tree, and open it. Our first level will have blocks at these coordinates:

(160, 128) (160, 192) (224, 160) (224, 192) (160, 288) (64, 288)

Make two more new files called CBlockControl.h and CBlockControl.cpp, and open up the header.

#ifndef CBLOCKCONTROL_H_INCLUDED
#define CBLOCKCONTROL_H_INCLUDED
#include "CBlock.h"
#include <vector>

using namespace std;

class CBlockControl {
    public:
        static vector<CBlock> BlockList;
        static bool LoadBlocksFromFile(char*);
};

#endif // CBLOCKCONTROL_H_INCLUDED
And...
#include "CBlockControl.h"

vector<CBlock> CBlockControl::BlockList;

bool CBlockControl::LoadBlocksFromFile(char* File) {
    FILE* FileHandle = fopen(File, "r");

    if (FileHandle == NULL) {
        return false;
    }

    int BlockCount;

    fscanf(FileHandle, "%d\n", &BlockCount);

    for (int i = 0; i < BlockCount; i++) {
        CBlock TmpBlock;
        int X, Y;
        fscanf(FileHandle, "%d ", &X);
        fscanf(FileHandle, "%d\n", &Y);
        TmpBlock.X = X;
        TmpBlock.Y = Y;
        TmpBlock.OnLoad("gfx/block.png", 32, 32, 1, false, 64, 64, 0);
        CBlockControl::BlockList.push_back(TmpBlock);
    }

    fclose(FileHandle);

    return true;
}
This is a class that will load every one of the blocks and their locations from a text file (namely, blocks/x.blocks, where x is the level number). When the static function is called, it takes in a char array. As we've done before with Tim's tutorials, it reads that file. That file contains a number (in level one's case, 6), followed by a next-line/return character. That number is stored in the integer BlockCount. Then, the function loops through every one of the blocks in the file (with the for loop), and loads their respective X and Y coordinates. Inside the for loop, a TmpBlock (TeMPoraryBLOCK) is created and its X and Y coordinates (attributes set by CEntity, and inherited by the CBlock class, remember) are set as the values in the X and Y integers in the for loop. Then, the TmpBlock is inserted into CBlockControl's static vector of CBlocks (CBlockControl::BlockList).

So, it seems that now we have everything needed to actually implement everything we've just written; to bring it all together and make a level. Well, first we have to accomodate for one slight change. There is no longer a CCamera. We don't need one as the map can't get bigger than the one level. So we have to change something in CArea. Remember the function CArea::OnRender(SDL_Surface* SurfDisplay, int CameraX, int CameraY)? Well since we don't need a camera anymore, we don't need that function anymore. But don't toss it! Who knows, we might later need the function for any additions you decide to make. So let's make a new function:

void CArea::OnRender(SDL_Surface* SurfDisplay);

Add the prototype to the class definition (make sure it's public), and write the function in CArea.cpp:

void CArea::OnRender(SDL_Surface* SurfDisplay) {
    MapList[0].OnRender(SurfDisplay, 0, 0);
}
We now have two functions with the same name, but they have different parameters, which is allowed. It does the same thing, except there's one map to render so there's no need to figure out which is the first map to render. Duh.

Finally, open up CApp. We're gonna put this thing together. In CApp.h, add the following things:

private:
    SDL_Surface* SurfBackground;
    CPlayer ActionJackson;
    int Moves;
public:
    void OnKeyDown(SDLKey, SDLMod, Uint16);
Remember, this isn't the whole thing; this is just what you need to add in the appropriate places. I think you already have the new function, but make sure it's there, in any case.

So then, in CApp.cpp... In the constructor, CApp::CApp(), add statements that define Moves as 0, and SurfBackground as NULL (like SurfDisplay). In CApp::OnInit(), we need to almost completely rewrite it:

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if ((SurfDisplay = SDL_SetVideoMode(WWIDTH, WHEIGHT, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    if ((SurfBackground = CSurface::OnLoad("gfx/background.png")) == false) {
        return false;
    }

    if (CArea::AreaControl.OnLoad("maps/1.area") == false) {
        return false;
    }

    if (ActionJackson.OnLoad("gfx/actionjackson.png", 32, 32, 1, false, 64, 64, 0) == false) {
        return false;
    }

    CEntity::EntityList.push_back(&ActionJackson);

    if (CBlockControl::LoadBlocksFromFile("blocks/1.blocks") == false) {
        return false;
    }

    for (int i = 0; i < CBlockControl::BlockList.size(); i++) {
        CBlock::BlockList.push_back(&CBlockControl::BlockList[i]);
        CEntity::EntityList.push_back(&CBlockControl::BlockList[i]);
    }

    ActionJackson.X = 352;
    ActionJackson.Y = 320;

    return true;
}

Let's talk about this. The first two if's start SDL, like we know already. The third is just something I thought would look nice. I have this cool red graphic that I made, and I put it in gfx, and called it background.png. This statement uses our CSurface function OnLoad and puts it on SurfBackground. Next, an area is loaded. I'll get to what's in that area file in a second. The next statement loads the player. Remember, CPlayer inherits everything from CEntity, and since CEntity had an OnLoad function that loaded the entity from an image file, so does CPlayer. And thus, ActionJackon (which we declared in the class definition) has it. NOTE: MY PARAMETERS MAY BE DIFFERENT FROM YOURS, SO CHANGE THEM AS IS NECESSARY. THERE'S NO NEED FOR TRANSPARENCY, AT LEAST NOT YET, SO JUST REMEMBER THAT.

ActionJackson is pushed back in the static EntityList vector, and then CBlockControl loads all the blocks from a file. Remember, it's static, so we can run it without having declared an object/instance of CBlockControl. Then, like I promised WAAAAAY back, CBlock's static BlockList vector would get those blocks. And, like we need to do and like we did for ActionJackson, we need to push the blocks back in EntityList, too. Finally, we just need to set initial X and Y coordinates for ActionJackson, and that's all for OnInit().

Go to OnRender(). Rewrite it as this:

void CApp::OnRender() {
    CSurface::OnDraw(SurfDisplay, SurfBackground, 0, 0);

    CArea::AreaControl.OnRender(SurfDisplay);

    for (int i = 0; i < CEntity::EntityList.size(); i++) {
        if (!CEntity::EntityList[i]) continue;
        CEntity::EntityList[i]->OnRender(SurfDisplay);
    }

    SDL_Flip(SurfDisplay);
}
Since we loaded a background to SurfBackground, we have to draw SurfBackground on SurfDisplay. And this MUST be the first render statement because otherwise, the background will be drawn OVER the other things. Also, the new function we created for CArea is called, taking only the filename and no camera coordinates (since we no longer need a camera).

Finally, the meat of the game. Go to CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode). Erase all that stuff involving camera movement, and rewrite the function as this:

void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_UP:
            if (ActionJackson.CanPlayerMoveUp() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X && CBlock::BlockList[i]->Y == ActionJackson.Y - 32) {
                        CBlock::BlockList[i]->OnMove(0, -32);
                    }
                }
                ActionJackson.OnMove(0, -32);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveUp() == YES) {
                ActionJackson.OnMove(0, -32);
                Moves++;
            }
            break;

        case SDLK_DOWN:
            if (ActionJackson.CanPlayerMoveDown() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X && CBlock::BlockList[i]->Y == ActionJackson.Y + 32) {
                        CBlock::BlockList[i]->OnMove(0, 32);
                    }
                }
                ActionJackson.OnMove(0, 32);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveDown() == YES) {
                ActionJackson.OnMove(0, 32);
                Moves++;
            }
            break;

        case SDLK_LEFT:
            if (ActionJackson.CanPlayerMoveLeft() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X - 32 && CBlock::BlockList[i]->Y == ActionJackson.Y) {
                        CBlock::BlockList[i]->OnMove(-32, 0);
                    }
                }
                ActionJackson.OnMove(-32, 0);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveLeft() == YES) {
                ActionJackson.OnMove(-32, 0);
                Moves++;
            }
            break;

        case SDLK_RIGHT:
            if (ActionJackson.CanPlayerMoveRight() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X + 32 && CBlock::BlockList[i]->Y == ActionJackson.Y) {
                        CBlock::BlockList[i]->OnMove(32, 0);
                    }
                }
                ActionJackson.OnMove(32, 0);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveRight() == YES) {
                ActionJackson.OnMove(32, 0);
                Moves++;
            }
            break;

        case SDLK_ESCAPE:
            Running = false;
            break;

        default: break;
    }
}

WOW. Let's go through that bunch of code. The same switch statement remains as before, but the inside is different. Let's take just the case of SDLK_UP. If the UP key is pressed: First, the function checks if the player can move, and that is done by calling the CanPlayerMove...() function and checking its return value. If there is a block (i.e. if CanPlayerMove...() returns BLOCK), then a for loop is run. For every block in CBlock's static BlockList vector, there's an if statement that checks if that block is the block that is next to the player. Otherwise, this function could just move any random block up, and that would obviously not be good. So this just figures out which block is next to the player. Once that's figured out, that block moves up, and the for loop continues. It's not going to return to the same block again, so it just keeps running until it's out of options. Then, ActionJackson moves up, sort of "following" the block. And the Move count increments up by one.

If ActionJackson.CanPlayerMove...() doesn't return BLOCK, none of that happens. The next if statement checks if that function returned YES. In this case, ActionJackson very simply moves up, not disturbing anything else, and the Move count increments up by one. Finally, if CanPlayerMove...() returns NO, nothing happens, so when up is pressed and the player can't move up, he DOESN'T.

This repeats for every arrow key. There we go! The game works! Now let's just make a use for CBlock's boolean attribute, OnHomeTile. Go to CApp::OnLoop(), and rewrite it as this:

void CApp::OnLoop() {
    for (int i = 0; i < CEntity::EntityList.size(); i++) {
        if (!CEntity::EntityList[i]) continue;
        CEntity::EntityList[i]->OnLoop();
    }

    bool BlocksAreHome = true;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        CBlock::BlockList[i]->CheckForHome();
        if (BlocksAreHome && !CBlock::BlockList[i]->OnHomeTile) {
            BlocksAreHome = false;
        } else { continue; }
    }

    if (BlocksAreHome) {
        Running = false;
    }
}
After the existing first for loop, a new boolean value is created. By default, BlocksAreHome is set to true, but that changes immediately if it isn't true. The next for loop runs through every block in CBlock's BlockList vector. Each of the blocks the loop runs through calls its CheckForHome() function, and accordingly sets its OnHomeTile boolean to true/false. Then, the block in the for loop is checked to be on its home tile with the if statement. If it's not on its home tile, BlocksAreHome is set to false. Otherwise, the for loop continues, leaving BlocksAreHome true. Finally, after the for loop, if BlocksAreHome is true, Running is set to false, and the game ends. This is just all I've written for the end of the level; feel free to add your own end-of-game stuff. Now the game is perfect. All you need to do is make the area, map, blocks, and graphics files. Thankfully, they're all packaged below. Also, in Define.h, set TILE_SIZE to 32, MAP_WIDTH to 20, and MAP_HEIGHT to 15.

The directory tree should be as follows (numbered by sublevel):
1. Sokoban (directory)
2. blocks (directory) gfx (directory), maps (directory), tilesets (directory).

In the blocks directory, put the files in the following .zip archive:
MediaFire - blocks.zip

In the gfx directory, put the files in the following .zip archive:
MediaFire - gfx.zip

In the maps directory, put the files in the following .zip archive:
MediaFire - maps.zip

In the tilesets directory, put the files in the following .zip archive:
MediaFire - tilesets.zip

COPYRIGHT:
Copyright 2008 Arseniy Banayev

This may be not be reproduced under any circumstances except for personal, private use. It may not be placed on any web site or otherwise distributed publicly without advance written permission. Use of this guide on any other web site or as a part of any public display is strictly prohibited, and a violation of copyright. This guide is only to be used on SDLTutorials.com, and only by its host, Tim Jones. If found anywhere else, the proper web law enforcement authorities will be notified for violating copyright.

All trademarks and copyrights contained in this document are owned by their respective trademark and copyright holders.

If you have any questions, comments, or misinformation concerning this guide, please leave comments or, in the rarest case, email me at xboteb13@gmail.com. Any electronic mail that I judge to be spam before opening the contents will be deleted automatically. Any malicious software attached to the electronic mail will not only be ignored and pointless, thanks to Ubuntu's security, but will be promptly reported to the appropriate law enforcement authorities and legal partners.

DISCLAIMER:
I will not be held responsible for any damages, reversible or irreversible, to your operating system, personal files, applications, or the physical computer itself, brought upon as a result of compiling and executing the given code or opening the provided files in the above packages.