Skip to content
Snippets Groups Projects
Commit 26f63c02 authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Ensure each grid template can be resolved, update templates, add solve script

parent 737c651a
No related branches found
No related tags found
1 merge request!37Resolve "Ensure all templates can be resolved, add solver script, fix templates"
Pipeline #1737 passed
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=0.0.33 app.versionName=0.0.34
app.versionCode=33 app.versionCode=34
This diff is collapsed.
Ensure each grid template can be resolved, update templates, add solve script
Vérification que chaque modèle peut être résolu, mise à jour des modèles, ajout d'un script (naïf) de résolution
...@@ -7,11 +7,18 @@ ALLOWED_DIFFICULTY_VALUES="easy medium hard nightmare" ...@@ -7,11 +7,18 @@ ALLOWED_DIFFICULTY_VALUES="easy medium hard nightmare"
GRIDS_COUNT=10 GRIDS_COUNT=10
for BLOCK_SIZE in ${ALLOWED_BLOCK_SIZE_VALUES}; do for BLOCK_SIZE in ${ALLOWED_BLOCK_SIZE_VALUES}; do
echo "Block size: ${BLOCK_SIZE}"
for DIFFICULTY in ${ALLOWED_DIFFICULTY_VALUES}; do for DIFFICULTY in ${ALLOWED_DIFFICULTY_VALUES}; do
echo "Difficulty: ${DIFFICULTY}" echo "Block size: ${BLOCK_SIZE} / Difficulty: ${DIFFICULTY}"
for i in $(seq ${GRIDS_COUNT}); do for i in $(seq ${GRIDS_COUNT}); do
python ${CURRENT_DIR}/generate.py ${BLOCK_SIZE} ${DIFFICULTY} | tail -n 1 # Generate grid candidate
GRID="$(python ${CURRENT_DIR}/generate.py ${BLOCK_SIZE} ${DIFFICULTY} | tail -n 1)"
# Ensure grid can be resolve without any assumption
CAN_BE_SOLVED="$(python ${CURRENT_DIR}/solve.py ${BLOCK_SIZE} ${GRID} | tail -n 1)"
if [ "${CAN_BE_SOLVED}" == "Ok" ]; then
echo "${BLOCK_SIZE} ${DIFFICULTY} ${GRID}"
else
echo "FAILED: ${BLOCK_SIZE} ${DIFFICULTY} ${GRID}"
fi
done done
done done
done done
#!/usr/bin/python
# -*- coding: iso-8859-15 -*-
#
# Naive sudoku grid solver
#
# Will try to solve sudoku grid without making any assumption
# -> pick and fill first cell where only one value is allowed
# and loop until grid is filled
#
# Will fail if solution needs any assumption on a cell value
#
import sys
import math
if (len(sys.argv) != 3):
print('Usage: solve.py block-size grid')
print('block-size: [2x2|3x2|3x3|4x4]')
exit()
blocksize, gridTemplate = sys.argv[1], sys.argv[2]
if not blocksize in ['2x2', '3x2', '3x3', '4x4']:
print('wrong size given')
exit()
splitted_blocksize = blocksize.split('x')
size_horizontal = int(splitted_blocksize[0])
size_vertical = int(splitted_blocksize[1])
boardSize = size_horizontal * size_vertical
if (len(gridTemplate) != boardSize * boardSize):
print('wrong grid length (should be '+str(boardSize * boardSize)+')')
exit()
debugSolveGrid = False
############################################################################
sys.stdout.write('Will solve grid: ['+str(size_horizontal)+'x'+str(size_vertical)+'] // '+gridTemplate+'\n')
stringValues = '0123456789ABCDEFG'
# draw grid (array style)
def drawGrid(grid):
gridVerticalSize = len(grid)
gridHorizontalSize = len(grid[0])
horizontalLineLength = ((size_horizontal + 1) * size_vertical) + 1
for row in range(gridHorizontalSize):
if ((row % size_vertical) == 0):
sys.stdout.write(('' * horizontalLineLength) + '\n')
for col in range(gridVerticalSize):
if ((col % size_horizontal) == 0):
sys.stdout.write('')
if grid[row][col] != 0:
sys.stdout.write(stringValues[grid[row][col]])
else:
sys.stdout.write(' ')
sys.stdout.write('\n')
sys.stdout.write(('' * horizontalLineLength) + '\n')
sys.stdout.write('\n')
# (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
# Init grid from given template
def initGrid(boardSize, gridTemplate):
grid = []
index = 0
for row in range(boardSize):
grid.append([])
for col in range(boardSize):
grid[row].append(stringValues.index(gridTemplate[index]))
index += 1
return grid
# Check if grid is fully completed, without any empty cell
def isFullyCompleted(grid):
for row in range(len(grid)):
for col in range(len(grid[row])):
if grid[row][col] == 0:
return False
return True
# Check if a list contains duplicates (conflicts)
def containsDuplicates(list):
tmp_set = set(list)
return (len(list) != len(tmp_set))
# Check if given grid contains conflicts
def hasConflict(grid, size_horizontal, size_vertical):
# Check horizontal conflicts
for row in range(len(grid)):
values = []
for col in range(len(grid[row])):
value = grid[row][col]
if value != 0:
values.append(value)
if containsDuplicates(values):
# print('Horizontal conflict found')
return True
# Check vertical conflicts
for col in range(len(grid[0])):
values = []
for row in range(len(grid)):
value = grid[row][col]
if value != 0:
values.append(value)
if containsDuplicates(values):
# print('Vertical conflict found')
return True
# Check sub-blocks conflicts
for blockRow in range(size_horizontal):
for blockCol in range(size_vertical):
# Get sub-square
blockColFrom = size_horizontal * int(col / size_horizontal)
blockRowFrom = size_vertical * int(row / size_vertical)
values = []
for rowInBlock in range(size_vertical):
for colInBlock in range(size_horizontal):
row = (blockRow * size_vertical) + rowInBlock;
col = (blockCol * size_horizontal) + colInBlock;
value = grid[row][col]
if value != 0:
values.append(value)
if containsDuplicates(values):
# print('Sub-block conflict found')
return True
return False
# Check if a value is allowed in a cell (without conflicting)
def isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue):
testGrid = copyGrid(grid)
testGrid[row][col] = candidateValue
if not hasConflict(testGrid, size_horizontal, size_vertical):
return True
return False
# Get allowed values in a cell (witjout conflicting)
def findAllowedValuesForCell(grid, size_horizontal, size_vertical, row, col):
allowedValues = []
if not hasConflict(grid, size_horizontal, size_vertical):
for candidateValue in range(1, size_horizontal * size_vertical + 1):
if isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue):
allowedValues.append(candidateValue)
return allowedValues
# Globally solve grid
def solve(grid, size_horizontal, size_vertical):
iterations = 0
maxIterations = 500
boardSize = size_horizontal * size_vertical
# Loop until grid is fully completed
while True:
iterations += 1
if isFullyCompleted(grid) or (iterations > maxIterations):
break
if debugSolveGrid:
print('===================================')
print('Iteration: '+str(iterations))
# Get first/next cell with only one allowed value
candidates = []
if debugSolveGrid:
print('Searching for empty cells...')
for row in range(len(grid)):
for col in range(len(grid[row])):
if grid[row][col] == 0:
if debugSolveGrid:
print('Found empty cell ['+str(col)+','+str(row)+']')
candidates.append([row,col])
if len(candidates):
for candidate in candidates:
candidateRow = candidate[0]
candidateCol = candidate[1]
allowedValues = findAllowedValuesForCell(grid, size_horizontal, size_vertical, candidateRow, candidateCol)
if debugSolveGrid:
print('Allowed values for cell ['+str(candidateCol)+','+str(candidateRow)+']: '+str(allowedValues))
if len(allowedValues) != 1:
if debugSolveGrid:
print(' Non unique allowed value for cell. Skip to next cell')
else:
value = allowedValues[0]
grid[candidateRow][candidateCol] = value
if debugSolveGrid:
print(' Found unique allowed value for cell ['+str(candidateCol)+','+str(candidateRow)+']: '+str(value))
drawGrid(grid)
#########################
sys.stdout.write('Building start grid:\n')
grid = initGrid(boardSize, gridTemplate)
drawGrid(grid)
sys.stdout.write('Checking grid:\n')
if hasConflict(grid, size_horizontal, size_vertical):
sys.stdout.write(' - oups, initial conflict found.\n')
else:
sys.stdout.write(' - ok, no initial conflict found.\n')
sys.stdout.write('\n')
sys.stdout.write('Solving grid...\n')
solve(grid, size_horizontal, size_vertical)
if isFullyCompleted(grid):
sys.stdout.write('Ok, solved grid:\n')
drawGrid(grid)
sys.stdout.write('Ok\n')
else:
sys.stdout.write('Failed to solve grid\n')
#!/usr/bin/env bash
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# Fetch grid from game templates
GRIDS="$(cat "${CURRENT_DIR}/../assets/files/templates.json" | grep "0" | sed 's/"//g' | sed 's/,//g')"
function get_block_size() {
BLOCK_SIZE=""
GRID_LENGTH="$(echo -n "${GRID}" | wc -c)"
if [ "${GRID_LENGTH}" == "16" ]; then
BLOCK_SIZE="2x2"
fi
if [ "${GRID_LENGTH}" == "36" ]; then
BLOCK_SIZE="3x2"
fi
if [ "${GRID_LENGTH}" == "81" ]; then
BLOCK_SIZE="3x3"
fi
if [ "${GRID_LENGTH}" == "256" ]; then
BLOCK_SIZE="4x4"
fi
echo "${BLOCK_SIZE}"
}
for GRID in ${GRIDS}; do
BLOCK_SIZE="$(get_block_size "${GRID}")"
echo "GRID: ${BLOCK_SIZE} / ${GRID}"
python ${CURRENT_DIR}/solve.py ${BLOCK_SIZE} ${GRID} | tail -n 1
done
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment