我正在修改蜂窝自动机,而我的运动检测功能的确表现得很奇怪。我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的范围-因为您在两个地方使用它们,并且在其中一个地方再次添加了一个!
使用
x
和y
,仅在需要时创建result
使用
final
进行滚动,并生成x
和y
,将它们确定在循环的内部将嵌套的
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