Guest User

Untitled

a guest
Dec 15th, 2017
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.09 KB | None | 0 0
  1. import numpy as np
  2. from functools import lru_cache
  3.  
  4.  
  5. def get_position_mask_bitmap(board, player):
  6. """Convert the game board numpy representation
  7. to a bitmap (encoded as integer). Store a bitmap
  8. for the positions of player his tokens and a
  9. bitmap for occupied cells. The order of
  10. the bits is the following (0-bit is least significant):
  11. . . . . . . .
  12. 5 12 19 26 33 40 47
  13. 4 11 18 25 32 39 46
  14. 3 10 17 24 31 38 45
  15. 2 9 16 23 30 37 44
  16. 1 8 15 22 29 36 43
  17. 0 7 14 21 28 35 42
  18.  
  19. Args:
  20. board (numpy array)
  21. player (int (1 or 2))
  22.  
  23. Return:
  24. position (int): token bitmap
  25. mask (int): occupied cell bitmap
  26. """
  27. position, mask = '', ''
  28. for j in range(6, -1, -1):
  29. mask += '0'
  30. position += '0'
  31. for i in range(0, 6):
  32. mask += ['0', '1'][board[i, j] != 0]
  33. position += ['0', '1'][board[i, j] == player]
  34. return int(position, 2), int(mask, 2)
  35.  
  36.  
  37. def print_bitmap(bitmap):
  38. """Print out the bitmap (7x7), encoded as int"""
  39. bitstring = bin(bitmap)[2:]
  40. bitstring = '0'*(49 - len(bitstring)) + bitstring
  41. for i in range(7):
  42. print(bitstring[i::7][::-1])
  43.  
  44.  
  45. @lru_cache(maxsize=None)
  46. def is_legal_move(mask, col):
  47. """If there is no 1-bit in the highest row
  48. of column `col`, then we can move there"""
  49. return (mask & (1 << 5 << (col*7))) == 0
  50.  
  51.  
  52. @lru_cache(maxsize=None)
  53. def make_move(position, mask, col):
  54. """Flip the highest zero-bit in column `col`
  55. of the mask bitmap and flip the bits in the
  56. position bitmap for the opponent to make a move"""
  57. new_position = position ^ mask
  58. new_mask = mask | (mask + (1 << (col*7)))
  59. return new_position, new_mask
  60.  
  61.  
  62. @lru_cache(maxsize=None)
  63. def connected_four(position):
  64. # Horizontal check
  65. m = position & (position >> 7)
  66. if m & (m >> 14):
  67. return True
  68.  
  69. # Diagonal \
  70. m = position & (position >> 6)
  71. if m & (m >> 12):
  72. return True
  73.  
  74. # Diagonal /
  75. m = position & (position >> 8)
  76. if m & (m >> 16):
  77. return True
  78.  
  79. # Vertical
  80. m = position & (position >> 1)
  81. if m & (m >> 2):
  82. return True
  83.  
  84. # Nothing found
  85. return False
  86.  
  87.  
  88. @lru_cache(maxsize=None)
  89. def possible(mask):
  90. bottom_mask = 4432676798593
  91. board_mask = bottom_mask * 63
  92. return (mask + bottom_mask) & board_mask
  93.  
  94.  
  95. @lru_cache(maxsize=None)
  96. def compute_winning_position(position, mask):
  97. # Vertical
  98. r = (position << 1) & (position << 2) & (position << 3)
  99.  
  100. # Horizontal
  101. p = (position << 7) & (position << 14)
  102. r |= p & (position << 21)
  103. r |= p & (position >> 7)
  104. p >>= 21
  105. r |= p & (position << 7)
  106. r |= p & (position >> 21)
  107.  
  108. # Diagonal \
  109. p = (position << 6) & (position << 12)
  110. r |= p & (position << 18)
  111. r |= p & (position >> 6)
  112. p >>= 18
  113. r |= p & (position << 6)
  114. r |= p & (position >> 18)
  115.  
  116. # Diagonal /
  117. p = (position << 8) & (position << 16)
  118. r |= p & (position << 24)
  119. r |= p & (position >> 8)
  120. p >>= 24
  121. r |= p & (position << 8)
  122. r |= p & (position >> 24)
  123.  
  124. return r & (4432676798593 * 63 ^ mask)
  125.  
  126.  
  127. @lru_cache(maxsize=None)
  128. def get_columns_with_bit(bmp):
  129. cols = [63, 8064, 1032192, 132120576, 16911433728,
  130. 2164663517184, 277076930199552]
  131. return [i for i in range(len(cols)) if cols[i] & bmp]
  132.  
  133.  
  134. @lru_cache(maxsize=None)
  135. def possible_non_losing_moves(position, mask, possible_mask):
  136. opponent_win = compute_winning_position(position ^ mask, mask)
  137. forced_moves = possible_mask & opponent_win
  138. if forced_moves:
  139. if forced_moves & (forced_moves - 1):
  140. return []
  141. else:
  142. possible_mask = forced_moves
  143. return get_columns_with_bit(possible_mask & ~(opponent_win >> 1))
  144.  
  145.  
  146. @lru_cache(maxsize=None)
  147. def popcount(m):
  148. c = 0
  149. while m:
  150. m &= (m - 1)
  151. c += 1
  152. return c
  153.  
  154.  
  155. @lru_cache(maxsize=2048)
  156. def insertion(M):
  157. idx = list(range(len(M)))
  158. M = list(M)
  159. for i in range(1, len(M)):
  160. if M[i] >= M[i-1]:
  161. continue
  162. for j in range(i):
  163. if M[i] < M[j]:
  164. M[j], M[j+1:i+1] = M[i], M[j:i]
  165. idx[j], idx[j+1:i+1] = idx[i], idx[j:i]
  166. break
  167. return M, idx
  168.  
  169.  
  170. def alphabeta_search(position, mask, ub_cache=None,
  171. lb_cache=None, valid_actions=[]):
  172. """Search game to determine best action; use alpha-beta pruning.
  173. this version searches all the way to the leaves."""
  174.  
  175. # First explore columns in the center
  176. actions = [3, 2, 4, 1, 5, 0, 6]
  177.  
  178. # Initialize our cache (dynamic programming)
  179. if ub_cache is None:
  180. ub_cache = {}
  181. if lb_cache is None:
  182. lb_cache = {}
  183.  
  184. # Functions used by alphabeta
  185. def max_value(position, mask, alpha, beta, moves):
  186. possible_mask = possible(mask)
  187.  
  188. # Check if game terminates
  189. if compute_winning_position(position, mask) & possible_mask:
  190. return int(np.ceil((42 - moves) / 2))
  191.  
  192. key = position + mask
  193.  
  194. # Try to get an upper bound for this position from cache
  195. try:
  196. v = ub_cache[key]
  197. if beta >= v:
  198. beta = v
  199. except KeyError as e:
  200. pass
  201.  
  202. v = max(alpha, (-42 + moves)//2)
  203. if v >= alpha:
  204. alpha = v
  205.  
  206. if alpha >= beta:
  207. return beta
  208.  
  209. if moves < 30:
  210. # This helps for positions close to the end
  211. _actions = possible_non_losing_moves(position, mask, possible_mask)
  212. if not len(_actions):
  213. return -int(np.ceil((41 - moves) / 2))
  214.  
  215. nr_winning_actions_per_col = [0]*7
  216. for col in actions:
  217. new_position, new_mask = make_move(position, mask, col)
  218. nr_winning_actions_per_col[col] = popcount(
  219. compute_winning_position(new_position ^ new_mask, new_mask)
  220. )
  221.  
  222. # This can possibly be replaced with np.argsort
  223. _actions = insertion(tuple(nr_winning_actions_per_col))[1][::-1]
  224. else:
  225. _actions = actions
  226.  
  227. for col in _actions:
  228. if is_legal_move(mask, col):
  229. new_position, new_mask = make_move(position, mask, col)
  230. v = max(v, min_value(
  231. new_position, new_mask,
  232. alpha, beta, moves + 1
  233. )
  234. )
  235. if v >= beta:
  236. return v
  237. if v > 0: # We found a move towards a win (not optimal)
  238. return v
  239. alpha = max(alpha, v)
  240.  
  241. # Put our new upper bound in the cache
  242. ub_cache[key] = alpha
  243. return v
  244.  
  245. def min_value(position, mask, alpha, beta, moves):
  246. possible_mask = possible(mask)
  247.  
  248. # Check if game terminates
  249. if compute_winning_position(position, mask) & possible_mask:
  250. return -int(np.ceil((42 - moves) / 2))
  251.  
  252. key = position + mask
  253.  
  254. try:
  255. v = lb_cache[key]
  256. if alpha <= v:
  257. alpha = v
  258. except KeyError as e:
  259. pass
  260.  
  261. v = min(beta, (42 - moves)//2)
  262. if v <= beta:
  263. beta = v
  264.  
  265. if alpha >= beta:
  266. return alpha
  267.  
  268. if moves < 30:
  269. # This helps for positions close to the end
  270. _actions = possible_non_losing_moves(position, mask, possible_mask)
  271. if not len(_actions):
  272. return int(np.ceil((41 - moves) / 2))
  273.  
  274. nr_winning_actions_per_col = [0]*7
  275. for col in actions:
  276. new_position, new_mask = make_move(position, mask, col)
  277. nr_winning_actions_per_col[col] = popcount(
  278. compute_winning_position(new_position ^ new_mask, new_mask)
  279. )
  280.  
  281. # This can possibly be replaced with np.argsort
  282. _actions = insertion(tuple(nr_winning_actions_per_col))[1][::-1]
  283. else:
  284. _actions = actions
  285.  
  286. for col in _actions:
  287. if is_legal_move(mask, col):
  288. new_position, new_mask = make_move(position, mask, col)
  289. v = min(v, max_value(
  290. new_position, new_mask,
  291. alpha, beta, moves + 1
  292. )
  293. )
  294. if v <= alpha:
  295. return v
  296. if v < 0: # We found a move towards a win (not optimal)
  297. return v
  298. beta = min(beta, v)
  299.  
  300. lb_cache[key] = beta
  301. return v
  302.  
  303. # Body of alphabeta_cutoff_search:
  304. n_bits = popcount(mask)
  305. best_score = -(42 - n_bits)//2
  306. beta = (42 - n_bits)//2
  307. best_action = None
  308.  
  309. nr_winning_actions_per_col = [0]*7
  310. for col in actions:
  311. if col in valid_actions and is_legal_move(mask, col):
  312. new_position, new_mask = make_move(position, mask, col)
  313. if connected_four(new_position ^ new_mask):
  314. return col, int(np.ceil((43 - n_bits - 1) / 2))
  315. nr_winning_actions_per_col[col] = popcount(
  316. compute_winning_position(new_position ^ new_mask, new_mask)
  317. )
  318.  
  319. actions = insertion(tuple(nr_winning_actions_per_col))[1][::-1]
  320.  
  321. for col in actions:
  322. if is_legal_move(mask, col):
  323. new_position, new_mask = make_move(position, mask, col)
  324.  
  325. v = min_value(new_position, new_mask, best_score, beta, n_bits + 1)
  326. if v > best_score:
  327. best_score = v
  328. best_action = col
  329.  
  330. return best_action, best_score
  331.  
  332.  
  333. def generate_move(board, player, saved_state):
  334. """Contains all code required to generate a move,
  335. given a current game state (board & player)
  336.  
  337. Args:
  338.  
  339. board (2D np.array): game board (element is 0, 1 or 2)
  340. player (int): your player number (token to place: 1 or 2)
  341. saved_state (object): returned value from previous call
  342.  
  343. Returns:
  344.  
  345. action (int): number in [0, 6]
  346. saved_state (optional, object): will be returned to you the
  347. next time your function is called
  348.  
  349. """
  350. position, mask = get_position_mask_bitmap(board, player)
  351. return alphabeta_search(position, mask)
Add Comment
Please, Sign In to add comment