Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- '''
- Created on Oct 4, 2009
- '''
- import sys
- from copy import deepcopy
- from threading import Timer
- hard_bubble_board = (
- 'pbgpbpyggrbpbgr',
- 'bgyrbpprgyrgpbb',
- 'bgyprypbpbbbrpb',
- 'bpprpbpbgrrrgrr',
- 'ppbrbpygpryryrb',
- 'rgrgpbyyypggrbr',
- 'gygrprrybbrrgrr',
- 'rgpbpggrbypyryr',
- )
- simple_bubble_board = (
- 'rgrgpbyyypggrbr',
- 'gygrprrybbrrgrr',
- 'rgpbpggrbypyryr',
- )
- X_VALUE = 5
- SPACE_VALUE = 6
- NUM_COLORS = 5
- SOLVER_COUNT = 0
- TOTAL_SOLVER_COUNT = 0
- SOLVER_MEMOIZED = 0
- USE_MEMOIZED = True
- USE_PSYCO = sys.platform == 'win32'
- STILL_SOLVING = True
- MEMOIZATION = {'': {'t': 0}}
- def PrintStats():
- global SOLVER_COUNT, TOTAL_SOLVER_COUNT, STILL_SOLVING
- TOTAL_SOLVER_COUNT += SOLVER_COUNT
- print "TOTAL_SOLVER_COUNT:", TOTAL_SOLVER_COUNT, "SOLVER_COUNT PER 10s:", SOLVER_COUNT, "SOLVER_COUNT PER 1s:", SOLVER_COUNT / 10, "MEMOIZATION SIZE:", len(MEMOIZATION), "MEMOIZATION HIT:", SOLVER_MEMOIZED
- SOLVER_COUNT = 0
- if STILL_SOLVING:
- Timer(10.0, PrintStats).start()
- conversion_c2i = {'p':0, 'b':1, 'g':2, 'y':3, 'r':4, 'x':X_VALUE, ' ':SPACE_VALUE}
- conversion_i2c = ('p', 'b', 'g', 'y', 'r', 'x', ' ')
- i2c_converter = lambda x: conversion_i2c[x]
- identity_converter = lambda x: x
- # convert to a faster underlying representation of the board
- bubble_board = [[conversion_c2i[c] for c in row] for row in simple_bubble_board]
- def compute_ball_adjacency(board, color, adjacency_table, adjacency_list, row_idx, col_idx):
- # Check the four bubbles adjacent for the same color value
- # (x+0,y-1)
- # (x+1,y+0)(x+0,y+0)(x-1, y+0)
- # (x+0,y+1)
- # Number of bubbles adjacent
- num_bubles_adjacent = 1
- # Check (x+1,y+0) .. first check whether we are in range
- r = row_idx
- c = col_idx + 1
- if c < len(board[0]):
- # if (0 or 1) that means we already figured out the ball count
- # Check whether we scored this one already
- if adjacency_table[r][c] == -1:
- # Make sure it is the same color
- if board[r][c] == color:
- # Same color update our score card
- adjacency_table[r][c] = 1
- adjacency_list.append((r, c))
- # go down the recursive path
- num_bubles_adjacent += compute_ball_adjacency(board, color, adjacency_table, adjacency_list, r, c)
- else:
- # We are not the same color so mark it as a 0
- adjacency_table[r][c] = 0
- # Check (x+0,y-1) .. first check whether we are in range
- r = row_idx - 1
- c = col_idx
- if r >= 0:
- # if (0 or 1) that means we already figured out the ball count
- # Check whether we scored this one already
- if adjacency_table[r][c] == -1:
- # Make sure it is the same color
- if board[r][c] == color:
- # Same color update our score card
- adjacency_table[r][c] = 1
- adjacency_list.append((r, c))
- # go down the recursive path
- num_bubles_adjacent += compute_ball_adjacency(board, color, adjacency_table, adjacency_list, r, c)
- else:
- # We are not the same color so mark it as a 0
- adjacency_table[r][c] = 0
- # Check (x+0,y-1) .. first check whether we are in range
- r = row_idx
- c = col_idx - 1
- if c >= 0:
- # if (0 or 1) that means we already figured out the ball count
- # Check whether we scored this one already
- if adjacency_table[r][c] == -1:
- # Make sure it is the same color
- if board[r][c] == color:
- # Same color
- # update our score card
- adjacency_table[r][c] = 1
- adjacency_list.append((r, c))
- # go down the recursive path
- num_bubles_adjacent += compute_ball_adjacency(board, color, adjacency_table, adjacency_list, r, c)
- else:
- # We are not the same color so mark it as a 0
- adjacency_table[r][c] = 0
- # Check (x+0,y+1) .. first check whether we are in range
- r = row_idx + 1
- c = col_idx
- if r < len(board):
- # if (0 or 1) that means we already figured out the ball count
- # Check whether we scored this one already
- if adjacency_table[r][c] == -1:
- # Make sure it is the same color
- if board[r][c] == color:
- # Same color
- # update our score card
- adjacency_table[r][c] = 1
- adjacency_list.append((r, c))
- # go down the recursive path
- num_bubles_adjacent += compute_ball_adjacency(board, color, adjacency_table, adjacency_list, r, c)
- else:
- # We are not the same color so mark it as a 0
- adjacency_table[r][c] = 0
- return num_bubles_adjacent
- def print_board(board, msg, converter=i2c_converter):
- print msg
- print ' |' + ''.join(['%3d ' % i for i in xrange(len(board[0]))])
- print ' +' + ('----' * (len(board[0])))
- for row in xrange(len(board)):
- print str(row) + '|',
- for col in xrange(len(board[0])):
- sys.stdout.write('%3s ' % converter(board[row][col]))
- print
- def mark_bubble_group(board, adj_count_list, row_idx, col_idx,
- X_VALUE=X_VALUE):
- # Go through the adj_count_list and exchange all the bubbles with a count > 0 with Xs
- for row, col in adj_count_list:
- assert board[row][col] != X_VALUE
- # convert to an x
- board[row][col] = X_VALUE
- def pick_bubble_group(bubbles_board, adj_count_list, row_idx, col_idx,
- X_VALUE=X_VALUE, SPACE_VALUE=SPACE_VALUE, mark_bubble_group=mark_bubble_group):
- # Make a copy of our bubbles, we are going to return this reduced board
- board = [[c for c in r] for r in bubbles_board]
- #print_board(board, "Bubbles: Before Updating Board")
- # Go through the adj_count_list and exchange all the bubbles with a count > 0 with Xs
- mark_bubble_group(board, adj_count_list, row_idx, col_idx)
- #print_board(board, "Bubbles: After marking with Xs picking (row,col)=(%d,%d)" % (row_idx, col_idx))
- #print
- # Determine the rows with a bubble we are going to remove
- rows_with_x = []
- # Determine the columns with a bubble we are going to remove
- cols_with_x = []
- # keep track of the last row and last column
- first_row = 0
- first_col = 0
- last_row = adj_count_list[0][0]
- last_col = adj_count_list[0][1]
- for row, col in adj_count_list:
- if row > last_row:
- last_row = row
- if col > last_col:
- last_col = col
- if row < first_row:
- first_row = row
- if col < first_col:
- first_col = col
- if not row in rows_with_x:
- rows_with_x.append(row)
- if not col in cols_with_x:
- cols_with_x.append(col)
- # make some extra copies
- last_row_copy, first_row_copy, last_col_copy, first_col_copy = last_row, first_row, last_col, first_col
- ####################
- # Shift down phase
- ####################
- while 1:
- ## Now go through from the last row to the first column and move any balls down into X spaces
- found_x = False
- # Go through from the last row to the first row we know will have xs
- for row in xrange(last_row, first_row - 1, -1):
- for col in cols_with_x:
- if board[row][col] == X_VALUE:
- found_x = True
- # copy columns down: row-1..0
- for row_copy in xrange(row, first_row, -1):
- board[row_copy][col] = board[row_copy - 1][col]
- # Clear top row
- board[first_row][col] = SPACE_VALUE
- # We know the first row will not have xs anymore as we have shifted down
- first_row += 1
- if not found_x:
- break
- #print_board(board, "Bubbles: After shift down")
- #print
- ####################
- # Shift right phase
- ####################
- while 1:
- found_hole = False
- for row in xrange(last_row, -1, -1):
- for col in xrange(last_col, first_col - 1, -1):
- assert board[row][col] != X_VALUE
- # if we did not hit a space we can continue
- if board[row][col] != SPACE_VALUE:
- continue
- # Search to the left to see if there are any columns that have a ball
- for col_search in xrange(col - 1, first_col - 1, -1):
- if board[row][col_search] != SPACE_VALUE:
- found_hole = True
- # shift right from this point to the first column
- for col_copy in xrange(col_search, -1, -1):
- board[row][col + col_copy - col_search] = board[row][col_copy]
- # Where we shifted we need to fill in with empty spaces
- for col_fill in xrange(col - col_search - 1, -1, -1):
- board[row][col_fill] = SPACE_VALUE
- # end our search
- break
- last_col -= 1
- first_col += 1
- if not found_hole:
- break
- #print_board(board, "Bubbles: After shift right")
- #print 'p',
- # Find any empty rows
- empty_row = -1
- for row in xrange(last_row_copy, -1, -1):
- for col in xrange(len(board[0])):
- if board[row][col] != SPACE_VALUE:
- break
- else:
- empty_row = row
- break
- # Find any empty columns
- empty_col = -1
- for col in xrange(last_col_copy, -1, -1):
- for row in xrange(len(board) - 1, -1, -1):
- if board[row][col] != SPACE_VALUE:
- break
- else:
- empty_col = col
- break
- #Prune our table of empty rows and columns
- for row in xrange(empty_row + 1):
- board.pop(0)
- for col in xrange(empty_col + 1):
- for row in xrange(len(board)):
- board[row].pop(0)
- #print_board(board, "Bubbles: After pruning")
- #print 'p',
- return board
- def pick_color(color, color_map, color_list, SPACE_VALUE=SPACE_VALUE):
- # Space 'color' must always return as space
- if color == SPACE_VALUE:
- return ' '
- # Otherwise if we have not picked a color yet than we pop one of our list
- if color_map[color] is None:
- color_map[color] = color_list.pop()
- return color_map[color]
- def normalized_hasher(board, NUM_COLORS=NUM_COLORS, pick_color=pick_color):
- """
- Since there are many combinations of boards that yield the same score we need to optimize the hasher to only
- store one version of the combinations. So we exchange the colors such that the colors are always ordered.
- The first ball(s) is color1, the second balls(s) is color2, etc..
- """
- color_map = [None] * NUM_COLORS
- color_list = ['p', 'b', 'g', 'y', 'r']
- # We add '|' as the salt between rows, so a board X x Y doesn't memoize as Y x X
- return '|'.join(''.join([pick_color(col_x, color_map, color_list) for col_x in rows]) for rows in board)
- def print_solver_best(best):
- num_rows = best['r']
- num_cols = best['c']
- uncompressed_board = [[conversion_c2i[best['b'][row_x * num_cols + col_x]] for col_x in range(num_cols)] for row_x in range(num_rows)]
- print_board(uncompressed_board, "Bubbles: Best Pick(row,col) = (%(y)d, %(x)d) TotalScore = %(t)d PickScore = %(p)d" % best)
- if best['s']:
- print
- print_solver_best(best['s'])
- def solver(board,
- conversion_i2c=conversion_i2c, normalized_hasher=normalized_hasher,
- USE_MEMOIZED=USE_MEMOIZED, MEMOIZATION=MEMOIZATION):
- global SOLVER_COUNT, SOLVER_MEMOIZED
- SOLVER_COUNT += 1
- if USE_MEMOIZED:
- hashed_board = normalized_hasher(board)
- has_best = MEMOIZATION.get(hashed_board, None)
- if has_best is not None:
- SOLVER_MEMOIZED += 1
- return has_best
- # best score for the current board
- # t = Total Score
- # p = The score for the picked bubble group
- # x = The column position of the picked bubble group
- # y = The row position of the picked bubble group
- # b = a compressed version of the board
- # r = number of rows in our board
- # c = number of columns in our board
- # s = solved board
- best = {'t': 0, 'p': 0, 'x': 0, 'y': 0, 's': None}
- # keep track of bubble groups we already picked so we don't go down that path again
- tried_board = [[c for c in row] for row in board]
- #print_board(board, "Bubbles: Before solver()")
- #score_card = [[0] * len(board[0]) for x in xrange(len(board))]
- for row_idx in xrange(len(board)):
- for col_idx in xrange(len(board[0])):
- # if we have already attempted this bubble group. Skip it
- if tried_board[row_idx][col_idx] == X_VALUE:
- continue
- # if empty space. Skip it
- if board[row_idx][col_idx] == SPACE_VALUE:
- continue
- # Create a adjacency count table with x,y set as 1 all others as -1
- adj_count_table = [[-1] * len(board[0]) for x in xrange(len(board))]
- adj_count_table[row_idx][col_idx] = 1
- adj_count_list = [(row_idx, col_idx)]
- color = board[row_idx][col_idx]
- n = compute_ball_adjacency(board, color, adj_count_table, adj_count_list, row_idx, col_idx)
- score = n * (n - 1)
- #score_card[row_idx][col_idx] = score
- #print_board(adj_count_table, "Color: %s Score(%d,%d)=%d Adjacent Balls(%d,%d)=%d Adjacency Table:" % \
- # (conversion_i2c[color], row_idx, col_idx, score, row_idx, col_idx, n),
- # converter=identity_converter)
- if score > 0:
- # mark down in our tried board that we attempted this bubble group
- mark_bubble_group(tried_board, adj_count_list, row_idx, col_idx)
- solved = solver(pick_bubble_group(board, adj_count_list, row_idx, col_idx))
- # If picking this bubble group leads to no points
- if solved['t'] == 0 and score > best['t']:
- # then our best score is just picking this bubble group
- best['t'] = score
- best['x'] = col_idx
- best['y'] = row_idx
- best['s'] = None
- best['p'] = score
- #best['a'] = tuple(adj_count_list)
- # If picking this bubble group leads to a score then add the bubble group score plus the solved board score
- if (solved['t'] + score) > best['t']:
- # We found a better score
- best['t'] = solved['t'] + score
- best['x'] = col_idx
- best['y'] = row_idx
- best['s'] = solved
- best['p'] = score
- #best['a'] = tuple(adj_count_list)
- #print_board(score_card, "Score Card:", converter=identity_converter)
- #print 's',
- if USE_MEMOIZED:
- # Store board if
- if best['t'] > 0:
- best['b'] = ''.join(''.join([conversion_i2c[col_x] for col_x in row]) for row in board)
- best['r'] = len(board)
- best['c'] = len(board[0])
- MEMOIZATION[hashed_board] = best
- return best
- if __name__ == '__main__':
- PrintStats()
- if USE_PSYCO:
- import psyco
- psyco.full()
- best = solver(bubble_board)
- print_solver_best(best)
- global STILL_SOLVING
- STILL_SOLVING = False
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement