Showing posts with label Other Blogs. Show all posts
Showing posts with label Other Blogs. Show all posts

Friday, October 28, 2011

I found something else to sink my time into

   So I have once again been trapped in another blogs past. I found The CRPG Addict just yesterday the day before yesterday. I think it might have been from a link by another blog I follow but I don't really recall much as its where all my time has recently gone to.
   What the blog is about is a guy who is playing all CRPG's (as defined  by a few lists and the commentators on his blog) released for Dos or Windows in chronological order of year released. He posts about what game he is currently playing and when he finishes it he posts what he thinks of it including rating it by his own scoring system. This is basically the kind of stuff I could read for days (and have read for days) as it goes through all the stuff I wish I could put time aside for. Ironically for me despite my avid love of games I can't seem to ever really budget that many into my day so being able to read what they are like and seeing someones view of them is quite fun for me. Also the comments his posts get are half the fun so if you pop on over there I definitely advise you read them as well as the posts themselves. A thing to note as well is that he is playing roguelikes and because of how the tend to keep evolving he is playing them multiple times by major releases and has already played the first generation of Nethack along with an number of other more self contained roguelikes. Most impressive is that he (just before starting the blog sadly) beat Rogue.
   I am currently up to where he starts on Pool of Radiance which is a game I have fallen into quite a number of times. Speaking of falling into a game I will probably end up playing a few of them (like say Pool of Radiance) so I will either disappear for a bit or start posting about said games. He seems to have needed to take October off because of his job which seeing as the month is almost over I guess I found it at a good time.
   So yeah, good read if you like computer roleplaying games, especially the older ones, or if you happen to want to learn about the older games. Now I am back off to the archives to toil away till some ungodly hour when Sleep finally manages to sneak up behind me and apply some lumber love to the back of my head.

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, May 6, 2011

GROGNARDIA: Open Friday: Gamebook Art

GROGNARDIA: Open Friday: Gamebook Art: "One of the contemporary trends of the hobby I must confess to not liking a great deal is the rather 'art heavy' nature of a lot of gamebooks..."

   To which I commented "Art is like using spices in cooking. Sprinkling it in improves the flavor but using to much overpowers the actual dish. Also like spices, good art tends to be expensive so using a lot can really jack up the price." but with some thought I realized I wanted to expand on it and include more cooking references for some reason.
   I want to start by say that art is not just to gamebooks like spices are to cooking. It is the same to gamebooks as spices are to cooking. Some things can be taken from this. First is that a gamebook does not Need art to be good and in fact some of the better rule systems are just as good with or without just like a really good steak.
   Also as I had mentioned using to much of it overpowers the actual dish. Its basically the joke "would you like some [main dish] with your [condiment]" and while this may just be over enthusiastic use of seasoning it could be malicious. A rich sauce with a shellfish dish may be used to hide the fact that the shellfish are no longer wholesome and just like that a lot of high quality art could easily hide a broken rule system long enough for you to purchase it.
   Another thing to look at is price. Sure for both spices and art the price for a decent quality has gone down some but the really good stuff is still expensive. I don't know about others but I would myself prefer a sprinkle of the really good as compared to a flood of average. Sure you might only be able to afford a few high quality pictures and a great cover for you book but this will improve the content without hiding it while if you spend the money on a lot of average art you will only be bringing the overall quality down. Remember a little good accents your work while a lot of average pulls your work towards that average.
   Art has its place in gamebooks and should not be left out unless you really can't afford it but its better to leave out if you can't afford art at least as good as your work.

Tuesday, April 5, 2011

D is for Death

   So far I have been staying to the D&D stuff for my letters so far but Death is just to good a topic to not talk about for from a roguelike prospective. Dieing is a constant threat in any good roguelike and not just that namby pamby "oh you died do you want to continue" stuff. Its full-on hardcore DEAD. There is no non-cheating way to continue with a character after it dies. But this is quite alright. In fact in my favorite computer game which gets stuck into the roguelike category has the motto "Losing is FUN". The game is Dwarf Fortress and not only does it have no win condition besides what you set yourself it can have a very persistent world. This means when you play the fortress mode and lose the next time if you play in the same world the ruins of your fortress will still be there. This was the first game I have ever seen that not only randomly generated a world for you to play in that could have a couple thousand years of history generated for it but that let you actually mess around with it in such a critical way. The only game that I have read about that does this much or more is not really so much a "game" as it is Rob Conley's Majestic Wilderlands setting where he has 30 years of real life gaming put into it and where for his campaigns his player's end game goals is setting themselves up in the world. He has had them create their own kingdoms in the setting.


   Of course there is more to roguelike death then persistent worlds or else Dwarf Fortress would be the only one really. To talk more in depth about death for them I will use Dungeon Crawl Stone Soup. There are many other good roguelikes to talk about but this one embodies it very well and I know quite a bit about it. In Crawl you will die. You will die sometimes even if you do everything 100% correct sometimes. Unlike most games from the game industry you will have to run from monsters. If you are fighting a hard one and are low on health you should drink that healing potion. Saving something like that for later means nothing when you die and there is no later.  Death is a constant companion even when you max your level. A lot of people like to avoid the last level of the dungeon even when they get as strong as they can be. They only go down there to grab the Orb so they can get out and win and even then they generally prefer to use apportation, a spell that grabs the Orb from a distance so they don't actually have to fight the monsters down there.


   Now some people might think constantly dieing and having to restart would be boring and not at all fun but they are wrong. Because of the dungeon being randomly generated every game besides some predefined levels it is all different and starting over with a new character is a strong point of the game. Later levels are good and all but there is a sweet spot after the very first part. Its when you are not going to die to easily but are still constantly in danger. This is one of the funnest points in the game for me and lets people play Crawl over and over many times past where other games of other types become quite boring, even if they to have random layouts for their play area.

   To wrap up this topic I want to clarify one thing. When talking about roguelikes above I mean non-commercial ones. There are a number of Japanese ones that don't have permadeath and the Pokemon mystery dungeon follow this. Diablo is another commercial roguelike with probably the biggest difference being that its a real-time game. The Japanese games have the setup they do because the creator of the original one which was called Torneko no Daibōken: Fushigi no Dungeon, played up the parts he thought where important to a roguelike game. Of course the ones that followed where that way because the first ones did it that way and where popular. Nothing against them because they are quite fun but I like my roguelikes with permadeath.

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.