import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.lang.*; import java.util.*; import java.sql.*; public class Othellus extends JFrame implements ActionListener { public Othellus() { // make clock and initialize the GUI bClock = new Stopwatch(); initComponents(); // creates markers whiteIcon = createIcon("images/white.gif"); blackIcon = createIcon("images/black.gif"); // create a board image if (boardSize == 8) boardPicture.setIcon(createIcon("images/8x8.gif")); else if (boardSize == 12) boardPicture.setIcon(createIcon("images/12x12.gif")); else boardPicture.setIcon(createIcon("images/16x16.gif")); // create the players white = new Player(WHITE); black = new Player(BLACK); // underlying board matrix board = new Matrix(boardSize); // generator of utility values gen = new Generator(boardSize, heuristic); // generator and executor of possible moves mov = new Moves(boardSize); //evaluating performance eval = new Evaluator(boardSize); possMovs = mov.possibleMoves(black, board.getMatrix()); black.active(); bClock.start(); repaint(); } /* * Returns an ImageIcon, or null if the filename path was invalid. * @param path a String containing the path to the image to load * @return an ImageIcon, either null or a new instance */ private ImageIcon createIcon(String path) { java.net.URL imgURL = Othellus.class.getResource(path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } /* * Returns a Dimension, holding the window size * @return a Dimension */ private Dimension setWindowSize() { int pad_x = 275; int pad_y = 100; return new Dimension(DIM + pad_x, DIM + pad_y); } /* * Paints the current view * @param g a Graphics object */ public void paint(Graphics g) { // update the clocks wClockLabel.setText(time); bClockLabel.setText(bClock.getTimeString(bClock.getSeconds())); // update the scoring fields wScoreLabel.setText(whiteScore+""); bScoreLabel.setText(blackScore+""); // update the GUI whiteInfoPane.repaint(); blackInfoPane.repaint(); gameInfoPane.repaint(); logo.setIcon(createIcon("images/logo2.gif")); boardPicture.paint(g.create(37,79,DIM,DIM)); updateBoard(g); } /* * Paints the current board configuration * @param g a Graphics object */ private void updateBoard(Graphics g) { // padding values int addX=70, addY=112; if (whiteIcon != null && blackIcon != null) { double x = (boardPicture.getWidth() - (2 * cutoffX)) / boardSize; double y = (boardPicture.getHeight() - (2 * cutoffY)) / boardSize; // paint out all the markers for (int i = 0; i < boardSize; ++i) { for (int j = 0; j < boardSize; ++j) { if (board.getMatrix()[i][j] == WHITE) whiteIcon.paintIcon(boardPicture, g, (int)(x * i) + addX, (int)(y * j) + addY); else if (board.getMatrix()[i][j] == BLACK) blackIcon.paintIcon(boardPicture, g, (int)(x * i) + addX, (int)(y * j) + addY); } } } } /* * Prints a message in the game status window * @param message the message to be printed */ private void printMessage(String message) { screen.insert(message + "\n",0); } /* * Catches mouse clicks * @param evt an MouseEvent */ private void boardPictureMouseClicked(java.awt.event.MouseEvent evt) { // getting the X value int mouseX = evt.getX(); // getting the Y value int mouseY = evt.getY(); // calling our "action" function getPosition(mouseX, mouseY, boardSize); } /* * Calculating the current position of the click in the board and performs the move * @param m_x x coordinate of mouse click * @param m_y y coordinate of mouse click * @param bs the board size */ private void getPosition(int m_x, int m_y, int bs) { // is click within board? if (m_x > cutoffX && m_x < boardPicture.getWidth() - cutoffX && m_y > cutoffY && m_y < boardPicture.getHeight() - cutoffY) { double x, y; double curX, curY; boolean wEnd = false, bEnd = false; // booleans to determine if game is over x = boardPicture.getWidth(); y = boardPicture.getHeight(); x = (x - 2 * (double)cutoffX)/boardSize; y = (y - 2 * (double)cutoffY)/boardSize; // these variables hold the current position curX = (double)(m_x - cutoffX)/x; curX = Math.ceil(curX); curY = (double)(m_y - cutoffY)/y; curY = Math.ceil(curY); // the human player (black) makes the first move if (black.isActive() && board.getMatrix()[(int)curX-1][(int)curY-1] == EMPTY && mov.getPossM()[(int)curX-1][(int)curY-1] == POSSIBLE) { // the old board matrix, before the move int old[][]; do { // human moves if (possMovs != 0) { bEnd = false; turnNumber++; // flip markers won by human board.setMatrix(mov.flip(black, (int)(curX-1), (int)(curY-1), board.getMatrix())); // calculate scores Tuple t = board.countScore(board.getMatrix()); whiteScore = t.first; blackScore = t.second; printMessage("White to move."); } else { if ((whiteScore + blackScore) != 64) printMessage("Black cannot move. Pass."); bEnd = true; } black.inactive(); white.active(); bClock.stop(); paint(this.getGraphics()); // computer to move wClock = Calendar.getInstance(); long time1 = wClock.getTimeInMillis(); possMovs = mov.possibleMoves(white, board.getMatrix()); if (possMovs != 0) { wEnd = false; turnNumber++; old = board.getMatrix(); int oldWScore = whiteScore; int oldBScore = blackScore; // calculate best move with minimax & heuristic Position p = miniMaxAB(white, level, 0, 0, -1000, 1000, true); // flip markers won by computer board.setMatrix(mov.flip(white, p.x, p.y, board.getMatrix())); possMovs = mov.possibleMoves(white, board.getMatrix()); // calculate scores Tuple t = board.countScore(board.getMatrix()); whiteScore = t.first; blackScore = t.second; // evaluate taken move eval.evaluateMove(white,p,old,board.getMatrix(),possMovs,((whiteScore - oldWScore) - (blackScore - oldBScore))); printMessage("Black to move."); } else { if ((whiteScore + blackScore) != 64) printMessage("White cannot move. Pass."); wEnd = true; } wClock = Calendar.getInstance(); long time2 = wClock.getTimeInMillis(); wTime += (time2-time1); //System.out.println(wTime); //DEBUG: Measuring computer's thinking time for comparison time = bClock.getTimeString(wTime/1000); white.inactive(); black.active(); paint(this.getGraphics()); possMovs = mov.possibleMoves(black, board.getMatrix()); if (possMovs == 0) { // black cannot move bEnd = true; } else { bEnd = false; bClock.start(); } // has the game reached an endstate? if (bEnd && wEnd) { bClock.stop(); endGame(); break; } } while(!wEnd && bEnd); } } } /* * Compares the available moves, and by using minimax search with alpha-beta pruning together * with some heuristics, choose the (estimated) best one * @param player the active player * @param lookahead the depth of the search * @param x the current x position * @param y the current y position * @param alpha the alpha value (the highest utility found so far by Max) * @param beta the beta value (the lowest utility found so far by Min) * @param isMax boolean telling if Max is active (true), or if Min is (false) * @return the Position for the (estimated) best move */ public Position miniMaxAB(Player pl, int lookahead, int x, int y, int alpha, int beta, boolean isMax) { Position pos = new Position(); int tempBoard[][] = new int[boardSize][boardSize]; int tempBoard2[][] = new int[boardSize][boardSize]; for (int i = 0; i < boardSize; ++i) { for (int j = 0; j < boardSize; ++j) { tempBoard[i][j] = board.getMatrix()[i][j]; tempBoard2[i][j] = mov.getPossM()[i][j]; } } //final ply reached, end search by calculating the utility if (lookahead == 0) { if (isMax) { pos.value = gen.getUtility(pl, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } else { if (pl.getColor() == WHITE) { pos.value = gen.getUtility(black, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } else { pos.value = gen.getUtility(white, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } } return pos; } int numMoves = mov.possibleMoves(pl, board.getMatrix()); // if there are no moves left, calculate the utility of the current leaf if (numMoves == 0) { if (isMax) { pos.value = gen.getUtility(pl, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } else { if (pl.getColor() == WHITE) { pos.value = gen.getUtility(black, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } else { pos.value = gen.getUtility(white, board.getMatrix(), mov.getPossM(), x, y, turnNumber); } } return pos; } // the x and y coordinates for the so far best found move int bestX = 0; int bestY = 0; if (isMax) { //max play for (int i = 0; i < boardSize; ++i) { for (int j = 0; j < boardSize; ++j) { if (mov.getPossM()[i][j] == POSSIBLE) { board.setMatrix(mov.flip(pl, i, j, board.getMatrix())); Position newPos; if (pl.getColor() == WHITE) { newPos = miniMaxAB(black, lookahead-1, i, j, alpha, beta, !isMax); } else { newPos = miniMaxAB(white, lookahead-1, i, j, alpha, beta, !isMax); } mov.setMatrix(tempBoard2); board.setMatrix(tempBoard); if (newPos.value > alpha) { alpha = newPos.value; bestX = i; bestY = j; if (alpha >= beta) { pos.value = beta; pos.x = bestX; pos.y = bestY; return pos; } } } } } pos.value = alpha; pos.x = bestX; pos.y = bestY; mov.setMatrix(tempBoard2); board.setMatrix(tempBoard); return pos; } else { //min play for (int i = 0; i < boardSize; ++i) { for (int j = 0; j < boardSize; ++j) { if (mov.getPossM()[i][j] == POSSIBLE) { board.setMatrix(mov.flip(pl, i, j, board.getMatrix())); Position newPos; if (pl.getColor() == WHITE) { newPos = miniMaxAB(black, lookahead-1, i ,j ,alpha, beta, !isMax); } else { newPos = miniMaxAB(white, lookahead-1, i, j, alpha, beta, !isMax); } mov.setMatrix(tempBoard2); board.setMatrix(tempBoard); if (newPos.value < beta) { beta = newPos.value; bestX = i; bestY = j; if (alpha >= beta) { pos.value = alpha; pos.x = bestX; pos.y = bestY; return pos; } } } } } pos.value = beta; pos.x = bestX; pos.y = bestY; mov.setMatrix(tempBoard2); board.setMatrix(tempBoard); return pos; } } /* * Initializes all the javax.swing components */ public void initComponents() { setTitle("O-Thell-Us"); setBackground(Color.white); setDefaultLookAndFeelDecorated(true); setResizable(false); java.awt.GridBagConstraints gridBagConstraints; windowPane = new JPanel(); windowPane.setPreferredSize(setWindowSize()); windowPane.setBackground(Color.white); whiteInfoPane = new JPanel(); whiteInfoPane.setBackground(Color.white); boardPicture = new JLabel(); logo = new JLabel(); wElapsedTime = new JLabel(); bElapsedTime = new JLabel(); wScoreLabel = new JLabel(); bScoreLabel = new JLabel(); // two different clocks wClockLabel = new JLabel(time); bClockLabel = new JLabel(bClock.getTimeString(bClock.getSeconds())); wScoreText = new JLabel(); bScoreText = new JLabel(); blackInfoPane = new JPanel(); blackInfoPane.setBackground(Color.white); screen = new JTextArea("Welcome to O-Thell-Us...",3,12); screen.setEditable(false); screen.setBorder(new javax.swing.border.SoftBevelBorder(1, Color.black, Color.black)); screen.setPreferredSize(new Dimension(84, 51)); screen.setMinimumSize(new Dimension(84, 51)); java.awt.GridBagConstraints gridBagConstraints2; gameInfoPane = new JPanel(); gameInfoPane.setBackground(Color.white); gameInfoPane.setBorder(new javax.swing.border.TitledBorder("Game Info")); quitButton = new JButton(); //Register a listener for the game controls. quitButton.addActionListener(this); boardPicture.addMouseListener( new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { boardPictureMouseClicked(evt); } }); addWindowListener( new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { exitForm(evt); } }); windowPane.setLayout(new java.awt.GridBagLayout()); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.gridheight = 4; gridBagConstraints.insets = new Insets(10,10,10,10); gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; windowPane.add(boardPicture, gridBagConstraints); // adding the game's logo logo.setIcon(createIcon("images/logo2.gif")); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; windowPane.add(logo, gridBagConstraints); // white's information area whiteInfoPane.setLayout(new java.awt.GridBagLayout()); whiteInfoPane.setBorder(new javax.swing.border.TitledBorder("White")); wElapsedTime.setText("Elapsed Time"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new Insets(0,10,0,0); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; whiteInfoPane.add(wElapsedTime, gridBagConstraints); wScoreText.setText("Score"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; gridBagConstraints.insets = new Insets(0,10,0,0); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; whiteInfoPane.add(wScoreText, gridBagConstraints); wClockLabel.setBackground(Color.white); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new Insets(10,10,10,10); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.ipadx = 1; whiteInfoPane.add(wClockLabel, gridBagConstraints); wScoreLabel.setText(whiteScore+""); wScoreLabel.setBackground(Color.white); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 2; gridBagConstraints.insets = new Insets(10,10,10,10); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.ipadx = 2; whiteInfoPane.add(wScoreLabel, gridBagConstraints); // adding white's statistics gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; windowPane.add(whiteInfoPane, gridBagConstraints); // black's information area blackInfoPane.setLayout(new java.awt.GridBagLayout()); blackInfoPane.setBorder(new javax.swing.border.TitledBorder("Black")); bElapsedTime.setText("Elapsed Time"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.insets = new Insets(0,10,0,0); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; blackInfoPane.add(bElapsedTime, gridBagConstraints); bScoreText.setText("Score"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new Insets(0,10,0,0); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; blackInfoPane.add(bScoreText, gridBagConstraints); bClockLabel.setBackground(Color.white); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.insets = new Insets(10,10,10,10); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; blackInfoPane.add(bClockLabel, gridBagConstraints); bScoreLabel.setText(blackScore+""); bScoreLabel.setBackground(Color.white); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new Insets(10,10,10,10); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; blackInfoPane.add(bScoreLabel, gridBagConstraints); // adding black's statistics gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 2; windowPane.add(blackInfoPane, gridBagConstraints); gameInfoPane.setLayout(new java.awt.GridBagLayout()); gridBagConstraints2 = new java.awt.GridBagConstraints(); gridBagConstraints2.gridx = 0; gridBagConstraints2.gridy = 0; gridBagConstraints2.ipadx = 25; gridBagConstraints2.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints2.insets = new Insets(10, 4, 10, 0); gameInfoPane.add(screen, gridBagConstraints2); // adding quit button quitButton.setText("Quit"); gridBagConstraints2 = new java.awt.GridBagConstraints(); gridBagConstraints2.gridx = 0; gridBagConstraints2.gridy = 1; gridBagConstraints2.ipadx = 25; gridBagConstraints2.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints2.insets = new Insets(10, 4, 10, 0); gameInfoPane.add(quitButton, gridBagConstraints2); // add game controls gridBagConstraints2 = new java.awt.GridBagConstraints(); gridBagConstraints2.gridx = 1; gridBagConstraints2.gridy = 3; windowPane.add(gameInfoPane, gridBagConstraints2); getContentPane().add(windowPane, java.awt.BorderLayout.CENTER); pack(); setVisible(true); } /* * End the game and print out some information and statistics */ private void endGame() { white.inactive(); black.inactive(); int diff = whiteScore - blackScore; if (diff > 0) printMessage("Game over! \nWhite wins by " + diff + " points"); else if (diff < 0) printMessage("Game over! \nBlack wins by " + Math.abs(diff) + " points"); else printMessage("Game over! \nIt's a draw!"); eval.printStats(); } /* * Catch an exit request * @param evt a WindowEvent */ private void exitForm(java.awt.event.WindowEvent evt) { System.exit(0); } /* * Catches the actions form the interface * @param e an ActionEvent */ public void actionPerformed(ActionEvent e) { if(e.getSource() == quitButton) { System.exit(0); } } /* * The main function * @param args the command line arguments */ public static void main(String args[]) { new Othellus().show(); } // JLabels private JLabel wElapsedTime;// white's elapsed time text private JLabel bElapsedTime;// black's elapsed time text private JLabel bScoreLabel; // black's score private JLabel wScoreLabel; // white's score private JLabel wScoreText; // white's score text private JLabel bScoreText; // black's score text private JLabel logo; // game logo private JLabel wClockLabel; // white's clock private JLabel bClockLabel; // black's clock private JLabel boardPicture;// the board // JPanels private JPanel windowPane; private JPanel whiteInfoPane; private JPanel blackInfoPane; private JPanel gameInfoPane; // JTextarea - game info window private JTextArea screen; // JButton - quit the game private JButton quitButton; private double cutoffX = 32; // 8: 33, 32; 12: 32, 34; 16: 34, 34 private double cutoffY = 32; // 8: 32, 32; 12: 32, 32; 16: 32, 33 private static final int BLACK = 0; private static final int WHITE = 1; private static final int EMPTY = 2; private static final int POSSIBLE = 3; private static final int DIM = 465; // the board picture dimension (for 8 x 8) private int possMovs = 0; private int whiteScore; private int blackScore; private int turnNumber = 0; private ImageIcon whiteIcon; private ImageIcon blackIcon; // the two players private Player white; private Player black; private Matrix board; private Generator gen; private Moves mov; private Evaluator eval; // Clocks for measuring elapsed time, two different variants tested here private Stopwatch bClock; private Calendar wClock; private String time = "00:00:00"; private long wTime; /********************** User settings *************************/ private int boardSize = 8; // the board size private int level = 3; // the minimax search depth (up to 10 is easily possible, 7 is suggested as highest for quick play) private int heuristic = 5; // heuristic choice: // 1 - Calculates the player utility score by its position and mobility // 2 - Calculates the player utility score by its position only // 3 - Combines the checkNeighbour with utility score by position and mobility // 4 - Combines the checkNeighbour with utility score by position only // 5 - Combines the enlargeCornerArea with utility score by position and mobility // 6 - Combines the enlargeCornerArea with utility score by position only // 7 - Take the move that flips most of the opponent's squares // 8 - Takes the move that minimizes the score on the surrounding squares /******************** End user settings ***********************/ } // class for holding a position and its current utility value class Position { int value = 0; int x; int y; }