Saturday, June 28, 2014

Making of a Simple Roguelike - Part 2 Screens

Untitled Document

Wherein I Admit to My Failure

   First of my failures is not getting another post up. Sorry about that as I just started my internship and it is a bit of a sharp turn about for my mind. Anyway my lack of posting is only partly having to learn another language in only a couple days. Honestly at this point I have the basics so its only learning the specific keywords for it.

   The big problem and failure on my part is not being able to pick something and stick with it. I have in my spare time coded and recoded my method of storing the three default screens. There is no way to get around the fact that it is just a few giant blobs of characters. So yeah, at the point where I am typing this I am going to stick with just hardcoding them in.

Now for the Screens Themselves

   The game screen where you will spend most of your time is important and thus I will begin with it. It was the easiest and probably hardest screen to design. Even now the design isn't complete. I was doing some tests on my map gen and figured out that a short map made with it feels best for this. This means that I actually ended up with too much room.


 ┌----------------------------------------------------┐ ┌---------------------┐ 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 |####################################################| |                     | 
 └----------------------------------------------------┘ |                     | 
                                                        |                     | 
 ┌------------------------------------------------------┘                     | 
 |                                                                            | 
 | HP:05                            a                                         | 
 |                                                                            | 
 | MoveRate: 5                      b                                         | 
 |                                                                            | 
 | DR Chance:                    c                                         | 
 |                                                                            | 
 | Block Chance:                 d                                         | 
 |                                                                            | 
 | Dodge Chance:                 e                                         | 
 |                                                                            | 
 └----------------------------------------------------------------------------┘

   The field of walls is actually the size of the map. It makes for a compact and relatively lineir experaince when I must admit my map gen wallows in dead ends and confusion normally. Letters are where equipment will be listed as I want it to be descriptive since there are only five spots. Now I just have to figure out what to do with the side. This doesn't matter though and it could just end up blank so onwards.

Start up and what does the screen you see look like?


 ┌----------------------------------------------------------------------------┐ 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |   Welcome to a challenge. If you are                                       | 
 |   here it means you want to join the             ▓     ▓▓▓▓  ▓▓▓▓  ▓▓▓     | 
 |   Adventurers guild but not just                 ▓     ▓  ▓  ▓  ▓  ▓  ▓    | 
 |   anyone can do that. You will need              ▓     ▓  ▓  ▓▓▓▓  ▓  ▓    | 
 |   your wits about you for this. I                ▓     ▓  ▓  ▓  ▓  ▓  ▓    | 
 |   have crafted a dungeon to test                 ▓▓▓▓  ▓▓▓▓  ▓  ▓  ▓▓▓     | 
 |   your mind more than your brawn we                                        | 
 |   can make you stronger but the mind                                       | 
 |   is a bit harder to work with.                                            | 
 |   Anyway if your sure you want to                                          | 
 |   take the challenge sign here                   ▓   ▓  ▓▓▓▓  ▓       ▓    | 
 |   here and here. Okay proceed do                 ▓▓  ▓  ▓     ▓   ▓   ▓    | 
 |   the stairs and remember   Your                 ▓ ▓ ▓  ▓▓    ▓   ▓   ▓    | 
 |   death isn't our fault and be                   ▓  ▓▓  ▓      ▓ ▓ ▓ ▓     | 
 |   careful as there are rumors of                 ▓   ▓  ▓▓▓▓    ▓   ▓      | 
 |   some breaks in the magic and more                                        | 
 |   powerful monsters showing up.                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                   ▓▓▓    ▓  ▓  ▓▓▓  ▓▓▓    | 
 |                                                  ▓   ▓   ▓  ▓   ▓    ▓     | 
 |                                                  ▓   ▓   ▓  ▓   ▓    ▓     | 
 |                                                  ▓   ▓   ▓  ▓   ▓    ▓     | 
 |                                                   ▓▓▓ ▓   ▓▓   ▓▓▓   ▓     | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 |                                                                            | 
 └----------------------------------------------------------------------------┘

   Once again I am dealing with too much space. Anyway it doesn't look too bad. In game there will be a frame around whichever option is selected at the moment. But all that aside we now have all the screens we need. Yes there is a pause screen but I am going to take a shortcut with that. Libtcod can make a frame which overwrites what it is drawn over so I will simply be drawing that and then the options.

   With that out of the way though I now have to put it into the code. I am thinking a simple class that just has getStartScreen and getGameScreen which takes the argument of a char**. It will actually be somewhat easier then it could be because of something I learned recently. Techincally it is something I should have known already but sometimes you just need stuff pointed out to you. The important thing I learned was that in notepad++ you can search and replace with regular expressions. I knew this but it took someone else pointing out that it means I can search for (.) and replace \1',' which will put that between each character.

   The problem that remains is caused by how I store the map I would have to rotate it because I go map[width][height]. I do this because it means when I am doing stuff in the code any array dealing with the map can be accessed with x,y. The problem in the past that I had was I would do it the other way and then get it all mixed up. So now I could just do the work, on the other hand I think just making a couple of private arrays going the other way will work fine. Once I put that down in code it seemed to work so I should be done with this.

Saturday, June 7, 2014

Making of a Simple Roguelike - Part 1 Setting Up

Untitled Document

So Libtcod

   As I noted in the planning post and in the heading just above here, I am going to use Libtcod for my roguelike. If you don't know of Libtcod it is a free API for roguelike development and provides many useful things like advanced true color console and input. Now that we have that out of the way we need a project to work on and add it too. I start with a blank project called JoiningTheAdventurersGuild but if your following along you can call yours whatever you want.

   To get Libtcod up and running in our project we need to grab it first. I am using the 1.5.2 Windows version to do this. In it there are a number of files but the ones we need to stick in our project folder are as follows:

  • libtcod-mingw.dll
  • libtcod-mingw-debug.dll
  • SDL.dll
  • terminal.png

   The png is very important as without it your program won't run. I had a good bit of trouble when I was working with the C# binding of libtcod as it didn't have it include at the time for some reason. The next step is to make sure the project knows what to do with those files so into the build options. Under debug in linker settings add a link to the libtcod\lib\libtcod-mingw-debug.a and then switch over to release and link to libtcod\lib\libtcod-mingw.a. Of course doing all this wont put anything on the screen and this isn't quite the point to go into how to use libtcod so I will just note that I copied the main.cpp from my default setup of libtcod to test it. If you want to take a look at it the first commit for the project on github has the code. With that I built and subsequently ran the project and everything worked. Now if you had problems there are some really good tutorials so take a look around or you can ask on the Libtcod forum. My suggestion for a C++ roguelike tutorial using Libtcod is here at Code::Umbra

Foundations

   Now to get some basic things hashed out. I looked into various ways of managing the game state and have decided I don't want any of the more complex stuff. There will be a start/home screen, the in-game stuff, and a menu for saving and quitting. They all stack one right on top of another and I will be dealing with this using some functions each with a while loop in it. Mind you the proper way would be to make an actual game state manager but that can be put in later (If you haven't been programming for long this is like saying I will clean my room tomorrow. You almost never go back and do stuff you put off if what you have works). Anyway I want to do some coding so I am going to try and mock up what I want with this and put what I end up with below.

#include "libtcod.hpp"
int MainScreen() {
    bool done = false;
    while (!done) {
        handleInput();
        //MainScreen Logic
        int gameoutput;
        if (gamestart) {
            gameoutput = GameScreen(randseed);
        }
        if (gameload) {
            gameoutput = GameScreen(levelseed);
        }
        if (gameoutput > 0) {
            setLoadSlot(gameoutput);
        }
        if (quit || gameoutput == -1) {
            done = true;
        }
        drawScreen(&mainscreen);
    }
    return 1;
}

int GameScreen(int seed) {
    bool ingame = true;
    int gameoutput = 0;
    while (ingame) {
        handleInput();
        //GameScreen Logic
        int pauseoutput
        if (paused) {
            pauseoutput = PauseScreen(&gamescreen);
        }
        if (pauseoutput == 1) {
            gameoutput = level;
            ingame = false;
        }
        if (gameover || pauseoutput == -1) {
            gameoutput = -1;
            ingame = false;
        }
        drawScreen(&gamescreen);
    }
    return gameoutput;
}

int PauseScreen(const /*some map or custom struct array*/ &gamescreen) {
    bool paused = true;
    int pauseoutput = 0;
    while (paused) {
        handleInput();
        //PauseScreen Logic
        if (quitgame) {
            pauseoutput = -1;
            paused = false;
        }
        if (savegame) {
            pauseoutput = 1;
            paused = false;
        }
        drawScreen(&pausescreen);
    }
    return pauseoutput;
}

int main() {
    TCODConsole::initRoot(80,50,"Joining The Adventurers Guild",false);
    return MainScreen();
}

   The above code wont work but it should show what I intend. I am basically stacking the states one on top of another with the only complicated bit being going to the pause screen. What is happening there is that I want the game screen to be in the background and maybe grayed out so I pass the current game screen to it. It is being passed as a const because I don't want it to be changed and this enforces that. Anyway this was the easy setup bit, all the fun and hard stuff goes in those comments mentioning logic and possibly whatever happens in handleInput.

   Speaking of handleInput I have figured out how I want to do it. Really when it comes down to it there is two control schemes only. A menu control scheme and the ingame control scheme. Since two of three are the menu version I can use a bool as the argument and the menu version will be true. Also lets rename it to getInput as I have decided to make it just return an int depending on what key is pressed and results that don't do something returning -1. The actual function code will be a simple switch statement. Code for both the change to the call and the implementation are just below.

int inputcode = getInput(true);
int getInput(bool menuscheme) {
    TCOD_key_t key = TCODConsole::checkForKeypress();
    if (menuscheme){
        if (key.vk == TCODK_ENTER || key.vk == TCODK_KPENTER) {
            return 0;
        }
        else if (key.vk == TCODK_ESCAPE || TCODK_BACKSPACE) {
            return 1;
        }
        else if (key.vk == TCODK_DOWN || key.vk == TCODK_KP2) {
            return 2;
        }
        else if (key.vk == TCODK_UP || key.vk == TCODK_KP8) {
            return 3;
        }
        else {
            return -1;
        }
    }
    else {
        switch(key.vk) {
            case TCODK_ESCAPE:
                return 0;
                break;
            case TCODK_CHAR:
                switch (key.c) {
                    default:
                        return -1;
                        break;
                }
                break;
            default:
                return -1;
                break;
        }
    }
}

   Do note that I have only filled out the menu keys. I only put how to deal with characters and the escape key in the game keys as I wanted to show how to do it. With that in place I now need to figure out how I will handle what to draw. It could be easy if I only wanted to use characters and everything was the same color. That is not the case though as I want colored enemies. This means a struct which contains the character and the color as follows:

struct Tile {
    char Symbol = ' ';
    TCODColor foreColor = TCODColor.lightestGrey;
    TCODColor backColor = TCODColor(15,15,15);
};

   Take note of the default backColor. While not important programmatically it is for the color scheme as a pure black can be too severe a contrast. Now to decide how the screen data is stored. Because the screen is always the same size I am thinking a simple 2d array of Tiles. With this decided I can write the drawScreen function.

void drawScreen(const Tile screen[][WINDOW_HEIGHT]) {
    TCODConsole::root->clear();
    for (int column = 0; column < WINDOW_HEIGHT; ++column){
        for (int row = 0; row < WINDOW_WIDTH; ++row){
            TCODConsole::root->putCharEx(row, column, screen[row][column].Symbol, screen[row][column].foreColor, screen[row][column].backColor);
        }
    }
    TCODConsole::flush();
}

   And that along with a couple of updates to the code all the remaining errors end up being about things not existing. More importantly those things don't exist because they are to be implemented in the logic. I now have most of the framework needed to get it running in some form. About all I can see needing to have the basic start up screen working is said start up screen. That will require what ends up being me just hardcoding the menu screens.

Small Addon

   As a bit of a fun in addition to the above I will layout how I plan to make dogs act. They will be the new monster on the 4th dungeon level and represented by a brown 'd'. Anyway I figured out a simple state machine setup for them that will make them hunt in a pack or so I hope. A dog will have three states depending on whether it can see other dogs or the player. The one they start in is alone and would be something like the following:

If see_player
   move_away
Else If saw_player
   reverse_of_last_move
Else
   rand_walk
If see_dog
   leave alone enter inpack

Next as you might intuit from the above will be the state of inpack

If see_player
   leave inpack enter hunting
If see_hunting_dog
   move_closer
else
   If distance_to_nearest_dog < 3
      move_away
   If !_see_dog
      leave inpack enter alone
   If distance_to_nearest_dog > 6
      move_closer
   else
      rand_walk

Finally of course is the state called hunting

If next_to_player
   attack_player
Else If see_player
   If other_dog_closer_to_player
      move_closer
   Else
      keep_distance_but_move
If saw_player
   move_towards_player
Else
   leave hunting enter alone

   And with that I hope to make an interesting pack behavior. If the dog is alone just wander or avoid the player but only just. When he finds another dog they will try to stay together but not too close. Finally if a dog that is currently in a pack sees the player other dogs that can see it will go to it. All dogs that can see the player will try to be just as close to the player as any other dog. In the end no dog will actually try to attack the player directly but if the player approaches a dog they will all get closer. I am basically trying to get the dogs to stay on the edge until the player either purposefully approaches them or ends up having to do so.

   Really it will be an interesting choice for the player. Do you deal with the dog now even though it will run away till it has a friend? How many do you let follow you before it is too many? And finally now that you let them follow you so far and you see a dog at the other end of the room how will you survive?

At least that is my hope for them