├── LICENSE.txt ├── README.md ├── agent.py ├── arthur.py ├── checkers.py ├── game.py ├── logfile ├── random_agent.py └── test.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 Andrew Edwards 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Checkers AI: a look at Arthur Samuel's ideas 2 | ===================================== 3 | 4 | Usage 5 | --- 6 | Download project. Navigate to directory. Do `python game.py`, and type in `arthur` when prompted for agent module. 7 | 8 | (Note: to adjust how long the computer player "thinks" about its next move, you can vary the default depth parameter of the look ahead search. Go into `arthur.py` and change `depth=x` parameter of the function `move_function`.) 9 | 10 | Files 11 | --- 12 | 13 | 14 | `checkers.py` 15 | 16 | This file contains the definition of the CheckerBoard class. Its methods include new game 17 | initialization, ASCII printed output, and getting legal moves from a given state. 18 | 19 | `agent.py` 20 | 21 | This file contains the implementation of the AI CheckersAgent class. All that is required 22 | of a CheckersAgent is that it have a function move_function() that takes in a game state and 23 | returns a legal move. 24 | 25 | `arthur.py` 26 | 27 | This file contains the implementation of an agent that is inspired by Arthur Samuel's 28 | historic machine learning checkers program. 29 | 30 | `game.py` 31 | 32 | This file contains the harness for running an actual game of checkers. 33 | 34 | > Written with [StackEdit](https://stackedit.io/). 35 | -------------------------------------------------------------------------------- /agent.py: -------------------------------------------------------------------------------- 1 | # Andrew Edwards -- almostimplemented.com 2 | # ======================================= 3 | # A checkers agent class. 4 | # 5 | # Last updated: July 21, 2014 6 | 7 | 8 | class CheckersAgent: 9 | def __init__(self, move_function): 10 | self.move_function = move_function 11 | 12 | def make_move(self, board): 13 | return self.move_function(board) 14 | -------------------------------------------------------------------------------- /arthur.py: -------------------------------------------------------------------------------- 1 | # Andrew Edwards -- almostimplemented.com 2 | # ======================================= 3 | # A checkers agent implementaiton based 4 | # on Arthur Samuel's historic program. 5 | # 6 | # Last updated: July 21, 2014 7 | 8 | import sys 9 | 10 | # Constants 11 | BLACK, WHITE = 0, 1 12 | VALID_SQUARES = 0x7FBFDFEFF 13 | 14 | INFINITY = sys.maxsize 15 | 16 | # Feature functions 17 | def adv(board): # Advancement 18 | """ 19 | The parameter is credited with 1 for each passive man in the 20 | fifth and sixth rows (counting in passive's direction) and 21 | debited with 1 for each passive man in the third and fourth 22 | rows. 23 | """ 24 | passive = board.passive 25 | 26 | rows_3_and_4 = 0x1FE00 27 | rows_5_and_6 = 0x3FC0000 28 | if passive == WHITE: 29 | rows_3_and_4, rows_5_and_6 = rows_5_and_6, rows_3_and_4 30 | 31 | bits_3_and_4 = rows_3_and_4 & board.pieces[passive] 32 | bits_5_and_6 = rows_5_and_6 & board.pieces[passive] 33 | return bin(bits_5_and_6).count("1") - bin(bits_3_and_4).count("1") 34 | 35 | def back(board): # Back Row Bridge 36 | """ 37 | The parameter is credited with 1 if there are no active kings 38 | on the board and if the two bridge squares (1 and 3, or 30 and 39 | 32) in the back row are occupied by passive pieces. 40 | """ 41 | active = board.active 42 | passive = board.passive 43 | if active == BLACK: 44 | if board.backward[BLACK] != 0: 45 | return 0 46 | back_row_bridge = 0x480000000 47 | else: 48 | if board.forward[WHITE] != 0: 49 | return 0 50 | back_row_bridge = 0x5 51 | 52 | if bin(back_row_bridge & board.pieces[passive]).count('1') == 2: 53 | return 1 54 | return 0 55 | 56 | def cent(board): # Center Control I 57 | """ 58 | The parameter is credited with 1 for each of the following 59 | squares: 11, 12, 15, 16, 20, 21, 24, 25 which is occupied by 60 | a passive man. 61 | """ 62 | passive = board.passive 63 | if passive == WHITE: 64 | center_pieces = 0xA619800 65 | else: 66 | center_pieces = 0xCC3280 67 | 68 | return bin(board.pieces[passive] & center_pieces).count("1") 69 | 70 | def cntr(board): # Center Control II 71 | """ 72 | The parameter is credited with 1 for each of the following 73 | squares: 11, 12, 15, 16, 20, 21, 24, 25 that is either 74 | currently occupied by an active piece or to which an active 75 | piece can move. 76 | """ 77 | active = board.active 78 | if active == BLACK: 79 | center_pieces = 0xA619800 80 | else: 81 | center_pieces = 0xCC3280 82 | 83 | active_center_count = bin(board.pieces[active] & center_pieces).count("1") 84 | 85 | moves = board.get_moves() 86 | if moves[0] < 0: 87 | moves = map(lambda x: x*(-1), moves) 88 | destinations = reduce(lambda x, y: x|y, 89 | [(m & (m ^ board.pieces[active])) for m in moves]) 90 | 91 | active_near_center_count = bin(destinations & center_pieces).count("1") 92 | 93 | return active_center_count + active_near_center_count 94 | 95 | def deny(board): # Denial of Occupancy 96 | """ 97 | The parameter is credited 1 for each square defined in MOB if 98 | on the next move a piece occupying this square could be 99 | captured without exchange. 100 | """ 101 | rf = board.right_forward() 102 | lf = board.left_forward() 103 | rb = board.right_backward() 104 | lb = board.left_backward() 105 | 106 | moves = [0x11 << i for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 107 | moves += [0x21 << i for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 108 | moves += [0x11 << i - 4 for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 109 | moves += [0x21 << i - 5 for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 110 | 111 | destinations = [0x10 << i for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 112 | destinations += [0x20 << i for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 113 | destinations += [0x1 << i - 4 for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 114 | destinations += [0x1 << i - 5 for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 115 | 116 | denials = [] 117 | 118 | for move, dest in zip(moves, destinations): 119 | B = board.peek_move(move) 120 | active = B.active 121 | ms_taking = [] 122 | ds = [] 123 | if (B.forward[active] & (dest >> 4)) != 0 and (B.empty & (dest << 4)) != 0: 124 | ms_taking.append((-1)*((dest >> 4) | (dest << 4))) 125 | ds.append(dest << 4) 126 | if (B.forward[active] & (dest >> 5)) != 0 and (B.empty & (dest << 5)) != 0: 127 | ms_taking.append((-1)*((dest >> 5) | (dest << 5))) 128 | ds.append(dest << 5) 129 | if (B.backward[active] & (dest << 4)) != 0 and (B.empty & (dest >> 4)) != 0: 130 | ms_taking.append((-1)*((dest << 4) | (dest >> 4))) 131 | ds.append(dest >> 4) 132 | if (B.backward[active] & (dest << 5)) != 0 and (B.empty & (dest >> 5)) != 0: 133 | ms_taking.append((-1)*((dest << 5) | (dest >> 5))) 134 | ds.append(dest >> 5) 135 | 136 | if not ms_taking: 137 | continue 138 | else: 139 | for m, d in zip(ms_taking, ds): 140 | C = B.peek_move(m) 141 | if C.active == active: 142 | if not dest in denials: 143 | denials.append(dest) 144 | continue 145 | if not C.takeable(d): 146 | if not dest in denials: 147 | denials.append(dest) 148 | 149 | return len(denials) 150 | 151 | 152 | def kcent(board): # King Center Control 153 | """ 154 | The parameter is credited with 1 for each of the following 155 | squares: 11, 12, 15, 16, 20, 21, 24, and 25 which is occupied 156 | by a passive king. 157 | """ 158 | passive = board.passive 159 | if passive == WHITE: 160 | center_pieces = 0xA619800 161 | passive_kings = board.forward[WHITE] 162 | else: 163 | center_pieces = 0xCC3280 164 | passive_kings = board.backward[BLACK] 165 | 166 | return bin(passive_kings & center_pieces).count("1") 167 | 168 | 169 | def mob(board): # Total Mobility 170 | """ 171 | The parameter is credited with 1 for each square to which the 172 | active side could move one or more pieces in the normal fashion 173 | disregarding the fact that jump moves may or may not be 174 | available. 175 | """ 176 | rf = board.right_forward() 177 | lf = board.left_forward() 178 | rb = board.right_backward() 179 | lb = board.left_backward() 180 | 181 | destinations = [0x10 << i for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 182 | destinations += [0x20 << i for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 183 | destinations += [0x1 << i - 4 for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 184 | destinations += [0x1 << i - 5 for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 185 | 186 | if not destinations: 187 | return 0 188 | return bin(reduce(lambda x, y: x|y, destinations)).count("1") 189 | 190 | def mobil(board): # Undenied Mobility 191 | """ 192 | The parameter is credited with the difference between MOB and 193 | DENY. 194 | """ 195 | return mob(board) - deny(board) 196 | 197 | def mov(board): # Move 198 | """ 199 | The parameter is credited with 1 if pieces are even with a 200 | total piece count (2 for men, and 3 for kings) of less than 24, 201 | and if an odd number of pieces are in the move system, defined 202 | as those vertical files starting with squares 1, 2, 3, and 4. 203 | """ 204 | black_men = bin(board.forward[BLACK]).count("1") 205 | black_kings = bin(board.backward[BLACK]).count("1") 206 | black_score = 2*black_men + 3*black_kings 207 | white_men = bin(board.backward[WHITE]).count("1") 208 | white_kings = bin(board.forward[WHITE]).count("1") 209 | white_score = 2*white_men + 3*white_kings 210 | 211 | if white_score < 24 and black_score == white_score: 212 | pieces = board.pieces[BLACK] | board.pieces[WHITE] 213 | if board.active == BLACK: 214 | move_system = 0x783c1e0f 215 | else: 216 | move_system = 0x783c1e0f0 217 | if bin(move_system & pieces).count("1") % 2 == 1: 218 | return 1 219 | 220 | return 0 221 | 222 | def thret(board): # Threat 223 | """ 224 | The parameter is credited with 1 for each square to which an 225 | active piece may be moved and in doing so threaten to capture 226 | a passive piece on a subsequent move. 227 | """ 228 | moves = board.get_moves() 229 | destinations = map(lambda x: (x ^ board.pieces[board.active]) & x, moves) 230 | origins = [x ^ y for (x, y) in zip(moves, destinations)] 231 | 232 | jumps = [] 233 | for dest, orig in zip(destinations, origins): 234 | if board.active == BLACK: 235 | rfj = (board.empty >> 8) & (board.pieces[board.passive] >> 4) & dest 236 | lfj = (board.empty >> 10) & (board.pieces[board.passive] >> 5) & dest 237 | if orig & board.backward[board.active]: # piece is king 238 | rbj = (board.empty << 8) & (board.pieces[board.passive] << 4) & dest 239 | lbj = (board.empty << 10) & (board.pieces[board.passive] << 5) & dest 240 | else: 241 | rbj = 0 242 | lbj = 0 243 | else: 244 | rbj = (board.empty << 8) & (board.pieces[board.passive] << 4) & dest 245 | lbj = (board.empty << 10) & (board.pieces[board.passive] << 5) & dest 246 | if dest & board.forward[board.active]: # piece at square is a king 247 | rfj = (board.empty >> 8) & (board.pieces[board.passive] >> 4) & dest 248 | lfj = (board.empty >> 10) & (board.pieces[board.passive] >> 5) & dest 249 | else: 250 | rfj = 0 251 | lfj = 0 252 | 253 | if (rfj | lfj | rbj | lbj) != 0: 254 | jumps += [-0x101 << i for (i, bit) in enumerate(bin(rfj)[::-1]) if bit == '1'] 255 | jumps += [-0x401 << i for (i, bit) in enumerate(bin(lfj)[::-1]) if bit == '1'] 256 | jumps += [-0x101 << i - 8 for (i, bit) in enumerate(bin(rbj)[::-1]) if bit == '1'] 257 | jumps += [-0x401 << i - 10 for (i, bit) in enumerate(bin(lbj)[::-1]) if bit == '1'] 258 | 259 | return len(jumps) 260 | 261 | def piece_score_diff(board, player): 262 | black_men = bin(board.forward[BLACK]).count("1") 263 | black_kings = bin(board.backward[BLACK]).count("1") 264 | black_score = 2*black_men + 3*black_kings 265 | white_men = bin(board.backward[WHITE]).count("1") 266 | white_kings = bin(board.forward[WHITE]).count("1") 267 | white_score = 2*white_men + 3*white_kings 268 | 269 | return black_score - white_score if player == BLACK else white_score - black_score 270 | 271 | def position_score(board, player): 272 | scores = [0x88000, 0x1904c00, 0x3A0502E0, 0x7C060301F] 273 | i = 1 274 | total = 0 275 | for s in scores: 276 | total = i*bin(board.pieces[player] & s).count("1") 277 | i += 1 278 | return total 279 | 280 | def score(board_old, board_new): 281 | if board_old.is_over(): 282 | return -INFINITY 283 | if board_new.is_over(): 284 | return INFINITY 285 | _adv = adv(board_new) - adv(board_old) 286 | _back = adv(board_new) - back(board_old) 287 | _cent = cent(board_new) - cent(board_old) 288 | _cntr = cntr(board_new) - cntr(board_old) 289 | _deny = deny(board_new) - deny(board_old) 290 | _kcent = kcent(board_new) - kcent(board_old) 291 | _mob = mob(board_new) - mob(board_old) 292 | _mobil = _mob - _deny 293 | _mov = mov(board_new) - mov(board_old) 294 | _thret = thret(board_new) - thret(board_old) 295 | 296 | undenied_mobility = 1 if _mobil > 0 else 0 297 | total_mobility = 1 if _mob > 0 else 0 298 | denial_of_occ = 1 if _deny > 0 else 0 299 | control = 1 if _cent > 0 else 0 300 | 301 | _demmo = 1 if denial_of_occ and not total_mobility else 0 302 | _mode_2 = 1 if undenied_mobility and not denial_of_occ else 0 303 | _mode_3 = 1 if not undenied_mobility and denial_of_occ else 0 304 | _moc_2 = 1 if not undenied_mobility and control else 0 305 | _moc_3 = 1 if undenied_mobility and not control else 0 306 | _moc_4 = 1 if not undenied_mobility and not control else 0 307 | 308 | board_score = _moc_2*(-1)*(2**18) \ 309 | + _kcent*(2**16) \ 310 | + _moc_4*(-1)*(2**14) \ 311 | + _mode_3*(-1)*(2**13) \ 312 | + _demmo*(-1)*(2**11) \ 313 | + _mov*(2**8) \ 314 | + _adv*(-1)*(2**8) \ 315 | + _mode_2*(-1)*(2**8) \ 316 | + _back*(-1)*(2**6) \ 317 | + _cntr*(2**5) \ 318 | + _thret*(2**5) \ 319 | + _moc_3*(2**4) \ 320 | + piece_score_diff(board_new, board_old.active)*(2**20) \ 321 | + position_score(board_new, board_old.active)*(2**14) 322 | 323 | return board_score 324 | 325 | def negamax(board_old, board_new, depth, alpha, beta, color): 326 | if depth == 0 or board_new.is_over(): 327 | return score(board_old, board_new)*color 328 | best_value = -INFINITY 329 | for move in board_new.get_moves(): 330 | B = board_new.peek_move(move) 331 | if B.active != board_new.active: 332 | val = -negamax(board_new, B, depth - 1, -beta, -alpha, -color) 333 | else: 334 | val = negamax(board_new, B, depth, alpha, beta, color) 335 | best_value = max(best_value, val) 336 | alpha = max(alpha, val) 337 | if alpha >= beta: 338 | break 339 | return best_value 340 | 341 | def move_function(board, depth=7): 342 | def search(move): 343 | B = board.peek_move(move) 344 | if B.active == board.active: 345 | return negamax(board, B, depth, -INFINITY, INFINITY, 1) 346 | else: 347 | return negamax(board, B, depth, -INFINITY, INFINITY, -1) 348 | 349 | return max(board.get_moves(), key=search) 350 | #pairs = zip(zip(board.get_moves(), get_move_strings(board)), 351 | #map(search, board.get_moves())) 352 | #print "Moves and ratings" 353 | #for pair in pairs: 354 | #print pair[0][1] + " with a rating of " + str(pair[1]) 355 | #print "" 356 | #best_pair = max(pairs, key=lambda x: x[1]) 357 | #return best_pair[0][0] 358 | 359 | def get_move_strings(board): 360 | rfj = board.right_forward_jumps() 361 | lfj = board.left_forward_jumps() 362 | rbj = board.right_backward_jumps() 363 | lbj = board.left_backward_jumps() 364 | 365 | if (rfj | lfj | rbj | lbj) != 0: 366 | rfj = [(1 + i - i//9, 1 + (i + 8) - (i + 8)//9) 367 | for (i, bit) in enumerate(bin(rfj)[::-1]) if bit == '1'] 368 | lfj = [(1 + i - i//9, 1 + (i + 10) - (i + 8)//9) 369 | for (i, bit) in enumerate(bin(lfj)[::-1]) if bit == '1'] 370 | rbj = [(1 + i - i//9, 1 + (i - 8) - (i - 8)//9) 371 | for (i, bit) in enumerate(bin(rbj)[::-1]) if bit == '1'] 372 | lbj = [(1 + i - i//9, 1 + (i - 10) - (i - 10)//9) 373 | for (i, bit) in enumerate(bin(lbj)[::-1]) if bit == '1'] 374 | 375 | if board.active == BLACK: 376 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rfj + lfj] 377 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rbj + lbj] 378 | return regular_moves + reverse_moves 379 | else: 380 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rfj + lfj] 381 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rbj + lbj] 382 | return reverse_moves + regular_moves 383 | 384 | 385 | rf = board.right_forward() 386 | lf = board.left_forward() 387 | rb = board.right_backward() 388 | lb = board.left_backward() 389 | 390 | rf = [(1 + i - i//9, 1 + (i + 4) - (i + 4)//9) 391 | for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 392 | lf = [(1 + i - i//9, 1 + (i + 5) - (i + 5)//9) 393 | for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 394 | rb = [(1 + i - i//9, 1 + (i - 4) - (i - 4)//9) 395 | for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 396 | lb = [(1 + i - i//9, 1 + (i - 5) - (i - 5)//9) 397 | for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 398 | 399 | if board.active == BLACK: 400 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rf + lf] 401 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rb + lb] 402 | return regular_moves + reverse_moves 403 | else: 404 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rb + lb] 405 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rf + lf] 406 | return reverse_moves + regular_moves 407 | -------------------------------------------------------------------------------- /checkers.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module defines the CheckerBoard class. 3 | """ 4 | # Andrew Edwards -- almostimplemented.com 5 | # ======================================= 6 | # A simple and efficient checker board class. 7 | # 8 | # Created July 29, 2014 9 | 10 | ### CONSTANTS 11 | 12 | # Black moves "forward", white moves "backward" 13 | BLACK, WHITE = 0, 1 14 | 15 | # The IBM704 had 36-bit words. Arthur Samuel used the extra bits to 16 | # ensure that every normal move could be performed by flipping the 17 | # original bit and the bit either 4 or 5 bits away, in the cases of 18 | # moving right and left respectively. 19 | 20 | UNUSED_BITS = 0b100000000100000000100000000100000000 21 | 22 | ### CLASSES 23 | 24 | class CheckerBoard: 25 | def __init__(self): 26 | """ 27 | Initiates board via new_game(). 28 | """ 29 | self.forward = [None, None] 30 | self.backward = [None, None] 31 | self.pieces = [None, None] 32 | self.new_game() 33 | 34 | def new_game(self): 35 | """ 36 | Resets current state to new game. 37 | """ 38 | self.active = BLACK 39 | self.passive = WHITE 40 | 41 | self.forward[BLACK] = 0x1eff 42 | self.backward[BLACK] = 0 43 | self.pieces[BLACK] = self.forward[BLACK] | self.backward[BLACK] 44 | 45 | self.forward[WHITE] = 0 46 | self.backward[WHITE] = 0x7fbc00000 47 | self.pieces[WHITE] = self.forward[WHITE] | self.backward[WHITE] 48 | 49 | self.empty = UNUSED_BITS ^ (2**36 - 1) ^ (self.pieces[BLACK] | self.pieces[WHITE]) 50 | 51 | self.jump = 0 52 | self.mandatory_jumps = [] 53 | 54 | def make_move(self, move): 55 | """ 56 | Updates the game state to reflect the effects of the input 57 | move. 58 | 59 | A legal move is represented by an integer with exactly two 60 | bits turned on: the old position and the new position. 61 | """ 62 | active = self.active 63 | passive = self.passive 64 | if move < 0: 65 | move *= -1 66 | taken_piece = int(1 << sum(i for (i, b) in enumerate(bin(move)[::-1]) if b == '1')/2) 67 | self.pieces[passive] ^= taken_piece 68 | if self.forward[passive] & taken_piece: 69 | self.forward[passive] ^= taken_piece 70 | if self.backward[passive] & taken_piece: 71 | self.backward[passive] ^= taken_piece 72 | self.jump = 1 73 | 74 | self.pieces[active] ^= move 75 | if self.forward[active] & move: 76 | self.forward[active] ^= move 77 | if self.backward[active] & move: 78 | self.backward[active] ^= move 79 | 80 | destination = move & self.pieces[active] 81 | self.empty = UNUSED_BITS ^ (2**36 - 1) ^ (self.pieces[BLACK] | self.pieces[WHITE]) 82 | 83 | if self.jump: 84 | self.mandatory_jumps = self.jumps_from(destination) 85 | if self.mandatory_jumps: 86 | return 87 | 88 | if active == BLACK and (destination & 0x780000000) != 0: 89 | self.backward[BLACK] |= destination 90 | elif active == WHITE and (destination & 0xf) != 0: 91 | self.forward[WHITE] |= destination 92 | 93 | self.jump = 0 94 | self.active, self.passive = self.passive, self.active 95 | 96 | def peek_move(self, move): 97 | """ 98 | Updates the game state to reflect the effects of the input 99 | move. 100 | 101 | A legal move is represented by an integer with exactly two 102 | bits turned on: the old position and the new position. 103 | """ 104 | B = self.copy() 105 | active = B.active 106 | passive = B.passive 107 | if move < 0: 108 | move *= -1 109 | taken_piece = int(1 << sum(i for (i, b) in enumerate(bin(move)[::-1]) if b == '1')/2) 110 | B.pieces[passive] ^= taken_piece 111 | if B.forward[passive] & taken_piece: 112 | B.forward[passive] ^= taken_piece 113 | if B.backward[passive] & taken_piece: 114 | B.backward[passive] ^= taken_piece 115 | B.jump = 1 116 | 117 | B.pieces[active] ^= move 118 | if B.forward[active] & move: 119 | B.forward[active] ^= move 120 | if B.backward[active] & move: 121 | B.backward[active] ^= move 122 | 123 | destination = move & B.pieces[active] 124 | B.empty = UNUSED_BITS ^ (2**36 - 1) ^ (B.pieces[BLACK] | B.pieces[WHITE]) 125 | 126 | if B.jump: 127 | B.mandatory_jumps = B.jumps_from(destination) 128 | if B.mandatory_jumps: 129 | return B 130 | 131 | if active == BLACK and (destination & 0x780000000) != 0: 132 | B.backward[BLACK] |= destination 133 | elif active == WHITE and (destination & 0xf) != 0: 134 | B.forward[WHITE] |= destination 135 | 136 | B.jump = 0 137 | B.active, B.passive = B.passive, B.active 138 | 139 | return B 140 | 141 | # These methods return an integer whose active bits are those squares 142 | # that can make the move indicated by the method name. 143 | def right_forward(self): 144 | return (self.empty >> 4) & self.forward[self.active] 145 | def left_forward(self): 146 | return (self.empty >> 5) & self.forward[self.active] 147 | def right_backward(self): 148 | return (self.empty << 4) & self.backward[self.active] 149 | def left_backward(self): 150 | return (self.empty << 5) & self.backward[self.active] 151 | def right_forward_jumps(self): 152 | return (self.empty >> 8) & (self.pieces[self.passive] >> 4) & self.forward[self.active] 153 | def left_forward_jumps(self): 154 | return (self.empty >> 10) & (self.pieces[self.passive] >> 5) & self.forward[self.active] 155 | def right_backward_jumps(self): 156 | return (self.empty << 8) & (self.pieces[self.passive] << 4) & self.backward[self.active] 157 | def left_backward_jumps(self): 158 | return (self.empty << 10) & (self.pieces[self.passive] << 5) & self.backward[self.active] 159 | 160 | def get_moves(self): 161 | """ 162 | Returns a list of all possible moves. 163 | 164 | A legal move is represented by an integer with exactly two 165 | bits turned on: the old position and the new position. 166 | 167 | Jumps are indicated with a negative sign. 168 | """ 169 | # First check if we are in a jump sequence 170 | if self.jump: 171 | return self.mandatory_jumps 172 | 173 | # Next check if there are jumps 174 | jumps = self.get_jumps() 175 | if jumps: 176 | return jumps 177 | 178 | # If not, then find normal moves 179 | else: 180 | rf = self.right_forward() 181 | lf = self.left_forward() 182 | rb = self.right_backward() 183 | lb = self.left_backward() 184 | 185 | moves = [0x11 << i for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 186 | moves += [0x21 << i for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 187 | moves += [0x11 << i - 4 for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 188 | moves += [0x21 << i - 5 for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 189 | return moves 190 | 191 | 192 | def get_jumps(self): 193 | """ 194 | Returns a list of all possible jumps. 195 | 196 | A legal move is represented by an integer with exactly two 197 | bits turned on: the old position and the new position. 198 | 199 | Jumps are indicated with a negative sign. 200 | """ 201 | rfj = self.right_forward_jumps() 202 | lfj = self.left_forward_jumps() 203 | rbj = self.right_backward_jumps() 204 | lbj = self.left_backward_jumps() 205 | 206 | moves = [] 207 | 208 | if (rfj | lfj | rbj | lbj) != 0: 209 | moves += [-0x101 << i for (i, bit) in enumerate(bin(rfj)[::-1]) if bit == '1'] 210 | moves += [-0x401 << i for (i, bit) in enumerate(bin(lfj)[::-1]) if bit == '1'] 211 | moves += [-0x101 << i - 8 for (i, bit) in enumerate(bin(rbj)[::-1]) if bit == '1'] 212 | moves += [-0x401 << i - 10 for (i, bit) in enumerate(bin(lbj)[::-1]) if bit == '1'] 213 | 214 | return moves 215 | 216 | def jumps_from(self, piece): 217 | """ 218 | Returns list of all possible jumps from the piece indicated. 219 | 220 | The argument piece should be of the form 2**n, where n + 1 is 221 | the square of the piece in question (using the internal numeric 222 | representaiton of the board). 223 | """ 224 | if self.active == BLACK: 225 | rfj = (self.empty >> 8) & (self.pieces[self.passive] >> 4) & piece 226 | lfj = (self.empty >> 10) & (self.pieces[self.passive] >> 5) & piece 227 | if piece & self.backward[self.active]: # piece at square is a king 228 | rbj = (self.empty << 8) & (self.pieces[self.passive] << 4) & piece 229 | lbj = (self.empty << 10) & (self.pieces[self.passive] << 5) & piece 230 | else: 231 | rbj = 0 232 | lbj = 0 233 | else: 234 | rbj = (self.empty << 8) & (self.pieces[self.passive] << 4) & piece 235 | lbj = (self.empty << 10) & (self.pieces[self.passive] << 5) & piece 236 | if piece & self.forward[self.active]: # piece at square is a king 237 | rfj = (self.empty >> 8) & (self.pieces[self.passive] >> 4) & piece 238 | lfj = (self.empty >> 10) & (self.pieces[self.passive] >> 5) & piece 239 | else: 240 | rfj = 0 241 | lfj = 0 242 | 243 | moves = [] 244 | if (rfj | lfj | rbj | lbj) != 0: 245 | moves += [-0x101 << i for (i, bit) in enumerate(bin(rfj)[::-1]) if bit == '1'] 246 | moves += [-0x401 << i for (i, bit) in enumerate(bin(lfj)[::-1]) if bit == '1'] 247 | moves += [-0x101 << i - 8 for (i, bit) in enumerate(bin(rbj)[::-1]) if bit == '1'] 248 | moves += [-0x401 << i - 10 for (i, bit) in enumerate(bin(lbj)[::-1]) if bit == '1'] 249 | 250 | return moves 251 | 252 | def takeable(self, piece): 253 | """ 254 | Returns true of the passed piece can be taken by the active player. 255 | """ 256 | active = self.active 257 | if (self.forward[active] & (piece >> 4)) != 0 and (self.empty & (piece << 4)) != 0: 258 | return True 259 | if (self.forward[active] & (piece >> 5)) != 0 and (self.empty & (piece << 5)) != 0: 260 | return True 261 | if (self.backward[active] & (piece << 4)) != 0 and (self.empty & (piece >> 4)) != 0: 262 | return True 263 | if (self.backward[active] & (piece << 5)) != 0 and (self.empty & (piece >> 5)) != 0: 264 | return True 265 | return False 266 | 267 | def is_over(self): 268 | return len(self.get_moves()) == 0 269 | 270 | def copy(self): 271 | """ 272 | Returns a new board with the exact same state as the calling object. 273 | """ 274 | B = CheckerBoard() 275 | B.active = self.active 276 | B.backward = [x for x in self.backward] 277 | B.empty = self.empty 278 | B.forward = [x for x in self.forward] 279 | B.jump = self.jump 280 | B.mandatory_jumps = [x for x in self.mandatory_jumps] 281 | B.passive = self.passive 282 | B.pieces = [x for x in self.pieces] 283 | return B 284 | 285 | def __str__(self): 286 | """ 287 | Prints out ASCII art representation of board. 288 | """ 289 | 290 | EMPTY = -1 291 | BLACK_KING = 2 292 | WHITE_KING = 3 293 | 294 | if self.active == BLACK: 295 | black_kings = self.backward[self.active] 296 | black_men = self.forward[self.active] ^ black_kings 297 | white_kings = self.forward[self.passive] 298 | white_men = self.backward[self.passive] ^ white_kings 299 | else: 300 | black_kings = self.backward[self.passive] 301 | black_men = self.forward[self.passive] ^ black_kings 302 | white_kings = self.forward[self.active] 303 | white_men = self.backward[self.active] ^ white_kings 304 | 305 | state = [[None for _ in range(8)] for _ in range(4)] 306 | for i in range(4): 307 | for j in range(8): 308 | cell = 1 << (9*i + j) 309 | if cell & black_men: 310 | state[i][j] = BLACK 311 | elif cell & white_men: 312 | state[i][j] = WHITE 313 | elif cell & black_kings: 314 | state[i][j] = BLACK_KING 315 | elif cell & white_kings: 316 | state[i][j] = WHITE_KING 317 | else: 318 | state[i][j] = EMPTY 319 | 320 | board = [None] * 17 321 | for i in range(9): 322 | board[2*i] = ["+", " - "] + ["+", " - "]*7 + ["+", "\n"] 323 | if i < 8: 324 | board[2*i + 1] = ["|", " "] \ 325 | + [a for subl in [["|", " "] for _ in range(7)] for a in subl] \ 326 | + ["|", "\n"] 327 | 328 | for i, chunk in enumerate(state): 329 | for j, cell in enumerate(chunk): 330 | if j < 4: 331 | if cell == BLACK: 332 | board[2*(7 - 2*i) + 1][2*(6 - 2*j) + 1] = \ 333 | "b" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 334 | elif cell == WHITE: 335 | board[2*(7 - 2*i) + 1][2*(6 - 2*j) + 1] = \ 336 | "w" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 337 | elif cell == BLACK_KING: 338 | board[2*(7 - 2*i) + 1][2*(6 - 2*j) + 1] = \ 339 | "B" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 340 | elif cell == WHITE_KING: 341 | board[2*(7 - 2*i) + 1][2*(6 - 2*j) + 1] = \ 342 | "W" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 343 | else: 344 | board[2*(7 - 2*i) + 1][2*(6 - 2*j) + 1] = \ 345 | " " + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 346 | else: 347 | if cell == BLACK: 348 | board[2*(6 - 2*i) + 1][2*(7 - 2*j) - 1] = \ 349 | "b" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 350 | elif cell == WHITE: 351 | board[2*(6 - 2*i) + 1][2*(7 - 2*j) - 1] = \ 352 | "w" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 353 | elif cell == BLACK_KING: 354 | board[2*(6 - 2*i) + 1][2*(7 - 2*j) - 1] = \ 355 | "B" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 356 | elif cell == WHITE_KING: 357 | board[2*(6 - 2*i) + 1][2*(7 - 2*j) - 1] = \ 358 | "W" + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 359 | else: 360 | board[2*(6 - 2*i) + 1][2*(7 - 2*j) - 1] = \ 361 | " " + str(1 + j + 8*i) + (' ' if j + 8*i < 9 else '') 362 | 363 | return "".join(map(lambda x: "".join(x), board)) 364 | -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements the game playing harness. 3 | """ 4 | # Andrew Edwards -- almostimplemented.com 5 | # ======================================= 6 | # Harness for running a checkers match. 7 | # 8 | # Last updated: July 21, 2014 9 | 10 | import checkers 11 | import agent 12 | import sys 13 | 14 | BLACK, WHITE = 0, 1 15 | 16 | def main(): 17 | print "***************************************************" 18 | print "* Checkers *" 19 | print "* *" 20 | print "* Andrew Edwards *" 21 | print "* www.almostimplemented.com *" 22 | print "***************************************************" 23 | print "\n" 24 | print "\n" 25 | 26 | n = -1 27 | while not n in [0, 1, 2]: 28 | n = raw_input("How many human players? (0, 1, 2): ") 29 | try: 30 | n = int(n) 31 | except ValueError: 32 | print "Please input 0, 1, or 2." 33 | 34 | if n == 2: 35 | B = checkers.CheckerBoard() 36 | print "Black moves first." 37 | turn = 1 38 | current_player = B.active 39 | while not game_over(B): 40 | print B 41 | 42 | legal_moves = B.get_moves() 43 | 44 | if B.jump: 45 | print "Make jump." 46 | print "" 47 | else: 48 | print "Turn %i" % turn 49 | print "" 50 | 51 | for (i, move) in enumerate(get_move_strings(B)): 52 | print "Move " + str(i) + ": " + move 53 | 54 | while True: 55 | move_idx = raw_input("Enter your move number: ") 56 | try: 57 | move_idx = int(move_idx) 58 | except ValueError: 59 | print "Please input a valid move number." 60 | continue 61 | 62 | if move_idx in range(len(legal_moves)): 63 | break 64 | else: 65 | print "Please input a valid move number." 66 | continue 67 | 68 | B.make_move(legal_moves[move_idx]) 69 | 70 | # If jumps remain, then the board will not update current player 71 | if B.active == current_player: 72 | print "Jumps must be taken." 73 | continue 74 | else: 75 | current_player = B.active 76 | turn += 1 77 | 78 | print B 79 | if B.active == WHITE: 80 | print "Congrats Black, you win!" 81 | else: 82 | print "Congrats White, you win!" 83 | 84 | return 0 85 | 86 | 87 | elif n == 1: 88 | agent_module = raw_input("Enter name of agent module: "); 89 | __import__(agent_module) 90 | agent_module = sys.modules[agent_module] 91 | cpu = agent.CheckersAgent(agent_module.move_function) 92 | while True: 93 | choice = raw_input("Enter 0 to go first and 1 to go second: ") 94 | try: 95 | choice = int(choice) 96 | break 97 | except ValueError: 98 | print "Please input 0 or 1." 99 | continue 100 | turn = 0 101 | B = checkers.CheckerBoard() 102 | current_player = B.active 103 | print "Black moves first." 104 | while not B.is_over(): 105 | print B 106 | if turn % 2 == choice: 107 | legal_moves = B.get_moves() 108 | if B.jump: 109 | print "Make jump." 110 | print "" 111 | else: 112 | print "Turn %i" % (turn + 1) 113 | print "" 114 | for (i, move) in enumerate(get_move_strings(B)): 115 | print "Move " + str(i) + ": " + move 116 | while True: 117 | move_idx = raw_input("Enter your move number: ") 118 | try: 119 | move_idx = int(move_idx) 120 | except ValueError: 121 | print "Please input a valid move number." 122 | continue 123 | if move_idx in range(len(legal_moves)): 124 | break 125 | else: 126 | print "Please input a valid move number." 127 | continue 128 | B.make_move(legal_moves[move_idx]) 129 | # If jumps remain, then the board will not update current player 130 | if B.active == current_player: 131 | print "Jumps must be taken." 132 | continue 133 | else: 134 | current_player = B.active 135 | turn += 1 136 | else: 137 | B.make_move(cpu.make_move(B)) 138 | if B.active == current_player: 139 | print "Jumps must be taken." 140 | continue 141 | else: 142 | current_player = B.active 143 | turn += 1 144 | print B 145 | if B.active == WHITE: 146 | print "Congrats Black, you win!" 147 | else: 148 | print "Congrats White, you win!" 149 | return 0 150 | else: 151 | agent_module = raw_input("Enter name of first agent module: "); 152 | __import__(agent_module) 153 | agent_module = sys.modules[agent_module] 154 | cpu_1 = agent.CheckersAgent(agent_module.move_function) 155 | agent_module = raw_input("Enter name of second agent module: "); 156 | __import__(agent_module) 157 | agent_module = sys.modules[agent_module] 158 | cpu_2 = agent.CheckersAgent(agent_module.move_function) 159 | debug = raw_input("Would you like to step through game play? [Y/N]: ") 160 | debug = 1 if debug.lower()[0] == 'y' else 0 161 | B = checkers.CheckerBoard() 162 | current_player = B.active 163 | if debug: 164 | print "sorry not ready" 165 | return 0 166 | else: 167 | while not B.is_over(): 168 | B.make_move(cpu_1.make_move(B)) 169 | if B.active == current_player: 170 | continue 171 | current_player = B.active 172 | while B.active == current_player and not B.is_over(): 173 | B.make_move(cpu_2.make_move(B)) 174 | current_player = B.active 175 | if B.active == WHITE: 176 | print "Congrats Black, you win!" 177 | else: 178 | print "Congrats White, you win!" 179 | return 0 180 | 181 | 182 | 183 | def game_over(board): 184 | return len(board.get_moves()) == 0 185 | 186 | def get_move_strings(board): 187 | rfj = board.right_forward_jumps() 188 | lfj = board.left_forward_jumps() 189 | rbj = board.right_backward_jumps() 190 | lbj = board.left_backward_jumps() 191 | 192 | if (rfj | lfj | rbj | lbj) != 0: 193 | rfj = [(1 + i - i//9, 1 + (i + 8) - (i + 8)//9) 194 | for (i, bit) in enumerate(bin(rfj)[::-1]) if bit == '1'] 195 | lfj = [(1 + i - i//9, 1 + (i + 10) - (i + 8)//9) 196 | for (i, bit) in enumerate(bin(lfj)[::-1]) if bit == '1'] 197 | rbj = [(1 + i - i//9, 1 + (i - 8) - (i - 8)//9) 198 | for (i, bit) in enumerate(bin(rbj)[::-1]) if bit == '1'] 199 | lbj = [(1 + i - i//9, 1 + (i - 10) - (i - 10)//9) 200 | for (i, bit) in enumerate(bin(lbj)[::-1]) if bit == '1'] 201 | 202 | if board.active == BLACK: 203 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rfj + lfj] 204 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rbj + lbj] 205 | return regular_moves + reverse_moves 206 | else: 207 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rfj + lfj] 208 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rbj + lbj] 209 | return reverse_moves + regular_moves 210 | 211 | 212 | rf = board.right_forward() 213 | lf = board.left_forward() 214 | rb = board.right_backward() 215 | lb = board.left_backward() 216 | 217 | rf = [(1 + i - i//9, 1 + (i + 4) - (i + 4)//9) 218 | for (i, bit) in enumerate(bin(rf)[::-1]) if bit == '1'] 219 | lf = [(1 + i - i//9, 1 + (i + 5) - (i + 5)//9) 220 | for (i, bit) in enumerate(bin(lf)[::-1]) if bit == '1'] 221 | rb = [(1 + i - i//9, 1 + (i - 4) - (i - 4)//9) 222 | for (i, bit) in enumerate(bin(rb)[::-1]) if bit == '1'] 223 | lb = [(1 + i - i//9, 1 + (i - 5) - (i - 5)//9) 224 | for (i, bit) in enumerate(bin(lb)[::-1]) if bit == '1'] 225 | 226 | if board.active == BLACK: 227 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rf + lf] 228 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rb + lb] 229 | return regular_moves + reverse_moves 230 | else: 231 | regular_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rb + lb] 232 | reverse_moves = ["%i to %i" % (orig, dest) for (orig, dest) in rf + lf] 233 | return reverse_moves + regular_moves 234 | 235 | if __name__ == '__main__': 236 | try: 237 | status = main() 238 | sys.exit(status) 239 | except KeyboardInterrupt: 240 | print "" 241 | print "Game terminated." 242 | sys.exit(1) 243 | -------------------------------------------------------------------------------- /logfile: -------------------------------------------------------------------------------- 1 | + - + - + - + - + - + - + - + - + 2 | | |w32| |w31| |w30| |w29| 3 | + - + - + - + - + - + - + - + - + 4 | |w28| |w27| |w26| |w25| | 5 | + - + - + - + - + - + - + - + - + 6 | | |w24| |w23| |w22| |w21| 7 | + - + - + - + - + - + - + - + - + 8 | | 20| | 19| | 18| | 17| | 9 | + - + - + - + - + - + - + - + - + 10 | | | 16| | 15| | 14| | 13| 11 | + - + - + - + - + - + - + - + - + 12 | |b12| |b11| |b10| |b9 | | 13 | + - + - + - + - + - + - + - + - + 14 | | |b8 | |b7 | |b6 | |b5 | 15 | + - + - + - + - + - + - + - + - + 16 | |b4 | |b3 | |b2 | |b1 | | 17 | + - + - + - + - + - + - + - + - + 18 | + - + - + - + - + - + - + - + - + 19 | | |w32| |w31| |w30| |w29| 20 | + - + - + - + - + - + - + - + - + 21 | |w28| |w27| |w26| |w25| | 22 | + - + - + - + - + - + - + - + - + 23 | | |w24| |w23| |w22| | 21| 24 | + - + - + - + - + - + - + - + - + 25 | | 20| | 19| | 18| |w17| | 26 | + - + - + - + - + - + - + - + - + 27 | | | 16| | 15| | 14| |b13| 28 | + - + - + - + - + - + - + - + - + 29 | |b12| |b11| |b10| | 9 | | 30 | + - + - + - + - + - + - + - + - + 31 | | |b8 | |b7 | |b6 | |b5 | 32 | + - + - + - + - + - + - + - + - + 33 | |b4 | |b3 | |b2 | |b1 | | 34 | + - + - + - + - + - + - + - + - + 35 | + - + - + - + - + - + - + - + - + 36 | | |w32| |w31| |w30| |w29| 37 | + - + - + - + - + - + - + - + - + 38 | |w28| |w27| |w26| | 25| | 39 | + - + - + - + - + - + - + - + - + 40 | | |w24| |w23| |w22| |w21| 41 | + - + - + - + - + - + - + - + - + 42 | | 20| | 19| | 18| |w17| | 43 | + - + - + - + - + - + - + - + - + 44 | | | 16| | 15| | 14| |b13| 45 | + - + - + - + - + - + - + - + - + 46 | |b12| |b11| |b10| |b9 | | 47 | + - + - + - + - + - + - + - + - + 48 | | |b8 | |b7 | |b6 | | 5 | 49 | + - + - + - + - + - + - + - + - + 50 | |b4 | |b3 | |b2 | |b1 | | 51 | + - + - + - + - + - + - + - + - + 52 | + - + - + - + - + - + - + - + - + 53 | | |w32| |w31| |w30| |w29| 54 | + - + - + - + - + - + - + - + - + 55 | |w28| |w27| |w26| | 25| | 56 | + - + - + - + - + - + - + - + - + 57 | | |w24| | 23| |w22| |w21| 58 | + - + - + - + - + - + - + - + - + 59 | | 20| |w19| | 18| |w17| | 60 | + - + - + - + - + - + - + - + - + 61 | | | 16| | 15| | 14| |b13| 62 | + - + - + - + - + - + - + - + - + 63 | |b12| |b11| |b10| |b9 | | 64 | + - + - + - + - + - + - + - + - + 65 | | |b8 | |b7 | |b6 | |b5 | 66 | + - + - + - + - + - + - + - + - + 67 | |b4 | |b3 | |b2 | | 1 | | 68 | + - + - + - + - + - + - + - + - + 69 | + - + - + - + - + - + - + - + - + 70 | | |w32| |w31| |w30| |w29| 71 | + - + - + - + - + - + - + - + - + 72 | |w28| |w27| |w26| | 25| | 73 | + - + - + - + - + - + - + - + - + 74 | | | 24| | 23| |w22| |w21| 75 | + - + - + - + - + - + - + - + - + 76 | |w20| |w19| | 18| |w17| | 77 | + - + - + - + - + - + - + - + - + 78 | | |b16| | 15| | 14| |b13| 79 | + - + - + - + - + - + - + - + - + 80 | |b12| | 11| |b10| |b9 | | 81 | + - + - + - + - + - + - + - + - + 82 | | |b8 | |b7 | |b6 | |b5 | 83 | + - + - + - + - + - + - + - + - + 84 | |b4 | |b3 | |b2 | | 1 | | 85 | + - + - + - + - + - + - + - + - + 86 | + - + - + - + - + - + - + - + - + 87 | | |w32| |w31| |w30| |w29| 88 | + - + - + - + - + - + - + - + - + 89 | |w28| |w27| | 26| | 25| | 90 | + - + - + - + - + - + - + - + - + 91 | | | 24| | 23| |w22| |w21| 92 | + - + - + - + - + - + - + - + - + 93 | |w20| |w19| | 18| |w17| | 94 | + - + - + - + - + - + - + - + - + 95 | | | 16| | 15| | 14| |b13| 96 | + - + - + - + - + - + - + - + - + 97 | |b12| | 11| |b10| |b9 | | 98 | + - + - + - + - + - + - + - + - + 99 | | |b8 | |b7 | |b6 | |b5 | 100 | + - + - + - + - + - + - + - + - + 101 | |b4 | |b3 | |b2 | | 1 | | 102 | + - + - + - + - + - + - + - + - + 103 | + - + - + - + - + - + - + - + - + 104 | | |w32| |w31| | 30| |w29| 105 | + - + - + - + - + - + - + - + - + 106 | |w28| |w27| | 26| |w25| | 107 | + - + - + - + - + - + - + - + - + 108 | | | 24| | 23| |w22| |w21| 109 | + - + - + - + - + - + - + - + - + 110 | |w20| |w19| | 18| |w17| | 111 | + - + - + - + - + - + - + - + - + 112 | | | 16| | 15| | 14| |b13| 113 | + - + - + - + - + - + - + - + - + 114 | |b12| |b11| |b10| |b9 | | 115 | + - + - + - + - + - + - + - + - + 116 | | |b8 | | 7 | |b6 | |b5 | 117 | + - + - + - + - + - + - + - + - + 118 | |b4 | |b3 | |b2 | | 1 | | 119 | + - + - + - + - + - + - + - + - + 120 | + - + - + - + - + - + - + - + - + 121 | | |w32| |w31| | 30| |w29| 122 | + - + - + - + - + - + - + - + - + 123 | | 28| |w27| | 26| |w25| | 124 | + - + - + - + - + - + - + - + - + 125 | | |w24| | 23| |w22| |w21| 126 | + - + - + - + - + - + - + - + - + 127 | |w20| |w19| | 18| |w17| | 128 | + - + - + - + - + - + - + - + - + 129 | | | 16| | 15| | 14| |b13| 130 | + - + - + - + - + - + - + - + - + 131 | |b12| |b11| |b10| |b9 | | 132 | + - + - + - + - + - + - + - + - + 133 | | |b8 | |b7 | |b6 | |b5 | 134 | + - + - + - + - + - + - + - + - + 135 | |b4 | | 3 | |b2 | | 1 | | 136 | + - + - + - + - + - + - + - + - + 137 | + - + - + - + - + - + - + - + - + 138 | | |w32| | 31| | 30| |w29| 139 | + - + - + - + - + - + - + - + - + 140 | | 28| |w27| |w26| |w25| | 141 | + - + - + - + - + - + - + - + - + 142 | | |w24| | 23| |w22| |w21| 143 | + - + - + - + - + - + - + - + - + 144 | |w20| |w19| | 18| |w17| | 145 | + - + - + - + - + - + - + - + - + 146 | | | 16| | 15| |b14| |b13| 147 | + - + - + - + - + - + - + - + - + 148 | |b12| |b11| |b10| | 9 | | 149 | + - + - + - + - + - + - + - + - + 150 | | |b8 | |b7 | |b6 | |b5 | 151 | + - + - + - + - + - + - + - + - + 152 | |b4 | | 3 | |b2 | | 1 | | 153 | + - + - + - + - + - + - + - + - + 154 | + - + - + - + - + - + - + - + - + 155 | | |w32| | 31| | 30| |w29| 156 | + - + - + - + - + - + - + - + - + 157 | | 28| |w27| | 26| |w25| | 158 | + - + - + - + - + - + - + - + - + 159 | | |w24| |w23| |w22| |w21| 160 | + - + - + - + - + - + - + - + - + 161 | |w20| |w19| | 18| |w17| | 162 | + - + - + - + - + - + - + - + - + 163 | | | 16| | 15| |b14| |b13| 164 | + - + - + - + - + - + - + - + - + 165 | |b12| |b11| |b10| |b9 | | 166 | + - + - + - + - + - + - + - + - + 167 | | |b8 | |b7 | |b6 | | 5 | 168 | + - + - + - + - + - + - + - + - + 169 | |b4 | | 3 | |b2 | | 1 | | 170 | + - + - + - + - + - + - + - + - + 171 | + - + - + - + - + - + - + - + - + 172 | | |w32| | 31| | 30| |w29| 173 | + - + - + - + - + - + - + - + - + 174 | | 28| |w27| | 26| |w25| | 175 | + - + - + - + - + - + - + - + - + 176 | | |w24| |w23| |w22| |w21| 177 | + - + - + - + - + - + - + - + - + 178 | | 20| |w19| | 18| |w17| | 179 | + - + - + - + - + - + - + - + - + 180 | | | 16| | 15| |b14| |b13| 181 | + - + - + - + - + - + - + - + - + 182 | |b12| |w11| |b10| |b9 | | 183 | + - + - + - + - + - + - + - + - + 184 | | |b8 | |b7 | |b6 | | 5 | 185 | + - + - + - + - + - + - + - + - + 186 | |b4 | | 3 | |b2 | | 1 | | 187 | + - + - + - + - + - + - + - + - + 188 | + - + - + - + - + - + - + - + - + 189 | | |w32| | 31| | 30| |w29| 190 | + - + - + - + - + - + - + - + - + 191 | | 28| |w27| | 26| |w25| | 192 | + - + - + - + - + - + - + - + - + 193 | | |w24| |w23| |w22| |w21| 194 | + - + - + - + - + - + - + - + - + 195 | | 20| | 19| | 18| |w17| | 196 | + - + - + - + - + - + - + - + - + 197 | | |b16| |w15| |b14| |b13| 198 | + - + - + - + - + - + - + - + - + 199 | |b12| | 11| |b10| |b9 | | 200 | + - + - + - + - + - + - + - + - + 201 | | |b8 | | 7 | |b6 | | 5 | 202 | + - + - + - + - + - + - + - + - + 203 | |b4 | | 3 | |b2 | | 1 | | 204 | + - + - + - + - + - + - + - + - + 205 | + - + - + - + - + - + - + - + - + 206 | | |w32| | 31| | 30| |w29| 207 | + - + - + - + - + - + - + - + - + 208 | | 28| |w27| | 26| |w25| | 209 | + - + - + - + - + - + - + - + - + 210 | | |w24| |w23| |w22| |w21| 211 | + - + - + - + - + - + - + - + - + 212 | | 20| |b19| | 18| |w17| | 213 | + - + - + - + - + - + - + - + - + 214 | | |b16| | 15| |b14| |b13| 215 | + - + - + - + - + - + - + - + - + 216 | |b12| | 11| | 10| |b9 | | 217 | + - + - + - + - + - + - + - + - + 218 | | |b8 | | 7 | |b6 | | 5 | 219 | + - + - + - + - + - + - + - + - + 220 | |b4 | | 3 | |b2 | | 1 | | 221 | + - + - + - + - + - + - + - + - + 222 | + - + - + - + - + - + - + - + - + 223 | | |w32| | 31| | 30| |w29| 224 | + - + - + - + - + - + - + - + - + 225 | |b28| |w27| | 26| |w25| | 226 | + - + - + - + - + - + - + - + - + 227 | | | 24| |w23| |w22| |w21| 228 | + - + - + - + - + - + - + - + - + 229 | | 20| | 19| | 18| | 17| | 230 | + - + - + - + - + - + - + - + - + 231 | | |b16| | 15| | 14| |b13| 232 | + - + - + - + - + - + - + - + - + 233 | |b12| | 11| | 10| |b9 | | 234 | + - + - + - + - + - + - + - + - + 235 | | |b8 | | 7 | | 6 | | 5 | 236 | + - + - + - + - + - + - + - + - + 237 | |b4 | | 3 | |b2 | |W1 | | 238 | + - + - + - + - + - + - + - + - + 239 | + - + - + - + - + - + - + - + - + 240 | | |w32| | 31| | 30| |w29| 241 | + - + - + - + - + - + - + - + - + 242 | |b28| |w27| | 26| |w25| | 243 | + - + - + - + - + - + - + - + - + 244 | | | 24| |w23| | 22| |w21| 245 | + - + - + - + - + - + - + - + - + 246 | | 20| | 19| |w18| | 17| | 247 | + - + - + - + - + - + - + - + - + 248 | | |b16| | 15| |b14| |b13| 249 | + - + - + - + - + - + - + - + - + 250 | |b12| | 11| | 10| | 9 | | 251 | + - + - + - + - + - + - + - + - + 252 | | |b8 | | 7 | | 6 | | 5 | 253 | + - + - + - + - + - + - + - + - + 254 | |b4 | | 3 | |b2 | |W1 | | 255 | + - + - + - + - + - + - + - + - + 256 | + - + - + - + - + - + - + - + - + 257 | | |w32| | 31| | 30| |w29| 258 | + - + - + - + - + - + - + - + - + 259 | |b28| |w27| | 26| |w25| | 260 | + - + - + - + - + - + - + - + - + 261 | | | 24| |w23| | 22| |w21| 262 | + - + - + - + - + - + - + - + - + 263 | | 20| | 19| |w18| |W17| | 264 | + - + - + - + - + - + - + - + - + 265 | | |b16| | 15| | 14| |b13| 266 | + - + - + - + - + - + - + - + - + 267 | |b12| | 11| | 10| | 9 | | 268 | + - + - + - + - + - + - + - + - + 269 | | |b8 | | 7 | | 6 | | 5 | 270 | + - + - + - + - + - + - + - + - + 271 | |b4 | | 3 | | 2 | | 1 | | 272 | + - + - + - + - + - + - + - + - + 273 | + - + - + - + - + - + - + - + - + 274 | | |w32| | 31| | 30| |w29| 275 | + - + - + - + - + - + - + - + - + 276 | |b28| |w27| | 26| |w25| | 277 | + - + - + - + - + - + - + - + - + 278 | | | 24| |w23| |b22| |w21| 279 | + - + - + - + - + - + - + - + - + 280 | | 20| | 19| | 18| | 17| | 281 | + - + - + - + - + - + - + - + - + 282 | | |b16| |w15| | 14| | 13| 283 | + - + - + - + - + - + - + - + - + 284 | |b12| | 11| | 10| | 9 | | 285 | + - + - + - + - + - + - + - + - + 286 | | |b8 | | 7 | | 6 | | 5 | 287 | + - + - + - + - + - + - + - + - + 288 | |b4 | | 3 | | 2 | | 1 | | 289 | + - + - + - + - + - + - + - + - + 290 | + - + - + - + - + - + - + - + - + 291 | | |w32| | 31| | 30| |w29| 292 | + - + - + - + - + - + - + - + - + 293 | |b28| |w27| | 26| |w25| | 294 | + - + - + - + - + - + - + - + - + 295 | | | 24| | 23| |b22| |w21| 296 | + - + - + - + - + - + - + - + - + 297 | | 20| | 19| | 18| | 17| | 298 | + - + - + - + - + - + - + - + - + 299 | | |w16| |w15| | 14| | 13| 300 | + - + - + - + - + - + - + - + - + 301 | |b12| | 11| | 10| | 9 | | 302 | + - + - + - + - + - + - + - + - + 303 | | |b8 | | 7 | | 6 | | 5 | 304 | + - + - + - + - + - + - + - + - + 305 | |b4 | | 3 | | 2 | | 1 | | 306 | + - + - + - + - + - + - + - + - + 307 | + - + - + - + - + - + - + - + - + 308 | | |w32| | 31| | 30| |w29| 309 | + - + - + - + - + - + - + - + - + 310 | |b28| |w27| | 26| | 25| | 311 | + - + - + - + - + - + - + - + - + 312 | | | 24| | 23| | 22| |w21| 313 | + - + - + - + - + - + - + - + - + 314 | | 20| |b19| |w18| | 17| | 315 | + - + - + - + - + - + - + - + - + 316 | | | 16| |w15| | 14| | 13| 317 | + - + - + - + - + - + - + - + - + 318 | | 12| | 11| | 10| | 9 | | 319 | + - + - + - + - + - + - + - + - + 320 | | |b8 | | 7 | | 6 | | 5 | 321 | + - + - + - + - + - + - + - + - + 322 | |b4 | | 3 | | 2 | | 1 | | 323 | + - + - + - + - + - + - + - + - + 324 | + - + - + - + - + - + - + - + - + 325 | | |w32| | 31| | 30| |w29| 326 | + - + - + - + - + - + - + - + - + 327 | |b28| |w27| | 26| | 25| | 328 | + - + - + - + - + - + - + - + - + 329 | | | 24| | 23| | 22| |w21| 330 | + - + - + - + - + - + - + - + - + 331 | | 20| |b19| |w18| | 17| | 332 | + - + - + - + - + - + - + - + - + 333 | | | 16| | 15| | 14| | 13| 334 | + - + - + - + - + - + - + - + - + 335 | | 12| | 11| | 10| | 9 | | 336 | + - + - + - + - + - + - + - + - + 337 | | |w8 | | 7 | | 6 | | 5 | 338 | + - + - + - + - + - + - + - + - + 339 | |b4 | | 3 | | 2 | | 1 | | 340 | + - + - + - + - + - + - + - + - + 341 | + - + - + - + - + - + - + - + - + 342 | | |w32| | 31| | 30| |w29| 343 | + - + - + - + - + - + - + - + - + 344 | |b28| |w27| | 26| | 25| | 345 | + - + - + - + - + - + - + - + - + 346 | | | 24| | 23| | 22| | 21| 347 | + - + - + - + - + - + - + - + - + 348 | | 20| |b19| |w18| |w17| | 349 | + - + - + - + - + - + - + - + - + 350 | | | 16| | 15| | 14| | 13| 351 | + - + - + - + - + - + - + - + - + 352 | | 12| |b11| | 10| | 9 | | 353 | + - + - + - + - + - + - + - + - + 354 | | | 8 | | 7 | | 6 | | 5 | 355 | + - + - + - + - + - + - + - + - + 356 | | 4 | | 3 | | 2 | | 1 | | 357 | + - + - + - + - + - + - + - + - + 358 | + - + - + - + - + - + - + - + - + 359 | | |w32| | 31| | 30| |w29| 360 | + - + - + - + - + - + - + - + - + 361 | |b28| |w27| | 26| | 25| | 362 | + - + - + - + - + - + - + - + - + 363 | | | 24| | 23| | 22| | 21| 364 | + - + - + - + - + - + - + - + - + 365 | | 20| |b19| | 18| |w17| | 366 | + - + - + - + - + - + - + - + - + 367 | | | 16| | 15| | 14| | 13| 368 | + - + - + - + - + - + - + - + - + 369 | | 12| |w11| | 10| | 9 | | 370 | + - + - + - + - + - + - + - + - + 371 | | | 8 | | 7 | | 6 | | 5 | 372 | + - + - + - + - + - + - + - + - + 373 | | 4 | | 3 | | 2 | | 1 | | 374 | + - + - + - + - + - + - + - + - + 375 | + - + - + - + - + - + - + - + - + 376 | | |w32| |w31| |w30| |w29| 377 | + - + - + - + - + - + - + - + - + 378 | |w28| |w27| |w26| |w25| | 379 | + - + - + - + - + - + - + - + - + 380 | | |w24| |w23| |w22| |w21| 381 | + - + - + - + - + - + - + - + - + 382 | | 20| | 19| | 18| | 17| | 383 | + - + - + - + - + - + - + - + - + 384 | | | 16| | 15| | 14| | 13| 385 | + - + - + - + - + - + - + - + - + 386 | |b12| |b11| |b10| |b9 | | 387 | + - + - + - + - + - + - + - + - + 388 | | |b8 | |b7 | |b6 | |b5 | 389 | + - + - + - + - + - + - + - + - + 390 | |b4 | |b3 | |b2 | |b1 | | 391 | + - + - + - + - + - + - + - + - + 392 | + - + - + - + - + - + - + - + - + 393 | | |w32| |w31| |w30| |w29| 394 | + - + - + - + - + - + - + - + - + 395 | |w28| |w27| |w26| |w25| | 396 | + - + - + - + - + - + - + - + - + 397 | | |w24| |w23| |w22| | 21| 398 | + - + - + - + - + - + - + - + - + 399 | | 20| | 19| | 18| |w17| | 400 | + - + - + - + - + - + - + - + - + 401 | | | 16| | 15| | 14| |b13| 402 | + - + - + - + - + - + - + - + - + 403 | |b12| |b11| |b10| | 9 | | 404 | + - + - + - + - + - + - + - + - + 405 | | |b8 | |b7 | |b6 | |b5 | 406 | + - + - + - + - + - + - + - + - + 407 | |b4 | |b3 | |b2 | |b1 | | 408 | + - + - + - + - + - + - + - + - + 409 | + - + - + - + - + - + - + - + - + 410 | | |w32| |w31| |w30| |w29| 411 | + - + - + - + - + - + - + - + - + 412 | |w28| |w27| |w26| | 25| | 413 | + - + - + - + - + - + - + - + - + 414 | | |w24| |w23| |w22| |w21| 415 | + - + - + - + - + - + - + - + - + 416 | | 20| | 19| | 18| |w17| | 417 | + - + - + - + - + - + - + - + - + 418 | | | 16| | 15| | 14| |b13| 419 | + - + - + - + - + - + - + - + - + 420 | |b12| |b11| |b10| |b9 | | 421 | + - + - + - + - + - + - + - + - + 422 | | |b8 | |b7 | |b6 | | 5 | 423 | + - + - + - + - + - + - + - + - + 424 | |b4 | |b3 | |b2 | |b1 | | 425 | + - + - + - + - + - + - + - + - + 426 | + - + - + - + - + - + - + - + - + 427 | | |w32| |w31| |w30| |w29| 428 | + - + - + - + - + - + - + - + - + 429 | |w28| |w27| |w26| | 25| | 430 | + - + - + - + - + - + - + - + - + 431 | | |w24| | 23| |w22| |w21| 432 | + - + - + - + - + - + - + - + - + 433 | | 20| |w19| | 18| |w17| | 434 | + - + - + - + - + - + - + - + - + 435 | | | 16| | 15| | 14| |b13| 436 | + - + - + - + - + - + - + - + - + 437 | |b12| |b11| |b10| |b9 | | 438 | + - + - + - + - + - + - + - + - + 439 | | |b8 | |b7 | |b6 | |b5 | 440 | + - + - + - + - + - + - + - + - + 441 | |b4 | |b3 | |b2 | | 1 | | 442 | + - + - + - + - + - + - + - + - + 443 | + - + - + - + - + - + - + - + - + 444 | | |w32| |w31| |w30| |w29| 445 | + - + - + - + - + - + - + - + - + 446 | |w28| |w27| |w26| | 25| | 447 | + - + - + - + - + - + - + - + - + 448 | | | 24| | 23| |w22| |w21| 449 | + - + - + - + - + - + - + - + - + 450 | |w20| |w19| | 18| |w17| | 451 | + - + - + - + - + - + - + - + - + 452 | | |b16| | 15| | 14| |b13| 453 | + - + - + - + - + - + - + - + - + 454 | |b12| | 11| |b10| |b9 | | 455 | + - + - + - + - + - + - + - + - + 456 | | |b8 | |b7 | |b6 | |b5 | 457 | + - + - + - + - + - + - + - + - + 458 | |b4 | |b3 | |b2 | | 1 | | 459 | + - + - + - + - + - + - + - + - + 460 | + - + - + - + - + - + - + - + - + 461 | | |w32| |w31| |w30| |w29| 462 | + - + - + - + - + - + - + - + - + 463 | |w28| |w27| | 26| | 25| | 464 | + - + - + - + - + - + - + - + - + 465 | | | 24| | 23| |w22| |w21| 466 | + - + - + - + - + - + - + - + - + 467 | |w20| |w19| | 18| |w17| | 468 | + - + - + - + - + - + - + - + - + 469 | | | 16| | 15| | 14| |b13| 470 | + - + - + - + - + - + - + - + - + 471 | |b12| | 11| |b10| |b9 | | 472 | + - + - + - + - + - + - + - + - + 473 | | |b8 | |b7 | |b6 | |b5 | 474 | + - + - + - + - + - + - + - + - + 475 | |b4 | |b3 | |b2 | | 1 | | 476 | + - + - + - + - + - + - + - + - + 477 | -------------------------------------------------------------------------------- /random_agent.py: -------------------------------------------------------------------------------- 1 | # Andrew Edwards -- almostimplemented.com 2 | # ======================================= 3 | # A checkers agent that picks a random move 4 | # 5 | # Last updated: July 21, 2014 6 | import random 7 | 8 | def move_function(board): 9 | return random.choice(board.get_moves()) 10 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import checkers 2 | import agent 3 | import arthur 4 | import random_agent 5 | 6 | BLACK, WHITE = 0, 1 7 | 8 | f = open('logfile', 'w') 9 | 10 | for i in range(100): 11 | print "game: " + str(i) 12 | B = checkers.CheckerBoard() 13 | cpu_1 = agent.CheckersAgent(lambda board: arthur.move_function(board, 4)) 14 | cpu_2 = agent.CheckersAgent(lambda board: arthur.move_function(board, 6)) 15 | current_player = B.active 16 | turn = 1 17 | while not B.is_over(): 18 | f.write(str(B)) 19 | if turn % 100 == 0: 20 | print "# of turns: " + str(turn) 21 | B.make_move(cpu_1.make_move(B)) 22 | if B.active == current_player: 23 | continue 24 | current_player = B.active 25 | turn += 1 26 | while not B.is_over() and B.active == current_player: 27 | B.make_move(cpu_2.make_move(B)) 28 | current_player = B.active 29 | if B.active == WHITE: 30 | print "Congrats Black, you win!" 31 | else: 32 | print "Congrats White, you win!" 33 | print "Game took %i turns" % turn 34 | --------------------------------------------------------------------------------