chbpku / pingpong.sessdsa Goto Github PK
View Code? Open in Web Editor NEW地空数算课期末作业漂移乒乓
地空数算课期末作业漂移乒乓
table.py: line 23
class Vector: # 矢量
def __init__(self, x, y=None):
if y is None:
self.x, self.y = x
else:
self.x, self.y = x, y
由@孙景南 同学发现
由于开局发球serve不属于回合内,所以不予记录中间过程,因此录像回放只会回放从球到达对面底线开始,到出界后几秒的画面,未展现中间触壁次数不符合要求的画面。
如下为一次发球失败实例:位置设在左上角,速度为3000
DIM:(-900000, 900000, 0, 1000000)
TMAX:800000
tick_step:1800
West:T_idiot
East:T_idiot
tick_total:0
winner:East
reason:invalid_bounce
log:
tick:0
side:
life:100000
pos:<900000,500000>
action:
bat:0
acc:0
run:0
op_side:
life:100000
pos:<-900000,500000>
action:
bat:0
acc:0
run:0
ball:
pos:<-900000,1000000>
velocity:<1000,3000>
tick:0
side:
life:100000
pos:<900000,500000>
action:
bat:0
acc:0
run:0
op_side:
life:100000
pos:<-900000,500000>
action:
bat:0
acc:0
run:0
ball:
pos:<900000,400000>
velocity:<1000,3000>
我们可以看出并未记录第一次发球过程
具体原因可能是GUI的球移动原理?(存疑)
player_action = player.play(TableData(self.tick, self.tick_step,dict_side, dict_op_side, dict_ball), player.datastore)
dict_op_side = {'position': copy.copy(op_player.pos), 'life': op_player.life, 'accelerate': op_player.action.acc, 'run_vector': op_player.action.run}
其中op_player.action.run既包含方向又包含距离
因为根本没有用,而且因为双方ds相同,这样处理两次有可能还会导致bug。
可以在双方的算法文件中增加函数
def modify(ds):
....
然后在对局结束,保存双方datastore之前调用双方的modify函数
这样方便在对战后将当盘数据加入datastore的历史数据中
class RacketAction: # 球拍动作
def __init__(self, tick, bat_vector, acc_vector, run_vector):
self.t0 = tick # tick时刻的动作,都是一维矢量,仅在y轴方向
self.bat = bat_vector # t0~t1迎球的动作矢量(移动方向及距离)
self.acc = acc_vector # t1触球加速矢量(加速的方向及速度)
self.run = run_vector # t1~t2跑位的动作矢量(移动方向及距离)
这是原来的RacketAction。
RacketAction是一个动作类,实际上玩家应该只需要返回三个数据,也就是后三个Vector对象。
而实际这个t0根本就没用上。这个参数出现在这里会带来不必要的干扰。
建议直接删除。
从小组内简单的程序的对战程序的效果来看,游戏参数需要调整。如果采取暴力打角的打法,直接打向对方的角落,实在是优势很大,没有给其他策略留下空间。建议将加速的代价大大增加,防止双方都重复持续暴力打角。另外可以减少跑位的代价,这样的话如果有优良的跑位策略,将能够在结果上体现出来。另外我个人觉得可以将生命值增加,将对战的时间延长一些,这样的话防止一方简单地由于运气或者由于先手优势而取胜,给暂时落后的但是实际对战能力更强的一方一些翻盘的机会。
DIM[2](y方向位置下限)与生命值,消耗系数等数据一同在table.py开头给出,应当认为这是一个能修改的参数。其余部分代码也确实没有将其默认为0,而是使用DIM得到范围。
这个问题早就应该提出来的,但是现在才比较明显。
建议所有T_*.py代码放在src目录下,所有非.py配置文件放在config目录下,图标文件放在res目录下,记录文件放在log目录下
不然现在的目录结构太乱了,我们也很不好测试。
根据道具出现的间隔和tickstep的关系,有的道具先手方有机会先抢到,有的道具后手有机会先抢到。
按已有参数,出现了连续4个道具一方有机会先抢,接着连续4个道具另一方有机会先抢的情况。
这似乎并不合适。
bat_distance = sign(self.action.bat) * min(abs(self.action.bat), self.get_velocity() * tick_step)
self.pos.y += bat_distance
以及
pos_y, velocity_y = player.serve(player.datastore) # 只提供y方向的位置和速度
是否有必要在Table.py中对参赛的函数的返回值取整,防止bug函数返回浮点数。
运行成功却没有在文件夹内生成记录
报错
D:\python3.61\python.exe E:/PYhomework/pingpong.sessdsa-master/show.py
Traceback (most recent call last):
File "E:/PYhomework/pingpong.sessdsa-master/show.py", line 204, in
main()
File "E:/PYhomework/pingpong.sessdsa-master/show.py", line 165, in main
t_passed = clock.tick()*m
TypeError: unsupported operand type(s) for *: 'int' and '_sre.SRE_Match'
Process finished with exit code 1
C:\Users\Cobalt-YangFan\AppData\Local\Programs\Python\Python35\python.exe C:/Users/Cobalt-YangFan/PycharmProjects/Final-Homework-SESSDSA/show.py
Traceback (most recent call last):
File "C:\Users\Cobalt-YangFan\AppData\Local\Programs\Python\Python35\lib\shelve.py", line 111, in __getitem__
value = self.cache[key]
KeyError: 'log'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:/Users/Cobalt-YangFan/PycharmProjects/Final-Homework-SESSDSA/show.py", line 190, in <module>
main()
File "C:/Users/Cobalt-YangFan/PycharmProjects/Final-Homework-SESSDSA/show.py", line 121, in main
log = readlog(logname)
File "C:/Users/Cobalt-YangFan/PycharmProjects/Final-Homework-SESSDSA/show.py", line 38, in readlog
log = d['log']
File "C:\Users\Cobalt-YangFan\AppData\Local\Programs\Python\Python35\lib\shelve.py", line 113, in __getitem__
f = BytesIO(self.dict[key.encode(self.keyencoding)])
File "C:\Users\Cobalt-YangFan\AppData\Local\Programs\Python\Python35\lib\dbm\dumb.py", line 141, in __getitem__
pos, siz = self._index[key] # may raise KeyError
KeyError: b'log'
Process finished with exit code 1
首先运行pingpong.py,然后运行show.py,报错如上。
GUI出现接近中心点也接不到的情况,也影响观赏性
假设在tn-1到tn之间因为某种原因游戏结束(时间耗尽,或者迎球方在tn时刻移动造成生命耗尽),不会保存tn时刻的记录,如果是因为生命耗尽引起,就无法知道迎球方的动作了。这种情况下可能tn-1时刻迎球的一方处于劣势,但因为tn时刻迎球方的行动导致胜负逆转,无法在记录上体现出来。
建议对最后一次log进行单独记录。
例如A+B-B 不等于 A
希望用类似abs(a-b)<0.000001的来判断相等
在任何一局对战中,传给play的op_side始终没有对方跑完位后的位置,我认为这不妥,不应该属于信息的不完全。我在击球的时候,确实不应该知道对方上次击球后的跑位,但是我应该知道上上次的对方击球的跑位,这是我在上次击完球之后,应该能够看见的。所以我认为在op_side中需要加入last_run_vector,来表示上上次对方跑位的状况,这个应该是策略的极其重要的一部分:对方的跑位策略。
table.py
第351行: self.cards中的道具是按照生成的时间顺序存放的
def deploy_card(self): # 放置道具到球桌上
...
self.cards.append(Card(card_info[0], card_info[1],
Position(random.randint(CARD_EXTENT[0], CARD_EXTENT[1]),
random.randint(CARD_EXTENT[2], CARD_EXTENT[3]))))
...
第147行: fly函数中检查是否获取道具是按照list_cards顺序检查
def fly(self, ticks, list_cards): # 球运动,更新位置,并返回触壁次数和路径经过的道具(元组)
# 判断card.pos,如果球经过的话,就返回count的同时返回所有经过的道具列表,并从list_cards中移除。
hit_cards = [card for card in list_cards if self.get_card(card)]
第381行: 调用fly时传入的是self.cards
count_bounce, hit_cards = self.ball.fly(self.tick_step, self.cards)
第387行: 路径经过的道具比较多的时候,由于有存放数量限制,最终存入的道具可能与遇到的先后顺序不匹配
# 将击中的道具加入道具箱
for card in hit_cards:
op_player.card_box.collect(card)
由于现在传参时用的是普通的copy,算法可以对所传对象所引用的对象进行修改(主要是cardbox、cards所引用的card),并且active_card传入时未使用copy,其参数也可以被修改。vector、card由于其字段是值类型因此可以用copy,但字段是引用类型的必须用deepcopy。
迎球和跑位的代价相同,完全可以不跑位,迎球的时候再移动,这样跑位就失去意义了,建议跑位消耗较少的生命值(需要预判),而迎球消耗较多的生命值。
嗯,希望能采纳一部分。
个人感觉变压器、隐身术形容十分贴切……加血包和掉血包也不错……
最后来一个zip文件
马车8限定主题道具包.zip
实际上对方的位置完全可以由上回合的自己的位置和击出球的速度算出来,因此隐身道具没有实际用途。
经过试验,将SPEED_FACTOR改到10能够很大程度地抑制打角打法。在原来的20的情形下,我们所写出的函数的策略与打角并无二致,但是将其改到10之后,能够轻松赢过打角打法。可能10并不是合适的参数,也许15会比较合适。总之游戏的参数还需修改。
elif self.velocity.y > 0: # 向上y+飞
# y方向的位置,考虑触壁反弹
bounce_ticks = (self.extent[3] - self.pos.y) / self.velocity.y
if bounce_ticks >= ticks: # 没有触壁
self.pos.y += self.velocity.y * ticks
return 0
else: # 至少1次触壁
# 计算后续触壁
ticks -= bounce_ticks
count, remain = divmod(ticks, ((self.extent[3] - self.extent[2]) / self.velocity.y))
if count % 2 == 0: # 偶数,则是速度改变方向
self.pos.y = self.extent[3] - remain * self.velocity.y
self.velocity.y = -self.velocity.y
else: # 奇数,速度方向不变
self.pos.y = self.extent[2] + remain * self.velocity.y
return count + 1
好像bounce_ticks可以取到浮点数,使ticks也取到浮点数,divmod第二个参数也可能是浮点数,所以remain和self.pos.y也会是浮点数。
from table import *
import pygame
from pygame.locals import *
import shelve
import sys
game_speed = 1
x, y = 18, 10
s_size = (1024, 600)
x_n = s_size[0] // 2 // 18
y_n = s_size[1] // 2 // 10
n = x_n if x_n < y_n else y_n
center = (s_size[0] / 2, s_size[1] / 2)
table = (
(center[0] - x / 2 * n, center[1] - y / 2 * n),
(center[0] + x / 2 * n, center[1] - y / 2 * n),
(center[0] + x / 2 * n, center[1] + y / 2 * n),
(center[0] - x / 2 * n, center[1] + y / 2 * n),)
def readlog(logname):
d = shelve.open(logname)
log = d['log']
d.close()
return log
def getdata(alog):
d = {}
d['ball_pos'] = alog.ball.pos
d['ball_v'] = alog.ball.velocity
d['tick'] = alog.tick
d['player'] = {}
d['player'][alog.side.side] = alog.side
d['player'][alog.op_side.side] = alog.op_side
return d
def pos_trans(oldpos):
pos_x = int((0.0 + oldpos.x / (DIM[1] - DIM[0])) * n * x + center[0])
pos_y = int((0.5 - oldpos.y / (DIM[3] - DIM[2])) * n * y + center[1])
return (pos_x, pos_y)
def draw_table(screen):
polygon_1 = (
(center[0] - x / 2 * n, center[1] - y / 2 * n),
(center[0] + x / 2 * n, center[1] - y / 2 * n),
(center[0] + x / 2 * n, center[1] - y / 2 * n - 10),
(center[0] - x / 2 * n, center[1] - y / 2 * n - 10),)
polygon_2 = (
(center[0] + x / 2 * n, center[1] + y / 2 * n),
(center[0] - x / 2 * n, center[1] + y / 2 * n),
(center[0] - x / 2 * n, center[1] + y / 2 * n + 10),
(center[0] + x / 2 * n, center[1] + y / 2 * n + 10),)
pygame.draw.polygon(screen, (0, 255, 0), polygon_1, 0)
pygame.draw.polygon(screen, (0, 255, 0), polygon_2, 0)
def draw_ball(screen, ball_pos):
pos = pos_trans(ball_pos)
pygame.draw.circle(screen, (0, 0, 0), pos, 8, 0)
def draw_player(screen, player):
if player.side == 'West':
color = (255, 0, 0)
poslist = ((player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0],
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] - 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0] - 10,
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] - 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0] - 10,
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] + 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0],
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] + 25))
else:
color = (0, 0, 255)
poslist = ((player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0],
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] - 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0] + 10,
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] - 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0] + 10,
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] + 25),
(player.pos.x / (DIM[1] - DIM[0]) * n * x + center[0],
-(player.pos.y / (DIM[3] - DIM[2]) - 0.5) * n * y + center[1] + 25))
pos = pos_trans(player.pos)
pygame.draw.polygon(screen, color, poslist, 0)
def draw_all(screen, ball_pos, player_1, player_2):
draw_table(screen)
draw_ball(screen, ball_pos)
draw_player(screen, player_1)
draw_player(screen, player_2)
def writeinfo(screen, player, font):
screen.blit(font.render('W', True, (0, 0, 0)), (table[0][0] - 24 - 100, table[0][1] - 100))
screen.blit(font.render('E', True, (0, 0, 0)), (table[1][0] + 100, table[1][1] - 100))
screen.blit(font.render(player['West'].name, True, (0, 0, 0)), (table[0][0] - 24 - 100 - 20, table[0][1] - 50))
screen.blit(font.render(player['East'].name, True, (0, 0, 0)), (table[1][0] + 100 - 20, table[1][1] - 50))
screen.blit(font.render(str(int(player['West'].life)), True, (0, 0, 0)), (table[0][0] - 24, table[0][1] - 100))
screen.blit(font.render(str(int(player['East'].life)), True, (0, 0, 0)), (table[1][0] - 80, table[1][1] - 100))
def main():
# 判断有无命令行参数
if len(sys.argv) == 2:
logname = sys.argv[1]
else:
# 这里对当前目录进行搜索,找到一个字节数不为0的dat文件
import os, re
file_list = os.listdir(os.getcwd())
# 编译正则表达式,寻找对应的文件名
r = re.compile(r'^[[EW].[A-Z]]T_[^-]+-VS-T_[^.]+.dat$')
# 首先保证是文件而不是目录,且不为空
namelist=[]#用来保存所有对战名称
print('请注意,本代码不支持未找到的报错,希望有人能改正\n')
for name in filter(lambda f: os.path.isfile(f) and os.path.getsize(f) != 0, file_list):
m = r.match(name)
if m is not None:
# 不为空,则拿到了一个正确的文件
logname = name[:-4] # 去除.dat后缀
namelist.append(logname)
#else:
# 没找到,说明本目录下没有这个测试文件
# raise NameError("No Test File in this directory.")
# 读出log
for i in range(len(namelist)):
print('第',i,'个',namelist[i])
ssssss=int(input('请输入你想看的对战的序号,从0开始,到%d结束\n' %(len(namelist)-1)))#序号
logname=namelist[ssssss]
log = readlog(logname)
# pygame初始化
pygame.init()
screen = pygame.display.set_mode(s_size)
font = pygame.font.SysFont("arial", 32)
clock = pygame.time.Clock()
# 读取两轮数据
d_current = getdata(log.pop(0))
player = d_current['player']
d_next = getdata(log.pop(0))
ball_pos = d_current['ball_pos']
ball_v = d_current['ball_v']
tick = d_current['tick']
next_tick = d_next['tick']
clock.tick()
over = False
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
# 画画
screen.fill((255, 255, 255))
writeinfo(screen, player, font)
draw_all(screen, ball_pos, player['West'], player['East'])
t_passed = clock.tick() * game_speed
# 最后一次记录之后再走半回合
if over and tick > next_tick + 1800:
screen.blit(font.render('Game over', True, (0, 0, 0)), (center[0] - 50, center[1]))
t_passed = 0
# 时间流逝和球的移动
tick += t_passed
ball_pos.x += ball_v.x * t_passed
ball_pos.y += ball_v.y * t_passed
# 半回合后读取下一次记录
if not over and tick >= next_tick:
d_current = d_next
player = d_current['player']
ball_pos = d_current['ball_pos']
ball_v = d_current['ball_v']
tick = d_current['tick']
# 判断是否为最后一次记录
if len(log) > 1:
d_next = getdata(log.pop(0))
next_tick = d_next['tick']
else:
over = True
# 碰到上下墙壁时进行反弹
if ball_pos.y >= DIM[3]:
ball_pos.y = (DIM[3]) * 2 - ball_pos.y
ball_v.y = -ball_v.y
elif ball_pos.y <= DIM[2]:
ball_pos.y = (DIM[2]) * 2 - ball_pos.y
ball_v.y = -ball_v.y
# 更新画面
pygame.display.update()
if name == 'main':
main()
我在play里面做了一个累计变量ds['time'],然后每调用一次play就+1,结果发现,多次运行时,这个值从上一次比赛结束的地方开始累加了。
能不能设置成可以选择的呢?
在dict_op_side中增加‘name’的key,以便区分不同的对战者
可在名字下作出三行-400格式的输出,分别代表接球、加速、跑位所耗体力,便于调试,也有利于对局的观赏。@pkuzhd
card_stick should be card_tick.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.