井字棋--课后程序(Python程序开发案例教程-黑马程序员编著-第7章-课后作业)

这篇具有很好参考价值的文章主要介绍了井字棋--课后程序(Python程序开发案例教程-黑马程序员编著-第7章-课后作业)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

实例2:井字棋

井字棋是一种在3 * 3格子上进行的连珠游戏,又称井字游戏。井字棋的游戏有两名玩家,其中一个玩家画圈,另一个玩家画叉,轮流在3 * 3格子上画上自己的符号,最先在横向、纵向、或斜线方向连成一条线的人为胜利方。如图1所示为画圈的一方为胜利者。

井字棋python,python,开发语言

 

图1 井字棋

本实例要求编写程序,实现具有人机交互功能的井字棋。

实例目标

  1. 理解面向对象的思想
  2. 能独立设计类
  3. 掌握类的继承和父类方法的重写

实例分析

根据实例描述的井字棋游戏的规则,下面模拟一次游戏的流程如图2所示。

井字棋python,python,开发语言

 

图2 井字棋游戏流程

图2中的描述的游戏流程如下:

  1. 重置棋盘数据,清理之前一轮的对局数据,为本轮对局做好准备。
  2. 显示棋盘上每个格子的编号,让玩家熟悉落子位置。
  3. 根据系统随机产生的结果确定先手玩家(先手使用X)。
  4. 当前落子一方落子。
  5. 显示落子后的棋盘。
  6. 判断落子一方是否胜利?若落子一方取得胜利,修改玩家得分,本轮对局结束,跳转至第(9)步。
  7. 判断是否和棋?若出现和棋,本轮对局结束,跳转至第(9)步。
  8. 交换落子方,跳转至第(4)步,继续本轮游戏。
  9. 显示玩家当前对局比分。

以上流程中,落子是游戏中的核心功能,如何落子则是体现电脑智能的关键步骤,实现智能落子有策略可循的。按照井字棋的游戏规则:当玩家每次落子后,玩家的棋子在棋盘的水平、垂直或者对角线任一方向连成一条直线,则表示玩家获胜。因此,我们可以将电脑的落子位置按照优先级分成以下三种:

(1)必胜落子位置

我方在该位置落子会获胜。一旦出现这种情况,显然应该毫不犹豫在这个位置落子。

(2)必救落子位置

对方在该位置落子会获胜。如果我方暂时没有必胜落子位置,那么应该在必救落子位置落子,以阻止对方获胜。

(3)评估子力价值

评估子力价值,就是如果在该位置落子获胜的几率越高,子力价值就越大;获胜的几率越低,子力价值就越小。

如果当前的棋盘上,既没有必胜落子位置,也没有必救落子位置,那么就应该针对棋盘上的每一个空白位置寻找子力价值最高的位置落子。

要编写一个评估子力价值的程序,需要考虑诸多因素,这里我们选择了一种简单评估子力价值的方式——只考虑某个位置在空棋盘上的价值,而不考虑已有棋子以及落子之后的盘面变化。下面来看一下在空棋盘上不同位置落子的示意图,如图3所示。

井字棋python,python,开发语言

 

图3 棋盘落子示意图

观察图3不难发现,玩家在空棋盘上落子的位置可分为以下3种情况:

  1. 中心点,这个位置共有4个方向可能和其它棋子连接成直线,获胜的几率最高。
  2. 4个角位,这4个位置各自有3个方向可能和其它棋子连接成直线,获胜几率中等。
  3. 4个边位,这4个位置各自有2个方向可能和其它棋子连接成直线,获胜几率最低。

综上所述,如果电脑在落子时,既没有必胜落子位置,也没有必救落子位置时,我们就可以让电脑按照胜率的高低来选择落子位置,也就是说,若棋盘的中心点没有棋子,则选择中心点作为落子位置;若中心点已有棋子,而角位没有棋子,则随机选择一个没有棋子的角位作为落子位置;若中心点和四个角位都有棋子,而边位没有棋子,则随机选择一个没有棋子的边位作为落子位置。

井字棋游戏一共需要设计4个类,不同的类创建的对象承担不同的职责,分别是:

(1)游戏类(Game):负责整个游戏流程的控制,是该游戏的入口。

(2)棋盘类(Board):负责显示棋盘、记录本轮对局数据、以及判断胜利等和对弈相关的处理工作。

(3)玩家类(Player):负责记录玩家姓名、棋子类型和得分、以及实现玩家在棋盘上落子。

(4)电脑玩家类(AIPlayer):是玩家类的子类。在电脑玩家类类中重写玩家类的落子方法,在重写的方法中实现电脑智能选择落子位置的功能。

设计后的类图如图4所示。

井字棋python,python,开发语言

 

图4 类结构图

本实例中涉及到多个类,为保证程序具有清晰的结构,可以将每个类的相关代码分别放置到与其同名的py文件中。另外,由于Player和AIPlayer类具有继承关系,可以将这两个类的代码放置到player.py文件中。

代码实现

本实例的实现过程如下所示。

  1. 创建项目

使用PyCharm创建一个名为“井字棋V1.0”的文件夹,在该文件夹下分别创建3个py文件,分别为board.py、game.py与player.py,此时程序的目录结构如图5所示。

井字棋python,python,开发语言

 

图5 井字棋文件目录

由于棋盘类是井字棋游戏的重点,因此我们先开发Board类。

  1. 设计Board

(1)属性

井字棋的棋盘上共有9个格子落子,落子也是有位置可循的,因此这里使用列表作为棋盘的数据结构,列表中的元素则是棋盘上的棋子,它有以下三种取值:

  1. " " 表示没有落子,是初始值;
  2. "X" 表示玩家在该位置下了一个X的棋子;
  3. "O" 表示玩家在该位置下了一个O的棋子。

其中列表中的元素为" "的位置才允许玩家落子。为了让玩家明确可落子的位置,需要增加可落子列表。根据图4中设计的类图,在board.py文件中定义Board类,并在该类的构造方法中添加属性board_data和movable_list,具体代码如下。

class Board(object):

    """棋盘类"""

    def __init__(self):

        self.board_data = [" "] * 9          # 棋盘数据

        self.movable_list = list(range(9))  # 可移动列表

(2)show_board()方法

show_board()方法实现创建一个九宫格棋盘的功能。游戏过程中显示的棋盘分为两种情况,一种是新一轮游戏开始前显示的有索引的棋盘,让玩家明确棋盘格子与序号的对应关系;另一种是游戏中显示当前落子情况的棋盘,会在玩家每次落子后展示。在Board类中添加show_board()方法,并在该方法中传递一个参数show_index,用于设置是否在棋盘中显示索引(默认为False,表示不显示索引),具体代码如下。

def show_board(self, show_index=False):

    """显示棋盘

    :param show_index: True 表示显示索引 / False 表示显示数据

    """

    for i in (0, 3, 6):

        print("       |       |")

        if show_index:

            print("   %d   |   %d   |   %d" % (i, i + 1, i + 2))

        else:

            print("   %s   |   %s   |   %s" % (self.board_data[i],

                                             self.board_data[i + 1],

                                             self.board_data[i + 2]))

        print("       |       |")

        if i != 6:

            print("-" * 23)

(3)move_down ()方法

move_down ()方法实现在指定的位置落子的功能,该方法接收两个参数,分别是表示落子位置的index和表示落子类型(X或者O)的chess,接收的这些参数都是落子前需要考虑的必要要素,具体代码如下。

def move_down(self, index, chess):

    """在指定位置落子

    :param index: 列表索引

    :param chess: 棋子类型 X O

    """

    # 1. 判断 index 是否在可移动列表中

    if index not in self.movable_list:

        print("%d 位置不允许落子" % index)

        return

    # 2. 修改棋盘数据

    self.board_data[index] = chess

    # 3. 修改可移动列表

    self.movable_list.remove(index)

以上代码首先判断落子位置是否可以落子,如果可以就将棋子添加到board_data列表的对应位置,并从movable_list列表中删除。

(4)is_draw ()方法

is_draw ()方法实现判断游戏是否平局的功能,该方法会查看可落子索引列表中是否有值,若没有值表示棋盘中的棋子已经落满了,说明游戏平局,具体代码如下。

def is_draw(self):

    """是否平局"""

    return not self.movable_list

(5)is_win ()方法

is_draw ()方法实现判断游戏是否胜利的功能,该方法会先定义方向列表,再遍历方向列表判断游戏是否胜利,胜利则返回True,否则返回False,具体代码如下。

def is_win(self, chess, ai_index=-1):

    """是否胜利

    :param chess: 玩家的棋子

    :param ai_index: 预判索引,-1 直接判断当前棋盘数据

    """

    # 1. 定义检查方向列表

    check_dirs = [[0, 1, 2], [3, 4, 5], [6, 7, 8],

                     [0, 3, 6], [1, 4, 7], [2, 5, 8],

                     [0, 4, 8], [2, 4, 6]]

    # 2. 定义局部变量记录棋盘数据副本

    data = self.board_data.copy()

    # 判断是否预判胜利

    if ai_index > 0:

        data[ai_index] = chess

    # 3. 遍历检查方向列表判断是否胜利

    for item in check_dirs:

        if (data[item[0]] == chess and

            data[item[1]] == chess

                and data[item[2]] == chess):

            return True

    return False

注意,is_win()方法的ai_index参数的默认值为-1,表示无需进行预判,即提示玩家最有利的落子位置;若该参数不为-1时,表示需要进行预判。

(6)reset_board ()方法

reset_board ()方法实现清空棋盘的功能,该方法中会先清空movable_list,再将棋盘上的数据全部置为初始值,最后往movable_list中添加0~8的数字,具体代码如下。

def reset_board(self):

    """重置棋盘"""

    # 1. 清空可移动列表数据

    self.movable_list.clear()

    # 2. 重置数据

    for i in range(9):

        self.board_data[i] = " "

        self.movable_list.append(i)

  1. 设计Player

根据图4中设计的类图,在player.py文件中定义Player类,分别在该类中添加属性和方法,具体内容如下。

(1)属性

在Player类中添加name、score、chess属性,具体代码如下。

import board

import random

class Player(object):

    """玩家类"""

    def __init__(self, name):

        self.name = name     # 姓名

        self.score = 0       # 成绩

        self.chess = None   # 棋子

(2)move()方法

move()方法实现玩家在指定位置落子的功能,该方法中会先提示用户棋盘上可落子的位置,之后使棋盘根据用户选择的位置重置棋盘数据后进行显示,具体代码如下。

def move(self, chess_board):

    """在棋盘上落子

    :param chess_board:

    """

    # 1. 由用户输入要落子索引

    index = -1

    while index not in chess_board.movable_list:

        try:

            index = int(input(" %s 输入落子位置 %s" %

                (self.name, chess_board.movable_list)))

        except ValueError:

            pass

    # 2. 在指定位置落子

    chess_board.move_down(index, self.chess)

  1. 设计AIPlayer

根据图4中设计的类图,在player.py文件中定义继承自Player类的子类AIPlayer。AIPlayer类中重写了父类的move()方法,在该方法中需要增加分析中的策略,使得计算机玩家变得更加聪明,具体代码如下。

class AIPlayer(Player):

    """智能玩家"""

    def move(self, chess_board):

        """在棋盘上落子

        :param chess_board:

        """

        print("%s 正在思考落子位置..." % self.name)

        # 1. 查找我方必胜落子位置

        for index in chess_board.movable_list:

            if chess_board.is_win(self.chess, index):

                print("走在 %d 位置必胜!!!" % index)

                chess_board.move_down(index, self.chess)

                return

        # 2. 查找地方必胜落子位置-我方必救位置

        other_chess = "O" if self.chess == "X" else "X"

        for index in chess_board.movable_list:

            if chess_board.is_win(other_chess, index):

                print("敌人走在 %d 位置必输,火速堵上!" % index)

                chess_board.move_down(index, self.chess)

                return

        # 3. 根据子力价值选择落子位置

        index = -1

        # 没有落子的角位置列表

        corners = list(set([0, 2, 6, 8]).intersection(

chess_board.movable_list))

        # 没有落子的边位置列表

        edges = list(set([1, 3, 5, 7]).intersection(

chess_board.movable_list))

        if 4 in chess_board.movable_list:

            index = 4

        elif corners:

            index = random.choice(corners)

        elif edges:

            index = random.choice(edges)

        # 在指定位置落子

        chess_board.move_down(index, self.chess)

  1. 设计Game

根据图4中设计的类图,在game.py文件中定义Game类,分别在该类中添加属性和方法,具体内容如下。

(1)属性

在Game类中添加chess_board、human、computer属性,具体代码如下。

import random

import board

import player

class Game(object):

    """游戏类"""

    def __init__(self):

        self.chess_board = board.Board()          # 棋盘对象

        self.human = player.Player("玩家")        # 人类玩家对象

        self.computer = player.AIPlayer("电脑")  # 电脑玩家对象

(2)random_player()方法

random_player()方法实现随机生成先手玩家的功能,该方法中会先随机生成0和1两个数,选到数字1的玩家为先手玩家,然后再为两个玩家设置棋子类型,即先手玩家为“X”,对手玩家为“O”,具体代码如下。

def random_player(self):

    """随机先手玩家

    :return: 落子先后顺序的玩家元组

    """

    # 随机到 1 表示玩家先手

    if random.randint(0, 1) == 1:

        players = (self.human, self.computer)

    else:

        players = (self.computer, self.human)

    # 设置玩家棋子

    players[0].chess = "X"

    players[1].chess = "O"

    print("根据随机抽取结果 %s 先行" % players[0].name)

    return players

(3)play_round ()方法

play_round ()方法实现一轮完整对局的功能,该方法的逻辑可按照实例分析的一次流程完成,具体代码如下。

def play_round(self):

    """一轮完整对局"""

    # 1. 显示棋盘落子位置

    self.chess_board.show_board(True)

    # 2. 随机决定先手

    current_player, next_player = self.random_player()

    # 3. 两个玩家轮流落子

    while True:

        # 下子方落子

        current_player.move(self.chess_board)

        # 显示落子结果

        self.chess_board.show_board()

        # 是否胜利?

        if self.chess_board.is_win(current_player.chess):

            print("%s 战胜 %s" % (current_player.name, next_player.name))

            current_player.score += 1

            break

        # 是否平局

        if self.chess_board.is_draw():

            print("%s %s 战成平局" % (current_player.name,

next_player.name))

            break

        # 交换落子方

        current_player, next_player = next_player, current_player

    # 4. 显示比分

    print("[%s] 对战 [%s] 比分是 %d : %d" % (self.human.name,

                                        self.computer.name,

                                        self.human.score,

                                        self.computer.score))

从上述代码可以看出,大部分的功能都是通过游戏中各个对象访问属性或调用方法实现的,这正好体现了类的封装性的特点,即每个类分工完成各自的任务。

(4)start ()方法

start ()方法实现循环对局的功能,该方法中会在每轮对局结束之后询问玩家是否再来一局,若玩家选择是,则重置棋盘数据后开始新一轮对局;若玩家选择否,则会退出游戏,具体代码如下。

def start(self):

    """循环开始对局"""

    while True:

        # 一轮完整对局

        self.play_round()

        # 询问是否继续

        is_continue = input("是否再来一盘(Y/N)?").upper()

        # 判断玩家输入

        if is_continue != "Y":

            break

        # 重置棋盘数据

        self.chess_board.reset_board()

最后在game.py文件中通过Game类对象调用start()方法启动井字棋游戏,具体代码如下。

if __name__ == '__main__':

    Game().start()

代码测试

运行程序,对战一局游戏的结果如下所示:

       |       |

   0   |   1   |   2

       |       |

-----------------------

       |       |

   3   |   4   |   5

       |       |

-----------------------

       |       |

   6   |   7   |   8

       |       |

根据随机抽取结果 电脑 先行

电脑 正在思考落子位置...

       |       |

       |       |   

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

       |       |   

       |       |

玩家输入落子位置 [0, 1, 2, 3, 5, 6, 7, 8]0

       |       |

   O   |       |   

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

       |       |   

       |       |

电脑 正在思考落子位置...

       |       |

   O   |       |   

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

   X   |       |   

       |       |

玩家输入落子位置 [1, 2, 3, 5, 7, 8]2

       |       |

   O   |       |   O

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

   X   |       |   

       |       |

电脑 正在思考落子位置...

敌人走在 1 位置必输,火速堵上!

       |       |

   O   |   X   |   O

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

   X   |       |   

       |       |

玩家输入落子位置 [3, 5, 7, 8]7

       |       |

   O   |   X   |   O

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

   X   |   O   |   

       |       |

电脑 正在思考落子位置...

       |       |

   O   |   X   |   O

       |       |

-----------------------

       |       |

       |   X   |   

       |       |

-----------------------

       |       |

   X   |   O   |   X

       |       |

玩家输入落子位置 [3, 5]5

       |       |

   O   |   X   |   O

       |       |

-----------------------

       |       |

       |   X   |   O

       |       |

-----------------------

       |       |

   X   |   O   |   X

       |       |

电脑 正在思考落子位置...

       |       |

   O   |   X   |   O

       |       |

-----------------------

       |       |

   X   |   X   |   O

       |       |

-----------------------

       |       |

   X   |   O   |   X

       |       |

电脑 玩家 战成平局

[玩家] 对战 [电脑] 比分是 0 : 0

是否再来一盘(Y/N)?n文章来源地址https://www.toymoban.com/news/detail-758156.html

到了这里,关于井字棋--课后程序(Python程序开发案例教程-黑马程序员编著-第7章-课后作业)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 中文数字对照表--课后程序(Python程序开发案例教程-黑马程序员编著-第4章-课后作业)

    阿拉伯数字因其具有简单易写、方便使用的特点成为了最流行的数字书写方式,但在使用阿拉伯数字计数时,可以对某些数字不漏痕迹的修改成其它数字,例如,将数字“1”修改为数字“7”,将数字“3”修改为数字“8”。为了避免引起不必要的麻烦,可以使用中文大写数字

    2024年02月05日
    浏览(77)
  • 图书管理系统登录页面--课后程序(Python程序开发案例教程-黑马程序员编著-第12章-课后作业)

    登录与注册是程序中最基本的模块。用户只有登录成功后,才可以使用应用系统中的全部功能。若用户没有登录账号,可通过注册界面设置登录账号信息。某图书管理系统的登录窗口如图1所示。   登录界面 图1的窗口中包含用户名、密码、验证码、登录、注册、退出。当用户

    2024年02月03日
    浏览(51)
  • 加油优惠价格计算-课后程序(JavaScript前端开发案例教程-黑马程序员编著-第2章-课后作业)

    一、案例描述 考核知识点 if 、 if…else 、if…else if…else 练习目标 掌握if单分支语句。 掌握if…else双分支语句 掌握if…else if…else多分支语句 需求分析 加油站,为了鼓励车主多加油,实行多加多优惠政策,具体优惠如下: 已知92号汽油,每升6元;如果大于等于20升,那么每

    2024年02月07日
    浏览(96)
  • 《JavaScript前端开发案例教程》课后习题及答案(黑马程序员编著)

    一、填空题 1.window.document.body可以简写为______。 2.console.log(alert(‘Hello’))在控制台的输出结果是______。 3.编辑器中“以UTF-8无BOM格式编码”中的BOM指的是______。 4.alert(‘测试’.length)的输出结果是______。 二、判断题 1.JavaScript是Java语言的脚本形式。( ) 2.JavaScript中的方法名不

    2024年02月09日
    浏览(55)
  • 微信小程序开发教程:项目一微信小程序入门 课后习题

    《微信小程序开发教程》主编/黄寿孟 易芳 陶延涛 湖南大学出版社 目录 一、单选题 二、多选题 三、判断题 四、填空题 五、简答题 1.请简述微信开发者工具中调试器功能。 2.请简述微信小程序开发环境的搭建过程。 六、编程题 1.请创建一个空白项目,在页面中输出Hello W

    2024年02月11日
    浏览(57)
  • Python---案例-多任务版TCP服务端程序开发

    1. 需求 目前我们开发的TCP服务端程序只能服务于一个客户端,如何开发一个多任务版的TCP服务端程序能够服务于多个客户端呢? 完成多任务,可以使用 线程 ,比进程更加节省内存资源。 2. 具体实现步骤 编写一个TCP服务端程序,循环等待接受客户端的连接请求 当客户端和服

    2024年02月04日
    浏览(48)
  • python简单易懂的小程序,python小程序开发教程

    本篇文章给大家谈谈python简单易懂的小程序,以及python小程序开发教程,希望对各位有所帮助,不要忘了收藏本站喔。 有不少同学学完 Python 后仍然很难将其灵活运用。我整理 37 个 Python 入门的小程序70个python练手项目。在实践中应用 Python 会有事半功倍的效果。 例子1:华氏

    2024年01月22日
    浏览(48)
  • 学生管理系统-课后程序(JAVA基础案例教程-黑马程序员编著-第六章-课后作业)

    【案例6-2】 学生管理系统 【案例介绍】 1.任务描述 在一所学校中,对学生人员流动的管理是很麻烦的,本案例要求编写一个学生管理系统,实现对学生信息的添加、删除、修改和查询功能。每个功能的具体要求如下: 系统的首页:用于显示系统所有的操作,并根据用户在控

    2024年02月03日
    浏览(63)
  • 日记本-课后程序(JAVA基础案例教程-黑马程序员编著-第七章-课后作业)

    【实验7-3】  日记本 【任务介绍】   1. 任务描述 编写一个日记本功能的程序,使用字节流经日记的具体信息记录在本地的txt文件中。当用户输入日记的特定内容后,会将输入的内容保存至本地的txt文件中。需要输入的内容包括“姓名”,“天气”、“标题”、“内容”的数

    2024年02月05日
    浏览(54)
  • 打印不同的图形-课后程序(JAVA基础案例教程-黑马程序员编著-第四章-课后作业)

    【案例4-1】打印不同的图形 记得 关注,收藏,评论哦,作者将持续更新。。。。 【案例介绍】 案例描述 本案例要求编写一个程序,可以根据用户要求在控制台打印出不同的图形。例如,用户自定义半径的圆形和用户自定义边长的正方形。 运行结果   【案例分析】 ( 1 )

    2024年02月01日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包