寻宝游戏之Python

寻宝游戏 一,实验目的 1,练习 MongoDB 操作,学习如何设计数据库 2,练习 Python 的 Flask 框架 3,学会用 pytest 测试 4

本文包含相关资料包-----> 点击直达获取<-------

寻宝游戏

一.实验目的

1.练习 MongoDB 操作,学习如何设计数据库

2.练习 Python 的 Flask 框架

3.学会用 pytest 测试

4.学会用定时任务执行函数

二.实验要求

考虑以下游戏场景:

  1. 每个游戏玩家都有一定数量的金币、宝物。有一个市场供玩家们买卖宝物。玩家可以将宝物放到市场上挂牌,自己确定价格。其他玩家支付足够的金币,可购买宝物。
  2. 宝物分为两类:一类为工具,它决定持有玩家的工作能力;一类为配饰,它决定持有玩家的运气。
  3. 每位玩家每天可以通过寻宝获得一件宝物,宝物的价值由玩家的运气决定。每位玩家每天可以通过劳动赚取金币,赚得多少由玩家的工作能力决定。(游戏中的一天可以是现实中的 1 分钟、5 分钟、10 分钟。自主设定。)
  4. 每个宝物都有一个自己的名字(尽量不重复)。每位玩家能够佩戴的宝物是有限的(比如一个玩家只能佩戴一个工具和两个配饰)。多余的宝物被放在存储箱中,不起作用,但可以拿到市场出售。
  5. 在市场上挂牌的宝物必须在存储箱中并仍然在存储箱中,直到宝物被卖出。挂牌的宝物可以被收回,并以新的价格重新挂牌。当存储箱装不下时,运气或工作能力值最低的宝物将被系统自动回收。
  6. 假设游戏永不停止而玩家的最终目的是获得最好的宝物。

请根据以上场景构建一个假想的 Web 游戏,可供多人在线上玩耍。界面尽可能简单(简单文字和链接即可,不需要 style)。后台的数据库使用 MongoDB。对游戏玩家提供以下几种操作:寻宝(可以自动每天一次)、赚钱(可以自动每天一次)、佩戴宝物、浏览市场、买宝物、挂牌宝物、收回宝物。

提交:程序 + 文档

要求:

  1. 文档主要用于解释你的数据库设计,即需要构建哪些 collection,每个 collection 的文档结构是什么,需要构建哪些索引,应用如何访问数据库(具体的 CRUD 命令);
  2. 为玩家的操作设计 JSON HTTP 协议的接口,自定义接口格式(request 和 response 的 JSON);为每个接口编写测试用例和测试代码;
  3. 不限制编程语言及 Web 框架。

三.代码执行顺序及使用方法

整个项目代码位于 flaskProject 文件夹中。

1.执行 init_db.py,初始化 treasures 宝物库。

2.执行 app.py,后台自动运行寻宝和赚钱进程,每 20s 结果会显示在命令行上;登录 localhost:5000/ 即可进入登录界面,登录界面对应 templates/index.html

3.用浏览器在 localhost:5000/中执行注册/登录操作进入用户页面,用户界面对应 templates/game.html

4.在用户界面网页中允许用户使用 post form 提交表单来执行操作,也可以直接遵循 app.py 中的路由规则用 url 的 get 执行相关操作

(如果用 postman 测试,还支持 post JSON 的输入)

5.如果要用 pytest,在命令行中输入 pytest 即可,pytest 的配置文件为_init_.py 和 conftest.py,pytest 会按顺序运行 test_user.py 中的函数。(注意运行时要先把 MongoDB 中 markets 和 players 两个 collection 更改为 collection markets.json 和 collection players 两个 JSON 文件的内容,删除当前数据然后用 MongoDB compass 直接导入即可,否则有些测试代码如买卖商品会无法执行。)

6.为了重现本项目测试的过程,我将用到的四个 collection 中的数据存了下来,分别为 collection markets.json,collection players.json,collection treasures.json 和 picurl.json,后两个是在 init_db.py 时自动生成的不用手动去建立,前两个是用户使用过程中产生的,可以直接往名为 webgame 的数据库中的 markets 和 players collection 中导入。

四.实验过程

1.数据库设计

分为四个 collection:分别是 treasures,players,markets 和 picurl,其中前三个是必须的

连接的数据库是 MongoDB

E-R 图如下:

1.1 treasures

treasures 是一个宝物库,是静态的,在 ini_db.py 中提前执行一遍即可建立,需要修改宝物库时再重新运行 ini_db.py 即可。

treasures 的结构类似以下:

python {"name": "10级工具", "property": "T", "level": 10}

name 表示物品名字,property 表示用途(T=tool 工具,A=accessory 饰品),level 表示宝物等级

建立关于 name 的索引:

collection 内容如下:

1.2 players

players 存储用户信息,在新用户注册时会添加文档,对应函数为 app.py 中的 register(username, password)函数。

players 的结构类似以下:

python {"name": username, "money": 1000, "password": password, "treasure": {"T": "1级工具", "A": "1级饰品"}, "box": []}

name 表示用户名字,money 表示用户金币数,password 是用户登录密码,treasures 是一个字典存当前装备的工具和饰品名,box 中的列表装用户多余的物品(最大值为 10 个)

建立关于 name 的索引:

collection 内容如下:

1.3 markets

markets 储存市场信息,当有人出售或购买物品时会往该 collection 添加或删除文档。

markets 的结构类似以下:

python {"name": treasure, "price": price, "owner": username}

name 表示商品名字,price 表示商品价格,owner 表示商品出售者。

collection 内容如下:

1.4 picurl

这个 collection 不是必须的,之前三个 collection 已经建好了,我还想把宝物和图片联系起来就加了这个数据库,是为了实现自己附加的查看工具图片模样的功能,它存储每样工具图片的路径。

markets 的结构类似以下:

{"name": "10级工具.jpg"}

name 表示工具的名字路径

图片被放在 static 文件夹下:

collection 内容如下:

2.基本功能函数实现(登录,cwur 等)

为了满足 pytest 的要求,除了登录注册,返回的都是 JSON 类型的数据。

2.1 登录/注册

index.html 的登录页的表单数据会被传到 login 函数中,login 函数会判断用户是否注册,未注册会跳转到 register 完成注册,已注册直接进入用户界面 game.html,密码错误会提示

```python

以下是不同表单的处理函数,跳转到对应的后端函数中

@app.route('/process', methods=['POST', 'GET']) def process(): if request.method == 'POST': username = request.form.get("Name") password = request.form.get("Password") return login(username, password) ```

```python

登录,转到用户主页

def login(username, password): players.create_index([("name", pymongo.ASCENDING)], unique=True) if players.find_one({"name": username}) is None: return register(username, password) else: if players.find_one({"name": username})['password'] != password: return "

玩家 %s 密码错误请重新输入

" % username user_dict = str(show_dict(players.find_one({"name": username}))) return render_template('game.html', Name=username, Userdict=user_dict)

注册

def register(username, password): var = players.insert_one({"name": username, "money": 1000, "password": password, "treasure": {"T": "1级工具", "A": "1级饰品"}, "box": []}).inserted_id return "

玩家 %s 注册成功,请返回登录页面

" % username + "

" ```

密码错误会无法进入游戏页面

登录注册的界面见后面前端展示,也可以 py app.py 后访问 localhost:5000 直接查看。

2.2 查看用户箱子

查看某用户的箱子,用法例如 localhost:5000/qk/box

```python def look_box(username): answer = show_dict(players.find_one({"name": username}))

answer["answer"] = "这是%s的box返回结果:" % username

return jsonify(answer)

```

2.3 浏览市场

查看市场,用法例如 localhost:5000/qk/market

```python

浏览市场

def look_market(username): # 没找到用户 if players.find_one({"name": username}) is None: return "

请先注册用户

" # 显示market res = { "answer": "玩家%s查看市场" % username } for treasure in markets.find(): res["%s" % treasure["_id"]] = show_dict(treasure) return jsonify(res) ```

2.4 佩戴宝物

配戴宝物,用法例如 localhost:5000/qk/wear/10 级工具,如果箱子没有或者宝物库没有则配戴失败。

```python

佩戴宝物

def wear(username, treasure): # box中没有该宝物 if treasures.find_one({"name": treasure}) is None: return jsonify({"error": "该宝物名不存在"}) ''' return "

宝物库中没有 %s 宝物

" % treasure + "

" + \ str(show_dict(players.find_one({"name": username}))) '''

# 要佩戴的宝物类型
t_class = treasures.find_one({"name": treasure})['property']
# 要替换的当前佩戴在身上的该类型宝物
original = players.find_one({"name": username})["treasure"][t_class]

# 用flag判断宝箱中有没有该宝物
flag = 0
box = players.find_one({'name': username})['box']
player_treasure = players.find_one({"name": username})["treasure"]
for t in box:
    if t == treasure:
        box.remove(t)
        box.append(original)
        player_treasure[t_class] = treasure
        # 更新宝箱和佩戴的宝物
        players.update_one({"name": username}, {"$set": {"box": box}})
        players.update_one({"name": username}, {"$set": {"treasure": player_treasure}})
        flag = 1
        answer = show_dict(players.find_one({"name": username}))
        answer["answer"] = "玩家%s穿戴成功" % username
        return jsonify(answer)
if flag == 0:
    return jsonify({"error": "存储箱没有该宝物"})

```

2.5 购买宝物

购买宝物,用法例如 localhost:5000/qk/buy/9 级工具,如果市场没有或者钱不够则购买失败。

```python

购买宝物

def buy(username, treasure): # 市场没有该宝物 if markets.find_one({"name": treasure}) is None: return jsonify({"error": "市场无此宝物"}) ''' return "

市场暂无 %s 宝物

" % treasure + "

" \ + str(show_dict(players.find_one({"name": username}))) ''' player = players.find_one({"name": username}) box1 = player['box'] if len(box1) >= 10: recovery_treasure(username) box = player['box'] box.append(treasure) players.update_one({"name": username}, {"$set": {"box": box}}) treasure_money = sys.maxsize id_ = markets.find_one({"name": treasure})[' id'] # 用id进行记录,因为市场重复 for thing in markets.find({"name": treasure}): if int(thing['price']) < treasure_money: treasure_money = int(thing['price']) id = thing[' id'] money1 = player['money'] - treasure_money # 买不起 if money1 < 0: return jsonify({"error": "余额不足"}) players.update_one({"name": username}, {"$set": {"money": money1}}) owner = markets.find_one({"_id": id })['owner'] money2 = players.find_one({"name": owner})['money'] + treasure_money players.update_one({"name": owner}, {"$set": {"money": money2}}) # 市场删除该宝物 markets.delete_one({"name": treasure}) return jsonify({"answer": "购买完成,请查看背包"}) ```

2.6 撤回宝物

撤回宝物,用法例如 localhost:5000/qk/withdraw/10 级饰品,如果市场没有则撤回失败。

```python

收回挂牌宝物

def withdraw(username, treasure): # 市场没有该宝物 if markets.find_one({"name": treasure, "owner": username}) is None: return jsonify({"error": "市场无此宝物"}) ''' return "

市场没有 %s 宝物

" % treasure + "

" \ + str(show_dict(players.find_one({"name": username}))) ''' # 市场删除宝物 markets.delete_one({"name": treasure, "owner": username}) # 玩家收回宝物 box = players.find_one({"name": username})['box'] if len(box) >= 10: recovery_treasure(username) box = players.find_one({"name": username})['box'] box.append(treasure) players.update_one({"name": username}, {"$set": {"box": box}}) return jsonify({"answer": "收回成功,请查看背包"}) ```

2.7 出售宝物

出售宝物,用法例如 localhost:5000/qk/sell/10 级饰品/1000,如果背包没有则出售失败。

```python

出卖宝物

def sell(username, treasure, price): box = players.find_one({'name': username})['box'] if treasure not in box: return jsonify({"error": "存储箱没有该宝物"}) price = int(price) player = players.find_one({"name": username}) # 卖家宝物到位 box = player['box'] for t in box: if t == treasure: box.remove(t) break players.update_one({"name": username}, {"$set": {"box": box}}) # 市场宝物到位 markets.insert_one({"name": treasure, "price": price, "owner": username}) return jsonify({"answer": "挂牌成功,请查看市场"}) ```

3.定时任务(寻宝 + 赚钱)函数实现

用到了 apscheduler 这个库,作用是定时执行程序

from flask_apscheduler import APScheduler

再实现需要自动执行的函数,这里是 find_treasure 和 find_money 函数

```python

自动寻宝

def find_treasure(): # 遍历每个玩家 for player in players.find(): name = player["name"] # 宝箱已满 if len(player['box']) >= 10: print("存储箱已满将回收一件低端宝物") recovery_treasure(name) # 得到的宝物和饰品的级别有关 box = players.find_one({"name": name})['box'] wear_treasure_name = player['treasure']['A'] wear_treasure_level = treasures.find_one({"name": wear_treasure_name})['level'] ls = [] for col in treasures.find({"level": {"$lte": wear_treasure_level + 2, "$gte": wear_treasure_level - 2}}): ls.append(col) # 随机寻宝 x = random.randint(0, len(ls) - 1) box.append(ls[x]['name']) # 更新宝物 players.update_one({"name": name}, {"$set": {"box": box}}) print("玩家 %-6s 获得宝物 %s" % (name, ls[x]['name']))

自动赚钱

def find_money(): # 遍历每个玩家 for player in players.find(): wear_treasure_name = player['treasure']['T'] wear_treasure_level = treasures.find_one({"name": wear_treasure_name})['level'] # 得到的金钱和工具的级别有关 money_get = random.randint((wear_treasure_level - 1) * 100, (wear_treasure_level + 1) * 100) # 打入账户 money = player['money'] + money_get name = player["name"] # 更新账户 players.update_one({"name": name}, {"$set": {"money": money}}) print("玩家 %-6s 金币到账 %d" % (name, money_get)) ```

写一个配置自动任务的类,将 find_treasure 和 find_money 设定为每 20 seconds 执行一次

```python

配置自动任务的类

class Config(object): JOBS = [ { 'id': 'job1', 'func': ' main :find_treasure', 'trigger': 'interval', 'seconds': 20,

    },
    {
        'id': 'job2',
        'func': '__main__:find_money',
        'trigger': 'interval',
        'seconds': 20,

    }
]

```

最后在主函数中执行

python if __name__ == "__main__": app.config.from_object(Config()) # 配置自动执行任务,后台寻宝赚钱 scheduler = APScheduler() scheduler.init_app(app) scheduler.start() app.run() # app开始运行

可以在终端看到结果

4.附加功能函数实现

为了游戏的丰富性、完整性和个性化,我添加了 pic_find,merge 和 finish 函数,功能如下图所示

注:finish 的通关指的是同时获得 10 级工具和 10 级饰品,缺一不可,merge 的融合需要付 100 金币

函数名 参数 功能
pic_find pic_url 表示图片路径 查看某样宝物的图片
merge username 表示用户名, treasure 表示第一个宝物, treasure2 表示第二个宝物 融合两件宝物成随机一件宝物
finish username 表示用户名 检查该用户是否通关

python def pic_find(pic_url): if pictures.find_one({"name": pic_url}) is None: return "<h1>找不到该图片信息</h1>" return render_template('picture.html', Name=pic_url)

```python

融合宝物

def merge(username, treasure, treasure2): player = players.find_one({"name": username}) box = player['box'] if player['money'] < 1000: return "

操作失败,当前金币小于1000无法寻宝

" + "

" \ + str(show_dict(players.find_one({"name": username})))

if treasure not in box:
    return "<h1>操作失败,存储箱没有 %s 宝物</h1>" % treasure + "<br><br>" \
           + str(show_dict(players.find_one({"name": username})))
if treasure2 not in box:
    return "<h1>操作失败,存储箱没有 %s 宝物</h1>" % treasure2 + "<br><br>" \
           + str(show_dict(players.find_one({"name": username})))
if treasure == treasure2:
    num = 0
    for t in box:
        if t == treasure:
            num += 1
    if num < 2:
        return "<h1>操作失败,存储箱没有两件 %s 宝物</h1>" % treasure2 + "<br><br>" \
               + str(show_dict(players.find_one({"name": username})))
for t in box:
    if t == treasure:
        box.remove(t)
        break
for t in box:
    if t == treasure2:
        box.remove(t)
        break
ls = []
for col in treasures.find():
    ls.append(col)
    # 随机寻宝
x = random.randint(0, len(ls) - 1)
new_treasure_name = ls[x]['name']
box.append(ls[x]['name'])
players.update_one({"name": username}, {"$set": {"box": box}})
money1 = player['money'] - 100
players.update_one({"name": username}, {"$set": {"money": money1}})
return "<h1>融合成功,得到 %s ,请查看背包,100元已经扣除</h1>" % new_treasure_name + "<br><br>" + \
       str(show_dict(players.find_one({"name": username})))

```

python def finish(username): player = players.find_one({"name": username}) box = player['box'] player_treasure = player["treasure"] flag_t = 0 flag_a = 0 for t in box: if t == "10级工具": flag_t = 1 break for t in box: if t == "10级饰品": flag_a = 1 break if player_treasure["T"] == "10级工具": flag_t = 1 if player_treasure["A"] == "10级饰品": flag_a = 1 if flag_a == 1 and flag_t == 1: return "<h1>通关成功,谢谢游玩</h1>" + "<br><br>" \ + str(show_dict(players.find_one({"name": username}))) else: return "<h1>不好意思,您还未集齐10级工具和10级饰品,请继续游玩</h1>" + "<br><br>" \ + str(show_dict(players.find_one({"name": username})))

演示如下:

5.pytest 测试

pytest 主要用到 get 路由的方法,不用访问前端也可完成。

app.py 中通过 route 来执行 get 方法,写了个 find_method 作为函数的中转站

pytest 的配置在 conftest.py 中,pytest 中的测试函数 test_user.py 中包含 13 个函数测试 crud,用 get 路由去访问

```python

根据不同参数设置不同路由,用于url的访问

@app.route("/ / ", methods=['GET']) @app.route("/ / / ", methods=['GET']) @app.route("/ / / / ", methods=['GET']) @app.route("/ / / / ", methods=['GET'])

一个中转站根据路由进行重定向

def find_method(username, operation, treasure='test', treasure2='test', price=0): if operation == 'login': return login(username, "123456") elif operation == 'box': return look_box(username) elif operation == 'market': return look_market(username) elif operation == 'wear': return wear(username, treasure) elif operation == 'buy': return buy(username, treasure) elif operation == 'withdraw': return withdraw(username, treasure) elif operation == 'sell': return sell(username, treasure, price) elif operation == 'merge': return merge(username, treasure, treasure2) elif operation == 'finish': return finish(username) else: return "

输入或操作错误

" ```

test_user.py 的函数都类似以下,一共 13 个,具体见 test_user.py

python def test_market(client: FlaskClient): response = client.get("/qk/market") json = response.get_json() print(json) assert json["answer"] == "玩家qk查看市场"

测试结果如下:

注意测试前要将 MongoDB 数据库中 markets 和 players 设置成 collection markets.json 和 collection players.json(在 flaskProject 文件夹中,建议删除原 collection 然后用 MongoDB compass 直接导入)

直接 pytest

用 coverage 测试

coverage 的测试结果

6.前端展示

前端代码位于 templates 文件夹中,可以满足用表单的 post 请求来对数据库执行操作,需要 import request

python from flask import Flask, render_template, request

app.py 中以 process 开头的函数都是处理表单的函数,从 form 中得到参数,调用相应函数即可

前端页面展示如下:

登录注册页面

用户开始游戏页面:

点击按钮的结果(以查看背包和查看市场为例子,返回 JSON,结果和直接用 get 方法是一样的但是这样用 post 可以保护个人信息):

查看背包

查看市场

五.注意事项

  1. 一开始所有函数返回的都是 HTML 文本,导致难以转变为 JSON 无法进行 pytest 测试,后来全部统一改成字典 jsonify 成 JSON 数据得以解决。
  2. JSON 输出会出现十六进制乱码,解决方法如下,app.py 的 JSON 配置中加以下两行

python # 为了防止json中文乱码请加入这两行 app.config['JSON_AS_ASCII'] = False app.config['JSONIFY_MIMETYPE'] = "application/json;charset=utf-8" 3. 当函数中往往需要输出 players.find_one({"name": username})),但是直接输出甚至会暴露用户的密码,所以要用一个函数去把字典中密码的部分去掉,用到以下函数

python # 处理每个操作返回的结果, def show_dict(dictionary): dict_ = {} for key in dictionary.keys(): if key != '_id' and key != "password": dict_[key] = dictionary[key] return dict_ 4. 用户的箱子只能存十个,超过十个时要回收等级最低的宝物,所以需要如下函数

python # 如果宝箱充满系统回收等级最低宝物 def recovery_treasure(name): box = players.find_one({"name": name})['box'] treasure_name = box[0] level = treasures.find_one({"name": box[0]})['level'] # 找到等级最低宝物 for treasure in box[1:]: temp = treasures.find_one({"name": treasure})['level'] if temp < level: level = temp treasure_name = treasure # 删除该宝物 for treasure in box: if treasure == treasure_name: box.remove(treasure) break # 更新宝箱 players.update_one({'name': name}, {"$set": {"box": box}}) print("玩家 %-6s 被系统回收宝物 %-6s" % (name, treasure_name))

参考文献

  • 基于asp.net的在线软件项目交易系统的设计与实现(电子科技大学·顾杰)
  • 分布式在线旅游搜索爬虫系统设计与实现(北京邮电大学·徐显炼)
  • 主题搜索引擎搜索策略的研究及算法设计(兰州大学·高庆芳)
  • 博客搜索引擎与排名技术研究(江南大学·严磊)
  • 基于.NET自定义控件的社区网站系统研究与实现(武汉理工大学·刘亚)
  • 网络游戏虚拟物品交易系统设计与实现(吉林大学·李云峰)
  • 面向特定网页的Web爬虫的设计与实现(吉林大学·马慧)
  • 面向游戏垂类的搜索系统的设计与实现(北京交通大学·马振领)
  • 分布式在线旅游搜索爬虫系统设计与实现(北京邮电大学·徐显炼)
  • 网络游戏虚拟物品交易系统设计与实现(吉林大学·李云峰)
  • 基于网络爬虫的搜索引擎的设计与实现(湖北工业大学·冯丹)
  • 基于Lucene的商品垂直搜索引擎研究与实现(东华大学·潘磊宁)
  • 基于.NET平台的游戏门户系统设计与实现(电子科技大学·余胜鹏)
  • 搜索引擎中网络爬虫技术研究(西安电子科技大学·郭海燕)
  • 面向游戏垂类的搜索系统的设计与实现(北京交通大学·马振领)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码客栈 ,原文地址:https://m.bishedaima.com/yuanma/36002.html

相关推荐

发表回复

登录后才能评论