还记得我写过小学生识字app,双人比赛版开发-优快云博客,这篇文章吗?这个游戏程序是个单机版,虽然利用平板屏幕比较大可以供2个小孩进行游戏,但在游戏中存在beeware开发安卓游戏的先天不足,就是不能同时按2个键,所以游戏体验感较差。最近,我发现python中的flask可以做局域网的服务器,之前一直想把所有小学生学习系列的app网络化,但因为没有钱租用互联网服务器就放弃了,现在准备把这个认字游戏网络化,再一步步将其他app网络化。当然这里的网络化我希望是局域网,因为如果有互联网,小孩容易上网瘾,还不如使用局域网的安全。用一部手机的Qpython做Flask服务器,就能够随时随地使用。
再写想法:
1.原来用Beeware做一个app_beeware能直接操作数据库吗-优快云博客的程序,小孩用一段时间内会觉得枯燥,最近看了一本叫《心流》的书,才知道小孩子能够短时间集中精神,我们也一样,所有有王者荣耀这样让你10多分钟结束一局的游戏。
2.小孩如果有人跟他一起玩,那会比较开心。所以我选择在app上面用一个输入框来保存服务器地址,这样可以随时改变服务器地址。这个也是首创的吧。还有,我也不知道如何用服务器和2部安卓手机,实现建网对战的游戏,所以在服务器段用sqlite3数据库,用一行来记录比赛时间、2个ip地址、2部手机提交的数据,实现联网,这个也参考其他游戏的设计。
下面,写全部代码和创作流程。
一、改造beeware的app,添加requests到pyproject.toml
我们需要用到python的requests库和miniaudio,用于网络通信。所以需要将它加入pyproject.toml里面
关于beeware开发app,参考官网BeeWare Tutorial
二、主程序app.py
from functools import partial
import toga
from toga.style.pack import COLUMN, LEFT, ROW, Pack
import sqlite3
import random
from time import time, sleep
from pathlib import Path
import miniaudio
import datetime
import shutil
import threading
import requests
#导入必要的库,其中这个miniaudio库是可以在Android上运行的,我尝试了很多播放声音的库,只有这个能被Beeware编译到安卓程序上
resources_folder = Path(__file__).joinpath("../resources/").resolve()
resources_folder1 = Path(__file__).joinpath("../resources/sy1").resolve()
resources_folder2 = Path(__file__).joinpath("../resources/sy2").resolve()
resources_folder3 = Path(__file__).joinpath("../resources/sy3").resolve()
db_filepath = resources_folder.joinpath("qwrznew.db")
sjcs = 1
if sjcs == 1:
sj_file = '/data/data/com.qwrzvs.qwrzvs/qwrznew.db'
if Path(sj_file).exists():
db_filepath = sj_file
else:
shutil.copy(db_filepath, sj_file)
db_filepath = sj_file
elif sjcs == 2:
db_filepath = '/storage/emulated/0/Pictures/qwrznew.db'
fwqip = '' #服务器ip地址,下面是在原有数据库中setupme表中新建1列,用于储存服务器ip地址
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
cursor = c.execute("SELECT COUNT(*) FROM pragma_table_info('setupme') WHERE name = 'fwqip';")
row = cursor.fetchone()
have_fwqip = row[0]
if have_fwqip == 0:
c.execute("ALTER TABLE setupme ADD COLUMN fwqip TEXT;")
conn.commit()
else:
cursor = c.execute("SELECT fwqip FROM setupme;")
row = cursor.fetchone()
fwqip = row[0]
c.close()
conn.close()
#服务器ip地址获取结束
global duihz, duipy, duizc1, duizc2, duizc3,duizc4,duizc5, duid, kw, jj
global stime, etime, ytime, begintime, testcs, cuo_cs, yshen, cid, tjcs
names = globals()
cid = 0
yshen = 0
testcs = 0
cuo_cs = 0
stime = time()
begintime = time()
rid = 0
cuot = 0
global njt
njt = '一年级'
xzng1 = 0
def bfsy(hzid,idsc):
if hzid < 1001:
stream = miniaudio.stream_file(str(resources_folder1.joinpath(str(hzid) + '.wav')))
elif hzid > 1000 and hzid < 2001:
stream = miniaudio.stream_file(str(resources_folder2.joinpath(str(hzid) + '.wav')))
else:
stream = miniaudio.stream_file(str(resources_folder3.joinpath(str(hzid) + '.wav')))
with miniaudio.PlaybackDevice() as device:
device.start(stream)
sleep(idsc)
#朗读汉字的声音
def bfsy_cuo(hzid,idsc):
stream = miniaudio.stream_file(str(resources_folder3.joinpath('cuo.wav')))
with miniaudio.PlaybackDevice() as device:
device.start(stream)
sleep(1.325)
if hzid < 1001:
stream = miniaudio.stream_file(str(resources_folder1.joinpath(str(hzid) + '.wav')))
elif hzid > 1000 and hzid < 2001:
stream = miniaudio.stream_file(str(resources_folder2.joinpath(str(hzid) + '.wav')))
else:
stream = miniaudio.stream_file(str(resources_folder3.joinpath(str(hzid) + '.wav')))
with miniaudio.PlaybackDevice() as device:
device.start(stream)
sleep(idsc)
#当选择错误时朗读汉字的声音,在后台播放,避免按键冲突
def maxmin(xzng):
if xzng < 9:
yshen = 0
else:
yshen = 1
if xzng > 16:
rongy = 1
else:
rongy = 0
if xzng in (1,9,17):
njt = '一年级'
njt1 = '二年级上'
elif xzng in (2,10,18):
njt = '二年级'
njt1 = '三年级上'
elif xzng in (3,11,19):
njt = '三年级'
njt1 = '四年级上'
elif xzng in (4,12,20):
njt = '四年级'
njt1 = '五年级上'
elif xzng in (5,13,21):
njt = '五年级'
njt1 = '六年级上'
elif xzng in (6,14,22):
njt = '六年级'
njt1 = '五年级'
else:
njt = '二年级'
njt1 = '三年级'
return(njt,njt1,yshen,rongy)
njt,njt1,yshen,rongy = maxmin(3)
#选择年级和有声无声的函数,默认是3
def u_record(testcs,cuo_cs,etime,begintime,cid):
if testcs > 0:
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
c.execute("UPDATE record SET ts = ?,ct = ?,mt = ? WHERE cid = ?;", (testcs,cuo_cs,int((etime-begintime) / testcs),cid))
conn.commit()
c.close()
conn.close()
#记录每次的题数,错题数,每题用多少秒,每个界面用一个cid
def u_cs1(duid):
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
query_sql = "update hz set cs1 = cs1 + 1 where id= ?"
c.execute(query_sql, (str(duid),))
conn.commit()
c.close()
conn.close()
#记录这个汉字的练习次数+1
def u_cs(addcs,duid):
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
query_sql = "update hz set cs = cs + ? where id= ?"
c.execute(query_sql, (str(addcs),str(duid),))
conn.commit()
c.close()
conn.close()
#为某个汉字加积分
def j_cs(addcs,duid):
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
query_sql = "update hz set cs = cs - ? where id= ?"
c.execute(query_sql, (str(addcs),str(duid),))
conn.commit()
c.close()
conn.close()
#为某个汉字减少积分
def tj_cs():
if globals()['testcs'] == 0:
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
query_str = 'select rq,sj,ts,ct,mt from record where ts > 0 order by rq desc limit 0,5;'
cursor = c.execute(query_str)
rows = cursor.fetchall()
stext3 = ""
for row in rows:
stext3 += "%s %s:做%d题,错%d题,平均用%d秒\n" % (row[0],row[1],row[2],row[3],row[4])
c.close()
conn.close()
globals()['tjcs'] = stext3
else:
stext3 = globals()['tjcs']
return(stext3)
#在界面的下面显示最近4天学习总题数等信息,方便家长检查孩子的学习情况
def set_all(njt,njt1):
fid = random.randint(0, 16)
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
cursor = c.execute(
"SELECT id, hz, py, zc1, zc2, zc3, zc4, zc5, kw, sc from (SELECT *,rank() over (PARTITION by hz order by id desc) as rank FROM hz where nj like '%" + njt + "%' or nj like '%" + njt1 + "%' order by cs asc) where rank = 1 limit " + str(fid) + ",25")
rows = cursor.fetchall()
hz = []
py = []
zc1 = []
zc2 = []
zc3 = []
zc4 = []
zc5 = []
hzid = []
kw = []
idsc = []
i = 1
for row in rows:
if i < 26:
hzid.append(row[0])
hz.append(row[1])
py.append(row[2])
zc1.append(row[3])
zc2.append(row[4])
zc3.append(row[5])
zc4.append(row[6])
zc5.append(row[7])
kw.append(row[8])
idsc.append(row[9])
i = i + 1
ii = list(range(1,26))
random.shuffle(ii)
c.close()
conn.close()
return ii,hzid,hz,py,zc1,zc2,zc3,zc4,zc5,kw,idsc
#按年级返回25个汉字的拼音组词、课文、id、和音频文件的播放长度
def build(app):
# 定义组件
c_box0 = toga.Box()
c_box = toga.Box()
b1_box = toga.Box()
b2_box = toga.Box()
b3_box = toga.Box()
b4_box = toga.Box()
b5_box = toga.Box()
box = toga.Box()
#c_input0,服务器ip地址的输入框
c_input0 = toga.TextInput(style=Pack(font_size=14),value=fwqip)
c_input = toga.TextInput(readonly=True,style=Pack(font_size=22))
c_label = toga.Label("请选择几年级开始学习...", style=Pack(text_align=LEFT))
c_label1 = toga.Label("", style=Pack(text_align=LEFT, font_size=12))
c_label2 = toga.Label("", style=Pack(text_align=LEFT))
c_label3 = toga.Label("", style=Pack(text_align=LEFT))
#界面设置
def submit_button_pressed(widget):
# 获取输入框的值,如果值跟数据库里面的ip相同不操作,否则更新到数据库
global fwqip
global cid,yshen,testcs,cou_cs,stime,begintime,rid,cout,njt,xzng1
if fwqip != c_input0.value:
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
fwqip = c_input0.value
c.execute(f"UPDATE setupme SET fwqip = '{fwqip}';")
conn.commit()
c.close()
conn.close()
fwqip = c_input0.value
#另外一个功能,发送zt=-1到服务器,将zt更新为0,以结束游戏,重新可以在建
try:
response = requests.get(f'http://{fwqip}:5000/vsdata')
response.raise_for_status() # 检查请求是否成功
data = response.json()
message = data.get('message')
c_input.value = message
params = {
'zt': -1,
}
response = requests.get(f'http://{fwqip}:5000/vsdatas', params=params)
if response.status_code == 200:
# 处理成功的响应
data = response.json()
message = data.get('message')
c_input.value = message
# print(response.json()) # 假设响应是 JSON 格式
# globals()['cid'] = 0
# globals()['yshen'] = 0
# globals()['testcs'] = 0
# globals()['cuo_cs'] = 0
# stime = time()
# begintime = time()
globals()['rid'] = 0
globals()['xzng1'] = 0
# cuot = 0
# njt = '一年级'
aaa = '一 二 三 四 五 六 无 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 有 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 容 易 玩'.split()
#重新游戏就将按钮全部初始化
for i in range(1,26):
# print(i + 10000)
setattr(names['button_' + str(i)],"text",aaa[i-1])
names['button_' + str(i)].style.update(color='black')
setattr(names['button_' + str(i)],"on_press",partial(bt1,str(i)))
setattr(names['button_' + str(i)],"enabled",True)
else:
# 处理错误的响应
print(f"Failed to fetch data0: {response.status_code}")
except requests.RequestException as e:
print(f'Error: {e}')
submit_button = toga.Button('连接或重新开始', on_press=submit_button_pressed)
def bt1(bb, widget):
global rid,arid,fwqip,etime,ytime
global hz,ii,duid,zc2,hzid,py,zc1,zc2,zc3,zc4,zc5,kw,idsc
stext = ''
xzng = int(bb)
if rid == 0:
globals()['xzng1'] = xzng
globals()['testcs'] += 1
globals()['etime'] = time()
globals()['ytime'] = int(etime - stime)
#记录用时
if rid == 0:
#rid=0为一开始
current_date = datetime.datetime.now()
formatted_date = current_date.strftime("%Y-%m-%d")
formatted_time = current_date.strftime("%H:%M:%S")
#每次rid=0时,生成一个记录,记录学习情况
conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
c = conn.cursor()
c.execute("INSERT INTO record (rq,sj,ts,ct,mt) VALUES (?,?,?,?,?);", (formatted_date,formatted_time,0,0,0))
conn.commit()
cursor = c.execute("SELECT cid from record order by cid desc")
row = cursor.fetchone()
c.close()
conn.close()
#生成记录结束
globals()['cid'] = row[0]
#提取cid到全局变量中
globals()['testcs'] = 0
#rid为0时,测试次数重计
globals()['cuo_cs'] = 0
#错误次数也重计
globals()['begintime'] = time()
#设定rid为0是的开始时间,可以计算做完25个汉字的时间
globals()['njt'],globals()['njt1'],globals()['yshen'],globals()['rongy'] = maxmin(xzng)
#将年级和有声的变量按选择存入到全局变量
globals()['ii'],globals()['hzid'],globals()['hz'],globals()['py'],globals()['zc1'],globals()['zc2'],globals()['zc3'],globals()['zc4'],globals()['zc5'],globals()['kw'],globals()['idsc']=set_all(globals()['njt'],globals()['njt1'])
#将全部要用到的变量存入全局变量
globals()['stime'] = time()
#登记或匹配开始游戏,发送rid=0,xzng(选择年级)到服务器登记并获取比赛时间bssj,对手ip(aip)
bssj = ''
aip = ''
params = {
'rid': 0,
'xzng': globals()['xzng1'],
}
response = requests.get(f'http://{fwqip}:5000/vsdatas', params=params)
# 检查响应状态码
if response.status_code == 200:
# 处理成功的响应
# print(response.json()) # 假设响应是 JSON 格式
data = response.json()
bssj = data.get('bssj')
aip = data.get('aip')
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
# print(bssj,aip,sj)
if bssj and aip:
#比赛需要有比赛时间,对方ip
miaocha = (datetime.datetime.strptime(bssj, "%Y-%m-%d %H:%M:%S") - datetime.datetime.strptime(sj, "%Y-%m-%d %H:%M:%S")).total_seconds()
# print(miaocha)
#如果秒差在6秒内,那么中断秒差时间,确保能够同时比赛
if miaocha > 0 and miaocha <= 6:
sleep(miaocha)
for i in range(1,26):
# 设置按键的汉字
# setattr(names['button_' + str(i)],"text",hz[ii[i-1]-1])
names['button_' + str(i)].text = hz[ii[i-1]-1]
names['button_' + str(i)].style.update(color='black')
#设置按键名称,即把汉字显示在按钮上,汉字通过随机错排列,这个ii就是打乱了的1到16顺序,2个-1是因为最小是0
#rid为0为一开始设置第一题
globals()['rid'] = 1
#rid变为1,算第一次,答对了rid才会增加1
globals()['duid'] = hzid[rid-1]
#这个是对的汉字在数据库中的id
globals()['jj'] = ii.index(rid) + 1
#这个变量是记忆对的汉字选择的按钮的编码,index是寻址,寻找rid在ii列中的位置,这个位置+1为按钮的参数
if yshen == 0:
if zc2[rid-1] == '':
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□')
else:
zc = []
zc.append(zc1[rid-1])
zc.append(zc2[rid-1])
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□') + "、" + zc2[rid-1].replace(hz[rid-1],'□')
if zc3[rid-1] != '':
zc.append(zc3[rid-1])
if zc4[rid-1] != '':
zc.append(zc4[rid-1])
if zc5[rid-1] != '':
zc.append(zc5[rid-1])
zc = random.sample(zc,2)
c_value = py[rid-1] + " " + zc[0].replace(hz[rid-1], '□')+ "、" + zc[1].replace(hz[rid-1],'□')
#无声是在输入栏显示拼音和隐去本字的组词,这里面采用随机数,在多个组词中随机选择一个
c_text = '来自:' + globals()['njt'] + globals()['njt1'] + '的' + kw[rid-1]
#显示来自年级和课文
c_label.text = c_text
c_label3.text = tj_cs()
#rid为0是,显示统计的4天学习情况
if yshen == 0:
c_input.value = c_value
else:
play_sound_background(hzid[rid-1],idsc[rid-1])
#当选择有声时,用miniaudio发出声音,不使用sleep就不会播放声音,sleep的时长就是播放声音的时长
else:
#秒差不对,要求结束这个在建
params = {
'zt': 0,
'xzng': xzng1,
}
response = requests.get(f'http://{fwqip}:5000/vsdatas', params=params)
if response.status_code == 200:
# 处理成功的响应
data = response.json()
message = data.get('message')
c_input.value = message
# print(response.json()) # 假设响应是 JSON 格式
else:
# 处理错误的响应
print(f"Failed to fetch data0: {response.status_code}")
else:
#没有反馈bssj和aip
pass
else:
# 处理错误的响应
print(f"Failed to fetch data2: {response.status_code}")
# print(bssj,aip)
else:
#rid不为0,既是在比赛中。。。
text5 = ""
if rid >=1:
text5 = text5 + hz[rid-1] + "(" + py[rid-1] + ") " + zc1[rid-1] + " " + zc2[rid-1] + " " + zc3[rid-1] + " " + zc4[rid-1] + " " + zc5[rid-1]
#text5是复习内容
if testcs > 0:
stext = '%d分钟,共答%d题,错%d题,每题%d秒。' % (int((etime-begintime) / 60),testcs,cuo_cs,int((etime-begintime) / testcs))
if xzng == globals()['jj'] and rid <=25:
if rongy == 1:
setattr(names['button_' + str(xzng)],"enabled",False)
names['button_' + str(xzng)].style.update(color='green')
else:
names['button_' + str(xzng)].style.update(color='black')
c_label1.text = "复习:" + text5
#回答正确才显示复习内容
if yshen == 1:
c_input.value = hz[rid-1] + " 字答对了。"
c_label2.text = '累计学习' + stext
else:
c_label2.text = '答对。累计' + stext
if ytime <= 7:
u_cs(2,hzid[rid-1])
#用时小于7秒加2分
else:
u_cs(1,hzid[rid-1])
#用时大于7秒加1分
globals()['stime'] = time()
arid = 0#等待赋值
#rid在下面9种情况下才发送更新rid到服务器,以减少服务器负担
if globals()['rid'] in (1,2,3,10,20,22,24,25,26):
params = {
'rid': rid,
}
response = requests.get(f'http://{fwqip}:5000/vsdatas', params=params)
# 检查响应状态码
if response.status_code == 200:
# 处理成功的响应
# print(response.json()) # 假设响应是 JSON 格式
data = response.json()
arid = int(data.get('rid'))
else:
# 处理错误的响应
print(f"Failed to fetch data1: {response.status_code}")
globals()['rid'] += 1
#回答正确,开始下一个汉字的计时和rid
if rid == 26:
#if arid > 0 and arid < 25:
if arid < 25 and arid >= 0:
c_input.value = '你赢了!'
else:
c_input.value = '继续努力!'
globals()['rid'] = 0
globals()['xzng1'] = 0
aaa = '一 二 三 四 五 六 无 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 有 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 容 易 玩'.split()
for i in range(1,26):
# print(i + 10000)
setattr(names['button_' + str(i)],"text",aaa[i-1])
names['button_' + str(i)].style.update(color='black')
setattr(names['button_' + str(i)],"enabled",True)
else:
globals()['duid'] = hzid[rid-1]
globals()['jj'] = ii.index(rid) + 1
#因为上面rid加1了,所以这2个全局变量都要变。
if yshen == 0:
if zc2[rid-1] == '':
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□')
else:
zc = []
zc.append(zc1[rid-1])
zc.append(zc2[rid-1])
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□') + "、" + zc2[rid-1].replace(hz[rid-1],'□')
if zc3[rid-1] != '':
zc.append(zc3[rid-1])
if zc4[rid-1] != '':
zc.append(zc4[rid-1])
if zc5[rid-1] != '':
zc.append(zc5[rid-1])
zc = random.sample(zc,2)
c_value = py[rid-1] + " " + zc[0].replace(hz[rid-1], '□')+ "、" + zc[1].replace(hz[rid-1],'□')
if yshen == 0:
c_input.value = c_value
else:
play_sound_background(hzid[rid-1],idsc[rid-1])
#上面为选择正确时的程序
elif xzng != globals()['jj'] and rid <= 25:
if rongy == 1:
c_label1.text = "提示:" + text5
#比赛模式不提示
globals()['cuo_cs'] += 1
if yshen == 1:
c_input.value = "答错了!请重新选择!"
c_label2.text = '累计学习' + stext
play_sound_cuo(hzid[rid-1],idsc[rid-1])
#选择错误时的程序,重新读一遍
else:
if zc2[rid-1] == '':
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□')
else:
zc = []
zc.append(zc1[rid-1])
zc.append(zc2[rid-1])
c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□') + "、" + zc2[rid-1].replace(hz[rid-1],'□')
if zc3[rid-1] != '':
zc.append(zc3[rid-1])
if zc4[rid-1] != '':
zc.append(zc4[rid-1])
if zc5[rid-1] != '':
zc.append(zc5[rid-1])
zc = random.sample(zc,2)
c_value = py[rid-1] + " " + zc[0].replace(hz[rid-1], '□')+ "、" + zc[1].replace(hz[rid-1],'□')
c_input.value = "重答:" + c_value
c_label2.text = '答错!累计' + stext
j_cs(3,hzid[rid-1])
#选择错误,扣3分
globals()['stime'] = time()
if rid <= 26:
c_text = '来自:' + globals()['njt'] + globals()['njt1'] + '的' + kw[rid-1]
c_label.text = c_text
c_label3.text = tj_cs()
if globals()['cid'] > 0:
u_record(testcs,cuo_cs,etime,begintime,cid)
if rid <=26:
u_cs1(hzid[rid-1])
#记录学习情况
aaa = '一 二 三 四 五 六 无 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 有 声 ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ 容 易 玩'.split()
#print(aaa) 用字符串变为数组,这样代码比较简
for i in range(1,26):
# print(i + 1000)
names['button_' + str(i)] = toga.Button(aaa[i-1], on_press=partial(bt1,str(i)),style=Pack(font_size=22))
#初始界面
# 设置组件样式和布局
c_box0.add(c_input0)
c_box0.add(submit_button)
c_box.add(c_input)
box.add(c_box0)
box.add(c_box)
for i in range(1,6):
b1_box.add(names['button_' + str(i)])
for i in range(6,11):
b2_box.add(names['button_' + str(i)])
for i in range(11,16):
b3_box.add(names['button_' + str(i)])
for i in range(16,21):
b4_box.add(names['button_' + str(i)])
for i in range(21,26):
b5_box.add(names['button_' + str(i)])
box.add(b1_box)
box.add(b2_box)
box.add(b3_box)
box.add(b4_box)
box.add(b5_box)
box.add(c_label)
box.add(c_label1)
box.add(c_label2)
box.add(c_label3)
# 设置 outer box 和 inner box 的样式
box.style.update(direction=COLUMN, padding=5)
b1_box.style.update(direction=ROW, padding=1)
b2_box.style.update(direction=ROW, padding=1)
b3_box.style.update(direction=ROW, padding=1)
b4_box.style.update(direction=ROW, padding=1)
c_box.style.update(direction=ROW, padding=5)
c_box0.style.update(direction=ROW, padding=5)
# 设置单个组件的样式
c_input0.style.update(width=240, flex=1)
c_input.style.update(width=345, flex=1)
# button.style.update(padding=15)
c_label.style.update(width=345, padding_left=4)
#上面的width用于撑开两个box,不同手机请修改数值
c_label1.style.update(width=345, padding_left=4)
c_label2.style.update(width=345, padding_left=4)
c_label3.style.update(width=345, padding_left=4)
submit_button.style.update(height=40, padding=1)
for i in range(1,26):
names['button_' + str(i)].style.update(width=68, height=68, padding=1)
return box
def play_sound_background(hzid,idsc):
thread = threading.Thread(target=bfsy, args=(hzid,idsc,))
thread.daemon = True
thread.start()
def play_sound_cuo(hzid,idsc):
thread = threading.Thread(target=bfsy_cuo, args=(hzid,idsc,))
thread.daemon = True
thread.start()
def main():
return toga.App("千纬认字(双人网络版)", "org.qwrzvs", startup=build)
if __name__ == "__main__":
main().main_loop()
需要的文件结构如下(我将所有资源上传到百度网盘吧):
sy1~sy3为生成的声音文件,qwrznew.db为数据库。
其他在程序里都有注释,sjcs选1,为将数据库放在手机可读写的data/data目录内,确保能够写入数据库。这里的qwrzvs.qwrzvs,需要探索在哪里。
可能跟这个有关:
编译apk文件参考python Gui编程工具详解:beeware - 沙漠渔溏、Beeware使用python开发安卓应用_android_屋顶上的蓝胖子-华为开发者空间
在briefcase create android之后,将
这个xml文件添加
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
二行。
然后briefcase build android,在命令行提示拿到apk文件,就可以安装。
二、服务器端代码。
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 22:39:02 2025
@author: Ybk
"""
from flask import Flask, jsonify, request
from pathlib import Path
import sqlite3
import datetime
db_filepath_vs = Path(__file__).joinpath("../vs.db").resolve()
app = Flask(__name__)
#千纬数学比赛的服务器端
@app.route('/vsdata', methods=['GET'])
def getvs_data():
response_data = {
'message': '连接成功'
}
return jsonify(response_data)
@app.route('/vsdatas', methods=['GET'])
def getvs_datas():
#连接数据库的代码,因为一直都用所以不关闭,等这个函数结束时关闭
conn = sqlite3.connect(db_filepath_vs, timeout=10, check_same_thread=False)
c = conn.cursor()
#默认反馈对手ip为空
xzng = 0
response_data = {
'aip': ''
}
#获取状态信息,初始zt为None,如果zt为0,那么设置为0,如果zt为-1,那么强制设置为0
zt = None
if request.args.get('zt') is None:
pass
else:
if request.args.get('zt') == '0':
cursor = c.execute("SELECT id FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
c.execute(f"update vs set zt = 0 where id ={vsid}")
conn.commit()
zt = 0
response_data = {
'aip': '',
'message': '可以重新游戏'
}
elif request.args.get('zt') == '-1':
#判断现在时间是否超过比赛时间的代码段
cursor = c.execute("SELECT id,bssj FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
biaosj = row[1]
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
miaocha = (datetime.datetime.strptime(sj, "%Y-%m-%d %H:%M:%S") - datetime.datetime.strptime(biaosj, "%Y-%m-%d %H:%M:%S")).total_seconds()
#如果现在时间超过比赛时间,那么就可以设置状态为0
if miaocha >= 0:
#删除匹配不成功的行,删除好像让服务器变卡,采取100次才执行一次删除
if vsid % 100 == 0:
c.execute("DELETE FROM vs where ip2 = '' and zt = 0;")
conn.commit()
#设置在建行为结束游戏状态
c.execute(f"update vs set zt = 0 where id ={vsid}")
conn.commit()
zt = -1
print(zt)
response_data = {
'aip': '',
'message': '可以重新游戏'
}
#设置zt代码结束
#获取选择年级
if request.args.get('xzng') is None:
xzng = 0
else:
xzng = int(request.args.get('xzng'))
# 尝试从 X-Forwarded-For 头部获取 IP 地址
ip_address = request.headers.get('X-Forwarded-For', None)
if ip_address:
# X-Forwarded-For 头部可能包含多个 IP 地址,用逗号分隔
ip_address = ip_address.split(',')
else:
# 如果没有 X-Forwarded-For 头部,则使用 remote_addr
ip_address = request.remote_addr
#获取ip地址结束
#如果没有反馈zt,分3种情况,从表中最后一行提取zt
if zt == None or zt == 0:
cursor = c.execute("SELECT zt FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
if row:
if row[0] == 0 and xzng > 0:
#如果zt为0,那么设置在建行,即zt=2
#判断现在时间是否超过比赛时间的代码段
cursor = c.execute("SELECT id,bssj FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
biaosj = row[1]
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
miaocha = (datetime.datetime.strptime(sj, "%Y-%m-%d %H:%M:%S") - datetime.datetime.strptime(biaosj, "%Y-%m-%d %H:%M:%S")).total_seconds()
#如果现在时间超过比赛时间,那么就可以插入在建
if miaocha >= 0:
insert_query = "INSERT INTO vs(zt,sj,ip1,ip2,rid1,rid2,xzng1,xzng2,bssj) VALUES(2,?,?,'',0,0,?,0,?);"
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
#这里设置比赛时间为当前时间加6秒,即6秒的匹配时间
bssj = now + datetime.timedelta(seconds=6)
bssjstr = bssj.strftime("%Y-%m-%d %H:%M:%S")
insert_data = (sj,ip_address,xzng,bssjstr)
c.execute(insert_query,insert_data)
conn.commit()
response_data = {
'bssj': bssjstr,
'aip': '',
'rid': 0
}
elif row[0] == 1:
#如果zt=1,即在比赛中
if request.args.get('rid') is None:
pass
else:
rid = int(request.args.get('rid'))
if rid < 25:
cursor = c.execute("SELECT id,ip1,ip2,rid1,rid2,bssj FROM vs where zt = 1 order by id desc limit 0,1")
else:
cursor = c.execute("SELECT id,ip1,ip2,rid1,rid2,bssj FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
ip1 = row[1]
ip2 = row[2]
rid1 = row[3]
rid2 = row[4]
bssjstr = row[5]
aip = ''
if ip1 == ip_address and rid < 26:
c.execute(f"update vs set rid1 = '{rid}' where id ={vsid}")
conn.commit()
rid = rid2
aip = ip2
elif ip2 == ip_address and rid < 26:
c.execute(f"update vs set rid2 = '{rid}' where id ={vsid}")
conn.commit()
rid = rid1 #反馈对方的rid,判断是否胜利
aip = ip1
response_data = {
'rid': rid,
'zt': 1,
'bssj': bssjstr,
'aip': aip
}
# 两个rid相加为50,即全部题做好,设置这个在建为0
if (rid1 + rid2) >= 49:
c.execute(f"update vs set zt = 0 where id ={vsid}")
conn.commit()
elif row[0] == 2:
#判断时间是否过期
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
#判断ip是否相同
cursor = c.execute("SELECT id,ip1,ip2,bssj FROM vs where zt = 2 order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
ip1 = row[1]
ip2 = row[2]
bssjstr = row[3]
miaocha = (datetime.datetime.strptime(bssjstr, "%Y-%m-%d %H:%M:%S") - datetime.datetime.strptime(sj, "%Y-%m-%d %H:%M:%S")).total_seconds()
if miaocha > 0 and miaocha < 6:
if ip1 != ip_address and ip2 == '':
c.execute(f"update vs set zt = 1,ip2 = '{ip_address}',xzng2 = {xzng} where id ={vsid}")
conn.commit()
response_data = {
'bssj': bssjstr,
'aip': ip1,
'rid': 0
}
else:
#时间过期了,设置状态为0
c.execute(f"update vs set zt = 0 where id ={vsid}")
conn.commit()
#再重新在建
insert_query = "INSERT INTO vs(zt,sj,ip1,ip2,rid1,rid2,xzng1,xzng2,bssj) VALUES(2,?,?,'',0,0,?,0,?);"
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
bssj = now + datetime.timedelta(seconds=5)
bssjstr = bssj.strftime("%Y-%m-%d %H:%M:%S")
insert_data = (sj,ip_address,xzng,bssjstr)
c.execute(insert_query,insert_data)
conn.commit()
response_data = {
'bssj': bssjstr,
'aip': '',
'rid': 0
}
else:
#判断现在时间是否超过比赛时间的代码段
cursor = c.execute("SELECT id,bssj FROM vs order by id desc limit 0,1")
row = cursor.fetchone()
vsid = row[0]
biaosj = row[1]
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
miaocha = (datetime.datetime.strptime(sj, "%Y-%m-%d %H:%M:%S") - datetime.datetime.strptime(biaosj, "%Y-%m-%d %H:%M:%S")).total_seconds()
#如果现在时间超过比赛时间,那么就可以插入在建
if miaocha >= 0:
insert_query = "INSERT INTO vs(zt,sj,ip1,ip2,rid1,rid2,xzng1,xzng2,bssj) VALUES(2,?,?,'',0,0,?,0,?);"
now = datetime.datetime.now()
sj = now.strftime("%Y-%m-%d %H:%M:%S")
bssj = now + datetime.timedelta(seconds=5)
bssjstr = bssj.strftime("%Y-%m-%d %H:%M:%S")
insert_data = (sj,ip_address,xzng,bssjstr)
c.execute(insert_query,insert_data)
conn.commit()
response_data = {
'bssj': bssjstr,
'aip': '',
'rid': 0
}
# print(ip_address)
c.close()
conn.close()
return jsonify(response_data)
if __name__ == '__main__':
app.run(host='192.168.1.140')
三、服务器端数据库。
四、运行效果:
电脑版:
说明:需要2部手机才可以玩。下一步改进,可以考虑用多线程进行网络通信,目前有一点点卡,但是还不会影响游戏体验。
在qpython安装flask,用手机热点就可以连接:
有时候不知道app.run(host='192.168.1.140')的ip,那么要查看连接热点的手机的网络:
里面的网关地址就是这个ip地址,改成这个就是服务器地址,可以玩了。
通过百度网盘分享的文件:vs.db,src.zip,双人网络认字.apk等3个文件
链接:https://pan.baidu.com/s/1UWu_gxiyP7fvLXwEhhcVSQ?pwd=8888
提取码:8888
src.zip为原代码,vs.db为服务器数据库,另外一个是安卓程序。