Giter Site home page Giter Site logo

pingpong.sessdsa's People

Contributors

chbpku avatar cx-bool avatar nithouson avatar pkuzhd avatar planetarianpku avatar ribombalt avatar troubadour-hell avatar zzy546 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pingpong.sessdsa's Issues

self.x ,self.y =x

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的球移动原理?(存疑)

传给play的参数中包含了对方跑位的距离

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既包含方向又包含距离

关于RacketAction:Unused Parameter: t0

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根本就没用上。这个参数出现在这里会带来不必要的干扰。
建议直接删除。

调整游戏参数的建议

从小组内简单的程序的对战程序的效果来看,游戏参数需要调整。如果采取暴力打角的打法,直接打向对方的角落,实在是优势很大,没有给其他策略留下空间。建议将加速的代价大大增加,防止双方都重复持续暴力打角。另外可以减少跑位的代价,这样的话如果有优良的跑位策略,将能够在结果上体现出来。另外我个人觉得可以将生命值增加,将对战的时间延长一些,这样的话防止一方简单地由于运气或者由于先手优势而取胜,给暂时落后的但是实际对战能力更强的一方一些翻盘的机会。

建议:优化目录结构

这个问题早就应该提出来的,但是现在才比较明显。
建议所有T_*.py代码放在src目录下,所有非.py配置文件放在config目录下,图标文件放在res目录下,记录文件放在log目录下
不然现在的目录结构太乱了,我们也很不好测试。

关于道具出现的时机

根据道具出现的间隔和tickstep的关系,有的道具先手方有机会先抢到,有的道具后手有机会先抢到。
按已有参数,出现了连续4个道具一方有机会先抢,接着连续4个道具另一方有机会先抢的情况。
这似乎并不合适。

关于对serve,play函数的返回值的取整

    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函数返回浮点数。

最新版show.py无法使用

报错
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

【BUG】show.py运行报错

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,报错如上。

关于记录不完全的问题

假设在tn-1到tn之间因为某种原因游戏结束(时间耗尽,或者迎球方在tn时刻移动造成生命耗尽),不会保存tn时刻的记录,如果是因为生命耗尽引起,就无法知道迎球方的动作了。这种情况下可能tn-1时刻迎球的一方处于劣势,但因为tn时刻迎球方的行动导致胜负逆转,无法在记录上体现出来。
建议对最后一次log进行单独记录。

跑位信息的bug

在任何一局对战中,传给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的问题

由于现在传参时用的是普通的copy,算法可以对所传对象所引用的对象进行修改(主要是cardbox、cards所引用的card),并且active_card传入时未使用copy,其参数也可以被修改。vector、card由于其字段是值类型因此可以用copy,但字段是引用类型的必须用deepcopy。

关于迎球和跑位

迎球和跑位的代价相同,完全可以不跑位,迎球的时候再移动,这样跑位就失去意义了,建议跑位消耗较少的生命值(需要预判),而迎球消耗较多的生命值。

隐身卡的问题

实际上对方的位置完全可以由上回合的自己的位置和击出球的速度算出来,因此隐身道具没有实际用途。

关于旋转球道具影响游戏平衡的反馈

_20170520204201
老师我之前汇报过一次的……这个过于影响游戏平衡:

CARD_SPIN = 'SP'  # 旋转球:用于抵消被用道具方施加在球拍上的加速,使之正常反弹回来;param=0,增加的速度乘以parm
CARD_SPIN_PARAM = 0

个人感觉这个属于设计漏洞,拿到旋转球的玩家拥有必胜策略,而旋转球的出现是随机的,独立于代码水平之外的,私以为用这个来决定胜负和掷骰子决定胜负没有什么本质区别。

因此我建议,删除这个道具,或者削弱这个道具的功能,或者限定这个道具只能在残局(比如双方生命值均小于30%,有大量范围无法运动到达)时出现

建议老师再次修改参数

经过试验,将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也会是浮点数。

解决了show.py的播放问题,可以选择播放

show by pkuzhd

读取对局文件, 把对局可视化

如果带了命令行参数, 打开参数里的文件

否则根据文件中的文件名打开文件(第116行附近)

注: 需要和table放在同一个目录下

注: 只输入文件名, 不要带扩展名

例: '[W.T]T_idiot-VS-T_idiot'

不要在意里面的各种神秘数字

from table import *
import pygame
from pygame.locals import *
import shelve
import sys

这个参数用来调整时间流逝的速率

game_speed=1时, 一来回需要3.6秒

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),)

读取文件, 返回文件中的log类

def readlog(logname):
d = shelve.open(logname)
log = d['log']
d.close()
return log

把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

把球桌坐标转换成pygame屏幕的坐标

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()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.