Pythonic way to handle coordinates

  • Thread starter Pierre-Alain Dorange
  • Start date
P

Pierre-Alain Dorange

Hi,
I'm used python for 3 months now to develop small arcade games (with
pygame module).

I just got a question about coordinates handling.
My games are in 2D so i deal with x,y coordinates for sprites (but woudl
be the same u-issue in 3D).

At the beginning i simply use x and y to pass to my methods
ie : sprite.move(x,y)
easy

after looking closely to pygame code, i noticed that pygame implement a
Rect class but no Point class, and instead require tuple for
coordinates.
ie : pygame.draw.line(surface,color,start,end)
where start and end are tuple, like (x,y).

I found this nice and rewrote my API to use tuples instead of 2
variables...
so i got could do :
loc=(x,y)
sprite.move(loc) or sprite.move((x,y))
...
loc=sprite.get_pos()

Nice, but i got a problem when my methodes return tuple, as it's
unmutable i could not modify them and i got to used tempory variables to
do that, not very clean :
loc=sprite.get_pos()
loc[0]+=10 < ERROR
...
x=loc[0]+10
loc=(x,loc[1])
i could also do :
(x,y)=sprite.get_pos()
x+=10
sprite.set_pos((x,y))
The second method could not be used when i'm instead one of my method, i
need to used the first one.

What is the elegant way to handle coordinates ?
Do i need to continue using tuples or do i need to write a Point class.
I feel a point class would be nice, because i could implement operators
with it ? But i think Point class must exist allready ?
 
T

The Music Guy

Hi,
I'm used python for 3 months now to develop small arcade games (with
pygame module).

I just got a question about coordinates handling.
My games are in 2D so i deal with x,y coordinates for sprites (but woudl
be the same u-issue in 3D).

At the beginning i simply use x and y to pass to my methods
ie : sprite.move(x,y)
easy

after looking closely to pygame code, i noticed that pygame implement a
Rect class but no Point class, and instead require tuple for
coordinates.
ie : pygame.draw.line(surface,color,start,end)
where start and end are tuple, like (x,y).

I found this nice and rewrote my API to use tuples instead of 2
variables...
so i got could do :
        loc=(x,y)
        sprite.move(loc) or sprite.move((x,y))
        ...
        loc=sprite.get_pos()

Nice, but i got a problem when my methodes return tuple, as it's
unmutable i could not modify them and i got to used tempory variables to
do that, not very clean :
        loc=sprite.get_pos()
        loc[0]+=10  < ERROR
        ...
        x=loc[0]+10
        loc=(x,loc[1])
i could also do :
        (x,y)=sprite.get_pos()
        x+=10
        sprite.set_pos((x,y))
The second method could not be used when i'm instead one of my method, i
need to used the first one.

What is the elegant way to handle coordinates ?
Do i need to continue using tuples or do i need to write a Point class.
I feel a point class would be nice, because i could implement operators
with it ? But i think Point class must exist allready ?

--
Pierre-Alain Dorange

Ce message est sous licence Creative Commons "by-nc-sa-2.0"
        <http://creativecommons.org/licenses/by-nc-sa/2.0/fr/>

Hi there,

First of all, Pygame will accept any sequence of two integers (or even
floats) and not just tuples, so you don't need to implement all your
coordinates as tuples, and thus you can use lists, which are mutable.

Second of all, for this situation I do the same as the Rect object and
implement .x and .y properties for objects that need individual access
of those coordinates. Example:
# WARNING: This code was written on-the-fly and has not been tested...
class MySprite(object):
def __init__(self, pos):
# This is a list, not a tuple. Pygame won't mind.
# Also, note that the type of the pos is implicitly
# type checked.
self._pos = map(int, pos[:2])

def set_x(self, x):
self._pos[0] = int(x)
x = property(lambda self: self._pos[0], set_x)

def set_y(self, y):
self._pos[1] = int(y)
y = property(lambda self: self._pos[1], set_y)

def set_pos(self, pos):
self._pos = map(int, pos[:2])
# Note that the return value is converted to tuple to prevent
# constructs like `mysprite.pos[0] = "foo"`, which would cause an
# invalid type of value to be assigned for the x coordinates.
pos = property(lambda self: tuple(self._pos), set_pos)

spr = MySprite((3,4))
spr.x 3
spr.y 4
spr.x = 8
spr.pos
(8,4)

My SDK implements a lot of things like this. Check it out if you're
looking for more examples: http://code.google.com/p/scrollback

-TMG
 
J

James Stroud

Pierre-Alain Dorange said:
What is the elegant way to handle coordinates ?
Do i need to continue using tuples or do i need to write a Point class.
I feel a point class would be nice, because i could implement operators
with it ? But i think Point class must exist allready ?

My instinctive advice is to use a point class. My wise advice is to put
it in a library you will maintain and reuse--or else you will find
yourself re-writing point classes all of the time.

A Point class will also give you a place to consolidate all of those
methods specific to manipulating points. Thanks to python magic methods,
you can also design some nifty shortcuts to access the attributes of
your point.

Here is a fun example for no other reason than I'm too tired to do
anything important right now:


class Point(object):
def __init__(self, axes):
self.axes_names = axes.split()
self.axes = dict(enumerate(self.axes_names))
for axis in self.axes_names:
setattr(self, axis, 0.0)
def check_axis(self, i):
if i not in self.axes:
raise ValueError, "No such axis %s" % i
def __setitem__(self, i, v):
self.check_axis(i)
setattr(self, self.axes, v)
def __getitem__(self, i):
self.check_axis(i)
return getattr(self, self.axes)
def __iter__(self):
return (getattr(self, i) for i in self.axes_names)
def move(self, other):
for i, v in enumerate(other):
self += v
def magnitude(self):
import math
return math.sqrt(sum(v**2 for v in self))


E.g.:


py> p = Point('x y z')
py> list(p)
[0.0, 0.0, 0.0]
py> p.move([2, 3, 4])
py> p.x, p.y, p.z
(2, 3, 4)
py> p[0], p[1], p[2]
(2, 3, 4)
py> list(p)
[2, 3, 4]
py> tuple(p)
(2, 3, 4)
py> p.magnitude()
5.3851648071345037
py> q = Point('x y z')
py> q.move([1, 2, 3])
py> p.move(q)
py> list(p)
[3.0, 5.0, 7.0]
py> p[1] = 15.0
py> list(p)
[3.0, 15.0, 7.0]


With the introduction of the second point, q, one can begin to see the
necessity for a class factory that produces Point classes of the desired
dimensionalities in desired coordinate systems:


def point(axes):
class P(Point):
def __init__(self, values):
Point.__init__(self, axes)
self.move(values)
return P


Note that we have have not used metaclasses--which would require messier
code but produce classes that would potentially behave a better as super
classes.

The point function is used as simply as it is written:

py> Point3D = point('x y z')
py> p = Point3D([0.0, 0.0, 0.0])
py> list(p)
[0.0, 0.0, 0.0]
py> q = Point3D([5.0, 6.0, 7.0])
py> list(q)
[5.0, 6.0, 7.0]
py> p.move(q)
py> list(p)
[5.0, 6.0, 7.0]


Incidentally, the classes returned by point are heritable, etc.


class Point3D(point('x y z')):
def __repr__(self):
return "Point3D(%s)" % list(self)
def __add__(self, other):
return Point3D(sum(t) for t in zip(self, other))


py> p = Point3D([8, 7, 6])
py> p
Point3D([8.0, 7.0, 6.0])
py> str(p)
'Point3D([8.0, 7.0, 6.0])'
py> q = Point3D([5, 4, 3])
py> r = p + q
py> r
Point3D([13.0, 11.0, 9.0])


Now have fun with this stuff. I need to go to sleep.

James


--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com
 
A

andrew cooke

although james's idea is quite neat (especially the idea of heritable
classes), my initial reaction was that it's too over-engineered. so
here's a different approach.

there are two "tricks" that can help make using tuples easier:

(1) unpacking the return value.
(x, y) = returns_a_point()
x += 1

(2) passing a tuple to a function that takes an x and a y value:
def takes_two_values(x, y):
...
p = (1, 2)
takes_two_values(*p)

and what I have done in the past is exploit these, so that functions/
methods that only takes a point takes two values while those that take
many points, other arguments, etc, take a tuple. this sounds messy
but works quite well because you tend to use the first kind of
functions when you are changing coordinates (and then it is useful to
treat a point as separate x and y values) and the second kind of
functions when you are handling collections of points that don't need
modifying.

andrew
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top