植物大战僵尸吧 关注:543,137贴子:4,945,804

【按键教程】用python写脚本 另附垫材24与变奏22的实现

只看楼主收藏回复

1楼致谢。
没有灯笼就没有一切。
感谢阿雪的键控EL教程 p/4133995972 。希望六月份能在两站地以内看到阿雪。
感谢炮帝的技术支持。
感谢冰冰做的撸炮数据贴,以及带着我入门带我飞。
写给太宗等被键控“复杂性”所烦恼的人。希望本帖能够切实降低键控的门槛。
本帖楼主到帖子发出时使用python长达一周,如帖子有问题还望不吝赐教。


1楼2017-02-25 20:14回复
    2楼摘要及简单Q&A。
    本帖是使用python3制作按键脚本的初级教程贴。本帖所涉及代码(包含若干“基础子函数”的集合,以及楼主写的示例)均放在https://github.com/ccxx2c2/pvz.py
    (按键精灵/vb称sub子函数, python等称function函数,java等称method方法,其实是一个东西)
    本帖所需要的前置工作:
    1.前往https://www.python.org/downloads/ 下载python3 最新版本。
    2.在https://sourceforge.net/projects/pywin32/files/pywin32 下载pywin32 最新版本。
    3.下载https://github.com/ccxx2c2/pvz.py 中的pvz.py,并且与你的脚本放在同一文件夹下。
    可选项:下载一个文本编辑器。python自带的解释器并不适合写代码。
    虽然电脑自带记事本也可以,但是楼主以自身经验推荐不要使用自带记事本。
    可以选择Notepad++, SublimeText, 或者PyCharm.
    楼主目前使用的是Notepad++。
    Q:已经有了按键精灵,为什么要使用Python写脚本?
    A:Python的脚本行数约是按键精灵的1/4~1/10.
    同时,Python可以在脚本中使用(假的)多线程。
    对多线程举例:单线程中要补个冰需要手算时间,往目标位置插入代码,而且要补几次插几次,算时间还非常费事。
    多线程时额外开一个“补冰线程”,在此线程中直接优雅地补冰, delay 5100, 循环即可。不用考虑各种时间矛盾。
    Q:对Python一无所知,如何使用Python写脚本?
    A:完成前置工作,
    看本帖,
    看一点python基础(到使用循环/定义函数就够了)
    或者参照本贴的例子,
    写一个最简单(8/10炮)的脚本,
    以这个脚本为模板疯狂改。
    Q:如果要在植吧发一个帖子,除了上面涉及的内容还需要什么?
    A:PVZ修改器1.8.7(由置顶下载,用来设置极限出怪组合),录像软件(个人使用lukool),一个视频网站的账号(似乎百度可以直传了?方法不明)。


    2楼2017-02-25 20:15
    回复
      3楼正文
      一、封装好的函数
      sleep(秒数x)
      同按键精灵中的 Delay xxx
      如名字,在x秒后进行下一个操作。
      这里的单位是秒。
      pvz.ChooseCard(第几行, 第几列, 是否是模仿者)
      在选卡页面选卡。
      第三个参数可以省略,默认为不是。
      如果选卡包含模仿者,需要把鼠标放在模仿者的卡片上才能选上它。
      示例:pvz.ChooseCard(2, 7) #会点击寒冰菇的位置
      pvz.ChooseCard(2, 7, True) #会点击复制寒冰菇
      pvz.LetsRock()
      点击这个按钮。楼主一般不用,因为总会在选卡页面选错东西(
      pvz.preJudge(预判时间t, 是否是大波)
      预判。会等待直到新的一波开始前t分秒。
      第二个参数可以省略,默认为不是。
      大波中由于倒计时关系改变,不设置为True会出问题。
      示例:pvz.preJudge(95) #PSD所用的预判0.95秒。
      pvz.preJudge(55, True) #第10波常用的预判时间,因为该波僵尸出生点靠右而拖后了发射炮的时间。
      pvz.preJudge(150, True) #第20波,可以炮炸珊瑚的时间。
      pvz.Card(第n张卡)
      选择卡槽中的这张卡片。
      如果选择铲子,可以考虑pvz.Card(12) (虽然不推荐,但是楼主也是这么写的。。)
      pvz.Pnt((行, 列))
      点击地图上的该行该列中心点。行和列可以不是整数。
      行和列外面需要打括号,这是为了使用数组时的方便。推荐将第一个参数使用数组。
      对pvz.scene赋值'PE','DE','RE'以区分场景。默认是PE。
      示例:pvz.Pnt((6, 9)) #点击PE/FE的6路9列。
      pvz.Pnt((3, 1), 'RE') #点击RE/ME的3路1列。
      pvz.diancaiList = [(4,9),(5,9)]
      for cd in range(2):
      pvz.Pnt(pvz.diancaiList[cd],'DE') #分别点击DE/NE的第4行9列、第5行9列。
      pvz.Pao(行, 列)
      首先在pvz.paoList中自动选择一门炮,然后点击地图上的该行该列中心点。行和列可以不是整数。
      行和列外面不需要打括号,指定的是落点。
      对pvz.scene赋值'PE','DE','RE'以区分场景。默认是PE。
      对pvz.paoList赋值以确定炮的位置。
      示例:Pao(2,9) #炮击2路9列。实际上炮击2路8.5列的右侧范围基本一致。
      pvz.paoList = [(1,5),(6,5),(3,1),(4,1),(3,3),(4,3),(2,5),(5,5),(3,5),(4,5)]
      pvz.Pao(5,8)#选择一门炮,落在5路8列。如果这是第33次调用,那么会调用3路1-2列的炮。5路8列是超多炮PD中常用来分离炸小鬼和伴舞的位置。
      pvz.paoList = [(x,y) for x in range(1,7) for y in range(7,0,-2) if(not(y==7 and (x==3 or x==4)))]
      pvz.Pao(1,9) #选择变奏22炮阵型中当前的炮,落在1路9列。请注意:如果炮和落点在【同一个位置】(比如操作1路7-8列的炮打1路8列),那么点击有可能会无效。本例中按照(1,7),(1,5)...(2,1),(3,5)...(4,1),(5,7)...(6,1)的顺序枚举。如果需要,请如同示例2般将炮的位置全部列出。
      pvz.wave20()
      当拥有9门以上炮时,闭着眼睛过20波的方法。限PE。示例见视频。


      3楼2017-02-25 20:15
      回复
        二、脚本中需要些什么 以PE10炮为例
        #!/usr/bin/python #对mac/linux用户,可以点击脚本文件即运行
        # -*- coding: utf-8 -*- #声明文字编码,在脚本用汉字时必备,全用英文也推荐写上
        import threading,pvz #载入多线程模块与本帖模块
        from pvz import * #可选,能够省略大部分pvz.xxxxx的前半部分。以下默认【使用了】该语句。
        pvz.scene = 'PE' #标明场景,可选,默认PE。必须大写,只能在'PE' 'DE' 'RE'中选一个。
        pvz.paoList = [(1,5),(6,5),(3,1),(4,1),(3,3),(4,3),(2,5),(5,5),(3,5),(4,5)] #必须,标明炮的位置
        ChooseCard(2, 7, True) #imIceShroom
        ChooseCard(2, 7) #IceShroom
        ChooseCard(2, 8) #DoomShroom
        ChooseCard(5, 4) #CoffeeBean
        ChooseCard(1, 3) #CherryBomb
        ChooseCard(3, 5) #Jalapeno
        ChooseCard(3, 2) #Squash
        ChooseCard(3, 1) #LilyPad
        ChooseCard(4, 7) #Pumpkin
        ChooseCard(2, 1) #PuffShroom
        LetsRock()
        #可选,选卡。楼主推荐把他分开,单独存在一个.py里面。
        print('当前句柄 %s' % win32gui.GetWindowText(hwnd))
        #在命令行中输出当前句柄(脚本所运行的窗口)的名称。当前句柄默认选择鼠标所在位置的,选错了脚本会无反应。
        然后就是核心——炮要怎么打了。
        我们可以先这样写:
        for wave in range(1, 21): #range(x,y)得到的是[x,y)中的整数
        preJudge(0) #实际上是反应炸,用一下预判以提高精度
        sleep(5.5-3.73) #3.73是炮的飞行时间
        Pao(2,8.1) #炸伴舞
        Pao(5,8.1)
        就可以了。
        然后会遇到问题:第10波僵尸出生靠右,可能无法刷新
        于是改为:
        for wave in range(1, 21):
        preJudge(0, wave%10 == 0) #用条件判断符的真假控制参数
          sleep(5.5-3.73) #3.73是炮的飞行时间
          if wave % 10 != 0 : #第1~9,11~19波
            Pao(2,8.1) #python的层次是靠相同的空白符实现的
            Pao(5,8.1)
          else : #第10与20波
            Pao(2,8.5)
            Pao(5,8.5)
        #ps:在贴吧只有使用全角空格' '才不会被吃(实际上是多个空白符被html认为成一个),但是这个不能在python中用于缩进!请自行替换成空格,或者从2楼提及的示例地址复制代码。
        我们都用脚本了,当然要用炮炸珊瑚嘛,于是:
        for wave in range(1, 21):
        if wave == 20 :
        preJudge(150, True)
            Pao(4,6) #炮炸珊瑚
            sleep(1.5) #记得两个分支的最终结束时间要一致
        else :
        preJudge(0, wave%10 == 0)
          sleep(5.5-3.73)
          if wave % 10 != 0 : #下同,略
        .....
        还差什么?第9波打完之后我们实际上还要用炮,但是现在这程序第10波会用第9波后面的两炮,因而我们需要:
        for wave in range(1, 21):
        ...
        else:
        Pao(2,8.1)
        Pao(5,8.1)
        if(wave % 10 == 9):
            pvz.nowPao += 4
        pvz.nowPao是控制下一次打哪里的变量(这里pvz.必须带着),一般来说需要再用4炮,那就+4
        好,现在我们用36行写好了一个PE10炮需要的【全部内容】。如果说把选卡和中间的空行去掉,那么只需要22行。


        4楼2017-02-25 20:15
        回复
          三、使用这个脚本
          打开游戏,到选卡页面
          将pvz.py放到pe10p.py的同一文件夹下
          将光标放到模仿者上面,然后用回车运行脚本
          或者打开这个文件夹的命令行,输入python pe10p.py 回车。
          四、注意:
          脚本本质是模拟点击,并不能保证点炮和捡阳光没有冲突。
          比如说,在正要发炮的时候,点了一下阳光的旁边,那么有可能炮就落在点的那个位置而不是落点了。种冰/铲垫才。。的时候也都这样。
          由于游戏程序的缺陷,如果把鼠标放在一堆钱上,会导致游戏显!著!变!卡!然后你的延时就会各种出错。


          5楼2017-02-25 20:15
          回复
            板凳


            IP属地:北京来自Android客户端11楼2017-02-25 20:18
            回复
              窝惠赛高,窝惠一统千秋!


              IP属地:浙江12楼2017-02-25 20:20
              回复
                五、使用多线程(示例是在pe10炮中强行加一个核弹)
                首先写一个分线程的流程:
                def booming():
                  boomList = [(3,8),(3,9),(4,8),(4,9)]
                  print('boomthread going')
                  sleep(6+5)
                  for BoomCnt in range(5):
                    print('boomcnt:%s' % BoomCnt)
                    Card(8) #LilyPad
                    Pnt(boomList[BoomCnt % len(boomList)])
                    sleep(0.01)
                    Card(3) #DoomShroom
                    Pnt(boomList[BoomCnt % len(boomList)])
                    sleep(0.01)
                    Card(4) #CoffeeBean
                    Pnt(boomList[BoomCnt % len(boomList)])
                    sleep(50)
                然后声明一个变量:
                doomThread = threading.Thread(target=booming, args=(,), name='boomThread')#target=处写目标函数,args是函数的参数,name是卖萌的。。
                在需要的位置加上这样两句话:
                doomThread.start() #线程开始
                for wave in range(1, 21):
                  .....
                doomThread.join() #如果此时线程还未结束,则等待线程结束再进行主程序。我们多半不需要。
                就可以了。(PS:这个是随手写的示例,实际上在PE10炮中不管其他而50秒1核弹可能导致提前刷新(
                扔在了https://github.com/ccxx2c2/pvz.py/blob/master/scripts/pe10p.py
                示例视频:录酷录全屏有点问题,成了80%……还好后面的视频没问题
                视频来自:土豆

                PS:sys.argv记录了"python"后面传入的参数个数,视频中的选卡是设置了 if len(sys.argv) == 2 ... 来实现的


                15楼2017-02-25 20:21
                回复
                  六:垫才24炮
                  原帖:p/1688588122
                  脚本:https://github.com/ccxx2c2/pvz.py/blob/master/scripts/diancai24p.py
                  视频:
                  视频来自:土豆

                  核心思想:让所有红眼都在|BDC|BDC|BDC|中死亡,受2轮垫,3个B,1个D。
                  据撸炮贴 p/2647079397 ,巨人开敲78列最短896,加上一次扔小鬼(开始到脱离105,收手37),加上两次砸垫材一次砸炮(开敲到砸中134,收手74),得到最短需要在15.88秒落下D,即14.80秒落下B。
                  这实际上最晚只需要0.93预判即可处理,但出于尊重原贴起见,选择了1.55预判(这是全炸巨人的上限)。
                  垫撑杆的时机不能早于处理炮发射后0.81s,实战使用了这个数据,0.03s后挖去。
                  脚本示例中,将垫才与10波的樱桃使用了新线程来跑。
                  第10波选择了0.55预判。出于对原作的尊敬,加了樱桃改为PSD。(原作试图以此消除“中场延迟”)
                  阳光下降1400.


                  16楼2017-02-25 20:21
                  收起回复
                    七、变奏22炮
                    原帖:p/1628467712
                    脚本:https://github.com/ccxx2c2/pvz.py/blob/master/scripts/change22p.py
                    视频:
                    视频来自:土豆

                    核心思想:上半场不变,下半场变|IP+PD|PSD|为|IP+PD|BDC|。
                    此处应该有花花的change6的PE24炮发布链接,但是居然找不到??看来这阵型的出现也是一个历史的必然。。
                    来自花花的ch6首发:双冰十六炮 p/1059655283
                    找到的最早的ch6炸小鬼:RE十六炮 p/1249179381
                    第一个能在精品区找到的ch6的PE24炮:归海的手动 p/1293803461
                    普通的24炮:PSD|PD|PI
                    为什么要有PSD呢?因为PI这波减速了的巨人,拦截它们的小鬼和拦截正常速度的小鬼需要的时间不同啊!
                    所以在场上混杂减速未减速巨人的时候,非要拦截两次不可。。
                    不过反正你要上3炮了,直接把减速巨人轰死就好了。
                    于是就有了PSD。
                    ch6的16炮:|IP+P|PS|
                    这里的S是要和下一波炸冰车的P一起处理上一波减速中出现的巨人的。再让他来一次10s的一波怎么也撑不住啊。
                    ch6的24炮:|IP+PD|PSD|
                    上面的炸小鬼版。是不是合情合理。
                    ……好像并不是啊 by FI & ND
                    炮出在哪呢?
                    由于这里PD是激活炸,所以和后面的PSD时间间隔相当近——近到这里PD的D根本不需要分离,直接炸、让后面的炮负责炸这次的小鬼就可以了!
                    本22炮:|IP+PD|BDC|
                    PDBD部分,P炸白眼,延迟2.2后D炸白眼小鬼&红眼,延迟2.2后B炸红眼小鬼&新一波巨人(重要!),延迟1.08后分离炸没减速巨人的小鬼。
                    这样算,B这门炮是2.2+2.2-2=新一波2.4秒落地,减去3.73得知是预判1.33秒的炮。
                    然后垫而处理撑杆。
                    由于这里B也要炸小鬼了,所以在PE和DE只能用在下半场(否则3路漏了。。),在RE可以用在任意三行。
                    实战中选取0.2s生效的预判冰,1.2s垫撑杆,4.2+3.73=7.93s炸7.3列。
                    在具体实现上,第9波与第19波的收尾选择了3炮炸6路以期待在4路留下一两个慢速僵尸,不过本次视频中没有。
                    需要特别指出的是,ch6【必须】采用冰轮11.5s全程35s的时间,以防止小丑炸9列,详见 p/4996917593


                    17楼2017-02-25 20:21
                    收起回复
                      全文完。大家再见,有问题请恕暂不解答,我早饭还没吃呢。
                      15楼pe10炮那个示例刚才写的时候忘了把空格符替换掉了,@失控的指令 其实就是沙发(


                      18楼2017-02-25 20:23
                      回复
                        已收藏~


                        IP属地:吉林来自Android客户端19楼2017-02-25 20:36
                        回复
                          蛤…屁(p)眼(y)通(t)红(hon)…?学了一学期不知道能这么用…


                          IP属地:山东来自Android客户端20楼2017-02-25 20:39
                          收起回复
                            膜dalao(


                            IP属地:福建来自Android客户端21楼2017-02-25 20:46
                            回复
                              辣鸡丁学不会


                              IP属地:广东来自Android客户端22楼2017-02-25 20:48
                              回复