diff --git a/grid.cabal b/grid.cabal
--- a/grid.cabal
+++ b/grid.cabal
@@ -1,5 +1,5 @@
 name:           grid
-version:        4.1
+version:        5.0
 synopsis:       Tools for working with regular grids (graphs, lattices).
 description:    Provides tools for working with regular arrangements
                 of tiles, such as might be used in a board game or some
@@ -42,7 +42,7 @@
   build-depends:   base ==4.*,
                    exact-combinatorics ==0.2.*,
                    test-framework-quickcheck2 ==0.3.*,
-                   QuickCheck ==2.4.* || ==2.5.*,
+                   QuickCheck ==2.4.* || ==2.5.* || ==2.6.*,
                    test-framework ==0.8.*,
                    grid,
                    base-unicode-symbols ==0.2.*
diff --git a/src/Math/Geometry/Grid.hs b/src/Math/Geometry/Grid.hs
--- a/src/Math/Geometry/Grid.hs
+++ b/src/Math/Geometry/Grid.hs
@@ -12,9 +12,34 @@
 -- The userguide is available at 
 -- <https://github.com/mhwombat/grid/wiki>.
 --
+-- In this package, tiles are called \"triangular\", \"square\", etc.,
+-- based on the number of /neighbours/ they have.
+-- For example, a square tile has four neighbours, and a hexagonal
+-- tile has six.
+-- There are only three regular polygons that can tile a plane:
+-- triangles, squares, and hexagons.
+-- Of course, other plane tilings are possible if you use irregular 
+-- polygons, or curved shapes, or if you combine tiles of different 
+-- shapes.
+--
+-- When you tile other surfaces, things get very interesting.
+-- Octagons will tile a /hyperbolic/ plane.
+-- (Alternatively, you can think of these as squares on a board game
+-- where diagonal moves are allowed.)
+--
+-- For a board game, you probably want to choose the grid type based
+-- on the number of /directions/ a player can move, rather than the
+-- number of sides the tile will have when you display it.
+-- For example, for a game that uses square tiles and allows the user
+-- to move diagonally as well as horizontally and vertically,
+-- consider using one of the grids with /octagonal/ tiles to model the
+-- board.
+-- You can still /display/ the tiles as squares, but for internal
+-- calculations they are octagons.
+--
 -- NOTE: Version 4.0 uses associated (type) synonyms instead of 
 -- multi-parameter type classes.
-
+--
 -- NOTE: Version 3.0 changed the order of parameters for many functions.
 -- This makes it easier for the user to write mapping and folding
 -- operations.
@@ -25,6 +50,8 @@
 
 module Math.Geometry.Grid
   (
+    -- * Example
+    -- $Example
     -- * The Grid class
     Grid(..),
     FiniteGrid(..),
@@ -61,9 +88,16 @@
     hexHexGrid,
     -- ** Parallelogram-shaped grid with hexagonal tiles
     ParaHexGrid,
-    paraHexGrid
-    -- * Example
-    -- $Example
+    paraHexGrid,
+    -- * Grids with octagonal tiles
+    -- ** Unbounded grid with octagonal tiles
+    UnboundedOctGrid,
+    -- ** Rectangular grid with octagonal tiles
+    RectOctGrid,
+    rectOctGrid,
+    -- ** Toroidal grid with octagonal tiles
+    TorOctGrid,
+    torOctGrid
   ) where
 
 import Math.Geometry.GridInternal (Grid(..), FiniteGrid(..), 
@@ -71,7 +105,9 @@
   ParaTriGrid, paraTriGrid, RectTriGrid, rectTriGrid, 
   TorTriGrid, torTriGrid, UnboundedSquareGrid, 
   RectSquareGrid, rectSquareGrid, TorSquareGrid, torSquareGrid, 
-  UnboundedHexGrid, HexHexGrid, hexHexGrid, ParaHexGrid, paraHexGrid)
+  UnboundedHexGrid, HexHexGrid, hexHexGrid, ParaHexGrid, paraHexGrid, 
+  UnboundedOctGrid, RectOctGrid, rectOctGrid, TorOctGrid, torOctGrid)
+
 
 {- $Example
    Create a grid.
diff --git a/src/Math/Geometry/GridInternal.hs b/src/Math/Geometry/GridInternal.hs
--- a/src/Math/Geometry/GridInternal.hs
+++ b/src/Math/Geometry/GridInternal.hs
@@ -37,7 +37,12 @@
     HexHexGrid,
     hexHexGrid,
     ParaHexGrid,
-    paraHexGrid
+    paraHexGrid,
+    UnboundedOctGrid,
+    RectOctGrid,
+    rectOctGrid,
+    TorOctGrid,
+    torOctGrid,
   ) where
 
 import Prelude hiding (null)
@@ -49,7 +54,7 @@
 import Data.Ord.Unicode ((≤), (≥))
 
 -- | A regular arrangement of tiles.
---   Minimal complete definition: @indices@ and @distance@.
+--   Minimal complete definition: @Index@, @indices@ and @distance@.
 class Grid g where
   type Index g
 
@@ -225,6 +230,9 @@
 -- Triangular tiles
 --
 
+-- | An unbounded grid with triangular tiles.
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
 data UnboundedTriGrid = UnboundedTriGrid deriving Show
 
 instance Grid UnboundedTriGrid where
@@ -453,6 +461,9 @@
 -- Square tiles
 --
 
+-- | An unbounde grid with square tiles.
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
 data UnboundedSquareGrid = UnboundedSquareGrid deriving Show
 
 instance Grid UnboundedSquareGrid where
@@ -540,7 +551,7 @@
 --      [((x-1) `mod` c,y), (x,(y+1) `mod` r), ((x+1) `mod` c,y), 
 --        (x,(y-1) `mod` r)]
   neighbours g = nub . map (normalise g) . neighbours UnboundedSquareGrid
-  distance g@(TorSquareGrid (r,c) _) (x1, y1) (x2, y2) =
+  distance g@(TorSquareGrid (r,c) _) (x1, y1) (x2, y2) = -- TODO redo
     if g `contains` (x1, y1) && g `contains` (x2, y2)
       then min adx (abs (c-adx)) + min ady (abs (r-ady))
       else undefined 
@@ -555,6 +566,18 @@
   normalise g (x,y) = (x `mod` c, y `mod` r)
     where (r, c) = size g
 
+denormaliseTor
+  :: (FiniteGrid g, Index g ~ (Int, Int), (Int, Int) ~ Size g) =>
+     g -> Index g -> [Index g]
+denormaliseTor g (x,y) = nub [(x2,y1), (x,y1), (x1,y1), 
+                              (x2,y),  (x,y),  (x1,y),
+                              (x2,y2), (x,y2), (x1,y2)]
+  where (r, c) = size g
+        x1 = x + c
+        y1 = y + r
+        x2 = x - c
+        y2 = y - r
+
 -- | @'torSquareGrid' r c@ returns a toroidal grid with @r@ 
 --   rows and @c@ columns, using square tiles. If @r@ and @c@ are 
 --   both nonnegative, the resulting grid will have @r*c@ tiles. Otherwise, 
@@ -566,6 +589,9 @@
 -- Hexagonal tiles
 --
 
+-- | An unbounded grid with hexagonal tiles
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
 data UnboundedHexGrid = UnboundedHexGrid deriving Show
 
 instance Grid UnboundedHexGrid where
@@ -656,4 +682,93 @@
 paraHexGrid ∷ Int → Int → ParaHexGrid
 paraHexGrid r c = 
   ParaHexGrid (r,c) [(x, y) | x ← [0..c-1], y ← [0..r-1]]
+
+
+--
+-- Octagonal tiles
+--
+
+-- | An unbounded grid with octagonal tiles.
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
+data UnboundedOctGrid = UnboundedOctGrid deriving Show
+
+instance Grid UnboundedOctGrid where
+  type Index UnboundedOctGrid = (Int, Int)
+  indices _ = undefined
+  neighbours _ (x,y) = [(x-1,y+1), (x,y+1), (x+1,y+1), (x+1,y), 
+                        (x+1,y-1), (x,y-1), (x-1,y-1), (x-1,y)]
+  distance _ (x1, y1) (x2, y2) = max (abs (x2-x1)) (abs (y2-y1))
+  contains _ _ = True
+
+--
+-- Rectangular grids with octagonal tiles
+--
+
+-- | A rectangular grid with octagonal tiles.
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
+data RectOctGrid = RectOctGrid (Int, Int) [(Int, Int)] deriving Eq
+
+instance Show RectOctGrid where 
+  show (RectOctGrid (r,c) _) = 
+    "rectOctGrid " ++ show r ++ " " ++ show c
+
+instance Grid RectOctGrid where
+  type Index RectOctGrid = (Int, Int)
+  indices (RectOctGrid _ xs) = xs
+  neighbours = neighboursBasedOn UnboundedOctGrid
+  distance = distanceBasedOn UnboundedOctGrid
+
+instance FiniteGrid RectOctGrid where
+  type Size RectOctGrid = (Int, Int)
+  size (RectOctGrid s _) = s
+
+instance BoundedGrid RectOctGrid where
+  tileSideCount _ = 4
+  boundary g = cartesianIndices . size $ g
+  centre g = cartesianCentre . size $ g
+
+-- | @'rectOctGrid' r c@ produces a rectangular grid with @r@ rows
+--   and @c@ columns, using octagonal tiles. If @r@ and @c@ are both 
+--   nonnegative, the resulting grid will have @r*c@ tiles. Otherwise, 
+--   the resulting grid will be null and the list of indices will be 
+--   null.
+rectOctGrid ∷ Int → Int → RectOctGrid
+rectOctGrid r c = 
+  RectOctGrid (r,c) [(x,y) | x ← [0..c-1], y ← [0..r-1]]
+
+--
+-- Toroidal grids with octagonal tiles.
+--
+
+-- | A toroidal grid with octagonal tiles.
+--   The grid and its indexing scheme are illustrated in the user guide,
+--   available at <https://github.com/mhwombat/grid/wiki>.
+data TorOctGrid = TorOctGrid (Int, Int) [(Int, Int)] deriving Eq
+
+instance Show TorOctGrid where 
+  show (TorOctGrid (r,c) _) = "torOctGrid " ++ show r ++ " " ++ show c
+
+instance Grid TorOctGrid where
+  type Index TorOctGrid = (Int, Int)
+  indices (TorOctGrid _ xs) = xs
+  neighbours g = nub . map (normalise g) . neighbours UnboundedOctGrid
+  distance g a b = minimum . map (distance UnboundedOctGrid a) $ bs
+    where bs = denormaliseTor g b
+
+instance FiniteGrid TorOctGrid where
+  type Size TorOctGrid = (Int, Int)
+  size (TorOctGrid s _) = s
+
+instance WrappedGrid TorOctGrid where
+  normalise g (x,y) = (x `mod` c, y `mod` r)
+    where (r, c) = size g
+
+-- | @'torOctGrid' r c@ returns a toroidal grid with @r@ 
+--   rows and @c@ columns, using octagonal tiles. If @r@ and @c@ are 
+--   both nonnegative, the resulting grid will have @r*c@ tiles. Otherwise, 
+--   the resulting grid will be null and the list of indices will be null.
+torOctGrid ∷ Int → Int → TorOctGrid
+torOctGrid r c = TorOctGrid (r,c) [(x, y) | x ← [0..c-1], y ← [0..r-1]]
 
diff --git a/src/Math/Geometry/GridMap.hs b/src/Math/Geometry/GridMap.hs
--- a/src/Math/Geometry/GridMap.hs
+++ b/src/Math/Geometry/GridMap.hs
@@ -42,7 +42,7 @@
 -- | A regular arrangement of tiles, having a value associated with
 --   each tile.
 --   Minimal complete definition: @toMap@, @toGrid@, @adjustWithKey@,
---   @mapWithKey.
+--   @mapWithKey@.
 --
 --   Note: Some of the methods have an @Ord@ constraint on the grid 
 --   index. This is purely to make it easier to write implementations.
@@ -101,12 +101,16 @@
   elems = M.elems . toMap
 
   -- | Map a function over all values in the map.
-  map ∷ GridMap gm b ⇒ (v → b) → gm v → gm b
+  map 
+    ∷ (GridMap gm v2, 
+        G.Index (BaseGrid gm v) ~ G.Index (BaseGrid gm v2)) ⇒ 
+    (v → v2) → gm v → gm v2
   map f = mapWithKey (\_ v → f v)
 
   -- | Map a function over all values in the map.
   mapWithKey 
-    ∷ (k ~ G.Index (BaseGrid gm v), GridMap gm v2) ⇒ 
+    ∷ (k ~ G.Index (BaseGrid gm v), k ~ G.Index (BaseGrid gm v2), 
+        GridMap gm v2) ⇒ 
       (k → v → v2) → gm v → gm v2
 
 {- $Compare
diff --git a/test/Math/Geometry/GridQC.hs b/test/Math/Geometry/GridQC.hs
--- a/test/Math/Geometry/GridQC.hs
+++ b/test/Math/Geometry/GridQC.hs
@@ -31,6 +31,17 @@
 pointAt g i = indices g !! (i `mod` n)
   where n = (length . indices) g
 
+minPathCount
+  ∷ (Eq (Index g), Grid g) ⇒ g → Index g → Index g → Int
+minPathCount g a b = length . minimalPaths g a $ b
+
+minPathCount2
+  ∷ (Eq (Index g), Grid g) ⇒ g → [Index g] → Index g → Int
+minPathCount2 g as b = sum . map (\x → minPathCount g x b) $ as
+
+neighbourCount ∷ ∀ g. Grid g ⇒ g → Index g → Int
+neighbourCount g x = length . neighbours g $ x
+
 --
 -- Tests that should apply to and are identical for all grids
 --
@@ -61,10 +72,10 @@
   sort (delete a (neighbours g a)) ≡ sort expected
     where a = g `pointAt` i
           expected = map fst $ filter (\p → 1 ≡ snd p) $ viewpoint g a
--- Note: In a small grid, a tile can be its own neighbour. However, when
--- we calculate the distance between a tile and itself, we get 0, not 1.
--- That's why we have to delete the tile from its list before comparing 
--- to the result from the neighbours function.
+-- Note: In a small but unbounded grid, a tile can be its own neighbour.
+-- However, when we calculate the distance between a tile and itself, we
+-- get 0, not 1. That's why we have to delete the tile from its list 
+-- before comparing to the result from the neighbours function.
 
 prop_edges_cw_neighbours ∷ (Grid g, Ord (Index g)) ⇒ g → Int → Property
 prop_edges_cw_neighbours g i = nonNull g ==> 
@@ -182,9 +193,7 @@
 
 prop_TriTriGrid_neighbour_count_in_bounds ∷ TriTriGrid → Int → Property
 prop_TriTriGrid_neighbour_count_in_bounds g i = nonNull g ==>
-  if tileCount g ≡ 1
-    then length (neighbours g x) ≡ 0
-    else length (neighbours g x) `elem` [1,2,3]
+  neighbourCount g x ≤ 3
   where x = g `pointAt` i
 
 prop_TriTriGrid_boundary_count_correct ∷ TriTriGrid → Property
@@ -237,9 +246,7 @@
 
 prop_ParaTriGrid_neighbour_count_in_bounds ∷ ParaTriGrid → Int → Property
 prop_ParaTriGrid_neighbour_count_in_bounds g i = nonNull g ==>
-  if tileCount g ≡ 1
-    then length (neighbours g x) ≡ 0
-    else length (neighbours g x) `elem` [1,2,3]
+  neighbourCount g x ≤ 3
   where x = g `pointAt` i
 
 prop_ParaTriGrid_boundary_count_correct ∷ ParaTriGrid → Property
@@ -283,9 +290,7 @@
 
 prop_RectTriGrid_neighbour_count_in_bounds ∷ RectTriGrid → Int → Property
 prop_RectTriGrid_neighbour_count_in_bounds g i = nonNull g ==>
-  if tileCount g ≡ 1
-    then length (neighbours g x) ≡ 0
-    else length (neighbours g x) `elem` [1,2,3]
+  neighbourCount g x ≤ 3
   where x = g `pointAt` i
 
 prop_RectTriGrid_boundary_count_correct ∷ RectTriGrid → Property
@@ -330,9 +335,7 @@
 
 prop_TorTriGrid_neighbour_count_in_bounds ∷ TorTriGrid → Int → Property
 prop_TorTriGrid_neighbour_count_in_bounds g i = nonNull g ==>
-  if tileCount g ≡ 1
-    then length (neighbours g x) ≡ 0
-    else length (neighbours g x) `elem` [1,2,3]
+  neighbourCount g x ≤ 3
   where x = g `pointAt` i
 
 --
@@ -374,18 +377,14 @@
 
 prop_RectSquareGrid_neighbour_count_in_bounds ∷ 
   RectSquareGrid → Int → Property
-prop_RectSquareGrid_neighbour_count_in_bounds g i = nonNull g ==> f
+prop_RectSquareGrid_neighbour_count_in_bounds g i = nonNull g ==> 
+  neighbourCount g x ≤ 4
   where x = g `pointAt` i
-        neighbourCount = length (neighbours g x)
-        (r, c) = size g
-        f | tileCount g ≡ 1 = neighbourCount ≡ 0
-          | r ≡ 1 || c ≡ 1  = neighbourCount `elem` [1,2]
-          | otherwise       = neighbourCount `elem` [2,3,4]
 
 prop_RectSquareGrid_num_min_paths_correct ∷ 
   RectSquareGrid → Int → Int → Property
 prop_RectSquareGrid_num_min_paths_correct g i j = nonNull g ==>
-  length (minimalPaths g a b) ≡ M.choose (deltaX+deltaY) deltaX
+  minPathCount g a b ≡ M.choose (deltaX+deltaY) deltaX
     where a = g `pointAt` i
           b = g `pointAt` j
           deltaX = abs $ fst b - fst a
@@ -408,7 +407,7 @@
 
 
 --
--- Toroidal grids with square-ish tiles
+-- Toroidal grids with square tiles
 --
 
 -- We want the number of tiles in a test grid to be O(n)
@@ -442,21 +441,14 @@
           ps = indices g
           a = head ps
           b = last ps
-          f | r ≡ 1 && c ≡ 1 = 0 -- zero-size torus
+          f | r ≡ 1 && c ≡ 1 = 0 -- single-tile torus
             | r ≡ 1 || c ≡ 1 = 1 -- a and b are the same
             | otherwise      = 2
 
 prop_TorSquareGrid_neighbour_count_in_bounds ∷ TorSquareGrid → Int → Property
-prop_TorSquareGrid_neighbour_count_in_bounds g i = nonNull g ==> f
+prop_TorSquareGrid_neighbour_count_in_bounds g i = nonNull g ==>
+  neighbourCount g x ≤ 4
   where x = g `pointAt` i
-        neighbourCount = length . neighbours g $ x
-        (r, c) = size g
-        f | tileCount g ≡ 1                = neighbourCount ≡ 1
-          | (r,c) ≡ (1,2) || (r,c) ≡ (2,1) = neighbourCount ≡ 2
-          | (r,c) ≡ (2,2)                  = neighbourCount ≡ 2
-          | r ≡ 1 || c ≡ 1                 = neighbourCount ≡ 3
-          | r ≡ 2 || c ≡ 2                 = neighbourCount ≡ 3
-          | otherwise                      = neighbourCount ≡ 4
 
 --
 -- Circular hexagonal grids   
@@ -494,9 +486,7 @@
 
 prop_HexHexGrid_neighbour_count_in_bounds ∷ HexHexGrid → Int → Property
 prop_HexHexGrid_neighbour_count_in_bounds g i = nonNull g ==> 
-  if tileCount g ≡ 1
-    then length (neighbours g x) ≡ 0
-    else length (neighbours g x) `elem` [2,3,4,5,6]
+  neighbourCount g x ≤ 6
   where x = g `pointAt` i
 
 prop_HexHexGrid_boundary_count_correct ∷ HexHexGrid → Property
@@ -549,13 +539,9 @@
           (r, c) = size g
 
 prop_ParaHexGrid_neighbour_count_in_bounds ∷ ParaHexGrid → Int → Property
-prop_ParaHexGrid_neighbour_count_in_bounds g i = nonNull g ==> f
+prop_ParaHexGrid_neighbour_count_in_bounds g i = nonNull g ==>
+  neighbourCount g x ≤ 6
   where x = g `pointAt` i
-        neighbourCount = length (neighbours g x)
-        (r, c) = size g
-        f | tileCount g ≡ 1 = neighbourCount ≡ 0
-          | r ≡ 1 || c ≡ 1  = neighbourCount `elem` [1,2]
-          | otherwise       = neighbourCount `elem` [2,3,4,5,6]
 
 prop_ParaHexGrid_boundary_count_correct ∷ ParaHexGrid → Property
 prop_ParaHexGrid_boundary_count_correct g = property $
@@ -565,7 +551,110 @@
 prop_ParaHexGrid_boundary_tiles_have_fewer_neighbours g = property $
   all (5>) . map (numNeighbours g) . boundary $ g
 
+--
+-- Rectangular grids with octagonal tiles
+--
 
+-- We want the number of tiles in a test grid to be O(n)
+sizedRectOctGrid ∷ Int → Gen RectOctGrid
+sizedRectOctGrid n = do
+  let n' = min n 12 -- calculation time for these grids grows quickly!
+  r ← choose (0,n')
+  let c = n' `div` (r+1)
+  return $ rectOctGrid r c
+
+instance Arbitrary RectOctGrid where
+  arbitrary = sized sizedRectOctGrid
+
+prop_RectOctGrid_tile_count_correct ∷ RectOctGrid → Property
+prop_RectOctGrid_tile_count_correct g = property $ 
+  tileCount g ≡ if r ≤ 0 || c ≤ 0 then 0 else r*c
+    where (r, c) = size g
+
+prop_RectOctGrid_distance_in_bounds ∷ RectOctGrid → Int → Int → Property
+prop_RectOctGrid_distance_in_bounds g i j = nonNull g ==>
+  distance g a b ≤ max r c
+    where (r, c) = size g
+          a = g `pointAt` i
+          b = g `pointAt` j
+
+-- If the ordering produced by rectOctGrid is ever changed, this
+-- property may need to be changed too. It relies on the first and last 
+-- elements being at opposite corners.
+prop_RectOctGrid_distance_corner_to_corner ∷ RectOctGrid → Property
+prop_RectOctGrid_distance_corner_to_corner g = r > 0 && c > 0 ==> 
+  a ≡ b || distance g a b ≡ (max r c) - 1
+    where (r, c) = size g
+          ps = indices g
+          a = head ps
+          b = last ps
+
+prop_RectOctGrid_neighbour_count_in_bounds ∷ 
+  RectOctGrid → Int → Property
+prop_RectOctGrid_neighbour_count_in_bounds g i = nonNull g ==>
+  neighbourCount g x ≤ 8
+  where x = g `pointAt` i
+
+prop_RectOctGrid_num_min_paths_correct ∷ 
+  RectOctGrid → Int → Int → Property
+prop_RectOctGrid_num_min_paths_correct g i j = nonNull g ==>
+  minPathCount g a b ≡
+    if a ≡ b then 1 else minPathCount2 g att b
+    where a = g `pointAt` i
+          b = g `pointAt` j
+          att = adjacentTilesToward g a b
+
+prop_RectOctGrid_boundary_count_correct ∷ RectOctGrid → Property
+prop_RectOctGrid_boundary_count_correct g = property $
+  (length . boundary) g ≡ (cartesianBoundaryCount . size) g
+
+prop_RectOctGrid_boundary_tiles_have_fewer_neighbours ∷ RectOctGrid → Property
+prop_RectOctGrid_boundary_tiles_have_fewer_neighbours g = property $
+  all (6>) . map (numNeighbours g) . boundary $ g
+
+
+--
+-- Toroidal grids with octagonal tiles
+--
+
+-- We want the number of tiles in a test grid to be O(n)
+sizedTorOctGrid ∷ Int → Gen TorOctGrid
+sizedTorOctGrid n = do
+  r ← choose (0,n)
+  let c = n `div` (r+1)
+  return $ torOctGrid r c
+
+instance Arbitrary TorOctGrid where
+  arbitrary = sized sizedTorOctGrid
+
+prop_TorOctGrid_tile_count_correct ∷ TorOctGrid → Property
+prop_TorOctGrid_tile_count_correct g = property $  
+  tileCount g ≡ if r ≤ 0 || c ≤ 0 then 0 else r*c
+    where (r, c) = size g
+
+prop_TorOctGrid_distance_in_bounds ∷ TorOctGrid → Int → Int → Property
+prop_TorOctGrid_distance_in_bounds g i j = nonNull g ==>
+  distance g a b ≤ min r c + abs (r-c)
+    where (r, c) = size g
+          a = g `pointAt` i
+          b = g `pointAt` j
+
+-- If the ordering produced by torOctGrid is ever changed, this property
+-- may need to be changed too.
+prop_TorOctGrid_distance_corner_to_corner ∷ TorOctGrid → Property
+prop_TorOctGrid_distance_corner_to_corner g = r > 0 && c > 0 ==> 
+  distance g a b ≡ if tileCount g ≡ 1 then 0 else 1
+    where (r, c) = size g
+          ps = indices g
+          a = head ps
+          b = last ps
+
+prop_TorOctGrid_neighbour_count_in_bounds ∷ TorOctGrid → Int → Property
+prop_TorOctGrid_neighbour_count_in_bounds g i = nonNull g ==>
+  neighbourCount g x ≤ 8
+  where x = g `pointAt` i
+
+
 test ∷ Test
 test = testGroup "Math.Geometry.GridQC"
   [
@@ -601,17 +690,17 @@
     testProperty "prop_neighbours_cw_viewpoint - TriTriGrid"
       (prop_neighbours_cw_viewpoint ∷ TriTriGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - TriTriGrid"
-      ( prop_edges_cw_neighbours ∷ TriTriGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ TriTriGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - TriTriGrid"
-      ( prop_edges_are_adjacent ∷ TriTriGrid → Property),
+      (prop_edges_are_adjacent ∷ TriTriGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - TriTriGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           TriTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - TriTriGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           TriTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - TriTriGrid"
-      ( prop_minimal_paths_are_valid ∷ TriTriGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ TriTriGrid → Int → Int → Property),
 
     -- ParaTriGrid tests
     testProperty "prop_ParaTriGrid_tile_count_correct"
@@ -645,17 +734,17 @@
     testProperty "prop_neighbours_cw_viewpoint - ParaTriGrid"
       (prop_neighbours_cw_viewpoint ∷ ParaTriGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - ParaTriGrid"
-      ( prop_edges_cw_neighbours ∷ ParaTriGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ ParaTriGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - ParaTriGrid"
-      ( prop_edges_are_adjacent ∷ ParaTriGrid → Property),
+      (prop_edges_are_adjacent ∷ ParaTriGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - ParaTriGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           ParaTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - ParaTriGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           ParaTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - ParaTriGrid"
-      ( prop_minimal_paths_are_valid ∷ ParaTriGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ ParaTriGrid → Int → Int → Property),
 
     -- RectTriGrid tests
     testProperty "prop_RectTriGrid_tile_count_correct"
@@ -687,17 +776,17 @@
     testProperty "prop_neighbours_cw_viewpoint - RectTriGrid"
       (prop_neighbours_cw_viewpoint ∷ RectTriGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - RectTriGrid"
-      ( prop_edges_cw_neighbours ∷ RectTriGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ RectTriGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - RectTriGrid"
-      ( prop_edges_are_adjacent ∷ RectTriGrid → Property),
+      (prop_edges_are_adjacent ∷ RectTriGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - RectTriGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           RectTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - RectTriGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           RectTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - RectTriGrid"
-      ( prop_minimal_paths_are_valid ∷ RectTriGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ RectTriGrid → Int → Int → Property),
 
     -- TorTriGrid tests
     testProperty "prop_TorTriGrid_tile_count_correct"
@@ -715,17 +804,17 @@
     testProperty "prop_neighbours_cw_viewpoint - TorTriGrid"
       (prop_neighbours_cw_viewpoint ∷ TorTriGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - TorTriGrid"
-      ( prop_edges_cw_neighbours ∷ TorTriGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ TorTriGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - TorTriGrid"
-      ( prop_edges_are_adjacent ∷ TorTriGrid → Property),
+      (prop_edges_are_adjacent ∷ TorTriGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - TorTriGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           TorTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - TorTriGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           TorTriGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - TorTriGrid"
-      ( prop_minimal_paths_are_valid ∷ TorTriGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ TorTriGrid → Int → Int → Property),
 
     -- RectSquareGrid tests
     testProperty "prop_RectSquareGrid_tile_count_correct"
@@ -759,17 +848,17 @@
     testProperty "prop_neighbours_cw_viewpoint - RectSquareGrid"
       (prop_neighbours_cw_viewpoint ∷ RectSquareGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - RectSquareGrid"
-      ( prop_edges_cw_neighbours ∷ RectSquareGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ RectSquareGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - RectSquareGrid"
-      ( prop_edges_are_adjacent ∷ RectSquareGrid → Property),
+      (prop_edges_are_adjacent ∷ RectSquareGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - RectSquareGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           RectSquareGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - RectSquareGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           RectSquareGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - RectSquareGrid"
-      ( prop_minimal_paths_are_valid ∷ RectSquareGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ RectSquareGrid → Int → Int → Property),
     testProperty "prop_RectSquareGrid_num_min_paths_correct"
       prop_RectSquareGrid_num_min_paths_correct,
 
@@ -791,17 +880,17 @@
     testProperty "prop_neighbours_cw_viewpoint - TorSquareGrid"
       (prop_neighbours_cw_viewpoint ∷ TorSquareGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - TorSquareGrid"
-      ( prop_edges_cw_neighbours ∷ TorSquareGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ TorSquareGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - TorSquareGrid"
-      ( prop_edges_are_adjacent ∷ TorSquareGrid → Property),
+      (prop_edges_are_adjacent ∷ TorSquareGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - TorSquareGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           TorSquareGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - TorSquareGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           TorSquareGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - TorSquareGrid"
-      ( prop_minimal_paths_are_valid ∷ TorSquareGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ TorSquareGrid → Int → Int → Property),
 
     -- HexHexGrid tests
     testProperty "prop_HexHexGrid_tile_count_correct"
@@ -835,17 +924,17 @@
     testProperty "prop_neighbours_cw_viewpoint - HexHexGrid"
       (prop_neighbours_cw_viewpoint ∷ HexHexGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - HexHexGrid"
-      ( prop_edges_cw_neighbours ∷ HexHexGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ HexHexGrid → Int → Property),
     testProperty "prop_edges_are_adjacent - HexHexGrid"
-      ( prop_edges_are_adjacent ∷ HexHexGrid → Property),
+      (prop_edges_are_adjacent ∷ HexHexGrid → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - HexHexGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           HexHexGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_have_min_length - HexHexGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           HexHexGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - HexHexGrid"
-      ( prop_minimal_paths_are_valid ∷ HexHexGrid → Int → Int → Property),
+      (prop_minimal_paths_are_valid ∷ HexHexGrid → Int → Int → Property),
 
     -- ParaHexGrid tests
     testProperty "prop_ParaHexGrid_tile_count_correct"
@@ -879,16 +968,92 @@
     testProperty "prop_neighbours_cw_viewpoint - ParaHexGrid"
       (prop_neighbours_cw_viewpoint ∷ ParaHexGrid → Int → Property),
     testProperty "prop_edges_cw_neighbours - ParaHexGrid"
-      ( prop_edges_cw_neighbours ∷ ParaHexGrid → Int → Property),
+      (prop_edges_cw_neighbours ∷ ParaHexGrid → Int → Property),
     testProperty "prop_adjacentTilesToward_moves_closer - ParaHexGrid"
-      ( prop_adjacentTilesToward_moves_closer ∷ 
+      (prop_adjacentTilesToward_moves_closer ∷ 
           ParaHexGrid → Int → Int → Property),
     testProperty "prop_edges_are_adjacent - ParaHexGrid"
-      ( prop_edges_are_adjacent ∷ ParaHexGrid → Property),
+      (prop_edges_are_adjacent ∷ ParaHexGrid → Property),
     testProperty "prop_minimal_paths_have_min_length - ParaHexGrid"
-      ( prop_minimal_paths_have_min_length ∷ 
+      (prop_minimal_paths_have_min_length ∷ 
           ParaHexGrid → Int → Int → Property),
     testProperty "prop_minimal_paths_are_valid - ParaHexGrid"
-      ( prop_minimal_paths_are_valid ∷ ParaHexGrid → Int → Int → Property)
+      (prop_minimal_paths_are_valid ∷ ParaHexGrid → Int → Int → Property),
+
+    -- RectOctGrid tests
+    testProperty "prop_RectOctGrid_tile_count_correct"
+      prop_RectOctGrid_tile_count_correct,
+    testProperty "prop_distance_reflexive - RectOctGrid"
+      (prop_distance_reflexive ∷ RectOctGrid → Int → Property),
+    testProperty "prop_distance_symmetric - RectOctGrid"
+      (prop_distance_symmetric ∷ RectOctGrid → Int → Int → Property),
+    testProperty "prop_minDistance_cw_distance - RectOctGrid"
+      (prop_minDistance_cw_distance ∷ RectOctGrid → Int → [Int] → Property),
+    testProperty "prop_grid_and_boundary_are_both_null_or_not - RectOctGrid"
+      (prop_grid_and_boundary_are_both_null_or_not ∷ RectOctGrid → Property),
+    testProperty "prop_boundary_in_grid - RectOctGrid"
+      (prop_boundary_in_grid ∷ RectOctGrid → Property),
+    testProperty "prop_boundary_tiles_have_fewer_neighbours - RectOctGrid"
+      (prop_boundary_tiles_have_fewer_neighbours ∷ RectOctGrid → Int → Property),
+    testProperty "prop_RectOctGrid_boundary_count_correct"
+      prop_RectOctGrid_boundary_count_correct,
+    testProperty "prop_RectOctGrid_boundary_tiles_have_fewer_neighbours"
+      prop_RectOctGrid_boundary_tiles_have_fewer_neighbours,
+    testProperty "prop_centres_equidistant_from_boundary - RectOctGrid"
+      (prop_centres_equidistant_from_boundary ∷ RectOctGrid → Property),
+    testProperty "prop_centres_farthest_from_boundary - RectOctGrid"
+      (prop_centres_farthest_from_boundary ∷ RectOctGrid → Int → Property),
+    testProperty "prop_RectOctGrid_distance_in_bounds"
+      prop_RectOctGrid_distance_in_bounds,
+    testProperty "prop_RectOctGrid_distance_corner_to_corner"
+      prop_RectOctGrid_distance_corner_to_corner,
+    testProperty "prop_RectOctGrid_neighbour_count_in_bounds"
+      prop_RectOctGrid_neighbour_count_in_bounds,
+    testProperty "prop_neighbours_cw_viewpoint - RectOctGrid"
+      (prop_neighbours_cw_viewpoint ∷ RectOctGrid → Int → Property),
+    testProperty "prop_edges_cw_neighbours - RectOctGrid"
+      (prop_edges_cw_neighbours ∷ RectOctGrid → Int → Property),
+    testProperty "prop_edges_are_adjacent - RectOctGrid"
+      (prop_edges_are_adjacent ∷ RectOctGrid → Property),
+    testProperty "prop_adjacentTilesToward_moves_closer - RectOctGrid"
+      (prop_adjacentTilesToward_moves_closer ∷ 
+          RectOctGrid → Int → Int → Property),
+    testProperty "prop_minimal_paths_have_min_length - RectOctGrid"
+      (prop_minimal_paths_have_min_length ∷ 
+          RectOctGrid → Int → Int → Property),
+    testProperty "prop_minimal_paths_are_valid - RectOctGrid"
+      (prop_minimal_paths_are_valid ∷ RectOctGrid → Int → Int → Property),
+    testProperty "prop_RectOctGrid_num_min_paths_correct"
+      prop_RectOctGrid_num_min_paths_correct,
+
+    -- TorOctGrid tests
+    testProperty "prop_TorOctGrid_tile_count_correct"
+      prop_TorOctGrid_tile_count_correct,
+    testProperty "prop_distance_reflexive - TorOctGrid"
+      (prop_distance_reflexive ∷ TorOctGrid → Int → Property),
+    testProperty "prop_distance_symmetric - TorOctGrid"
+      (prop_distance_symmetric ∷ TorOctGrid → Int → Int → Property),
+    testProperty "prop_minDistance_cw_distance - TorOctGrid"
+      (prop_minDistance_cw_distance ∷ TorOctGrid → Int → [Int] → Property),
+    testProperty "prop_TorOctGrid_distance_in_bounds"
+      prop_TorOctGrid_distance_in_bounds,
+    testProperty "prop_TorOctGrid_distance_corner_to_corner"
+      prop_TorOctGrid_distance_corner_to_corner,
+    testProperty "prop_TorOctGrid_neighbour_count_in_bounds"
+      prop_TorOctGrid_neighbour_count_in_bounds,
+    testProperty "prop_neighbours_cw_viewpoint - TorOctGrid"
+      (prop_neighbours_cw_viewpoint ∷ TorOctGrid → Int → Property),
+    testProperty "prop_edges_cw_neighbours - TorOctGrid"
+      (prop_edges_cw_neighbours ∷ TorOctGrid → Int → Property),
+    testProperty "prop_edges_are_adjacent - TorOctGrid"
+      (prop_edges_are_adjacent ∷ TorOctGrid → Property),
+    testProperty "prop_adjacentTilesToward_moves_closer - TorOctGrid"
+      (prop_adjacentTilesToward_moves_closer ∷ 
+          TorOctGrid → Int → Int → Property),
+    testProperty "prop_minimal_paths_have_min_length - TorOctGrid"
+      (prop_minimal_paths_have_min_length ∷ 
+          TorOctGrid → Int → Int → Property),
+    testProperty "prop_minimal_paths_are_valid - TorOctGrid"
+      (prop_minimal_paths_are_valid ∷ TorOctGrid → Int → Int → Property)
  ]
 
