Mappy

Using this tutorial as a basis, I have a basic map going for my little Roguelike.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using libtcod;

namespace RLtest
{

    public class Program
        {

        static int ScreenX = 20;
        static int ScreenY = 15;
        static int nPlayerX = 10;
        static int nPlayerY = 10;

        const int MAP_X = 20;
        const int MAP_Y = 15;
        const int TILE_FLOOR = 0;
        const int TILE_WALL = 1;

        static int[,] nMapArray = new int[MAP_Y, MAP_X]
        {
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0 },
    { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 },
    { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 },
    { 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
        };

        static void screenInit()
            {
                libtcod.TCODConsole.initRoot(ScreenX, ScreenY, "RLTest", false);
            }

        static void DrawMap()
        {
            for (int x = 0; x < MAP_X; x++)
            {
                for (int y = 0; y < MAP_Y; y++)
                {
                    switch (nMapArray[y, x])
                    {
                        case TILE_FLOOR:
                            TCODConsole.root.putCharEx(x, y, '.', TCODColor.desaturatedRed, TCODColor.black);
                            break;

                        case TILE_WALL:
                            TCODConsole.root.putCharEx(x, y, '#', TCODColor.grey, TCODColor.black);
                            break;
                    }
                }
            }
        }

        static void HandleKeys()
        {
                //Output 

                TCODConsole.root.print(nPlayerX, nPlayerY, "@");
                TCODConsole.flush();

                //Input
                TCODKey Key = TCODConsole.checkForKeypress();
                if (Key.KeyCode == TCODKeyCode.KeypadOne)
                {
                    if (IsPassable(nPlayerX - 1, nPlayerY + 1))
                    {
                        nPlayerY++;
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadTwo)
                {
                    if (IsPassable(nPlayerX, nPlayerY + 1))
                    {
                        nPlayerY++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadThree)
                {
                    if (IsPassable(nPlayerX + 1, nPlayerY + 1))
                    {
                        nPlayerY++;
                        nPlayerX++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadFour)
                {
                    if (IsPassable(nPlayerX - 1, nPlayerY))
                    {
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadSix)
                {
                    if (IsPassable(nPlayerX + 1, nPlayerY))
                    {
                        nPlayerX++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadSeven)
                {
                    if (IsPassable(nPlayerX - 1, nPlayerY - 1))
                    {
                        nPlayerY--;
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadEight)
                {
                    if (IsPassable(nPlayerX, nPlayerY-1))
                    {
                        nPlayerY--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadNine)
                {
                    if (IsPassable(nPlayerX + 1, nPlayerY - 1))
                    {
                        nPlayerY--;
                        nPlayerX++;
                    }
                }
        }
        static bool IsPassable(int x, int y)
        {
            //Check co-ords are valid
            if (x < 0 || x >= MAP_X || y < 0 || y >= MAP_Y)
                return false;
            //Store value specified
            int nTileValue = nMapArray[y, x];
            //Return true if tile is passable
            if (nTileValue == TILE_FLOOR)
                return true;

            return false;

        }

        [STAThread]
        static void Main(string[] args)
        {
            screenInit();
            while (true)
            {
                DrawMap();
                HandleKeys();
                TCODConsole.root.clear();

            }
        }
        }
}

Running this code gives the following little screen, which allows movement of the @ symbol and also prevents the player walking through walls or off the screen.

rltest_map

Of course I want to have random dungeon generation rather than just a single, hard-coded map, but this is a good starting point to learn how the map is created and drawn. I suppose the next step is to try and change a value from floor to wall using a for loop.

There seem to be several different ways of approaching the programming of a Roguelike, and I will need to learn more about programming to know which way is best. One thing I have seen is the directional values defined as strings like ‘NORTH’ instead of (x, y-1). I might put this in next time.

I particularly need to learn more about how to use methods and functions – I guess a bit of theory is in order! I may write down my understanding of the terms, to clarify them in my mind, and also to be corrected if anyone more knowledgeable that I happens past this blog.

Posted in Code | Leave a comment

Sidetracking

My next subject to work on was making a dungeon for my little guy to walk about in, but I got sidetracked while messing around with a small tutorial, which involved colouring a character. I eventually want to use Libtcod in my RL, so I decided I had better get used to programming with it. With some Googling, fiddling around and peeking at source code, I managed to get the last program running using Libtcod rather than the system library. Some of the libraries included aren’t actually being used at this stage, I’ll have to clear that up.

I had a bit of confusion about  TCODConsole.Flush() , thinking that it did the same as Console.Clear() for some reason. Turns out what I was looking for was TCODConsole.root.clear(). Flush updates the characters and any colour changes, while clear…well, clears the console. While I remember, I also had a problem running the keyboard input through the switch -turns out that second equals sign is very important!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using libtcod;

namespace RLtest
{

    public class Program
        {

        static int ScreenX = 60;
        static int ScreenY = 50;
        static int nPlayerX = 40;
        static int nPlayerY = 12;

        static void screenInit()
            {

                libtcod.TCODConsole.initRoot(ScreenX, ScreenY, "RLTest", false);
            }
        static void HandleKeys()
        {
                //Output 

                TCODConsole.root.print(nPlayerX, nPlayerY, "@");
                TCODConsole.flush();

                //Input
                TCODKey Key = TCODConsole.checkForKeypress();
                if (Key.KeyCode == TCODKeyCode.KeypadOne)
                {
                    if (nPlayerX > 1 && nPlayerY < (ScreenY - 2))
                    {
                        nPlayerY++;
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadTwo)
                {
                    if (nPlayerY < (ScreenY - 2))
                    {
                        nPlayerY++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadThree)
                {
                    if (nPlayerX < (ScreenX - 2) && nPlayerY < (ScreenY - 2))
                    {
                        nPlayerY++;
                        nPlayerX++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadFour)
                {
                    if (nPlayerX > 1)
                    {
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadSix)
                {
                    if (nPlayerX < (ScreenX - 2))
                    {
                        nPlayerX++;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadSeven)
                {
                    if (nPlayerX > 1 && nPlayerY > 1)
                    {
                        nPlayerY--;
                        nPlayerX--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadEight)
                {
                    if (nPlayerY > 1)
                    {
                        nPlayerY--;
                    }
                }

                else if (Key.KeyCode == TCODKeyCode.KeypadNine)
                {
                    if (nPlayerX < (ScreenX - 2) && nPlayerY > 1)
                    {
                        nPlayerY--;
                        nPlayerX++;
                    }
                }
        }

        [STAThread]
        static void Main(string[] args)
        {
            screenInit();
            while (true)
            {
                HandleKeys();
                TCODConsole.root.clear();

            }
        }
        }
}
Posted in Code | Tagged , , , | Leave a comment

Initial Forays

I have spent a lot of time going over the C# references linked in the last post, I am beginning to get some grasp of how the language works. The logical nature of programming appeals to the scientific side of my mind, which hasn’t had much exercise since I finished high school, although it is still much easier for me to look at examples in terms of game mechanics, and probably always will be.

I have looked with interest over several <1KB Roguelikes, and they seem to be a good starting point to understand the code of the most basic elements of the genre. As RLs tend to be very complicated, and have many, many features, it is difficult to look at the source code and know where to start. The 1KRL that was hosted at http://nrkn.com/1kRl/, which seems to be down at the moment, was the one I chose to dissect first. As the code is heavily abbreviated, I went through and substituted all the letters for the words they stand for, to make it easier to understand. I am not sure of the name of the author of this RL, if you know, tell me in the comments.

You can download the original VS solution here, and the expanded solution (VS2010)here.

I have also been looking with interest at some name generation code, that I would eventually like to play with myself. My understanding of different parts of the code is dubious at the moment, but it is a great way to get direction for my learning. I have uploaded some interesting sites to the reference page, if you are interested.

Anyway, on to actually learning to code something. Based on “How to write a Roguelike in 15 Steps”, and a couple of RL coding tutorials I have looked at, getting my little @ to move around the screen is the first thing to do. I am using this tutorial, although it is in C++, it is easy enough to convert it to C# with some Googling and browsing reference sites.

I’ll document what I did here, because this is my blog and I want to. Here is the first part…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RLtest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Clear();

            int nPlayerX, nPlayerY;

            nPlayerX = 40;
            nPlayerY = 12;

            while (true)
            {
                Console.SetCursorPosition(nPlayerX, nPlayerY);
                Console.Write("@");

            }
        }
    }
}

What this does, which is very exciting for me, and extremely boring for anyone else, is to draw an @ in the middle of the console screen. My first step towards a roguelike!


Cue epic guitar solo!

The next step is to get the symbol to move when the appropriate key is pressed. I prefer using the numpad for input, so I’m going with that. I had a bit of trouble figuring out how to use Console.ReadKey, but I got the solution from that 1KRL I have been studying – if you append .KeyChar to the end of the method, it will grab a char for you rather than the data type it gets by default, which is something like System.ConsoleKeyInfo.

namespace RLtest
{
    class Program
    {
        static void Main(string[] args)
        {

            int nPlayerX, nPlayerY;

            nPlayerX = 40;
            nPlayerY = 12;
            char input;

            while (true)
            {
                //Output
                Console.Clear();
                Console.SetCursorPosition(nPlayerX, nPlayerY);
                Console.Write("@");

                //Input
                input = (char)Console.ReadKey(true).KeyChar;

                //Processing
                switch(input)
                {
                case '8':
                        nPlayerY--;
                        break;
                case '4':
                        nPlayerX--;
                        break;
                case '6':
                        nPlayerX++;
                        break;
                case '2':
                        nPlayerY++;
                        break;
                }
            }
        }
    }
}

So now we have… an @ walking around a screen! Joy! As suggested in the tutorial, I can now add a little extra code to make it impossible to walk off the edge of the screen and crash the program. The values here for the window size are the default window size, which is 80×25, but we may want to change that in the future, so I am going to add some variables so that I can change that easily later. The numbers used here I chose arbitrarily to test if the movement code would work. As well as this, I added the ability to move diagonally as suggested in the tutorial.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RLtest
{
    class Program
    {
        static void Main(string[] args)
        {

            int nPlayerX, nPlayerY, ScreenX, ScreenY;

            ScreenX = 100;
            ScreenY = 30;
            nPlayerX = 40;
            nPlayerY = 12;

            System.Console.WindowWidth = ScreenX;
            System.Console.WindowHeight = ScreenY;

            char input;

            while (true)
            {
                //Output
                Console.Clear();     
                Console.SetCursorPosition(nPlayerX, nPlayerY);
                Console.Write("@");

                //Input
                input = (char)Console.ReadKey(true).KeyChar;

                //Processing
                switch(input)
                {
                case '1':
                        if (nPlayerX > 1 && nPlayerY < (ScreenY - 2))
                        {
                            nPlayerY++;
                            nPlayerX--;
                        }
                        break;

                case '2':
                        if (nPlayerY < (ScreenY-2))   
                            nPlayerY++;  
                        break;

                case '3':
                        if (nPlayerX < (ScreenX - 2) && nPlayerY < (ScreenY - 2))
                        {
                            nPlayerY++;
                            nPlayerX++;
                        }
                        break;

                case '4':
                        if (nPlayerX > 1)
                            nPlayerX--;
                        break;

                case '6':
                        if (nPlayerX < (ScreenX-2))
                            nPlayerX++;
                        break;

                case '7':
                        if (nPlayerX > 1 && nPlayerY > 1)
                        {
                            nPlayerY--;
                            nPlayerX--;
                        }
                        break;

                case '8':
                        if (nPlayerY > 1)
                            nPlayerY--;
                        break;

                case '9':
                        if (nPlayerX < (ScreenX - 2) && nPlayerY > 1)
                        {
                            nPlayerY--;
                            nPlayerX++;
                        }
                        break;

                }            
            }
        }
    }
}

I’ll leave this for now, the next step is to deal with map generation.