|
| 1 | +# v8 logic = |
| 2 | +# v7 logic + set pooling + different algos for each mode - skimming(incompatible with v8 logic) |
| 3 | +# basically have a set of dead pools |
| 4 | +# once a board dies, it goes into the hashed set |
| 5 | +# taking of set union lookup O(1) |
| 6 | + |
| 7 | +# v8.1 |
| 8 | +# removed atrocious deepcopy from the code |
| 9 | + |
| 10 | + |
| 11 | +from copy import Error |
| 12 | +from typing import List |
| 13 | +import itertools |
| 14 | +from itertools import repeat |
| 15 | +import numpy as np |
| 16 | +import pprint |
| 17 | +import heapq |
| 18 | +import copy |
| 19 | + |
| 20 | +import numpy |
| 21 | +import time |
| 22 | + |
| 23 | + |
| 24 | +# improved branching algorithm by creating local and global pools, releasing memory on local pools when they are found useless |
| 25 | + |
| 26 | + |
| 27 | +# practicing the logic in fevertime before applying it to the main game |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +start = time.time() |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +MAX_INT=15 |
| 37 | + |
| 38 | +REMOVED_FROM_MEMORY=None |
| 39 | + |
| 40 | + |
| 41 | +class Path: |
| 42 | + # only used to show a non-zero lost path |
| 43 | + def __init__(self, sel:int, tar:int) -> None: |
| 44 | + self.sel=sel |
| 45 | + self.tar=tar |
| 46 | + def __repr__(self) -> str: |
| 47 | + return f"<Path {self.sel}->{self.tar}>" |
| 48 | + |
| 49 | + |
| 50 | +class BoardLoc: |
| 51 | + def __init__(self, lost:int, id:int) -> None: |
| 52 | + self.lost=lost |
| 53 | + self.id=id |
| 54 | + def __repr__(self) -> str: |
| 55 | + return f"({self.lost}, {self.id})" |
| 56 | + # def is_origin(self) -> bool: |
| 57 | + # return (self.lost==0) and (self.id==0) |
| 58 | + |
| 59 | + |
| 60 | +class B_path: |
| 61 | + def __init__(self, id:BoardLoc=BoardLoc(0,0), paths:List[Path]=[]) -> None: |
| 62 | + self.id=id |
| 63 | + # list of paths |
| 64 | + self.paths=paths[:] |
| 65 | + def __repr__(self) -> str: |
| 66 | + return f"<B_path id={self.id} paths={self.paths}>" |
| 67 | + |
| 68 | +# a board class |
| 69 | +# a board always has its id but its never stored inside the id |
| 70 | + |
| 71 | +class FeverBoard: |
| 72 | + # completely changed in form |
| 73 | + def __init__(self, board, last_board:BoardLoc) -> None: |
| 74 | + self.board=board |
| 75 | + self.last_board=last_board |
| 76 | + self.hash=hash(board.tobytes()) |
| 77 | + |
| 78 | + def __repr__(self) -> str: |
| 79 | + return f"<FeverBoard {self.board} last_board={self.last_board}>" |
| 80 | + |
| 81 | + |
| 82 | + def get_valid_moves(self, loc:BoardLoc): |
| 83 | + # advantages of having a path group |
| 84 | + # share same id and reduce the amount of evaluation at a given time |
| 85 | + """returns a list of B_paths which is also a list\n |
| 86 | + will be concatnated later\n |
| 87 | + ex)\n |
| 88 | + [\n |
| 89 | + 0: [int,int,int] |
| 90 | + 1: B_path |
| 91 | + 2: B_path |
| 92 | + ...\n |
| 93 | + ]\n |
| 94 | + Remember to not append 0 length path values""" |
| 95 | + rt = [[]]+[B_path(loc) for x in repeat(None, MAX_INT)] |
| 96 | + # 15 elements |
| 97 | + # print(rt) |
| 98 | + |
| 99 | + nonzero=np.where(self.board != 0)[0] |
| 100 | + if len(nonzero) == 0: |
| 101 | + raise Error("ASKED TO FIND PATHS ON AN EMPTY BOARD") |
| 102 | + if len(nonzero) == 1: |
| 103 | + # there are multiple cases here |
| 104 | + if self.board[nonzero[0]] == 1: |
| 105 | + # if there is only 1 number |
| 106 | + rt[nonzero[0]+1].paths.append(Path(np.byte(nonzero[0]), np.byte(nonzero[0]))) |
| 107 | + return rt |
| 108 | + rt[0].append(nonzero[0]) |
| 109 | + return rt |
| 110 | + for key, select in enumerate(nonzero): |
| 111 | + # print("-- select --") |
| 112 | + # print(select) |
| 113 | + if self.board[select] > 1: |
| 114 | + # print("tile appears more than twice. adding paired") |
| 115 | + rt[0].append(np.byte(select)) # in c, it would be presented as a single byte |
| 116 | + # print("-- target --") |
| 117 | + for target in nonzero[key+1:]: |
| 118 | + # print(target) |
| 119 | + rt[min(target, select)+1].paths.append(Path(np.byte(select), np.byte(target))) |
| 120 | + return rt |
| 121 | + |
| 122 | + # methods to do stuff |
| 123 | + def copy_do_move_pair_move(self, loc:BoardLoc, sel:int): |
| 124 | + """ |
| 125 | + this method does a move thats "paired" and returns a new instance |
| 126 | + """ |
| 127 | + board=copy.copy(self.board) |
| 128 | + # board elements are primitives. |
| 129 | + # shallow copy will work as well |
| 130 | + board[sel]-=2 |
| 131 | + # change the value |
| 132 | + return FeverBoard(board, loc) |
| 133 | + def copy_do_move_lost(self, loc:BoardLoc, sel:int, tar:int): |
| 134 | + """ |
| 135 | + this method does a move where "sel" and "tar" are different and returns a new instance |
| 136 | + """ |
| 137 | + board=copy.copy(self.board) |
| 138 | + if sel == tar: |
| 139 | + # the only time it is the same is when the board only has 1 number |
| 140 | + # otherwise, its going to be a paired move with lost 0 |
| 141 | + board[sel]-=1 |
| 142 | + else: |
| 143 | + board[sel]-=1 |
| 144 | + board[tar]-=1 |
| 145 | + board[tar-sel-1]+=1 |
| 146 | + # board elements are primitives. |
| 147 | + # shallow copy will work as well |
| 148 | + # change the value |
| 149 | + return FeverBoard(board, loc) |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + def __hash__(self): |
| 154 | + return self.hash |
| 155 | + def __eq__(self, other: object) -> bool: |
| 156 | + return self.hash==other.hash |
| 157 | + |
| 158 | + def is_finished(self) -> bool: |
| 159 | + return not np.any(self.board, 0) |
| 160 | + |
| 161 | + def format_board(board): |
| 162 | + return_board=np.zeros(MAX_INT, numpy.byte) # extremely efficient |
| 163 | + for tile in board: |
| 164 | + return_board[tile-1]+=1 |
| 165 | + return return_board |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | +hash_dict={} |
| 171 | +# stores hash |
| 172 | +# { |
| 173 | +# __hash__:BoardLoc |
| 174 | +# __hash__:BoardLoc |
| 175 | +# } |
| 176 | + |
| 177 | +# used to look up instances |
| 178 | +# BoardLoc contains the attribute lost which can be used to evalutate which board is better |
| 179 | + |
| 180 | + |
| 181 | +global_board_pool=[[] for _ in repeat(None, 30)] |
| 182 | +global_path_pool=[[] for _ in repeat(None, 30)] |
| 183 | + |
| 184 | + |
| 185 | +# we dont evaluate paths until needed |
| 186 | +# evalutate boards |
| 187 | +# evalutate paths |
| 188 | +# repeat |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +# print("="*100) |
| 193 | +original_board=FeverBoard(FeverBoard.format_board([6,10,6,7,15,9,7,3,4,8]), None) |
| 194 | +# print(original_board) |
| 195 | +global_board_pool[0].append(original_board) |
| 196 | +hash_dict[original_board.hash]=BoardLoc(0,0) |
| 197 | + |
| 198 | + |
| 199 | +# pprint.pprint(global_board_pool) |
| 200 | +# pprint.pprint(hash_dict) |
| 201 | + |
| 202 | +# pprint.pprint(FeverBoard(FeverBoard.format_board([3]), None).get_valid_moves(BoardLoc(0,0))) |
| 203 | + |
| 204 | +# new_board=original_board.copy_do_move_pair_move(BoardLoc(0,0), 0) |
| 205 | +# new_board=FeverBoard(FeverBoard.format_board([6,10,6,7,15,9,7,3,4,8]), BoardLoc(1,2)) |
| 206 | +# print(original_board.hash) |
| 207 | +# print(new_board.hash) |
| 208 | + |
| 209 | + |
| 210 | +# exit() |
| 211 | + |
| 212 | +cut=0 |
| 213 | +def board_eval_recursive(root_board, board_lost): |
| 214 | + # print(root_board.hash) |
| 215 | + if root_board.is_finished() == True: |
| 216 | + print(f"found at cut {cut}") |
| 217 | + print("time took") |
| 218 | + print(time.time()-start) |
| 219 | + return |
| 220 | + bl=BoardLoc(board_lost, len(global_board_pool[board_lost])) |
| 221 | + if root_board.hash in hash_dict: |
| 222 | + if hash_dict[root_board.hash].lost < board_lost: |
| 223 | + # raise Error("AN EXISTING HASH DICTIONARY HAD LOWER LOST THAN NEW") |
| 224 | + # this statement should also return the funciton |
| 225 | + # i just wanted to test some theory |
| 226 | + return |
| 227 | + if hash_dict[root_board.hash].lost > board_lost: |
| 228 | + print("DEBUG: dictionary board had higher lost than new board. replacing...") |
| 229 | + global_board_pool[hash_dict[root_board.hash].lost][hash_dict[root_board.hash].id]=None |
| 230 | + # set the original board to a compromised board = "None" |
| 231 | + # this is a bad practice but memorywise better |
| 232 | + hash_dict[root_board.hash]=bl |
| 233 | + else: |
| 234 | + return |
| 235 | + else: |
| 236 | + hash_dict[root_board.hash]=bl |
| 237 | + # print(bl) |
| 238 | + global_board_pool[bl.lost].append(root_board) |
| 239 | + moves=root_board.get_valid_moves(bl) |
| 240 | + for move in moves[0]: |
| 241 | + board_eval_recursive(root_board.copy_do_move_pair_move(bl, move), board_lost) |
| 242 | + path_lost=board_lost |
| 243 | + for bpath in moves[1:]: |
| 244 | + # get the remaining paths |
| 245 | + path_lost+=1 |
| 246 | + if len(bpath.paths) > 0: |
| 247 | + global_path_pool[path_lost].append(bpath) |
| 248 | + # means we didnt find a solution |
| 249 | + return None |
| 250 | +def construct_boards(board): |
| 251 | + boards=[board] |
| 252 | + board=global_board_pool[board.last_board.lost][board.last_board.id] |
| 253 | + while board.last_board != None: |
| 254 | + boards.append(board) |
| 255 | + board=global_board_pool[board.last_board.lost][board.last_board.id] |
| 256 | + boards.append(global_board_pool[0][0]) |
| 257 | + return boards[::-1] |
| 258 | + |
| 259 | + |
| 260 | + |
| 261 | + |
| 262 | +def main(): |
| 263 | + cut=0 |
| 264 | + for _ in repeat(None, 20): |
| 265 | + # print("----------") |
| 266 | + print(f"cut={cut}") |
| 267 | + # evaluate all 0 boards |
| 268 | + # this shallow copy will allow the board to not get interuppted by the for loop |
| 269 | + for id, board in enumerate(global_board_pool[cut][:]): |
| 270 | + if board is None: continue |
| 271 | + bl=BoardLoc(cut, id) |
| 272 | + # print(f"Looping board id {bl}") |
| 273 | + moves=board.get_valid_moves(bl) |
| 274 | + for paired_move in moves[0]: |
| 275 | + board_eval_recursive(board.copy_do_move_pair_move(bl, paired_move), cut) |
| 276 | + |
| 277 | + path_lost=cut |
| 278 | + for bpath in moves[1:]: |
| 279 | + # get the remaining paths |
| 280 | + path_lost+=1 |
| 281 | + if len(bpath.paths) > 0: |
| 282 | + global_path_pool[path_lost].append(bpath) |
| 283 | + |
| 284 | + # print("="*100) |
| 285 | + # print("board pool") |
| 286 | + # pprint.pprint(global_board_pool) |
| 287 | + # print("="*100) |
| 288 | + # # print("path pool") |
| 289 | + # # pprint.pprint(global_path_pool) |
| 290 | + # print("="*100) |
| 291 | + # print("dict pool") |
| 292 | + # pprint.pprint(hash_dict) |
| 293 | + |
| 294 | + # print("-----") |
| 295 | + # print(f"failed to find within cut:{cut}") |
| 296 | + cut+=1 |
| 297 | + # print(f"cut is now {cut}") |
| 298 | + # print("generating new boards...") |
| 299 | + for bpath in global_path_pool[cut]: |
| 300 | + # print(f"evaluating board at loc {bpath.id}") |
| 301 | + stem_board=global_board_pool[bpath.id.lost][bpath.id.id] |
| 302 | + for p in bpath.paths: |
| 303 | + bl=BoardLoc(cut, len(global_board_pool[cut])) |
| 304 | + new_board=stem_board.copy_do_move_lost(bpath.id, p.sel, p.tar) |
| 305 | + # check hash before adding a board |
| 306 | + # probably optimize this in v9 |
| 307 | + if new_board.is_finished() == True: |
| 308 | + print(f"found at cut {cut}") |
| 309 | + print("time took") |
| 310 | + print(time.time()-start) |
| 311 | + return |
| 312 | + if new_board.hash in hash_dict: |
| 313 | + if hash_dict[new_board.hash].lost < cut+1: |
| 314 | + # raise Error("AN EXISTING HASH DICTIONARY HAD LOWER LOST THAN NEW") |
| 315 | + # # this statement should also return the funciton |
| 316 | + # # i just wanted to test some theory |
| 317 | + # do nothing |
| 318 | + pass |
| 319 | + if hash_dict[new_board.hash].lost > cut+1: |
| 320 | + print("DEBUG: dictionary board had higher lost than new board. replacing...") |
| 321 | + global_board_pool[hash_dict[new_board.hash].lost][hash_dict[new_board.hash].id]=None |
| 322 | + # set the original board to a compromised board = "None" |
| 323 | + # this is a bad practice but memorywise better |
| 324 | + hash_dict[new_board.hash]=bl |
| 325 | + # append because this is a better board |
| 326 | + global_board_pool[cut].append(new_board) |
| 327 | + # if hash_dict[new_board.hash].lost == new_board: |
| 328 | + # print("++++++++++++++++++++++++++") |
| 329 | + else: |
| 330 | + hash_dict[new_board.hash]=bl |
| 331 | + # append if hash was different |
| 332 | + global_board_pool[cut].append(new_board) |
| 333 | + global_path_pool[cut]=REMOVED_FROM_MEMORY |
| 334 | + # print("done adding boards") |
| 335 | + # print("="*100) |
| 336 | + # print("board pool") |
| 337 | + # pprint.pprint(global_board_pool) |
| 338 | + # print("="*100) |
| 339 | + # print("path pool") |
| 340 | + # pprint.pprint(global_path_pool) |
| 341 | + # print("="*100) |
| 342 | + # print("dict pool") |
| 343 | + # pprint.pprint(hash_dict) |
| 344 | +main() |
| 345 | +main() |
| 346 | +main() |
| 347 | +main() |
0 commit comments