我正在修改蜂窝自动机,而我的运动检测功能的确表现得很奇怪。我80%确信这是我的实现,但是我不知道问题出在哪里。既然我已经花费了7H的大部分时间来尝试使它工作,有人可以看一下并启发我吗?它不会:

private int[] cellularSearch(short xPos, short yPos)
{
 // the center position is our current position; the others are potentially free positions
 byte[][] positions = new byte[][]{{0,0,0},{0,1,0},{0,0,0}};
 int[] result = new int[2];
 byte strike=0;
 int dice0=0, dice1=0;


  while(strike<9)
  {
    dice0 =  r.nextInt(3)-1;
    result[0] = xPos + dice0;

    if((result[0] >= 0)
    && (result[0] < img.getWidth()))
    {
        dice1 = r.nextInt(3)-1;
        result[1] = yPos + dice1;

        if((result[1] >= 0)
        && (result[1] < img.getHeight()))
        {
           if((positions[dice1+1][dice0+1] != 1))       // if this isn't our own cell and wasn't tried before
           {
                if(img.getRGB(result[0], result[1]) == Color.white.getRGB())     // if the new cell is free
                {
                    return result;
                }
           }

           positions[dice1+1][dice0+1]=1;       // we need to use +1 to create a correlation between the linkage in the matrix and the actual positions around our cell
           strike++;
        }
    }
  }
}


该代码可以正常工作,并且可以正确识别像素何时为白色并返回其位置。我的问题是结果的分布。鉴于我在行和列上都使用了Random,因此我期望在所有可能的位置上都具有几乎相等的分布,但是发生的是,此代码似乎更喜欢在所输入坐标上方的单元格(它命中了它大约是其他坐标的3倍)和坐标下方的一个坐标(它的坐标是其他坐标的2倍)。

当我启动程序时,每次运行时所有像素都慢慢移至窗口顶部(相对于我的冗长代码(是原来的3倍),它是真正的随机性),所以在某处肯定有错误。有人可以帮忙吗?

先感谢您!

编辑:谢谢大家的努力!对未编译的代码很抱歉,但是我在提取大量注释代码的同时提取了该函数的主要目的(我实现该功能的其他方法)。该代码在本地具有return语句并运行。在接下来的几个小时内,我将慢慢回答您的所有问题(很快就会有晚餐)。

EDIT2:我尝试了@DodgyCodeException和@tevemadar的建议,并列出了所有8个位置,然后在每次进入函数时都将它们随机排序,然后遍历它们,分别进行尝试。仍然选择了当前单元格正上方和正下方的位置。我很困惑。这是我为该功能编写的旧的超级意大利面条代码,它完美地运行且没有错误,均等的分配,并且(很奇怪的是)这是我在这里提到的所有内容中最有效的实现。在我吃完午餐并归档了一些文件后,我将对其进行彻底研究(距离我编写该书已经有2年了),以了解它为什么如此有效。如果有人还有想法,我会完全开放。

 boolean allRan=false;
 int lastDice=0, anteLastDice=0, dice = r.nextInt(3)+1;

 //the initial dice usage is for selecting the row on which we'll operate:
 //dice = 1 or 3 -> we operate above or under our current cell; dice = 2 -> we operate on the same row
while(!allRan)
{
 if((dice==1) || (dice==3))
    {
    int i= r.nextInt(3);

    if(((xPos-1+i) < img.getWidth())
    && ((xPos-1+i) >= 0))
       {
       if(((yPos-1) >= 0)
       && (img.getRGB(xPos-1+i, yPos-1) == Color.white.getRGB())
       && (dice==1))
          {
          result[0] = xPos-1+i;
          result[1] = yPos-1;
          above++;

          endTime = (int) System.currentTimeMillis();
          section4Runtime += (double) (endTime - startTime) / 1000;
          return result;
          }
       else if(((yPos+1) < img.getHeight())
       && (img.getRGB(xPos-1+i, yPos+1) == Color.white.getRGB())
       && (dice==3))
          {
          result[0] = xPos-1+i;
          result[1] = yPos+1;
          below++;

          endTime = (int) System.currentTimeMillis();
          section4Runtime += (double) (endTime - startTime) / 1000;
          return result;
          }
       }

    // if this section is reached, it means that: the initial dice roll didn't find a free cell, or the position was out of bounds, or the dice rolled 2
    // in this section we do a dice reroll (while remembering and avoiding our previous values) so that we cover all dice rolls
    if(dice==1)
        {
         if(lastDice==0)
            {
            lastDice=dice;
            dice += r.nextInt(2)+1;     // we incrmeent randomly towards 2 or 3.
            }
         else
            {
             if(lastDice==2)
                 {
                  if(anteLastDice==0)
                    {
                     anteLastDice= lastDice;
                     lastDice=dice;
                     dice=3;
                    }
                  else
                    {
                     allRan=true;
                    }
                 }
             else if(lastDice==3)
                 {
                  if(anteLastDice==0)
                    {
                     anteLastDice= lastDice;
                     lastDice=dice;
                     dice=2;
                    }
                  else
                    {
                     allRan=true;
                    }
                 }
            }
        }
        else        // dice is 3
        {
            if(lastDice==0)
            {
             lastDice=dice;
             dice -= r.nextInt(2)+1;     // we decrement randomly towards 2 or 1.
            }
         else
            {
             if(lastDice==2)
                 {
                  if(anteLastDice==0)
                    {
                     anteLastDice= lastDice;
                     lastDice=dice;
                     dice=1;
                    }
                  else
                    {
                     allRan=true;
                    }
                 }
             else if(lastDice==1)
                 {
                  if(anteLastDice==0)
                    {
                     anteLastDice= lastDice;
                     lastDice=dice;
                     dice=2;
                    }
                  else
                    {
                     allRan=true;
                    }
                 }
            }
        }
    }

 if(dice==2)
    {
    int i=0;
    i += r.nextInt(2)==0?-1:1;

    if(((xPos+i) < img.getWidth())
    && ((xPos+i) >= 0)
    && (img.getRGB(xPos+i, yPos) == Color.white.getRGB()))
       {
       result[0] = xPos+i;
       result[1] = yPos;
       leveled++;

       endTime = (int) System.currentTimeMillis();
       section4Runtime += (double) (endTime - startTime) / 1000;
       return result;
       }

    // same as above: a dice reroll (with constrictions)
    if(lastDice==0)
        {
        lastDice=dice;
        dice+= r.nextInt(2)==0?-1:1;        // randomly chose if you decrement by 1 or increment by 1
        }
    else
        {
         if(lastDice==1)
            {
             if(anteLastDice==0)
                {
                 anteLastDice= lastDice;
                 lastDice=dice;
                 dice =3;
                }
             else
                {
                 allRan=true;
                }
            }
         else if(lastDice==3)
            {
             if(anteLastDice==0)
                {
                 anteLastDice= lastDice;
                 lastDice=dice;
                 dice =1;
                }
             else
                {
                 allRan=true;
                }
            }

        }
    }
}

return result;


经过深思熟虑,我终于明白了。我们所有人的所有想法都违反了我正在使用的第一个实现的基本“规则”:第一个实现是尝试在3行中的任意一行上尝试随机位置,然后继续进行下一行(不返回到尝试在该行的其他位置)。例如:如果算法选择了上面的行,它将随机尝试左上角以查看其是否空闲;如果不是,那么它将尝试与当前单元格和下面的行相同的行(同样,仅保留其可能的位置之一)而不会返回。我们所有的想法都在遍历单元周围的所有可能性,这意味着顶线和底线的点击率要比中间点高是不可避免的(因为顶部和底端各有3个可能的点,而中间点只有2个)。同样,当田野中有孔时,最可能将其填满的单元是那些沿对角线移动(最终向上或向下)或直接向上或向下移动的单元,因为那些侧向移动的单元仅具有左/右选项。唯一未解之谜是为什么(使用我们提出的实现方法)模型通常使用恰好在当前单元格上方的点。我不知道为什么它喜欢在大多数情况下都采用这种实现方式。尽管如此,新算法(反映了旧算法,但更轻巧)是:

boolean[] lines = new boolean[]{false, false, false};
byte checks =0;

while(checks < 3)       // just 3 tries in total
{
    dice = r.nextInt(3);

    if(lines[dice]== false)
    {
        lines[dice] = true;             // just 1 try per line
        // calculated here since we reuse dice below
        result[1] = yPos - 1 + dice;    // will be above if dice==0; will be below if dice==2; same line if dice==1

        if((dice == 0) || (dice == 2))        // top/bottom line
            {dice = r.nextInt(3)-1;}
        else if(dice == 1)        // middle line
            {dice = r.nextInt(2)==0?-1:1;}        // we exclude the middle point since that's our current position

        result[0] = xPos + dice;    // logic is calculated above and just applied here
        checks++;
    }

    if((result[0] >= 0)
    && (result[0] < img.getWidth())
    && (result[1] >= 0)
    && (result[1] < img.getHeight()))
    {
        if (img.getRGB(result[0], result[1]) == Color.white.getRGB())     // if the new cell is free
        {
            return result;
        }
    }
}
result[0] = -1;     // in case we get here, reset the value so it's not used


这使代码从167行减少到33行(并使它更具可读性)。我不知道该选择谁作为最佳解决方案。请提出您的想法。

最佳答案

首先,我必须承认我看不到您的算法应该执行的操作-我不清楚为什么您这样做时为什么要滚动每个骰子,而其他时候却使用现有值。

对于一种清晰易懂的算法,我建议在循环内确定dice变量的范围,同时滚动两个变量并使它们成为final,以便您知道每次迭代都只有一个两次掷骰子:

while(strike < 9) {
    final int roll1 = r.nextInt(3) - 1;
    final int roll2 = r.nextInt(3) - 1;

    strike += handleRoll(roll1,roll2);
}


您可以通过为您的handleRoll()编写一个简单的计数器来证明自己的分布,然后再替换您的真实代码。

int[] counts = int[6];
void handleRoll(int roll1, int roll2) {
     counts[1 + roll1] ++;
     counts[4 + roll2] ++;
     return 1;
}


(增加所需的执行次数以获取足够大的样本以进行推理)

确保在整个程序中使用相同的Random实例-不要继续制作新的实例。

(您可以通过创建一个Coordinate类和一个创建随机变量的工厂来整理一下)



我简化了您的代码,如下所示:


进行了一系列提取方法重构以整理细节
将您的掷骰更改为使用0到2的范围,而不是-1到+1的范围-因为您在两个地方使用它们,并且在其中一个地方再次添加了一个!
使用xy,仅在需要时创建result
使用final进行滚动,并生成xy,将它们确定在循环的内部
将嵌套的if转换为&&逻辑
更改了一些奇怪的类型选择。 positions网格似乎是为boolean设计的。在Java中使用short几乎没有任何价值。


所以:

private int[] cellularSearch(int xPos, int yPos) {
     boolean[][] positions =
            new boolean[][] { { false, false, false },
                              { false, true, false },
                              { false, false, false } };
    int strike = 0;

    while (strike < 9) {
        final int dice0 = r.nextInt(3);
        final int dice1 = r.nextInt(3);

        final int x = xPos + dice0 - 1;
        final int y = yPos + dice1 - 1;

        if (isInXrange(x) && isInYRange(y)) {
            if (!alreadyTried(positions, dice1, dice0) && isWhite(x, y)) {
                return new int[] { x, y };
            }

            markAsTried(positions, dice1, dice0);
            strike++;
        }
    }
    return null; // or whatever you intend to happen here
}

private boolean isInXrange(int x) {
    return (x >= 0) && (x < img.getWidth());
}

private boolean isInYRange(int y) {
    return (y >= 0) && (y < img.getHeight());
}

private boolean alreadyTried(boolean[][] positions, final int dice1, final int dice0) {
    return positions[dice1 + 1][dice0 + 1];
}

private static void markAsTried(boolean[][] positions, int dice1, int dice0) {
    positions[dice1][dice0] = true;
}

private boolean isWhite(final int x, final int y) {
    return img.getRGB(x, y) == Color.white.getRGB();
}


我认为这与您的代码等效,但有一个例外-如果第一次滚动使您不在图像的宽度范围内,则不会滚动第二个骰子。如果您愿意,可以稍后将其重新添加以提高性能。

但这暴露了一些问题。看来是要尝试每个单元格(您有一个3x3的网格,并且已选择9个“打击”),但是当strike在图像外部时,它不会递增x,y。以前尝试过该位置时,它的确会增加strike。因此,您可以在没有尝试所有单元的情况下退出循环。

我没有看到导致您描述的权重的特定方式-
但看起来可能会导致意想不到的结果。

(无论如何-由于您提供的代码无法编译,因此您在提供给我们的代码中也没有观察到它)

如果要检查每个单元格,则最好随机整理要尝试的单元格列表,然后按顺序对其进行测试:

 List<Coords> coordsToTry = new ArrayList<>();
 for(int x=0; x<2; x++) {
     for(int y=0; y<2; y++) {
         coordsToTry.add(new Coords( x, y));
     }
 }
 Collections.shuffle(coordsToTry);

 for(Coords coords : coordsToTry) {
    if(isWhite(coords)) {
        return coords;
    }
 }
 return null; // or whatever is meant to happen when nothing found

09-30 17:45
查看更多