共计 4684 个字符,预计需要花费 12 分钟才能阅读完成。
[Python] 黑白棋(翻转棋)小游戏
游戏介绍
黑白棋 (Reversi or Othello) 在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。
规则
黑白棋的每颗棋子由黑白两色组成,一面白,一面黑。每次落子,把本方颜色的棋子放在棋盘的空格上,若在横、竖、斜八个方向的任一方向上有本方棋子,则被夹在中间的对手棋子全部翻转为本方棋子颜色; 并且仅在可以翻转棋 子的地方才能落子。如果一方至少有一步合法棋步可下,他就必须落子,不得弃权。
棋盘已满或双方都没有棋子可下时棋局结束,以棋子数目来计算胜负,棋子多的一方获胜。在棋盘还没有下满时,如果一方的棋子已经被对方吃光,则棋局也结束,将对手棋子吃光的一方获胜。
胜负判定
两位玩家轮流下棋,直到一方没有符合规则的落子位置,在这种情况下,剩下的一方 继续下棋,直到对手有了可以落子的位置。此时,恢复两者轮流下棋的顺序。如果一方落子在非法位置,则视为放弃本次对弈,对方获胜。
游戏结束的条件:
- 整个棋盘满了
- 一方的棋子已经被对方吃光
- 两名玩家都没有可以落子的棋盘格
- 一方落子在非法位置
前 3 种情况以棋子数目来计算胜负,棋子多的一方获胜; 第四种情况判定对方获胜。
人机对弈流程
首先,程序询问用户棋盘的大小。接着,程序询问用户“计算机持黑棋还是白棋”。
在本程序中,我们用字母’X’代表黑棋,用字母’O’代表白棋,并且假设总是黑棋玩家先走。
所以,如果计算机持黑棋,计算机就先走; 否则,程序提示人类玩家先走。
每走一步,程序输出棋盘。黑白棋玩家轮流下棋,直到一个玩家无符合规则的落子位置。
此时,程序输出信息“O player has no valid move.”(假设白棋玩家无棋可走),并且提示黑棋玩家继续下棋。
每走一步,程序除输出棋盘外,还要检测游戏是否结束。如果程序检查出游戏结束,输出输赢信息并中止程序。输赢信息可以是:“O player wins.”,“X player wins.”或者“Draw!”. 如果用户落子非法,程序应检测到并且输出“Invalid move.”,结束程序,宣布赢家。
计算机选择落子位置的策略
对每个可能的落子位置,都进行尝试,计算该位置的“分值”(可以翻转的对手棋子数量),分值越高则在该位置落子越有利。计算每个可能位置的分值,选择最大值位置落子。需要注意的是: 可能有 2 个或多个棋盘格有相同的分值。这种情况下,选择行字母最小的棋盘格。如果两个棋盘格分值相同且在同一行,则选择列字母较小的棋盘格。
程序执行
代码
完整代码见
代码仓库
棋盘代码
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Board():
def __init__(self, n):
self.n = n
self.board = self.generateBoard()
self.chess = {0: '.', 1: 'O', 2: 'X'}
def generateBoard(self):
# 0 empty 1 white 2 black
i = int(self.n / 2)
board = [[0] * self.n for _ in range(self.n)]
board[i][i]=board[i-1][i-1] = 1
board[i][i-1]=board[i-1][i] = 2
return board
def draw(self):
index = 'abcdefghijklmnopqrstuvwxyz'
print(' ',*index[:self.n])
for h,row in zip(index,self.board):
print(h,*map('.OX'.__getitem__,row))
print()
游戏逻辑
#!/usr/bin/python
# -*- coding: utf-8 -*-
from board import Board
import itertools
import operator
import collections
from functools import reduce
from constant import Status
class Reversi():
_DIRECTIONS = [(1,0),(1,1),(1,-1),(-1,0),(-1,1),(-1,-1),(0,1),(0,-1)]
def __init__(self, n, turn):
self.n = n # board dimension
self.b = Board(n) # board
self.turn = 0 if turn == 'X' or turn == 'x' else 1 # player turn
self.step = 1 # game step
self.status = Status.WAIT # game status
def isValidPosition(self,x,y):
return 0 <= x < self.n and 0 <= y < self.n
def nextPosition(self,direction,x,y):
x+=direction[0]
y+=direction[1]
return x,y
def score(self,r,c):
return list(itertools.chain.from_iterable([self.scoreDirection(r+m[0],c+m[1],m,self.step%2+1,[]) for m in Reversi._DIRECTIONS]))
def scoreDirection(self,x,y,direction,color,turn):
if not self.isValidPosition(x,y) or self.b.board[x][y]==0 :
return []
if self.b.board[x][y]!=color:
turn+=[(x,y)]
return self.scoreDirection(*self.nextPosition(direction,x,y),direction,color,turn)
else:
return turn
def checkPut(self, pos):
# check person put
assert len(pos)>=2 , 'move position disable'
r = ord(pos[0]) - 97
c = ord(pos[1]) - 97
assert 0 <= r < self.n and 0 <= c < self.n, 'move position disable'
turnList = self.score(r, c)
if turnList:
# turn chess
for x,y in turnList+[(r,c)]:
self.b.board[x][y] = self.step % 2+1
return True
else:
return False
def checkGame(self):
# check game status
empty,oNum,xNum = operator.itemgetter(0,1,2)(collections.Counter(itertools.chain.from_iterable(self.b.board)))
hasPut = True
pos,turnList = self.aiPut()
if not turnList:
self.step += 1
posNext,turnListNext = self.aiPut()
if not turnListNext:
hasPut = False
else:
self.step -= 1
print('{} player has no valid move'.format(self.b.chess[self.step % 2+1]))
self.step -= 1
self.turn -= 1
print('{} player go on'.format(self.b.chess[self.step % 2+1]))
if empty ==0 or oNum==0 or xNum == 0 or not hasPut:
self.status = [Status.DRAW.value,Status.OWIN.value,Status.XWIN.value][(oNum > xNum)-(oNum<xNum)]
def cmp(self,a,b):
if len(a[1])>len(b[1]):
return a
elif len(a[1])==len(b[1]) and a[0]<b[0]:
return a
else:
return b
def aiPut(self):
# computer put
allPos = filter(lambda pos : self.b.board[pos[0]][pos[1]]==0,itertools.product(range(self.n),repeat=2))
allScoreForPos = map(lambda pos: [pos,self.score(pos[0],pos[1])],allPos)
maxScorePos = reduce(self.cmp,allScoreForPos,[(),[]])
return maxScorePos[0],maxScorePos[1]
def aiPlay(self):
pos,turnList = self.aiPut()
if turnList:
print('Computer places {} at {}'.format(self.b.chess[self.step % 2+1],chr(pos[0]+97)+chr(pos[1]+97)))
for x,y in turnList+[pos]:
self.b.board[x][y] = self.step % 2+1
reversi.b.draw()
self.step += 1
self.turn += 1
def pPlay(self):
pos = input('Enter move for {} (RowCol):'.format(self.b.chess[self.step % 2+1]))
if self.checkPut(pos):
reversi.b.draw()
self.step += 1
self.turn += 1
else:
print('Invalid move')
def play(self):
self.status = Status.ONGOING
plays = [self.aiPlay,self.pPlay]
while self.status == Status.ONGOING:
plays[self.turn % len(plays)]()
self.checkGame()
else:
print('Game over. {}'.format(Status(self.status)))
if __name__ == "__main__":
print('Enter the board dimension:')
try:
n = int(input())
except Exception as e:
print('the board dimension is invalid, start game with default dimension = 4')
n = 4
assert 4 <= n <= 26 and n % 2 == 0, 'the board dimension is disable'
print('Computer plays (X/O):')
turn = input()
assert turn in ['X','x','O', 'o'], 'the symbol of computer is disable'
# generate game
reversi = Reversi(n, turn)
# draw board
reversi.b.draw()
reversi.play()
input('Enter to quit')