简体   繁体   中英

How do I create Logic based AI for my Java Tic Tac Toe Program?

I've almost put the wraps on my program, but I want to add some way for users to play against the computer. For this, I need to create some kind of conditional logic based AI, but can't see which approach to take.

I don't want the computer to take its turn randomly, I want it to actually play like a human player. Would I have to use a deeply nested if statements and multiple moves for each possible turn, or use some kind of for or while loop?

My code reduced to just the WinChecker is here:

public void calculatevictory(){
            if (b1.getText() == b2.getText() && b2.getText() == b3.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b4.getText() == b5.getText() && b5.getText() == b6.getText() && b4.getText() != "")
            {
                win = true ;
            }
            else if (b7.getText() == b8.getText() && b8.getText() == b9.getText() && b7.getText() != "")
            {
                win = true ;
            }
            else if (b1.getText() == b4.getText() && b4.getText() == b7.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b2.getText() == b5.getText() && b5.getText() == b8.getText() && b2.getText() != "")
            {
                win = true ;
            }
            else if (b3.getText() == b6.getText() && b6.getText() == b9.getText() && b3.getText() != "")
            {
                win = true ;
            }
            else if (b1.getText() == b5.getText() && b5.getText() == b9.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b3.getText() == b5.getText() && b5.getText() == b7.getText() && b3.getText() != "")
            {
                win = true ;
            }
            else 
            {
                win = false ;
            }
        }

My code so far is here:

/* PROGRAM NAME : TIC TAC TOE 
 * AUTHOR : Jack Robinson
 * DATE : 7/17/2014 
 */

// Importing necessary classes and directories 

import java.util.Random ;
import java.util.Scanner ;
import javax.swing.JOptionPane ;
import javax.swing.JFrame ;
import javax.swing.JPanel ;
import java.util.InputMismatchException ;
import java.awt.BorderLayout ;
import java.awt.* ;
import java.awt.event.* ;
import javax.swing.JTextArea ;
import javax.swing.JButton ;
import javax.swing.JRadioButton ;
import java.awt.Font ;

//Creating class

class TicTacToe 
 {
     //Declaring all instance members 

    public int count = 1 ; 
    public String letter;
    public boolean b1bool = true ;
    public boolean b2bool = true ;
    public boolean b3bool = true ;
    public boolean b4bool = true ;
    public boolean b5bool = true ;
    public boolean b6bool = true ;
    public boolean b7bool = true ;
    public boolean b8bool = true ;
    public boolean b9bool = true ;
    public boolean win = false ;
    public boolean gameinit = true ;
    private JButton b1 ;
    private JButton b2 ;
    private JButton b3 ;
    private JButton b4 ;
    private JButton b5 ;
    private JButton b6 ;
    private JButton b7 ;
    private JButton b8 ;
    private JButton b9 ;
    private JFrame gamescreen ;
    private JFrame introscreen ;
    public Font TToeFont = new Font("Arial",Font.PLAIN,40);

    // Main method


    public static void main(String []args) 
    {
        // Calls the method which pops up the intro screen ;
        TicTacToe runnext = new TicTacToe();


        runnext.popupintroscreen();

    }
    public void popupintroscreen()
    {
        //Creating introduction text and it's wrapper

        JTextArea introtext = new JTextArea("Welcome to TicTacToe v1.0. This is a Simple Tic Tac Toe app coded  by Abhishek Pisharody. Press the button below to play, or view the        instructions first, if you prefer. We hope you enjoy playing. Thank you.");
        introtext.setEditable(false);
        introtext.setLineWrap(true);

        // ----------------------------------- DECLARING ALL JBUTTONS IN THE INTRO SCREEN AND ADDING ACTION LISTENERS ---------------------------------------------------

        JButton startgamebutton = new JButton("Start Game");
        startgamebutton.setToolTipText("Start a game of Tic-Tac-Toe");
        startgamebutton.setFont(TToeFont);
        startgamebutton.addActionListener(new ActionListener()
        {
           @Override
           public void actionPerformed(ActionEvent gamestart)
           {

            if(gameinit == true){
            JOptionPane.showMessageDialog(null, "Loading.....done!");
            tictactoe();
            gameinit = false;
        } else {
            JOptionPane.showMessageDialog(null, "The game is already running" , "Error" , JOptionPane.ERROR_MESSAGE);
        }
           }
        });

        JButton showinstructions = new JButton("Show Instructions");
        showinstructions.setToolTipText("View game instructions. Worth checking out even if you know how to play.");
        showinstructions.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent displayinstructionsprompt)
            {
              JOptionPane.showMessageDialog(null, "Nothing to see here..yet..off you go!");  
            }
        });

        JButton highscoresbutton = new JButton("High Scores");
        highscoresbutton.setToolTipText("Show high scores for the game");
        highscoresbutton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent highscoresbuttonclicked)
            {
                JOptionPane.showMessageDialog(null,"Not coded yet!");
            }
        });

        JButton quitgamebutton = new JButton("Quit Game");
        quitgamebutton.setToolTipText("Quit the game. But why? :(");
        quitgamebutton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent onquitgamebuttonclick)
            {
                int response = JOptionPane.showConfirmDialog(null, "Are you sure you want to quit the game?" , "Really quit?" , JOptionPane.YES_NO_OPTION );
                if(response == JOptionPane.YES_OPTION)
                {
                System.exit(0);
            } 
            }
        });

        //Creating intro screen content pane and adding buttons to it


        JPanel gamebuttonsholder = new JPanel(new BorderLayout());
        gamebuttonsholder.setSize(400,100);
        gamebuttonsholder.add(introtext,BorderLayout.PAGE_START);
        gamebuttonsholder.add(startgamebutton,BorderLayout.PAGE_END);
        gamebuttonsholder.add(showinstructions,BorderLayout.LINE_START);
        gamebuttonsholder.add(highscoresbutton,BorderLayout.CENTER);
        gamebuttonsholder.add(quitgamebutton,BorderLayout.LINE_END);

        //Creating the screen itself and setting it visible


        introscreen = new JFrame("Tic Tac Toe");
        introscreen.setSize(400,400);
        introscreen.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        introscreen.setLocationRelativeTo(null);
        introscreen.add(gamebuttonsholder);
        introscreen.setVisible(true);
    }

    // Creating the method which powers the game ;

        public int tictactoe()
        {
            // ----=---------------------- CREATING ALL JBUTTONS ON THE GAME SCREEN AND ADDING ACTION LISTENERS AND REACTIONS -----------------------------------------

            b1 = new JButton("");
            b1.setToolTipText("Mark this box");
            b1.setFont(TToeFont);
            b1.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                   if (b1bool == true){ count++;
                    if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                b1.setText(letter);
                b1bool = false ;
                calculatevictory();
                processturn();

            }}});

            b2 = new JButton("");
            b2.setFont(TToeFont);
            b2.setToolTipText("Mark this box");
            b2.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                   if (b2bool == true){ count++;
                    if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                b2.setText(letter);
                b2bool = false ;
                calculatevictory();
                processturn();
            }}
                });


            b3 = new JButton("");
            b3.setToolTipText("Mark this box");
            b3.setFont(TToeFont);
            b3.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                     if(b3bool == true){
                         count++;
                    if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                b3.setText(letter);
                b3bool = false ;
                calculatevictory();
                processturn();
            }}

            });

            b4 = new JButton("");
            b4.setToolTipText("Mark this box");
            b4.setFont(TToeFont);
            b4.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                    if(b4bool == true){

                    count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b4.setText(letter);
                    b4bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            b5 = new JButton("");
            b5.setToolTipText("Mark this box");
            b5.setFont(TToeFont);
            b5.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                    if (b5bool == true){
                        count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b5.setText(letter);
                    b5bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            b6 = new JButton("");
            b6.setToolTipText("Mark this box");
            b6.setFont(TToeFont);
            b6.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                    if (b6bool == true){
                        count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b6.setText(letter);
                    b6bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            b7 = new JButton("");
            b7.setToolTipText("Mark this box");
            b7.setFont(TToeFont);
            b7.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                    if (b7bool == true){
                        count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b7.setText(letter);
                    b7bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            b8 = new JButton("");
            b8.setToolTipText("Mark this box");
            b8.setFont(TToeFont);
            b8.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                    if(b8bool == true){
                        count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b8.setText(letter);
                    b8bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            b9 = new JButton("");
            b9.setToolTipText("Mark this box");
            b9.setFont(TToeFont);
            b9.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent onclickb1)
                {
                   if(b9bool == true){
                       count++;
                        if(count == 1||count == 3 || count == 5 || count == 7 || count == 9 || count == 11)
                    {
                      letter = "O" ;

                }
                else if ( count == 2 || count == 4 || count == 6 || count == 8 || count == 10)
                {
                    letter = "X" ;                  

                }
                    b9.setText(letter);
                    b9bool = false ;
                    calculatevictory();
                    processturn();
                }}
            });

            //CREATING GAME SCREEN LAYOUT

            GridLayout gamescreenlayout = new GridLayout(3,3);
            //CREAING GAME SCREEN CONTENT PANE AND ADDING BUTTONS TO IT

            JPanel gamescreencontent = new JPanel();
            gamescreencontent.setLayout(gamescreenlayout);
            gamescreencontent.setSize(400,400);
            gamescreencontent.add(b1);
            gamescreencontent.add(b2);
            gamescreencontent.add(b3);
            gamescreencontent.add(b4);
            gamescreencontent.add(b5);
            gamescreencontent.add(b5);
            gamescreencontent.add(b6);
            gamescreencontent.add(b7);
            gamescreencontent.add(b8);
            gamescreencontent.add(b9);

            // CREATING GAME SCREEN AND SETTING IT VISIBLE


            gamescreen = new JFrame("Tic-Tac-Toe");
            gamescreen.setSize(400,400) ;
            gamescreen.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            gamescreen.setLocationRelativeTo(null);
            gamescreen.add(gamescreencontent);
            gamescreen.setVisible(true);

            int gamestuff = 1 ;
            return gamestuff  ;
        }

        // This method checks if someone won the game every time you click a button 

        public void calculatevictory(){
            if (b1.getText() == b2.getText() && b2.getText() == b3.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b4.getText() == b5.getText() && b5.getText() == b6.getText() && b4.getText() != "")
            {
                win = true ;
            }
            else if (b7.getText() == b8.getText() && b8.getText() == b9.getText() && b7.getText() != "")
            {
                win = true ;
            }
            else if (b1.getText() == b4.getText() && b4.getText() == b7.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b2.getText() == b5.getText() && b5.getText() == b8.getText() && b2.getText() != "")
            {
                win = true ;
            }
            else if (b3.getText() == b6.getText() && b6.getText() == b9.getText() && b3.getText() != "")
            {
                win = true ;
            }
            else if (b1.getText() == b5.getText() && b5.getText() == b9.getText() && b1.getText() != "")
            {
                win = true ;
            }
            else if (b3.getText() == b5.getText() && b5.getText() == b7.getText() && b3.getText() != "")
            {
                win = true ;
            }
            else 
            {
                win = false ;
            }
        }

        // This method sends the win message




            public void processturn()
            {
            if (win == true)
            {    
                int restart = JOptionPane.showConfirmDialog(null, letter + " wins! Play again? " ,letter + " Wins!" , JOptionPane.YES_NO_OPTION);
                if(restart == JOptionPane.NO_OPTION)
                {
                    gamescreen.dispose();                                        
                    resetgame();
                }
                else {
                    resetgame();
            }}
            else if ( count == 10 && win == false)
            {
                JOptionPane.showMessageDialog(null, "Tie Game");
                resetgame();
            }
        }
         // This method resets all game variables 

        public void resetgame()
        {
         count = 1 ; 

         b1bool = true ;
         b2bool = true ;
         b3bool = true ;
         b4bool = true ;
         b5bool = true ;
         b6bool = true ;
         b7bool = true ;
         b8bool = true ;
         b9bool = true ;
         win = false ;
         gameinit = true ;
         b1.setText("");
         b2.setText("");
         b3.setText("");
         b4.setText("");
         b5.setText("");
         b6.setText("");
         b7.setText("");
         b8.setText("");
         b9.setText("");
        }
    }



    // END OF PROGRAM

Here you go, an entire tic-tac-toe in less than 70 lines of code (in Scala - totally impossible to be this concise in many other languages). First argument to the application is a "true" or "false" to specify human first. Second argument is an integer that will control the depth in which the AI will think (how many moves ahead). Use coordinates to specify your move (starting at 0,0). The App will print the board each move and stop when the game has ended.

It uses a simple brute force lookahead AI, which by the way is very slow for the first couple of moves. You could optimize this either by:

  • Adding memoization so game trees are not recomputed
  • Adding a lookup table for opening moves
  • Just optimize the code, so using mutable data structures and such

code:

import scalaz.syntax.id._
import scala.collection.immutable.IndexedSeq
import scala.util.Try

case class Board(moves: List[(Int, Int)], maxDepth: Int = 4) {
  val grid: IndexedSeq[(Int, Int)] = (0 to 2).flatMap(i => (0 to 2).map((_, i)))

  def isThree(l: List[(Int, Int)], axis: ((Int, Int)) => Int): Boolean =
    l.map(_.swap |> axis).distinct.size == 3 && (l.map(axis).distinct.size == 1 ||
      l.map(axis) == l.map(_.swap |> axis) || l.map(axis) == l.map(_.swap |> axis).reverse)

  def playerWon(player: List[((Int, Int), Int)]): Boolean =
    player.map(_._1).combinations(3).map(_.sortBy(_._1)).exists(p => isThree(p, _._1) || isThree(p, _._2))

  def hasWinner: Boolean = {
    val (crosses, circles) = moves.zipWithIndex.partition(_._2 % 2 == 0)
    playerWon(crosses) || playerWon(circles)
  }

  def computerMove: Board = Board({
    val remainingMoves = grid.filterNot(moves.contains)
    var (mvOpt, badMoves, depth) = (Option.empty[(Int, Int)], Set.empty[(Int, Int)], 1)
    while (mvOpt.isEmpty && depth <= maxDepth) {
      val lookAheadFunc: ((Int, Int)) => Boolean = mv => Board(mv +: moves, maxDepth) |>
        List.fill[Board => Board](depth - 1)(_.computerMove).foldLeft[Board => Board](identity)(_ compose _) |>
        (_.hasWinner)
      if (remainingMoves.size >= depth) mvOpt = if (depth % 2 == 0) {
        badMoves ++= remainingMoves.filter(lookAheadFunc)
        val okMoves = remainingMoves.filterNot(badMoves)
        if (okMoves.size == 1) okMoves.headOption else None
      } else remainingMoves.filterNot(badMoves).find(lookAheadFunc)
      depth += 1
    }
    mvOpt.getOrElse(remainingMoves.filterNot(badMoves).headOption.getOrElse(remainingMoves.head))
  } +: moves, maxDepth)

  def print: Board = {
    (0 to 2).reverse.foreach(i => println((moves.reverse.zipWithIndex.map(p =>
      if (p._2 % 2 == 0) (p._1, "X") else (p._1, "O")) ++
      grid.filterNot(moves.contains).map((_, "B"))).filter(_._1._2 == i).sortBy(_._1._1).map(_._2).mkString))
    this
  }

  def humanAttempt: Try[Board] = Try({
    val Array(x, y) = readLine("Your move (of form x,y) $ ").split(",").map(_.toInt)
    require(!(x > 2 || x < 0 || y > 2 || y < 0 || moves.contains((x, y))), "Move not valid: " +(x, y))
    Board((x, y) +: moves, maxDepth)
  })

  def humanMove: Board = {
    var moveAttempt = humanAttempt
    while (moveAttempt.isFailure) {
      println("Bad move: " + moveAttempt.failed.get.getMessage)
      moveAttempt = humanAttempt
    }
    moveAttempt.get
  }
}

object TicTacToe extends App {
  var (board, humanNext) = (Board(Nil, args.drop(1).headOption.map(_.toInt).getOrElse(4)).print, args(0).toBoolean)
  while (!board.hasWinner && board.moves.size != 9) {
    board = (if (humanNext) board.humanMove else {
      println("Computer moving")
      board.computerMove
    }).print
    humanNext = !humanNext
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM