Kaynağa Gözat

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

Taddeus Kroes 5 yıl önce
ebeveyn
işleme
b62da615a7
1 değiştirilmiş dosya ile 118 ekleme ve 83 silme
  1. 118 83
      strategy.py

+ 118 - 83
strategy.py

@@ -1,19 +1,21 @@
 import time
-from collections import deque
-from itertools import islice
-from parser import COLUMNS, NOBLOCK, detect_blocks, detect_exa, \
-                   detect_held, print_board, is_basic, is_bomb, bomb_to_basic
+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
-
-        return sum(find_closest(i)
-                   for col in self.iter_columns()
-                   for i in islice(col, depth))
+        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(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()))