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

Merge branch '64-add-improve-levels' into 'master'

Resolve "Add/improve levels"

Closes #64

See merge request !58
parents 9281d9b7 03f1dd39
No related branches found
No related tags found
1 merge request!58Resolve "Add/improve levels"
Pipeline #2691 passed
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
app.versionName=0.1.3
app.versionCode=52
app.versionName=0.1.4
app.versionCode=53
This diff is collapsed.
Improve grids generator, update levels
Amélioration du générateur de grilles, mise à jour des niveaux
#!/usr/bin/env bash
# This script should be run and redirected to a file like "batch.sh > grids.log"
# Then this grids file should be imported in "parse_grids.sh" to generate json
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
ALLOWED_BLOCK_SIZE_VALUES="2x2 3x2 3x3 4x4"
ALLOWED_DIFFICULTY_VALUES="easy medium hard nightmare"
GRIDS_COUNT=10
GRIDS_COUNT=40
for BLOCK_SIZE in ${ALLOWED_BLOCK_SIZE_VALUES}; do
CELLS_COUNT=$(echo "(${BLOCK_SIZE})^2" | sed 's/x/\*/g' | bc)
for DIFFICULTY in ${ALLOWED_DIFFICULTY_VALUES}; do
echo "Block size: ${BLOCK_SIZE} / Difficulty: ${DIFFICULTY}"
for i in $(seq ${GRIDS_COUNT}); do
# Generate grid candidate
GRID="$(python ${CURRENT_DIR}/generate.py ${BLOCK_SIZE} ${DIFFICULTY} | tail -n 1)"
# Ensure grid can be resolve without any assumption
# Ensure grid can be resolved 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}"
# Count "0" in grid, compute "emptiness" ratio
STRING="${GRID//[^0]}"
ZEROS_COUNT=$(echo "${#STRING}")
RATIO=$(echo "100*${ZEROS_COUNT}/${CELLS_COUNT}" | bc)
RATIO_STRING="$(printf "%02d" ${RATIO})"
echo "${BLOCK_SIZE} (${RATIO_STRING}%) ${DIFFICULTY} ${GRID}"
else
echo "FAILED: ${BLOCK_SIZE} ${DIFFICULTY} ${GRID}"
echo "FAILED / ${BLOCK_SIZE} ${DIFFICULTY}"
fi
done
done
......
This diff is collapsed.
#!/usr/bin/env bash
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# Generated grids (source)
GRIDS_FILE="$1"
# Game templates
GAME_TEMPLATES_FILE="${CURRENT_DIR}/../assets/files/templates.json"
# Parameters
ALLOWED_BLOCK_SIZE_VALUES="2x2 3x2 3x3 4x4"
MAX_GRIDS_COUNT_PER_LEVEL=20
# Fetch grid from input file
VALID_GRIDS="$(cat "${GRIDS_FILE}" | grep -v "FAILED" | grep "0" | sort | sed 's/ /_/g')"
OUTPUT="{\"templates\":{"
FIRST_BLOCK=1
function clean_grids() {
GRIDS_TO_CLEAN="$1"
echo "${GRIDS_TO_CLEAN}" | cut -d"_" -f4 | sed 's/^/"/' | sed 's/$/"/' | tr '\n' ',' | sed 's/,$//'
}
for BLOCK_SIZE in ${ALLOWED_BLOCK_SIZE_VALUES}; do
GRIDS=$(echo "${VALID_GRIDS}" | grep "${BLOCK_SIZE}");
GRIDS_COUNT=$(echo "${GRIDS}" | wc -l | awk '{print $1}')
echo "${BLOCK_SIZE}: found ${GRIDS_COUNT} grids"
# example with 100 grids, 10 per level:
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# | <- 1/3 -> | <- 1/3 -> | <- 1/3 -> |
# | | | |
# 1111111111------------------2222222222-----------------------3333333333-------------------4444444444
# | 10 | | 10 | | 10 | | 10 |
# easy
GRIDS_LEVEL_1="$(echo "${GRIDS}" | head -n ${MAX_GRIDS_COUNT_PER_LEVEL})"
# medium
GRIDS_LEVEL_2="$(echo "${GRIDS}" | head -n $(echo "${GRIDS_COUNT}/3 - ${MAX_GRIDS_COUNT_PER_LEVEL}/2" | bc) | tail -n ${MAX_GRIDS_COUNT_PER_LEVEL})"
# hard
GRIDS_LEVEL_3="$(echo "${GRIDS}" | tail -n $(echo "${GRIDS_COUNT}/3 + ${MAX_GRIDS_COUNT_PER_LEVEL}/2" | bc) | head -n ${MAX_GRIDS_COUNT_PER_LEVEL})"
# nightmare
GRIDS_LEVEL_4="$(echo "${GRIDS}" | tail -n ${MAX_GRIDS_COUNT_PER_LEVEL})"
if [ $FIRST_BLOCK -eq 1 ]; then
FIRST_BLOCK=0
else
OUTPUT="${OUTPUT},"
fi
OUTPUT="${OUTPUT} \"${BLOCK_SIZE}\": {"
OUTPUT="${OUTPUT} \"easy\": [$(clean_grids "${GRIDS_LEVEL_1}")],"
OUTPUT="${OUTPUT} \"medium\": [$(clean_grids "${GRIDS_LEVEL_2}")],"
OUTPUT="${OUTPUT} \"hard\": [$(clean_grids "${GRIDS_LEVEL_3}")],"
OUTPUT="${OUTPUT} \"nightmare\": [$(clean_grids "${GRIDS_LEVEL_4}")]"
OUTPUT="${OUTPUT} }"
done
OUTPUT="${OUTPUT} }}"
echo ${OUTPUT} | jq > "${GAME_TEMPLATES_FILE}"
echo "Ok, done. Grids saved in ${GAME_TEMPLATES_FILE}"
......@@ -12,18 +12,17 @@
#
import sys
import math
if (len(sys.argv) != 3):
print('Usage: solve.py block-size grid')
print('block-size: [2x2|3x2|3x3|4x4]')
exit()
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()
if blocksize not in ['2x2', '3x2', '3x3', '4x4']:
print('wrong size given')
exit()
splitted_blocksize = blocksize.split('x')
size_horizontal = int(splitted_blocksize[0])
......@@ -32,196 +31,206 @@ 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()
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')
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')
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
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
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
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))
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 = []
# Check horizontal conflicts
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
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
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
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
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
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)
iterations = 0
maxIterations = 500
# Loop until grid is fully completed
while True:
iterations += 1
if isFullyCompleted(grid) or (iterations > maxIterations):
break
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)
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')
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')
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')
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