浏览代码

various corrections to geometry ; increase test coverage

olinox 9 年之前
父节点
当前提交
6a1ba38440
共有 7 个文件被更改,包括 353 次插入216 次删除
  1. 1 1
      .travis.yml
  2. 115 132
      pypog/geometry.py
  3. 70 73
      tests/geometry/test_line.py
  4. 18 8
      tests/geometry/test_pivot.py
  5. 42 0
      tests/geometry/test_rect.py
  6. 105 2
      tests/geometry/test_triangle.py
  7. 2 0
      tests/gridviewer/GridViewer.py

+ 1 - 1
.travis.yml

@@ -8,6 +8,6 @@ install:
   - pip install nose2
   - pip install coveralls
 script: 
-  - nose2 --with-coverage
+  - nose2 --with-coverage --coverage pypog
 after_success:
   coveralls

+ 115 - 132
pypog/geometry.py

@@ -33,7 +33,7 @@ def neighbours_of(cell_shape, x, y):
     elif cell_shape == HEX: 
         return hex_neighbours_of(x, y)
     else:
-        raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
+        raise ValueError("'cell_shape' has to be a value from GRID_GEOMETRIES")
 
 def hex_neighbours_of(x, y):
     """ returns the list of coords of the neighbours of a cell on an hexagonal grid"""
@@ -76,19 +76,17 @@ def line2d(cell_shape, x1, y1, x2, y2):
     grid could be one of the GRIDTYPES values"""
     if not all(isinstance(c, int) for c in [x1, y1, x2, y2]):
         raise TypeError("x1, y1, x2, y2 have to be integers")
-    if (x1, y1) == (x2, y2):
-        return [(x1, y1)]
     if cell_shape == HEX:
-        return hex_2d_line(x1, y1, x2, y2)
+        return _brH(x1, y1, x2, y2)
     elif cell_shape == SQUARE: 
-        return squ_2d_line(x1, y1, x2, y2)
+        return _brC(x1, y1, x2, y2)
     else:
-        raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
+        raise ValueError("'cell_shape' has to be a value from GRID_GEOMETRIES")
 
 def line3d(cell_shape, x1, y1, z1, x2, y2, z2):
     """returns a line from x1,y1,z1 to x2,y2,z2
     grid could be one of the GRIDTYPES values"""
-    if not all(isinstance(c, int) for c in [x1, y1, z1, x2, y2, z2]):
+    if not all(isinstance(c, int) for c in [z1, z2]):
         raise TypeError("x1, y1, z1, x2, y2, z2 have to be integers")
     hoLine = line2d(cell_shape, x1, y1, x2, y2)
     if z1 == z2:
@@ -97,48 +95,13 @@ def line3d(cell_shape, x1, y1, z1, x2, y2, z2):
         ligneZ = _brC(0, z1, (len(hoLine)-1), z2)
         return [(hoLine[d][0], hoLine[d][1], z) for d, z in ligneZ]
 
-def hex_2d_line(x1, y1, x2, y2):
-    """returns a line from x1,y1 to x2,y2 on an hexagonal grid
-    line is a list of coordinates"""
-    if (x1, y1) == (x2, y2):
-        return [(x1, y1)]
-    return _brH(x1, y1, x2, y2)
+def _brC(x1, y1, x2, y2):
+    """Line Bresenham algorithm for square grid"""
+    result = []
 
-def hex_3d_line(x1, y1, z1, x2, y2, z2):
-    """returns a line from x1,y1,z1 to x2,y2,z2 on an hexagonal grid
-    line is a list of coordinates"""
-    hoLine = hex_2d_line(x1, y1, x2, y2)
-    if z1 == z2:
-        return [(x, y, z1) for x, y in hoLine]
-    else:
-        zLine = _brC(0, z1, (len(hoLine)-1), z2)
-        dicZ = {d:[] for d, z in zLine}
-        for d, z in zLine:
-            dicZ[d].append(z)
-        return [(hoLine[d][0], hoLine[d][1], z) for d, liste_z in dicZ.items() for z in liste_z]
-
-def squ_2d_line(x1, y1, x2, y2):
-    """returns a line from x1,y1 to x2,y2 on an square grid
-    line is a list of coordinates
-    """
     if (x1, y1) == (x2, y2):
         return [(x1, y1)]
-    return _brC(x1, y1, x2, y2)
-    
-def squ_3d_line(x1, y1, z1, x2, y2, z2):
-    """returns a line from x1,y1,z1 to x2,y2,z2 on an square grid
-    line is a list of coordinates"""
-    hoLine = squ_2d_line(x1, y1, x2, y2)
-    if z1 == z2:
-        return [(x, y, z1) for x, y in hoLine]
-    else:
-        zLine = _brC(0, z1, (len(hoLine)-1), z2)
-        return [(hoLine[d][0], hoLine[d][1], z) for d, z in zLine]
 
-def _brC(x1, y1, x2, y2):
-    """Line Bresenham algorithm for square grid"""
-    result = []
-    
     # DIAGONAL SYMETRY
     V = ( abs(y2 - y1) > abs(x2 - x1) )
     if V: y1, x1, y2, x2 = x1, y1, x2, y2
@@ -169,6 +132,9 @@ def _brC(x1, y1, x2, y2):
     
 def _brH(x1, y1, x2, y2):
     """Line Bresenham algorithm for hexagonal grid"""
+    if (x1, y1) == (x2, y2):
+        return [(x1, y1)]
+    
     reversed_sym = (x1 > x2)
     if reversed_sym:
         x1, x2 = x2, x1
@@ -216,6 +182,7 @@ def _brH_h(x1, y1, x2, y2):
             result.append(pos)
             d += 0.5
         
+        # in case of error in the algorithm, we should avoid infinite loop:
         if pos[0] > x2:
             result = []
             break
@@ -260,6 +227,7 @@ def _brH_v(x1, y1, x2, y2):
             result.append(pos)
             offset -= 1.5
         
+        # in case of error in the algorithm, we should avoid infinite loop:
         if direction*pos[1] > direction*y2:
             result = []
             break
@@ -293,35 +261,48 @@ def triangle(cell_shape, xa, ya, xh, yh, iAngle):
     """Returns a list of (x, y) coordinates in a triangle
     A is the top of the triangle, H if the middle of the base
     """
+    if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
+        raise TypeError("xa, ya, xh, yh should be integers")
+    if not iAngle in ANGLES:
+        raise ValueError("iAngle should be one of the ANGLES values")   
+    
     if cell_shape == SQUARE:
-        return triangle_sq(xa, ya, xh, yh, iAngle)
+        return _triangle_sq(xa, ya, xh, yh, iAngle)
     elif cell_shape == HEX: 
-        return triangle_hex(xa, ya, xh, yh, iAngle)
+        return _triangle_hex(xa, ya, xh, yh, iAngle)
     else:
-        raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
+        raise ValueError("'cell_shape' has to be a value from GRID_GEOMETRIES")
 
 def triangle3d(cell_shape, xa, ya, za, xh, yh, zh, iAngle):
     """Returns a list of (x, y, z) coordinates in a 3d-cone
     A is the top of the cone, H if the center of the base
+    
+    WARNING: result is a dictionnary of the form {(x, y): (-z, +z)}
+    
+    This is for performance reason, because on a 2d grid, you generrally don't need a complete list of z coordinates
+    as you don't want to display them: you just want to know if an altitude is inside a range.
+    
+    That could change in later version
     """
+    # TODO: review the result form
+    
+    if not all(isinstance(c, int) for c in [za, zh]):
+        raise TypeError("xa, ya, za, xh, yh, zh should be integers")
+        # a triangle2d will be built during algo, so other args will be checked later
+    
     if cell_shape == SQUARE:
-        return triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle)
+        return _triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle)
     elif cell_shape == HEX: 
-        return triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle)
+        return _triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle)
     else:
-        raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
+        raise ValueError("'cell_shape' has to be a value from GRID_GEOMETRIES")    
+    
 
-def triangle_sq(xa, ya, xh, yh, iAngle):   
-    """Returns a list of (x, y) coordinates in a triangle
-    A is the top of the triangle, H if the middle of the base
-    (square grid)
+def _triangle_sq(xa, ya, xh, yh, iAngle):   
+    """ triangle algorithm on square grid
     """
-    if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
-        raise TypeError("xa, ya, xh, yh should be integers")
-    if not iAngle in ANGLES:
-        raise ValueError("iAngle should be one of the ANGLES values")
     if (xa, ya) == (xh, yh):
-        return [(xa, ya)]    
+        return [(xa, ya)] 
     
     result = []
     
@@ -344,7 +325,7 @@ def triangle_sq(xa, ya, xh, yh, iAngle):
     
     # base (lower slope)
     x1, y1, x2, y2 = min(lines, key=lambda x: (abs ( (x[3] - x[1]) / (x[2] - x[0]) ) if x[2] != x[0] else 10**10))
-    base = squ_2d_line(x1, y1, x2, y2)
+    base = line2d(SQUARE, x1, y1, x2, y2)
     y_base = y1
     lines.remove( (x1, y1, x2, y2) )
     
@@ -354,7 +335,7 @@ def triangle_sq(xa, ya, xh, yh, iAngle):
     for x1, y1, x2, y2 in lines:
         if y_top == None: 
             y_top = y2
-        hat.extend( squ_2d_line(x1, y1, x2, y2) )
+        hat.extend( line2d(SQUARE, x1, y1, x2, y2) )
     
     # sense (1 if top is under base, -1 if not)
     sense = 1 if y_top > y_base else -1
@@ -368,55 +349,9 @@ def triangle_sq(xa, ya, xh, yh, iAngle):
 
     return result
 
-def triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle):
-    """returns a dictionnary {coord: (-dh, +dh)}
-    coord keys are the cells in the triangle, (-dh, +dh) value is the vertical amplitude"""
-    
-    #TODO: review result form
-    
-    if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
-        raise TypeError("xa, ya, za, xh, yh have to be integers")
-    if not iAngle in ANGLES:
-        raise ValueError("iAngle should be one of the ANGLES values")
-    if (xa, ya) == (xh, yh):
-        return [(xa, ya)]  
-
-    result = {} 
-    
-    flat_triangle = triangle_sq(xa, ya, xh, yh, iAngle)
-    k = 1 / ( iAngle * sqrt(3) )
-
-    length = max( abs(xh - xa), abs(yh - ya) )
-
-    vertical_line = squ_2d_line(0, za, length, zh)
-    
-    #on cree un dictionnaire ou x est la cle, et ou la valeur est une liste de z
-    vertical_line_dict = {d:[] for d, z in vertical_line}
-    for d, z in vertical_line:
-        vertical_line_dict[d].append(z)
-        
-    #approximation: on met a jour la hauteur en fonction de la distance au centre
-    for x, y in flat_triangle:
-        distance = int( max( abs(x - xa), abs(y - ya) ) )
-        try:
-            z_list = vertical_line_dict[ distance ]
-        except KeyError:
-            distance = length
-            z_list = vertical_line_dict[ distance ]
-        dh = int( k * distance ) + 1 if distance > 0 else 0
-        result[ (x, y) ] = ( (min(z_list) - dh) , (max(z_list) + dh) ) 
-    return result
-
-
-def triangle_hex(xa, ya, xh, yh, iAngle):   
-    """Returns a list of (x, y) coordinates in a triangle
-    A is the top of the triangle, H if the middle of the base
-    (hexagonal grid)
+def _triangle_hex(xa, ya, xh, yh, iAngle):   
+    """  triangle algorithm on hexagonal grid
     """
-    if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
-        raise TypeError("xa, ya, xh, yh should be integers")
-    if not iAngle in [1, 2, 3]:
-        raise ValueError("iAngle should be one of the ANGLES values")
     if (xa, ya) == (xh, yh):
         return [(xa, ya)]    
     
@@ -449,7 +384,7 @@ def triangle_hex(xa, ya, xh, yh, iAngle):
     
     # base (lower slope)
     x1, y1, x2, y2 = min(segments, key=lambda x: (abs ( (x[3] - x[1]) / (x[2] - x[0]) ) if x[2] != x[0] else 10**10))
-    base = hex_2d_line(x1, y1, x2, y2)
+    base = line2d(HEX, x1, y1, x2, y2)
     y_base = y1
     segments.remove( (x1, y1, x2, y2) )
     
@@ -459,7 +394,7 @@ def triangle_hex(xa, ya, xh, yh, iAngle):
     for x1, y1, x2, y2 in segments:
         if y_sommet == None: 
             y_sommet = y2
-        chapeau.extend( hex_2d_line(x1, y1, x2, y2) )
+        chapeau.extend( line2d(HEX, x1, y1, x2, y2) )
     
     # sense (1 if top is under base, -1 if not)
     sens = 1 if y_sommet > y_base else -1
@@ -473,34 +408,59 @@ def triangle_hex(xa, ya, xh, yh, iAngle):
 
     return result
 
-def triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle):
-    """returns a dictionnary {coord: (-dh, +dh)}
-    coord (x,y) keys are the cells in the triangle, 
-    (-dh, +dh) value is the vertical amplitude"""
-    flat_trangle = triangle_hex(xa, ya, xh, yh, iAngle)
+
+def _triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle):
+    """ 3d triangle algorithm on square grid"""
+    result = []
+    
+    flat_triangle = triangle(SQUARE, xa, ya, xh, yh, iAngle)
+    k = 1 / ( iAngle * sqrt(3) )
+
+    length = max( abs(xh - xa), abs(yh - ya) )
+
+    vertical_line = line2d(SQUARE, 0, za, length, zh)
     
-    #TODO: review result form
+    # build a dict with X key and value is a list of Z values
+    vertical_line_dict = {d:[] for d, z in vertical_line}
+    for d, z in vertical_line:
+        vertical_line_dict[d].append(z)
+        
+    # this is approximative: height is update according to the manhattan distance to center
+    for x, y in flat_triangle:
+        distance = int( max( abs(x - xa), abs(y - ya) ) )
+        try:
+            z_list = vertical_line_dict[ distance ]
+        except KeyError:
+            distance = length
+            z_list = vertical_line_dict[ distance ]
+        dh = int( k * distance ) + 1 if distance > 0 else 0
+        result[ (x, y) ] = ( (min(z_list) - dh) , (max(z_list) + dh) ) 
+    return result
+
+def _triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle):
+    """ 3d triangle algorithm on hexagonal grid """
     
-    if (xa, ya) == (xh, yh):
-        return [(xa, ya)]   
+    flat_triangle = triangle(HEX, xa, ya, xh, yh, iAngle)
+
     result = {} 
     
     k = 1 / ( iAngle * sqrt(3) )
     
+    # use cubic coordinates
     xua, yua, zua = cv_off_cube(xa, ya)
     xuh, yuh, zuh = cv_off_cube(xh, yh)
     
     length = max( abs(xuh - xua), abs(yuh - yua), abs(zuh - zua) )
 
-    vertical_line = squ_2d_line(0, za, length, zh)
+    vertical_line = line2d(SQUARE, 0, za, length, zh)
     
-    #on cree un dictionnaire ou x est la cle, et ou la valeur est une liste de z
+    # build a dict with X key and value is a list of Z values
     vertical_line_dict = {d:[] for d, z in vertical_line}
     for d, z in vertical_line:
         vertical_line_dict[d].append(z)
         
-    #approximation: on met a jour la hauteur en fonction de la distance au centre
-    for x, y in flat_trangle:
+    # this is approximative: height is update according to the manhattan distance to center
+    for x, y in flat_triangle:
         xu, yu, zu = cv_off_cube(x, y)
         distance = int( max( abs(xu - xua), abs(yu - yua), abs(zu - zua) ) )
         try:
@@ -512,21 +472,45 @@ 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"""
+    # check the args:
+    try:
+        x, y = center
+    except ValueError:
+        raise TypeError("'center' should be an tuple of (x, y) coordinates with x and y integers (given: {})".format(center))
+    if not isinstance(x, int) or not isinstance(y, int):
+        raise ValueError("'center' should be an tuple of (x, y) coordinates with x and y integers (given: {})".format(center))
+        
+    try:
+        for coord in coordinates:
+            try:
+                x, y = coord
+                if not isinstance(x, int) or not isinstance(y, int):
+                    raise ValueError() 
+            except ValueError:
+                raise ValueError("'coordinates' should be an list of (x, y) coordinates with x and y integers (given: {})".format(coordinates))
+    except TypeError:
+        raise TypeError("'coordinates' should be an list of (x, y) coordinates with x and y integers (given: {})".format(coordinates))
+    
+    if not isinstance(rotations, int):
+        raise TypeError("'rotations' should be an integer (given: {})".format(rotations))
+    
+    # call the method according to cells shape
     if cell_shape == SQUARE:
-        return squ_pivot(center, coordinates, rotations)
+        return _squ_pivot(center, coordinates, rotations)
     elif cell_shape == HEX: 
-        return hex_pivot(center, coordinates, rotations)
+        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):
+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"""
@@ -548,7 +532,7 @@ def hex_pivot(center, coordinates, rotations):
         
     return result
 
-def squ_pivot(center, coordinates, rotations):
+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"""
@@ -572,21 +556,18 @@ def squ_pivot(center, coordinates, rotations):
 ## cubic coordinates
 def cv_cube_off(xu, yu, zu):
     """convert cubic coordinates (xu, yu, zu) in standards coordinates (x, y) [offset]"""
-    if not all(isinstance(c, int) for c in [xu, yu, zu]):
-        raise TypeError("!err: xu, yu et zu doivent etre des entiers")
     y = int( xu + ( zu - (zu & 1) ) / 2 )
     x = zu
     return (x, y)        
 
 def cv_off_cube(x, y):
     """converts standards coordinates (x, y) [offset] in cubic coordinates (xu, yu, zu)"""
-    if not all(isinstance(c, int) for c in [x, y]):
-        raise TypeError("!err: x et y doivent etre des entiers")
     zu = x
     xu = int( y - ( x - (x & 1) ) / 2 )
     yu = int( -xu -zu )
     return (xu, yu, zu)    
 
+# unused
 def cube_round(x, y, z):
     """returns the nearest cell (in cubic coords)
     x, y, z can be floating numbers, no problem."""
@@ -600,10 +581,12 @@ def cube_round(x, y, z):
         rz = -rx-ry
     return (rx, ry, rz)
 
+# unused
 def hex_distance_cube(xa, ya, za, xb, yb, zb):
     """returns the manhattan distance between the two cells"""
     return max(abs(xa - xb), abs(ya - yb), abs(za - zb))
 
+# unused
 def distance_off(xa, ya, xb, yb):
     """ distance between A and B (offset coordinates)"""
     # 10 times quicker if no conversion...

+ 70 - 73
tests/geometry/test_line.py

@@ -11,86 +11,83 @@ from pypog import geometry
 class Test(unittest.TestCase):
     """test line algorithms"""
 
-    def test_hex_line(self):
-        """ 2d line on hexagonal grid """
-        cell_shape = geometry.HEX
-        
-        line = geometry.line2d(cell_shape, 1,1,1,1)
-        self.assertEqual(line, [(1,1)])
-        
-        line = geometry.line2d(cell_shape, 0,0,1,1)
-        self.assertEqual(line, [(0,0), (0,1), (1,1)])
- 
-        line = geometry.line2d(cell_shape, 0,0,7,3)
-        self.assertEqual(line, [(0,0), (1,0), (2,1), (3,1), (4,2), (5,2), (6,3), (7,3)] )
- 
-        line = geometry.line2d(cell_shape, 4,3,0,3)
-        self.assertEqual(line, [(4,3), (3,2), (2,3), (1,2), (0,3)] )
- 
-        line = geometry.line2d(cell_shape, 3,0,3,3)
-        self.assertEqual(line, [(3,0), (3,1), (3,2), (3,3)] )
+    def test_line_errors(self):
+        self.assertRaises( TypeError, geometry.line2d, geometry.HEX, "a", 1, 1, 1)
+        self.assertRaises( TypeError, geometry.line2d, geometry.HEX, 1, "a", 1, 1)
+        self.assertRaises( TypeError, geometry.line2d, geometry.HEX, 1, 1, "a", 1)
+        self.assertRaises( TypeError, geometry.line2d, geometry.HEX, 1, 1, 1, "a")
+        self.assertRaises( ValueError, geometry.line2d, 0, 1, 1, 1, 1)
+
+        self.assertRaises( TypeError, geometry.line3d, geometry.HEX, 1, 1, "a", 1, 1, 1)
+        self.assertRaises( TypeError, geometry.line3d, geometry.HEX, 1, 1, 1, 1, 1, "a")
         
+    def test_line(self):
+        """ 2d line on square or hexagonal grid """
+        cell_shape = geometry.HEX
         
-    def test_squ_line(self):
-        """ 2d line on square grid """
-        cell_shape = geometry.SQUARE
-        line = geometry.line2d(cell_shape,0,0,0,1)
-        self.assertEqual(line, [(0,0), (0,1)])
+        attended = {
+                    geometry.HEX:    {
+                                      (1,1,1,1): [(1,1)], 
+                                      (0,0,1,1): [(0,0), (0,1), (1,1)], 
+                                      (1,1,0,0): [(1,1), (0,1), (0,0)], 
+                                      (0,0,7,3): [(0,0), (1,0), (2,1), (3,1), (4,2), (5,2), (6,3), (7,3)], 
+                                      (7,3,0,0): [(7,3), (6,3), (5,2), (4,2), (3,1), (2,1), (1,0), (0,0)], 
+                                      (4,3,0,3): [(4,3), (3,2), (2,3), (1,2), (0,3)], 
+                                      (0,3,4,3): [(0,3), (1,2), (2,3), (3,2), (4,3)], 
+                                      (3,0,3,3): [(3,0), (3,1), (3,2), (3,3)], 
+                                      (3,3,3,0): [(3,3), (3,2), (3,1), (3,0)]
+                                     }, 
+                    
+                    geometry.SQUARE: {
+                                      (1,1,1,1): [(1,1)], 
+                                      (0,0,0,1): [(0,0), (0,1)], 
+                                      (0,1,0,0): [(0,1), (0,0)], 
+                                      (0,0,1,1): [(0,0), (1,1)], 
+                                      (1,1,0,0): [(1,1), (0,0)], 
+                                      (0,0,7,3): [(0,0), (1,0), (2,1), (3,1), (4,2), (5,2), (6,3), (7,3)], 
+                                      (7,3,0,0): [(7,3), (6,3), (5,2), (4,2), (3,1), (2,1), (1,0), (0,0)], 
+                                      (4,3,0,3): [(4,3), (3,3), (2,3), (1,3), (0,3)], 
+                                      (0,3,4,3): [(0,3), (1,3), (2,3), (3,3), (4,3)], 
+                                      (3,0,3,3): [(3,0), (3,1), (3,2), (3,3)],                      
+                                      (3,3,3,0): [(3,3), (3,2), (3,1), (3,0)]                    
+                                     }
+                   }
         
-        line = geometry.line2d(cell_shape,0,0,1,1)
-        self.assertEqual(line, [(0,0), (1,1)])
+        for cell_shape, tests in attended.items():
+            for args, result in tests.items():
+                line = geometry.line2d(cell_shape, *args)
+                self.assertEqual(line, result)
         
-        line = geometry.line2d(cell_shape,0,0,7,3)
-        self.assertEqual(line, [(0,0), (1,0), (2,1), (3,1), (4,2), (5,2), (6,3), (7,3)] )
- 
-        line = geometry.line2d(cell_shape,4,3,0,3)
-        self.assertEqual(line, [(4,3), (3,3), (2,3), (1,3), (0,3)] )
- 
-        line = geometry.line2d(cell_shape,3,0,3,3)
-        self.assertEqual(line, [(3,0), (3,1), (3,2), (3,3)] )
     
-    def test_hex_line_3d(self):
-        """ 3d line on hexagonal grid """
+    def test_line_3d(self):
+        """ 3d line on hexagonal and square grid """
         cell_shape = geometry.HEX
-        line = geometry.line3d(cell_shape,1,1,1,1,1,1)
-        self.assertEqual(line, [(1,1,1)])
-    
-        line = geometry.line3d(cell_shape,1,1,0,1,1,1)
-        self.assertEqual(line, [(1,1,0), (1,1,1)])
-    
-        line = geometry.line3d(cell_shape,0,0,0,1,1,1)
-        self.assertEqual(line, [(0,0,0), (0,1,0), (1,1,1)])
-    
-        line = geometry.line3d(cell_shape,0,0,0,7,3,7)
-        self.assertEqual(line, [(0,0,0), (1,0,1), (2,1,2), (3,1,3), (4,2,4), (5,2,5), (6,3,6), (7,3,7)] )
- 
-        line = geometry.line3d(cell_shape,4,3,10,0,3,3)
-        self.assertEqual(line, [(4,3,10), (3,2,9), (3,2,8), (2,3,7), (2,3,6), (1,2,5), (1,2,4), (0,3,3)] )
- 
-        line = geometry.line3d(cell_shape,3,0,0,3,3,0)
-        self.assertEqual(line, [(3,0,0), (3,1,0), (3,2,0), (3,3,0)] )
         
-    def test_squ_line_3d(self):
-        """ 3d line on square grid """
-        cell_shape = geometry.SQUARE
-        line = geometry.line3d(cell_shape,1,1,1,1,1,1)
-        self.assertEqual(line, [(1,1,1)])
-    
-        line = geometry.line3d(cell_shape,1,1,0,1,1,1)
-        self.assertEqual(line, [(1,1,0), (1,1,1)])
-    
-        line = geometry.line3d(cell_shape,0,0,0,1,1,1)
-        self.assertEqual(line, [(0,0,0), (1,1,1)])
-    
-        line = geometry.line3d(cell_shape,0,0,0,7,3,7)
-        self.assertEqual(line, [(0,0,0), (1,0,1), (2,1,2), (3,1,3), (4,2,4), (5,2,5), (6,3,6), (7,3,7)] )
- 
-        line = geometry.line3d(cell_shape,4,3,10,0,3,3)
-        self.assertEqual(line, [(4,3,10), (3,3,9), (3,3,8), (2,3,7), (2,3,6), (1,3,5), (1,3,4), (0,3,3)] )
- 
-        line = geometry.line3d(cell_shape,3,0,0,3,3,0)
-        self.assertEqual(line, [(3,0,0), (3,1,0), (3,2,0), (3,3,0)] )
-
+        attended = {
+                    geometry.HEX:    {
+                                      (1,1,1,1,1,1) : [(1,1,1)], 
+                                      (1,1,0,1,1,1) : [(1,1,0), (1,1,1)], 
+                                      (0,0,0,1,1,1) : [(0,0,0), (0,1,0), (1,1,1)], 
+                                      (0,0,0,7,3,7) : [(0,0,0), (1,0,1), (2,1,2), (3,1,3), (4,2,4), (5,2,5), (6,3,6), (7,3,7)], 
+                                      (4,3,10,0,3,3): [(4,3,10), (3,2,9), (3,2,8), (2,3,7), (2,3,6), (1,2,5), (1,2,4), (0,3,3)], 
+                                      (3,0,0,3,3,0) : [(3,0,0), (3,1,0), (3,2,0), (3,3,0)]
+                                     }, 
+                    
+                    geometry.SQUARE: {
+                                      (1,1,1,1,1,1) : [(1,1,1)], 
+                                      (1,1,0,1,1,1) : [(1,1,0), (1,1,1)], 
+                                      (0,0,0,1,1,1) : [(0,0,0), (1,1,1)], 
+                                      (0,0,0,7,3,7) : [(0,0,0), (1,0,1), (2,1,2), (3,1,3), (4,2,4), (5,2,5), (6,3,6), (7,3,7)], 
+                                      (4,3,10,0,3,3): [(4,3,10), (3,3,9), (3,3,8), (2,3,7), (2,3,6), (1,3,5), (1,3,4), (0,3,3)], 
+                                      (3,0,0,3,3,0) : [(3,0,0), (3,1,0), (3,2,0), (3,3,0)]
+                                     }
+                   }
+        
+        for cell_shape, tests in attended.items():
+            for args, result in tests.items():
+                line = geometry.line3d(cell_shape, *args)
+                self.assertEqual(line, result)
+        
 
 if __name__ == "__main__":
     unittest.main()

+ 18 - 8
tests/geometry/test_pivot.py

@@ -8,10 +8,22 @@ from pypog import geometry
 
 class Test(unittest.TestCase):
 
+    def test_pivot_errors(self):
+        # invalid cell shape
+        self.assertRaises(ValueError, geometry.pivot, 0, (0,0), [(0,0)], 1)
+        
+        self.assertRaises(TypeError, geometry.pivot, 0,  "a"    , [(0,0)], 1)
+        self.assertRaises(ValueError, geometry.pivot, 0, ("a",0), [(0,0)], 1)
+        
+        self.assertRaises(TypeError, geometry.pivot, 0, (0,0), 0, 1)
+        self.assertRaises(ValueError, geometry.pivot, 0, (0,0), ["a", (0,0)], 1)
+        self.assertRaises(ValueError, geometry.pivot, 0, (0,0), [("a",0), (0,0)], 1)
+
+        self.assertRaises(TypeError, geometry.pivot, 0, (0,0), 1, "a")
+
     def test_hex_pivot(self):
         """ pivot on hexagonal grid """
         
-        
         attended = [
                     [(5, 5), (4, 5), (6, 6)], 
                     [(5, 6), (4, 7), (6, 6)],
@@ -24,12 +36,11 @@ class Test(unittest.TestCase):
         
         
         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(geometry.pivot( geometry.HEX, (6,6), [(6,6)], i), [(6,6)])
+            result = geometry.pivot(geometry.HEX, (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):
+    def test_squ_pivot(self):
         """ pivot on square grid """
         attended = [
                     [(6, 6), (6, 5), (5, 5), (5, 6)],
@@ -40,10 +51,9 @@ class Test(unittest.TestCase):
                    ]
 
         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(geometry.pivot( geometry.SQUARE, (6,6), [(6,6)], i), [(6,6)])
+            result = geometry.pivot(geometry.SQUARE, (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()

+ 42 - 0
tests/geometry/test_rect.py

@@ -0,0 +1,42 @@
+'''
+Created on 11 dec. 2016
+
+@author: olinox
+'''
+import unittest
+
+from pypog import geometry
+
+
+class Test(unittest.TestCase):
+
+    def test_rect_errors(self):
+        for method in (geometry.rect, geometry.hollow_rect):
+            self.assertRaises( TypeError, method, "a", 1, 1, 1)
+            self.assertRaises( TypeError, method, 1, "a", 1, 1)
+            self.assertRaises( TypeError, method, 1, 1, "a", 1)
+            self.assertRaises( TypeError, method, 1, 1, 1, "a")
+
+    def test_rect(self):
+        
+        self.assertEquals(geometry.rect(0,0,0,0), [(0,0)])
+        self.assertCountEqual(geometry.rect(0,0,1,1), [(0,0), (0,1), (1,1), (1,0)])
+        self.assertCountEqual(geometry.rect(1,1,0,0), [(0,0), (0,1), (1,1), (1,0)])
+        self.assertCountEqual(geometry.rect(4,3,7,5), [(4, 3), (4, 4), (4, 5), (5, 5), (6, 5), (7, 5), (7, 4), (7, 3), (6, 3), (5, 3), (6, 4), (5, 4)])
+        self.assertCountEqual(geometry.rect(3,3,9,9), [(3, 3), (9, 9), (9, 8), (9, 7), (9, 5), (9, 6), (9, 4), (9, 3), (8, 4), (7, 3), (6, 4), (4, 4), 
+                                                       (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (4, 9), (5, 9), (6, 9), (7, 9), (8, 9), (4, 5), 
+                                                       (5, 4), (6, 5), (7, 4), (8, 5), (4, 6), (5, 5), (6, 6), (7, 5), (8, 6), (4, 7), (5, 6), (6, 7), 
+                                                       (7, 6), (8, 7), (4, 8), (5, 7), (6, 8), (7, 7), (8, 8), (7, 8), (5, 8), (8, 3), (6, 3), (4, 3), 
+                                                       (5, 3)])
+
+        self.assertEquals(geometry.hollow_rect(0,0,0,0), [(0,0)])
+        self.assertCountEqual(geometry.hollow_rect(0,0,1,1), [(0,0), (0,1), (1,1), (1,0)])
+        self.assertCountEqual(geometry.hollow_rect(1,1,0,0), [(0,0), (0,1), (1,1), (1,0)])
+        self.assertCountEqual(geometry.hollow_rect(4,3,7,5), [(4, 3), (4, 4), (4, 5), (5, 5), (6, 5), (7, 5), (7, 4), (7, 3), (6, 3), (5, 3)])
+        self.assertCountEqual(geometry.hollow_rect(3,3,9,9), [(3, 3), (9, 9), (9, 8), (9, 7), (9, 5), (9, 6), (9, 4), (9, 3), (7, 3), (3, 4), 
+                                                              (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (4, 9), (5, 9), (6, 9), (7, 9), (8, 9), 
+                                                              (8, 3), (6, 3), (4, 3), (5, 3)])
+
+
+if __name__ == "__main__":
+    unittest.main()

+ 105 - 2
tests/geometry/test_triangle.py

@@ -11,6 +11,26 @@ from pypog import geometry
 class Test(unittest.TestCase):
     """test triangle algorithms"""
 
+    def test_triangle_errors(self):
+        
+        for cell_shape in (geometry.HEX, geometry.SQUARE):
+            self.assertRaises( TypeError, geometry.triangle, cell_shape, "a", 1, 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle, cell_shape, 1, "a", 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle, cell_shape, 1, 1, "a", 1, 1)
+            self.assertRaises( TypeError, geometry.triangle, cell_shape, 1, 1, 1, "a", 1)
+            self.assertRaises( ValueError, geometry.triangle, cell_shape, 1, 1, 1, 1, -1)
+    
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, "a", 1, 1, 1, 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, 1, "a", 1, 1, 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, 1, 1, "a", 1, 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, 1, 1, 1, "a", 1, 1, 1)
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, 1, 1, 1, 1, "a", 1, 1)
+            self.assertRaises( TypeError, geometry.triangle3d, cell_shape, 1, 1, 1, 1, 1, "a", 1)
+            self.assertRaises( ValueError, geometry.triangle3d, cell_shape, 1, 1, 1, 1, 1, 1, -1)
+            
+        self.assertRaises( ValueError, geometry.triangle, 0, 1, 1, 1, 1, 1)
+        self.assertRaises( ValueError, geometry.triangle3d, 0, 1, 1, 1, 1, 1, 1, 1)
+
     def test_sq_triangle(self):
         """test triangle algorithms on square grid"""
         cell_shape = geometry.SQUARE
@@ -18,15 +38,98 @@ class Test(unittest.TestCase):
         for i in geometry.ANGLES:
             self.assertCountEqual(geometry.triangle(cell_shape, 0, 0, 0, 0, i), [(0,0)])
 
-        #TODO: complete
+        # TODO: check and validate
+#         # left to right
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # top to bottom
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # right to left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # bottom to top
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # top left to bottom right
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # bottom right to top left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # top right to bottom left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # bottom right to top left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
 
+        
 
     def test_hex_triangle(self):
         """test triangle algorithms on hexagonal grid"""
         cell_shape = geometry.HEX
         for i in geometry.ANGLES:
             self.assertCountEqual(geometry.triangle(cell_shape, 0, 0, 0, 0, i), [(0,0)])
-        #TODO: complete
+
+        # left to right
+        self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [(3, 3), (3, 4), (3, 3), (4, 5), (4, 4), (4, 3), (4, 2), (4, 1), (4, 1), (3, 1), (3, 2), (2, 3)])
+        self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [(3, 3), (4, 4), (4, 3), (4, 2), (4, 2), (3, 2), (2, 3)])
+        self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [(3, 3), (4, 4), (4, 3), (4, 2), (4, 2), (3, 2), (2, 3)])
+        
+        # TODO: check and validate
+        
+#         # top to bottom
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+        # right to left
+        self.assertCountEqual(geometry.triangle(cell_shape, 4, 3, 2, 3, 1), [(3, 2), (3, 1), (3, 2), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 5), (3, 4), (3, 3), (4, 3)])
+        self.assertCountEqual(geometry.triangle(cell_shape, 4, 3, 2, 3, 2), [(3, 2), (2, 2), (2, 3), (2, 4), (2, 4), (3, 3), (4, 3)])
+        self.assertCountEqual(geometry.triangle(cell_shape, 4, 3, 2, 3, 3), [(3, 2), (2, 2), (2, 3), (2, 4), (2, 4), (3, 3), (4, 3)])
+         
+#         # bottom to top
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # top left to bottom right
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # bottom right to top left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # top right to bottom left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+#         
+#         # bottom right to top left
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 1), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 2), [])
+#         self.assertCountEqual(geometry.triangle(cell_shape, 2, 3, 4, 3, 3), [])
+
+        
     
     def test_sq_triangle_3d(self):
         """test triangle3d algorithms on square grid"""

+ 2 - 0
tests/gridviewer/GridViewer.py

@@ -50,6 +50,8 @@ class GridViewer(QMainWindow):
         self.make_grid()
         
     def make_grid(self):
+        
+        self.selection = []
         shape = geometry.HEX if self.ui.opt_hex.isChecked() else geometry.SQUARE
         width = self.ui.spb_width.value()
         height = self.ui.spb_height.value()