all repos — Legends-RPG @ 5dc45a6a3236bb0f37cda02f65aecb54b0e7212e

A fantasy mini-RPG built with Python and Pygame.

pytmx/utils.py (view raw)

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