“笨办法”学Python 3基础篇系列文章
“笨办法”学Python 3基础篇 第一部分-打印与输入
“笨办法”学Python 3基础篇 第二部分-文件操作
“笨办法”学Python 3基础篇 第三部分-函数
“笨办法”学Python 3基础篇 第四部分-数据容器与程序结构
“笨办法”学Python 3基础篇 第五部分-面向对象的类
“笨办法”学Python 3基础篇 第六部分-项目骨架与自动测试
“笨办法”学Python 3基础篇 第七部分-搭建简易的网站
搭建简易的网站
- “笨办法”学Python 3基础篇系列文章
- 前言
- 7.1 Web框架-flask安装和项目创建
- 7.2 利用面向对象方法设计三体游戏
- 7.3 编写game.py的自动测试脚本
- 7.4 编写HTML模板
- 7.5 创建三体web游戏引擎
- 7.6 游戏运行效果
- 结语
前言
终于来到了本系列的最后一篇了,有点小兴奋。虽然中间间隔了很多时间,但每一次码字都让我懂得了坚持和总结带来的好处,都能够再一次地对原来储存在脑海中某个角落的知识进行重构,变成自己知识大厦中的地基,脑子也在每次总结中进行了一次精神地“按摩”。
在本文中,我将用面向对象的编程方法编写一个对话类的三体小游戏(章北海作为主角),并通过flask创建简单的游戏网站(目前只学到这)。
7.1 Web框架-flask安装和项目创建
要使用网站服务,需要安装一个“Web”框架,减少开发负担。在书中,主要介绍了flask框架的应用。在激活虚拟环境后,通过
pip install flask
实现安装,安装过程如下图:
flask安装完成后,需要创建一个threebody项目,在PowerShell中输入如下命令:
cd projects
mkdir threebody
cd threebody
mkdir bin
mkdir threebody
mkdir tests
mkdir docs
mkdir templates
new-item -type file .\threebody\__init__.py
new-item -type file .\tests\__init__.py
与之前项目创建不同的是,新建了一个名为“templates”的目录,用于存放网站的html模板。
7.2 利用面向对象方法设计三体游戏
在threebody\threebody目录下新建一个game.py作为三体游戏的主程序。整个三体游戏的设计思路是通过场景切换进行的,游戏者通过做出选择进入不同剧情,类似《秋之回忆》之类的游戏。程序的核心为类Scene的设计,代码具体为:
class Scene:
"""基本场景类"""
def __init__(self, name, description):
self.name = name
self.description = description
self.paths = {}
def enter(self, direction):
return self.paths.get(direction, None)
def add_paths(self, paths):
self.paths.update(paths)
每个场景作为对象对类Scene进行初始化。初始化时渲染场景的名字和剧情描述,并初始化决定剧情走向的字典paths。通过enter(direction)函数返回direction指定的场景对象,通过add_paths(paths)函数来更新剧情走向字典paths,从而建立不同场景对象的剧情联系。关于剧情的走向脉络如下图所示:
游戏剧情从自然选择号开始,到太空陵墓结束。中间每个场景如果选择错误,就会进入死亡场景(根据剧情不同,死亡场景还需要细分)。在死亡场景中可以通过选择play again重玩游戏。这一剧情流向通过Scene中的add_paths实现。具体代码如下:
natureSelectionFleet.add_paths({
'1': death_1_1,
'2': death_1_2,
'3': death_1_3,
'4': escape,
'*': natureSelectionFleet
})
escape.add_paths({
'1': death_2,
'2': death_2,
'3': death_2,
'4': control_terminal,
'*': escape
})
control_terminal.add_paths({
'1': extrasolar,
'2': death_3,
'3': death_3,
'4': death_3,
'*': control_terminal
})
extrasolar.add_paths({
'1': fleet_earth,
'2': death_2,
'3': death_4,
'*': extrasolar
})
fleet_earth.add_paths({
'1': death_2,
'2': ultimate_rule_fleet,
'3': ultimate_rule_fleet,
'*': fleet_earth
})
ultimate_rule_fleet.add_paths({
'1': blue_space_fleet,
'2': death_6,
'3': death_5,
'4': death_2,
'*': ultimate_rule_fleet
})
blue_space_fleet.add_paths({
'1': last_episode,
'2': death_6,
'*': blue_space_fleet
})
这里的*代表玩家的无效输入,无效输入时,剧情场景不切换,停留在原场景中。剩下的工作是设计不同的场景对象,具体的代码为:
#自然选择号死亡场景
death1 = """
自然选择号在与三体文明'水滴'探测器的激战中被摧毁......
"""
death_1_1 = Scene("游戏失败",
f"""
原来你也接收了思想钢印。
警卫员!
马上逮捕章北海!
{death1}
""")
death_1_2 = Scene("游戏失败",
f"""
章北海,你这个懦夫!
你的内心充分暴露出来了,表面是个人类必胜论者,
内心确是一个失败论者。
警卫员!
马上逮捕章北海!
{death1}
""")
death_1_3 = Scene("游戏失败",
f"""
你是云天明?
警卫员!
马上通知地球舰队!
{death1}
""")
death_2 = Scene("游戏失败", death1)
death_3 = Scene("游戏失败",
f"""
飞船控制失败!
{death1}
"""
)
death_4 = Scene("游戏失败",
f"""
东方延绪:
章北海, 我代表星舰地球逮捕你。
全员准备,返航迎击三体人的探测器。
{death1}
""")
death_5 = Scene("游戏失败",
f"""
在更改航线后,遭遇了三体文明的舰队,被击毁...
""")
death_6 = Scene("游戏失败",
f"""
遭遇了黑暗森林法则,被星舰地球中的其它战舰次声波核弹攻击,
全员阵亡!
""")
#自然选择号
natureSelectionFleet = Scene("自然选择号",
"""
东方延绪:
章北海执行舰长,我是自然选择号的舰长东方延绪。欢迎您。
现在我把自然选择号的指挥权交给您。飞船有四种前进模式,
当进入前进4模式时,船员必须进入睡眠仓。现在,我需要
再次确认您是否受到思想钢印的影响,请您回答我这个问题:
'人类是否必胜?'
[1] 必胜
[2] 必败
[3] 这要取决于我所见到的三体文明
[4] 人的意志胜过一切
""")
#逃离太阳系
escape = Scene("自然选择号战舰控制中心",
"""
东方延绪:
章北海舰长,现在我交出自然选择号的控制权。据地球舰队传来的最新消息:
三体文明的探测器-水滴-已经抵达太阳系的边缘,15分钟后将与地球舰队接触。
现在飞船等待您发布第一条命令:
[1] 前进1 逃离太阳系
[2] 前进2 逃离太阳系
[3] 前进3 逃离太阳系
[4] 前进4 逃离太阳系
""")
#飞控终端
control_terminal = Scene("自然选择号战舰飞控终端","""
警告!
警告!
警告!
飞船进入前进4必须通知所有船员进入深海睡眠模式。请输入飞控密码:
[1] Man Always Remember Love Because Of Romance Only
[2] If You Give Me A Pivot Point I Could Move The Earth
[3] The Sun Rather Than The Earth At The Center Of The Universe
[4] Two Heads Are Always Better Than One
""")
# 太阳系外
extrasolar = Scene("自然选择号战舰","""
飞船已经飞出了太阳系,身后的蓝色空间号、深空号、终极规律号以及企业号正在追击。
下一步命令是:
[1] 延续人类文明,向前飞,向远飞
[2] 掉头击毁追击战舰
[3] 将舰队指挥权交给东方延续舰长
"""
)
# 星舰地球
fleet_earth = Scene("星舰地球","""
地球舰队已经被水滴摧毁.......
五艘战舰必须延续文明的责任,太空将是我们最后的归宿。星舰地球决定:
[1] 处决人类的叛徒章北海,回去与水滴战斗
[2] 处决人类的叛徒章北海,继续飞往十八光年外的天鹅座NH558J2
[3] 章北海是英雄,继续飞往十八光年外的天鹅座NH558J2
"""
)
#终极规律号
ultimate_rule_fleet = Scene("终极规律号","""
终极规律号战舰中出现了一种不安的气氛......
十四光年,飞船的资源是有限的,如何得到补给?
舰长,我们该怎么办?
[1] 向其它四艘战舰发射次声波氢弹
[2] 向其它四艘战舰求援,请求补给
[3] 要求星舰地球更改航程,找到补给点
[4] 回地球吧
"""
)
#蓝色空间号
blue_space_fleet = Scene("蓝色空间号","""
警告!
警告!
警告!
终极规律号的次声波氢弹冲击波即将到达....
防御成功,舰队损伤5%...
等待下一步命令:
[1] 发射高能伽马射线激光击毁终极规律号
[2] 逃离
""")
#最后的场景
last_episode = Scene("太空陵墓","""
蓝色空间号带走了所有的燃料和配件,并将自然选择号、企业号、深空号的
残骸切割多段,围城巨石阵,构建了一处太空陵墓,纪念人类第一次体会到
宇宙‘黑暗森林’的可怕。
恭喜你,顺利通过了剧情!
"""
)
START = 'natureSelectionFleet'
# 返回场景名字对应的场景对象
def load_scene(name):
return globals().get(name)
# 返回场景对象对应的场景名字
def name_scene(scene):
for key, value in globals().items():
if value == scene:
return key
7.3 编写game.py的自动测试脚本
接下来,将编写game.py的自动测试脚本。在tests\目录下新建game_tests.py,测试工具仍然为nosetests。分别对game.py中Scene的初始化函数、add_paths和enter函数进行测试。具体代码为:
from nose.tools import *
from threebody.game import *
def test_scene():
gold = Scene("GoldRoom",
"""This room has gold in it you can grab.
There's a door to the north.""")
assert_equal(gold.name, "GoldRoom")
assert_equal(gold.paths, {})
def test_add_enter_paths():
center = Scene("Center", "Test room in the center.")
north = Scene("North", "Test room in the north.")
south = Scene("South", "Test room in the south.")
center.add_paths({'north': north, 'south': south})
assert_equal(center.enter('north'), north)
assert_equal(center.enter("south"), south)
def test_game_map():
start_room = load_scene(START)
assert_equal(start_room.enter('1'), death_1_1)
assert_equal(start_room.enter('2'), death_1_2)
assert_equal(start_room.enter('3'), death_1_3)
assert_equal(start_room.enter('4'), escape)
assert_equal(start_room.enter('*'), natureSelectionFleet)
需要注意的是,在引入特性时,需要从threebody\game.py中引入所有提到的类和全局变量。PowerShell中的测试结果为:
7.4 编写HTML模板
在三体游戏中,我们采用了布局模板和POST表单。在templates\目录下创建show_room.html和layout.html两个文件。布局模板代码为:
<html>
<head>
<title>三体游戏</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
主要包含标题“三体游戏”。POST表单代码为:
{% extends "layout.html" %}
{% block content %}
<h1>{{ room.name }}</h1>
<pre>
{{ room.description }}
</pre>
{% if room.name == "游戏失败"%}
<p><a href="/">Play Again?</a></p>
{% elif room.name == "太空陵墓" %}
<p><a href="/">退出游戏</a></p>
{% else %}
<p>
<form action='/play' method='POST'>
- <input type='text' name='action'><input type='SUBMIT'>
</form>
</p>
{% endif %}
{% endblock %}
在这个html文件中,首先加载布局模板,其次将场景对象的名字和剧情描述显示在网页上。随后启用 if-else 条件判断语句,当检测到场景名为“游戏失败”的字符串时,在剧情描述底部显示 “play again?”;当检测到场景名为“太空陵墓”的字符串时,在剧情描述底部显示“退出游戏”;否则就显示文本框,等待用户输入信息推动剧情发展。
7.5 创建三体web游戏引擎
游戏引擎的python文件应创建在项目主目录下,即第一个threebody目录下。我创建了一个名为app.py的web引擎文件。主要引用了flask网络框架中的FLASK, session, redirect,url_for, request,render_template特性。当然,为了运行游戏,还需要引入game.py。具体代码如下:
from flask import Flask, session, redirect, url_for, request
from flask import render_template
from threebody import game
app = Flask(__name__)
@app.route("/")
def index():
#this is used to "setup" session with starting values
session['scene_name'] = game.START #利用会话临时存储场景名字
return redirect(url_for("play"))
@app.route("/play", methods=['POST', 'GET'])
def play():
scene_name = session.get('scene_name') #获得当前场景的名字
if request.method == 'GET':
room = game.load_scene(scene_name) #通过场景的名字加载场景对象
return render_template("show_room.html", room=room)
else:
action = request.form.get('action')
if scene_name and action:
room = game.load_scene(scene_name)
next_scene = room.enter(action) #获取下一个场景的对象
if not next_scene:
next_scene = room.enter('*')
#将下一个场景的名字存在临时会话中,供web服务器使用
session['scene_name'] = game.name_scene(next_scene)
else:
session['scene_name'] = game.name_scene(next_scene)
return redirect(url_for("play"))
# YOU SHOULD CHANGE THIS IF YOU PUT ON THE INTERNET
app.secret_key = 'sdjffiweriwuer923riew3shfs'
if __name__ == "__main__":
app.run()
程序的第一个主要函数为index(), 代表了主目录“/”,使用了session临时会话来存储场景名字,以工下次调用时使用。返回“/play"目录。程序第二个主要函数为play()。在"/play"目录下有两种模式POST或GET,当未获得用户输入时,默认为GET,此时引擎加载show_room.html文件。当用户输入有效信息后,进入post模式,此时引擎根据信息得到下一个剧情场景,并存储在session会话中,返回play目录进行下一场景加载。最后,通过app.run()语句来自动运行web游戏引擎。
7.6 游戏运行效果
在Poweshell中,运行
python app.py
结果为:
此时debug模式是关闭的,如果要打开,则将app.run()修改为app.run(debug = True)。在本地网页端运行时,根据提示,在浏览器上输入localhost:5000/即可。效果如下图:
结语
这是CSDN上自己写的第一个系列的文章,内容虽然没有什么新意,但坚持写作,把自己的思路理的非常清晰,也知道了什么地方还没有掌握。接下来,要沉静一段时间,继续学习,坚持写作。