用Python3写一个中国象棋游戏

一:目的

就是为了熟悉Python语法

二:效果

用Python3写一个中国象棋游戏

三:使用方式

1.用Python3运行里面的main.py即可;

2.wdsa和上右下左键控制光标移动;空格键选中棋子,再按则是相当于移动棋子,如果在原地再按空格键取消选中;

3.按q结束游戏,或者吃了主帅后结束游戏

四:源码

https://github.com/silentdoer/chinese_chess_python3

支持Linux和Windows(我这边用ubuntu18.04和Win10 Powershell测试的,如果是其他终端不能保证布局会像GIF中的那样整齐,但是功能肯定是没问题的)

五:源码介绍

1.首先需要一个能够实时获取按键的工具方法/工具类,否则每次按完上下左右等功能按键后都要按Enter键太不方便,代码如下:

class Getch():
    """ 用来实现不需要按enter键读取输入字符 """

    def __init__(self):
        import platform
        system_name = platform.system()
        if system_name == 'Windows':
            self.impl = GetchWindows()
        else:  # 默认是Linux(目前就支持Windows和Linux即可)
            self.impl = GetchUnix()

    def __call__(self): return self.impl()


class GetchUnix:
    def __init__(self):
        pass

    def __call__(self):
        # 不要用import sys, tty这种逗号的方式导入
        import sys
        import tty
        import termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


class GetchWindows:
    def __init__(self):
        pass

    def __call__(self):
        import msvcrt
        return msvcrt.getch()

通过getch()就能实时获取按下的按键

2.通过一个公共模块来实现清屏和打印颜色文本的操作,代码如下:

def printc(new_line, content, forecolor='', backcolor=''):
    if forecolor != '':
        forecolor = ';' + forecolor
    if backcolor != '':
        backcolor = ';' + backcolor

    flag = ''
    if new_line:
        flag = '\n'
    print('\033[0' + forecolor + backcolor + 'm' + content + '\033[0m', end=flag)

def clear_console():
    import platform
    import os
    system_name = platform.system()
    if system_name == 'Windows':
        os.system("cls")
    else:  # 默认是Linux(目前就支持Windows和Linux即可)
        os.system("clear")

3.定义一些常量,主要用于描述阵营,棋子类型(如象,馬,車等),颜色常量,代码如下:

CAMP_RED = '红方'
CAMP_WHITE = '白方'
CAMP_NONE = 'NONE'
CAMP_CURSOR = 'CURSOR'

FORE_RED = '31'
FORE_YELLOW = '33'
FORE_BLUE = '34'
FORE_WHITE = '37'
BACK_BLACK = '40'

CHESS_CHE = 1
CHESS_MA = 2
CHESS_XIANG = 3
CHESS_SHI = 4
CHESS_BOSS = 5
CHESS_PAO = 6
CHESS_BING = 7
# 普通的*
CHESS_PIECE = 8
CHESS_CURSOR = 9

4.然后我们需要定义棋子类,这里因为想熟悉Python语法,所以拆成了Chess和ChineseChess来做用来熟悉继承,代码如下:

class Piece:
    """ 定义棋子基类 """

    def __init__(self, pic):
        self._pic = pic

    def show(self, new_line=False):
        if new_line:
            print(self._pic)
        else:
            print(self._pic, end='')
# coding:utf-8
import piece
import constants
import common


class ChessPiece(piece.Piece):
    """
    定义象棋棋子
    """

    __camp = ''

    __type = ''

    def __init__(self, pic, camp):
        piece.Piece.__init__(self, pic)
        self.__camp = camp

    def get_camp(self):
        return self.__camp

    def get_pic(self):
        return self._pic

    def set_camp(self, camp):
        self.__camp = camp

    def set_pic(self, pic):
        self._pic = pic

    def set_type(self, chess_type):
        self.__type = chess_type

    def get_type(self):
        return self.__type

    def show(self, new_line=False):
        if self.__camp == constants.CAMP_RED:
            common.printc(new_line, self._pic, constants.FORE_RED)
        elif self.__camp == constants.CAMP_WHITE:
            common.printc(new_line, self._pic, constants.FORE_WHITE)
        elif self.__camp == constants.CAMP_CURSOR:
            common.printc(new_line, self._pic, constants.FORE_YELLOW)
        else:
            common.printc(new_line, self._pic, constants.FORE_BLUE)

5.接下来就是游戏环境类了,这个类比较大,也是关键,线上代码后面会讲一下思路:

# coding:utf-8
import sys

from chess_piece import ChessPiece
import constants
from getch import Getch
import chess_strategy
import common


class GameContext:
    """ 游戏环境类 """

    game_over = False
    winner = ''
    # 是否已经选中棋子
    piece_checked = False
    # 红方先行
    firster = constants.CAMP_RED
    _pieces = []
    _width = 9
    _height = 10
    _getch = Getch()
    # 字符串默认值可以是'',数组默认值是[],字典默认值是{},数值默认值是0,而对象默认值是None
    _cursor_original_backup = None
    _cursor_loc = {'x': 4, 'y': 1}
    # 用于记录checked后,被checked的piece的坐标,用于判定是否是取消checked
    _checked_loc = {}
    _checked = False

    CURSOR_CHECKED = ' 〠 '
    CURSOR_UNCHECKED = ' 〄 '
    PIECE_NONE = ' * '

    strategies = None

    def __init__(self, camp=constants.CAMP_RED):
        if camp == constants.CAMP_WHITE:
            self._cursor_loc['y'] = 8
        elif camp != constants.CAMP_RED:
            print('阵营参数错误,游戏退出')
            exit()
        self.firster = camp
        # init pieces
        # init NONE Camp
        for y in range(self._height):
            line = []
            for x in range(self._width):
                tmp = ChessPiece(self.PIECE_NONE, constants.CAMP_NONE)
                tmp.set_type(constants.CHESS_PIECE)
                line.append(tmp)
            self._pieces.append(line)
        del line
        # init RED Camp and Pic
        for idx in range(self._width):
            self._pieces[0][idx].set_camp(constants.CAMP_RED)
        self._pieces[0][0].set_pic(' 車 ')
        self._pieces[0][0].set_type(constants.CHESS_CHE)
        self._pieces[0][1].set_pic(' 馬 ')
        self._pieces[0][1].set_type(constants.CHESS_MA)
        self._pieces[0][2].set_pic(' 象 ')
        self._pieces[0][2].set_type(constants.CHESS_XIANG)
        self._pieces[0][3].set_pic(' 士 ')
        self._pieces[0][3].set_type(constants.CHESS_SHI)
        self._pieces[0][4].set_pic(' 將 ')
        self._pieces[0][4].set_type(constants.CHESS_BOSS)
        self._pieces[0][5].set_pic(' 士 ')
        self._pieces[0][5].set_type(constants.CHESS_SHI)
        self._pieces[0][6].set_pic(' 象 ')
        self._pieces[0][6].set_type(constants.CHESS_XIANG)
        self._pieces[0][7].set_pic(' 馬 ')
        self._pieces[0][7].set_type(constants.CHESS_MA)
        self._pieces[0][8].set_pic(' 車 ')
        self._pieces[0][8].set_type(constants.CHESS_CHE)
        # 上面的camp已经通过循环统一设置过了
        self._pieces[2][1].set_pic(' 炮 ')
        self._pieces[2][1].set_type(constants.CHESS_PAO)
        self._pieces[2][1].set_camp(constants.CAMP_RED)
        self._pieces[2][7].set_pic(' 炮 ')
        self._pieces[2][7].set_type(constants.CHESS_PAO)
        self._pieces[2][7].set_camp(constants.CAMP_RED)
        self._pieces[3][0].set_pic(' 卒 ')
        self._pieces[3][0].set_type(constants.CHESS_BING)
        self._pieces[3][0].set_camp(constants.CAMP_RED)
        self._pieces[3][2].set_pic(' 卒 ')
        self._pieces[3][2].set_type(constants.CHESS_BING)
        self._pieces[3][2].set_camp(constants.CAMP_RED)
        self._pieces[3][4].set_pic(' 卒 ')
        self._pieces[3][4].set_type(constants.CHESS_BING)
        self._pieces[3][4].set_camp(constants.CAMP_RED)
        self._pieces[3][6].set_pic(' 卒 ')
        self._pieces[3][6].set_type(constants.CHESS_BING)
        self._pieces[3][6].set_camp(constants.CAMP_RED)
        self._pieces[3][-1].set_pic(' 卒 ')
        self._pieces[3][-1].set_type(constants.CHESS_BING)
        self._pieces[3][-1].set_camp(constants.CAMP_RED)

        # init WHITE Camp and Pic
        for idx in range(self._width):
            self._pieces[-1][idx].set_camp(constants.CAMP_WHITE)
        self._pieces[-1][0].set_pic(' 車 ')
        self._pieces[-1][0].set_type(constants.CHESS_CHE)
        self._pieces[-1][1].set_pic(' 馬 ')
        self._pieces[-1][1].set_type(constants.CHESS_MA)
        self._pieces[-1][2].set_pic(' 相 ')
        self._pieces[-1][2].set_type(constants.CHESS_XIANG)
        self._pieces[-1][3].set_pic(' 仕 ')
        self._pieces[-1][3].set_type(constants.CHESS_SHI)
        self._pieces[-1][4].set_pic(' 帥 ')
        self._pieces[-1][4].set_type(constants.CHESS_BOSS)
        self._pieces[-1][5].set_pic(' 仕 ')
        self._pieces[-1][5].set_type(constants.CHESS_SHI)
        self._pieces[-1][6].set_pic(' 相 ')
        self._pieces[-1][6].set_type(constants.CHESS_XIANG)
        self._pieces[-1][7].set_pic(' 馬 ')
        self._pieces[-1][7].set_type(constants.CHESS_MA)
        self._pieces[-1][8].set_pic(' 車 ')
        self._pieces[-1][8].set_type(constants.CHESS_CHE)
        # 上面的camp已经通过循环统一设置过了
        self._pieces[-3][1].set_pic(' 砲 ')
        self._pieces[-3][1].set_type(constants.CHESS_PAO)
        self._pieces[-3][1].set_camp(constants.CAMP_WHITE)
        self._pieces[-3][7].set_pic(' 砲 ')
        self._pieces[-3][7].set_type(constants.CHESS_PAO)
        self._pieces[-3][7].set_camp(constants.CAMP_WHITE)
        self._pieces[-4][0].set_pic(' 兵 ')
        self._pieces[-4][0].set_type(constants.CHESS_BING)
        self._pieces[-4][0].set_camp(constants.CAMP_WHITE)
        self._pieces[-4][2].set_pic(' 兵 ')
        self._pieces[-4][2].set_type(constants.CHESS_BING)
        self._pieces[-4][2].set_camp(constants.CAMP_WHITE)
        self._pieces[-4][4].set_pic(' 兵 ')
        self._pieces[-4][4].set_type(constants.CHESS_BING)
        self._pieces[-4][4].set_camp(constants.CAMP_WHITE)
        self._pieces[-4][6].set_pic(' 兵 ')
        self._pieces[-4][6].set_type(constants.CHESS_BING)
        self._pieces[-4][6].set_camp(constants.CAMP_WHITE)
        self._pieces[-4][8].set_pic(' 兵 ')
        self._pieces[-4][8].set_type(constants.CHESS_BING)
        self._pieces[-4][8].set_camp(constants.CAMP_WHITE)

        # init cursor 〄 〠
        self._cursor_original_backup: ChessPiece = self._pieces[self._cursor_loc['y']][self._cursor_loc['x']]
        tmp = ChessPiece(self.cursor_pic(), constants.CAMP_CURSOR)
        tmp.set_type(constants.CHESS_CURSOR)
        self._pieces[self._cursor_loc['y']][self._cursor_loc['x']] = tmp
        del tmp

        self.strategies = chess_strategy.ChessStrategy(self._pieces, True)

    def show_map(self):
        for y in range(self._height):
            for x in range(self._width):
                self._pieces[y][x].show()
            if y == 4:
                print()
                # 楚汉分割线
                print('***********************************')
            else:
                print('\n')

    def clear_map(self):
        return common.clear_console()

    def cursor_move(self, p_target):
        self._pieces[self._cursor_loc['y']][self._cursor_loc['x']] = self._cursor_original_backup
        self._cursor_original_backup = self._pieces[p_target['y']][p_target['x']]
        tmp = ChessPiece(self.cursor_pic(), constants.CAMP_CURSOR)
        tmp.set_type(constants.CHESS_CURSOR)
        self._pieces[p_target['y']][p_target['x']] = tmp
        self._cursor_loc = p_target
        # 刷新地图
        self.clear_map()
        self.show_map()

    # 参数类型限定和返回值类型限定可以不要【类似ts里可以不要】
    def can_move(self, direct: str) -> bool:
        if direct == 'w':
            if self._cursor_loc['y'] - 1 < 0:
                return False
        elif direct == 'd':
            if self._cursor_loc['x'] + 1 >= self._width:
                return False
        elif direct == 's':
            if self._cursor_loc['y'] + 1 >= self._height:
                return False
        elif direct == 'a':
            if self._cursor_loc['x'] - 1 < 0:
                return False
        else:
            return False
        return True

    def cursor_pic(self) -> str:
        if self._checked:
            return self.CURSOR_CHECKED
        else:
            return self.CURSOR_UNCHECKED

    """ Python3里可以手动指定返回值类型,而且参数类型也是可以指定的 """
    def control(self) -> None:
        while True:
            ch = self._getch()
            # 清空之前的显示状态
            self.clear_map()
            # 重新绘制象棋布局
            self.show_map()

            if ch == 'w' or ch == 'A' or ch == b'w' or ch == b'H':
                if self.can_move('w'):
                    self.cursor_move({'x': self._cursor_loc['x'], 'y': self._cursor_loc['y'] - 1})
                print('↑', end='')
            elif ch == 'd' or ch == 'C' or ch == b'd' or ch == b'M':
                if self.can_move('d'):
                    self.cursor_move({'x': self._cursor_loc['x'] + 1, 'y': self._cursor_loc['y']})
                print('→', end='')
            elif ch == 's' or ch == 'B' or ch == b's' or ch == b'P':
                if self.can_move('s'):
                    self.cursor_move({'x': self._cursor_loc['x'], 'y': self._cursor_loc['y'] + 1})
                print('↓', end='')
            elif ch == 'a' or ch == 'D' or ch == b'a' or ch == b'K':
                if self.can_move('a'):
                    self.cursor_move({'x': self._cursor_loc['x'] - 1, 'y': self._cursor_loc['y']})
                print('←', end='')
            elif ch.lower() == 'q' or ch == b'q':
                print('game quit!')
                sys.stdout.flush()
                break
            elif ch == ' ' or ch == b' ':
                if not self._checked:
                    self.do_check()
                    print('选中', end='')
                else:
                    self.do_release()
                    print('释放', end='')
                # 判定游戏是否结束
                if self.game_over:
                    print('\ngame over!, 胜利方是:' + self.winner)
                    sys.stdout.flush()
                    break
            else:
                print('无效按键', end='')
            # 立刻刷新输入流,否则getch()获取的字符不会立刻显示
            sys.stdout.flush()

    def can_check(self):
        tmp = self._cursor_original_backup.get_camp()
        if tmp != constants.CAMP_RED and tmp != constants.CAMP_WHITE:
            return False
        if self.firster == constants.CAMP_RED and tmp == constants.CAMP_WHITE:
            return False
        if self.firster == constants.CAMP_WHITE and tmp == constants.CAMP_RED:
            return False
        return True

    def do_check(self):
        if self.can_check():
            self._checked = ~self._checked
            self._pieces[self._cursor_loc['y']][self._cursor_loc['x']].set_pic(self.cursor_pic())
            # 复制一份,和Java一样基础类型是值赋值,而对象是引用赋值
            self._checked_loc = self._cursor_loc.copy()
            self.clear_map()
            self.show_map()

    def do_release(self):
        # 判定是否是取消,是取消则不翻转当前选手
        if self._cursor_loc['x'] == self._checked_loc['x'] and self._cursor_loc['y'] == self._checked_loc['y']:
            self._checked = ~self._checked
            self._pieces[self._cursor_loc['y']][self._cursor_loc['x']].set_pic(self.cursor_pic())
            self.clear_map()
            self.show_map()
            return
        if self.can_release():
            self._checked = ~self._checked
            # 判断游戏是否结束
            if self._cursor_original_backup.get_camp() != constants.CAMP_NONE and self._cursor_original_backup.get_type() == constants.CHESS_BOSS:
                self.game_over = True
                self.winner = self.firster
            # 切换释放棋子后的布局
            self._cursor_original_backup = self._pieces[self._checked_loc['y']][self._checked_loc['x']]
            tmp = ChessPiece(self.PIECE_NONE, constants.CAMP_NONE)
            tmp.set_type(constants.CHESS_PIECE)
            self._pieces[self._checked_loc['y']][self._checked_loc['x']] = tmp
            self._pieces[self._cursor_loc['y']][self._cursor_loc['x']].set_pic(self.cursor_pic())
            self.clear_map()
            self.show_map()
            # 切换选手
            self.reverse_role()

    """ 策略层,TODO 待完善 """
    def can_release(self):
        # 初步判定,至少不能吃自己这边的子【Python里函数调用太坑了吧,刚才self._cursor_original_backup.get_camp这种方式居然不报错。。】
        if self._cursor_original_backup.get_camp() == self.firster:
            return False
        # 其他规则
        tmp = self._pieces[self._checked_loc['y']][self._checked_loc['x']]
        if tmp.get_type() == constants.CHESS_BING:
            return self.strategies.bing(tmp, self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_CHE:
            return self.strategies.che(self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_PAO:
            return self.strategies.pao(self._cursor_original_backup, self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_MA:
            return self.strategies.ma(self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_XIANG:
            return self.strategies.xiang(tmp, self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_SHI:
            return self.strategies.shi(tmp, self._checked_loc, self._cursor_loc)
        if tmp.get_type() == constants.CHESS_BOSS:
            return self.strategies.boss(tmp, self._cursor_original_backup, self._checked_loc, self._cursor_loc)
        return True

    def reverse_role(self):
        if self.firster == constants.CAMP_RED:
            self.firster = constants.CAMP_WHITE
        else:
            self.firster = constants.CAMP_RED

    def start(self):
        self.clear_map()
        self.show_map()
        self.control()


# 和类定义相关的代码就要求空两行,上面是类结束,所以这里需要空两行(当然不是强制的)
if __name__ == '__main__':
    game = GameContext()
    game.clear_map()
    game.show_map()
    game.control()

6.接下来是策略模块,用于对各类棋子的移动和吃子进行限制,这个没啥好讲的,根据象棋规则看代码即可:

import math

from chess_piece import ChessPiece
import constants


# 好吧,Python规范里要求类和其他代码之间空两行,而且要求整个文件最后一行是空行,然后#和前面的代码空两个空格
class ChessStrategy:

    pieces: []

    _height = 0
    _width = 0

    _red_up = True
    _red_line = 4
    _white_line = 5
    _boss_x_left = 3
    _boss_x_right = 5
    _boss_y_red = 2
    _boss_y_white = 7

    # [ChessPiece]限定列表元素类型为ChessPiece
    def __init__(self, pieces: [ChessPiece], red_up: bool = True):
        self.pieces = pieces
        self._height = len(pieces)
        self._height = len(pieces[0])
        # 红方是否在地图上方,这关乎到兵不能后退等问题
        self._red_up = red_up
        # 红方和白方的楚河汉界线
        if red_up:
            self._red_line = 4
            self._white_line = 5
            self._boss_y_red = 2
            self._boss_y_white = 7
        else:
            self._red_line = 5
            self._white_line = 4
            self._boss_y_red = 7
            self._boss_y_white = 2

    """ 是否直着走 """
    def is_straight(self, origin: {}, dest: {}) -> bool:
        if (origin['x'] - dest['x']) * (origin['y'] - dest['y']) == 0:
            return True

    """ 兵的阵营,象棋地图,待移动兵的坐标 """
    def bing(self, active: ChessPiece, origin: {}, dest: {}):
        # 存在斜着走判定
        if not self.is_straight(origin, dest):
            return False
        # 存在移动超过多步判定
        if abs(origin['x'] - dest['x']) > 1 or abs(origin['y'] - dest['y']) > 1:
            return False
        # 不能后退判定和过了河才能左右移动判定
        if self._red_up:
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] - origin['y'] < 0:
                    return False
                # 过河才能左右移动
                if abs(dest['x'] - origin['x']) > 0:
                    if origin['y'] <= self._red_line:
                        return False
            else:
                if dest['y'] - origin['y'] > 0:
                    return False
                # 过了河才能左右移动
                if abs(dest['x'] - origin['x']) > 0:
                    if origin['y'] >= self._white_line:
                        return False
        else:  # 红方在下面
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] - origin['y'] > 0:
                    return False
                # 过了河才能左右移动
                if abs(dest['x'] - origin['x']) > 0:
                    if origin['y'] >= self._white_line:
                        return False
            else:
                if dest['y'] - origin['y'] < 0:
                    return False
                # 过了河才能左右移动
                if abs(dest['x'] - origin['x']) > 0:
                    if origin['y'] <= self._white_line:
                        return False
        return True

    """ 車 """
    def che(self, origin: {}, dest: {}):
        if not self.is_straight(origin, dest):
            return False
        if abs(origin['x'] - dest['x']) == 1 or abs(origin['y'] - dest['y']) == 1:
            return True
        # 横着走
        if origin['x'] - dest['x'] != 0:
            for idx in range(min(origin['x'], dest['x']) + 1, max(origin['x'], dest['x'])):
                if self.pieces[dest['y']][idx].get_type() != constants.CHESS_PIECE:
                    return False
        else:  # 竖着走
            for idx in range(min(origin['y'], dest['y']) + 1, max(origin['y'], dest['y'])):
                if self.pieces[idx][dest['x']].get_type() != constants.CHESS_PIECE:
                    return False
        return True

    """ 炮 """
    def pao(self, dest_piece: ChessPiece, origin: {}, dest: {}):
        if not self.is_straight(origin, dest):
            return False

        middle_count = 0
        if origin['x'] - dest['x'] != 0:  # 横着走
            for idx in range(min(origin['x'], dest['x']) + 1, max(origin['x'], dest['x'])):
                if self.pieces[dest['y']][idx].get_type() != constants.CHESS_PIECE:
                    middle_count += 1
        else:  # 竖着走
            for idx in range(min(origin['y'], dest['y']) + 1, max(origin['y'], dest['y'])):
                if self.pieces[idx][dest['x']].get_type() != constants.CHESS_PIECE:
                    middle_count += 1
        if middle_count > 1:
            return False
        if middle_count == 1 and dest_piece.get_camp() == constants.CAMP_NONE:
            return False
        if middle_count == 0 and dest_piece.get_camp() != constants.CAMP_NONE:
            return False
        return True

    def ma(self, origin: {}, dest: {}):
        # 走日字判断
        if abs((origin['x'] - dest['x']) * (origin['y'] - dest['y'])) != 2:
            return False
        # 拗马脚判断
        tmpy = math.trunc((dest['y'] - origin['y'])/2)
        tmpx = math.trunc((dest['x'] - origin['x'])/2)
        middlex = origin['x'] + tmpx
        middley = origin['y'] + tmpy
        if self.pieces[middley][middlex].get_camp() != constants.CAMP_NONE:
            return False
        return True

    def xiang(self, active: ChessPiece, origin: {}, dest: {}):
        # 判断是否是田字走法
        if abs(origin['x'] - dest['x']) != 2 or abs(origin['y'] - dest['y']) != 2:
            return False
        # 判断是否拗象脚
        tmpx = (dest['x'] - origin['x'])//2 + origin['x']
        tmpy = (dest['y'] - origin['y'])//2 + origin['y']
        if self.pieces[tmpy][tmpx].get_camp() != constants.CAMP_NONE:
            return False
        # 象不能过河的判断
        if self._red_up:
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] > self._red_line:
                    return False
            else:
                if dest['y'] < self._white_line:
                    return False
        else:  # 红方在下面
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] < self._red_line:
                    return False
            else:
                if dest['y'] > self._white_line:
                    return False
        return True

    def shi(self, active: ChessPiece, origin: {}, dest: {}):
        # 判断是否走的斜线且距离为1
        if abs((dest['x'] - origin['x']) * (dest['y'] - origin['y'])) != 1:
            return False
        # 判断是否移出左右边界
        if dest['x'] < self._boss_x_left or dest['x'] > self._boss_x_right:
            return False
        # 判断是否移出Y轴边界
        if self._red_up:
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] > self._boss_y_red:
                    return False
            else:
                if dest['y'] < self._boss_y_white:
                    return False
        else:  # 红方在下面
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] < self._boss_y_red:
                    return False
            else:
                if dest['y'] > self._boss_y_white:
                    return False
        return True

    def boss(self, active: ChessPiece, dest_piece: ChessPiece, origin: {}, dest: {}):
        # 判断是否将帅见面,这种情况下可以移动到对方大本营里吃对方主将
        if active.get_type() == constants.CHESS_BOSS and dest_piece.get_type() == constants.CHESS_BOSS and origin['x'] == dest['x']:
            middle_count = 0
            for idx in range(min(origin['y'], dest['y']) + 1, max(origin['y'], dest['y'])):
                if self.pieces[idx][dest['x']].get_type() != constants.CHESS_PIECE:
                    middle_count += 1
                    break
            if middle_count == 0:
                return True
        # 判断是否走的直线且距离为1
        if abs(dest['x'] - origin['x']) + abs(dest['y'] - origin['y']) != 1:
            return False
        # 判断是否移出左右边界
        if dest['x'] < self._boss_x_left or dest['x'] > self._boss_x_right:
            return False
        # 判断是否移出Y轴边界
        if self._red_up:
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] > self._boss_y_red:
                    return False
            else:
                if dest['y'] < self._boss_y_white:
                    return False
        else:  # 红方在下面
            if active.get_camp() == constants.CAMP_RED:
                if dest['y'] < self._boss_y_red:
                    return False
            else:
                if dest['y'] > self._boss_y_white:
                    return False
        return True

7.最后就是main模块,其实就是创建个游戏环境类然后运行游戏的作用:

# coding:utf-8
import platform
import sys

if __name__ == '__main__':
    system = platform.system()
    if system == 'Windows':
        print('请使用Linux系统,最好是Ubuntu18.x版本,否则其他的控制台输出的布局可能会不整齐')
        # exit()
    if sys.version_info < (3, 0) or sys.version_info >= (4, 0):
        print('请使用Python3.x')
        exit()
    print("Game started.")
    import game_context
    import constants
    game = game_context.GameContext(constants.CAMP_WHITE)
    game.start()

六:源码分析

 这里主要分析游戏环境类,这个是整个游戏的关键;

1.象棋的地图是通过二维的ChessPiece数组来实现管理的;通过二维数组来实现打印出棋子、光标、空白,这三种都是ChessPiece,不过是它们的阵营,pic等不同

2.在游戏环境类里通过类属性,记录由谁先行,游戏是否结束,胜利方是谁,是否选中棋子,当前光标位置,选中棋子的位置等数据,用于后续的逻辑判定

3.在init方法里初始化象棋地图,然后每个棋子都自我实现了printc方法来打印自己(包括颜色,文本/pic),以及初始化光标等参数

4.这里实现光标移动原理其实就是getch()获得移动按键后,然后修改_pieces,接着清屏和show_map(),这些方法都在类里可以找到;

5.游戏的控制逻辑在control(self)方法里实现,通过循环调用getch()不断的监听按键输入,然后判断按键类型,如果是方向键则通过cursor_move()方法来实现移动光标,内部通过can_move()来判断是否可以进行方向移动

,比如肯定不能越界;然后判断如果是空格键,表示是要选中棋子或者释放棋子,这里则通过判断当前光标是释放状态还是选中状态来分别执行do_check()和do_release(),在do_check()里通过can_check()来判断是否可以

选中某个piece(因为象棋地图里每个元素都是piece,所以肯定需要判断当前光标所在位置的piece是不是象棋棋子还是空白棋子),选中后光标的图案也会改变;

在do_release()里也是通过can_release()来判断是否可以释放棋子,比如咱们选中了白方棋子,那么释放棋子的地方肯定不能是另一个白方棋子,因为自己人不能吃自己人。。;然后在can_release()里调用策略模块里的策略

,分别对不同类型的棋子进行规则判断,比如炮吃子的话只能隔一个子吃,馬走日且不能拗马脚等等;

6.总结:重要的方法为control(),cursor_move(),can_move(),do_check(),can_check(),do_release(),can_release(),__init()__这些都是主要的业务逻辑判断;

七:其他

这里还有个我用Rust写的俄罗斯方块,不过文章没有写的很全但是代码啥的都在github里了,大家有兴趣可以去看看:https://www.cnblogs.com/silentdoer/p/12160871.html

上一篇:Cookie加密处理


下一篇:LeetCode4:寻找两个有序数组的中位数