我正在修补细胞自动机,我的动作检测功能非常奇怪。我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个位置的列表,然后每次进入函数时将它们随机播放,然后迭代它们,尝试各个部分。仍然恰好在当前单元格的正上方和下方的位置被选中。我很困惑。这是我为这个函数编写的旧的超级意大利面条代码,它完美地运行,没有错误,平等分配,并且(奇怪的是)它是我尝试过的最有效的实现。在我吃完午餐并提交一些文书工作之后,我会彻底研究它(我写这篇文章已经有两年了),看看为什么它能很好地运作。如果还有人有想法,我会完全开放。
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行(并使其更具可读性)。我不知道选择谁作为最佳解决方案。如果您有任何想法,请提出建议。
答案 0 :(得分:2)
java.util.Random的分布不是那么不均匀。您可以使用以下代码进行确认:
public static void main(String[] args) throws Exception {
final int N = 3;
Random r = new Random();
int[] counts = new int[N];
for (int i = 0; i <= 100_000; i++) {
counts[r.nextInt(N)]++;
}
System.out.println(Arrays.toString(counts));
}
<强>更新强>
正如您所说,上面的代码产生了相当均匀的分布值。但是,在循环开头添加以下行:
if (i % 6 == 0)
r = new Random(0);
然后你得到[16667,33333,50000]。一个值的频率是第一个的两倍,另一个频率是第一个频率的3倍。这将随机数生成器设置为具有常量种子的新创建的生成器。它模拟你的代码,你在其中创建一个new Random()
的函数入口(尽管没有种子参数),然后你的函数调用nextInt()
六次 - 这个if (i % 6 == 0)
语句确保每6次迭代也会创建一个新的RNG。
检查您的代码并确保您只在整个程序中创建一次RNG。
答案 1 :(得分:2)
首先,我必须承认我无法看到你的算法应该做什么 - 我不清楚你为什么要在你做的时候滚动每个骰子,其他时候使用现有的值。
为了获得一个清晰,易于理解的算法,我建议在循环中确定您的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
类和创建随机类的工厂来稍微整理一下这些内容。
我简化了你的代码:
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&#34;罢工&#34;) - 但它并没有增加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
答案 2 :(得分:1)
java.util.Random
是伪随机数生成器(wikipedia上的定义),需要进行种子处理。
来自文档:
如果使用相同的种子创建了两个Random实例,并且为每个实例创建了相同的方法调用序列,则它们将生成并返回相同的数字序列。为了保证这个属性,为Random类指定了特定的算法。
如果你想确保获得好的随机数,请使用SecureRandom
,这可以保证产生非确定性输出
答案 3 :(得分:1)
由于您对两个'骰子'的组合分布感兴趣,在@ DodgyCodeException的建议之上,您可以查看统计信息,如
public static void main(String[] args) {
Random r=new Random();
int stat[]=new int[9];
for(int i=0;i<9000;i++)
stat[r.nextInt(3)+r.nextInt(3)*3]++;
for (int i : stat)
System.out.println(i);
}
然而它甚至也是如此。
<小时/> 从两个幂的范围生成随机数和其他方面之间存在细微的差别,所以如果你真的想要做一些魔术,你可以使用这样一个事实:你实际上是从8种可能性中选择一个位置(因为中间一个在开头排除了)。 像这样的东西
final int xhelper[]=new int[]{-1, 0, 1,-1, 1,-1, 0, 1};
final int yhelper[]=new int[]{-1,-1,-1, 0, 0, 1, 1, 1};
...
int dir=r.nextInt(8);
int dice0=xhelper[dir];
int dice1=yhelper[dir];
但实际上我并不认为这会有所作为。