i_wanna_be_the_guy吧 关注:62,620贴子:1,118,499

【i wanna】论1024<=y<2048时单次取消情况下ya绕路的可行性探究

只看楼主收藏回复


使用工具:
1.jtool farlands(方便调y坐标到给定的范围)
2.dev-cpp(别问为什么,问就是因为我不想再新建一个解决方案)
3.洛谷博客(写初稿用)


IP属地:湖南1楼2021-08-12 11:30回复
    零、简单的定义
    * 单次取消:对于每一段跳跃,kid在上升过程中最多只能触发一次“松开shift操作”(即纵向速度乘以0.45的“跳跃取消”操作)。也就是说,最小jc和仙人掌跳等需要触发两次以上的“松开shift”操作是不允许的。
    * ya(y-align):kid的y坐标中小数点后的部分。


    IP属地:湖南2楼2021-08-12 11:31
    回复
      一、前置知识


      IP属地:湖南3楼2021-08-12 11:32
      回复
        1.kid的跳跃机制。
        当shift按下时,会给予kid一个向上的速度,一段跳为-8.5像素/帧(以下简写为p/f),二段跳为-7p/f。
        并且,每一帧里重力都会给予kid一个向下的加速度,为-0.4p/f^2。
        当shift松开时,如果kid正处于上升状态,则他的纵向速度会乘以0.45。
        这些操作的顺序如下:


        IP属地:湖南4楼2021-08-12 11:33
        回复
          2.kid移动时触碰方块的机制。
          在引擎内部的实现上,如果以当前的速度运动后不会碰到方块的话,那么就继续运动;
          如果碰到了,则先后进行横向移动和纵向移动的判断:先沿着横向移动的方向一直移动,直到碰到方块;再沿着纵向移动的方向一直移动,直到碰到方块。
          那么,问题是:“沿着移动方向移动一段距离直到碰到方块”(即gm中move_contact_solid()函数)这一操作是怎样实现的。
          如图,在jtool中进行一次满力跳,并截取落地前的最后一帧和落地时的第一帧作为比较,并观察y坐标的变化:


          我们可以看到,尽管kid的纵向速度为小数,但落地前后的ya根本没有发生变化。y坐标在落地前后只有整数部分发生了变化。由此可以推断出上文中“移动...直到碰到方块”的操作方式:
          //用了c++语言来表示,因为我根本不会gm语法qaq
          //hspeed同理
          int step=player.vspeed<0?-1:1;
          while (!(player.touchWall()))
          player.y+=step;
          上面代码只是一种可行、直观(且朴素)的实现方式,关于优化的方法、gm内部究竟是怎么实现的,暂且放下不云。


          IP属地:湖南5楼2021-08-12 11:34
          回复
            3.浮点数运算过程中的误差
            在iwanna大部分引擎中,x,y坐标是用单精度浮点数(即float)存储的。因为用有限的二进制数字无法精确的表示某些十进制小数,所以在十进制下,浮点数的运算过程和实际值存在着误差。更具体的,可以参见诗剑的视频(https://www.bilibili.com/video/BV1EA41157mk)。
            正是因为单精度浮点数的运算误差,才有了iwanna中各种各样的ya。


            IP属地:湖南6楼2021-08-12 11:36
            收起回复
              4.ya绕的必要条件
              要使得ya绕能够实现,必须要使kid在经过同一地点时有着不同的ya,具体到本文,我们要使得kid站立在方块上时能够有至少两个不同的ya A,B,C...且通过原地调整无法使得它们之间两两到达。我认为这才是严格意义上的ya绕,而不是利用kid不同高度下落过程中的y坐标变化的错开性进行的绕路(后者我称之为伪ya绕,因为虽然和y坐标有关,但和kid起跳前的ya几乎无任何关系)
              前置知识部分到此结束,下面进入正题。


              IP属地:湖南7楼2021-08-12 11:37
              回复
                二、为什么选择1024<=y<2048?
                在前置知识中的第三点,我们提到过浮点数运算过程中有误差。事实上误差的大小是与整数部分的大小相关的。(诗剑视频里面也有说)因为float存储的空间有限,所以不难发现,整数部分所需的空间越小,就有更多的比特用来存储小数部分,所以小数部分的精度也就会越小。
                而当1024<=y<2048时,y的整数部分在2^10到2^11之间,在这范围内,小数部分的精度是固定的。之所以选择1024<=y<2048,是因为从上往下数第三面的y坐标为1216至1832,不会发生在同一面内小数精度上的跨越现象,从而方便了ya的计算和ya绕的实现。
                此外再次提醒:以下的计算,结论等都是在1024<=y<2048的范围内才能成立的。


                IP属地:湖南8楼2021-08-12 11:38
                回复
                  三、给定跳跃前的ya和跳跃帧数,求跳跃后的ya。
                  由前置知识,很容易写出代码如下:
                  const float startfloor=2000;//起跳点坐标的整数部分
                  //(只需要在1024和2048之间,并保证移动时y坐标在1024以上就行了
                  float getValign(float valignBefore,int singleJump,int pause,int doubleJump)
                  {
                  //valignBefore:跳跃前的ya
                  //singleJump:一段跳的帧数
                  //pause:间隔帧数
                  //doubleJump:二段跳的帧数
                  //函数返回值:跳跃后的ya
                  int i;
                  float pos=startfloor+valignBefore;//当前y坐标
                  float vspeed=-8.5f;//当前纵向速度
                  if (singleJump>0)
                  {
                  for (i=1;i<=singleJump-1;i++)
                  {
                  vspeed+=0.4f;
                  pos+=vspeed;
                  }
                  if (vspeed<0)
                  vspeed*=0.45f;
                  }
                  for (i=1;i<=pause;i++)
                  {
                  vspeed+=0.4f;
                  pos+=vspeed;
                  }
                  if (doubleJump>0)
                  {
                  vspeed=-7.0f;
                  for (i=1;i<=doubleJump-1;i++)
                  {
                  vspeed+=0.4f;
                  pos+=vspeed;
                  }
                  if (vspeed<0)
                  {
                  vspeed*=0.45f;
                  }
                  }
                  while (1)
                  {
                  vspeed+=0.4f;
                  if (round(pos+vspeed)>startfloor)
                  {
                  if (vspeed==0.4f)
                  break;
                  vspeed=0.0f;
                  while (round(pos+1)<=startfloor)
                  pos++;
                  }
                  else
                  pos+=vspeed;
                  }
                  return pos-(int)pos;//返回pos小数点后的部分,即为跳跃后的ya
                  }


                  IP属地:湖南9楼2021-08-12 11:43
                  收起回复
                    可以测试一下以上代码的正确性。打开jtool farlands,将y坐标调到前文指定的范围,接着在某个ya下随便跳一次,记录下Jump,Pause和Djump,再在主函数中调用该函数,比对一下函数返回结果和jtool上显示的结果是否一致。



                    IP属地:湖南10楼2021-08-12 11:46
                    回复
                      四、对于kid原地起跳可以到达的ya的搜索和一个令人吃惊的结论
                      这里我采取了bfs的形式进行搜索,每次从队列中取出一个ya,枚举各段跳的帧数,模拟出跳跃后的ya并压入队列。
                      注意这里我没有通过预处理的方式优化模拟跳跃过程:因为浮点数运算有误差,不满足结合性,如1234.4+0.1+0.2不等于1234.4+(0.1+0.2)。当然也不是不能优化,但这里我还是选择比较稳妥的处理方式。
                      代码如下:


                      IP属地:湖南11楼2021-08-12 11:47
                      回复
                        int getMaxPause(float valignBefore,int singleJump)
                        {
                        int i;
                        int res=0;
                        float pos=startfloor+valignBefore;
                        float vspeed=-8.5f;
                        if (singleJump>0)
                        {
                        for (i=1;i<=singleJump-1;i++)
                        {
                        vspeed+=0.4f;
                        pos+=vspeed;
                        }
                        if (vspeed<0)
                        {
                        vspeed*=0.45f;
                        }
                        }
                        while (1)
                        {
                        res++;
                        vspeed+=0.4f;
                        if (round(pos+vspeed+1)>startfloor)
                        return res-1;
                        else pos+=vspeed;
                        }
                        }
                        map<float,int> vis;
                        void bfs()
                        {
                        queue<float> Q;
                        float t=startfloor+0.4f;
                        Q.push(t-(int)t);
                        vis[t-(int)t]=1;
                        int i,j,k,m;
                        float x;
                        float ya;
                        while (!Q.empty())
                        {
                        x=Q.front();
                        Q.pop();
                        //if (vis[x]>10)
                        //return;
                        printf("%d\n",vis[x]);
                        for (i=1;i<=23;i++)//singleJump
                        {
                        ya=getValign(x,i,0,0);
                        if (vis[ya])
                        continue;
                        vis[ya]=vis[x]+1;
                        Q.push(ya);
                        }
                        for (i=1;i<=23;i++)
                        {
                        m=getMaxPause(x,i);
                        for (j=1;j<=m;j++)
                        for (k=1;k<=19;k++)
                        {
                        ya=getValign(x,i,j,k);
                        if (vis[ya])
                        continue;
                        vis[ya]=vis[x]+1;
                        Q.push(ya);
                        }
                        }
                        }
                        }
                        int main()
                        {
                        bfs();
                        map<float,int>::iterator i;
                        printf("start writing to file:\n");
                        freopen("valign_1024-2048_result.out","w",stdout);
                        printf("Total num:%llu\n",vis.size());
                        for (i=vis.begin();i!=vis.end();i++)
                        printf("ya=%.14f\n",i->first);
                        fclose(stdout);
                        system("valign_1024-2048_result.out");
                        return 0;
                        }


                        IP属地:湖南12楼2021-08-12 11:47
                        回复
                          其中vis[ya]表示到达该ya至少需要跳跃几次,使用了C++中的map容器。
                          以上,我本来是想着搜索十次就退出(见bfs()中注释部分),然而,令人惊讶的是,printf("%d\n",vis[x]);只输出到了6,就已经全部搜索完毕了。


                          IP属地:湖南13楼2021-08-12 11:48
                          回复
                            输出文件中的内容:
                            Total num:3277
                            ya=0.09997558593750
                            ya=0.10009765625000
                            ya=0.10021972656250
                            ya=0.10034179687500
                            ya=0.10046386718750
                            ya=0.10058593750000
                            ya=0.10070800781250
                            ya=0.10083007812500
                            ya=0.10095214843750
                            ya=0.10107421875000
                            ya=0.10119628906250
                            ya=0.10131835937500
                            ya=0.10144042968750
                            ya=0.10156250000000
                            ya=0.10168457031250
                            ya=0.10180664062500
                            ya=0.10192871093750
                            ya=0.10205078125000
                            ya=0.10217285156250
                            ... ...(略)
                            注意到第一行"Total num:3277",也就是说,在初始ya下,光靠跳跃能够获得的所有ya只有三千多种!
                            这个结论,真的是颠覆了正常人对于ya的认知!(
                            打开jtool farlands,在前文指定的y范围内随便跳若干次,可以发现,只要是jtool上能达到的ya,在该表中都可以找到。


                            IP属地:湖南14楼2021-08-12 11:49
                            回复
                              另外,我们也可以看到,第二行显示的第一个ya居然小于0.1,经实测,不是代码的问题,在初始ya下进行一次1+4+6的跳跃即可得到这个ya。这又是浮点数存储精度捣的鬼:因为当你把一个整数部分在1024至2048之间的float浮点数加上0.4时,其实是加上了0.40002441406250。所以产生了“再往下移0.4格会碰到方块”的假象。修改startfloor后发现,在正常的0<=y<608范围内,无法出现类似的情况,所以这种现象是1024<=y<2048范围内才能看到的奇观!


                              IP属地:湖南15楼2021-08-12 11:50
                              收起回复