前言
黄金点游戏的进一步效果,和小组成员讨论本来打算采用强化学习的方法,并已在上篇博客中介绍了已经学习的强化学习方法,但是在进一步深入了解后,发现若要使用强化学习或者其它神经网络学习算法存在一个很严重的问题,即缺少训练集。若单纯靠对算法的改进以及借鉴网上所拥有的对这种黄金点游戏的趋势可能介绍,反而就有些背离强化学习的的初衷。所以在反复斟酌和讨论后,小组成员达成统一意见,决定改变方向,将黄金点游戏做成一个能够联机的游戏,以下是关于该游戏联网的介绍
游戏规则
黄金点游戏规则:
N个同学(N通常大于10),每人写一个0~100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到G值。提交的数字最靠近G(取绝对值)的同学得到N分,离G最远的同学得到-2分,其他同学得0分。
收获与感悟
这次实验所花的时间是在这几次实验中最多的,不仅仅是花费了时间精力去学习强化学习算法,同时也花了更大量的时间去了解关于网络这一块的原理和实现。不过一分耕耘一分收获,这次的收获是前所未有的,尤其是在网络连接这块,结合这学期正在学习的计算机网络课程,进一步加深了包括TCP在内的一些常见的联网协议的了解。因为亲自动手落实实践,对联网中的细节处理有了更深入的了解。
二、使用步骤
1.play_online.py
1.1引用包
import wx
import client
import server
1.2 online类
代码如下:
class online(wx.Frame):
"""We simple derive a new class of Frame"""
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(400, 760))
# self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE,)
self.Show(True)
self.CreateStatusBar() # 创建窗口底部的状态栏
panel = wx.Panel(self)
panel.PicShow = wx.StaticBitmap(parent=panel, pos=(0,0), size=(390, 650))
img = wx.Image('./40)CGAI{$IA))N2W%}TGBQ7.png', wx.BITMAP_TYPE_ANY )
panel.PicShow.SetBitmap(wx.BitmapFromImage(img))
panel.button_client = wx.Button(panel.PicShow, -1, "if Client", pos=(40, 20))
panel.Bind(wx.EVT_BUTTON, self.on_client, panel.button_client)
panel.button_client.SetDefault()
panel.button_server = wx.Button(panel.PicShow, -1, "if Server", pos=(40, 80))
panel.Bind(wx.EVT_BUTTON, self.on_server, panel.button_server)
panel.button_server.SetDefault()
def on_client(self, e):
HOST = wx.GetTextFromUser("puts server ip", caption="IP", default_value="",
parent=None) # 还没有判定ip合法
if_first_round = 1
while True:
if_continue = client.open_client(HOST,self,if_first_round)
if_first_round = 0
if if_continue == 0:
break
def on_server(self, e):
num = wx.GetNumberFromUser('参与人数', '请输入人数(最多二十人,最少两人)', 'New Game', 2, 2, 20)
server.open_server(self, num)
创建了一个多人游戏窗口,包含选择Client和server的按钮
2. Client.py
2.1引用包
import wx
from socket import *
2.2 get_name函数
代码如下:
def get_name():
person_name = wx.GetTextFromUser("puts your name", caption="Name", default_value="",
parent=None)
return person_name
创建在首轮游戏中用户输入用户名的窗口
2.3 get_valid_num函数
代码如下:
def get_valid_num(self,player_name):
while (1):
person_num = wx.GetPasswordFromUser("Player " + player_name+ " puts your number",
caption="Number", default_value="",
parent=None)
try:
person_num = int(person_num)
if (person_num > 0 and person_num < 100):
break
else:
dlg = wx.MessageDialog(self, "Number range error!", "error!", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
except Exception as e:
dlg = wx.MessageDialog(self, str(e), "error!", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
return person_num
创建在游戏中用户输入合法数字的窗口
2.3 open_client函数
代码如下:
def open_client(HOST,self,if_first_round):
# HOST = '127.0.0.1' #服务器ip地址,等价于localhost
# HOST = '10.132.15.141'
PORT = 21567 #通信端口号
BUFSIZ = 1024 #接收数据缓冲大小
ADDR = (HOST, PORT)
try:
tcpCliSock = socket(AF_INET, SOCK_STREAM) #创建客户端套接字
tcpCliSock.connect(ADDR) #发起TCP连接
while True:
# data = input('> ') #接收用户输入
if if_first_round == 1:
global person_name
person_name = get_name()
person_num = get_valid_num(self,person_name)
tcpCliSock.send(bytes(person_name, 'utf-8'))
data = tcpCliSock.recv(BUFSIZ)
tcpCliSock.send(bytes(str(person_num), 'utf-8')) #客户端发送消息,必须发送字节数组
else:
person_num = get_valid_num(self,person_name)
tcpCliSock.send(bytes(str(person_num), 'utf-8')) #客户端发送消息,必须发送字节数组
data = tcpCliSock.recv(BUFSIZ) #接收发送成功消息
if not data : #如果接收服务器信息失败,或没有消息回应
break
# print(data.decode('utf-8')) #打印回应消息,或者str(data,"utf-8")
data = data.decode('utf-8')
dlg = wx.MessageDialog(self, data, "wait", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
tcpSerSock = socket(AF_INET, SOCK_STREAM) #创建TCP服务器套接字
tcpSerSock.bind(('', 21567)) #套接字与地址绑定
tcpSerSock.listen(5) #监听连接,同时连接请求的最大数目
while True:
print('等待客户端的连接...')
tcpCliSock, addr = tcpSerSock.accept() #接收客户端连接请求
print('取得连接:', addr)
score = tcpCliSock.recv(BUFSIZ)
score = score.decode('utf-8')
break
dlg = wx.MessageDialog(self, score, "Game Result", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
# 还差返回结果
tcpCliSock.send(bytes("nothing!!!", 'utf-8'))
showmessage = tcpCliSock.recv(BUFSIZ)
showmessage = showmessage.decode('utf-8')
dlg = wx.MessageDialog(self, showmessage, "if continue", wx.OK) # show continue
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
tcpCliSock.send(bytes("nothing!!!", 'utf-8'))
if int(showmessage[6]) == 0:
h_score = tcpCliSock.recv(BUFSIZ)
h_score = h_score.decode('utf-8') # h_score
dlg = wx.MessageDialog(self, h_score, "final historical score", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
break
tcpCliSock.close() #关闭客户端socket
except Exception as e:
dlg = wx.MessageDialog(self, str(e), "error!", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
return int(showmessage[6])
与server取得连接
3. Server.py
3.1引用包
from socket import *
import wx
import numpy as np
import threading
3.2 加锁
lock=threading.Lock()
多线程防止同时修改
3.3 person类
代码如下:
class person:
def __init__(self, name,num,ip):
self.name = name
self.num = num
self.ip = ip
self.score_list = []
self.score = 10 # 初始分数为10
def change_score(self, num):
self.score += num
self.score_list.append(self.score)
def show(self):
return (str(self.name) + ":" + str(self.score))
def get_name(self):
return self.name
def get_num(self):
return self.num
def get_ip(self):
return self.ip
def show_scorelist(self):
sc = ""
for i in self.score_list:
sc += str(i) + " "
return (str(self.name) + ":" + sc)
def set_num(self,num):
self.num = num
实例化玩家
3.4 get_name函数
代码如下:
def get_name():
person_name = wx.GetTextFromUser("puts your name", caption="Name", default_value="",
parent=None)
return person_name
创建在游戏中用户输入用户名的窗口
3.5 get_valid_num函数
代码如下:
def get_valid_num(self,player_name):
while (1):
person_num = wx.GetPasswordFromUser("Player " + player_name+ " puts your number",
caption="Number", default_value="",
parent=None)
try:
person_num = int(person_num)
if (person_num > 0 and person_num < 100):
break
else:
dlg = wx.MessageDialog(self, "Number range error!", "error!", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
except Exception as e:
dlg = wx.MessageDialog(self, str(e), "error!", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
return person_num
创建在游戏中用户输入合法数字的窗口
3.6 Gold_Point函数
代码如下:
def Gold_Point(people, self):
num = []
fscore = []
for i in range(len(people)):
number = people[i].get_num()
num.append(number)
Gpoint = np.mean(num) * 0.618
num = [abs(x - Gpoint) for x in num] # 减去Gpoint
num = [x - min(num) for x in num] # 减去最小值
farther_num = max(num)
for i in range(len(num)):
if num[i] == 0:
people[i].change_score(len(people))
elif num[i] == farther_num:
people[i].change_score(-2)
else:
pass
fscore =''
for person in people:
fscore += person.show()+'\n'
return fscore
计算黄金点的算法函数
3.7 getData函数
代码如下:
def getData(tcpCliSock,BUFSIZ,people,num,self,flag,ip): # flag是否需要初始化人物,addr是输入的ip
global number
lock.acquire()
number = num[0]
if flag == 0:
pname = str(tcpCliSock.recv(BUFSIZ))[2:-1] # 连续接收指定字节的数据,接收到的是字节数组
tcpCliSock.send(
bytes('you have successfully send data, please wait for result', 'utf-8')) # 向客户端发送时间戳数据,必须发送
pnum = int(tcpCliSock.recv(BUFSIZ))
people.append(person(pname,pnum,ip))
else:
pnum = int(tcpCliSock.recv(BUFSIZ))
for p in people:
if p.get_ip() == ip:
p.set_num(pnum)
break
print(people[1].get_name())
print(people[1].get_num())
number -=1
print(number)
if number == 0:
print(number)
#tcpCliSock.send('[%s] %s' % (bytes(ctime(), 'utf-8'), data))
tcpCliSock.send(bytes('you have successfully send data, please wait for result', 'utf-8')) #向客户端发送时间戳数据,必须发送字节数组
# if num <= 0:
# tcpCliSock.send(bytes('0', 'utf-8'))
num[0] = number
lock.release()
与客户端创建连接,并接受客户端传送的数据
3.8 open_sever函数
代码如下:
def open_server(self,number):
flag = 0 # 表示是否继续,如果是1就是继续的,0就是第一轮
HOST = '' #主机号为空白表示可以使用任何可用的地址。
PORT = 21567 #端口号
BUFSIZ = 1024 #接收数据缓冲大小
ADDR = (HOST, PORT)
tcpSerSock = socket(AF_INET, SOCK_STREAM) #创建TCP服务器套接字
tcpSerSock.bind(ADDR) #套接字与地址绑定
tcpSerSock.listen(20) #监听连接,同时连接请求的最大数目
while True:
num = [number]
if flag == 0:
global person_name
person_name = get_name()
person_num = get_valid_num(self, person_name)
people = [person(person_name, person_num,'127.0.0.1')]
else:
person_num = get_valid_num(self, person_name)
for p in people:
if p.get_ip() == '127.0.0.1':
p.set_num(person_num)
break
num[0] -= 1
try:
address = []
while num[0] >0:
print('等待客户端的连接...')
tcpCliSock, addr = tcpSerSock.accept() # 接收客户端连接请求
print('取得连接:', addr)
address.append(addr[0])
#lock可能存在问题,不能解决多用户同时输入的情况
getData(tcpCliSock,BUFSIZ,people,num,self,flag,addr[0])
print("hello")
if num[0] <= 0:
print("stop!")
print(address)
score = Gold_Point(people, self)
show_total_score(score, self)
# 再开始connet
continue_flag = wx.GetNumberFromUser('是否继续游戏', '1代表继续游戏,0代表停止游戏', 'New Game?', 0, 0, 1)
if continue_flag == 1:
flag = 1
else:
h_score = show_historical_score(people, self)
for addre in address:
tcpCliSock = socket(AF_INET, SOCK_STREAM) # 创建客户端套接字
tcpCliSock.connect((addre,21567))
tcpCliSock.send(bytes(str(score),"utf-8"))#) # 向客户端发送时间戳数据,必须发送
tcpCliSock.recv(BUFSIZ)
tcpCliSock.send(bytes(str("庄家强制选择" + str(continue_flag) + ",1代表继续,0代表停止,如果为1,请继续选择client链接"),
"utf-8")) # ) # 向客户端发送时间戳数据,必须发送
# tcpCliSock.recv(BUFSIZ)
# tcpCliSock.send(bytes(str(h_score), "utf-8")) # ) # 向客户端发送时间戳数据,必须发送
if continue_flag == 0:
tcpCliSock.recv(BUFSIZ)
tcpCliSock.send(bytes(str(h_score), "utf-8")) # ) # 向客户端发送时间戳数据,必须发送
break
tcpCliSock.close() #关闭与客户端的连接
except Exception as e:
print(e)
tcpSerSock.close() #关闭服务器socket
创建服务器,并与客户端取得连接,向客户端发送数据
3.9 show_total_score函数
代码如下:
def show_total_score(score, self):
dlg = wx.MessageDialog(self, score, "Game Result", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它
展示该轮的分数
3.10 show_historical_score函数
代码如下:
def show_historical_score(people,self):
sc = ""
for i in people:
sc += i.show_scorelist() + "\n"
dlg = wx.MessageDialog(self, sc, "Game Result", wx.OK) # 创建一个对话框,有一个ok的按钮
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 完成后,销毁它。
return sc
展示历史上每一轮的分数
三、结果展示
界面展示
初始界面
其中Continue是在单机模式下的继续游戏按钮,如果实在联机模式下,则有单独的弹窗进行提示选择;而About和Exit的效果展示已在之前的博客有提及,且并未改变,这里变不再赘述
点击Start NewGame
若这里选择单机模式,就会进入个人单机人机对战模式,目前这里的AI算法采用的和以前一样是随机数,为之后扩展神经网络算法提供了接口,因为单机时的具体流程在之前的博客已介绍,这里不再赘述
点击多人游戏
这里选择是作为服务器还是客户端
到此每台主机无论是作为服务器,还是客户端都会经历的操作流程介绍完毕,接下来将进行关于多人游戏时分别作为客户端和服务器的展示
联网游戏展示
客户端展示
客户端首先输入目标服务器的IP地址,
若加入失败会返回如图的操作
输入正确IP
成功则弹出输入名字和数字的弹窗,并返回结果提醒
服务器计算完毕,返回结果,客户端弹窗展示结果
此时客户端玩家决定是否继续游戏
如果所有玩家都继续游戏则继续
当客户端玩家继续上次的用户名进行游玩,输入数字,确定后返回是否送达结果
服务器计算完毕,返回结果
继续选择,若有玩家选0
此时有人选择0,结束游戏
客户端接收并展示多轮以后中的所有结果
服务器展示
由服务器端确定参与人数,这步操作需要在最前面完成
服务器端输入自己的名字,这之后的操作与客户端的操作没有顺序要求
服务器端输入自己的数字
此时在等待客户端连接
当客户完成输入时会,服务器端会自动计算并弹出结果
由服务器端确认是否继续新一轮游戏,1为继续,0为推出
输入1选择继续
继续上一次输入的用户名,输入新一轮的数字
当对方也完成输入后,自动计算并弹出结果
庄家继续选择,选择0结束
有玩家拒绝结束游戏返回
最后自动弹出几轮的结果
总结
从工作量的角度,这次实验的工作量甚至不比前几次相加起来低。同时,由于开始编写代码项目时的不规范,结构框架不清晰,导致了徒增了很多的工作量。因此,在后面编写新的py文件时,尤其是在将联网功能和之前写的黄金点功能进行连接时,对整个软件的代码框架结构进行了较大的改变,之前存在很多功能混合,接口封闭等现象,这些都变成了新的功能拓展时的阻碍。虽说困难重重,花了大量时间和精力,但是经过修改后的软件项目不仅仅是实现了功能上的扩展,更重要的是实现了软件的一次重构,更方便以后功能的拓展,使软件的架构更加科学完善