Let’s Build Rogue-Like … Part 6

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 6 is all about making sure there is a monster for hero to kill. In practice this means creating objects for different monsters and showing them in screen. Additionally monsters should be able to move in different ways such as randomly or stay in place.

I created a new method called RunMonsterLogic which should make it possible for monster to move according to selected strategies. Changes between strategies are not yet possible but this method is intended to be a crude finite state machine (FSM).

The logic for not leaving the map and not hitting the walls has also been implemented. I did this in RunCharacterTryToMove method. This works so well that I remade the logic in hero’s movement system to use it as well. This cleans up the previous code.

namespace AdventureGame001
{
    class GameObject
    {
        internal const int SCREEN_WIDTH = 80;
        internal const int SCREEN_HEIGHT = 24;

        CharacterHero MyHero;
        internal int GameFinished = 0;

        String[] GameMapCurrent;

        List<String[]> GameMapList = new List<string[]>();

        List<Character> MonsterList = new List<Character>();

        Random random = new Random();

        internal void RunGameInitializer()
        {
            int selection = 0;
            do
            {
                Console.WriteLine("Game Initializer");
                Console.WriteLine("1: New Game");
                Console.WriteLine("2: Continue");
                Console.WriteLine("3: Exit Game");
                selection = int.Parse(Console.ReadLine());
            } while (selection != 1 && selection != 2 && selection != 3);

            switch (selection)
            {
                case 1:
                    RunCreateHeroCharacter();
                    RunCreateGameMap();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 2:
                    RunLoadGameData();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 3:
                    GameFinished = 3;
                    break;
                default:
                    break;
            }
        }

        internal void RunGameLoop()
        {
            ConsoleKey PlayerInput = RunGetPlayerInput();

            RunGameLogic(PlayerInput);

            RunMonsterLogic();

            RunDrawGameWindowLayout();

            if (GameFinished == 0)
            {
                GameFinished = RunDetermineEndCondition();
            }
        }

        void RunMonsterLogic()
        {
            foreach (Character monster in MonsterList)
            {
                switch (monster.Strategy)
                {
                    case Character.NPCStrategy.Wander:
                        monster.MonsterStatus = "!";
                        switch (random.Next(8))
                        {
                            case 0:
                                RunCharacterTryToMove(monster, -1, 0);
                                break;
                            case 1:
                                RunCharacterTryToMove(monster, 1, 0);
                                break;
                            case 2:
                                RunCharacterTryToMove(monster, 0, -1);
                                break;
                            case 3:
                                RunCharacterTryToMove(monster, 0, 1);
                                break;
                            default:
                                break;
                        }
                        break;
                    case Character.NPCStrategy.Sleep:
                        monster.MonsterStatus = "?";
                        break;
                    case Character.NPCStrategy.Patrol:
                        monster.MonsterStatus = monster.MonsterType;
                        break;
                    case Character.NPCStrategy.Fight:
                        monster.MonsterStatus = monster.MonsterType;
                        break;
                    default:
                        break;
                }
            }
        }

        Boolean RunCharacterTryToMove(Character thisChar, int toX, int toY)
        {
            if ((thisChar.Position.X > 0) && 
                (thisChar.Position.X < SCREEN_WIDTH - 1) &&
                (thisChar.Position.Y > 0) &&
                (thisChar.Position.Y < SCREEN_HEIGHT - 1)
                )
            {
                if (RunWallMovementTest(GameMapCurrent[thisChar.Position.Y + toY][thisChar.Position.X + toX]) == true)
                {
                    thisChar.Position.X += toX;
                    thisChar.Position.Y += toY;
                    return true;
                }
            }
            return false;
        }

        void RunCreateHeroCharacter()
        {
            MyHero = new CharacterHero();
            MyHero.FoodLeft = 100;
            MyHero.HitPointsLeft = 1;
            MyHero.Position.X = 1;
            MyHero.Position.Y = 1;
            MyHero.ActivityMessage = "Hero Created!";
        }

        ConsoleKey RunGetPlayerInput()
        {
            return Console.ReadKey(true).Key;
        }

        void RunGameLogic(ConsoleKey playerinput)
        {
            switch (playerinput)
            {
                case ConsoleKey.A:
                case ConsoleKey.LeftArrow:
                    if (RunCharacterTryToMove(MyHero,-1,0) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes West!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go West!";
                    }
                    break;
                case ConsoleKey.D:
                case ConsoleKey.RightArrow:
                    if (RunCharacterTryToMove(MyHero, 1, 0) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes East!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go East!";
                    }
                    break;
                case ConsoleKey.W:
                case ConsoleKey.UpArrow:
                    if (RunCharacterTryToMove(MyHero, 0, -1) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes North!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go North!";
                    }
                    break;
                case ConsoleKey.S:
                case ConsoleKey.DownArrow:
                    if (RunCharacterTryToMove(MyHero, 0, 1) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes South!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go South!";
                    }
                    break;
                case ConsoleKey.PageDown:
                    if ( RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true )
                    {
                        MyHero.ActivityMessage = "Going Down to Next Level!";
                    }
                    break;
                case ConsoleKey.PageUp:
                    if (RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true)
                    {
                        MyHero.ActivityMessage = "Going Up to Previous Level!";
                    }
                    break;
                case ConsoleKey.Q:
                    RunSaveGameData();
                    GameFinished = 3;
                    break;
                default:
                    break;
            }
            MyHero.FoodLeft--;
        }

        void RunLoadGameData()
        {
            List<String> loaddata = new List<string>();
            String line = "";

            StreamReader file = new StreamReader(Environment.CurrentDirectory + @"\AdventureGame001.txt");
            while ((line = file.ReadLine()) != null)
            {
                loaddata.Add(line);
            }
            file.Close();

            int dummy = 0;
            int numberofmaps = int.Parse(loaddata[dummy]);
            for (int i=0; i < numberofmaps; i++)
            {
                String[] dummymap = new String[24];
                for (int j=0; j < 24; j++)
                {
                    dummy++;
                    dummymap[j] = loaddata[dummy];
                }
                GameMapList.Add(dummymap);
            }
            GameMapCurrent = GameMapList.Last<string[]>();

            MyHero = new CharacterHero();
            dummy++;
            MyHero.FoodLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.HitPointsLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.X = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.Y = int.Parse(loaddata[dummy]);

            MyHero.ActivityMessage = "Hero Loaded!";

        }

        void RunSaveGameData()
        {
            List<String> savedata = new List<string>();
            savedata.Add(GameMapList.Count.ToString());
            foreach(string[] map in GameMapList)
            {
                foreach(string iter in map)
                {
                    savedata.Add(iter);
                }
            }
            savedata.Add(MyHero.FoodLeft.ToString());
            savedata.Add(MyHero.HitPointsLeft.ToString());
            savedata.Add(MyHero.Position.X.ToString());
            savedata.Add(MyHero.Position.Y.ToString());

            using (StreamWriter outputFile = new StreamWriter(Environment.CurrentDirectory + @"\AdventureGame001.txt"))
            {
                foreach (string line in savedata)
                    outputFile.WriteLine(line);
            }
        }


        void RunCreateGameMap()
        {
            String[] ZeroLevel = new String[]
                {
                "                                                                                ",
                "   --------------              --------------------           -------------     ",
                "   |            |              |        %         |           |           |     ",
                "   |            |              |     C A V E      |           |           |     ",
                "   |            |              |                  |           |           |     ",
                "   --------------              ---------+----------           -------------     ",
                "                                                                                ",
                "                                                                                ",
                "                                                                                ",
                "                                                             ----------         ",
                "       ------                     ------+-------             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |  S H O P   |             ----------         ",
                "       ------                     |            |                                ",
                "                                  --------------                                ",
                "                                                                                ",
                "                                                                                ",
                "       -----                       ---------                  --------          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       -----                       ---------                  --------          ",
                "                                                                                "
            };

            GameMapCurrent = ZeroLevel;
            GameMapList.Add(GameMapCurrent);

            Character TestMonster1 = new Character("B", 10, 15, Character.NPCStrategy.Wander);
            MonsterList.Add(TestMonster1);
            Character TestMonster2 = new Character("B", 20, 15, Character.NPCStrategy.Sleep);
            MonsterList.Add(TestMonster2);
        }


        void RunDrawGameWindowLayout()
        {
            Console.Clear();
            for (int i=0; i < SCREEN_HEIGHT; i++)
            {
                for (int j=0; j < SCREEN_WIDTH; j++)
                {
                    Console.Write(GameMapCurrent[i][j]);
                }
            }

            Console.SetCursorPosition(0, 24);
            Console.Write("Food Left: {0}", MyHero.FoodLeft);

            Console.SetCursorPosition(40, 24);
            Console.Write(MyHero.ActivityMessage);

            foreach (Character monster in MonsterList)
            {
                Console.SetCursorPosition(monster.Position.X, monster.Position.Y);
                Console.Write(monster.MonsterStatus);
            }

            Console.SetCursorPosition(MyHero.Position.X, MyHero.Position.Y);
            Console.Write("@");
        }

        Boolean RunWallMovementTest(char testedlocation)
        {
            if (testedlocation == '-' || testedlocation == '|')
            {
                MyHero.ActivityMessage = "Hero Hit wall";
                return false;
            }
            return true;
        }

        Boolean RunStairsMovementTest(char testedlocation)
        {
            if (testedlocation == '%')
            {
                return true;
            }
            return false;
        }

        int RunDetermineEndCondition()
        {
            if (MyHero.FoodLeft <= 0)
            {
                return 1;
            }

            if (MyHero.HitPointsLeft <= 0)
            {
                return 2;
            }

            return 0;
        }

    }
}

Character has changes to match selection strategy. Currently some strategies are not supported (Patrol and Fight).

namespace AdventureGame001
{
    class Character
    {
        internal enum NPCStrategy
        {
            Wander,
            Sleep,
            Patrol,
            Fight
        }

        internal Character() { } 
        internal Character(string newType, int newX, int newY, NPCStrategy newStrategy)
        {
            MonsterType = newType;
            MonsterStatus = newType;
            Position.X = newX;
            Position.Y = newY;
            Strategy = newStrategy;
        }
        internal string MonsterType = "";
        internal string MonsterStatus = "";
        internal struct CharacterPosition
        {
            internal int X;
            internal int Y;
        }
        internal CharacterPosition Position;
        internal int HitPointsLeft = 0;

        internal NPCStrategy Strategy = NPCStrategy.Sleep;
    }
}

In my tests two test monsters have been created and they act according to their selected strategies.

2016-02-24

Let’s Build Rogue-Like … Part 5

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 5 is all about making sure there is some way to load and store maps (and other game data).

I started by making two methods: RunLodGameData and RunSaveGameData for loading and storing game data. I read and write data as strings as this allows me to easily change save files by hand as well as see what was done and what was actual state of game at the point of storing the game data.

The rest of the functionality include adding save button that also works as quit. as well as functionality to choose between new game and pre-existing game.

namespace AdventureGame001
{

    class Program
    {
        static void Main(string[] args)
        {
            // Window Settings
            Console.Title = "My Rogue-Like Game";
            Console.WindowWidth = 80;
            Console.BufferWidth = 80;
            Console.WindowHeight = 25;
            Console.BufferHeight = 25;

            RunShowStartScreen();

            // Create a game object
            GameObject MyGame = new GameObject();
            if (MyGame != null)
            {
                MyGame.RunGameInitializer();
            }

            do
            {
                MyGame.RunGameLoop();
            } while (MyGame.GameFinished == 0);

            RunShowEndScreen(MyGame.GameFinished);
        }

        static void RunShowStartScreen()
        {
            Console.Clear();
            Console.SetCursorPosition(15, 10);
            Console.WriteLine("This is a Rogue-Like Game Called \"My Rogue-Like Game\".");
            Console.SetCursorPosition(15, 15);
            Console.WriteLine("(c) Janne Kemppi, 2016");
            Console.SetCursorPosition(15, 20);
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }


        static void RunShowEndScreen(int reason)
        {
            Console.Clear();
            Console.SetCursorPosition(20, 10);
            switch (reason)
            {
                case 1:
                    Console.WriteLine("You Died for Hunger...");
                    break;

                case 2:
                    Console.WriteLine("You Died for External Violence...");
                    break;

                case 3:
                    Console.WriteLine("You Stopped Playing via Exit...");
                    break;

                default:
                    Console.WriteLine("You Died for General Error...");
                    break;
            }
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }
    }
}
namespace AdventureGame001
{
    class GameObject
    {
        internal const int SCREEN_WIDTH = 80;
        internal const int SCREEN_HEIGHT = 24;

        CharacterHero MyHero;
        internal int GameFinished = 0;

        String[] GameMapCurrent;

        List<String[]> GameMapList = new List<string[]>();

        internal void RunGameInitializer()
        {
            int selection = 0;
            do
            {
                Console.WriteLine("Game Initializer");
                Console.WriteLine("1: New Game");
                Console.WriteLine("2: Continue");
                Console.WriteLine("3: Exit Game");
                selection = int.Parse(Console.ReadLine());
            } while (selection != 1 && selection != 2 && selection != 3);

            switch (selection)
            {
                case 1:
                    RunCreateHeroCharacter();
                    RunCreateGameMap();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 2:
                    RunLoadGameData();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 3:
                    GameFinished = 3;
                    break;
                default:
                    break;
            }
        }

        internal void RunGameLoop()
        {
            ConsoleKey PlayerInput = RunGetPlayerInput();

            RunGameLogic(PlayerInput);

            RunDrawGameWindowLayout();

            if (GameFinished == 0)
            {
                GameFinished = RunDetermineEndCondition();
            }
        }

        void RunCreateHeroCharacter()
        {
            MyHero = new CharacterHero();
            MyHero.FoodLeft = 100;
            MyHero.HitPointsLeft = 1;
            MyHero.Position.X = 1;
            MyHero.Position.Y = 1;
            MyHero.ActivityMessage = "Hero Created!";
        }

        ConsoleKey RunGetPlayerInput()
        {
            return Console.ReadKey(true).Key;
        }

        void RunGameLogic(ConsoleKey playerinput)
        {
            switch (playerinput)
            {
                case ConsoleKey.A:
                case ConsoleKey.LeftArrow:
                    if ( (MyHero.Position.X > 0) )
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X - 1]) == true)
                        {
                            MyHero.Position.X -= 1;
                            MyHero.ActivityMessage = "Hero Goes West!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go West!";
                    }
                    break;
                case ConsoleKey.D:
                case ConsoleKey.RightArrow:
                    if (MyHero.Position.X < SCREEN_WIDTH - 1)
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X + 1]) == true)
                        {
                            MyHero.Position.X += 1;
                            MyHero.ActivityMessage = "Hero Goes East!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go East!";
                    }
                    break;
                case ConsoleKey.W:
                case ConsoleKey.UpArrow:
                    if (MyHero.Position.Y > 0)
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y - 1][MyHero.Position.X]) == true)
                        {
                            MyHero.Position.Y -= 1;
                            MyHero.ActivityMessage = "Hero Goes North!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go North!";
                    }
                    break;
                case ConsoleKey.S:
                case ConsoleKey.DownArrow:
                    if (MyHero.Position.Y < SCREEN_HEIGHT - 1)
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y + 1][MyHero.Position.X]) == true)
                        {
                            MyHero.Position.Y += 1;
                            MyHero.ActivityMessage = "Hero Goes South!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go South!";
                    }
                    break;
                case ConsoleKey.PageDown:
                    if ( RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true )
                    {
                        MyHero.ActivityMessage = "Going Down to Next Level!";
                    }
                    break;
                case ConsoleKey.PageUp:
                    if (RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true)
                    {
                        MyHero.ActivityMessage = "Going Up to Previous Level!";
                    }
                    break;
                case ConsoleKey.Q:
                    RunSaveGameData();
                    GameFinished = 3;
                    break;
                default:
                    break;
            }
            MyHero.FoodLeft--;
        }

        void RunLoadGameData()
        {
            List<String> loaddata = new List<string>();
            String line = "";

            StreamReader file = new StreamReader(Environment.CurrentDirectory + @"\AdventureGame001.txt");
            while ((line = file.ReadLine()) != null)
            {
                loaddata.Add(line);
            }
            file.Close();

            int dummy = 0;
            int numberofmaps = int.Parse(loaddata[dummy]);
            for (int i=0; i < numberofmaps; i++)
            {
                String[] dummymap = new String[24];
                for (int j=0; j < 24; j++)
                {
                    dummy++;
                    dummymap[j] = loaddata[dummy];
                }
                GameMapList.Add(dummymap);
            }
            GameMapCurrent = GameMapList.Last<string[]>();

            MyHero = new CharacterHero();
            dummy++;
            MyHero.FoodLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.HitPointsLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.X = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.Y = int.Parse(loaddata[dummy]);

            MyHero.ActivityMessage = "Hero Loaded!";

        }

        void RunSaveGameData()
        {
            List<String> savedata = new List<string>();
            savedata.Add(GameMapList.Count.ToString());
            foreach(string[] map in GameMapList)
            {
                foreach(string iter in map)
                {
                    savedata.Add(iter);
                }
            }
            savedata.Add(MyHero.FoodLeft.ToString());
            savedata.Add(MyHero.HitPointsLeft.ToString());
            savedata.Add(MyHero.Position.X.ToString());
            savedata.Add(MyHero.Position.Y.ToString());

            using (StreamWriter outputFile = new StreamWriter(Environment.CurrentDirectory + @"\AdventureGame001.txt"))
            {
                foreach (string line in savedata)
                    outputFile.WriteLine(line);
            }
        }


        void RunCreateGameMap()
        {
                        String[] ZeroLevel = new String[]
                            {
                            "                                                                                ",
                            "   --------------              --------------------           -------------     ",
                            "   |            |              |        %         |           |           |     ",
                            "   |            |              |     C A V E      |           |           |     ",
                            "   |            |              |                  |           |           |     ",
                            "   --------------              ---------+----------           -------------     ",
                            "                                                                                ",
                            "                                                                                ",
                            "                                                                                ",
                            "                                                             ----------         ",
                            "       ------                     ------+-------             |        |         ",
                            "       |    |                     |            |             |        |         ",
                            "       |    |                     |            |             |        |         ",
                            "       |    |                     |  S H O P   |             ----------         ",
                            "       ------                     |            |                                ",
                            "                                  --------------                                ",
                            "                                                                                ",
                            "                                                                                ",
                            "       -----                       ---------                  --------          ",
                            "       |   |                       |       |                  |      |          ",
                            "       |   |                       |       |                  |      |          ",
                            "       |   |                       |       |                  |      |          ",
                            "       -----                       ---------                  --------          ",
                            "                                                                                "
                        };

                        GameMapCurrent = ZeroLevel;
                        GameMapList.Add(GameMapCurrent);
        }


        void RunDrawGameWindowLayout()
        {
            Console.Clear();
            for (int i=0; i < SCREEN_HEIGHT; i++)
            {
                for (int j=0; j < SCREEN_WIDTH; j++)
                {
                    Console.Write(GameMapCurrent[i][j]);
                }
            }

            Console.SetCursorPosition(0, 24);
            Console.Write("Food Left: {0}", MyHero.FoodLeft);

            Console.SetCursorPosition(40, 24);
            Console.Write(MyHero.ActivityMessage);

            Console.SetCursorPosition(MyHero.Position.X, MyHero.Position.Y);
            Console.Write("@");
        }

        Boolean RunWallMovementTest(char testedlocation)
        {
            if (testedlocation == '-' || testedlocation == '|')
            {
                MyHero.ActivityMessage = "Hero Hit wall";
                return false;
            }
            return true;
        }

        Boolean RunStairsMovementTest(char testedlocation)
        {
            if (testedlocation == '%')
            {
                return true;
            }
            return false;
        }

        int RunDetermineEndCondition()
        {
            if (MyHero.FoodLeft <= 0)
            {
                return 1;
            }

            if (MyHero.HitPointsLeft <= 0)
            {
                return 2;
            }

            return 0;
        }

    }
}

Let’s Build Rogue-Like … Part 4

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 4 is having some kind of a map and making sure hero ”@” does not go through walls.

I added a ZeroLevel map as string array and then use GameMapCurrent as final map that will be used throughout the game when action moves to dungeon.

My idea is that as I create levels there will be a GameMapList object that houses generated levels. Currently it only holds Level Zero.

Then I started to ponder how to implement doors. Simpliest method is actually use ”+” for doors and make proper checks for walls. Thus problem of implementing doors actually disappeared. 🙂

I also added stairwell (marked with ”%” in map). This allowed me to make hero move up and down between levels. I use ”PageUp” and ”PageDown” for this movement. I have not yet made actual level changing code as it requires procedural map creation.

Finally I added arrows for movement for sheer convenience as well. Some Rogue purists will no doubt be endlessly irritated by these heretical activities. 🙂

namespace AdventureGame001
{
    class GameObject
    {
        internal const int SCREEN_WIDTH = 80;
        internal const int SCREEN_HEIGHT = 24;

        CharacterHero MyHero;
        internal int GameFinished = 0;

        String[] GameMapCurrent;

        List<String[]> GameMapList = new List<string[]>();

        internal void RunGameInitializer()
        {
            RunCreateHeroCharacter();
            RunCreateGameMap();
            RunDrawGameWindowLayout();
            Console.CursorVisible = false;
            GameFinished = 0;
        }

        internal void RunGameLoop()
        {
            ConsoleKey PlayerInput = RunGetPlayerInput();

            RunGameLogic(PlayerInput);

            RunDrawGameWindowLayout();

            GameFinished = RunDetermineEndCondition();
        }

        void RunCreateHeroCharacter()
        {
            MyHero = new CharacterHero();
            MyHero.FoodLeft = 100;
            MyHero.HitPointsLeft = 1;
            MyHero.Position.X = 1;
            MyHero.Position.Y = 1;
            MyHero.ActivityMessage = "Hero Created!";
        }

        ConsoleKey RunGetPlayerInput()
        {
            return Console.ReadKey(true).Key;
        }

        void RunGameLogic(ConsoleKey playerinput)
        {
            switch (playerinput)
            {
                case ConsoleKey.A:
                case ConsoleKey.LeftArrow:
                    if ( (MyHero.Position.X > 0) )
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X - 1]) == true)
                        {
                            MyHero.Position.X -= 1;
                            MyHero.ActivityMessage = "Hero Goes West!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go West!";
                    }
                    break;
                case ConsoleKey.D:
                case ConsoleKey.RightArrow:
                    if (MyHero.Position.X < SCREEN_WIDTH - 1)                     {                         if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X + 1]) == true)                         {                             MyHero.Position.X += 1;                             MyHero.ActivityMessage = "Hero Goes East!";                         }                     }                     else                     {                         MyHero.ActivityMessage = "Hero Tries to Go East!";                     }                     break;                 case ConsoleKey.W:                 case ConsoleKey.UpArrow:                     if (MyHero.Position.Y > 0)
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y - 1][MyHero.Position.X]) == true)
                        {
                            MyHero.Position.Y -= 1;
                            MyHero.ActivityMessage = "Hero Goes North!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go North!";
                    }
                    break;
                case ConsoleKey.S:
                case ConsoleKey.DownArrow:
                    if (MyHero.Position.Y < SCREEN_HEIGHT - 1)
                    {
                        if (RunWallMovementTest(GameMapCurrent[MyHero.Position.Y + 1][MyHero.Position.X]) == true)
                        {
                            MyHero.Position.Y += 1;
                            MyHero.ActivityMessage = "Hero Goes South!";
                        }
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go South!";
                    }
                    break;
                case ConsoleKey.PageDown:
                    if ( RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true )
                    {
                        MyHero.ActivityMessage = "Going Down to Next Level!";
                    }
                    break;
                case ConsoleKey.PageUp:
                    if (RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true)
                    {
                        MyHero.ActivityMessage = "Going Up to Previous Level!";
                    }
                    break;
                default:
                    break;
            }
            MyHero.FoodLeft--;
        }

        void RunCreateGameMap()
        {

            String[] ZeroLevel = new String[]
                {
                "                                                                                ",
                "   --------------              --------------------           -------------     ",
                "   |            |              |        %         |           |           |     ",
                "   |            |              |     C A V E      |           |           |     ",
                "   |            |              |                  |           |           |     ",
                "   --------------              ---------+----------           -------------     ",
                "                                                                                ",
                "                                                                                ",
                "                                                                                ",
                "                                                             ----------         ",
                "       ------                     ------+-------             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |  S H O P   |             ----------         ",
                "       ------                     |            |                                ",
                "                                  --------------                                ",
                "                                                                                ",
                "                                                                                ",
                "       -----                       ---------                  --------          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       -----                       ---------                  --------          ",
                "                                                                                "
            };

            GameMapCurrent = ZeroLevel;
            GameMapList.Add(GameMapCurrent);
        }

        void RunDrawGameWindowLayout()
        {
            Console.Clear();
            for (int i=0; i < SCREEN_HEIGHT; i++)
            {
                for (int j=0; j < SCREEN_WIDTH; j++)
                {
                    Console.Write(GameMapCurrent[i][j]);
                }
            }

            Console.SetCursorPosition(0, 24);
            Console.Write("Food Left: {0}", MyHero.FoodLeft);

            Console.SetCursorPosition(40, 24);
            Console.Write(MyHero.ActivityMessage);

            Console.SetCursorPosition(MyHero.Position.X, MyHero.Position.Y);
            Console.Write("@");
        }

        Boolean RunWallMovementTest(char testedlocation)
        {
            if (testedlocation == '-' || testedlocation == '|')
            {
                MyHero.ActivityMessage = "Hero Hit wall";
                return false;
            }
            return true;
        }

        Boolean RunStairsMovementTest(char testedlocation)
        {
            if (testedlocation == '%')
            {
                return true;
            }
            return false;
        }

        int RunDetermineEndCondition()
        {
            if (MyHero.FoodLeft <= 0)
            {
                return 1;
            }

            if (MyHero.HitPointsLeft <= 0)
            {
                return 2;
            }

            return 0;
        }

    }
}

And finally a pretty picture…

2016-02-19

Let’s Build Rogue-Like … Part 3

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 3 is getting ”@” move in the screen.

First, I decided to create a GameObject object which would hold entire initialization and game loop. The reason is that it is typically far easier to run things with object’s methods than static methods in C#. It also cleans up the main method nicely.

Main method has two screens for showing fancy graphics: start screen and end screen. The latter also shows why character died. So far there are only two reasons: hunger and external violence.

namespace AdventureGame001
{

    class Program
    {
        static void Main(string[] args)
        {
            // Window Settings
            Console.Title = "My Rogue-like Game";
            Console.WindowWidth = 80;
            Console.BufferWidth = 80;
            Console.WindowHeight = 25;
            Console.BufferHeight = 25;

            RunShowStartScreen();

            // Create a game object
            GameObject MyGame = new GameObject();
            if (MyGame != null)
            {
                MyGame.RunGameInitializer();
            }

            do
            {
                MyGame.RunGameLoop();
            } while (MyGame.GameFinished == 0);

            RunShowEndScreen(MyGame.GameFinished);
        }

        static void RunShowStartScreen()
        {
            Console.Clear();
            Console.SetCursorPosition(15, 10);
            Console.WriteLine("This is a Rogue-Like Game Called \"My Rogue-Like Game\" ");
            Console.SetCursorPosition(15, 15);
            Console.WriteLine("(c) Janne Kemppi, 2016");
            Console.SetCursorPosition(15, 20);
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }

        static void RunShowEndScreen(int reason)
        {
            Console.Clear();
            Console.SetCursorPosition(20, 10);
            switch (reason)
            {
                case 1:
                    Console.WriteLine("You Died for Hunger...");
                    break;

                case 2:
                    Console.WriteLine("You Died for External Violence...");
                    break;

                default:
                    Console.WriteLine("You Died for General Error...");
                    break;
            }
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }
    }
}

The GameObject object runs the actual game and it’s main part is called
RunGameLoop. This is general method of making game loops. First user input
is gathered and then its effects determined. This causes changed circumstances
which force us to redraw map display. Finally hero’s status is looked at to
see if character has died for one reason or another.

The logic determining what will be done is based on studying individual key
strokes. This works well in Rogue-like games where every command is a single
key stroke.

Activity messages ae used to show what happened based on a key stroke.

namespace AdventureGame001
{
    class GameObject
    {
        internal const int SCREEN_WIDTH = 80;
        internal const int SCREEN_HEIGHT = 24;

        CharacterHero MyHero;
        internal int GameFinished = 0;

        internal void RunGameInitializer()
        {
            RunCreateHeroCharacter();
            RunDrawGameWindowLayout();
            Console.CursorVisible = false;
            GameFinished = 0;
        }

        internal void RunGameLoop()
        {
            ConsoleKey PlayerInput = RunGetPlayerInput();

            RunGameLogic(PlayerInput);

            RunDrawGameWindowLayout();

            GameFinished = RunDetermineEndCondition();
        }

        void RunCreateHeroCharacter()
        {
            MyHero = new CharacterHero();
            MyHero.FoodLeft = 100;
            MyHero.HitPointsLeft = 1;
            MyHero.Position.X = 1;
            MyHero.Position.Y = 1;
            MyHero.ActivityMessage = "Hero Created!";
        }

        ConsoleKey RunGetPlayerInput()
        {
            return Console.ReadKey(true).Key;
        }

        void RunGameLogic(ConsoleKey playerinput)
        {
            switch (playerinput)
            {
                case ConsoleKey.A:
                    if (MyHero.Position.X > 0)
                    {
                        MyHero.Position.X -= 1;
                        MyHero.ActivityMessage = "Hero Goes West!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go West!";
                    }
                    break;
                case ConsoleKey.D:
                    if (MyHero.Position.X < SCREEN_WIDTH - 1)
                    {
                        MyHero.Position.X += 1;
                        MyHero.ActivityMessage = "Hero Goes East!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go East!";
                    }
                    break;
                case ConsoleKey.W:
                    if (MyHero.Position.Y > 0)
                    {
                        MyHero.Position.Y -= 1;
                        MyHero.ActivityMessage = "Hero Goes North!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go North!";
                    }
                    break;
                case ConsoleKey.S:
                    if (MyHero.Position.Y < SCREEN_HEIGHT - 1)
                    {
                        MyHero.Position.Y += 1;
                        MyHero.ActivityMessage = "Hero Goes South!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go South!";
                    }
                    break;
                default:
                    break;
            }

            MyHero.FoodLeft--;
        }

        void RunDrawGameWindowLayout()
        {
            Console.Clear();
            Console.SetCursorPosition(0, 24);
            Console.Write("Food Left: {0}", MyHero.FoodLeft);

            Console.SetCursorPosition(40, 24);
            Console.Write(MyHero.ActivityMessage);

            Console.SetCursorPosition(MyHero.Position.X, MyHero.Position.Y);
            Console.Write("@");

        }

        int RunDetermineEndCondition()
        {
            if (MyHero.FoodLeft <= 0)
            {
                return 1;
            }

            if (MyHero.HitPointsLeft <= 0)
            {
                return 2;
            }

            return 0;
        }

    }
}

Finally there is main character who is the hero. I have decided to use
inheritance here to make both monsters and hero as characters. I hope
that it will eventually save me few lines when I start implementing
different kinds of monsters.

Character class sets hit points that reflect hero’s ability to withstand
external violence (like sword strikes) and position in map (with X and Y
coordinates matching screens width and height). CharacterHero is a sub-class
of Character class which adds food status to character. Monsters will never
run out of food. Monsters will also not have activity messages, they are
there just to die for hero’s sword.

namespace AdventureGame001
{
    class Character
    {
        internal int HitPointsLeft = 0;

        internal struct CharacterPosition
        {
            internal int X;
            internal int Y;
        }
        internal CharacterPosition Position;

    }
}

namespace AdventureGame001
{
    class CharacterHero : Character
    {
        internal int FoodLeft = 0;
        internal string ActivityMessage = "";
    }
}

Finally a couple of pretty pictures….

2016-02-17

2016-02-17 (1)

 

2016-02-17 (2)

Let’s build Rogue-like … Part 2

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 2 is the classic ”Hello World!”

My first decision is to select a language I am intimate with to serve as a basis for this. I can choose from C, C++, Qt, Symbian (just kidding), Objective-C, Swift, Java and C#. I decide to use C# because I intend to write future games with Unity and it goes hand in hand with it. I select Windows 10 as it is easily most common operating system available. My IDE is going to be Visual Studio 2015 as it is easily best IDE around (Xcode makes me scream in frustration sometimes).

There are libraries and frameworks available for Rogue-coders but I decide against them at this point. I’d like to use C#’s own extensive set of classes as basis. I hope I do not regret my decision. I also decide to use English as language in my project in vain hope of gaining more attention.

I also decide to make this a console application because it allows concentrating on game rules rather than graphics. I do freely admit that graphics is my weak point and fixing it requires me to study Blender in future.

Anyways, my second goal is to get famous Hello World! text in console display:

2016-02-15

And it works! Every great game starts from this. 🙂

And here is the secret source code:

namespace AdventureGame001
{
    class Program
    {
        static void Main(string[] args)
        {
            // Window Settings
            Console.Title = "My Rogue-like Game";
            Console.WindowWidth = 80;
            Console.BufferWidth = 80;
            Console.WindowHeight = 25;
            Console.BufferHeight = 25;

            Console.Write("Hello World!");

            Console.ReadLine();
        }
    }
}

Let’s Build Rogue-like… Part 1

After a discussion with one of my students in IOS course I came into conclusion that trying practical game making could be a good exercise for my limited free time. After pondering what would be a good idea, I started to dream about old genre of Rogue-likes.

The following web site looked like a good start:

http://www.roguebasin.com

I found interesting idea:

How to Write a Roguelike in 15 Steps

And here we go…

Step 1 is deciding to write a game …. and I have now fulfilled it. 🙂