Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

Tuesday, August 30, 2011

Libtcod in C#: I give up


   No not on Libtcod but instead on trying to program a roguelike without something like it. I don't currently know enough about putting pictures on screen to make what I want on my own. I "tried" in the 7DRL but used a library to display the screen and it did not allow me the control of the the console that I wanted.
   I have given up and thus can proceed with it as Libtcod does all that I need and then some. I actually have my recursive shadow casting code all tidied up into a library so I could technically use it but Libtcod comes with basically the same code already built in so using my library would just add another complication. I could just use the generic C# random number generator but Libtcod has a slightly better generator and allows me to specify the seed so I can get the same string of numbers whenever I want.
   I do however still plan to work on the pathfinding myself as it interests me and I want to see what I can do with it before just using Libtcod's built in pathfinding.
   There may be a few posts on Libtcod in specific and the Roguelike I will be slapping together but nothing is certain as Collage just started up on Monday so my schedule may less free time in it real soon. Anyway a few things I picked up while testing Libtcod.

  • The libtcod 1.5.1 c# download did not have terminal.png in it which is the default font so your code will fail for lack of it. To get it you can get any of the other 1.5.1 downloads and maybe any of the downloads though I have only checked the 1.5.1 versions. (or just change the default font if you happen to have another just laying around)
  • You need libtcod-net-unmanaged.dll in the same folder as your .exe and libtcod-VS.dll needs to be with it. libtcod-VS.dll also needs to be in the same folder as libtcod-net.dll so basicly its easier to have them all just in there with your exe. A quick list of what I had in with the exe to get libtcod running correctly:
    • libtcod-net.dll
    • libtcod-net-unmanaged.dll
    • libtcod-VS.dll
    • SDL.dll
    • zlib1.dll
    • terminal.png
  • I use Microsoft Visual Studios for this as I get a good version of it free through collage. Making the game as a console app works fine though normally you would have the blank console that normally appears as well as the game console made by Libtcod. You can get rid of the blank one through project properties and changing output type from console application to windows application.
  • libtcod-net.dll is the one you add as a reference, you don't need to add any of the others.
Overall I like what I saw once I got libtcod working right and I had figured out what I needed to do. The documentation for libtcod 1.5.1 is Here though it seems the C# stuff needs a lot of work to actually reflect reality as it is but overall is quite helpful. Of course I will probably do a post about the basic stuff and what I observed happening anyway.

Wednesday, July 27, 2011

Thoughts dealing with self contained dungeon features

   Recently I have been rereading Ascii Dreams a blog by the creator of Unangband and I ended up thinking quite a bit about some of what I read. As it pertains to this post I was thinking on what I had read in This article which is part 6 of 9 on how Unangband's dungeon is generated. Particularly I was interested in the idea of complex dungeon features being achieved by simple self contained states. An example from the post is that there is regular Water which when hit by something that can hurt water causes it to transition to another state, the crest of a way. The crest in turn hurts all adjacent water tiles and transitions into a wave then next turn into rough water which does nothing except transition into Water. combining this all together and you get dynamic waves that don't need to store any kind of information past what they are. While not that realistic of a water system it does a good enough job without all the hassle of trying to figure out stuff like how to combine flows and water pressure.
   I in an uncharacteristic bout of whimsy programming set up a little console demo thing. Nothing complex but I was going for base level simplicity anyway. I started it to find out what a fire affect would look like. The rules for the fire was that if a space in a cardinal direction was empty the space became fire and the current fire tile transitions to smoke. Smoke does nothing but transistion to empty space but serves the same purpose that the rough water did, IE a buffer zone so the fire does not double back on itself.
   Of course I made a mistake in the code that would check if a space was empty and did not realize it so I made light smoke which I had smoke transition to. That was the beggining of my sinking more then four hours into something that technicly could have been done in one. You see the smoke was interesting. Sure all it did was transition turn by turn till it was gone but each state only took three lines of code in a simple Switch, one of  which was for the break. The code went like so:
      case '▒':
         NewMap[x,y] = '░'
         break;
Now I will admit I am a little over exuberant about all this and it is mostly old new and what not but still I am posting it because it was amusing for me to fool around with. Anyway the next thing I whipped up was A weird arrow thing that I can't really remember what I did except that because of how I cycled through the tiles it ended up having artifacts and a big fondness for diagonally downwards arrows and the whole thing ended up being transformed into the current form.
   As I mentioned I like how the smoke worked and wanted to do more with it so I thought of it in roguelike terms. How could I incorporate the spreading of smoke as a goal in and of itself. The easy answer would have been magic so I decided to try a different route. Smoke arrows was what I finally came up with. What good would they be? Why smoke obscures line of sight and maybe even light. The only problem I had was that the simple smoke I had lasted at most three turns. Of course while in flight the arrow itself puts out smoke so there is a trail but and seeing as I did not really plan on doing anything big with it the arrows have infinite range seeing as all they do is check if the space in the direction they are facing is empty and going there if so. It still did not leave a smoke trail like I wanted so I did something more complex. Well calling it complex is a little overstating it. All I did was have the tile be a number going from 6 to 1 and when the console actually writes out the screen I have it put '░' for 1 and 2, '▒' for 3 and 4, and '▓' for 5 and 6. I only applied this to the smoke left directly in the arrows path and it created a good looking affect.
   Now for the specifics on the actual code. The map itself is held in an char array called CurrentMap and is 35 characters wide and 21 characters tall though because of asthetics I only have the actual game space take up 35 by 19 leaving the bottom two rows blank where in a roguelike the stats and such would be. I then use a for loop nested in a for loop to fill in the NewMap array with what the game space will look like. All this currently means is that it puts a wall around the game space made of '#' and fills the interior with spaces. After that a few thing including the code that resizes the actual console size and buffer as well as a line that puts a character in the middle of the map so I can test the functionality of whatever I am working on.
   The main game loop is simple enough, just a do while loop that continues until you press the escape key. Inside the loop the first thing is once again a for loop nested in a for loop which lets me cycle through all the characters stored in CurrentMap and put them through a Switch with a case for each of the currently implemented tiles. Because most of them are just smoke and the ones that are not I have elsewhere in the code most of the cases take three lines if that. Then surprise, surprise more for loops though this set is important as its the place we put all the characters on the screen from. First there is an If statement that checks whether CurrentMap[x, y] != NewMap[x, y] so I don't end up redrawing the whole game area every time and only change what has actually changed. Then it has the bit of code that changes the numbers into smoke, just another switch. Finally the program makes the CurrentMap equal the NewMap and thats all there is to the main loop.
   Of course thats not all the code though. I have most of the functionality of the various things in methods. I have one for how fire spreads, one to write what I want where I want, one for all the smoke arrow directions all in one, and finally two I should probably merge together because the only difference is one checks if the  spaces next to a tile in the cardinal directions are clear and the other checks the diagonal directions. Both the checks return a bool array so I can just declare it 'i' and do If (i[0]). The only thing really stopping me right now is after I removed the weird arrow thing I did not use the diagonal check for anything so having to check four extra spaces did not make much sense.
   The full code as of now is below and its not really commented that well as I only did what I needed to have right when I was coding it. I should have more even though most of it is really simple if only for the fact I know I will forget something import later when I come back to it. If you have any questions just ask in the comments. Also if you see any errors on my part don't keep it to yourself and snigger at my incompetence, point it out in the comments so others can avoid my mistakes.



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

namespace Testing_with_Squares
{
    class Program
    {
        static char[,] CurrentMap = new char[35, 21];
        static char[,] NewMap = new char[35, 21];
        static void Main(string[] args)
        {
            for (int x = 0; x < 35; x++)
            {
                for (int y = 0; y < 19; y++)
                {
                    if ((x == 0) || (x == 34) || (y == 0) || (y == 18))
                    {
                        NewMap[x, y] = '#';
                    }
                    else
                    {
                        NewMap[x, y] = ' ';
                    }
                }
            }
            Console.SetWindowSize(35, 21);
            Console.SetBufferSize(35, 21);
            Console.CursorVisible = false;
            NewMap[17, 9] = '>';
            ConsoleKeyInfo cki = new ConsoleKeyInfo();
            do
            {
                cki = Console.ReadKey(true);
                for (int x = 0; x < 35; x++)
                {
                    for (int y = 0; y < 21; y++)
                    {
                        switch (CurrentMap[x, y])
                        {
                            case '*':
                                FireSpread(x, y);
                                break;
                            case '1':
                                NewMap[x, y] = ' ';
                                break;
                            case '2':
                                NewMap[x, y] = '1';
                                break;
                            case '3':
                                NewMap[x, y] = '2';
                                break;
                            case '4':
                                NewMap[x, y] = '3';
                                break;
                            case '5':
                                NewMap[x, y] = '4';
                                break;
                            case '6':
                                NewMap[x, y] = '5';
                                break;
                            case '░':
                                NewMap[x, y] = ' ';
                                break;
                            case '▒':
                                NewMap[x, y] = '░';
                                break;
                            case '▓':
                                NewMap[x, y] = '▒';
                                break;
                            case '<':
                            case '>':
                            case '^':
                            case 'v':
                                SmokeTravel(x, y);
                                break;
                            default:
                                break;
                        }
                    }
                }
                for (int x = 0; x < 35; x++)
                {
                    for (int y = 0; y < 21; y++)
                    {
                        if (CurrentMap[x, y] != NewMap[x, y])
                        {
                            switch (NewMap[x, y])
                            {
                                case '6':
                                case '5':
                                    Write(x, y, '▓');
                                    break;
                                case '4':
                                case '3':
                                    Write(x, y, '▒');
                                    break;
                                case '2':
                                case '1':
                                    Write(x, y, '░');
                                    break;
                                default:
                                    Write(x, y, NewMap[x, y]);
                                    break;
                            }
                            CurrentMap[x, y] = NewMap[x, y];
                        }
                    }
                }
            } while (cki.Key != ConsoleKey.Escape);
        }

        static void Write(int x, int y, char c)
        {
            Console.SetCursorPosition(x, y);
            Console.Write(c);
        }

        static void FireSpread(int x, int y)
        {
            NewMap[x, y] = '░';
            bool[] i = CardinalCheck(x, y);   // 0 = ^, 1 = >, 2 = v, 3 = <
            if (i[0])
            {
                NewMap[x, y - 1] = '*';
            }
            if (i[1])
            {
                NewMap[x + 1, y] = '*';
            }
            if (i[2])
            {
                NewMap[x, y + 1] = '*';
            }
            if (i[3])
            {
                NewMap[x - 1, y] = '*';
            }
        }
       
        static void SmokeTravel(int x, int y)
        {
            NewMap[x, y] = '6';
            bool[] i = CardinalCheck(x, y);   // 0 = ^, 1 = >, 2 = v, 3 = <
            switch (CurrentMap[x, y])
            {
                case '<':
                    if (i[3])
                    {
                        x--;
                        NewMap[x, y] = '<';
                        i = CardinalCheck(x, y);
                    }
                    break;
                case '>':
                    if (i[1])
                    {
                        x++;
                        NewMap[x, y] = '>';
                        i = CardinalCheck(x, y);
                    }
                    break;
                case '^':
                    if (i[0])
                    {
                        y--;
                        NewMap[x, y] = '^';
                        i = CardinalCheck(x, y);
                    }
                    break;
                case 'v':
                    if (i[2])
                    {
                        y++;
                        NewMap[x, y] = 'v';
                        i = CardinalCheck(x, y);
                    }
                    break;
                default:
                    break;
            }
            switch (NewMap[x, y])
            {
                case '<':
                case '>':
                    if (i[0]) { NewMap[x, y - 1] = '▓'; }
                    if (i[2]) { NewMap[x, y + 1] = '▓'; }
                    break;
                case '^':
                case 'v':
                    if (i[1]) { NewMap[x + 1, y] = '▓'; }
                    if (i[3]) { NewMap[x - 1, y] = '▓'; }
                    break;
                default:
                    break;
            }
        }

        static bool[] CardinalCheck(int x, int y)
        {
            bool[] i = new bool[4];   // 0 = ^, 1 = >, 2 = v, 3 = <
            if ((CurrentMap[x, y - 1] == ' ') && (NewMap[x, y - 1] == ' '))
            { i[0] = true; }
            if ((CurrentMap[x + 1, y] == ' ') && (NewMap[x + 1, y] == ' '))
            { i[1] = true; }
            if ((CurrentMap[x, y + 1] == ' ') && (NewMap[x, y + 1] == ' '))
            { i[2] = true; }
            if ((CurrentMap[x - 1, y] == ' ') && (NewMap[x - 1, y] == ' '))
            { i[3] = true; }
            return i;
        }

        static bool[] DiagonalCheck(int x, int y)
        {
            bool[] ii = new bool[4];   // 0 = ┌218, 1 = ┐191, 2 = ┘217, 3 = └192
            if ((CurrentMap[x - 1, y - 1] == ' ') && (NewMap[x - 1, y - 1] == ' '))
            { ii[0] = true; }
            if ((CurrentMap[x + 1, y - 1] == ' ') && (NewMap[x + 1, y - 1] == ' '))
            { ii[1] = true; }
            if ((CurrentMap[x + 1, y + 1] == ' ') && (NewMap[x + 1, y + 1] == ' '))
            { ii[2] = true; }
            if ((CurrentMap[x - 1, y + 1] == ' ') && (NewMap[x - 1, y + 1] == ' '))
            { ii[3] = true; }

            return ii;
        }
    }
}

Friday, June 17, 2011

What could you program in a month?

   I don't know what I can do but apparently Shamus over at Twenty Sided can do this:

Friday, March 11, 2011

7DRL Day 6

   Today I was once again stalled at the AI. I could not manage the code I started yesterday so I tried to mock up a simple come at me AI but did not manage even that. Tomorrow I will learn some more and in the least put out my thing as is at the end of the day. I think I accidentally put the hardest part off till last and am regretting it.

Thursday, March 10, 2011

7DRL Day 5

   Today was collage so once again not much done. I did not finish the simple ai I was working on...   Well I have not finished debugging it. My creatures cheat a little in that they simply check a line between the player and them out to their sight range to see if anything is blocking its view or the player is out of range. So while it technically knows where you are at all times it only sees you when it should, or rather that is how it should work and right now it does not even see you all the time though that may be a problem with some other part of my code. At least I have it so I can only see the monster when it is in sight though that was simply checking if the monster location was visible.

Tuesday, March 8, 2011

7DRL Day 4

   Today I mostly wrestled with FOV and failed to track down the last bug.
The red should be lit just like the green is.

Monday, March 7, 2011

7DRL Day 3


   Today I worked on getting the map to display right and shift along when the player gets to the edge. I also did the start screen and detailed the flavor for the game. The '<' in the screen just represents the place you start at and nothing more.
   I was able to use the following code to place the start area in the upper left corner and plan to use an inversion of it to place the Boss in the other corner.

bool testwalk = false; int startX=0, startY=0;
while (!testwalk)
{
    if (map.tile[startY, startX].Walkable) { testwalk = true; }
    else if (startY < startX) { startY++; }
    else { startX++; }
}
map.tile[startY, startX].Symbol = '<';
map.tile[startY, startX].Walkable = false;
player = new Player(10, true, startX, startY, '@', TermColor.White);


   I set the map.tile.walkable that the player is on to start so that monsters an stuff do not walk onto it. When you move it resets it to true and your new position to back to false. the "player"  has in order hp, alive, x, y, symbol, and the color. Tomorrow or the next day there will also be soul strength.
   I did not do all that I wanted today but because of collage I did not have the time. My '@' now walks in rooms and hallways only and not in the walls so once I get it so it does not automatically show the whole map it will at least be interesting to explore. Because of how I set it up Visibility is its own thing so you will be able to see through monsters but not walk into their square and stand on them.

Sunday, March 6, 2011

7DRL Day 2

   I mostly just worked on getting my map generator working today. While I like the maps it makes there are some leftover artifacts from the generation method and the code is currently slower than I want it but for the first map generator I have ever made it does incredibly well. I also have my bit of code that makes a text file of the map still in so I can continue to test it and see exactly what is going on.
    class Map
    {
        public int Height, Width;
        public Tile[,] tile;
        public Map(int Height, int Width)
        {
            this.Height = Height;
            this.Width = Width;
            this.tile = new Tile[Width, Height];
        }
    }
    class Tile
    {
        public bool Walkable;
        public bool Seethrough;
        public bool Room_Wall;
        public char Symbol;
        public Tile(bool Walkable, bool Seethrough, bool Room_Wall, char Symbol)
        {
            this.Walkable = Walkable;
            this.Seethrough = Seethrough;
            this.Room_Wall = Room_Wall;
            this.Symbol = Symbol;
        }
    }
    class Map_Generation
    {
        public Map map;
        private Random rand = new Random();
        private Rect Previous_Room,Starting_Room, hallway;
        private List rooms = new List();
        private int  Room_Count, Max_Size,Room_Fit, Hallway_Direction, BadRoom_Count, BackTrack;
        private bool Room_Made;
        private void New_Room(int x, int y)
        {
            rooms[Room_Count] = (new Rect(x, y, rand.Next(5, 11), rand.Next(5, 11)));
        }
        private void New_Room(int x, int y, int w, int h)
        {
            rooms[Room_Count] = (new Rect(x, y, w, h));
        }
        private void New_Hallway(int Offset)
        {
            switch (rand.Next(4))
            {
                case 0:
                    hallway = new Rect(rand.Next(Previous_Room.X + 1, Previous_Room.X + Previous_Room.Width - 1), Previous_Room.Y - Offset + 1, 1, Offset);
                    Hallway_Direction = 0;
                    break;
                case 1:
                    hallway = new Rect(Previous_Room.X + Previous_Room.Width - 1, rand.Next(Previous_Room.Y + 1, Previous_Room.Y + Previous_Room.Height - 1), Offset, 1);
                    Hallway_Direction = 1;
                    break;
                case 2:
                    hallway = new Rect(rand.Next(Previous_Room.X + 1, Previous_Room.X + Previous_Room.Width - 1), Previous_Room.Y + Previous_Room.Height - 1, 1, Offset);
                    Hallway_Direction = 2;
                    break;
                case 3:
                    hallway = new Rect(Previous_Room.X - Offset + 1, rand.Next(Previous_Room.Y + 1, Previous_Room.Y + Previous_Room.Height - 1), Offset, 1);
                    Hallway_Direction = 3;
                    break;
                default:
                    throw new InvalidOperationException();
            }
        }
        private void Transcribe_Room()
        {
            for (int a = 0; a < rooms[Room_Count].Height; a++)
            {
                for (int b = 0; b < rooms[Room_Count].Width; b++)
                {
                    if ((a == 0) || (a == rooms[Room_Count].Height - 1) || (b == 0) || (b == rooms[Room_Count].Width - 1))
                    {
                        map.tile[rooms[Room_Count].X + b, rooms[Room_Count].Y + a].Room_Wall = true;
                    }
                    else
                    {
                        map.tile[rooms[Room_Count].X + b, rooms[Room_Count].Y + a].Seethrough = true;
                        map.tile[rooms[Room_Count].X + b, rooms[Room_Count].Y + a].Walkable = true;
                        map.tile[rooms[Room_Count].X + b, rooms[Room_Count].Y + a].Symbol = '.';
                    }
                }
            }
        }
        private void Transcribe_Hallway()
        {
            for (int a = 0;a
            {
                for (int b = 0; b < hallway.Width; b++)
                {
                    map.tile[hallway.X + b, hallway.Y + a].Seethrough = true;
                    map.tile[hallway.X + b, hallway.Y + a].Walkable = true;
                    map.tile[hallway.X + b, hallway.Y + a].Symbol = '.';
                }
            }
        }
        private bool Check_Position()
        {
            if ((rooms[Room_Count].Y < 1) || (rooms[Room_Count].Y + rooms[Room_Count].Height >= map.Height - 1) || (rooms[Room_Count].X < 1) || (rooms[Room_Count].X + rooms[Room_Count].Width >= map.Width - 1))
            {
                return false;
            }
            else
            {
                for (int a = 0; a < rooms[Room_Count].Height; a++)
                {
                    for (int b = 0; b < rooms[Room_Count].Width; b++)
                    {
                        if (map.tile[rooms[Room_Count].X + b, rooms[Room_Count].Y + a].Room_Wall)
                        {
                            return false;
                        }
                    }
                }
                return true;
            }
        }
        public Map_Generation(int Height, int Width)
        {
            this.map = new Map(Height, Width);
            this.Max_Size = 600;
            for (int a = 0; a < Height; a++) { for (int b = 0; b < Width; b++) { map.tile[a, b] = new Tile(false, false, false, '#'); } }
            rooms.Add(new Rect(0, 0, 0, 0));
            New_Room((rand.Next(Width - 15,Width - 10)), rand.Next(Height - 15,Height - 10));
            Starting_Room = rooms[Room_Count];
            Previous_Room = rooms[Room_Count];
            Transcribe_Room();
            Room_Count++;
            while (Room_Count < Max_Size)
            {
                Room_Made = false;
                BackTrack = 1;
                Previous_Room = rooms[Room_Count-BackTrack];
                rooms.Add(new Rect(0,0,0,0));
                while (!Room_Made)
                {
                    New_Hallway(rand.Next(2, 9));
                    New_Room(0, 0);
                    switch (Hallway_Direction)
                    {
                        case 0:
                            New_Room(hallway.X - (rooms[Room_Count].Width / 2), hallway.Y - rooms[Room_Count].Height + 1, rooms[Room_Count].Width, rooms[Room_Count].Height);
                            break;
                        case 1:
                            New_Room(hallway.X + hallway.Width - 1, hallway.Y - (rooms[Room_Count].Height / 2), rooms[Room_Count].Width, rooms[Room_Count].Height);
                            break;
                        case 2:
                            New_Room(hallway.X - (rooms[Room_Count].Width / 2), hallway.Y + hallway.Width - 1, rooms[Room_Count].Width, rooms[Room_Count].Height);
                            break;
                        case 3:
                            New_Room(hallway.X - rooms[Room_Count].Width + 1, hallway.Y - (rooms[Room_Count].Height / 2), rooms[Room_Count].Width, rooms[Room_Count].Height);
                            break;
                        default:
                            throw new InvalidOperationException();
                    }
                    if (Check_Position())
                    {
                        Transcribe_Room();
                        Transcribe_Hallway();
                        Room_Made = true;
                        Room_Count++;
                        BadRoom_Count = 0;
                    }
                    BadRoom_Count++;
                    if (BadRoom_Count > 20)
                    {
                        BackTrack++;
                        if (BackTrack > Room_Count) { BackTrack = 1; Room_Fit++; }
                        Previous_Room = rooms[Room_Count - BackTrack];
                        BadRoom_Count = 0;
                    }
                }
                if (Room_Fit >= 50)
                {
                    Max_Size = Room_Count;
                }
            }
            StreamWriter SW = new StreamWriter("Test.txt");
            for (int a = 0; a < map.Height; a++)
            {
                for (int b = 0; b < map.Width; b++)
                {
                    SW.Write(map.tile[a, b].Symbol);
                }
                SW.WriteLine();
            }
            SW.Close();
        }
    }

   I spent most of my time working on it trying to get placement working right till I figured out I needed to store all of the rooms instead not. I finally figured it out and decided on using a list which works out quite well for what I needed.
   The other thing that took a while was cutting features. I originally was going to have water and lava tiles but I ended up tossing that for this game because I had enough on my hands with just generating a regular map. I still need to add doors to it but this was a purposeful choice to not include it here. The way I have it planned I will need to add them later but that will be easy because I will only need to look for map tiles that are both using the '.' character and have Room_Wall as true. I have an example of the map output Here on the Bay12 forums thread for 7DRL 2011.

Saturday, March 5, 2011

7DRL Day 1

   I have mostly just been setting up my project so far. I have the '@' walking around the screen at least. Most of my time was taken by figuring out Malison which is a C# terminal graphics library similar to curses. I also set up a few classes including an important one that will be of much help. It is a Direction class that I basically took from These Two articles on the blog "Writing Kode". Here is the Direction class I have:
    class Direction
    {
        private int Index, DX, DY;
        private string Name;
        public static readonly Direction North = new Direction() { Index = 0, DX = 0, DY = -1, Name = "North" };
        public static readonly Direction East = new Direction() { Index = 1, DX = 1, DY = 0, Name = "East" };
        public static readonly Direction South = new Direction() { Index = 2, DX = 0, DY = 1, Name = "South" };
        public static readonly Direction West = new Direction() { Index = 3, DX = -1, DY = 0, Name = "West" };
        public static readonly Direction Northeast = new Direction() { Index = 4, DX = 1, DY = -1, Name = "Norhteast" };
        public static readonly Direction Southeast = new Direction() { Index = 5, DX = 1, DY = 1, Name = "Southeast" };
        public static readonly Direction Northwest = new Direction() { Index = 6, DX = -1, DY = -1, Name = "Northwest" };
        public static readonly Direction Southwest = new Direction() { Index = 7, DX = -1, DY = 1, Name = "Southwest" };
        public Vec ApplyTransform(Vec Location)
        {
            return new Vec(Location.X + DX, Location.Y + DY);
        }
        public Direction Inverse
        {
            get
            {
                switch (Name)
                {
                    case "North":
                        return Direction.South;
                    case "Northeast":
                        return Direction.Southwest;
                    case "East":
                        return Direction.West;
                    case "Southeast":
                        return Direction.Northwest;
                    case "South":
                        return Direction.North;
                    case "Southwest":
                        return Direction.Northeast;
                    case "West":
                        return Direction.East;
                    case "Northwest":
                        return Direction.Southeast;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
        public Direction NextPerpendicular
        {
            get
            {
                switch (Name)
                {
                    case "North":
                        return Direction.East;
                    case "Northeast":
                        return Direction.Southeast;
                    case "East":
                        return Direction.South;
                    case "Southeast":
                        return Direction.Southwest;
                    case "South":
                        return Direction.West;
                    case "Southwest":
                        return Direction.Northeast;
                    case "West":
                        return Direction.North;
                    case "Northwest":
                        return Direction.Northeast;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
        public Direction PreviousPerpendicular
        {
            get
            {
                switch (Name)
                {
                    case "North":
                        return Direction.West;
                    case "Northeast":
                        return Direction.Northwest;
                    case "East":
                        return Direction.North;
                    case "Southeast":
                        return Direction.Northeast;
                    case "South":
                        return Direction.East;
                    case "Southwest":
                        return Direction.Southeast;
                    case "West":
                        return Direction.South;
                    case "Northwest":
                        return Direction.Southwest;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
        public Direction Next
        {
            get
            {
                switch (Name)
                {
                    case "North":
                        return Direction.Northeast;
                    case "Northeast":
                        return Direction.East;
                    case "East":
                        return Direction.Southeast;
                    case "Southeast":
                        return Direction.South;
                    case "South":
                        return Direction.Southwest;
                    case "Southwest":
                        return Direction.West;
                    case "West":
                        return Direction.Northwest;
                    case "Northwest":
                        return Direction.North;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
        public Direction Previous
        {
            get
            {
                switch (Name)
                {
                    case "North":
                        return Direction.Northwest;
                    case "Northeast":
                        return Direction.North;
                    case "East":
                        return Direction.Northeast;
                    case "Southeast":
                        return Direction.East;
                    case "South":
                        return Direction.Southeast;
                    case "Southwest":
                        return Direction.South;
                    case "West":
                        return Direction.Southwest;
                    case "Northwest":
                        return Direction.West;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
    }
   The "Vec" in ApplyTransform is a Point that is special to Malison. By using it I can simply put
Vec a = new Vec(1,0);
Terminal[a].Write("Hello World");
and it will write at 1,0. Vec is nice because you can add it to another so I could do a = a + a; and it would print at 2,0. Points can not be added together so this is quite useful because I can just for movement put Player.Location += Direction.West.ApplyTransform(Player.Location); and it would move the location west. I can do this anytime I need to move something.
   Another useful thing with the Direction class is those things with the switches in them. Direction.Next will get the next clockwise direction so when a monster wants to get to the player if the way is blocked I can easily check other directions. There is also previous, inverse, next perpendicular, and previous perpendicular.
   Of course till I have more than a lame tech demo this is all just hot air but at the least I can fly high in my hot air balloon.