Browse Source

add pivot functions to geometry ; add Piece class

olinox 9 years ago
parent
commit
3f6ec73766
5 changed files with 260 additions and 3 deletions
  1. 1 0
      .travis.yml
  2. 6 1
      pypog/Grid.py
  3. 146 0
      pypog/Piece.py
  4. 58 2
      pypog/geometry.py
  5. 49 0
      tests/geometry/test_pivot.py

+ 1 - 0
.travis.yml

@@ -4,6 +4,7 @@ python:
   - "3.4"
   - "3.5"
 install:
+  - pip install cov-core
   - pip install nose2
   - pip install coveralls
 script: 

+ 6 - 1
pypog/Grid.py

@@ -8,7 +8,7 @@ from pypog import pathfinder
 
 
 class Grid(object):
-    def __init__(self, cell_shape, width, height):
+    def __init__(self, cell_shape, width, height, roof = None):
         self._cell_shape = None
         self.cell_shape = cell_shape
         
@@ -17,6 +17,8 @@ class Grid(object):
         self._height = 0
         self.height = height
         
+        self._roof = roof
+
         self._cells = {}
         self._build()
         
@@ -57,6 +59,9 @@ class Grid(object):
             raise ValueError("'width' has to be a strictly positive integer")
         self._height = height    
     
+    @property
+    def roof(self):
+        return self._roof
     
     def cell(self, x, y):
         return self._cells[(x, y)]

+ 146 - 0
pypog/Piece.py

@@ -0,0 +1,146 @@
+'''
+Created on 6 dec. 2016
+    Piece could represent any object on the grid: player, creature, pawn...etc
+@author: olinox
+'''
+from pypog import geometry
+
+
+class Piece(object):
+    def __init__(self):
+        super(Piece, self).__init__()
+        self._position = ()
+        self._rotation = 0
+        self._height = 1
+        self._zR = 0
+        self._cell_shape = None
+        self._shape = [ (0,0) ]
+        
+    # properties
+    @property
+    def position(self):
+        """return the (x, y) position of the Piece"""
+        return self._position
+        
+    @position.setter
+    def position(self, position):
+        """update the (x, y) position of the Piece
+        x, y have to be integers"""
+        try:
+            _, _ = position
+            if not all([isinstance(var, int) for var in position]):
+                raise ValueError("'position' have to be an (x, y) tuple, with x and y integers")
+        except ValueError:
+            raise ValueError("'position' have to be an (x, y) tuple, with x and y integers")
+        self._position = position
+    
+    @property
+    def x(self):
+        """x coordinate of the Piece"""
+        return self._position[0]
+
+    @property
+    def y(self):
+        """y coordinate of the Piece"""
+        return self._position[1]
+
+    @property
+    def height(self):
+        """height (in cells) of the Piece"""
+        return self._height
+
+    @height.setter
+    def height(self, height):
+        """set the height (in cells) of the Piece
+        'height' has to be a positive integer"""
+        if not isinstance(height, int):
+            raise TypeError("'height' has to be an integer")
+        if not height >= 0:
+            raise TypeError("'height' has to be positive")
+        self._height = height
+
+    @property
+    def zR(self):
+        """return the relative altitude of the Piece
+        the relative altitude is the difference of altitude between the bottom of the Piece
+        and the altitude of its position"""
+        return self._zR
+
+    @zR.setter
+    def zR(self, zR):
+        """set the relative altitude of the Piece"""
+        if not isinstance(zR, int):
+            raise TypeError("'zR' has to be an integer")
+        self._zR = zR
+
+    @property
+    def shape(self):
+        """return the shape of the Piece
+        shape is a list of relative coordinates, assuming (0, 0) is the position of the piece
+        eg: [(-1, -1), (-1, 0), (0, 0)]"""
+        return self._shape
+
+    @shape.setter
+    def shape(self, shape):
+        """set the shape of the piece (see shape property for more informations)"""
+        if not isinstance(shape, list):
+            raise TypeError("'shape' has to be a list")
+        for item in shape:
+            try:
+                _, _ = item
+                if not all([isinstance(var, int) for var in item]):
+                    raise ValueError("'shape' have to be a list of (x, y) tuples, with x and y integers")
+            except ValueError:
+                raise ValueError("'shape' have to be a list of (x, y) tuples, with x and y integers")
+        self._shape = shape
+
+    @property
+    def cell_shape(self):
+        """return the 'cell_shape' from GRID_GEOMETRIES for which the Piece is made for.
+        Cell's shape is needed for pivot algorithm, and for graphical purposes"""
+        return self._cell_shape
+    
+    @cell_shape.setter
+    def cell_shape(self, cell_shape):
+        """set the 'cell_shape' of the piece (see cell_shape property for more informations)"""
+        self._cell_shape = cell_shape
+    
+    @property
+    def rotation(self):
+        """return the current rotation of the piece.
+        rotation is an integer representing the number of pivot applied to the piece"""
+        return self._rotation
+
+    @rotation.setter
+    def rotation(self, rotation):
+        """set the 'rotation' of the piece (see rotation property for more informations)"""
+        if not isinstance(rotation, int):
+            raise TypeError("'rotation' has to be an integer")        
+        self._rotation = rotation       
+    
+    # methods
+    def occupation(self):
+        """return the list of the (x, y) coordinates currently occupied by the Piece"""
+        result = [ (xr + self.x, yr + self.y) for xr, yr in self._shape ]
+        if self._rotation != 0:
+            result = geometry.pivot( self._cell_shape, self.position, result, self.rotation )
+        return result
+
+    def occupation3d(self, dz = 0):
+        """return the list of the (x, y, z) coordinates currently occupied by the Piece
+        
+        Because Pieces altitude zR is relative, the z coordinate returned is not an absolute altitude
+        If you want an absolute altitude, use the 'dz' modifier to correct the result."""
+        occupation = self.occupation()
+        return [(x, y, z) for x, y in occupation for z in range(dz + self.zR, dz + self.zR + self.height)]
+
+    def move(self, x, y, zR = None):
+        """move the piece to (x, y) position and zR relative altitude"""
+        self.position = x, y
+        if zR != None:
+            self.zR = zR
+    
+    def rotate(self, i):
+        """pivot the Piece i times (counterclockwise rotation)"""
+        self.rotation += i
+        

+ 58 - 2
pypog/geometry.py

@@ -21,9 +21,9 @@ Created on 5 dec. 2016
 from math import sqrt
 
 
-GRID_GEOMETRIES = (4, 5)
+GRID_GEOMETRIES = (4, 6)
 SQUARE = 4
-HEX = 5
+HEX = 6
 ANGLES = (1, 2, 3)
 
 ## neigbours
@@ -507,6 +507,62 @@ def triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle):
         result[ (x, y) ] = ( (min(z_list) - dh) , (max(z_list) + dh) ) 
     return result
 
+## pivot
+
+def pivot(cell_shape, center, coordinates, rotations):
+    """pivot 'rotations' times the coordinates (list of (x, y) tuples) 
+    around the center coordinates (x,y)
+    Rotation is counterclockwise"""
+    if cell_shape == SQUARE:
+        return squ_pivot(center, coordinates, rotations)
+    elif cell_shape == HEX: 
+        return hex_pivot(center, coordinates, rotations)
+    else:
+        raise ValueError("'cell_shape' has to be a value from GRID_GEOMETRIES")
+
+
+def hex_pivot(center, coordinates, rotations):
+    """pivot 'rotations' times the coordinates (list of (x, y) tuples) 
+    around the center coordinates (x,y)
+    On hexagonal grid, rotates of 60 degrees each time"""
+    if coordinates == [center] or rotations%6 == 0:
+        return coordinates
+    
+    x0, y0 = center
+    xu0, yu0, zu0 = cv_off_cube(x0, y0)
+    result = []
+    
+    for x, y in coordinates:
+        xu, yu, zu = cv_off_cube(x, y)
+        dxu, dyu, dzu = xu - xu0, yu - yu0, zu - zu0
+        for _ in range(rotations):
+            dxu, dyu, dzu = -dzu, -dxu, -dyu
+        xru, yru, zru = dxu + xu0, dyu + yu0, dzu + zu0
+        xr, yr = cv_cube_off(xru, yru, zru)
+        result.append( (xr, yr) )
+        
+    return result
+
+def squ_pivot(center, coordinates, rotations):
+    """pivot 'rotations' times the coordinates (list of (x, y) tuples) 
+    around the center coordinates (x,y)
+    On square grid, rotates of 90 degrees each time"""
+    if coordinates == [center] or rotations%4 == 0:
+        return coordinates
+    
+    x0, y0 = center
+    result = []
+    
+    for x, y in coordinates:
+        
+        dx, dy = x - x0, y - y0
+        for _ in range(rotations):
+            dx, dy = dy, -dx
+            
+        xr, yr = dx + x0, dy + y0
+        result.append( (xr, yr) )
+        
+    return result
 
 ## cubic coordinates
 def cv_cube_off(xu, yu, zu):

+ 49 - 0
tests/geometry/test_pivot.py

@@ -0,0 +1,49 @@
+'''
+Created on 6 dec. 2016
+
+@author: olinox
+'''
+import unittest
+from pypog import geometry
+
+class Test(unittest.TestCase):
+
+    def test_hex_pivot(self):
+        """ pivot on hexagonal grid """
+        
+        
+        attended = [
+                    [(5, 5), (4, 5), (6, 6)], 
+                    [(5, 6), (4, 7), (6, 6)],
+                    [(6, 7), (6, 8), (6, 6)],
+                    [(7, 6), (8, 7), (6, 6)],
+                    [(7, 5), (8, 5), (6, 6)],
+                    [(6, 5), (6, 4), (6, 6)],
+                    [(5, 5), (4, 5), (6, 6)]
+                   ]
+        
+        
+        for i in range( len(attended) ):
+            self.assertCountEqual(geometry.hex_pivot( (6,6), [(6,6)], i), [(6,6)])
+            result = geometry.hex_pivot( (6,6), [(5,5), (4,5), (6,6)], i )
+            self.assertCountEqual(result, attended[i])
+            self.assertCountEqual(result, geometry.pivot(geometry.HEX, (6,6), [(5,5), (4,5), (6,6)], i))
+
+    def test_squ_line(self):
+        """ pivot on square grid """
+        attended = [
+                    [(6, 6), (6, 5), (5, 5), (5, 6)],
+                    [(6, 6), (5, 6), (5, 7), (6, 7)],
+                    [(6, 6), (6, 7), (7, 7), (7, 6)],
+                    [(6, 6), (7, 6), (7, 5), (6, 5)],
+                    [(6, 6), (6, 5), (5, 5), (5, 6)]
+                   ]
+
+        for i in range( len(attended) ):
+            self.assertCountEqual(geometry.hex_pivot( (6,6), [(6,6)], i), [(6,6)])
+            result = geometry.squ_pivot( (6,6), [(6,6), (6,5), (5,5), (5,6)], i )
+            self.assertCountEqual(result, attended[i])
+            self.assertCountEqual(result, geometry.pivot(geometry.SQUARE, (6,6), [(6,6), (6,5), (5,5), (5,6)], i))
+     
+if __name__ == "__main__":
+    unittest.main()