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 org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=0.1.3 app.versionName=0.1.4
app.versionCode=52 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 #!/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)" CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
ALLOWED_BLOCK_SIZE_VALUES="2x2 3x2 3x3 4x4" ALLOWED_BLOCK_SIZE_VALUES="2x2 3x2 3x3 4x4"
ALLOWED_DIFFICULTY_VALUES="easy medium hard nightmare" ALLOWED_DIFFICULTY_VALUES="easy medium hard nightmare"
GRIDS_COUNT=10 GRIDS_COUNT=40
for BLOCK_SIZE in ${ALLOWED_BLOCK_SIZE_VALUES}; do 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 for DIFFICULTY in ${ALLOWED_DIFFICULTY_VALUES}; do
echo "Block size: ${BLOCK_SIZE} / Difficulty: ${DIFFICULTY}" echo "Block size: ${BLOCK_SIZE} / Difficulty: ${DIFFICULTY}"
for i in $(seq ${GRIDS_COUNT}); do for i in $(seq ${GRIDS_COUNT}); do
# Generate grid candidate # Generate grid candidate
GRID="$(python ${CURRENT_DIR}/generate.py ${BLOCK_SIZE} ${DIFFICULTY} | tail -n 1)" 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)" CAN_BE_SOLVED="$(python ${CURRENT_DIR}/solve.py ${BLOCK_SIZE} ${GRID} | tail -n 1)"
if [ "${CAN_BE_SOLVED}" == "Ok" ]; then 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 else
echo "FAILED: ${BLOCK_SIZE} ${DIFFICULTY} ${GRID}" echo "FAILED / ${BLOCK_SIZE} ${DIFFICULTY}"
fi fi
done done
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 @@ ...@@ -12,18 +12,17 @@
# #
import sys import sys
import math
if (len(sys.argv) != 3): if (len(sys.argv) != 3):
print('Usage: solve.py block-size grid') print('Usage: solve.py block-size grid')
print('block-size: [2x2|3x2|3x3|4x4]') print('block-size: [2x2|3x2|3x3|4x4]')
exit() exit()
blocksize, gridTemplate = sys.argv[1], sys.argv[2] blocksize, gridTemplate = sys.argv[1], sys.argv[2]
if not blocksize in ['2x2', '3x2', '3x3', '4x4']: if blocksize not in ['2x2', '3x2', '3x3', '4x4']:
print('wrong size given') print('wrong size given')
exit() exit()
splitted_blocksize = blocksize.split('x') splitted_blocksize = blocksize.split('x')
size_horizontal = int(splitted_blocksize[0]) size_horizontal = int(splitted_blocksize[0])
...@@ -32,196 +31,206 @@ size_vertical = int(splitted_blocksize[1]) ...@@ -32,196 +31,206 @@ size_vertical = int(splitted_blocksize[1])
boardSize = size_horizontal * size_vertical boardSize = size_horizontal * size_vertical
if (len(gridTemplate) != boardSize * boardSize): if (len(gridTemplate) != boardSize * boardSize):
print('wrong grid length (should be '+str(boardSize * boardSize)+')') print('wrong grid length (should be ' + str(boardSize * boardSize) + ')')
exit() exit()
debugSolveGrid = False 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' stringValues = '0123456789ABCDEFG'
# draw grid (array style) # draw grid (array style)
def drawGrid(grid): def drawGrid(grid):
gridVerticalSize = len(grid) gridVerticalSize = len(grid)
gridHorizontalSize = len(grid[0]) gridHorizontalSize = len(grid[0])
horizontalLineLength = ((size_horizontal + 1) * size_vertical) + 1 horizontalLineLength = ((size_horizontal + 1) * size_vertical) + 1
for row in range(gridHorizontalSize): for row in range(gridHorizontalSize):
if ((row % size_vertical) == 0): if ((row % size_vertical) == 0):
sys.stdout.write(('' * horizontalLineLength) + '\n') sys.stdout.write(('' * horizontalLineLength) + '\n')
for col in range(gridVerticalSize): for col in range(gridVerticalSize):
if ((col % size_horizontal) == 0): if ((col % size_horizontal) == 0):
sys.stdout.write('') sys.stdout.write('')
if grid[row][col] != 0: if grid[row][col] != 0:
sys.stdout.write(stringValues[grid[row][col]]) sys.stdout.write(stringValues[grid[row][col]])
else: else:
sys.stdout.write(' ') sys.stdout.write(' ')
sys.stdout.write('\n') sys.stdout.write('\n')
sys.stdout.write(('' * horizontalLineLength) + '\n') sys.stdout.write(('' * horizontalLineLength) + '\n')
sys.stdout.write('\n') sys.stdout.write('\n')
# (deep) copy of grid # (deep) copy of grid
def copyGrid(grid): def copyGrid(grid):
copiedGrid = [] copiedGrid = []
for row in range(len(grid)): for row in range(len(grid)):
copiedGrid.append([]) copiedGrid.append([])
for col in range(len(grid[row])): for col in range(len(grid[row])):
copiedGrid[row].append(grid[row][col]) copiedGrid[row].append(grid[row][col])
return copiedGrid return copiedGrid
# Init grid from given template # Init grid from given template
def initGrid(boardSize, gridTemplate): def initGrid(boardSize, gridTemplate):
grid = [] grid = []
index = 0 index = 0
for row in range(boardSize): for row in range(boardSize):
grid.append([]) grid.append([])
for col in range(boardSize): for col in range(boardSize):
grid[row].append(stringValues.index(gridTemplate[index])) grid[row].append(stringValues.index(gridTemplate[index]))
index += 1 index += 1
return grid return grid
# Check if grid is fully completed, without any empty cell # Check if grid is fully completed, without any empty cell
def isFullyCompleted(grid): def isFullyCompleted(grid):
for row in range(len(grid)): for row in range(len(grid)):
for col in range(len(grid[row])): for col in range(len(grid[row])):
if grid[row][col] == 0: if grid[row][col] == 0:
return False return False
return True return True
# Check if a list contains duplicates (conflicts) # Check if a list contains duplicates (conflicts)
def containsDuplicates(list): def containsDuplicates(list):
tmp_set = set(list) tmp_set = set(list)
return (len(list) != len(tmp_set)) return (len(list) != len(tmp_set))
# Check if given grid contains conflicts # Check if given grid contains conflicts
def hasConflict(grid, size_horizontal, size_vertical): def hasConflict(grid, size_horizontal, size_vertical):
# Check horizontal conflicts # 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)): for row in range(len(grid)):
value = grid[row][col] values = []
if value != 0: for col in range(len(grid[row])):
values.append(value) value = grid[row][col]
if containsDuplicates(values): if value != 0:
# print('Vertical conflict found') values.append(value)
return True if containsDuplicates(values):
# print('Horizontal conflict found')
# Check sub-blocks conflicts return True
for blockRow in range(size_horizontal):
for blockCol in range(size_vertical): # Check vertical conflicts
# Get sub-square for col in range(len(grid[0])):
blockColFrom = size_horizontal * int(col / size_horizontal) values = []
blockRowFrom = size_vertical * int(row / size_vertical) for row in range(len(grid)):
values = [] value = grid[row][col]
for rowInBlock in range(size_vertical): if value != 0:
for colInBlock in range(size_horizontal): values.append(value)
row = (blockRow * size_vertical) + rowInBlock; if containsDuplicates(values):
col = (blockCol * size_horizontal) + colInBlock; # print('Vertical conflict found')
value = grid[row][col] return True
if value != 0:
values.append(value) # Check sub-blocks conflicts
if containsDuplicates(values): for blockRow in range(size_horizontal):
# print('Sub-block conflict found') for blockCol in range(size_vertical):
return True # 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) # Check if a value is allowed in a cell (without conflicting)
def isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue): def isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue):
testGrid = copyGrid(grid) testGrid = copyGrid(grid)
testGrid[row][col] = candidateValue testGrid[row][col] = candidateValue
if not hasConflict(testGrid, size_horizontal, size_vertical): if not hasConflict(testGrid, size_horizontal, size_vertical):
return True return True
return False return False
# Get allowed values in a cell (witjout conflicting) # Get allowed values in a cell (witjout conflicting)
def findAllowedValuesForCell(grid, size_horizontal, size_vertical, row, col): def findAllowedValuesForCell(grid, size_horizontal, size_vertical, row, col):
allowedValues = [] allowedValues = []
if not hasConflict(grid, size_horizontal, size_vertical): if not hasConflict(grid, size_horizontal, size_vertical):
for candidateValue in range(1, size_horizontal * size_vertical + 1): for candidateValue in range(1, size_horizontal * size_vertical + 1):
if isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue): if isValueAllowed(grid, size_horizontal, size_vertical, row, col, candidateValue):
allowedValues.append(candidateValue) allowedValues.append(candidateValue)
return allowedValues return allowedValues
# Globally solve grid # Globally solve grid
def solve(grid, size_horizontal, size_vertical): def solve(grid, size_horizontal, size_vertical):
iterations = 0 iterations = 0
maxIterations = 500 maxIterations = 500
boardSize = size_horizontal * size_vertical
# Loop until grid is fully completed
# Loop until grid is fully completed while True:
while True: iterations += 1
iterations += 1 if isFullyCompleted(grid) or (iterations > maxIterations):
if isFullyCompleted(grid) or (iterations > maxIterations): break
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: if debugSolveGrid:
print('Allowed values for cell ['+str(candidateCol)+','+str(candidateRow)+']: '+str(allowedValues)) print('===================================')
if len(allowedValues) != 1: print('Iteration: ' + str(iterations))
if debugSolveGrid:
print(' Non unique allowed value for cell. Skip to next cell') # Get first/next cell with only one allowed value
else: candidates = []
value = allowedValues[0] if debugSolveGrid:
grid[candidateRow][candidateCol] = value print('Searching for empty cells...')
if debugSolveGrid: for row in range(len(grid)):
print(' Found unique allowed value for cell ['+str(candidateCol)+','+str(candidateRow)+']: '+str(value)) for col in range(len(grid[row])):
drawGrid(grid) 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') sys.stdout.write('Building start grid:\n')
grid = initGrid(boardSize, gridTemplate) grid = initGrid(boardSize, gridTemplate)
drawGrid(grid) drawGrid(grid)
sys.stdout.write('Checking grid:\n') sys.stdout.write('Checking grid:\n')
if hasConflict(grid, size_horizontal, size_vertical): if hasConflict(grid, size_horizontal, size_vertical):
sys.stdout.write(' - oups, initial conflict found.\n') sys.stdout.write(' - oups, initial conflict found.\n')
else: else:
sys.stdout.write(' - ok, no initial conflict found.\n') sys.stdout.write(' - ok, no initial conflict found.\n')
sys.stdout.write('\n')
sys.stdout.write('\n')
sys.stdout.write('Solving grid...\n')
sys.stdout.write('Solving grid...\n') solve(grid, size_horizontal, size_vertical)
solve(grid, size_horizontal, size_vertical)
if isFullyCompleted(grid):
if isFullyCompleted(grid): sys.stdout.write('Ok, solved grid:\n')
sys.stdout.write('Ok, solved grid:\n') drawGrid(grid)
drawGrid(grid) sys.stdout.write('Ok\n')
sys.stdout.write('Ok\n') else:
else: sys.stdout.write('Failed to solve grid\n')
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