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