Commit b62da615 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Improve fragmentation/columnsize scores, fiddle with params, comment some broken code

parent 295cfec6
import time import time
from collections import deque from itertools import combinations, islice
from itertools import islice from parse import COLUMNS, NOBLOCK, detect_blocks, detect_exa, \
from parser import COLUMNS, NOBLOCK, detect_blocks, detect_exa, \
detect_held, print_board, is_basic, is_bomb, bomb_to_basic detect_held, print_board, is_basic, is_bomb, bomb_to_basic
GRAB, DROP, SWAP, LEFT, RIGHT = range(5) GRAB, DROP, SWAP, LEFT, RIGHT, SPEED = range(6)
GET = ((GRAB,), (SWAP, GRAB), (GRAB, SWAP, DROP, SWAP, GRAB)) GET = ((GRAB,), (SWAP, GRAB), (GRAB, SWAP, DROP, SWAP, GRAB))
PUT = ((DROP,), (DROP, SWAP), (DROP, SWAP, GRAB, SWAP, DROP)) PUT = ((DROP,), (DROP, SWAP), (DROP, SWAP, GRAB, SWAP, DROP))
#REVERSE = [DROP, GRAB, SWAP, RIGHT, LEFT]
MIN_BASIC_GROUP_SIZE = 4 MIN_BASIC_GROUP_SIZE = 4
MIN_BOMB_GROUP_SIZE = 2 MIN_BOMB_GROUP_SIZE = 2
FIND_GROUPS_DEPTH = 20 FIND_GROUPS_DEPTH = 4
FRAG_DEPTH = 20 FRAG_DEPTH = 4
COLSIZE_PRIO = 5 COLSIZE_PRIO = 5
COLSIZE_PRIO_HIGH = 7
COLSIZE_MAX = 8
BOMB_POINTS = 2
MIN_ROWS = 2
class State: class State:
...@@ -23,10 +25,12 @@ class State: ...@@ -23,10 +25,12 @@ class State:
self.exa = exa self.exa = exa
self.held = held self.held = held
def has_holes(self): def grabbing_of_dropping(self):
return any(self.blocks[i] == NOBLOCK skip = self.colskip(self.exa)
for col in self.iter_columns() i = (skip + 1) * COLUMNS + self.exa
for i in col) return i < len(self.blocks) and self.blocks[i] == NOBLOCK
#return any(len(col) > 1 and col[1] == NOBLOCK
# for col in map(tuple, self.iter_columns()))
def iter_columns(self): def iter_columns(self):
nrows = self.nrows() nrows = self.nrows()
...@@ -54,45 +58,60 @@ class State: ...@@ -54,45 +58,60 @@ class State:
for col in range(COLUMNS): for col in range(COLUMNS):
yield self.nrows() - self.colskip(col) yield self.nrows() - self.colskip(col)
#def highest_column(self):
# for i, block in enumerate(self.blocks):
# if block != NOBLOCK:
# return self.nrows() - i // COLUMNS
def hole_score(self): def empty_column_score(self):
skip = 0
for i, block in enumerate(self.blocks):
if block != NOBLOCK:
skip = i // COLUMNS
break
nrows = self.nrows()
score = 0 score = 0
for col in range(COLUMNS): for col in range(COLUMNS):
for row in range(self.nrows()): for row in range(skip, nrows):
i = row * COLUMNS + col if self.blocks[row * COLUMNS + col] != NOBLOCK:
if self.blocks[i] != NOBLOCK:
break break
score += row + 1 score += row - skip + 1
return score return score
def score(self, points, moves, prev): def score(self, points, moves, prev):
frag = self.fragmentation() #colsizes = list(self.colsizes())
colsizes = list(self.colsizes())
#mincol = min(colsizes) #mincol = min(colsizes)
maxcol = max(colsizes) #maxcol = max(colsizes)
colsize_score = maxcol, colsizes.count(maxcol) #, -mincol #colsize_score = maxcol, colsizes.count(maxcol) #, -mincol
#colsize_score = tuple(sorted(colsizes, reverse=True)) #colsize_score = tuple(sorted(colsizes, reverse=True))
#if prev.nrows() >= 6: #if prev.nrows() >= 6:
# return colsize_score, -points, frag, len(moves) # return colsize_score, -points, frag, len(moves)
colsize_score = maxcol, self.hole_score() #colsize_score = maxcol, self.empty_column_score()
prev_colsize = max(prev.colsizes()) frag = self.fragmentation()
colsize_score = self.empty_column_score()
#return -points, frag + colsize_score, len(moves)
if prev_colsize >= COLSIZE_PRIO: frag += colsize_score
return colsize_score, frag, -points, len(moves) prev_colsize = max(prev.colsizes())
if prev_colsize >= COLSIZE_PRIO_HIGH:
return colsize_score, len(moves), -points, frag
elif prev_colsize >= COLSIZE_PRIO:
return -points, colsize_score, frag, len(moves)
else: else:
return -points, frag, colsize_score, len(moves) return -points, frag, colsize_score, len(moves)
def score_moves(self): def score_moves(self):
# clear exploding blocks before computing colsize # clear exploding blocks before computing colsize
prev = self.copy() #prev = self.copy()
prev.score_points() #prev.score_points()
for moves in self.gen_moves(): for moves in self.gen_moves():
try: try:
points, newstate = self.simulate(moves) points, newstate = self.simulate(moves)
yield newstate.score(points, moves, prev), moves yield newstate.score(points, moves, self), moves
except AssertionError: except AssertionError:
pass pass
...@@ -126,14 +145,11 @@ class State: ...@@ -126,14 +145,11 @@ class State:
s = self.copy() s = self.copy()
points = 0 points = 0
# clear the current board before planning the next move #if not moves:
#s.score_points() # return s.score_points(), s
if not moves:
return s.score_points(), s
# avoid swapping/grabbing currently exploding items # avoid swapping/grabbing currently exploding items
unmoveable = s.find_unmovable_blocks() #unmoveable = s.find_unmovable_blocks()
for move in moves: for move in moves:
if move == LEFT: if move == LEFT:
...@@ -147,7 +163,7 @@ class State: ...@@ -147,7 +163,7 @@ class State:
row = s.colskip(s.exa) row = s.colskip(s.exa)
assert row < s.nrows() assert row < s.nrows()
i = row * COLUMNS + s.exa i = row * COLUMNS + s.exa
assert i not in unmoveable #assert i not in unmoveable
s.held = s.blocks[i] s.held = s.blocks[i]
s.blocks[i] = NOBLOCK s.blocks[i] = NOBLOCK
elif move == DROP: elif move == DROP:
...@@ -157,20 +173,24 @@ class State: ...@@ -157,20 +173,24 @@ class State:
i = row * COLUMNS + s.exa i = row * COLUMNS + s.exa
s.blocks[i - COLUMNS] = s.held s.blocks[i - COLUMNS] = s.held
s.held = NOBLOCK s.held = NOBLOCK
points += s.score_points() #points += s.score_points()
elif move == SWAP: elif move == SWAP:
row = s.colskip(s.exa) row = s.colskip(s.exa)
assert row < s.nrows() - 2 assert row < s.nrows() - 2
i = row * COLUMNS + s.exa i = row * COLUMNS + s.exa
j = i + COLUMNS j = i + COLUMNS
assert i not in unmoveable #assert i not in unmoveable
assert j not in unmoveable #assert j not in unmoveable
s.blocks[i], s.blocks[j] = s.blocks[j], s.blocks[i] s.blocks[i], s.blocks[j] = s.blocks[j], s.blocks[i]
points += s.score_points() #points += s.score_points()
if moves and max(self.colsizes()) < COLSIZE_MAX:
assert max(s.colsizes()) <= COLSIZE_MAX
points += s.score_points()
return points, s return points, s
def find_groups(self, depth=FIND_GROUPS_DEPTH): def find_groups(self, depth=FIND_GROUPS_DEPTH, minsize=2):
def follow_group(i, block, group): def follow_group(i, block, group):
if self.blocks[i] == block and i not in visited: if self.blocks[i] == block and i not in visited:
group.append(i) group.append(i)
...@@ -185,9 +205,7 @@ class State: ...@@ -185,9 +205,7 @@ class State:
block = self.blocks[i] block = self.blocks[i]
group = [] group = []
follow_group(i, block, group) follow_group(i, block, group)
if len(group) > 1: if len(group) >= minsize:
#for j in group:
# visited.add(j)
yield block, group yield block, group
def neighbors(self, i): def neighbors(self, i):
...@@ -208,31 +226,30 @@ class State: ...@@ -208,31 +226,30 @@ class State:
def fragmentation(self, depth=FRAG_DEPTH): def fragmentation(self, depth=FRAG_DEPTH):
""" """
Sum the minimum distance from every block in the first 3 layers to the Minimize the sum of dist(i,j) for all blocks i,j of the same color.
closest block of the same color. Prioritize horitontal distance to avoid column stacking.
""" """
def find_closest(i): def dist(i, j):
block = self.blocks[i] yi, xi = divmod(i, COLUMNS)
work = deque([(i, -1)]) yj, xj = divmod(j, COLUMNS)
visited = {i} if groups[i] == groups[j]:
return abs(yj - yi)
while work: #return abs(xj - xi) * 2 + abs(yj - yi) - 1
i, dist = work.popleft() return abs(xj - xi) + abs(yj - yi) * 2 - 1
if dist >= 0 and self.blocks[i] == block: colors = {}
return dist groups = {}
groupsizes = {}
for j in self.neighbors(i):
if j not in visited: for groupid, (block, group) in enumerate(self.find_groups(depth, 1)):
visited.add(j) colors.setdefault(block, []).extend(group)
work.append((j, dist + 1)) for i in group:
groups[i] = groupid
# only one of this type -> don't count as fragmented groupsizes[i] = len(group)
return 0
return sum(find_closest(i) return sum(dist(i, j) # * (1 + 2 * is_bomb(block))
for col in self.iter_columns() for block, color in colors.items()
for i in islice(col, depth)) for i, j in combinations(color, 2))
def score_points(self, multiplier=1): def score_points(self, multiplier=1):
remove = [] remove = []
...@@ -243,7 +260,7 @@ class State: ...@@ -243,7 +260,7 @@ class State:
remove.extend(group) remove.extend(group)
points += len(group) * multiplier points += len(group) * multiplier
elif is_bomb(block) and len(group) >= MIN_BOMB_GROUP_SIZE: elif is_bomb(block) and len(group) >= MIN_BOMB_GROUP_SIZE:
#points += 10 points += BOMB_POINTS
remove.extend(group) remove.extend(group)
for i, other in enumerate(self.blocks): for i, other in enumerate(self.blocks):
if other == bomb_to_basic(block): if other == bomb_to_basic(block):
...@@ -262,24 +279,28 @@ class State: ...@@ -262,24 +279,28 @@ class State:
points += self.score_points(min(2, multiplier * 2)) points += self.score_points(min(2, multiplier * 2))
return points return points
def has_explosion(self):
return any(is_bomb(block) and
any(self.blocks[j] == block for j in self.neighbors(i))
for i, block in enumerate(self.blocks))
def gen_moves(self): def gen_moves(self):
yield () yield ()
for src in range(COLUMNS): def make_move(diff):
diff = src - self.exa
direction = RIGHT if diff > 0 else LEFT direction = RIGHT if diff > 0 else LEFT
mov1 = abs(diff) * (direction,) return abs(diff) * (direction,)
for src in range(COLUMNS):
mov1 = make_move(src - self.exa)
yield mov1 + (SWAP,) yield mov1 + (SWAP,)
yield mov1 + (GRAB, SWAP, DROP) yield mov1 + (GRAB, SWAP, DROP)
yield mov1 + (SWAP, GRAB, SWAP, DROP)
for dst in range(COLUMNS): for dst in range(COLUMNS):
diff = dst - src if dst != src:
direction = RIGHT if diff > 0 else LEFT mov2 = make_move(dst - src)
mov2 = abs(diff) * (direction,) for get in GET:
for i, get in enumerate(GET):
if i > 1 or diff != 0:
for put in PUT: for put in PUT:
yield mov1 + get + mov2 + put yield mov1 + get + mov2 + put
...@@ -287,7 +308,18 @@ class State: ...@@ -287,7 +308,18 @@ class State:
if self.held != NOBLOCK: if self.held != NOBLOCK:
return (DROP,) return (DROP,)
if self.nrows() < MIN_ROWS:
return ()
if self.grabbing_of_dropping():
return ()
if self.has_explosion():
return ()
score, moves = min(self.score_moves()) score, moves = min(self.score_moves())
if not moves:
return (SPEED,)
return moves return moves
def print(self): def print(self):
...@@ -299,7 +331,7 @@ class State: ...@@ -299,7 +331,7 @@ class State:
def moves_to_keys(moves): def moves_to_keys(moves):
return ''.join('jjkad'[move] for move in moves) return ''.join('jjkadl'[move] for move in moves)
if __name__ == '__main__': if __name__ == '__main__':
...@@ -314,10 +346,13 @@ if __name__ == '__main__': ...@@ -314,10 +346,13 @@ if __name__ == '__main__':
state.print() state.print()
print() print()
print('empty cols:', state.empty_column_score())
print()
start = time.time() start = time.time()
moves = state.solve() moves = state.solve()
print('moves:', moves_to_keys(moves))
end = time.time() end = time.time()
print('moves:', moves_to_keys(moves))
print('elapsed:', end - start) print('elapsed:', end - start)
print() print()
...@@ -326,9 +361,9 @@ if __name__ == '__main__': ...@@ -326,9 +361,9 @@ if __name__ == '__main__':
newstate.print() newstate.print()
print() print()
#for score, moves in sorted(state.score_moves()): for score, moves in sorted(state.score_moves()):
# print('move %18s:' % moves_to_keys(moves), score) print('move %18s:' % moves_to_keys(moves), score)
# #print('moves:', moves_to_keys(moves), moves) #print('moves:', moves_to_keys(moves), moves)
# #print('score:', score) #print('score:', score)
#print('\nmoves:', moves_to_keys(state.solve())) #print('\nmoves:', moves_to_keys(state.solve()))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment