diff --git a/android/gradle.properties b/android/gradle.properties
index 85b94f88ee157e1d1b3cec184c8948902443d36f..65eed6426393974efb5a056ec44936d42b5ef2a1 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
-app.versionName=0.0.7
-app.versionCode=7
+app.versionName=0.0.8
+app.versionCode=8
diff --git a/assets/files/templates.json b/assets/files/templates.json
index d0312cf72a6cf310d52a43b2c10a4958036123c8..cd7ae73e952b2c251ac4e5addee7204f2c0ac351 100644
--- a/assets/files/templates.json
+++ b/assets/files/templates.json
@@ -2,43 +2,78 @@
   "templates": {
     "2x2": {
       "easy": [
-        "0000320400210100",
-        "0003034000040000",
-        "0402000000004020",
-        "0002001443002000",
-        "4312020000000020"
+        "0120040000010300",
+        "4000002030000401",
+        "4000030000010023",
+        "0100000410022300",
+        "0040000101343012",
+        "0000020334020000",
+        "0003130200200134",
+        "2000431000230000",
+        "0103304000000300",
+        "0043000000144030"
       ],
       "medium": [
-        "3012200003000000",
-        "0003104030002100",
-        "0410100400000030",
-        "0100000030120000"
+        "4000010000010034",
+        "3002040100031300",
+        "4000000302000010",
+        "0100300000020043",
+        "0000004040013000",
+        "1003000420000000",
+        "1000000302300002",
+        "2001010400430000",
+        "0401200300004000",
+        "3004042000002000"
       ],
       "hard": [
-        "0001000003000034",
-        "1000032000040002",
-        "0420000000100040",
-        "0300010010030240"
+        "0001000004200200",
+        "4030100000040000",
+        "0130021000002000",
+        "2000130000010020",
+        "0103300000010400",
+        "0000042000001004",
+        "0300000301044000",
+        "0001010002000003",
+        "0020020300010300",
+        "0410200010000200"
       ]
     },
     "3x3": {
       "easy": [
-        "402370008801000000009086400108020000200060009000090207006450100000000603700032905",
-        "000100035060203000020059041002008304000000000509600700450320010000401070780005000",
-        "000130000070960050596000001000700105380000079704009000800000243040083010000091000",
-        "000300000001006030384200100700083056600000003830610002008005649060400200000002000"
+        "004210007090083010000500200007050831910600740400000000006100000801007450050300000",
+        "740150820562400070000000006308002000106040932090700605901830067680001000437690018",
+        "798413000340002809251800047003060092070000008519728030127650980900281670860907501",
+        "061030480300100000408060000009401008832070904100093056000009002903748001704052830",
+        "807625419000038602056090837729050060508061973103800500070902041081047296092316780",
+        "046000510815400039709060800074000208082541060053000104060014025091002300007093000",
+        "305000000009020600742301085918043500030107020407080019803710096674009132200406700",
+        "090002015165097040078450693802914000000005309000003081014529876580706132006130054",
+        "608010307070650800409008000000083600965074003300000072104500730806307200030020064",
+        "046050900000600700825010430002470085150809640000060000900238000038000120200190860"
       ],
       "medium": [
-        "800050003004902700000340600305100800200030001001008409006093000003807200100060004",
-        "020000370500036029000200605004701006000000000600904100802009000950340007043000050",
-        "005060300091000005206400078000957010000000000080143000510004709900000620002030500",
-        "040006090080000320090570040005307900800090004009804200050089030073000010010700060"
+        "200960400006070900700000200000002040064090800002080307090000030038000502407100098",
+        "900500103010000005200001470825390604740000308000005702000219000060854020090063000",
+        "847300950900040003100008640004030001309804005000070406401785009000020004500000200",
+        "032070100048100090105300002001000050003000000200817400000783004000901000507426830",
+        "028000196506400700900816205069042000000078002702000000000591008100000000695004000",
+        "300000005006800100200005090002500000000107420718000003007008054460000200830640001",
+        "460100507915007000000000030000709002040000103000200050007024600000000780100675309",
+        "010039020000600041200000003360107002020003160000265370040076005100904700090010000",
+        "840063502100090003030702100204087300000420007080000009000076930050314200026000000",
+        "010805306004010800000000400057400008021073040040200710600000130000380004470026080"
       ],
       "hard": [
-        "010900700050000060006207900600700080320804096080006007005602300060000010008003040",
-        "000009850900600402007050001000000093861000724730000000100020600406003009092800000",
-        "064900100000020006107006090000050364030000010576040000090500201700090000005007680",
-        "008007010512000370470051000000705900000010000004306000000560098045000763030900100"
+        "300089050520600007679150300000390804090068000700010005000071030000000000000935701",
+        "016007000000040105000000020023900046840000900607300010002501000008000600100639000",
+        "001800200089006050000053000600004700090070000007200809002700604000002100304000020",
+        "005000001000002598021000000009006003010000045000018700030049806407000000090760010",
+        "060000000058093000934080700002650007800900020400010008009307106000000080040009075",
+        "092500730030000900004930100008320506607050020300000000000005810500070009000208050",
+        "400063001008507000000900070300000910890070000106005004001600402000058036500400000",
+        "000007800004003000000000092000000184007009030810300059009034010400600020756190000",
+        "000003040009020078060700000952604000040210000000000900300500780090001060006002010",
+        "040070620300800904062000700407080000000615040056040000784060009600100070900000000"
       ]
     }
   }
diff --git a/generator/batch.sh b/generator/batch.sh
new file mode 100755
index 0000000000000000000000000000000000000000..db347183e0300a15b2ef6d13a6e74bc31ad2a93e
--- /dev/null
+++ b/generator/batch.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+
+ALLOWED_SIZE_VALUES="2 3"
+ALLOWED_DIFFICULTY_VALUES="easy medium hard"
+GRIDS_COUNT=10
+
+for SIZE in ${ALLOWED_SIZE_VALUES}; do
+  echo "Size: ${SIZE}x${SIZE}"
+  for DIFFICULTY in ${ALLOWED_DIFFICULTY_VALUES}; do
+    echo "Difficulty: ${DIFFICULTY}"
+    for i in $(seq ${GRIDS_COUNT}); do
+      python ${CURRENT_DIR}/generate.py ${SIZE} ${DIFFICULTY} | tail -n 1
+    done
+  done
+done
diff --git a/generator/generate.py b/generator/generate.py
new file mode 100644
index 0000000000000000000000000000000000000000..62772326bce89f6af22d2c815882a14b0cb9daff
--- /dev/null
+++ b/generator/generate.py
@@ -0,0 +1,198 @@
+import sys
+from random import randint, shuffle
+
+if (len(sys.argv) != 3):
+  print('Usage: generate.py size difficulty')
+  print('size: [2|3]')
+  print('difficulty: [easy|medium|hard]')
+  exit()
+
+size, difficulty = sys.argv[1], sys.argv[2]
+
+size = int(size)
+
+if not size in [2, 3]:
+  print('wrong size given')
+  exit()
+
+if not difficulty in ['easy', 'medium', 'hard']:
+  print('wrong difficulty given')
+  exit()
+
+############################################################################
+
+# medium -> 5 (default) ; easy -> 1 ; hard -> 5
+difficultyLevel = 5;
+if difficulty == 'easy':
+   difficultyLevel = 1
+if difficulty == 'hard':
+   difficultyLevel = 10
+
+# draw grid (array style)
+def drawGrid(grid):
+  for row in range(len(grid)):
+    for col in range(len(grid[row])):
+      if grid[row][col] != 0:
+        sys.stdout.write(str(grid[row][col]))
+      else:
+        sys.stdout.write(' ')
+    sys.stdout.write('\n')
+  sys.stdout.write('\n')
+
+# draw grid (inline style)
+def drawGridInline(grid):
+  for row in range(len(grid)):
+    for col in range(len(grid[row])):
+      sys.stdout.write(str(grid[row][col]))
+  sys.stdout.write('\n')
+
+#initialise empty grid
+def generateEmptyGrid(size):
+  emptyGrid = []
+  for i in range(size * size):
+    emptyGrid.append([])
+    for j in range(size * size):
+      emptyGrid[i].append(0)
+  return emptyGrid
+
+#A check if the grid is full
+def checkFullyCompletedGrid(grid):
+  for row in range(len(grid)):
+    for col in range(len(grid[row])):
+      if grid[row][col] == 0:
+        return False
+  return True
+
+# (deep) copy of grid
+def copyGrid(grid):
+  copiedGrid = []
+  for row in range(len(grid)):
+     copiedGrid.append([])
+     for col in range(len(grid[row])):
+        copiedGrid[row].append(grid[row][col])
+  return copiedGrid
+
+#A backtracking/recursive function to check all possible combinations of numbers until a solution is found
+def solveGrid(grid):
+  gridSize = len(grid)
+  cellsCount = len(grid) * len(grid[0])
+  numberList = [(value + 1) for value in range(gridSize)]
+
+  global solutionsCount
+
+  #Find next empty cell
+  for i in range(0, cellsCount):
+    row = i // gridSize
+    col = i % gridSize
+    if grid[row][col] == 0:
+      shuffle(numberList)
+      for value in numberList:
+        #Check that this value has not already be used on this row
+        if not(value in grid[row]):
+          #Check that this value has not already be used on this column
+          foundInColumn = False
+          for r in range(0, gridSize):
+            if (value == grid[r][col]):
+              foundInColumn = True
+
+          if not foundInColumn:
+            # Get sub-square
+            blockColFrom = size * int(col / size)
+            blockRowFrom = size * int(row / size)
+            square = [grid[i][blockColFrom:blockColFrom + size] for i in range(blockRowFrom, blockRowFrom + size)]
+
+            #Check that this value has not already be used on this sub square
+            if not any(value in squareLine for squareLine in square):
+              grid[row][col] = value
+              if checkFullyCompletedGrid(grid):
+                solutionsCount += 1
+                break
+              else:
+                if solveGrid(grid):
+                  return True
+      break
+  grid[row][col] = 0
+
+#A backtracking/recursive function to check all possible combinations of numbers until a solution is found
+def fillGrid(grid):
+  gridSize = len(grid)
+  cellsCount = len(grid) * len(grid[0])
+  numberList = [(value + 1) for value in range(gridSize)]
+
+  global solutionsCount
+
+  #Find next empty cell
+  for i in range(0, cellsCount):
+    row = i // gridSize
+    col = i % gridSize
+    if grid[row][col] == 0:
+      shuffle(numberList)
+      for value in numberList:
+        #Check that this value has not already be used on this row
+        if not(value in grid[row]):
+          #Check that this value has not already be used on this column
+          foundInColumn = False
+          for r in range(0, gridSize):
+            if (value == grid[r][col]):
+              foundInColumn = True
+
+          if not foundInColumn:
+            # Get sub-square
+            blockColFrom = size * int(col / size)
+            blockRowFrom = size * int(row / size)
+            square = [grid[i][blockColFrom:blockColFrom + size] for i in range(blockRowFrom, blockRowFrom + size)]
+
+            #Check that this value has not already be used on this sub square
+            if not any(value in squareLine for squareLine in square):
+              grid[row][col] = value
+              if checkFullyCompletedGrid(grid):
+                return True
+              else:
+                if fillGrid(grid):
+                  return True
+      break
+  grid[row][col] = 0
+
+solutionsCount = 1
+def computeResolvableGrid(grid, wantedAttempts):
+  gridSize = size * size
+  global solutionsCount
+
+  # A higher number of attempts will end up removing more numbers from the grid
+  # Potentially resulting in more difficiult grids to solve!
+
+  # Start Removing Numbers one by one
+  attempts = wantedAttempts
+  while attempts > 0:
+    # Select a random cell that is not already empty
+    row = randint(0, gridSize - 1)
+    col = randint(0, gridSize - 1)
+    while grid[row][col] == 0:
+      row = randint(0, gridSize - 1)
+      col = randint(0, gridSize - 1)
+
+    # Remove value in this random cell
+    savedCellValue = grid[row][col]
+    grid[row][col] = 0
+
+    solutionsCount = 0
+    solveGrid(copyGrid(grid))
+
+    # Non unique solution => restore this cell value
+    if solutionsCount != 1:
+      grid[row][col] = savedCellValue
+      attempts -= 1
+
+grid = generateEmptyGrid(size)
+
+sys.stdout.write('Solved grid:\n')
+fillGrid(grid)
+drawGrid(grid)
+
+computeResolvableGrid(grid, difficultyLevel)
+
+sys.stdout.write('Generated grid:\n')
+drawGrid(grid)
+
+sys.stdout.write('Inline grid:\n')
+drawGridInline(grid)