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

Improve grids generator, update levels

parent 9281d9b7
No related branches found
No related tags found
1 merge request!58Resolve "Add/improve levels"
Pipeline #2514 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.
Please register or to comment