我正在为我的大学项目创建一个TicTacToe游戏,当我完成了计算机AI的代码后,最终得到了一大堆代码。

它允许计算机做出成功的决定,停止如果计算机无法进行任何获胜动作或阻止玩家获胜,则该玩家不能获胜并随意移动。我的代码包含一系列ifelse if语句。

我想知道是否有可能以任何方式减少这种代码量。我尝试将一个if语句放入一个方法中,然后多次调用该方法,但这不起作用,因为else语句无法运行。

public void AI(){
        count++;
        if(buttons[1].getText().equals("O") && buttons[2].getText().equals("O") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);
        } else if(buttons[4].getText().equals("O") && buttons[5].getText().equals("O") && buttons[6].getText().equals("")){
            buttons[6].setText("O");
            buttons[6].setEnabled(false);
        } else if(buttons[7].getText().equals("O") && buttons[8].getText().equals("O") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        } 

        else if(buttons[2].getText().equals("O") && buttons[3].getText().equals("O") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[5].getText().equals("O") && buttons[6].getText().equals("O") && buttons[4].getText().equals("")){
            buttons[4].setText("O");
            buttons[4].setEnabled(false);                
        } else if(buttons[8].getText().equals("O") && buttons[9].getText().equals("O") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("O") && buttons[3].getText().equals("O") && buttons[2].getText().equals("")){
            buttons[2].setText("O");
            buttons[2].setEnabled(false);                
        } else if(buttons[4].getText().equals("O") && buttons[6].getText().equals("O") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        } else if(buttons[7].getText().equals("O") && buttons[9].getText().equals("O") && buttons[8].getText().equals("")){
            buttons[8].setText("O");
            buttons[8].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("O") && buttons[4].getText().equals("O") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        } else if(buttons[2].getText().equals("O") && buttons[5].getText().equals("O") && buttons[8].getText().equals("")){
            buttons[4].setText("O");
            buttons[4].setEnabled(false);                
        } else if(buttons[3].getText().equals("O") && buttons[6].getText().equals("O") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        }

        else if(buttons[4].getText().equals("O") && buttons[7].getText().equals("O") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[5].getText().equals("O") && buttons[8].getText().equals("O") && buttons[2].getText().equals("")){
            buttons[2].setText("O");
            buttons[2].setEnabled(false);                
        } else if(buttons[6].getText().equals("O") && buttons[9].getText().equals("O") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("O") && buttons[7].getText().equals("O") && buttons[4].getText().equals("")){
            buttons[4].setText("O");
            buttons[4].setEnabled(false);                
        } else if(buttons[2].getText().equals("O") && buttons[8].getText().equals("O") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        } else if(buttons[3].getText().equals("O") && buttons[9].getText().equals("O") && buttons[6].getText().equals("")){
            buttons[6].setText("O");
            buttons[6].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("O") && buttons[5].getText().equals("O") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        } else if(buttons[5].getText().equals("O") && buttons[9].getText().equals("O") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[1].getText().equals("O") && buttons[9].getText().equals("O") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        }

        else if(buttons[3].getText().equals("O") && buttons[5].getText().equals("O") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        } else if(buttons[7].getText().equals("O") && buttons[5].getText().equals("O") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);                
        } else if(buttons[7].getText().equals("O") && buttons[3].getText().equals("O") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[2].getText().equals("X") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);
        } else if(buttons[4].getText().equals("X") && buttons[5].getText().equals("X") && buttons[6].getText().equals("")){
            buttons[6].setText("O");
            buttons[6].setEnabled(false);                
        } else if(buttons[7].getText().equals("X") && buttons[8].getText().equals("X") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        } 

        else if(buttons[2].getText().equals("X") && buttons[3].getText().equals("X") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[5].getText().equals("X") && buttons[6].getText().equals("X") && buttons[4].getText().equals("")){
            buttons[4].setText("O");
            buttons[4].setEnabled(false);                
        } else if(buttons[8].getText().equals("X") && buttons[9].getText().equals("X") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[3].getText().equals("X") && buttons[2].getText().equals("")){
            buttons[2].setText("O");
            buttons[2].setEnabled(false);                
        } else if(buttons[4].getText().equals("X") && buttons[6].getText().equals("X") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        } else if(buttons[7].getText().equals("X") && buttons[9].getText().equals("X") && buttons[8].getText().equals("")){
            buttons[8].setText("O");
            buttons[8].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[4].getText().equals("X") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        } else if(buttons[2].getText().equals("X") && buttons[5].getText().equals("X") && buttons[8].getText().equals("")){
            buttons[8].setText("O");
            buttons[8].setEnabled(false);                
        } else if(buttons[3].getText().equals("X") && buttons[6].getText().equals("X") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        }

        else if(buttons[4].getText().equals("X") && buttons[7].getText().equals("X") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[5].getText().equals("X") && buttons[8].getText().equals("X") && buttons[2].getText().equals("")){
            buttons[2].setText("O");
            buttons[2].setEnabled(false);                
        } else if(buttons[6].getText().equals("X") && buttons[9].getText().equals("X") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[7].getText().equals("X") && buttons[4].getText().equals("")){
            buttons[4].setText("O");
            buttons[4].setEnabled(false);                
        } else if(buttons[2].getText().equals("X") && buttons[8].getText().equals("X") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        } else if(buttons[3].getText().equals("X") && buttons[9].getText().equals("X") && buttons[6].getText().equals("")){
            buttons[6].setText("O");
            buttons[6].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[5].getText().equals("X") && buttons[9].getText().equals("")){
            buttons[9].setText("O");
            buttons[9].setEnabled(false);                
        } else if(buttons[5].getText().equals("X") && buttons[9].getText().equals("X") && buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        } else if(buttons[1].getText().equals("X") && buttons[9].getText().equals("X") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        }

        else if(buttons[3].getText().equals("X") && buttons[5].getText().equals("X") && buttons[7].getText().equals("")){
            buttons[7].setText("O");
            buttons[7].setEnabled(false);                
        } else if(buttons[7].getText().equals("X") && buttons[5].getText().equals("X") && buttons[3].getText().equals("")){
            buttons[3].setText("O");
            buttons[3].setEnabled(false);                
        } else if(buttons[7].getText().equals("X") && buttons[3].getText().equals("X") && buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("X") && buttons[5].getText().equals("O") && buttons[9].getText().equals("X")) {
            buttons[6].setText("O");
            buttons[6].setEnabled(false);            
        }    

        else if(buttons[3].getText().equals("X") && buttons[5].getText().equals("O") && buttons[7].getText().equals("X")) {
            buttons[4].setText("O");
            buttons[4].setEnabled(false);            
        }

        else if(buttons[5].getText().equals("")){
            buttons[5].setText("O");
            buttons[5].setEnabled(false);                
        }

        else if(buttons[1].getText().equals("")){
            buttons[1].setText("O");
            buttons[1].setEnabled(false);                
        }
        else {
            if(count >= 9)
                checkWin();
            else
                RandomMove();
        }

        checkWin();

    }


编辑:我已经检查了if语句,检查计算机可以做出获胜举动或阻止玩家获胜的所有不同可能性。

编辑3:

for(int i=0; i<=3; i++){
    if (buttons[i*3].getText().equals("X") && buttons[i*3+1].getText().equals("X") && buttons[i*3+2].getText().equals(""))
        buttons[i*3+2].setText("O");
        win = true;
    }


评论

有时可以使用字典代替一系列if语句。您考虑过这种方法吗?

乍一看,您的AI逻辑似乎与UI /用户界面紧密结合。

@Seeker:字典或哈希表是一种将不同键映射到值的数据结构。词典通常可以帮助简化问题的表示,并且对于许多应用程序来说都是很好的工具。在Java中,我认为您需要实现Map(相当于我正在描述的数据结构)。

@gjdanis我不确定地图对您有帮助吗?您如何建议OP应该使用它?

只是,你知道,把这个留在这里... xkcd.com/832

#1 楼

是的,您确实需要使用循环并将其概括化!

为了让您学习更多,我不会提供任何确切的代码,但是我会告诉您一些有关您需要做的事情


您的数组似乎基于索引1-9,我建议改为使用索引0到8。数组索引始终从零开始,好像您只是没有使用它。而是使用它。
使用for循环遍历游戏板的行,列和对角线。 3行,3列和2条对角线。 for (int x = 0; x < 3; x++)是循环遍历所有x(列)的一种简单方法。提示:可以使用index / 3index % 3%是“模”运算符)来计算按钮的行和列。这就是为什么您应该使用基于零索引的数组的原因。

您可能想要对enumUNPLAYEDX使用O。使用适当的enum确实可以帮助您大规模清理代码。

public enum PlayedBy {
UNPLAYED,X,O;
}

解耦。这有点高级,您应该主要关注的是什么。但是将来,它将为您带来很大的帮助。现在,您的AI知道有关视图的所有信息(视图=用户看到的对象)。如果您想让两个AI相互竞争并且只输出到控制台该怎么办?如果您想为此制作一个Android应用程序怎么办?如果您想使用GWT制作Web应用程序怎么办? (将来可能会想要!)您应该研究MVC模式和/或使用接口!解耦代码。

您经常重复此部分

buttons[x].setText("O");
buttons[x].setEnabled(false);


这应该放在方法中:

void setButtonPlayer(JButton position, String player) {
    position.setText(player);
    position.setEnabled(false);
}


现在只需要使用setButtonPlayer(buttons[4], "O");来移动按钮4。如果使用枚举,则可以使用setButtonPlayer(JButton button, PlayedBy player)

稍作清理后,建议您发布一个后续问题,因为届时可能还有更多可以清理的内容。

评论


\ $ \ begingroup \ $
嗨,我不确定如何使用for循环来检查计算机进行制胜的所有不同可能性。我尝试了一些东西,但是我不确定这是否正确。我已经更新了我的帖子,如果我走的路正确,请告诉我。谢谢
\ $ \ endgroup \ $
–寻求者
2014年3月17日20:43

\ $ \ begingroup \ $
@Seeker几乎在正确的路径上。仅循环从0到2,并获取第一个按钮的索引以i * 3进行检查。或者,使用二维数组JButton [] []按钮= new JButton [3] [3];二维数组可能很难入门,但从长远来看,它们将为您提供帮助。但是,如果您愿意,可以像现在一样坚持使用一维数组。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年3月17日21:04

\ $ \ begingroup \ $
这个问题听起来有点愚蠢,但是我们为什么要这么做* 3。
\ $ \ endgroup \ $
–寻求者
2014年3月17日21:06

\ $ \ begingroup \ $
@Seeker一点也不。很好的问题。考虑一下要检查哪些索引,据我了解,您想要(0 1 2),(3 4 5),(6 7 8)。即,(i * 3,i * 3 + 1,i * 3 + 2)。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年3月17日在21:17

\ $ \ begingroup \ $
@ Mat'sMug是是是是;)
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年3月18日在8:12

#2 楼

当遇到这样的问题时,它有助于实施责任链模式。在这种情况下,您将具有不同的层次结构,包括获胜举动,防御举动和随机举动。

您应该按照以下方式创建一个界面:

public interface TicTacToeStrategy {
    public boolean apply(Button[] buttons);
}


然后,您应该对此策略有很多实现....

类似的东西:

public ForTheWinStrategy implements TicTacToeStrategy {
    public boolean apply(Button[] buttons) {
        if (.... there is a winning move) {
            apply the winning move
            return true;
        }
        return false;
    }
}


然后您可以拥有DefensiveStrategyRandomStrategy

 private static final TicTacToeStrategy[] STRATEGIES = {
     new ForTheWinStrategy(),
     new DefensiveStrategy(),
     new RandomStrategy()
 };


您的主要代码可以变成:

for(TicTacToeStrategy strategy : STRATEGIES) {
    if (strategy.apply(buttons)) {
        break;
    }
}


对于所有if语句....您都需要以某种方式对其进行合理化。

将它们提取为一个函数会很好,或者设置多个三合一行设置....

private static final int[][] THREEINAROW = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
    {1, 4, 7},
    {2, 5, 8},
    {3, 5, 9},
    {1, 5, 9},
    {3, 5, 7}
}


通过上述设置,您可以相对容易地说:

for (int[] tiaw : THREEINAROW) {
    if (2 are us, and one is empty) {
        we have a winner!
    }
} 


评论


\ $ \ begingroup \ $
嗨,我刚刚阅读了您的示例,但不清楚从何处调用新的DefensiveStrategy()和新的RandomStrategy()。
\ $ \ endgroup \ $
–寻求者
2014年3月17日20:32

\ $ \ begingroup \ $
@Seeker它们被声明为STRATEGIES数组中的实例,然后在if(strategy.apply(buttons)){....}的情况下在主循环内被调用。
\ $ \ endgroup \ $
–rolfl
2014年3月17日20:33



\ $ \ begingroup \ $
我只是在看您的代码,我发现理解last for循环的工作原理有点困惑,因为(int [] tiaw:THREEINAROW){。如果我没记错的话,它将遍历预定义的Threeinarow数组,并检查是否有两个按钮具有相同的值,如果相同,则进行获胜操作,但是如何在if语句中传递按钮?
\ $ \ endgroup \ $
–寻求者
2014年3月17日在21:02

#3 楼

您基本上对所有内容进行了硬编码。不建议对AI进行硬编码。如果您编写的内容稍微复杂些怎么办?然后,您可能会遇到AI完全失败的情况。这就是为什么我认为您应该开发自己的算法或实现其他人的算法的原因。
对于您的问题,我建议您实现minimax算法,该算法基本上是递归检查所有可能的选项以完成游戏。这是伪代码:

function minimax(node, depth, maximizingPlayer)
    if depth = 0 or node is a terminal node
        return the heuristic value of node
    if maximizingPlayer
        bestValue := -∞
        for each child of node
            val := minimax(child, depth - 1, FALSE))
            bestValue := max(bestValue, val);
        return bestValue
    else
        bestValue := +∞
        for each child of node
            val := minimax(child, depth - 1, TRUE))
            bestValue := min(bestValue, val);
        return bestValue

(* Initial call for maximizing player *)
minimax(origin, depth, TRUE)


源:http://en.wikipedia.org/wiki/Minimax

另外一个实现minimax的源用于井字游戏:
http://www.codeproject.com/Articles/43622/Solve-Tic-Tac-Toe-with-the-MiniMax-algorithm

评论


\ $ \ begingroup \ $
我不知道对AI的任何定义都排除了对所有选项进行硬编码的可能性。
\ $ \ endgroup \ $
– David Richerby
2014年3月18日在2:51

\ $ \ begingroup \ $
有人在乎解释为什么我会遇到缺点吗?
\ $ \ endgroup \ $
– Sarp Kaya
2014年3月18日在8:44

\ $ \ begingroup \ $
“如果您要编写一些更复杂的内容怎么办?”-如果问题不同,那么是的,您需要使用不同的代码。对于此问题,此方法有效。
\ $ \ endgroup \ $
– AakashM
2014年3月18日在10:58

\ $ \ begingroup \ $
@AakashM代码检查的目的是检查代码。这不是stackoverflow。因此,我认为没有理由不建议完全不同的方法,因为它更优雅,更容易理解。
\ $ \ endgroup \ $
– Sarp Kaya
2014年3月18日在11:47

\ $ \ begingroup \ $
FWIW,当我阅读问题标题时,我想到的第一件事就是minimax,并且惊讶地发现了一串if-else-if语句。当然,它们很好地作为开始,在OP的学习阶段,minimax可能太先进了,但是这个答案几乎不值得批评!
\ $ \ endgroup \ $
– David Harkness
2014年3月18日在16:50

#4 楼

我最近写了一个程序来做这样的事情,我的实现基本上归结为:


要存储标记,我有一个enum Mark { NOBODY, X, O }
要存储移动在游戏中,我有一个Move类,可以跟踪标记和在板上的位置
要存储游戏状态,我有一个Board类。我的实现使电路板对象不可变,并且只能通过其put(Move m)方法派生其他电路板对象。它还提供了查询其单元格中保存的值的方法。Player接口表示播放器。对于真正的玩家,我有一个MCV东西,而我对ComputerPlayer的实现委托给一些Strategy对象。 (Player界面还提供了一种报告游戏结束状态的方法,以促进自学AI)。轮到玩家时,游戏模型调用玩家的getMove(Board b)方法来获取代表他们对特定状态的响应的Move对象。
但是,有关游戏实际规则的所有信息,包括有效动作,获胜等包含在单独的类Rules中。可以使用一种方法获得包含板的所有行,列和对角线的Iterable,使用另一种方法查询特定板状态的获胜者,使用另一种方法检查Move对象是否可以合法添加到Board中,或者获取他们可以合法进行的所有举动的清单。我在AI的实现中仅使用这些来决定移动的位置,使其完全独立于特定规则,因此如果我更改这些规则,它们仍然可以工作。


#5 楼

您遇到的问题是您要检查每个特定的情况,而不是抽象情况的类型。这使得您正在编写的代码更加难以阅读,编写,调试和维护。

例如,您拥有:

else if(buttons[1].getText().equals("X") && buttons[4].getText().equals("X") &&  buttons[7].getText().equals("")){
    buttons[7].setText("O");
    buttons[7].setEnabled(false);}


您可以使用的第一层抽象将是定义一种方法来发现正方形中的内容,这将使代码更具可读性:

private boolean containsX(int square){
    return buttons[square].getText().equals("X");}


方法)使代码更具可读性:

else if(containsX(1) && containsX(4) && isEmpty(7){
    placeMove("O", 7);}    


现在您已将代码抽象为可以读取的方法,但是仍然一次指定了“逻辑” ,您也可以抽象出来。其他人在此页面上提出了不同的建议方法,但是想法总是相同的,您需要大胆地表达每一行的具体含义。因此,除了上面的行以外,您可能还具有:

else if (canWinInFirstColumn)
    { playMoveInFirstColumn() }


但是,一旦您开始编写这些内容,您就会意识到它们对于每一列都是相同的,并且canWinInFirstColumn的代码可以简单地改制成canWinInColumnNumber(int colNumber)并适用于每一列。

然后您意识到列和行的代码是相似的,您只需要担心对角线,因此您可以为每个对角线创建方法,然后有一个方法将其全部调用;

private boolean canWin(){
    for (int i=0;i<3;i++){
         if (canWinInRow(i)){
                 playMoveInRow(i);
                 return true;
                 }
         if (canWinInColumn(i)){
                 playMoveInColumn(i);
                 return true;
                 }
         }
    if (canWinInFirstDiagonal){
        playMoveInFirstDiagonal();
        return true;
        }
    if (canWinInSecondDiagonal){
        playMoveInSecondDiagonal();
        return true;
        }
    return false;
}


显然,您可以继续进行并提取很多代码进入包含逻辑的方法中,有经验的程序员将跳过这些步骤中的许多步骤,最后得到一个调用了几个函数的方法。使代码更整洁还有其他“技巧”,例如,注意到电路板是对称的,或者使用数学运算来确定一行或一列中有两个部分和一个空白,或者两个或两个部分又有一个空白。空的空间。

最后,您可以看到分配给他们的板子“视图”(即带有字母X的按钮是带有字母X或O的按钮),但它也可以作为“模型”使用一倍,即您正在使用它来工作出什么地方。将两者分开是很常见的,因此您将拥有一个简单的数组(例如整数),其中包含每个正方形中的内容(0 =空,1 = X,2 = O),那么您的逻辑就可以变得更加整洁(没有到处都需要.getText()。equals('X'),只需== 1),您只有一个方法可以从模型中读取所有0、1和2,并将相应平方的值设置为X或O(视情况而定)。这意味着“密集”代码使用的是高效的数据结构,仅呈现结果(上面带有文本的按钮)的代码仅在需要时才进行更新。

希望这会有所帮助:)