Lachlan's misadventures in games programming

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

Thursday, 14 August 2014

Python - Parsing XML to Object

8/14/2014 12:50:00 am Posted by Lachlan , 2 comments
My apologies for it having been a while.

In the course of programming a roguelike in Python, I wrote the following chunk of code which might be helpful to others. Given a filename of an XML file and a constructor, it parses the XML file and uses the constructor to fill the object with the contents of the XML file (so - a field called 'SPAM' in the XML file would automatically populate the variable SPAM in the object created by the constructor). If the variable is a boolean or integer, it'll copy the XML as such.
import xml.etree.ElementTree as ET

def parse(filename, objectConstructor):
    tree = ET.parse(filename)
    root = tree.getroot()
    items = []
    for i in root:
        item = objectConstructor()
        for j in i:
            t = item.__dict__[j.tag]
            v = j.text
            if isinstance(t, bool):
                v = v == 'true'
            elif isinstance(t, int):
                v = int(v)          
            item.__dict__[j.tag] = v
        items.append(item)
    return items
    

Friday, 22 February 2013

Range Calculation Code - New Version

2/22/2013 12:53:00 am Posted by Lachlan , No comments

My old range calculation code (found here) was, as I admitted, slow and... well... rubbish.

Completely rewritten. Almost no trace of the old code whatsoever, and I do it in a far, far, far simpler manner now.

Again - licensed under Lachlan's Very Public License, being a modified Unlicense. Do whatever you want with it, don't blame me if something goes wrong, and donations/attribution/a comment saying its useful would be really nice.


public List<coord> GetRange(coord start, int range, bool PlayerCollides = true)
        {
            
            List<coord> returnlist = new List<coord>();

            int[,] distances = new int[range * 2 + 3, range * 2 + 3];
            for (int ix = 0; ix < distances.GetLength(0); ix++)
            {
                for (int iy = 0; iy < distances.GetLength(1); iy++)
                {
                    // -1 is not yet visited
                    // -2 is a collision point
                    distances[ix, iy] = (PlayerCollides ?
                                        (CheckCollision(ix + start.X - range - 1, iy + start.Y - range - 1)) :
                                        (TileCollides(ix + start.X - range - 1, iy + start.Y - range - 1))) ?
                                        -2 : -1;
                }
            }

            const int STRAIGHT_COST = 10;
            const int DIAG_COST = 14;

            distances[range + 1 ,range + 1] = 0;            
            
            for (int i = 1; i <= range; i++)
            {
                for (int jx = -i; jx <= i; jx++) { for (int jy =  -i; jy <= i; jy++)
                {
                    if (distances[jx + range + 1, jy + range + 1] == -1)
                    {
                        int minDistance = int.MaxValue;
                        for (int lx = -1; lx < 2; lx++)
                        {
                            for (int ly = -1; ly < 2; ly++)
                            {
                                if (!(lx == 0 && ly == 0))
                                {
                                    int distance = distances[jx + range + lx + 1, jy + range + ly + 1];
                                    if (distance >= 0)
                                    {
                                        minDistance = Math.Min(minDistance, distance +
                                            (((lx == 0) || (ly == 0)) ? STRAIGHT_COST : DIAG_COST));
                                    }
                                }
                            }
                        }
                        if (minDistance != int.MaxValue)
                        {
                            distances[jx + range + 1, jy + range + 1] = minDistance;
                            if (minDistance <= (range * STRAIGHT_COST))
                            {
                                returnlist.Add(new coord(jx + start.X, jy + start.Y));
                            }
                        }
                    }
                }}
            }

            return returnlist;
        }

Sunday, 17 February 2013

Not Quite A* Movement Range Calculation

2/17/2013 03:02:00 pm Posted by Lachlan , , , , , 1 comment
Update - 22/02/13 - 
Turns out this code really really did suck. Don't use it. I've completely rewritten it, actually using a little thought rather then misappropriating a poorly chosen algorithm and trying to shove it in.

The new one is on here


I looked at A*, then made something similar to calculate ranges of movement. This code is so you can determine the locations that a character can move to, knowing where collisions are and the like. You will have to provide functions CheckCollision and TileCollides, and probably change a couple of other things to make it work for you. Comments are pretty much non existent, and the code works  but isn't optimised or very fast.

Again, I figured since I'd done it, that this code could be useful. It's released under the following license:


Lachlan's Very Public Licence - which is a slightly modified Unlicense
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.  
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 
If you use the source, the Author (Lachlan Kingsford) would actually like to know - but it's by no means necessary. Feel welcome to provide a copy of what you do via email, or comment on www.nerdygentleman.com. Attribution is also nice and appreciated, but again, by no means necessary. Donations via paypal are also nice. 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


    public class coord
    {
        public int X;
        public int Y;
        public coord() { X = 0; Y = 0;  }
        public coord(int _X, int _Y) { X = _X; Y = _Y; }
        public static bool operator==(coord first, coord second)
        {
            return ((first.X == second.X) && (first.Y == second.Y));
        }
        public static bool operator !=(coord first, coord second)
        {
            return !((first.X == second.X) && (first.Y == second.Y));
        }
        public override bool Equals(object O)
        {
            return (coord)this == (coord)O;
        }
        public override string ToString()
        {
            return "(" + X.ToString() + ", " + Y.ToString() + ")";
        }
    }

    public class workingRangeVertex// : IComparable<workingRangeVertex>
    {
        public int G;
        public coord V;
        public workingRangeVertex parent;
        public workingRangeVertex(coord _V, workingRangeVertex _Parent, int _G)
        { 
            V = _V;
            parent = _Parent;
            G = _G;
        }
        public override string ToString()
        {
            return V.ToString();
        }
    }

 public List<coord> GetRange(coord start, int range, bool PlayerCollides = true)
        {
            List<workingRangeVertex> openList = new List<workingRangeVertex>();
            List<workingRangeVertex> closedList = new List<workingRangeVertex>();

            workingRangeVertex startV = new workingRangeVertex(start, null, 0);

            openList.Add(startV);

            const int STRAIGHT_COST = 10;
            const int DIAG_COST = 14;            

            while (openList.Count() != 0)
            {
                workingRangeVertex V = openList[0];
                {
                    openList.Remove(V);
                    closedList.Add(V);
                    for (int ix = -1; ix < 2; ix++)
                    {
                        for (int iy = -1; iy < 2; iy++)
                        {
                            if (!(ix == 0 && iy == 0))
                            {    
                                coord i = new coord(V.V.X + ix, V.V.Y + iy);
                                workingRangeVertex closePath = closedList.Find((f) => { return ((f.V.X == i.X) && (f.V.Y == i.Y)); });
                                if (closePath == null)
                                {
                                    if (PlayerCollides ? (!CheckCollision(i.X, i.Y)) : (!TileCollides(i.X, i.Y)))
                                    {
                                        int cost = V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST);
                                        if (cost <= range)
                                        {
                                            workingRangeVertex openPath = openList.Find((f) => { return ((f.V.X == i.X) && (f.V.Y == i.Y)); });
                                            if (openPath == null)
                                            {
                                                openList.Add(new workingRangeVertex(i, V, cost));
                                            }
                                            else
                                            {
                                                openPath.G = Math.Min((V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST)), openPath.G);
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    closePath.G = Math.Min((V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST)), closePath.G);
                                }
                            }
                        }
                    }
                }
            }

            List<coord> returnlist = new List<coord>();
            foreach (workingRangeVertex V in closedList)
            {
                returnlist.Add(V.V);
                Console.WriteLine(V.V.X.ToString() + ", " + V.V.Y.ToString());
            }
            return returnlist;
        }

Wednesday, 13 February 2013

A* Pathfinding

2/13/2013 08:14:00 pm Posted by Lachlan , , , No comments

Work is still progressing (between study and work-proper) on Roguelikish.

I've added A* pathfinding. As a result, you actually do some path finding to move when you click somewhere, and your party isn't quite as stupid when trying to follow.

I figured since I'd done it, that the pathfinding code could be useful. It's released under the following license:
Lachlan's Very Public Licence - which is a slightly modified Unlicense


This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
If you use the source, the Author (Lachlan Kingsford) would actually like to know - but it's by no means necessary. Feel welcome to provide a copy of what you do via email, or comment on www.nerdygentleman.com. Attribution is also nice and appreciated, but again, by no means necessary.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

I've attempted to implement the A* algorithm as described on A* Pathfinding for Beginners. There's probably bugs. Diagonals are costly (they cost 1.4 x the movement of verticals).  The comments are probably old, inaccurate, or just plain wrong and probably shouldn't be relied on. The programming style is amateur, but it works.

The function provides a list<coord> of the path to take to reach coord end from coord start.

Any questions - I may or may not be able to answer. I implemented the algorithm, but only mostly understood it.


//Required classes
    public class coord
    {
        public int X;
        public int Y;
        public coord() { X = 0; Y = 0;  }
        public coord(int _X, int _Y) { X = _X; Y = _Y; }
        public static bool operator==(coord first, coord second)
        {
            return ((first.X == second.X) && (first.Y == second.Y));
        }
        public static bool operator !=(coord first, coord second)
        {
            return !((first.X == second.X) && (first.Y == second.Y));
        }
    }

    public class workingPathVertex : IComparable<workingPathVertex>
    {
        public coord V;
        public int G;
        public int H;
        public int F() { return G + H; }
        public workingPathVertex parent ;
        public workingPathVertex(coord _V, workingPathVertex _Parent, int _G, int _H)
        { 
            V = _V;
            parent = _Parent;
            G = _G;
            H = _H;
        }
        public int CompareTo(workingPathVertex that)
        {
            return (that.F() > F()) ? -1 : 1;
        }
    }

//Functions
//Relies on two 2d boolean arrays - which can fairly easily be written out.
//One is CheckCollision(int, int) which returns whether the tile can be collided 
//  with - and whether the thing on the tile can be collided with. So - players 
//  would make it return to
//The other is TileCollides (int,int) which returns whether the tile can be
//  collided with. Behaviour is controlled from "playercollides" arg
//Some debugging codes still in there, and the comments are old, barely updated
//  and possibly flatly wrong.

public List<coord> GetPath(coord start, coord end, bool PlayerCollides = true)
        {
            List<workingPathVertex> openList = new List<workingPathVertex>();
            List<workingPathVertex> closedList = new List<workingPathVertex>();

            workingPathVertex startV = new workingPathVertex(start, null, 0, Manhatten(start,end)) ;                  
            workingPathVertex V = startV;

            const int STRAIGHT_COST = 10;
            const int DIAG_COST = 14;

            for (int ix = -1; ix < 2; ix++)
            {
                for (int iy = -1; iy < 2; iy++)
                {
                    if (!(ix == 0 && iy == 0))
                    {
                        coord i = new coord(V.V.X + ix, V.V.Y + iy);
                        if (PlayerCollides ? (!CheckCollision(i.X,i.Y)) : (!TileCollides(i.X, i.Y)))
                        {
                            openList.Add(new workingPathVertex(i, V, V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST), Manhatten(i, end)));
                        }
                    }
                }
            }

            openList.Remove(V);
            closedList.Add(V);

            bool finished = false;
            bool succeeded = false;
            workingPathVertex endVertex = null;

            bool surroundtarget = false;

            if (PlayerCollides ? (CheckCollision(end.X,end.Y)) : (TileCollides(end.X, end.Y))) {                 
                surroundtarget = true;
                bool surroundPossible = false;
                for (int ix = -1; ix < 2; ix++)
                {
                    for (int iy = -1; iy < 2; iy++)
                    {
                        if (!(ix == 0 && iy == 0))
                        {
                            coord i = new coord(V.V.X + ix, V.V.Y + iy);
                            surroundPossible = (PlayerCollides ? (!CheckCollision(i.X, i.Y)) : (!TileCollides(i.X, i.Y))) || surroundPossible;
                        }
                    }
                }
                finished = !surroundPossible;
            };

            //This is lazy looping I know, but it's late, and I'm tired, and it should work
            while (!finished)
            {


                V = openList.Min();
                openList.Remove(V);
                closedList.Add(V);

                ////Debuggy stuff
                //Console.WriteLine("ITERATION:\n V moved is " + V.V.X.ToString() + "  " + V.V.Y.ToString());
                //Console.WriteLine("OPEN LIST:");
                //foreach (workingPathVertex Z in openList)
                //{
                //    Console.WriteLine(Z.V.X.ToString() + "\t" + Z.V.Y.ToString() + ") " + Z.F() + "\t" + Z.G + "\t" + Z.H);
                //}

                //foreach (workingPathVertex Z in closedList)
                //{
                //    Console.WriteLine(Z.V.X.ToString() + "\t" + Z.V.Y.ToString() + ") " + Z.F() + "\t" + Z.G + "\t" + Z.H);
                //}


                if (!surroundtarget)
                {
                    if ((V.V.X == end.X) && (V.V.Y) == end.Y) { finished = true; succeeded = true; endVertex = V; };
                }
                else
                {
                    if ((Math.Abs(V.V.X - end.X) <= 1) && ((Math.Abs(V.V.Y - end.Y) <= 1))) { finished = true; succeeded = true; endVertex = V; };
                }


                //Find lowest F
                for (int ix = -1; ix < 2; ix++)
                {
                    for (int iy = -1; iy < 2; iy++)
                    {
                        if (!(ix == 0 && iy == 0))
                        {                         
                            coord i = new coord(V.V.X + ix, V.V.Y + iy);
                            workingPathVertex closePath = closedList.Find((f) => { return ((f.V.X == i.X) && (f.V.Y == i.Y)); });
                            if ((closePath == null) && (PlayerCollides ? (!CheckCollision(i.X, i.Y)) : (!TileCollides(i.X, i.Y))))
                            {
                                workingPathVertex iPath = openList.Find((f) => { return ((f.V.X == i.X) && (f.V.Y == i.Y)); });                                
                                if (iPath != null)
                                {
                                    //If iPath is already on openList                                
                                    int GviaV = V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST);
                                    if (GviaV < iPath.G) { iPath.G = GviaV; iPath.parent = V; }
                                }
                                else
                                {
                                    //If iPath is not on the openlist
                                    iPath = new workingPathVertex(i, V, V.G + (((ix == 0) || (iy == 0)) ? STRAIGHT_COST : DIAG_COST), Manhatten(i, end));
                                    openList.Add(iPath);
                                }
                            }
                        }
                    }
                }
                if (openList.Count <= 0) { finished = true; }
            }

            if (succeeded)
            {
                //Console.WriteLine("SUCCESS: ");
                List<coord> path = new List<coord>();

                workingPathVertex nextVertex = endVertex;
                while (nextVertex != null)
                {
                    //Console.WriteLine(nextVertex.V.X.ToString() + " " + nextVertex.V.Y.ToString());
                    path.Add(nextVertex.V);
                    nextVertex = nextVertex.parent;                    
                }
                path.Reverse();
                path.RemoveAt(0);
                return path;
            }
            else
            {
                //Console.WriteLine("FAILED");
                return null;
            }

            

        }

        //Calculates manhatten distance
        int Manhatten(coord A, coord B)
        {
            return (Math.Abs(A.X - B.X) + Math.Abs(A.Y + B.Y));
        }
    }
Edit (12.29AM 13/02/2013 AEDST) Fixed Bug

Tuesday, 14 February 2012

Form Code, and Squishing

2/14/2012 12:52:00 am Posted by Lachlan , No comments

I got squishing working! The White box (which will soon be replaced with actual sprites) can now detect being squished vertically or horizontally, and call a script.

In other news, I've started implementing actual graphics for the levels. How I'm doing it, is each feature will have a string identifying its style. The styles are stored in structs like the following:

struct FeatureStyle
{
SDL_Surface* surface_;
FeatureStyleMode mode_;
SDL_Rect middle_;
SDL_Rect left_edge_;
SDL_Rect top_left_corner_;
SDL_Rect top_edge_;
SDL_Rect top_right_corner_;
SDL_Rect right_edge_;
SDL_Rect bottom_right_corner_;
SDL_Rect bottom_edge_;
SDL_Rect bottom_left_corner_;
};

The mode_  simply defined if its just one, tiles, or tiled with borders. The SDL_Rects define where each part of the image is. The problem is, that this has just led to the biggest piece of copy-paste form-code in my recent history. I've been discussing over at StackOverflow whether there is a better way . There is a good chance I'll implement Konrad Rudolf's excellent suggestion. You can find the discussion on http://stackoverflow.com/questions/9260911/avoiding-copy-paste-code-initialising-a-series-of-sdl-rects-in-a-struct/9261170#9261170 if you feel like contribution. It follows, unadulterated.


case M_AddFeatureStyle:
{
std::string descriptor = TrimQuotes(args[0]);
std::string file = TrimQuotes(args[1]);


FeatureStyleMode mode = (FeatureStyleMode)boost::lexical_cast<int>(args[2]);


SDL_Rect middle = RectZeroes;
if (args.size() >= 6 ) 
{
middle.x = boost::lexical_cast<int>(args[3]); 
middle.y = boost::lexical_cast<int>(args[4]);
middle.w = boost::lexical_cast<int>(args[5]);
middle.h = boost::lexical_cast<int>(args[6]);
}


SDL_Rect left_edge = RectZeroes;
if (args.size() >= 10 ) 
{
left_edge.x = boost::lexical_cast<int>(args[7]); 
left_edge.y = boost::lexical_cast<int>(args[8]);
left_edge.w = boost::lexical_cast<int>(args[9]);
left_edge.h = boost::lexical_cast<int>(args[10]);
}


SDL_Rect top_left_corner = RectZeroes;
if (args.size() >= 14 ) 
{
top_left_corner.x = boost::lexical_cast<int>(args[11]); 
top_left_corner.y = boost::lexical_cast<int>(args[12]);
top_left_corner.w = boost::lexical_cast<int>(args[13]);
top_left_corner.h = boost::lexical_cast<int>(args[14]);
}


SDL_Rect top_edge = RectZeroes;
if (args.size() >= 18 ) 
{
top_edge.x = boost::lexical_cast<int>(args[15]); 
top_edge.y = boost::lexical_cast<int>(args[16]);
top_edge.w = boost::lexical_cast<int>(args[17]);
top_edge.h = boost::lexical_cast<int>(args[18]);
}


SDL_Rect top_right_corner = RectZeroes;
if (args.size() >= 22 ) 
{
top_right_corner.x = boost::lexical_cast<int>(args[19]); 
top_right_corner.y = boost::lexical_cast<int>(args[20]);
top_right_corner.w = boost::lexical_cast<int>(args[21]);
top_right_corner.h = boost::lexical_cast<int>(args[22]);
}


SDL_Rect right_edge = RectZeroes;
if (args.size() >= 26 ) 
{
right_edge.x = boost::lexical_cast<int>(args[23]); 
right_edge.y = boost::lexical_cast<int>(args[24]);
right_edge.w = boost::lexical_cast<int>(args[25]);
right_edge.h = boost::lexical_cast<int>(args[26]);
}


SDL_Rect bottom_right_corner = RectZeroes;
if (args.size() >= 30 ) 
{
bottom_right_corner.x = boost::lexical_cast<int>(args[27]); 
bottom_right_corner.y = boost::lexical_cast<int>(args[28]);
bottom_right_corner.w = boost::lexical_cast<int>(args[29]);
bottom_right_corner.h = boost::lexical_cast<int>(args[30]);
}


SDL_Rect bottom_edge = RectZeroes;
if (args.size() >= 34 ) 
{
bottom_edge.x = boost::lexical_cast<int>(args[31]); 
bottom_edge.y = boost::lexical_cast<int>(args[32]);
bottom_edge.w = boost::lexical_cast<int>(args[33]);
bottom_edge.h = boost::lexical_cast<int>(args[34]);
}


SDL_Rect bottom_left_corner = RectZeroes;
if (args.size() >= 38 ) 
{
bottom_left_corner.x = boost::lexical_cast<int>(args[35]); 
bottom_left_corner.y = boost::lexical_cast<int>(args[36]);
bottom_left_corner.w = boost::lexical_cast<int>(args[37]);
bottom_left_corner.h = boost::lexical_cast<int>(args[38]);
}


t->AddFeatureStyle(
descriptor, 
file, 
mode, 
middle, 
left_edge, 
top_left_corner, 
top_edge, 
top_right_corner, 
right_edge, 
bottom_right_corner, 
bottom_edge, 
bottom_left_corner);

}
break;