aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarianne Chevrot <blackmoor+git@devys.org>2020-05-05 20:49:29 +0200
committerMarianne Chevrot <blackmoor+git@devys.org>2020-05-05 20:56:47 +0200
commit4e9ad30ac8a1bc35ed2c2548793d70579e30732e (patch)
tree23caed3a1c429715aa3103d720063f3745407a8a
parent7a077a93c8ca55776302042fd9e526e7c09559ef (diff)
downloadkana_quest_solver-4e9ad30ac8a1bc35ed2c2548793d70579e30732e.tar.gz
kana_quest_solver-4e9ad30ac8a1bc35ed2c2548793d70579e30732e.tar.bz2
kana_quest_solver-4e9ad30ac8a1bc35ed2c2548793d70579e30732e.zip
Add arrows kana type and improved grid repr
-rwxr-xr-xsolver.py112
-rw-r--r--tests_solver/level_test_ar_x.yaml8
-rw-r--r--tests_solver/level_test_type.yaml9
-rw-r--r--tests_solver/level_test_type_short.yaml9
-rw-r--r--tests_solver/test_solver.py141
5 files changed, 231 insertions, 48 deletions
diff --git a/solver.py b/solver.py
index ff53960..b21ea88 100755
--- a/solver.py
+++ b/solver.py
@@ -18,18 +18,22 @@ import zlib
import docopt
import yaml
-
class Kana:
+ types_without_kana = ('void', 'empt')
+ types_valid_for_chain = ('norm', 'froz', 'rock', 'ar_u', 'ar_l', 'ar_d', 'ar_r')
+ types_with_kana = types_valid_for_chain + ('myst', )
+ types_all = types_with_kana + types_without_kana
+
def __init__(self, type_name, kana=None):
self.type_name = type_name
self.kana = kana
#print(type_name)
#print(kana)
- assert type_name in ('void', 'norm', 'empt', 'froz', 'rock', 'myst')
- if type_name in ('norm', 'rock', 'froz', 'myst'):
- assert kana[0] in ('k', 's', 'n')
+ assert type_name in self.types_all
+ if type_name in self.types_with_kana:
+ assert kana[0] in ('k', 's', 'n', 'h', 't', 'm')
assert kana[1] in ('a', 'i', 'u', 'e', 'o')
def __repr__(self):
@@ -38,27 +42,11 @@ class Kana:
def __eq__(self, other):
return self.type_name == other.type_name and self.kana == other.kana
-kana_void = Kana("void")
-
-
-def is_swappable(kana1, kana2):
- table_ok = {
- "norm": ("norm", "empt", "froz"),
- "froz": ("norm", "empt"),
- }
- if kana1.type_name in table_ok:
- if kana2.type_name in table_ok[kana1.type_name]:
- return True
- elif kana2.type_name in table_ok:
- if kana1.type_name in table_ok[kana2.type_name]:
- return True
- return False
+kana_void = Kana('void')
class KanaGrid:
- valid_types_for_chain = ("froz", "norm", "rock")
-
def __init__(self, size, grid, action_count=0, score=0, parent=None):
self.width = size[0]
@@ -77,13 +65,43 @@ class KanaGrid:
parent=self.parent,
)
+ def is_swappable(self, pos1, pos2):
+ kana1 = self.get_kana(pos1)
+ kana2 = self.get_kana(pos2)
+ table_ok = {
+ 'norm': ('norm', 'empt', 'froz', 'ar_u', 'ar_r', 'ar_d', 'ar_l'),
+ 'empt': ('norm', 'froz', 'ar_u', 'ar_r', 'ar_d', 'ar_l'),
+ 'froz': ('norm', 'empt', 'ar_u', 'ar_r', 'ar_d', 'ar_l'),
+ 'ar_u': ('norm', 'empt', 'froz', 'ar_d' ),
+ 'ar_r': ('norm', 'empt', 'froz', 'ar_l'),
+ 'ar_d': ('norm', 'empt', 'froz', 'ar_u' ),
+ 'ar_l': ('norm', 'empt', 'froz', 'ar_r' ),
+ }
+ if kana1.type_name in table_ok:
+ if kana2.type_name in table_ok[kana1.type_name]:
+ ar_vect_ok = {
+ 'ar_u': ( 0, -1),
+ 'ar_l': (-1, 0),
+ 'ar_d': ( 0, 1),
+ 'ar_r': ( 1, 0),
+ }
+ vect1_target = ar_vect_ok.get(kana1.type_name, None)
+ vect2_target = ar_vect_ok.get(kana2.type_name, None)
+ if vect1_target or vect2_target:
+ vect1 = (pos2[0] - pos1[0], pos2[1] - pos1[1])
+ vect2 = (pos1[0] - pos2[0], pos1[1] - pos2[1])
+ if vect1 != vect1_target and vect2 != vect2_target:
+ return False
+ return True
+ return False
+
def action(self, pos, action_type):
- kana = self.get_kana(pos)
if action_type == "reveal":
- if kana.type_name == "myst":
+ kana = self.get_kana(pos)
+ if kana.type_name == 'myst':
new_grid = self.copy()
new_grid.action_count += 1
- new_grid.set_kana(pos, Kana("norm", kana.kana))
+ new_grid.set_kana(pos, Kana('norm', kana.kana))
return new_grid
elif action_type in ("up", "right", "down", "left"):
if action_type == "up":
@@ -94,8 +112,7 @@ class KanaGrid:
pos_dest = (pos[0], pos[1]+1)
elif action_type == "left":
pos_dest = (pos[0]-1, pos[1])
- kana_dest = self.get_kana(pos_dest)
- if is_swappable(kana, kana_dest):
+ if self.is_swappable(pos, pos_dest):
new_grid = self.copy()
new_grid.action_count += 1
new_grid.swap_kana(pos, pos_dest)
@@ -105,7 +122,7 @@ class KanaGrid:
for y in range(self.height):
for x in range(self.width):
kana = self.get_kana((x, y))
- if kana.type_name in self.valid_types_for_chain:
+ if kana.type_name in Kana.types_valid_for_chain:
yield (x, y)
def populate_chain(self, pos1, chain_positions):
@@ -123,7 +140,7 @@ class KanaGrid:
if pos2 in chain_positions:
continue
kana2 = self.get_kana(pos2)
- if kana2.type_name in self.valid_types_for_chain:
+ if kana2.type_name in Kana.types_valid_for_chain:
if is_kana_compatible(kana1, kana2):
self.populate_chain(pos2, chain_positions)
@@ -172,25 +189,24 @@ class KanaGrid:
def swap_kana(self, pos1, pos2):
kana_dst = self.get_kana(pos2)
- if kana_dst.type_name == "froz":
+ if kana_dst.type_name == 'froz':
pos_tmp = pos1
pos1 = pos2
pos2 = pos_tmp
kana_dst = self.get_kana(pos2)
- # important
kana_src = self.get_kana(pos1)
pos_src = pos1
pos_dst = pos2
vect = (pos2[0] - pos1[0], pos2[1] - pos1[1])
- while is_swappable(kana_src, kana_dst):
+ while self.is_swappable(pos_src, pos_dst):
#print("swap between src %s (%s) dst %s (%s)"
# % (kana_src, pos_src, kana_dst, pos_dst))
self.set_kana(pos_src, kana_dst)
self.set_kana(pos_dst, kana_src)
- if kana_src.type_name != "froz":
+ if kana_src.type_name != 'froz':
break
pos_src = pos_dst
@@ -226,20 +242,34 @@ class KanaGrid:
def repr_grid(grid, grid_size):
+ indicator_map = {
+ 'ar_u': ('\x1b[30m∧\x1b[0m', '\x1b[30m∧\x1b[0m'),
+ 'ar_r': ('\x1b[30m>\x1b[0m', '\x1b[30m>\x1b[0m'),
+ 'ar_d': ('\x1b[30m∨\x1b[0m', '\x1b[30m∨\x1b[0m'),
+ 'ar_l': ('\x1b[30m<\x1b[0m', '\x1b[30m<\x1b[0m'),
+ 'froz': (' \x1b[36m', '\x1b[0m '),
+ 'rock': (' \x1b[1;40m', '\x1b[0m '),
+ 'myst': ('\x1b[33m?', '?\x1b[0m'),
+ }
lines = []
kana_iter = iter(grid)
for y in range(grid_size[1]):
- line = ""
+ line = ''
for x in range(grid_size[0]):
kana = next(kana_iter)
- if kana.type_name == "void":
- line += " "
- elif kana.type_name == "empt":
- line += "| |"
- elif kana.type_name == "myst":
- line += "| ?? |"
- elif kana.type_name in ("froz", "norm", "rock"):
- line += "| %s |" % kana.kana
+ tname = kana.type_name
+ if tname == 'void':
+ line += ' '
+ elif tname == 'empt':
+ line += '| |'
+ elif tname in indicator_map:
+ line += '|%s%s%s|' % (
+ indicator_map[tname][0],
+ kana.kana,
+ indicator_map[tname][1],
+ )
+ elif kana.type_name in Kana.types_with_kana:
+ line += '| %s |' % kana.kana
lines.append(line)
return '\n'.join(lines)
diff --git a/tests_solver/level_test_ar_x.yaml b/tests_solver/level_test_ar_x.yaml
new file mode 100644
index 0000000..633a843
--- /dev/null
+++ b/tests_solver/level_test_ar_x.yaml
@@ -0,0 +1,8 @@
+# kana quest grid level 1.1
+size: [3, 1]
+max_actions: 1
+target_score: 2
+grid: [
+ [ar_r, su ], [empt, null], [norm, so ],
+]
+# The kana 'no' need to be protected else the program convert it into False...
diff --git a/tests_solver/level_test_type.yaml b/tests_solver/level_test_type.yaml
new file mode 100644
index 0000000..0eec70a
--- /dev/null
+++ b/tests_solver/level_test_type.yaml
@@ -0,0 +1,9 @@
+# kana quest grid level 1.1
+size: [6, 2]
+max_actions: 6
+target_score: 8
+grid: [
+ [ar_r, sa ], [empt, null], [norm, so ], [rock, se ], [myst, ka ], [ar_d, na ],
+ [ar_u, su ], [ar_l, nu ], [void, null], [void, null], [void, null], [froz, ke ],
+]
+# The kana 'no' need to be protected else the program convert it into False...
diff --git a/tests_solver/level_test_type_short.yaml b/tests_solver/level_test_type_short.yaml
new file mode 100644
index 0000000..7fa3a9a
--- /dev/null
+++ b/tests_solver/level_test_type_short.yaml
@@ -0,0 +1,9 @@
+# kana quest grid level 1.1
+size: [2, 2]
+max_actions: 3
+target_score: 3
+grid: [
+ [myst, ka ], [ar_d, ne ],
+ [void, null], [froz, ke ],
+]
+# The kana 'no' need to be protected else the program convert it into False...
diff --git a/tests_solver/test_solver.py b/tests_solver/test_solver.py
index e2b45a5..82aca73 100644
--- a/tests_solver/test_solver.py
+++ b/tests_solver/test_solver.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import os
+import pprint
import sys
sys.path.append(os.getcwd())
@@ -8,13 +9,29 @@ sys.path.append(os.getcwd())
from solver import *
def test_is_swappable():
- assert is_swappable(Kana("norm", "su"), Kana("norm", "su"))
- assert is_swappable(Kana("froz", "su"), Kana("norm", "su"))
- assert is_swappable(Kana("norm", "su"), Kana("empt"))
- assert is_swappable(Kana("empt"), Kana("froz", "su"))
- assert not is_swappable(Kana("norm", "su"), Kana("rock", "su"))
- assert not is_swappable(Kana("froz", "su"), Kana("froz", "su"))
- assert not is_swappable(Kana("empt"), Kana("empt"))
+
+ swappable_grids = [
+ [Kana("norm", "su"), Kana("norm", "su")],
+ [Kana("froz", "su"), Kana("norm", "su")],
+ [Kana("norm", "su"), Kana("empt" )],
+ [Kana("empt" ), Kana("froz", "su")],
+ ]
+
+ for swappable_grid in swappable_grids:
+ kanagrid = KanaGrid((2, 1), swappable_grid)
+ print(kanagrid.grid)
+ assert kanagrid.is_swappable((0, 0), (1, 0))
+
+ not_swappable_grids = [
+ [Kana("norm", "su"), Kana("rock", "su")],
+ [Kana("froz", "su"), Kana("froz", "su")],
+ [Kana("empt" ), Kana("empt" )],
+ ]
+
+ for not_swappable_grid in not_swappable_grids:
+ kanagrid = KanaGrid((2, 1), not_swappable_grid)
+ print(kanagrid.grid)
+ assert not kanagrid.is_swappable((0, 0), (1, 0))
def test_kana_grid():
@@ -47,3 +64,113 @@ def test_kana_grid():
assert kanagrid_new.grid == expected_grid
+
+def test_kana_arrow_swap():
+ kanagrid_orig = KanaGrid((2, 1), [Kana("ar_r", "su"), Kana("empt",)])
+ kanagrid_new = kanagrid_orig.action(pos=(0, 0), action_type="right")
+ expected_grid = KanaGrid((2, 1), [Kana("empt",), Kana("ar_r", "su")])
+
+ print("kanagrid_orig")
+ print(kanagrid_orig)
+ print("kanagrid_new")
+ print(kanagrid_new)
+ print("expected_grid")
+ print(expected_grid)
+
+ assert kanagrid_new.grid == expected_grid.grid
+
+def test_kana_arrow_swap():
+ moves = [
+ {
+ 'move_ok': 'up',
+ 'other_move_ok': 'down',
+ 'dest_pos': (1, 0),
+ 'orig': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("ar_u", "ko"), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ]),
+ 'moved': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("ar_u", "ko"), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ], action_count=1),
+ },
+ {
+ 'move_ok': 'right',
+ 'other_move_ok': 'left',
+ 'dest_pos': (2, 1),
+ 'orig': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("ar_r", "ko"), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ]),
+ 'moved': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("ar_r", "ko"),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ], action_count=1),
+ },
+ {
+ 'move_ok': 'down',
+ 'other_move_ok': 'up',
+ 'dest_pos': (1, 2),
+ 'orig': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("ar_d", "ko"), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ]),
+ 'moved': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("ar_d", "ko"), Kana("empt" ),
+ ], action_count=1),
+ },
+ {
+ 'move_ok': 'left',
+ 'other_move_ok': 'right',
+ 'dest_pos': (0, 1),
+ 'orig': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("ar_l", "ko"), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ]),
+ 'moved': KanaGrid((3, 3), [
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ Kana("ar_l", "ko"), Kana("empt" ), Kana("empt" ),
+ Kana("empt" ), Kana("empt" ), Kana("empt" ),
+ ], action_count=1),
+ },
+ ]
+
+ for move in moves:
+ print("pprint.pprint(move):")
+ pprint.pprint(move)
+ for action in ("up", "right", "down", "left"):
+ print("\n"*3)
+ print("action:", action)
+
+ # try to move arrowed kana
+ kanagrid_test = move['orig'].copy()
+ kanagrid_test = kanagrid_test.action((1, 1), action)
+ print("=== move arrowed kana ===")
+ print(kanagrid_test)
+ if action == move['move_ok']:
+ assert move['orig'].is_swappable((1, 1), move['dest_pos'])
+ assert kanagrid_test == move['moved']
+ else:
+ assert kanagrid_test is None
+
+ for other_pos in ((1, 0), (2, 1), (1, 2), (0, 1)):
+ # try to move other kana with the arrowed one
+ kanagrid_test = move['orig'].copy()
+ kanagrid_test = kanagrid_test.action(other_pos, action)
+ print("=== move other kana ===")
+ print(kanagrid_test)
+ if action == move['other_move_ok'] \
+ and other_pos == move['dest_pos']:
+ assert move['orig'].is_swappable(other_pos, (1, 1))
+ assert kanagrid_test == move['moved']
+ else:
+ assert kanagrid_test is None
+