"""\
Very simple game of checkers.
"""
_copyright = "(c) 2003 Gregor Raýman - www.grayman.de"
_license = """\
Copyright (c) 2003, Gregor Rayman - www.grayman.de"
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
- Neither the name of Gregor Rayman nor the names of its contributors may be used to
endorse or promote products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &AS IS& AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
_version = "0.1"
_build = "$Id: checkers.py,v 1.7 2003/10/03 23:52:40 gra Exp $"
import random
BOARD_SIZE = 8
INITIAL_ROWS = 3
RED = 1
WHITE = 2
_otherColor = {RED: WHITE, WHITE: RED}
class InvalidMove:
def __init__(self, move):
self.move = move
class Board:
def __init__(self, size = BOARD_SIZE):
self.fields = []
self.size = size
for i in range(self.size):
row = [None] * self.size
self.fields.append(row)
self.counts = {RED: [0, 0], WHITE: [0, 0]}
for i in range(self.size // 2):
for j in range(INITIAL_ROWS):
Piece(self, RED, (j, i * 2 + j % 2))
Piece(self, WHITE, (self.size - j - 1, i * 2 + (self.size - j - 1) % 2))
self.history = []
self.turn = RED
self.upsidedown = False
def __str__old(self):
result = ''
rowindex = range(self.size - 1, -1, -1)
colindex = range(self.size)
if self.upsidedown:
rowindex, colindex = colindex, rowindex
for r in rowindex:
rowstr = '%d: ' % (r + 1)
for c in colindex:
p = self.fields[r][c]
if p is None: rowstr += (r+c)%2 and ' ' or '.'
else: rowstr += str(p)
rowstr += ' '
result += rowstr + '\n'
rowstr = ' '
for c in colindex:
rowstr += '%c ' % (ord('a') + c)
result += rowstr
return result
def __str__(self):
if self.upsidedown:
boardtodisp = lambda c: self.size - c - 1
else:
boardtodisp = lambda c: c
line1 = list(' . ' * (self.size // 2) + ' .' * (self.size % 2) + ' ')
line2 = list(' .' * (self.size // 2) + ' ' * (self.size % 2) + ' ')
display = []
for i in range(self.size):
display.append(line1[:])
line1, line2 = line2, line1
if self.history:
move = self.history[-1]
rr, cc = move.steps[0]
for r, c in move.steps:
if abs(r-rr) == 2 or abs(c-cc) == 2:
display[boardtodisp((r+rr)//2)][boardtodisp((c+cc)//2) * 2 + 1] = '*'
display[boardtodisp(r)][boardtodisp(c) * 2 + 1] = '*'
rr, cc = r, c
display[boardtodisp(r)][boardtodisp(c) * 2] = '('
display[boardtodisp(r)][boardtodisp(c) * 2 + 2] = ')'
for r, row in enumerate(self.fields):
for c, p in enumerate(row):
if p:
display[boardtodisp(r)][boardtodisp(c) * 2 + 1] = str(p)
display.reverse()
result = ''
for i, line in enumerate(display):
result += "%d: " % (boardtodisp(self.size - i - 1) + 1) + ''.join(line) + '\n'
result += ' '
for i in range(self.size):
result += '%c ' % (boardtodisp(i) + ord('a'))
return result
def suggest(self, gaze):
suggestion = self.getBestMoves(gaze)
if suggestion is None:
return None
score, moves = suggestion
return score, random.choice(moves)
def undo(self):
if (self.history):
self.history[-1].revert()
def getPossibleMoves(self):
allMoves = []
jumps = False
for row in self.fields:
for piece in row:
if not piece is None:
jump, moves = piece.getPossibleMoves()
if jump and not jumps:
jumps = True
allMoves = []
if jump or not jumps:
allMoves += moves
return allMoves
def getMoveScore(self, move, gaze):
try:
move.applyto(self)
possible = self.getPossibleMoves()
if gaze <= 0:
return self._score() + (len(possible),)
else:
if not possible:
return (1000, 1000, 1000, 1000)
nextGaze = gaze - len(possible)
bestNextScore = (-10000, -10000, -10000, -10000)
for nextMove in possible:
nextScore = self.getMoveScore(nextMove, nextGaze)
if nextScore > bestNextScore:
bestNextScore = nextScore
return -bestNextScore[0], -bestNextScore[1], -bestNextScore[2], -bestNextScore[3]
finally:
self.undo()
def getBestMoves(self, gaze):
possible = self.getPossibleMoves()
if not possible:
return None
if len(possible) == 1:
return (0,0,0,1), possible
best = []
bestScore = (-1000, -1000, -1000, -1000)
for move in possible:
score = self.getMoveScore(move, gaze)
c = cmp(score, bestScore)
if c > 0:
best = [move]
bestScore = score
elif c == 0:
best.append(move)
return bestScore, best
def _score(self):
"returns the score from the point of view of the next player"
totalDiff = self.counts[_otherColor[self.turn]][0] - self.counts[self.turn][0]
queenDiff = self.counts[_otherColor[self.turn]][1] - self.counts[self.turn][1]
total = self.counts[RED][0] + self.counts[WHITE][0]
if totalDiff < 0:
return totalDiff, queenDiff, total
if totalDiff > 0:
return totalDiff, queenDiff, -total
if queenDiff < 0:
return totalDiff, queenDiff, total
if queenDiff > 0:
return totalDiff, queenDiff, -total
return totalDiff, queenDiff, 0
def _check(self):
for r, row in enumerate(self.fields):
for c, field in enumerate(row):
if not field is None:
assert(field.coordinates == (r, c))
class Piece:
def __init__(self, board, color, coordinates):
self.board = board
self.color = color
self.coordinates = coordinates
self.queen = False
self.onboard = True
self.__mark = False
assert(board.fields[coordinates[0]][coordinates[1]] is None)
board.fields[coordinates[0]][coordinates[1]] = self
board.counts[color][0] += 1
def promote(self):
assert(not self.queen and self.onboard)
self.queen = True
self.board.counts[self.color][1] += 1
def demote(self):
assert(self.queen and self.onboard)
self.queen = False
self.board.counts[self.color][1] -= 1
def moveto(self, coordinates):
self._check()
assert(self.board.fields[coordinates[0]][coordinates[1]] is None)
self.board.fields[self.coordinates[0]][self.coordinates[1]] = None
self.coordinates = coordinates
self.board.fields[self.coordinates[0]][self.coordinates[1]] = self
self._check()
def remove(self):
self._check()
assert(self.onboard)
self.board.counts[self.color][0] -= 1
if self.queen: self.board.counts[self.color][1] -= 1
self.board.fields[self.coordinates[0]][self.coordinates[1]] = None
self.onboard = False
self._check()
def putback(self):
self._check()
assert(not self.onboard)
self.board.counts[self.color][0] += 1
if self.queen: self.board.counts[self.color][1] += 1
assert(self.board.fields[self.coordinates[0]][self.coordinates[1]] == None)
self.board.fields[self.coordinates[0]][self.coordinates[1]] = self
self.onboard = True
self._check()
def __str__(self):
if self.color == RED:
return self.queen and 'R' or 'r'
return self.queen and 'W' or 'w'
def __repr__(self):
return "%c%d" % (ord('a') + self.coordinates[1], 1 + self.coordinates[0]) + str(self)
def _check(self):
if self.onboard:
assert(self.board.fields[self.coordinates[0]][self.coordinates[1]] == self)
def getPossibleMoves(self):
if self.color != self.board.turn:
return False, []
if self.queen:
directions = ((1, 1), (-1, 1), (1, -1), (-1, -1))
elif self.color == RED:
directions = ((1, 1), (1, -1))
else:
directions = ((-1, 1), (-1, -1))
moves = self._getPossibleJumps(directions)
if moves:
return True, moves
for dr, dc in directions:
r, c = self.coordinates[0]+dr, self.coordinates[1]+dc
if (0 <= r < self.board.size) and (0 <= c < self.board.size):
p = self.board.fields[r][c]
if p is None:
moves.append(Move([self.coordinates, (r, c)]))
return False, moves
def _getPossibleJumpsRec(self, directions, jumps, pos, steps):
found = False
for dr, dc in directions:
tr, tc = pos[0] + dr, pos[1] + dc
r, c = tr + dr, tc + dc
if (0 <= r < self.board.size) and (0 <= c < self.board.size):
pt = self.board.fields[tr][tc]
p = self.board.fields[r][c]
if (p is None and (not pt is None and pt.color != self.color and not pt.__mark)):
pt.__mark = True
steps.append((r, c))
try:
if not self._getPossibleJumpsRec(directions, jumps, (r, c), steps):
jumps.append(Move(steps[:]))
found = True
finally:
steps.pop()
pt.__mark = False
return found
def _getPossibleJumps(self, directions):
jumps = []
steps = [self.coordinates]
self._getPossibleJumpsRec(directions, jumps, self.coordinates, steps)
return jumps
class Move:
def __init__(self, steps):
self.steps = steps
self.applied = False
def __repr__(self):
dash = ''
result = ''
for r, c in self.steps:
result += dash + "%c%d" % (ord('a') + c, 1 + r)
return result
def applyto(self, board):
"Applies the move."
self.piece = board.fields[self.steps[0][0]][self.steps[0][1]]
if self.piece is None:
raise InvalidMove(str(self))
self.piece.moveto(self.steps[-1])
if self.piece.coordinates[0] in (0, self.piece.board.size -1) and not self.piece.queen:
self.promotes = True
self.piece.promote()
else:
self.promotes = False
self.taken = []
rr, cc = self.steps[0]
for r, c in self.steps[1:]:
tr, tc = (r+rr) // 2, (c+cc) // 2
if (tr != r and tr != rr):
self.taken.append(self.piece.board.fields[tr][tc])
self.piece.board.fields[tr][tc].remove()
rr, cc = r, c
self.piece.board.history.append(self)
self.piece.board.turn = _otherColor[self.piece.color]
self.applied = True
def revert(self):
"Reverts the move. Moves the pieces back and puts back the taken pieces"
assert(self.applied)
assert(self.piece.board.history[-1] == self)
self.piece.board.history.pop()
self.piece.board.turn = self.piece.color
for p in self.taken:
p.putback()
if self.promotes:
self.piece.demote()
self.piece.moveto(self.steps[0])
self.applied = False
del self.piece
import cmd
class Game(cmd.Cmd):
def __init__(self, difficulty=20, completekey='tab', stdin=None, stdout=None):
cmd.Cmd.__init__(self, completekey, stdin, stdout)
difficulty = difficulty < 5 and 5 or difficulty > 25 and 25 or difficulty
self.difficulty = difficulty
def preloop(self):
self.board = Board()
self.stdout.write("Welcome to the game\n\n")
self.stdout.write(str(self.board))
self.stdout.write("\n\n")
def emptyline(self):
pass
def do_play(self, move):
possible = self.board.getPossibleMoves()
matching = [m for m in possible if str(m).startswith(move)]
if 1 == len(matching):
matching[0].applyto(self.board)
self.stdout.write(str(self.board))
self.stdout.write("\n\n")
self.computerplay()
elif len(matching):
self.stdout.write("Which move did you mean?\n")
for m in matching:
self.stdout.write(" " + str(m) + "\n")
else:
self.stdout.write("InvalidMove!\n")
self.stdout.write("Possible moves are:\n")
for m in possible:
self.stdout.write(" " + str(m) + "\n")
def help_play(self):
self.stdout.write("""\
Play a move.
The move has to be specified in the form <letter><number>...<letter><number>.
Example c3d4. The move does not have to be specified completely, it is enough
to enter as much as is needed to identify the possible move.
Example:
When the game starts, the only possible move from to column "a" is "a3b4", so
in the case only "a" has to be entered. If only one move is possible, only the
word "play" has to be entered. Otherwise is the word "play" optional.
If an invalid move is entered, or if the move is not identified by the entry,
a list of all allowed moves will be printed.
""")
def computerplay(self):
suggestion = self.board.suggest(20)
if suggestion is None:
self.stdout.write("Game is over\n")
return True
else:
suggestion[1].applyto(self.board)
self.stdout.write("\n\n" + str(suggestion[1]) + "\n\n")
self.stdout.write(str(self.board))
self.stdout.write("\n\n")
def do_swap(self, dummy = None):
self.board.upsidedown = not self.board.upsidedown
self.computerplay()
def help_swap(self):
self.stdout.write("Swaps the sides\n")
def do_undo(self, dummy = None):
self.board.undo()
self.board.undo()
self.stdout.write(str(self.board))
self.stdout.write("\n\n")
def help_undo(self):
self.stdout.write("Undo the last move\n")
def do_quit(self, dummy = None):
return True
def help_quit(self):
self.stdout.write("Quit the game\n")
def do_license(self, dummy = None):
self.help_license()
def help_license(self):
self.stdout.write(_license)
def default(self, line):
if line.strip():
self.do_play(line)
if "__main__" == __name__:
g = Game()
g.cmdloop()