接口声明
/reg
GET:
输出一个注册表单,其中
id:用户名
pd:密码
POST:
接收一个表单{id="",pd=""},返回:
0:注册成功
-1:注册失败
/attend
GET:
输出一个游戏参与表单,其中
id:用户名
pd:密码
num:1-100之间的实数,表示所要提交的数字
POST:
接受一个表单(id="",pd="",num=""),返回:
-1:用户名不存在
-2:密码错误
-3:数字范围出错(必须在1-100之间)
-4:游戏结束
left_time+","+now_turn+","+success?:
left_time:表示下一轮多少秒后开始
now_turn:当前轮数
success?:1表示提交成功,0表示提交失败(服务器锁中,正在计算)
/result
GET:
返回n行,倒序的golden_number
/
GET:
返回综合信息榜
设计说明
这次服务器是由forwil同学编写的。
游戏一开始(第0轮)有20s的注册时间,这段时间内必须注册好用户,注册的用户提交{用户名,密码}到/reg,服务器返回0表示提交成功,返回-1表示用户名重复了。这保证了一个用户名只能存在一个。
每次用户往/attend提交自己的 {用户名、密码、数}时,服务器返回三个信息:下一轮比赛开始时间time,当前提交的轮数turn,当前提交是否成功succ。通过后两个信息可以得知 自己的提交结果。而time预示了下一轮比赛的开始时间,客户端可以sleep(time)再继续提交,所以说这样客户端很容易实现自动游戏。
这就意味着,客户端和服务端之间不“等待”,服务端不等待所有用户提交后才进行游戏。所以游戏时间是均匀的(设定为1.2s一次),为了保证能计算出游戏结果,在游戏的最后0.2s里不允许用户再进行提交了。
为了让客户端程序能得到更多可供AI调用的信息,我们在/result里返回了历史的Golden number,来让客户端“学习”golden number分布的模式。
程序主体采用Python编写,http请求处理使用webpy框架实现,主要对每个url实现了GET/POST相应。
在综合信息榜(/)上,为了实现更好的信息展示效果,我们用了canvas元素来绘制折线图,加上Ajax通信手段,来实现异步刷新golden number 变化曲线。
主程序分两类线程,一类线程用来处理http请求,这方面的线程调度由webpy自动处理。另一个线程是游戏结果处理线程。两类线程之间通过全局变量来通信。
我主要写了客户端,根据以上接口进行注册和提交,并尝试了40个线程同时提交,效果还可以。但由于ruby的多线程是用户级的 ,调度上可能稍慢,如果分开多个进程效果更好。
提交策略参考了邹欣老师的一篇关于黄金点游戏的文章,根据一般情形下 黄金点游戏的趋势来提交。
为了能让服务器能在一秒内完成比赛,我们在设计接口的时候进行了一些简化。 游戏开始后只有两个请求,并且通过返回距下一次游戏开始的时间来减少请求。 后来forwil同学又简化了数据库操作,使数据先暂存,直到游戏结束再进行存储。这样大大减少 了服务器的负担,基本上能一秒进行一次游戏。
回答问题
你对于这个系统的服务器和接口是如何设计的? 应该采取哪些设计让游戏能顺利完成? 写出具体的接口。
接口上边已经说了。
你和你的同伴分工负责, 设计出服务器应该有几个功能模块, 这些功能模块之间的关系 (用 UML 或其它图例来表示)。
功能模块主要分http请求处理线程和游戏处理线程。 模块之间通过全局变量来通信。简单的说就是客户端请求提交数字,服务端接受并返回下一次游戏开始时间,客户端sleep相应时间避免无意义的提交。
写出每个模块功能的伪代码,要能做到让另一个同学能看到这些伪代码,就能明确实现的要求并马上开始实现。
方法很简单,上面一句话就能明白了。
我们的课程有 60 名学生 (60 个客户程序),如何能设计服务器程序和交互的接口让它能在 1 秒钟之内就完成一轮比赛?
可以,只需要加个随机随机化让用户分散在0.5秒里均匀访问就行了。当然我们不能假设用户的行为,但可以规定。
程序代码
1 import web 2 import string 3 import time 4 import thread 5 from web import form 6 import data 7 8 urls = ( 9 '/reg','reg', 10 '/attend','attend', 11 '/','board', 12 '/result','result', 13 '/resulthtml','resulthtml', 14 '/scorehtml','scorehtml', 15 '/infohtml','infohtml', 16 '/turnhtml','turnhtml', 17 '/totalboard','totalboard' 18 ) 19 20 render = web.template.render('templates/') 21 app = web.application(urls, globals()) 22 23 regform = form.Form( 24 form.Textbox("id", 25 form.notnull), 26 form.Password("pd", 27 form.notnull) 28 ) 29 30 attendform = form.Form( 31 form.Textbox("id", 32 form.notnull), 33 form.Password("pd", 34 form.notnull), 35 form.Textbox("num", 36 form.regexp('\d+', 'Must be a digit')), 37 #form.Validator('Must in 1-100', lambda x: 100>=string.atof(x) >=1)), 38 ) 39 40 41 class turnhtml: 42 def GET(self): 43 return data.nowturn 44 45 class infohtml: 46 def GET(self): 47 if data.gameover==1: 48 return "<b>Game is over!</b>" 49 else: 50 return "LEFT "+ str(data.TURNTIME-(time.time()-data.nowstart)) + "s TO SUBMIT YOUR NUMBER" 51 52 class result: 53 def GET(self): 54 s = "" 55 for i in range(len(data.alluped)-1,-1,-1): 56 s += str(data.alluped[i]['result']) + '\n' 57 return s 58 59 class resulthtml: 60 def GET(self): 61 s = "<table border='1'><tr><td>Round:</td>" 62 l = len(data.alluped) 63 for i in range(l-1,max(l-12,-1),-1): 64 s += "<td>"+str(i)+"</td>" 65 s +="</tr><td></td>" 66 for i in range(l-1,max(l-12,-1),-1): 67 s += "<td>"+str(data.alluped[i]['result'])+"</td>" 68 s += "</tr></table>" 69 return s 70 71 class scorehtml: 72 def GET(self): 73 return render.scorehtml(data.users,data.alluped) 74 75 class totalboard: 76 def GET(self): 77 return render.totalboard(data.users,data.alluped) 78 79 class reg: 80 def GET(self): 81 form = regform() 82 return render.reg(form) 83 def POST(self): 84 form = regform() 85 if not form.validates(): 86 return render.reg(form) 87 else: 88 i = web.input() 89 myvar = dict(id = i.id) 90 result = list(data.db.select('users',myvar,where = "id = $id")) 91 if result==[]: 92 data.db.insert('users',id = i.id,pd = i.pd) 93 data.users[i.id]=0.0 94 return 0 95 else: 96 return -1 97 98 class attend: 99 def GET(self): 100 form = attendform() 101 return render.attend(form) 102 def POST(self): 103 form = attendform() 104 if not form.validates(): 105 return render.attend(form) 106 else: 107 i = web.input() 108 i.num = string.atof(i.num) 109 result = list(data.db.select('users',where = 'id = "%s"'%(i.id))) 110 if result==[]: 111 return -1 112 if result[0]['pd']!=i.pd: 113 return -2 114 if 100<i.num or i.num<1: 115 return -3 116 if data.gameover == 1: 117 return -4 118 a = data.nowuped.get(i.id) 119 t = data.TURNTIME-(time.time()-data.nowstart) 120 if a==None and t>=data.DEAD: 121 data.nown += 1 122 data.nowtot += i.num 123 data.nowuped[i.id] = dict(num = i.num,deltscore = 0) 124 return "%f,%d,%d"%(data.TURNTIME-(time.time()-data.nowstart),data.nowturn,a==None and t>=data.DEAD) 125 126 class board: 127 def GET(self): 128 t = data.TURNTIME-(time.time()-data.nowstart) 129 #data.alluped.reverse() 130 a = render.board(data.DEAD,data.TURNTIME,t>=data.DEAD,data.nowturn,t,data.alluped,data.users,data.TURNTIME) 131 #data.alluped.reverse() 132 return a 133 def dealwinlost(uped,users,avgn,nowturn): 134 minv = 101 135 maxv = 0 136 winner = 0 137 for i in uped: 138 delt = abs(uped[i]['num']-avgn) 139 if delt>maxv: 140 maxv = delt 141 if delt<minv: 142 minv = delt 143 winner = i 144 uped[winner]['deltscore'] = 10 145 for i in uped: 146 if i==winner: 147 continue 148 delt = abs(uped[i]['num']-avgn) 149 if delt==maxv: 150 uped[i]['deltscore'] = -1 151 a = list(data.db.select('users')) 152 for i in a: 153 if i['id'] not in uped and i['id']!=winner: 154 uped[i['id']] = dict(num = 0.0,deltscore = -5) 155 for i in a: 156 users[i['id']] += uped[i['id']]['deltscore'] 157 return winner 158 159 def gameinit(): 160 a = list(data.db.select('users')) 161 for i in a: 162 data.users[i['id']]=0.0 163 164 def roundinit(): 165 data.nowuped = {} 166 data.nown = 0 167 data.nowtot = 0 168 169 def round(): 170 data.nowstart = time.time() 171 time.sleep(data.TURNTIME-data.DEAD) 172 if (data.nown!=0): 173 avgn = data.nowtot/data.nown * 0.618 174 data.nowuped['winner'] = dealwinlost(data.nowuped,data.users,avgn,data.nowturn) 175 data.nowuped['result'] = avgn 176 data.nowuped['turn'] = data.nowturn 177 data.alluped.append(data.nowuped) 178 data.nowturn += 1 179 if data.nowturn == 101: 180 data.gameover = 1 181 t =data.TURNTIME-(time.time()-data.nowstart) 182 if t>0: 183 time.sleep(t) 184 185 def timer1s(): 186 gameinit() 187 while True: 188 roundinit() 189 round() 190 if data.gameover ==1: 191 break 192 thread.exit_thread() 193 194 if __name__ == "__main__": 195 thread.start_new_thread(timer1s,()) 196 app.run()
1 require "net/http" 2 params = Hash.new 3 params[:id] = ARGV[0] 4 params[:pd] = ARGV[1] 5 addr = "http://192.168.1.3" 6 uri = URI.parse(addr + "/reg") 7 res = Net::HTTP.post_form(uri, params) 8 9 def gen(last) 10 if last > 5 11 temp = last - rand * Math.exp( last / 5 ) 12 return 1 if temp < 1 13 temp 14 else 15 last + rand * Math.exp( last / 2 ) 16 end 17 end 18 last = 40 19 20 while true 21 params[:num] = gen(last) 22 uri = URI.parse(addr + "/attend") 23 res = Net::HTTP.post_form(uri, params) 24 str = res.body.split(/,/) 25 26 time = str[0].to_f 27 turn = str[1].to_i 28 succ = str[2].to_i 29 30 puts time 31 sleep(time) 32 33 uri = URI.parse(addr + "/result") 34 res = Net::HTTP.get(uri) 35 str = res.split(/\n/) 36 last = str[0].to_f 37 end