|
|
@ -1,10 +1,10 @@ |
|
|
|
#!/usr/bin/env python3 |
|
|
|
|
|
|
|
import json |
|
|
|
from enum import Enum |
|
|
|
import functools |
|
|
|
from enum import Enum |
|
|
|
from pprint import pprint |
|
|
|
from klotski import Group, Layout |
|
|
|
from klotski import Block, Group, Layout |
|
|
|
|
|
|
|
|
|
|
|
@functools.cache |
|
|
@ -19,16 +19,17 @@ class Solution: |
|
|
|
Z = 2 |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def __solu_info(layout: str) -> tuple[int, int]: # (max_step, ethnic_size) |
|
|
|
def __solu_info(layout: str) -> tuple[int, int]: # (ethnic_deep, ethnic_size) |
|
|
|
cases = [x for x, y in global_data().items() if layout in y['solutions']] |
|
|
|
steps = [global_data()[x]['step'] for x in cases] |
|
|
|
assert min(steps) == 0 |
|
|
|
return max(steps), len(cases) |
|
|
|
|
|
|
|
def __init__(self, code: str, solu_type: Type): |
|
|
|
self.__name = '' |
|
|
|
self.__code = code |
|
|
|
self.__type = solu_type |
|
|
|
self.__max_step, self.__ethnic_size = Solution.__solu_info(code) |
|
|
|
self.__ethnic_deep, self.__ethnic_size = Solution.__solu_info(code) |
|
|
|
|
|
|
|
@property |
|
|
|
def code(self) -> str: |
|
|
@ -38,6 +39,14 @@ class Solution: |
|
|
|
def type(self) -> Type: |
|
|
|
return self.__type |
|
|
|
|
|
|
|
@property |
|
|
|
def name(self) -> str: |
|
|
|
return self.__name |
|
|
|
|
|
|
|
@name.setter |
|
|
|
def name(self, name: str) -> None: |
|
|
|
self.__name = name |
|
|
|
|
|
|
|
@property |
|
|
|
def group(self) -> Group: |
|
|
|
return self.layout.group |
|
|
@ -47,21 +56,22 @@ class Solution: |
|
|
|
return Layout(self.code) |
|
|
|
|
|
|
|
@property |
|
|
|
def max_step(self) -> int: |
|
|
|
return self.__max_step |
|
|
|
def ethnic_deep(self) -> int: |
|
|
|
return self.__ethnic_deep |
|
|
|
|
|
|
|
@property |
|
|
|
def ethnic_size(self) -> int: |
|
|
|
return self.__ethnic_size |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
val = f'{self.ethnic_size}, {self.max_step}' |
|
|
|
return f'{self.code} {self.type.name} ({val})' |
|
|
|
return (f'<Solution {self.name} [{self.code}] {self.type.name} ' |
|
|
|
f'({self.ethnic_size} {self.ethnic_deep})>') |
|
|
|
|
|
|
|
|
|
|
|
class SoluGroup: |
|
|
|
def __init__(self, name: str): |
|
|
|
self.__name = name |
|
|
|
self.__name = '' |
|
|
|
self.__inner_name = name # klotski group name |
|
|
|
self.__x_solus: list[Solution] = [] |
|
|
|
self.__y_solus: list[Solution] = [] |
|
|
|
self.__z_solus: list[Solution] = [] |
|
|
@ -78,6 +88,14 @@ class SoluGroup: |
|
|
|
def name(self) -> str: |
|
|
|
return self.__name |
|
|
|
|
|
|
|
@name.setter |
|
|
|
def name(self, name: str) -> None: |
|
|
|
self.__name = name |
|
|
|
|
|
|
|
@property |
|
|
|
def inner_name(self) -> str: |
|
|
|
return self.__inner_name |
|
|
|
|
|
|
|
@property |
|
|
|
def solus(self) -> list[Solution]: |
|
|
|
return self.__x_solus + self.__y_solus + self.__z_solus |
|
|
@ -94,13 +112,32 @@ class SoluGroup: |
|
|
|
def group_size(self) -> int: |
|
|
|
return self.group.size |
|
|
|
|
|
|
|
@property |
|
|
|
def group_valid_size(self) -> int: |
|
|
|
|
|
|
|
def is_invalid_solu(layout: Layout) -> bool: |
|
|
|
if str(layout)[0] != 'D': # 2x2 block address not 13 |
|
|
|
return False |
|
|
|
block_seq = layout.dump_seq() |
|
|
|
is_type_x = block_seq[12] == Block.SPACE and block_seq[16] == Block.SPACE |
|
|
|
is_type_y = block_seq[15] == Block.SPACE and block_seq[19] == Block.SPACE |
|
|
|
is_type_z = block_seq[9] == Block.SPACE and block_seq[10] == Block.SPACE |
|
|
|
return not (is_type_x or is_type_y or is_type_z) |
|
|
|
|
|
|
|
num = len([x for x in list(self.group.cases()) if is_invalid_solu(x)]) |
|
|
|
return self.group_size - num |
|
|
|
|
|
|
|
@property |
|
|
|
def ethnic_sizes(self) -> list[int]: |
|
|
|
return sorted([x.ethnic_size for x in self.solus], key=lambda x: -x) |
|
|
|
|
|
|
|
@property |
|
|
|
def max_ethnic_size(self) -> int: |
|
|
|
return max(self.ethnic_sizes) |
|
|
|
return max(x.ethnic_size for x in self.solus) |
|
|
|
|
|
|
|
@property |
|
|
|
def max_ethnic_deep(self) -> int: |
|
|
|
return max(x.ethnic_deep for x in self.solus) |
|
|
|
|
|
|
|
@property |
|
|
|
def min_case(self) -> Layout: |
|
|
@ -111,63 +148,195 @@ class SoluGroup: |
|
|
|
return min(x.layout for x in self.solus) |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
return (f'<SoluGroup {self.name} ({self.group_size}) ' |
|
|
|
f'[{self.min_solu}/{self.min_case}] {self.solu_num} -> {self.ethnic_sizes}>') |
|
|
|
return (f'<SoluGroup {self.name} {self.inner_name} ({self.group_valid_size}/{self.group_size}) ' |
|
|
|
f'[{self.min_solu}/{self.min_case}] ({self.max_ethnic_deep}) ' |
|
|
|
f'{self.solu_num} -> {self.ethnic_sizes}>') |
|
|
|
|
|
|
|
def build_m_index(self) -> None: |
|
|
|
assert self.__name[-1] == 'M' |
|
|
|
|
|
|
|
assert len(self.__x_solus) == len(self.__y_solus) |
|
|
|
self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) |
|
|
|
y_index = {str(y.layout.to_horizontal_mirror())[:7]: x for x, y in enumerate(self.__x_solus)} |
|
|
|
self.__y_solus.sort(key=lambda x: y_index[x.code]) |
|
|
|
|
|
|
|
for num in range(len(self.__x_solus)): |
|
|
|
self.__x_solus[num].name = f'{self.name}-{num:03}X' |
|
|
|
self.__y_solus[num].name = f'{self.name}-{num:03}Y' |
|
|
|
|
|
|
|
z_map = {x.code: x for x in self.__z_solus} |
|
|
|
z_pairs = [tuple(sorted([x.layout, x.layout.to_horizontal_mirror()])) for x in self.__z_solus] |
|
|
|
z_pairs = [(z_map[str(x)[:7]], z_map[str(y)[:7]]) for x, y in set(z_pairs)] |
|
|
|
z_pairs.sort(key=lambda x: (-x[0].ethnic_size, -x[0].ethnic_deep, x[0].layout)) |
|
|
|
|
|
|
|
for num in range(len(z_pairs)): |
|
|
|
if z_pairs[num][0] == z_pairs[num][1]: |
|
|
|
z_pairs[num][0].name = f'{self.name}-{num:02}Z' |
|
|
|
else: |
|
|
|
z_pairs[num][0].name = f'{self.name}-{num:02}ZL' |
|
|
|
z_pairs[num][1].name = f'{self.name}-{num:02}ZR' |
|
|
|
|
|
|
|
def build_lx_index(self) -> dict[str, str]: |
|
|
|
assert self.__name[-1] == 'L' |
|
|
|
right_data = {} |
|
|
|
self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) |
|
|
|
for num in range(len(self.__x_solus)): |
|
|
|
self.__x_solus[num].name = f'{self.name}-{num:01}X' |
|
|
|
mirror = str(self.__x_solus[num].layout.to_horizontal_mirror())[:7] |
|
|
|
right_data[mirror] = f'{num:01}Y' |
|
|
|
return right_data |
|
|
|
|
|
|
|
def build_rx_index(self) -> dict[str, str]: |
|
|
|
assert self.__name[-1] == 'R' |
|
|
|
left_data = {} |
|
|
|
self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) |
|
|
|
for num in range(len(self.__x_solus)): |
|
|
|
self.__x_solus[num].name = f'{self.name}-{num:01}X' |
|
|
|
mirror = str(self.__x_solus[num].layout.to_horizontal_mirror())[:7] |
|
|
|
left_data[mirror] = f'{num:01}Y' |
|
|
|
return left_data |
|
|
|
|
|
|
|
def apply_ly_index(self, data: dict[str, str]) -> None: |
|
|
|
assert self.__name[-1] == 'L' |
|
|
|
for solu in self.__y_solus: |
|
|
|
solu.name = f'{self.name}-{data[solu.code]}' |
|
|
|
|
|
|
|
def apply_ry_index(self, data: dict[str, str]) -> None: |
|
|
|
assert self.__name[-1] == 'R' |
|
|
|
for solu in self.__y_solus: |
|
|
|
solu.name = f'{self.name}-{data[solu.code]}' |
|
|
|
|
|
|
|
def build_lz_index(self) -> dict[str, str]: |
|
|
|
assert self.__name[-1] == 'L' |
|
|
|
data = {} |
|
|
|
self.__z_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) |
|
|
|
for num in range(len(self.__z_solus)): |
|
|
|
self.__z_solus[num].name = f'{self.name}-{num:01}Z' |
|
|
|
mirror = str(self.__z_solus[num].layout.to_horizontal_mirror())[:7] |
|
|
|
data[mirror] = f'{num:01}Z' |
|
|
|
return data |
|
|
|
|
|
|
|
def apply_rz_index(self, data: dict[str, str]) -> None: |
|
|
|
assert self.__name[-1] == 'R' |
|
|
|
for solu in self.__z_solus: |
|
|
|
solu.name = f'{self.name}-{data[solu.code]}' |
|
|
|
|
|
|
|
|
|
|
|
class SoluPattern: |
|
|
|
def __init__(self, *solu_groups: SoluGroup): |
|
|
|
self.__pattern_id = -1 |
|
|
|
inner_type_id = solu_groups[0].inner_name[:3] |
|
|
|
self.__type_id = {'174': 0, '169': 1, '164': 2, '159': 3, '154': 4, '149': 5}[inner_type_id] |
|
|
|
|
|
|
|
assert len(solu_groups) in [1, 2] |
|
|
|
if len(solu_groups) == 1: |
|
|
|
self.__is_single = True |
|
|
|
self.__solu_m = solu_groups[0] |
|
|
|
self.__group_m = solu_groups[0] |
|
|
|
else: |
|
|
|
self.__is_single = False |
|
|
|
self.__solu_l, self.__solu_r = solu_groups # TODO: confirm l/r |
|
|
|
assert self.__solu_l.name[:-1] == self.__solu_r.name[:-1] |
|
|
|
assert self.__solu_l.solu_num == self.__solu_r.solu_num |
|
|
|
assert self.__solu_l.group_size == self.__solu_r.group_size |
|
|
|
assert self.__solu_l.ethnic_sizes == self.__solu_r.ethnic_sizes |
|
|
|
if solu_groups[0].min_solu < solu_groups[1].min_solu: |
|
|
|
self.__group_l, self.__group_r = solu_groups |
|
|
|
else: |
|
|
|
self.__group_r, self.__group_l = solu_groups |
|
|
|
assert self.__group_l.inner_name[:-1] == self.__group_r.inner_name[:-1] |
|
|
|
assert self.__group_l.solu_num == self.__group_r.solu_num |
|
|
|
assert self.__group_l.group_size == self.__group_r.group_size |
|
|
|
assert self.__group_l.ethnic_sizes == self.__group_r.ethnic_sizes |
|
|
|
|
|
|
|
@property |
|
|
|
def type_id(self) -> int: |
|
|
|
return self.__type_id |
|
|
|
|
|
|
|
@property |
|
|
|
def pattern_id(self) -> int: |
|
|
|
return self.__pattern_id |
|
|
|
|
|
|
|
@pattern_id.setter |
|
|
|
def pattern_id(self, id_val: int) -> None: |
|
|
|
self.__pattern_id = id_val |
|
|
|
if self.__is_single: |
|
|
|
self.__group_m.name = f'{self.type_id}-{self.pattern_id:02}M' |
|
|
|
else: |
|
|
|
self.__group_l.name = f'{self.type_id}-{self.pattern_id:02}L' |
|
|
|
self.__group_r.name = f'{self.type_id}-{self.pattern_id:02}R' |
|
|
|
|
|
|
|
@property |
|
|
|
def pattern_size(self) -> int: |
|
|
|
if self.__is_single: |
|
|
|
return self.__solu_m.group_size |
|
|
|
return self.__solu_l.group_size * 2 |
|
|
|
return self.__group_m.group_size |
|
|
|
return self.__group_l.group_size * 2 |
|
|
|
|
|
|
|
@property |
|
|
|
def pattern_valid_size(self) -> int: |
|
|
|
if self.__is_single: |
|
|
|
return self.__group_m.group_valid_size |
|
|
|
assert self.__group_l.group_valid_size == self.__group_r.group_valid_size |
|
|
|
return self.__group_l.group_valid_size * 2 |
|
|
|
|
|
|
|
@property |
|
|
|
def ethnic_sizes(self) -> list[int]: |
|
|
|
if self.__is_single: |
|
|
|
return self.__solu_m.ethnic_sizes |
|
|
|
return self.__solu_l.ethnic_sizes |
|
|
|
return self.__group_m.ethnic_sizes |
|
|
|
assert self.__group_l.ethnic_sizes == self.__group_r.ethnic_sizes |
|
|
|
return self.__group_l.ethnic_sizes |
|
|
|
|
|
|
|
@property |
|
|
|
def max_ethnic_size(self) -> int: |
|
|
|
return max(self.ethnic_sizes) |
|
|
|
if self.__is_single: |
|
|
|
return self.__group_m.max_ethnic_size |
|
|
|
assert self.__group_l.max_ethnic_size == self.__group_r.max_ethnic_size |
|
|
|
return self.__group_l.max_ethnic_size |
|
|
|
|
|
|
|
@property |
|
|
|
def max_ethnic_deep(self) -> int: |
|
|
|
if self.__is_single: |
|
|
|
return self.__group_m.max_ethnic_deep |
|
|
|
assert self.__group_l.max_ethnic_deep == self.__group_r.max_ethnic_deep |
|
|
|
return self.__group_l.max_ethnic_deep |
|
|
|
|
|
|
|
@property |
|
|
|
def min_case(self) -> Layout: |
|
|
|
if self.__is_single: |
|
|
|
return self.__solu_m.min_case |
|
|
|
return self.__group_m.min_case |
|
|
|
else: |
|
|
|
return min(self.__solu_l.min_case, self.__solu_r.min_case) |
|
|
|
return min(self.__group_l.min_case, self.__group_r.min_case) |
|
|
|
|
|
|
|
@property |
|
|
|
def min_solu(self) -> Layout: |
|
|
|
if self.__is_single: |
|
|
|
return self.__solu_m.min_solu |
|
|
|
return self.__group_m.min_solu |
|
|
|
else: |
|
|
|
return min(self.__group_l.min_solu, self.__group_r.min_solu) |
|
|
|
|
|
|
|
@property |
|
|
|
def solus(self) -> list[Solution]: |
|
|
|
if self.__is_single: |
|
|
|
return self.__group_m.solus |
|
|
|
else: |
|
|
|
return min(self.__solu_l.min_solu, self.__solu_r.min_solu) |
|
|
|
return self.__group_l.solus + self.__group_r.solus |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
if self.__is_single: |
|
|
|
name = self.__solu_m.name |
|
|
|
solu_num = f'{self.__solu_m.solu_num}' |
|
|
|
name = self.__group_m.name |
|
|
|
inner_name = self.__group_m.inner_name |
|
|
|
solu_num = f'{self.__group_m.solu_num}' |
|
|
|
group_repr = f'{self.__group_m}' |
|
|
|
else: |
|
|
|
name = f'{self.__solu_l.name}|{self.__solu_r.name}' |
|
|
|
solu_num = f'{self.__solu_l.solu_num}*2' |
|
|
|
return (f'<SoluPattern {name} ({self.pattern_size}) ' |
|
|
|
f'[{self.min_solu}/{self.min_case}] {solu_num} -> {self.ethnic_sizes}>') |
|
|
|
name = f'{self.__group_l.name}|{self.__group_r.name}' |
|
|
|
inner_name = f'{self.__group_l.inner_name}|{self.__group_r.inner_name}' |
|
|
|
solu_num = f'{self.__group_l.solu_num}*2' |
|
|
|
group_repr = f'{self.__group_l}\n{self.__group_r}' |
|
|
|
return (f'<SoluPattern {name} {inner_name} ({self.pattern_valid_size}/{self.pattern_size}) ' |
|
|
|
f'[{self.min_solu}/{self.min_case}] ({self.max_ethnic_deep}) ' |
|
|
|
f'{solu_num} -> {self.ethnic_sizes}>\n{group_repr}') |
|
|
|
|
|
|
|
def build_index(self) -> None: |
|
|
|
if self.__is_single: |
|
|
|
self.__group_m.build_m_index() |
|
|
|
else: |
|
|
|
self.__group_r.apply_ry_index(self.__group_l.build_lx_index()) |
|
|
|
self.__group_l.apply_ly_index(self.__group_r.build_rx_index()) |
|
|
|
self.__group_r.apply_rz_index(self.__group_l.build_lz_index()) |
|
|
|
|
|
|
|
|
|
|
|
def load_valid_solutions(num: int) -> list[Solution]: |
|
|
@ -178,7 +347,7 @@ def load_valid_solutions(num: int) -> list[Solution]: |
|
|
|
return data_x + data_y + data_z |
|
|
|
|
|
|
|
|
|
|
|
def split_into_groups(solus: list[Solution]) -> list[SoluPattern]: |
|
|
|
def split_patterns(solus: list[Solution]) -> list[SoluPattern]: |
|
|
|
groups = {} |
|
|
|
for solu in solus: |
|
|
|
group = str(solu.group) |
|
|
@ -200,21 +369,21 @@ def split_into_groups(solus: list[Solution]) -> list[SoluPattern]: |
|
|
|
for pattern_tuple in sorted(set(pattern_tuples)): |
|
|
|
patterns.append(SoluPattern(*[groups[x] for x in pattern_tuple])) |
|
|
|
|
|
|
|
return sorted(patterns, key=lambda x: (-x.pattern_size, -x.max_ethnic_size, x.min_solu)) |
|
|
|
patterns.sort(key=lambda x: (-x.pattern_valid_size, -x.pattern_size, -x.max_ethnic_size, x.min_solu)) |
|
|
|
for pattern_id, pattern in enumerate(patterns): |
|
|
|
pattern.pattern_id = pattern_id |
|
|
|
pattern.build_index() |
|
|
|
return patterns |
|
|
|
|
|
|
|
|
|
|
|
def main() -> None: |
|
|
|
|
|
|
|
# solu = Solution('DAAF4CC', Solution.Type.X) |
|
|
|
# print(solu) |
|
|
|
|
|
|
|
# for solu_group in split_into_groups(load_valid_solutions(1)): |
|
|
|
# print(solu_group) |
|
|
|
|
|
|
|
solus = [] |
|
|
|
for num in range(6): # 0/1/2/3/4/5 |
|
|
|
for solu_group in split_into_groups(load_valid_solutions(num)): |
|
|
|
print(solu_group) |
|
|
|
for solu_pattern in split_patterns(load_valid_solutions(num)): |
|
|
|
solus.extend(solu_pattern.solus) |
|
|
|
print(solu_pattern, end='\n\n') |
|
|
|
print('-' * 238) |
|
|
|
pprint(solus) |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|