all repos — Legends-RPG @ 05d9e3d3d1f5fd253dc04a612313eac3b634def0

A fantasy mini-RPG built with Python and Pygame.

data/pytmx/utils.py (view raw)

  1from itertools import tee, islice, izip, product
  2from collections import defaultdict
  3
  4from pygame import Rect
  5
  6from .constants import *
  7
  8
  9def read_points(text):
 10    return [ tuple(map(lambda x: int(x), i.split(',')))
 11         for i in text.split() ]
 12
 13
 14def parse_properties(node):
 15    """
 16    parse a node and return a dict that represents a tiled "property"
 17    """
 18
 19    # the "properties" from tiled's tmx have an annoying quality that "name"
 20    # and "value" is included. here we mangle it to get that junk out.
 21
 22    d = {}
 23
 24    for child in node.findall('properties'):
 25        for subnode in child.findall('property'):
 26            d[subnode.get('name')] = subnode.get('value')
 27
 28    return d
 29
 30
 31def decode_gid(raw_gid):
 32    # gids are encoded with extra information
 33    # as of 0.7.0 it determines if the tile should be flipped when rendered
 34    # as of 0.8.0 bit 30 determines if GID is rotated
 35
 36    flags = 0
 37    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
 38    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
 39    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
 40    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)
 41
 42    return gid, flags
 43
 44
 45def handle_bool(text):
 46    # properly convert strings to a bool
 47    try:
 48        return bool(int(text))
 49    except:
 50        pass
 51
 52    try:
 53        text = str(text).lower()
 54        if text == "true":   return True
 55        if text == "yes":    return True
 56        if text == "false":  return False
 57        if text == "no":     return False
 58    except:
 59        pass
 60
 61    raise ValueError
 62
 63
 64# used to change the unicode string returned from xml to proper python
 65# variable types.
 66types = defaultdict(lambda: str)
 67types.update({
 68    "version": float,
 69    "orientation": str,
 70    "width": int,
 71    "height": int,
 72    "tilewidth": int,
 73    "tileheight": int,
 74    "firstgid": int,
 75    "source": str,
 76    "name": str,
 77    "spacing": int,
 78    "margin": int,
 79    "trans": str,
 80    "id": int,
 81    "opacity": float,
 82    "visible": handle_bool,
 83    "encoding": str,
 84    "compression": str,
 85    "gid": int,
 86    "type": str,
 87    "x": int,
 88    "y": int,
 89    "value": str,
 90})
 91
 92
 93def pairwise(iterable):
 94    # return a list as a sequence of pairs
 95    a, b = tee(iterable)
 96    next(b, None)
 97    return izip(a, b)
 98
 99
100def group(l, n):
101    # return a list as a sequence of n tuples
102    return izip(*(islice(l, i, None, n) for i in xrange(n)))
103
104
105def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
106    """
107    generate a set of non-overlapping rects that represents the distribution
108    of the specified gid.
109
110    useful for generating rects for use in collision detection
111    """
112
113    if isinstance(tileset, int):
114        try:
115            tileset = tmxmap.tilesets[tileset]
116        except IndexError:
117            msg = "Tileset #{0} not found in map {1}."
118            raise IndexError, msg.format(tileset, tmxmap)
119
120    elif isinstance(tileset, str):
121        try:
122            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
123        except IndexError:
124            msg = "Tileset \"{0}\" not found in map {1}."
125            raise ValueError, msg.format(tileset, tmxmap)
126
127    elif tileset:
128        msg = "Tileset must be either a int or string. got: {0}"
129        raise ValueError, msg.format(type(tileset))
130
131    gid = None
132    if real_gid:
133        try:
134            gid, flags = tmxmap.map_gid(real_gid)[0]
135        except IndexError:
136            msg = "GID #{0} not found"
137            raise ValueError, msg.format(real_gid)
138
139    if isinstance(layer, int):
140        layer_data = tmxmap.getLayerData(layer).data
141    elif isinstance(layer, str):
142        try:
143            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
144            layer_data = layer.data
145        except IndexError:
146            msg = "Layer \"{0}\" not found in map {1}."
147            raise ValueError, msg.format(layer, tmxmap)
148
149    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
150    if gid:
151        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
152    else:
153        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]
154
155    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
156    return rects
157
158
159def simplify(all_points, tilewidth, tileheight):
160    """
161    kludge:
162
163    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
164    a clumsy or inelegant, yet effective, solution to a problem, typically
165    using parts that are cobbled together."
166
167    -- wikipedia
168
169    turn a list of points into a rects
170    adjacent rects will be combined.
171
172    plain english:
173        the input list must be a list of tuples that represent
174        the areas to be combined into rects
175        the rects will be blended together over solid groups
176
177        so if data is something like:
178
179        0 1 1 1 0 0 0
180        0 1 1 0 0 0 0
181        0 0 0 0 0 4 0
182        0 0 0 0 0 4 0
183        0 0 0 0 0 0 0
184        0 0 1 1 1 1 1
185
186        you'll have the 4 rects that mask the area like this:
187
188        ..######......
189        ..####........
190        ..........##..
191        ..........##..
192        ..............
193        ....##########
194
195        pretty cool, right?
196
197    there may be cases where the number of rectangles is not as low as possible,
198    but I haven't found that it is excessively bad.  certainly much better than
199    making a list of rects, one for each tile on the map!
200
201    """
202
203    def pick_rect(points, rects):
204        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
205        x = ox
206        y = oy
207        ex = None
208
209        while 1:
210            x += 1
211            if not (x, y) in points:
212                if ex is None:
213                    ex = x - 1
214
215                if ((ox, y+1) in points):
216                    if x == ex + 1 :
217                        y += 1
218                        x = ox
219
220                    else:
221                        y -= 1
222                        break
223                else:
224                    if x <= ex: y-= 1
225                    break
226
227        c_rect = Rect(ox*tilewidth,oy*tileheight,\
228                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)
229
230        rects.append(c_rect)
231
232        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
233        kill = [ p for p in points if rect.collidepoint(p) ]
234        [ points.remove(i) for i in kill ]
235
236        if points:
237            pick_rect(points, rects)
238
239    rect_list = []
240    while all_points:
241        pick_rect(all_points, rect_list)
242
243    return rect_list
244