You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
animal-chess/AnimalChess_Engine_Rules.py

497 lines
22 KiB

from copy import deepcopy
from typing import List, Tuple
from collections import Counter
# 棋子配置
BOARD = [["--", "--", "redElephant", "--", "--", "--", "blackMouse", "--", "--"],
["--", "redBird", "--", "--", "--", "--", "--", "blackFox", "--"],
["--", "--", "redWolf", "--", "--", "--", "blackCheetah", "--", "--"],
["--", "--", "redLion", "--", "--", "--", "blackLion", "--", "--"],
["--", "--", "redCheetah", "--", "--", "--", "blackWolf", "--", "--"],
["--", "redFox", "--", "--", "--", "--", "--", "blackBird", "--"],
["--", "--", "redMouse", "--", "--", "--", "blackElephant", "--", "--"]]
DIMENSION_ROW = 7
DIMENSION_COL = 9
# 河流配置
WATER_BLOCK = [(1,3), (1,4), (1,5),
(2,3), (2,4), (2,5),
(4,3), (4,4), (4,5),
(5,3), (5,4), (5,5)]
WATER_WIDTH = 3
WATER_HEIGHT = 2
# 陷阱配置
RED_TRAP_BLOCK = [(2, 0), (3, 1), (4, 0)]
BLACK_TRAP_BLOCK = [(2, 8), (3, 7), (4, 8)]
# 巢穴配置
RED_DEN_BLOCK = (3, 0)
BLACK_DEN_BLOCK = (3, 8)
DEN_BLOCK = [RED_DEN_BLOCK, BLACK_DEN_BLOCK]
# 动物种数
ANIMAL_SPECIES = 7
# 动物名字
ANIMAL_NAME_SHORT = "MBFWCLE"
ANIMAL_NAME = { ANIMAL_NAME_SHORT[i]: [
"Mouse",
"Bird",
"Fox",
"Wolf",
"Cheetah",
"Lion",
"Elephant"
][i] for i in range(ANIMAL_SPECIES)}
# 动物强度
ANIMAL_POWER = { ANIMAL_NAME_SHORT[i] : i + 1 for i in range(ANIMAL_SPECIES) }
class GameState:
def __init__(self):
self.board = [ [ (BOARD[i][j][0], BOARD[i][j][3] if BOARD[i][j][0] == 'r' else \
BOARD[i][j][0] if BOARD[i][j][0] == '-' else \
BOARD[i][j][5] ) \
for j in range(DIMENSION_COL)] for i in range(DIMENSION_ROW)]
self.waterBlock = WATER_BLOCK
self.denBlock = DEN_BLOCK
self.moveFunctions = {"M": self.mouseMove,
"B": self.birdMove,
"F": self.foxMove,
"W": self.standardMove,
"C": self.standardMove,
"L": self.lionMove,
"E": self.standardMove}
self.red_to_move = True
self.history = []
self.conquered = False
self.draw = False
self.queue7 = []
self.queue17 = []
def queueMoveToken(self, targetRow:int, targetCol:int, color:str, animal:str) -> str:
return f"{color}{animal}{targetRow}{targetCol}"
def isFault7(self, move) -> bool:
if len(self.queue7) < 6:
return False
else:
status = Counter(self.queue7 + [f"{move.nowRow}{move.nowCol}{move.nowStat}"] )\
.most_common()[0][1] >= 3
self.queue7.pop(0)
return status
def isFault17(self, move):
if len(self.queue17) < 16:
return False
else:
counter = Counter(self.queue17 + [f"{move.nowRow}{move.nowCol}{move.nowStat}"])
status = (len(counter) <= 5)
self.queue17.pop(0)
return status
def makeMove(self, move) -> bool:
''' 下棋,返回值表示是否吃子 '''
self.board[move.nowRow][move.nowCol] = ("-", "-")
self.board[move.targetRow][move.targetCol] = move.nowStat
# 记录,悔棋用
self.history.append(move)
# 更新 7-3 违例 和 17-5 违例队列
self.queue7.append(self.queueMoveToken(move.targetRow, move.targetCol,\
move.nowStat[0], move.nowStat[1]))
self.queue17.append(self.queueMoveToken(move.targetRow, move.targetCol,\
move.nowStat[0], move.nowStat[1]))
# 回合交换
self.red_to_move = not self.red_to_move
return move.isTargetEaten
def undoMove(self):
''' 悔棋 '''
if len(self.history) != 0: # make sure that there is a move to undo
move = self.history.pop()
self.board[move.nowRow][move.nowCol] = move.nowStat
self.board[move.targetRow][move.targetCol] = move.targetStat
self.red_to_move = not self.red_to_move # swap players
self.conquered = False
self.draw = False
def getValidMoves(self) -> Tuple[List, bool]:
''' 获取所有可能的走法 '''
moves = []
for row in range(len(self.board)):
for col in range(len(self.board[row])):
turn = self.board[row][col][0]
if (turn == "r" and self.red_to_move)\
or (turn == "b" and not self.red_to_move):
self.moveFunctions[self.board[row][col][1]](row, col, moves)
# 死棋,判负
if len(moves) == 0:
print("No moves?")
return moves, True
else:
self.conquered = False
self.draw = False
return moves, False
def inWater(self,row,col):
''' 判断是否在水中 '''
return True if (row, col) in WATER_BLOCK else False
def jumpConditions(self, nowRow, nowCol, targetRow, targetCol, jumpDirectionRow, jumpDirectionCol, enemyColor):
''' 判断狮子是否满足跳河条件 '''
# 如果狮子可以纵向向上跳河(河对面为空地或敌方棋子)
if jumpDirectionRow < 0 and\
self.board[targetRow + (WATER_HEIGHT * jumpDirectionRow)][targetCol][0] in ['-', enemyColor]:
# 如果路径中有老鼠,则不满足
for i in range(WATER_HEIGHT):
if self.board[targetRow + i * jumpDirectionRow][targetCol][1] == "M":
return False
# 如果河对面没有任何动物,则狮子可以跳河
if self.board[targetRow + (WATER_HEIGHT * jumpDirectionRow)][targetCol][0] == "-":
return True
# 如果河对面为敌方动物,且比狮子弱,则狮子可以跳河
elif self.isEatable(nowRow, nowCol, targetRow + (WATER_HEIGHT * jumpDirectionRow), targetCol):
return True
# 如果狮子选择纵向向下跳河(河对面为空地或敌方棋子)
elif jumpDirectionRow > 0 and\
self.board[targetRow + (WATER_HEIGHT * jumpDirectionRow)][targetCol][0] in ['-', enemyColor]:
# 如果路径中有老鼠,则不满足
for i in range(WATER_HEIGHT):
if self.board[targetRow + i * jumpDirectionRow][targetCol][1] == "M":
return False
# 如果河对面没有任何动物,则狮子可以跳河
if self.board[targetRow + (WATER_HEIGHT * jumpDirectionRow)][targetCol][0] == "-":
return True
# 如果河对面为敌方动物,且比狮子弱,则狮子可以跳河
elif self.isEatable(nowRow, nowCol, targetRow + (WATER_HEIGHT * jumpDirectionRow), targetCol):
return True
# 如果狮子选择横向跳河
elif jumpDirectionCol != 0 and\
self.board[targetRow][targetCol + (WATER_WIDTH * jumpDirectionCol)][0] in ['-', enemyColor]:
# 如果路径中有老鼠,则不满足
for i in range(WATER_WIDTH):
if self.board[targetRow + i * jumpDirectionCol][targetCol][1] == "M":
return False
# 如果河对面没有任何动物,则狮子可以跳河
if self.board[targetRow][targetCol + (WATER_WIDTH - 1) * jumpDirectionCol][0] == "-":
return True
# 如果河对面为敌方动物,且比狮子弱,则狮子可以跳河
elif self.isEatable(nowRow, nowCol, targetRow , targetCol + (WATER_WIDTH * jumpDirectionCol)):
return True
return False
def isFriendBase(self, targetRow, targetCol, enemyColor):
''' 判断是否为我方基地 '''
if enemyColor == "b" and\
targetRow == RED_DEN_BLOCK[0] and\
targetCol == RED_DEN_BLOCK[1]:
return True
elif enemyColor == "r" and\
targetRow == BLACK_DEN_BLOCK[0] and\
targetCol == BLACK_DEN_BLOCK[1]:
return True
return False
def isEnemyBase(self, targetRow, targetCol, enemyColor):
''' 判断是否为敌方基地 '''
if enemyColor == "b" and\
targetRow == BLACK_DEN_BLOCK[0] and\
targetCol == BLACK_DEN_BLOCK[1]:
return True
elif enemyColor == 'r' and\
targetRow == RED_DEN_BLOCK[0] and\
targetCol == RED_DEN_BLOCK[1]:
return True
else:
return False
def enemyConquerDen(self):
''' 判断是否到达敌方基地 '''
if self.board[RED_DEN_BLOCK[0]] [RED_DEN_BLOCK[1]] [0] == "b" \
or self.board[BLACK_DEN_BLOCK[0]][BLACK_DEN_BLOCK[1]][0] == "r":
self.conquered = True
return True
return False
def isFriendTrap(self, targetRow, targetCol, enemyColor):
''' 判断是否为我方陷阱'''
if (enemyColor == 'b') and ((targetRow, targetCol) in RED_TRAP_BLOCK):
return True
elif (enemyColor == 'r') and ((targetRow, targetCol) in BLACK_TRAP_BLOCK):
return True
return False
def isEnemyTrap(self, targetRow, targetCol, enemyColor):
''' 判断是否为敌方陷阱 '''
if (enemyColor == 'b') and ((targetRow, targetCol) in BLACK_TRAP_BLOCK):
return True
elif (enemyColor == 'r') and ((targetRow, targetCol) in RED_TRAP_BLOCK):
return True
return False
def isEatable(self, row, col, targetRow, targetCol):
''' 判断能否吃掉敌方动物 '''
friend = self.board[row][col]
enemy = self.board[targetRow][targetCol]
# 如果目标不是动物,直接返回
if enemy[0] == "-":
return False
# 陷阱逻辑
# 自家陷阱里的动物不会被任何敌方动物吃掉
if enemy[0] == 'r' and (targetRow,targetCol) in RED_TRAP_BLOCK:
return False
if enemy[0] == 'b' and (targetRow,targetCol) in BLACK_TRAP_BLOCK:
return False
# 敌方陷阱里的自家动物会被所有敌方动物吃掉
if enemy[0] == 'b' and (targetRow,targetCol) in RED_TRAP_BLOCK:
return True
if enemy[0] == 'r' and (targetRow,targetCol) in BLACK_TRAP_BLOCK:
return True
# 大象不能吃老鼠
if friend[1] == 'E' and enemy[1] == 'M':
return False
# 老鼠逻辑
if friend[1] == 'M':
# 在水里的老鼠可以吃水里的其他老鼠
if self.inWater(row, col)\
and self.inWater(targetRow, targetCol):
return True
# 在水里的老鼠不能吃大象
elif self.inWater(row, col)\
and not self.inWater(targetRow, targetCol):
return False
# 不在水里的老鼠不能吃水里的其他老鼠
elif not self.inWater(row, col)\
and self.inWater(targetRow, targetCol):
return False
# 不在水里的老鼠可以吃大象
elif enemy[1] == 'E':
return True
# 除此以外,按强度判断
elif ANIMAL_POWER[enemy[1]] <= ANIMAL_POWER[friend[1]]:
return True
# 其他动物逻辑:按强度判断
elif ANIMAL_POWER[enemy[1]] <= ANIMAL_POWER[friend[1]]:
return True
def mouseMove(self, row, col, moves):
''' 老鼠的移动方法 '''
directions = [(-1, 0), # 上
(0, -1), # 左
(1, 0), # 下
(0, 1)] # 右
enemyColor = "b" if self.red_to_move else "r"
for direction in directions:
# 获取目标位置
targetRow = row + direction[0] * 1
targetCol = col + direction[1] * 1
# 检查目标位置是否出界
if 0 <= targetRow < DIMENSION_ROW and 0 <= targetCol < DIMENSION_COL:
targetColor = self.board[targetRow][targetCol][0]
targetAnimal = self.board[targetRow][targetCol][1]
# 如果目标位置为空地/水地,且不是我方基地
if targetColor == "-" \
and not self.isFriendBase(targetRow, targetCol, enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置为敌方动物(判断能否吃掉)
elif targetColor == enemyColor \
and self.isEatable(row, col, targetRow, targetCol):
moves.append(Move(row, col, targetRow, targetCol, self.board))
def foxMove(self, row, col, moves):
''' 狐狸的移动方法 '''
directions = [(-1, 0), # 上
(-1, -1), # 左上
(0, -1), # 左
(1, -1), # 左下
(1, 0), # 下
(1, 1), # 右下
(0, 1), # 右
(-1, 1)] # 右上
enemyColor = "b" if self.red_to_move else "r"
for direction in directions:
# 获取目标位置
targetRow = row + direction[0] * 1
targetCol = col + direction[1] * 1
# 检查目标位置是否出界
if 0 <= targetRow < DIMENSION_ROW and 0 <= targetCol < DIMENSION_COL:
targetColor = self.board[targetRow][targetCol][0]
targetAnimal = self.board[targetRow][targetCol][1]
# 如果目标位置为陆空地,且不为水地,且不是我方基地(可以直接移动)
if targetColor == "-"\
and not self.inWater(targetRow, targetCol)\
and not self.isFriendBase(targetRow, targetCol, enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置有敌方动物(判断能否吃掉)
elif targetColor == enemyColor\
and not self.inWater(targetRow, targetCol)\
and self.isEatable(row, col, targetRow, targetCol):
moves.append(Move(row, col, targetRow, targetCol, self.board))
def birdMove(self, row, col, moves):
''' 老鹰的移动方法 '''
directions = [(-1, 0), # 上
(0, -1), # 左
(1, 0), # 下
(0, 1)] # 右
enemyColor = "b" if self.red_to_move else "r"
for direction in directions:
# 获取目标方向
targetRow = row + direction[0] * 1
targetCol = col + direction[1] * 1
possibleMovesInWater = []
# 检查目标位置是否出界 / 是否碰到棋子
while 0 <= targetRow < DIMENSION_ROW and 0 <= targetCol < DIMENSION_COL:
targetColor = self.board[targetRow][targetCol][0]
targetAnimal = self.board[targetRow][targetCol][1]
# 如果目标位置是陷阱,在陷阱上停下
if self.isEnemyTrap(targetRow, targetCol, enemyColor)\
or self.isFriendTrap(targetRow, targetCol, enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board) )
break
# 如果目标位置为陆空地,且不是我方基地(可以直接移动)
elif targetColor == "-"\
and not self.inWater(targetRow, targetCol)\
and not self.isFriendBase(targetRow, targetCol, enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置为水空地,可能可以移动
elif targetColor == "-"\
and self.inWater(targetRow, targetCol):
possibleMovesInWater.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置不是空地
elif targetColor != "-":
# 水中有动物,路径作废
if self.inWater(targetRow, targetCol):
possibleMovesInWater.clear()
break
# 陆地动物,做吃子判断
else:
# 如果为自家动物,停下
if targetColor != enemyColor:
break
# 如果能吃掉,则可以走到那一格,否则最多在其前面停下
if self.isEatable(row, col, targetRow, targetCol):
moves.append(Move(row, col, targetRow, targetCol, self.board))
break
targetRow += direction[0]
targetCol += direction[1]
moves.extend(possibleMovesInWater)
def lionMove(self, row, col, moves):
''' 狮子的移动方法 '''
directions = [(-1, 0), # 上
(0, -1), # 左
(1, 0), # 下
(0, 1)] # 右
enemyColor = "b" if self.red_to_move else "r"
for direction in directions:
# 获取目标位置
targetRow = row + direction[0] * 1
targetCol = col + direction[1] * 1
# 检查目标位置是否出界
if 0 <= targetRow < DIMENSION_ROW and 0 <= targetCol < DIMENSION_COL:
targetColor = self.board[targetRow][targetCol][0]
targetAnimal = self.board[targetRow][targetCol][1]
# 如果目标位置为陆空地,且不是我方基地(可以直接移动)
if targetColor == "-"\
and not self.inWater(targetRow,targetCol)\
and not self.isFriendBase(targetRow,targetCol,enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置在水中(跳河逻辑)
elif targetColor == "-" and self.inWater(targetRow,targetCol):
# 确定跳跃方向
jumpDirectionRow = targetRow - row
jumpDirectionCol = targetCol - col
# 判断是否满足纵向跳河条件
if jumpDirectionRow != 0\
and self.jumpConditions(row,col,targetRow,targetCol,jumpDirectionRow,jumpDirectionCol,enemyColor):
moves.append(Move(row, col, targetRow + (WATER_HEIGHT * jumpDirectionRow), targetCol, self.board))
# 判断是否满足横向跳河条件
elif jumpDirectionCol != 0\
and self.jumpConditions(row,col,targetRow,targetCol,jumpDirectionRow,jumpDirectionCol,enemyColor):
moves.append(Move(row, col, targetRow, targetCol + (WATER_WIDTH * jumpDirectionCol), self.board))
# 如果目标位置为草地,有敌方动物(判断能否吃掉)
elif targetColor == enemyColor\
and not self.inWater(targetRow, targetCol)\
and self.isEatable(row, col, targetRow, targetCol):
moves.append(Move(row, col, targetRow, targetCol, self.board))
def standardMove(self, row, col, moves):
''' 其他动物的移动方法 '''
directions = [(-1, 0), # 上
(0, -1), # 左
(1, 0), # 下
(0, 1)] # 右
enemyColor = "b" if self.red_to_move else "r"
for direction in directions:
# 获取目标位置
targetRow = row + direction[0] * 1
targetCol = col + direction[1] * 1
# 检查目标位置是否出界
if 0 <= targetRow < DIMENSION_ROW and 0 <= targetCol < DIMENSION_COL:
targetColor = self.board[targetRow][targetCol][0]
targetAnimal = self.board[targetRow][targetCol][1]
# 如果目标位置为陆空地,且不是我方基地(可以直接移动)
if targetColor == "-"\
and not self.inWater(targetRow, targetCol)\
and not self.isFriendBase(targetRow, targetCol, enemyColor):
moves.append(Move(row, col, targetRow, targetCol, self.board))
# 如果目标位置为草地,有敌方动物(判断能否吃掉)
elif targetColor == enemyColor\
and not self.inWater(targetRow, targetCol)\
and self.isEatable(row, col, targetRow, targetCol):
moves.append(Move(row, col, targetRow, targetCol, self.board))
class Move(object):
''' 表示每一步的决策 '''
def __init__(self, nowRow, nowCol, targetRow, targetCol, board):
self.nowRow = nowRow
self.nowCol = nowCol
self.targetRow = targetRow
self.targetCol = targetCol
self.nowStat = board[self.nowRow][self.nowCol]
self.targetStat = board[self.targetRow][self.targetCol]
self.isTargetEaten = self.targetStat[0] != "-" # 判定是否属于吃子情形
self.moveID = self.nowRow * 1000 + self.nowCol * 100 + self.targetRow * 10 + self.targetCol
def __repr__(self):
return f"{self.nowStat[1]}{self.nowRow}{self.nowCol}\
{self.targetStat}{self.targetRow}{self.targetCol}"
def __str__(self):
return ANIMAL_NAME[self.nowStat[1]] + f"({self.nowRow}, {self.nowCol})"\
+ f" -> ({self.targetRow}, {self.targetCol})"
def __eq__(self, other):
return self.moveID == other.moveID if isinstance(other, Move)\
else False
def __hash__(self):
return hash(self.__repr__())