首先声明!!!


  • 1、此为知识点总结内容;
  • 2、如有使用或转载请注明出处;
  • 3、如有不足,欢迎批评指正;

* 学习路线

  • 路线规划一(一般小学生):

    • 对于能力一般的学员,36课就是他们的归宿,不用继续往下学;
    • 36课之后,进入 百题速刷(数理解题)、**海龟绘图(竞赛操作题)**两个篇章;
    • 36课在1.5-2小时的线下课,可以填充15节左右。加上百题速刷和海龟绘图,可以填充30多节;
    • 也就是说,这条路线完美解决48节一年课程;然后可以考虑考级。。。
    • 不太建议第一年比赛,因为选择题部分,涉及到算法、数据结构等内容。。。
    • 当然,由于操作题比较合理,而不太正规的赛事的理论题都是拿来主义,自己也不懂。。
    • 大概率:给你一个题库,背一背就可以了。然后主攻操作题。
    • 大部分学生其实无法进入更高阶段的学习,那继续 用Python 数理解题海龟绘图 运作第二年;
  • 路线规划二(优质小学生):

    • 从37课后,可以继续学完,到最后。也可以按照路线一,然后再37课往后;
    • 而本套课程学完之后,解题 和 绘图 也完毕了,考级也考了,比赛也比了;
    • 后续就是 软件开发游戏开发 的各种扩展主题课,直至到中学阶段;
  • 路线规划三(优质中学生):

    • 优质小学生开始进化优质中学生,更强的数理能力和逻辑能力,此时升级到C++信奥赛体系;
    • 这里就开始规划一说的,正规赛事的教学和集训,开始涉及到:
      • 数据结构(链表、堆栈、队列、二叉树等等)
      • 图论(邻接矩阵、无相连通图、节点等)
      • 算法(贪心、递推、二分、枚举、高精度、各种排序等等)
      • 初等数学(代数、平面几何、数论、组合数学等)
    • 当然,这是后话!!!

一、做个小软件.Tkinter入门

1. Tkinter初步

Tkinter是一款Python内置的GUI软件界面开发库,用这个库可以做软件

  • 学习这款库,有以下注意点:

    • 由于是针对小学生5年级+,不按成人那种手册式教学
    • 也就是说:用到哪里,就只讲哪里,并且不涉及原理
    • 如果对软件开发感兴趣,后续独立主题课会开设项目式课程
    • Tkinter和Pygame只花5-6节蜻蜓点水一下,做个了解,学个概念,跟做就行
    • 系统性和软游项目课程会做独立课程,基础课中的内容学完啥也做不了的
  • tkinter库旗下的方法:

方法名 说明
.Tk() 生成窗体的方法,返回值是一个对象(点出万物的变量),设置当前窗体
.Label() 生成一个文字标签,返回值同上,设置当前文本
.mainloop() 循环窗体,不断刷新窗体内容

.Tk()方法下的方法:

方法名 说明
.geometry() 窗体的大小,长高坐标,具体传参看后续代码
.title() 窗体的标题,参数即标题名称

.Label()方法下的方法:

方法名 说明
.pack() 将文本内容定位到指定的窗体上,细节看后续代码

2. 代码详解

通过以上的知识点,完成一个包含文本的窗体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 引入GUI库 tkinter
import tkinter as tk
# 库下的.Tk()方法,称为窗体组件,可以生成一个窗口
# 注意1:Tk中的T是大写,区分大小写
# 注意2:方法返回一个可以点出万物的变量(是对象,还没学这概念)
# 注意3:可以通过print(type(t))来查看类型
t = tk.Tk()
# 设置窗体大小,800x600是长和高,[400是x轴,200是y轴]
t.geometry("800x600")
#t.geometry("800x600+400+200")
# 设置窗体标题
t.title("tkinter 初步")

# 引入一个Label()文字组件
# 注意1:Label(t[,option])方法组件区分大小写
# 注意2:参数t是必须,text是文字内容
lab1 = tk.Label(t, text="我的第一个tk程序!")
# 入驻窗体
lab1.pack()

# 循环,让窗体内交互内容不断刷新
# 但是,由于我们的编辑器自带了轮询,所以不加也在刷新
# 可是,如果脱离编辑器环境,就变成静态的死窗口了
# 结论,还是加上去
tk.mainloop()

二、交互.按钮和文本框

1. 按钮和文本框

按钮**.Button()组件方法和单行文本框.Entry()**组件方法的使用

  • tkinter库旗下的方法:
方法名 说明
.Button() 生成一个按钮,参数看后续代码
.Entry() 生成一个单行文本框,参数看后续代码

.Entry()旗下的方法:

方法名 说明
.get() 可以获取单行文本框的值
.delete() 可以删除单行文本框的值,清空直接范围:0-“end”

.Label()旗下的方法:

方法名 说明
.config() 可以修改文本内的值
  • 如何实现按钮点击触发:
    • 在Button()按钮内设置时,有一个属性:command
    • 将一个函数fn赋值给command属性,即可完成点击按钮触发函数

2. 代码详解

通过以上的知识点,构建一个按钮和输入框,并实现交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建一个文本框
# 注意1:.Entry()组件方法,区分大小写
ent1 = tk.Entry(t, font=("宋体", 20))
ent1.pack()

# 自定义函数
def fn():
#print("感谢点击!")
# 获取文本框的值
res = ent1.get()
# 改变文本标签的值
lab1.config(text=res)
# 清空文本框
# 0表示第一个字符,"end"表示到最后一个字符
ent1.delete(0, "end")

# 创建一个按钮
# 注意1:.Button()组件方法,区分大小写
# 注意2:command是交互属性,可以调用自定义函数
but1 = tk.Button(t, text="确认", command=fn)
but1.pack()

三、合法性.验证和弹窗

1. 验证数据

可以通过以往的只是了判断输入框的数据是否符合要求

1
2
3
4
5
6
# 判断res是否为空,当然可以判断各种其它验证
if res == "":
print("数据不可以为空!")
else:
lab1.config(text=res)
ent1.delete(0, "end")

2. 弹窗组件

弹窗组件是一个需要引入的独立库,通过这个组件可以实现弹窗效果

  • tkinter.messagebox库旗下的方法:
方法名 说明
.showinfo() 提示框的类型设定为:普通提示
.showwarning() 提示框的类型设定为:警告提示

.showinfo()和showwarning()方法旗下的属性:

方法名 说明
title 提示的标题
message 提示内容的详情文本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 引入信息提示框
import tkinter.messagebox as tmb

# 判断res是否为空,当然可以判断各种其它验证
if res == "":
#print("数据不可以为空!")
# 提示组件有集中类别:showinfo(普通消息)、showwarning(警告消息)等...
tmb.showwarning(title="警告信息", message="输入框不可以为空!")
# 让输入框获得焦点
ent1.focus()
else:
lab1.config(text=res)
ent1.delete(0, "end")


四、载入天空.绘图组件

1. 绘制天空

绘图组件:Canvas()提供了一些简单的绘图功能

  • tkinter库旗下的方法:
方法名 说明
.Canvas() 在窗体上绘制图形,可以设置bg、width和height

.Canvas()方法旗下的方法:

方法名 说明
.create_oval(x1,y1,x2,y2[,options]) 创建一个圆的图形,前四个参数为左上角和右下角坐标位置
.move(oval, moveX, moveY) 将绘图区的一个图形,移动相应坐标的数值
1
2
3
4
5
# 载入绘图区域
cv = tk.Canvas(t, bg="lightblue", width="800", height="300")
# 绘制一个橙红太阳
oval = cv.create_oval(450, 150, 350, 50, fill="orange")
cv.pack()

.Tk()方法旗下的方法:

方法名 说明
.resizeable(x,y) 设置窗体的x和y是否允许拖动,都设置False,即无法最大化
1
2
3
4
5
# 移动太阳
cv.move(oval, 0, 100)

# 无法调整大小,x和y轴
t.resizable(False, False)

五、下拉的魅力.菜单组件

1. 设计菜单

软件有一个重要的功能,就是菜单系统:Menu()组件可以实现

tkinter库旗下的方法:

方法名 说明
.Menu() 创建一个菜单组
1
2
3
4
5
6
# 创建第一个菜单容器,用于存放具体的菜单组
one = tk.Menu(t)
# 再创建一个菜单组,内嵌到one菜单容器里
# 注意1:第一参数,内嵌到哪个菜单容器
# 注意2:tearoff只有两个值1和0,1表示可独立分离,0不可以
file = tk.Menu(one, tearoff=0)

.Menu()旗下的方法:

方法名 说明
.add_cascade() 在指定的菜单组中添加一个菜单项
.add_separator() 创建一条水平线
1
2
3
4
5
6
7
8
9
# 在one这个菜单容器中添加一个"文件"菜单项,设置它隶属于file菜单组
one.add_cascade(label="文件", menu=file)
file.add_command(label="新建")
file.add_command(label="打开")
# 水平分割线
file.add_separator()
file.add_command(label="保存")
# cmmand执行执行函数
file.add_command(label="关闭", command=close)

.Tk()旗下的方法:

方法名 说明
.destroy() 关闭窗口
.config() 将组件展现
1
2
# 将菜单容器one展现出来
t.config(menu=one)
1
2
3
# 关闭
def close():
t.destroy()

2. 总结

对Tkinter库的一些说明

  • 基础课程点到为止,学完了解即可,也无法真正开发软件(所需的知识点非常多的)
  • 后续主题课会通过各种应用软件的开发给有兴趣的同学选择性系统学习

六、做个小游戏.Pygame入门

1. Pygame初步

Pygame库是一款专门用于游戏开发的库,用这个库可以做游戏

  • 学习这款库,有以下注意点:
    • 由于是针对小学生5年级+,不按成人那种手册式教学
    • 也就是说:用到哪里,就只讲哪里,并且不涉及原理
    • 基础课程点到为止介绍Pygame,但主题课只会讲解Pygame zero
    • Pygame zero进行了简化封装,更利于小朋友学习研究
    • 官方文档翻译中文版:https://blog.csdn.net/Enderman_xiaohei/article/details/87708373

pygame库旗下的方法:

方法名 说明
.init() 初始化pygame模块及检查系统
.display 属性,控制窗口和屏幕的模块

.display属性旗下的方法:

方法名 说明
.set_mode() 设置窗体的大小,传递元祖参数,返回Surface模块
.set_caption() 属性,控制窗口和屏幕的模块
.flip() 刷新屏幕
.update() 刷新屏幕部分内容,在不传参时:同上

Surface模块旗下的方法:

方法名 说明
.fill() 设置背景颜色

2. 代码详解

窗口显示、背景色、标题等设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 引入库pygame,并设置别名pg
import pygame as pg

# 初始化
# 注意1:pgame库旗下的方法init()
# 注意2:它的作用是导入pygame各个模块以及检查系统等
pg.init()

# .display属性:控制窗口和屏幕的模块
# .display旗下set_mode()方法,
# 注意1:传递的参数是一个元祖数据,(长, 高)
# 注意2:赋值给screen,这个变量就可以控制窗体和屏幕
# .display旗下set_caption()方法,设置标题
screen = pg.display.set_mode((800, 600))
pg.display.set_caption("Pygame游戏")

# 背景色
bgcolor = "pink"


# 轮询
while True:
# screen旗下的fill()方法,设置背景色
screen.fill(bgcolor)
# pg.display旗下的flip()方法,刷新屏幕
pg.display.flip()

七、关掉它.event事件

1. 卸载操作quit()

初始化init()加载所需的各种模块,相对应的卸载就是quit()方法

1
2
# 卸载模块
pg.quit()

2. event事件入门

事件:即需要通过触发才能执行的程序,比如鼠标点击、键盘按键等触发操作

  • 事件如何触发执行?
    • 需要不断循环刷新检测,因为鼠标键盘触发是随机时间触发的
    • 事件类型也有不少,也需要每次把各种事件循环出来一一检测

pygame库旗下的属性(模块):

模块名 说明
.event 处理事件和事件列队的模块

.event事件模块下的方法:

方法名 说明
.get() 从事件列队里获取事件

.event.get()方法遍历返回值下的属性:

方法名 说明
.type 获取事件对应的值
1
2
3
4
# 遍历事件
for e in pg.event.get():
# 获取事件对应的值
print(e.type)
  • 通过检测关闭窗口对应的事件值,然后卸载pygame
1
2
3
4
5
6
# 遍历事件
for e in pg.event.get():
# 如果检测到事件值为256(即点击了关闭按钮)
if e.type == 256:
# 卸载pygame
pg.quit()
  • pygame也提供了常量也对应常用的值:pg.QUIT(这个会返回256)
1
2
# pg.QUIT系统常量,直接返回256
if e.type == pg.QUIT:
  • 关闭代码编写完毕后,发现会报一个错误:原因是卸载后不认识了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
isOpen = True
# 轮询
while isOpen:
screen.fill(bgcolor)
pg.display.flip()

# 遍历事件
for e in pg.event.get():
# 如果检测到事件值为256(即点击了关闭按钮)
# pg.QUIT系统常量,直接返回256
if e.type == pg.QUIT:
isOpen = False

# 卸载pygame
pg.quit()

八、移动的圆.绘制方法

1. 图形绘制

pygame也自带了各种绘图的方法,我们这里学习一下绘制一个圆以及矩形

pygame库旗下的属性(模块):

模块名 说明
.draw 绘图模块
.time 用于管理实践的模块

.draw模块下的方法:

方法名 说明
.rect() 绘制一个矩形,参数参考代码
.circle() 绘制一个圆,参数参考代码

.time模块下的方法:

方法名 说明
.Clock() 可以返回一个追踪时间的变量(对象)

.Clock()方法下的方法:

方法名 说明
.tick() 设置刷新频率

2. 代码详情

绘制一个静态的矩形,绘制一个可以移动的圆,并设置刷新频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 时间追踪
clock = pg.time.Clock()
# 小球默认位置
x, y = 390, 290

# 轮询
while isOpen:
# 频率
clock.tick(10)
# 矩形
pg.draw.rect(screen, ("blue"), (100, 100, 35, 35))
# 绘制圆
pg.draw.circle(screen, ("red"), (x, y), 20)
# 默认情况下向右移动
x += 20

九、键盘控制.事件监听

1. 键盘监听

本节课想要通过用户键盘的操作,来改变小球的移动方向

  • e.type可以获取到事件的类型:
    • 键盘按下时返回值是:768
    • 键盘弹起时返回值是:769
    • 可以直接判断:e.type == 768
    • 或者pygame提供了常量pg.KEYDOWN值为:768
1
2
# 判断是否键盘按下
elif e.type == pg.KEYDOWN:
  • 而对于上下左右的按键常量为:

    • 上:pg.K_UP
    • 下:pg.K_DOWN
    • 左:pg.K_LEFT
    • 右:pg.K_RIGHT
  • 但是,判断具体按键并不是e.type,通过手册查询到是e.key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 小球方向
dir = "right"

isOpen = True
# 轮询
while isOpen:
# 判断方向
if dir == "right":
x += 20
elif dir == "left":
x -= 20
elif dir == "up":
y -= 20
elif dir == "down":
y += 20

for e in pg.event.get():
if e.type == pg.QUIT:
isOpen = False
# 判断是否键盘按下
elif e.type == pg.KEYDOWN:
# 判断按下了左键盘
if e.key == pg.K_LEFT:
#print("左")
dir = "left"
elif e.key == pg.K_RIGHT:
#print("右")
dir = "right"
elif e.key == pg.K_UP:
#print("上")
dir = "up"
elif e.key == pg.K_DOWN:
#print("下")
dir = "down"

十、碰撞检测.坐标重叠

1. 坐标重叠

我们想让移动的球对方块进行碰撞,采用坐标重叠的方法设计

  • 如果和判断两个图形重叠:
    • 得到方块的坐标位置为:x=>100, y=>100
    • 加上方块本身的大小35,算出占用的区域:x(100~135), y(100~135)
    • 有些判断希望擦边一点点不算碰撞,那可以设计一个值让判断更加中心一点
1
2
3
4
5
6
7
8
9
10
11
12
13
# 方块默认位置
rx, ry = 100, 100

while isOpen:
# 方块占用的区域为:x(100~135),y(100~135)
pg.draw.rect(screen, ("blue"), (rx, ry, 35, 35))

# 判断小球是否碰撞方块
if rx != -9999 and ry != -9999:
if x > rx and x < rx + 35 and y > ry and y < ry + 35:
# 让方块飞到天际去
rx = -9999
ry = -9999
  • 用类似的方法判断边缘碰撞:
1
2
3
# 判断小球碰撞边界,边缘坐标+本身大小
if x < 0 + 20 or x > 800 - 20 or y < 0 + 20 or y > 600 - 20:
x, y = 390, 290

十一、计算得分.输入文本

1. 文字输入

pygame库提供了文字输入的模块方法font

pygame库旗下的属性(模块):

模块名 说明
.font 文字模块

.font模块下的方法:

方法名 说明
.Sysfont() 返回一个可以创建字体的变量(对象Font)
1
2
3
4
# 设置文本
# 注意1:参数1为字体名称,可以在C盘windows下Fonts查阅
# 注意2:参数2为大小20
font = pg.font.SysFont("simhei", 20)

.Sysfont()方法下方法:

方法名 说明
.render() 绘制文本

Surface模块下的方法:

方法名 说明
.blit() 将图像或文字图形绘制到窗体上
1
2
3
4
5
6
7
# 得分文本
# 注意1:参数1为文本,参数2为是否平滑抗锯齿
# 注意2:参数3为元祖模式颜色,参数4
# 注意3:blit方法将文本绘制到窗体上
screen.blit(font.render("得分:", True, ("black")), (720,10))
# 分数文本
screen.blit(font.render(str(score), True, ("black")), (780, 10))
  • 对数字进行变动:
1
2
3
4
5
6
7
8
9
10
# 得分
score = 0

# 判断小球是否碰撞方块
if rx != -9999 and ry != -9999:
score += 1

# 判断小球碰撞边界
if x < 0 + 20 or x > 800 - 20 or y < 0 + 20 or y > 600 - 20:
score = 0

十二、总裁思维.面向对象

1. 面向过程

之前学习的编程方式就是面向过程,即员工思维。

**提醒:面向对象难度较大,对于教培老师,当然要掌握;对于小学生,可选;**
  • 所谓员工思维:
    • 按部就班的完成每一个步骤:比如上班:
      • 1.起床 2.洗脸 3.坐公交 4.打卡进公司 5.工作 …等
    • 用程序代码理解:自上而下按顺序的一步步完成既定的代码程序;

2. 面向对象

面向对象就好比总裁思维,那总裁的思维是哪些?

  • 所谓总裁思维:
    • 按需分配让他人执行所要的步骤:比如上班:
      • 1.到公司 2.分配任务 3.完
    • 用程序代码理解:以调用的方式,给各个模块发送指令,并执行;

3. 属性和方法

我们之前已经零星的接触过属性和方法的区别

  • 属性和方法概念:

    • 方法其实就是函数,只不过有一个点语法:pg.init()
    • 方法的其实就是:对象.方法() ,一般用于执行某种行为动作
    • 而属性,就是不带括号的:e.type
    • 属性语法为:对象.属性,一般用于获取某种特性的值
    • 备注:我们之前点出万物的变量都可以理解为对象
  • 实例演练,小汽车对象:

    • 它有哪些属性呢?
      • 价格, 大小, 颜色…等等可以理解为属性
    • 它有哪些方法呢?
      • 行驶, 停靠…等等可以理解为方法
    • 获取特性的是属性,执行动作的是方法

十三、泰拉瑞亚.类与对象

1. 生成概念

有玩过泰拉瑞亚的世界生成形式的游戏吗?大概这个意思

游戏模型图

  • 理解上图:
    • 世界模型可以理解为基石(一切的基础),也可以理解为生产世界的工厂,也是后面“类”这个概念;
    • 生成世界123,即:世界工厂生产的行为,英文为new,新建,创建的意思;
    • 各种特性的副本,即:对象,对象是具有各种属性和方法的,比如:
      • 火山熔岩副本,具有热、灼烧的属性,需要有降温、灭火等方法
      • 寒冰洞窟副本,具有冷、冰冻的属性,需要有保暖、碎冰等方法
      • 沙漠戈壁副本,。。。。。
    • 也就是说,类可以包含全部的功能,而生成对象的时候呢,随机选择特性载入;
    • 下面就需要通过代码来实现这个类和对象。

2. 创建类和对象

类是一切的基石,而对象是类生成出来的一个副本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编写一个世界类
# 注意1:约定俗成,类名首字母大写
# 注意2:class和pass是关键字
# 注意3:两种写法 class World: 或 class World():
class World:
# 空类会报错,需要pass填充
pass


# 创建一个对象,即副本,直接函数形式调用即可
# 注意1:为方便后续使用,一般会将调用类()返回给一个变量
# 注意2:这个w就是这个对象
# 注意3:World()看上去是函数调用,但有自己的名称:实例化
w = World()
print(w)
print(type(w))

十四、载入特性.创建属性

1. 初始化函数

初始化函数:__ init __() 一般在定义类时定义,也可以称为初始化方法

  • 初始化函数作用:
    • 如果把类理解成函数,当实例化(创建对象)时,会自动执行初始化函数;
    • 初始化函数,也叫初始化方法,在别的语言称为构造方法,这里也可以;
    • 参数1是必填参数,固定关键字self,调用本类里的属性和方法用的;
    • 那么它的作用到底是什么呢?很简单:在创建对象时自动执行,初始化各项数据;
1
2
3
4
5
6
7
8
9
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
def __init__(self):
# 函数没内容,pass占位
pass

w = World()

1. 类的属性

类的属性,一般用于保证创建的对象具有相应的特性

  • 那么,如何创建三个不同特性的世界副本呢?通过初始化函数试试
1
2
3
4
5
6
7
8
9
10
11
12
13
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
# 注意2:self代表World本身
def __init__(self):
# 创建属性
self.name = "火山"
self.gender = "熔岩"


w1 = World()
print(w1.name+w1.gender,"的世界被创建!")
  • 以上的代码只能创建有一个特性的世界副本。如何创建不同的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 世界类
class World():
# 定义初始化函数
# 注意1:参数1固定为self,必传,关键字
# 注意2:self代表World本身的实例化对象
def __init__(self, name, gender):
# 创建属性
self.name = name
self.gender = gender

# 给初始化函数传参
# 相当于给生成的世界做自定义选项
# 或者添加了世界种子之类的东西
w1 = World("火山", "熔岩")
print(w1.name+w1.gender,"的世界被创建!")

w2 = World("寒冰", "洞窟")
print(w2.name+w2.gender,"的世界被创建!")
  • 结构分析:
    • 每个对象都是内存独立分配的空间;
    • 每个对象的属性,都是独立的,和其它对象属性毫无关系;

结构分析图


十五、固有特性.静态属性

1. 静态概念

所谓静态,无法实例化改变,数据定义后可以共享给所有对象使用

  • 所谓静态属性:
    • 静态属性,直接在类的顶部定义,无法通过对象改变的属性;
    • 静态属性可以用一个特殊的语法:类.静态属性,而无需实例化对象;
    • 什么样的环境需要静态:所有对象的某个属性特性都是同一个值,那就可以静态(共享);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 世界类
class World:
# 静态属性
# 注意1:不在初始化函数里,不需要self
# 注意2:不通过函数赋值,可直接赋值
age = 1000
def __init__(self, name, gender):
self.name = name
self.gender = gender


w1 = World("火山", "熔岩")
print(w1.age)

w2 = World("寒冰", "洞窟")
print(w2.age)

# 类.静态属性
print(World.age)

2. 动态属性

动态属性,就很好理解了。直接在实例化对象创建的属性

1
2
3
4
5
6
w1 = World("火山", "熔岩")
print(w1.age)
# 实例化对象直接创建的属性
# 类似普通变量,在类中不存在,本身也是实例属性
w1.info = "我是动态属性"
print(w1.info)
  • 名词解释:
    • 创建属性:一般是在类里声明的属性,比如 self.name 这种;
    • 实例属性:实例化后对象调用的属性,比如 w1.name 这种;
    • 静态属性:可以用类直接调用,也可以对象调用的属性:World.age 这种;
    • 动态属性:类里面没有定义的属性,在实例化后对象直接声明的属性:w1.info 这种;

十六、行动激活.创建方法

1. 实例方法

实例方法(函数),也是普通类里的方法,用于执行实例化对象后的某种行为

1
2
3
4
5
6
7
8
9
10
# 方法(函数),self是函数必填的
def run(self):
return "遇到火山熔岩副本,我要使用降温灭火装备!"

w1 = World("火山", "熔岩")
# 实例方法
print(w1.run())

w2 = World("寒冰", "洞窟")
print(w2.run())

如果要实现针对不同对象(副本),采取对应的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 世界类
class World:
age = 1000
def __init__(self, name, gender, a, b):
self.name = name
self.gender = gender
self.a = a
self.b = b

# 类方法(函数),self是函数必填的
# 注意:字符串太长,可以在+号后面加一个\表示换行
def run(self):
return "遇到" + self.name + self.gender +\
"副本,我要使用" + self.a + self.b + "装备!"


w1 = World("火山", "熔岩", "降温", "灭火")
print(w1.run())

w2 = World("寒冰", "洞窟", "保暖", "碎冰")
print(w2.run())

2. 静态方法

和静态属性一样,静态方法是每一个对象(副本)都可以共享使用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 静态方法
# 在普通方法上方设置@staticmethod就变成静态方法
# 这种@的语法叫做装饰器语法
@staticmethod
def over():
return "Game Over!"

w1 = World("火山", "熔岩", "降温", "灭火")
print(w1.run())
print(w1.over())

w2 = World("寒冰", "洞窟", "保暖", "碎冰")
print(w2.run())
print(w2.over())

# 静态方法可以直接通过类名.方法()
print(World.over())

3. 动态方法

和动态属性一样,在类外声明的普通函数,并用实例化对象调用的方法

1
2
3
4
5
6
7
# 普通函数
def eat():
return "吃饭"

# 将函数名赋值给动态属性
w2.eat = eat
print(w2.eat())

4. 类方法

使用装饰器@classmethod声明的方法

  • 类方法:
    • 和静态方法一样,支持 类.方法() 的调用方式;
    • 静态方法只处理数据,无法获取本类的数据,比如无法使用self;
    • 类方法必须传递 cls 参数,通过这个 cls 来调用本类的静态属性等;
1
2
3
4
5
6
7
# 类方法
@classmethod
def info(cls):
return cls.age

# 类方法
print(World.info())

十七、私有属性.封装

1. 封装性

面向对象有三大特性:封装、继承和多态,那什么是封装?

  • 封装的解释:
    • 对属性进行私有化操作,这种行为就是封装;
    • 为何要封装,防止这些属性被污染;
    • 怎么理解污染?想一下电脑机箱,为何要把主板、内存、显卡封在壳子里,只留个插孔;
  • 我们先看下公有属性,如何赋值和取值的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 世界类
class World:

def __init__(self, name, gender):
self.name = name
self.gender = gender


w1 = World("火山", "熔岩")
# 共有属性特性
# 注意1:可以直接赋值
# 注意2:可以直接输出
w1.name = "沙漠"
print(w1.name)
  • 将公有属性,转换为私有属性后:
1
2
3
4
5
6
7
8
def __init__(self, name, gender):
# 私有属性,只要在前面加上两个下划线即可
self.__name = name
self.__gender = gender

w1 = World("火山", "熔岩")
# 私有属性直接获取,报错
print(w1.__name)
  • 封装私有化后,提供一个可以取值的入口方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 世界类
class World:

def __init__(self, name, gender):
# 私有属性,只要在前面加上两个下划线即可
self.__name = name
self.__gender = gender

def getName(self):
return self.__name

w1 = World("火山", "熔岩")
# 私有变量被封装了,但可以设置一个方法作为调用入口
# 这个调用入口就好比机箱提供的插口区
print(w1.getName())
  • 封装私有化赋值,有时不是在实例化后赋值的,可以再建立一个赋值入口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 世界类
class World():

def __init__(self, name = "", gender = ""):
# 私有属性,只要在前面加上两个下划线即可
self.__name = name
self.__gender = gender

def setName(self, name):
self.__name = name

def getName(self):
return self.__name

w1 = World()
# 私有化后的属性赋值
w1.setName("沙漠")
# 私有变量被封装了,但可以设置一个方法作为调用入口
# 这个调用入口就好比机箱提供的插口区
print(w1.getName())

2. 私有方法

私有方法和私有属性概念一样

1
2
3
4
5
6
# 我是私有方法,外部无法调用
def __run(self):
return "运行。。。"

# 无法执行私有方法
print(w1.__run())
  • 私有方法也需要一个入口,来执行
1
2
3
4
5
6
7
8
9
10
# 我是私有方法,外部无法调用
def __run(self):
return "运行。。。"

# 对外公开的方法
def go(self):
return self.__run()

# 启动
print(w1.go())

十八、装饰器.@property

1. @property

getName()setName() 是一种比较通用的封装属性的手段,但还是有点麻烦

  • 先补遗下容易出错的点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 世界类
class World:

def __init__(self, name = "火山", gender = "熔岩"):
self.__name = name
self.__gender = gender

# 对外获取私有属性__name
def getName(self):
return self.__name

w1 = World()
# 私有属性无法直接赋值取值,但操作语法并不报错
# 这里的__name已经是动态属性,和类里面的私有属性无关
w1.__name = "寒冰"
# 这里输出的是动态属性 寒冰
print(w1.__name)
# 之类获取到私有属性 火山
print(w1.getName())
  • 问题分析:
    • 私有属性赋值有两种,一种就是 初始化函数传参,第二种就是 setName() 方法;
    • 我们希望属性在对象这里,直接用属性赋值取值,可读性更好一些;
    • 所以,需要使用装饰器@property来完成这种功能,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 世界类
class World:

def __init__(self, name = "火山", gender = "熔岩"):
self.__name = name
self.__gender = gender


# 装饰器
@property
def name(self):
return self.__name

# 装饰器
@name.setter
def name(self, name):
self.__name = name

def getName(self):
return self.__name


w1 = World()
w1.name = "寒冰"
# 下面两种获取均为 寒冰
print(w1.name)
print(w1.getName())

十九、第二大特性.继承

1. 继承概念

父亲有100w,儿子继承了父亲的100w,那么儿子就有了100w,这就是继承

  • 继承扩展理解:
    • 世界类或世界工厂,在生成火山、寒冰、沙漠副本时,逻辑都在本类或本工厂里;
    • 当这些副本需求内容过多,或逻辑逐步复杂时,世界工厂内的代码将变得冗余难以理解;
    • 尤其是当需要通过实例化调用时传递不同参数时,也会变成杂乱可读性差;
    • 那么,我们将这个工厂开始拆分,分为三个工厂:火山工厂、寒冰工厂、沙漠工厂;

继承流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 火山类
class Fire:
name = "火山"
gender = "熔岩"

def fight(self):
print("在"+ self.name + self.gender +"里战斗!")

# 寒冰类
class Cold:
name = "寒冰"
gender = "洞窟"

def fight(self):
print("在"+ self.name + self.gender +"里战斗!")

# 火山对象
f = Fire()
f.fight()

# 寒冰对象
c = Cold()
c.fight()
  • 结构分析:
    • 模型拆分后,逻辑上清晰很多,不用在一个类中设计太多不同副本的功能;
    • 初始化函数的传递参数上,直接被静态属性取代了;
    • 但暴露出一个新的问题,每个副本都有相同的行为和属性怎么办?
      • 比如,这里 fight() 战斗方法,由于拆分后,又冗余了;

二十、复制共性.改写特性

1. 子类继承

要解决火山、寒冰等工厂冗余问题,我们还得请回世界工厂来整合共性

子类继承流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 世界类(父类)
class World:
# 将相同的逻辑放在父类继承
age = 1000
def fight(self):
print("在"+ self.name + self.gender +"里战斗!")

# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
class Fire(World):
name = "火山"
gender = "熔岩"


# 寒冰类(子类)
class Cold(World):
name = "寒冰"
gender = "洞窟"


# 火山对象
f = Fire()
print(f.age)
f.fight()

# 寒冰对象
c = Cold()
c.fight()

2. 改写特性

有时,我们需要修改父类继承下来的属性和方法,以及初始化函数

  • 初始化函数:
    • 上面为了方便理解,均用了静态属性;
    • 本次我们尝试用初始化函数的声明的属性试一试;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 世界类(父类)
class World:
# 将相同的逻辑放在父类继承
age = 1000

# 初始化函数
def __init__(self, name, gender):
self.name = name
self.gender = gender


def fight(self):
print("在"+ self.name + self.gender +"里战斗!")

# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
class Fire(World):
pass


# 寒冰类(子类)
class Cold(World):
pass


# 火山对象
f = Fire("火山", "熔岩")
print(f.age)
f.fight()

# 寒冰对象
c = Cold("寒冰", "洞穴")
c.fight()

  • 如果取消掉实例化的传递参数,开始改写属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 火山类(子类)
# 注意1:子类继承父类,只需要在括号里传入父类名
class Fire(World):
# 重写属性
def __init__(self):
# 子类调用父类的初始化函数,并传递参数
World.__init__(self, "火山", "熔岩")


# 寒冰类(子类)
class Cold(World):
# 重写属性
def __init__(self):
World.__init__(self, "寒冰", "洞穴")


# 火山对象
f = Fire()
print(f.age)
f.fight()

# 寒冰对象
c = Cold()
c.fight()
  • 那么,如何改写方法呢?
1
2
3
4
5
# 重写方法
def fight(self):
#调用父类
World.fight(self)
print(123)

二十一、继承中的抽象化

1. 抽象化

对于父类,我们有几个问题有待解决,比如父类要不要封装等

  • 对于继承的思考:

    • 首先,父类能不能实例化,答案:现在能,但我们希望不能;
    • 因为,我们只是希望使用子类创建对象副本,父类只是提供规范;
    • 或者,用好理解的语言表述:总裁只是下达命令,真正执行的是员工;
    • 员工按照命令去执行即可,但员工自然也有自己独有的特性罢了;
    • 所以,在父类中,有些属性和方法,我们只希望提供规范让子类实现:
      • 而这种属性和方法称为:抽象属性抽象方法
  • 创建一个抽象的世界类,防止用户实例化:

1
2
3
4
5
6
7
8
9
10
11
12
# 引入abc模块,实现抽象化
import abc

# 世界类(抽象父类)
class World(metaclass=abc.ABCMeta):

# 抽象方法
@abc.abstractmethod
def fight(self):pass

# 无法实例化
w1 = World()
  • 创建一个子类去继承抽象父类,实例化后强制要求实现抽象方法:
1
2
3
4
5
6
7
# 火山类(子类)
class Fire(World):
pass

# 实例化后,报错,提示必须实现父类的抽象方法
# 也就是,总裁的命令必须完成
f = Fire()
1
2
3
4
5
6
7
8
# 火山类(子类)
class Fire(World):

def fight(self):
return "战斗!"

f = Fire()
print(f.fight())
  • 最后一个抽象属性定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 引入abc模块,实现抽象化
import abc

# 世界类(抽象父类)
class World(metaclass=abc.ABCMeta):

#抽象属性
@property
@abc.abstractmethod
def name(self):pass

# 抽象方法
@abc.abstractmethod
def fight(self):pass


# 火山类(子类)
class Fire(World):

@property
def name(self):
return self.__name

@name.setter
def name(self, name):
self.__name = name

def fight(self):
return "在" + self.__name + "区域战斗!"

# 抽象属性也必须强制定义
f = Fire()
f.name = "火山"
print(f.fight())
  • 为了更加直观,改写一下import引入方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 我们只用abc模块中的两个方法,这样可以去掉abc.的前缀
from abc import ABCMeta, abstractmethod

# 世界类(抽象父类)
class World(metaclass=ABCMeta):

#抽象属性
@property
@abstractmethod
def name(self):pass

# 抽象方法
@abstractmethod
def fight(self):pass

二十二、第三大特性.多态

1. 多态的概念

多态:即多种形态,执行同一种命令根据场景不同而执行不同的行为特征

  • 多态理解:

    • go()这个方法,“走”的意思,但环境不同意思不同:
      • 在餐厅前go(),表示吃饭;
      • 在校门前go(),表示上课;
      • 在网吧前go(),表示打游戏;
    • 那么方法,肯定是固定的,那么就必须实现子类重写父类;
    • 而调用者也是固定的,否则叫什么多态?
  • 第一种做法,通过固有函数作为执行者,传入不同环境实现不同行为的多态;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from abc import ABCMeta, abstractmethod

# 世界类(抽象父类)
class World(metaclass=ABCMeta):

# 抽象方法
@abstractmethod
def fight(self):pass

# 火山类(子类)
class Fire(World):

def fight(self):
print("与炎魔战斗!")


# 寒冰类(子类)
class Cold(World):

def fight(self):
print("与霜魔战斗!")


# 战斗
def fight(obj):
obj.fight()

# 函数调用,传入不同的子类对象实现执行不同行为
fight(Fire())
fight(Cold())

'''
f = Fire()
c = Cold()

fight(f)
fight(c)
'''
  • 第二种做法,通过普通父类的方法去执行子类的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 世界类(父类)
class World:

def fight(self, obj):
obj.fight()

# 火山类(子类)
class Fire(World):

def fight(self):
print("与炎魔战斗!")


# 寒冰类(子类)
class Cold(World):

def fight(self):
print("与霜魔战斗!")


w = World()
# 调用父类的方法执行子类的方法
w.fight(Fire())
w.fight(Cold())
  • 好处:当有非常多的副本,但他们通用的功能是战斗,而战斗的细节又不同,就避免大量的if等判断操作。

二十三、面向对象工具箱

1. 工具箱合集

面向对象中,有很多工具方法来更方便的查询或构建我们的代码

  • 一般带有前后两个下划线的方法,称为魔术方法:
    • __ init __ () :初始化函数,或称为构造方法,当实例化时
    • __ str __ () :打印对象时,替代原本的描述信息
    • __ del __() :析构函数,和 初始化 正好相反,当删除实例化对象时触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 世界类
class World:
def __init__(self, x, y):
self.x = x
self.y = y

# __str__() 替代本来的对象描述
def __str__(self):
return "这是一个世界类" + str(self.x)

# 析构函数
def __del__(self):
print("删除对象时,我被触发" + str(self.y))


w = World(10, 20)
print(w)
del w

2. super()函数

重写父类方法时,我们还需要得到父类方法最终的结果,使用super()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 世界类(父类)
class World:
def run(self):
return "World!"

# 火类
class Fire(World):
def run(self):
# 通过 super()函数调用父类方法
return super().run() + "Fire!"


f = Fire()
print(f.run())

二十四、实训.塞尔达传说①

1. 需求分析

从本节课开始,我们用面向对象实战一款经典的开放世界文字版:塞尔达传说

  • 具体要求如下:
    • 游戏开始,醒来,系统让你输入用户名;
    • 当然,不管你输入什么(除了林克),系统都会纠正你叫“林克”;输入正确,不会纠正;
    • 你走出洞穴,生命体只有1颗星,遇到烤火老爷爷,要给你两个烤苹果;
    • 你接受吃了烤苹果,则变成3颗星,你走入寒冰区,老爷爷要给你防寒服,你可以选择;
    • 防寒服可以增加你在本区域的防御值,你原本的防御值为1,遇到寒冰怪,血量为1,它先手;
    • 对战胜利或失败,失败退出游戏;胜利后,进入火山区,矿工要给你防火服,同上;
    • 然后,走入城堡拯救公主,公主介绍Boss,攻击力10000,防御力4000,生命值5000,它先手;
    • 她说可以给你一把攻击力8999的光之弓,还加送一张先手祝福之卷轴;胜利或失败;

2. 初始化数据

直接面向对象,对于新手来说不太容易,先面向过程开始,从底层打工开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 游戏开始
print("Loading...")
print("Loading...")
print("Loading...")
print("游戏开始....")
print("林克,林克,林克,醒一醒....")

# 初始化名字
name = input("请输入你的名字:")
if name != "林克":
print("你忘了吗?你叫林克!")
name = "林克"

# 初始化血量
life = 1
print("\n走出洞穴,遇到一个老爷爷,他要送你两个烤苹果!")
flag = int(input("是否接受?接受输入->1,不接受输入->0:"))
if flag == 1:
life += 2
print("你的生命值+2")

# 初始化防御力
defence = 1
# 初始化攻击力
attack = 1

# 统计目前状态
print("\n目前生命值:%d,防御力:%d,攻击力:%d"%(life, defence, attack))

二十五、实训.塞尔达传说②

1. 对战系统

和怪物攻击,默认怪先手,通过数值加减来判断胜利或失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 走入寒冰区
print("\n走入寒冰区,老爷爷要送你防寒服!")
# 区域防御力
defence_cold = 0
flag = int(input("是否接受?接受输入->1,不接受输入->0:"))
if flag == 1:
defence_cold = defence + 1
print("你的寒冰区防御力+1")

# 遇到第一个怪
print("\n你遇到了寒冰怪,开始挑战!")
enemy1_atk = 2
enemy1_life = 1

# 互相攻击,直到一方倒下
while True:
# 怪物先手,开始攻击,攻击算法:怪物攻击力 - 玩家防御力
# 玩家的生命值 = 玩家生命总量 - (怪物攻击力 - 玩家区域防御力)
# 吃满buff第一轮:3 - (2 - 2) = 3
# 不吃buff第一轮:1 - (2 - 1) = 0 死了
life = life - (enemy1_atk - defence_cold)
# 判断玩家生命
if life <= 0:
print("挑战失败!")
print("Game over!")
break

# 玩家开始攻击
enemy1_life = enemy1_life - attack
# 判断怪物的生命
if enemy1_life <= 0:
print("你胜利了!目前生命值为:%d"%life)
break

# 胜利后继续走入火山区
print("\n胜利后继续走入火山区")

# 统计目前状态
print("\n目前生命值:%d,防御力:%d,攻击力:%d"%(life, defence, attack))

2. 退出机制

胜利后可继续往下执行,但失败后,要退出整个系统,需要函数处理

1
2
3
4
5
6
7
8
# 主要函数
def main():
# 游戏开始
...


# 入口
main()

二十六、实训.塞尔达传说③

1. 重复对战

直接继续复制寒冰区的代码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 获取防火服
print("\n火山区矿工告诉你,里面很热,需要防火服")
# 区域防御力
defence_fire = 0
flag = int(input("是否接受?接受输入->1,不接受输入->0:"))
if flag == 1:
defence_fire = defence + 1
print("你的火山区防御力+1")

# 遇到第一个怪
print("\n你遇到了熔岩怪,进入战斗模式!")
enemy2_atk = 2
enemy2_life = 1

# 互相攻击,直到一方倒下
while True:
life = life - (enemy2_atk - defence_fire)
# 判断玩家生命
if life <= 0:
print("挑战失败!")
print("Game over!")
return

# 玩家开始攻击
enemy2_life = enemy2_life - attack
# 判断怪物的生命
if enemy2_life <= 0:
print("你胜利了!目前生命值为:%d"%life)
break

# 胜利后继续走入城堡
print("\n胜利后走入城堡迎接公主")

# 统计目前状态
print("\n目前生命值:%d,防御力:%d,攻击力:%d"%(life, defence, attack))

2. Boss登场

介绍Boss和初始化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 公主介绍Boss
print("\n公主:我的骑士啊!恶魔的攻击力为10000,防御力4000,生命值5000,而且先手!")
print("我这里有一把攻击力为8999光之弓,还有一张先手祝福之卷轴!")
flag1 = int(input("是否接受光之弓?接受输入->1,不接受输入->0:"))
flag2 = int(input("是否接受先手祝福之卷轴?接受输入->1,不接受输入->0:"))

# 恶魔数据
enemy3_life = 5000
enemy3_defence = 4000
enemy3_atk = 10000

# 更新攻击力
if flag1 == 1:
attack += 8999

二十七、实训.塞尔达传说④

1. Boss对战

Boss对战和小怪基本一致,主要是否接受先手Buff这个条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 互相攻击,直到一方倒下
while True:
# 判断先手
if flag2 == 1:
# 玩家开始攻击
enemy3_life = enemy3_life + enemy3_defence - attack
# 取消先手buff
flag2 = 0
# 判断怪物的生命
if enemy3_life <= 0:
print("\n你战胜了恶魔,拯救了公主,游戏通关!")
return


life = life - (enemy3_atk - defence)
# 判断玩家生命
if life <= 0:
print("你被恶魔打败啦,公主也没救回来!")
print("Game over!")
return

# 玩家开始攻击
enemy3_life = enemy3_life + enemy3_defence - attack
# 判断怪物的生命
if enemy3_life <= 0:
print("\n你战胜了恶魔,拯救了公主,游戏通关!")
return
  • 员工的编码思维已经做完,后面要改成面向对象的方式。

二十八、实训.塞尔达传说⑤

1. 旁白类

文字游戏,太多旁白,我们先从这里入手进行整理吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 旁白类
class Aside:

# 开场语音
@staticmethod
def start():
print("Loading...")
print("Loading...")
print("Loading...")
print("游戏开始....")
print("林克,林克,林克,醒一醒....")

# 介绍Boss
@staticmethod
def boss():
print("\n公主:我的骑士啊!恶魔的攻击力为10000,防御力4000,生命值5000,而且先手!")
print("我这里有一把攻击力为8999光之弓,还有一张先手祝福之卷轴!")


# 游戏胜利
@staticmethod
def win():
print("你战胜了恶魔,拯救了公主,游戏通关!")

# 败于谁下
@staticmethod
def who(name):
print("你被%s打败啦,公主也没救回来!"%name)

# 游戏失败
@staticmethod
def over():
print("Game over!")

# 战斗旁白
@staticmethod
def fight(name):
print("\n你遇到了%s,进入战斗模式!"%name)

# 单局胜利
@staticmethod
def single(name):
print("恭喜你战胜了%s!"%name)

# 人物状态
@staticmethod
def status(life, defence, attack):
print("目前生命值:%d,防御力:%d,攻击力:%d"%(life, defence, attack))

# 输入提示
@staticmethod
def input(info):
return input("是否接受%s?接受输入->1,不接受输入->0:"%info)
  • 然后,在主程序用静态方法来调用这些归类好的方法即可:
1
2
3
4
5
6
7
8
# 开场
Aside.start()

#...
Aside.who(name)
Aside.over()

#...

二十九、实训.塞尔达传说⑥

1. 人物类

将玩家的数据信息,单独通过一个类来管理调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 人物类
class Person:
# 初始化
def __init__(self, name, life, defence, attack):
self.__name = name
self.__life = life
self.__defence = defence
self.__attack = attack
self.__defence_cold = 0
self.__defence_fire = 0

@property
def name(self):
return self.__name

@name.setter
def name(self, name):
self.__name = name

@property
def life(self):
return self.__life

@life.setter
def life(self, life):
self.__life = life

@property
def defence(self):
return self.__defence

@defence.setter
def defence(self, defence):
self.__defence = defence

@property
def attack(self):
return self.__attack

@attack.setter
def attack(self, attack):
self.__attack = attack

@property
def defence_cold(self):
return self.__defence_cold

@defence_cold.setter
def defence_cold(self, defence_cold):
self.__defence_cold = defence_cold

@property
def defence_fire(self):
return self.__defence_fire

@defence_cold.setter
def defence_fire(self, defence_fire):
self.__defence_fire = defence_fire
  • 然后通过Aside开场时,生成人物对象,在主程序操作人物数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 开场语音
@staticmethod
def start():
print("Loading...")
print("Loading...")
print("Loading...")
print("游戏开始....")
print("林克,林克,林克,醒一醒....")

# 初始化名字
name = input("请输入你的名字:")
if name != "林克":
print("你忘了吗?你叫林克!")
name = "林克"

# 实例化人物数据
p = Person(name, 1, 1, 1)
return p
  • 主程序,涉及到人物的六个属性时,全部加上对象.属性的格式:
1
2
3
4
5
# 攻击
p.life = p.life - (enemy1_atk - p.defence_cold)

# 统计目前状态
Aside.status(p.life, p.defence, p.attack)

三十、实训.塞尔达传说⑦

1. 怪物类

将怪物的数据信息,单独通过一个类来管理调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 怪物类
class Enemy:
# 初始化
def __init__(self, life, attack, defence = 0):
self.__life = life
self.__attack = attack
self.__defence = defence

@property
def life(self):
return self.__life

@life.setter
def life(self, life):
self.__life = life

@property
def attack(self):
return self.__attack

@attack.setter
def attack(self, attack):
self.__attack = attack

@property
def defence(self):
return self.__defence

@defence.setter
def defence(self, defence):
self.__defence = defence
  • 主程序,涉及到怪物的三个属性时,全部加上对象.属性的格式:
1
2
3
4
5
# 创建寒冰怪,实例化
en1 = Enemy(1, 2)

# 怪物属性调用
p.life = p.life - (en1.attack - p.defence_cold)

三十一、实训.塞尔达传说⑧

1. 战斗类

将小怪和boss战的逻辑,存储到一个单独的类进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 战斗类
class Fight:

# 和普通怪战斗
@staticmethod
def general(p, en, Aside, defence):
# 互相攻击,直到一方倒下
while True:
# 怪物先手:攻击:怪物攻击力 - 玩家防御力
# 玩家生命值 = 玩家生命总量 - (怪物攻击 - 玩家区域防御力)
# 吃满buff第一轮:3 - (2 - 2) = 3
# 不吃buff第一轮:1 - (2 - 1) = 0 死亡
p.life = p.life - (en.attack - defence)
# 判断玩家生命
if p.life <= 0:
Aside.who(en.name)
Aside.over()
return False

# 玩家开始攻击
en.life = en.life - p.attack
# 判断怪物的生命
if en.life <= 0:
Aside.single(en.name)
return True


# 和Boss战斗
@staticmethod
def boss(p, en, Aside, flag2):
# 互相攻击
while True:
# 判断先手
if flag2 == 1:
# 玩家攻击
en.life = en.life + en.defence - p.attack
# 取消先手
flag2 = 0
# 判断怪物生命
if en.life <= 0:
Aside.win()
return False

# 正常手(没有buff,我就是先手,有buff,我就是后手)
p.life = p.life - (en.attack - p.defence)
# 判断玩家生命
if p.life <= 0:
Aside.who(en.name)
Aside.over()
return False

# 玩家攻击
en.life = en.life + en.defence - p.attack
# 判断怪物生命
if en.life <= 0:
Aside.win()
return True
  • 主程序调用的时候,直接传入必要的参数即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建熔岩怪
en2 = Enemy("熔岩怪", 1, 2)
# 遇到第二个怪,熔岩怪
Aside.fight(en2.name)

# 战斗
if Fight.general(p, en2, Aside, p.defence) == False: return

# 创建Boss
en3 = Enemy("恶魔", 5000, 10000, 4000)

# 战斗
if Fight.boss(p, en3, Aside, flag2) == False: return

三十二、实训.塞尔达传说⑨

1. 场景类

目前四个场景都在主程序中,分成独立类,方便以后扩展升级

  • 在main.py中,执行主函数,已经只是调用关系,根据场景一一调用,非常清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 引入类库
from Aside2 import *
from Original2 import *
from Cold2 import *
from Fire2 import *
from Castle2 import *

def main():
# 开场语音
p = Aside.start()

# 执行场景一
o = Original()
o.exec(p, Aside)

# 执行场景二
c = Cold()
c.exec(p, Aside)

# 执行场景三
f = Fire()
f.exec(p, Aside)

# 执行场景四
cc = Castle()
cc.exec(p, Aside)
  • 创建世界的抽象父类,在基础知识部分学习过,其它四个场景,则继承它
1
2
3
4
5
6
7
8
9
from abc import ABCMeta, abstractmethod
from Enemy2 import *
from Fight2 import *
# 世界类(抽象父类)
class World(metaclass=ABCMeta):

# 抽象方法,执行场景
@abstractmethod
def exec():pass
  • 子类的构建如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from World2 import *
# 初始场景(子类)
class Original(World):

# 执行
def exec(self, p, Aside):
print("\n走出洞穴,遇到一个老爷爷,他要送你两个烤苹果!")
flag = int(Aside.input("烤苹果"))
if flag == 1:
p.life += 2
print("你的生命值+2")

# 统计目前状态
Aside.status(p.life, p.defence, p.attack)
  • 其余的三个子类,一模一样,不再贴代码。
  • 这么做的好处显而易见,扩展场景,只需要在这个类中设计即可

三十三、实训.塞尔达传说⑩

1. 启动主程序

观察目前结构,调整重复代码,并实现场景的多态调用,实现总裁式一行启动模式

  • 在World类中,增加一个统计数据的方法,来避免每个子类重复操作
1
2
3
4
# 统计数据
def stats(self, p):
# 统计目前状态
Aside.status(p.life, p.defence, p.attack)
  • 子类调用
1
2
# 统计目前状态
super().stats(p)
  • Aside类,直接通过World引入,避免前台传递Aside类

  • 在World类中,创建一个静态方法,实现多态的调用效果

  • 由于return 被封装到子类的方法里了,无法实现退出函数,需要再传递回来

1
2
3
4
# 静态方法
@staticmethod
def run(obj, p):
return obj.exec(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def main():
# 开场语音
p = Aside.start()

# 执行场景一(初始区)
World.run(Original(), p)

# 执行场景二(寒冰区)
if World.run(Cold(), p) == False: return

# 执行场景三(火山区)
if World.run(Fire(), p) == False: return

# 执行场景四(城堡区)
if World.run(Castle(), p) == False: return
1
2
# 战斗
if Fight.general(p, en1, Aside, p.defence_cold) == False: return False
1
2
3
4
5
# 引入game
import game2 as g

# 塞尔达,启动
g.main()