diff --git a/CuckooChess/.classpath b/CuckooChess/.classpath new file mode 100644 index 0000000..48158d7 --- /dev/null +++ b/CuckooChess/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/CuckooChess/.externalToolBuilders/jar_builder.launch b/CuckooChess/.externalToolBuilders/jar_builder.launch new file mode 100644 index 0000000..a646598 --- /dev/null +++ b/CuckooChess/.externalToolBuilders/jar_builder.launch @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CuckooChess/.project b/CuckooChess/.project new file mode 100644 index 0000000..e39bad1 --- /dev/null +++ b/CuckooChess/.project @@ -0,0 +1,28 @@ + + + CuckooChess + + + CuckooChessEngine + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/jar_builder.launch + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/CuckooChess/.settings/org.eclipse.jdt.core.prefs b/CuckooChess/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..44f8e5d --- /dev/null +++ b/CuckooChess/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Sat May 08 15:39:56 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs b/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 0000000..3c96de9 --- /dev/null +++ b/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,3 @@ +#Sun May 09 18:44:06 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/CuckooChess/build_jar.xml b/CuckooChess/build_jar.xml new file mode 100644 index 0000000..4cf251c --- /dev/null +++ b/CuckooChess/build_jar.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CuckooChess/src/gui/AppletGUI.form b/CuckooChess/src/gui/AppletGUI.form new file mode 100644 index 0000000..22a7dc0 --- /dev/null +++ b/CuckooChess/src/gui/AppletGUI.form @@ -0,0 +1,309 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/CuckooChess/src/gui/AppletGUI.java b/CuckooChess/src/gui/AppletGUI.java new file mode 100644 index 0000000..afec545 --- /dev/null +++ b/CuckooChess/src/gui/AppletGUI.java @@ -0,0 +1,440 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package gui; + +import guibase.ChessController; +import guibase.GUIInterface; + +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +import chess.ComputerPlayer; +import chess.Move; +import chess.Position; + +/** + * The main class for the chess GUI. + * @author petero + */ +public class AppletGUI extends javax.swing.JApplet implements GUIInterface { + private static final long serialVersionUID = 7357610346389734323L; + ChessBoardPainter cbp; + ChessController ctrl; + final static int ttLogSize = 19; // Use 2^19 hash entries. + String moveListStr = ""; + String thinkingStr = ""; + + /** Initializes the applet AppletGUI */ + @Override + public void init() { + ctrl = new ChessController(this); + try { + java.awt.EventQueue.invokeAndWait(new Runnable() { + public void run() { + initComponents(); + cbp = (ChessBoardPainter)ChessBoard; + ctrl.newGame(PlayerWhite.isSelected(), ttLogSize, true); + ctrl.startGame(); + } + }); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + /** + * Entry point for the GUI version of the chess program. + */ + public static void main(String[] args) { + javax.swing.JApplet theApplet = new AppletGUI(); + theApplet.init(); + javax.swing.JFrame window = new javax.swing.JFrame(ComputerPlayer.engineName); + window.setContentPane(theApplet); + window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); + window.pack(); + window.setVisible(true); + } + + /** This method is called from within the init() method to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + private void initComponents() { + + PlayerColor = new javax.swing.ButtonGroup(); + MainPanel = new javax.swing.JPanel(); + ChessBoardPanel = new javax.swing.JPanel(); + ChessBoard = new ChessBoardPainter(); + jPanel1 = new javax.swing.JPanel(); + NewGame = new javax.swing.JButton(); + SettingsPanel = new javax.swing.JPanel(); + PlayerWhite = new javax.swing.JRadioButton(); + PlayerBlack = new javax.swing.JRadioButton(); + TimeLabel = new javax.swing.JLabel(); + TimeSlider = new javax.swing.JSlider(); + ShowThinking = new javax.swing.JCheckBox(); + FlipBoard = new javax.swing.JCheckBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + LogTextArea = new javax.swing.JTextPane(); + StatusLine = new javax.swing.JTextField(); + Forward = new javax.swing.JButton(); + Backward = new javax.swing.JButton(); + + ChessBoardPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); + ChessBoardPanel.setPreferredSize(new java.awt.Dimension(500, 500)); + + ChessBoard.addMouseListener(new java.awt.event.MouseAdapter() { + public void mousePressed(java.awt.event.MouseEvent evt) { + ChessBoardMousePressed(evt); + } + public void mouseReleased(java.awt.event.MouseEvent evt) { + ChessBoardMouseReleased(evt); + } + }); + ChessBoard.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { + public void mouseDragged(java.awt.event.MouseEvent evt) { + ChessBoardMouseDragged(evt); + } + }); + + javax.swing.GroupLayout ChessBoardPanelLayout = new javax.swing.GroupLayout(ChessBoardPanel); + ChessBoardPanel.setLayout(ChessBoardPanelLayout); + ChessBoardPanelLayout.setHorizontalGroup( + ChessBoardPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ChessBoard, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE) + ); + ChessBoardPanelLayout.setVerticalGroup( + ChessBoardPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ChessBoard, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE) + ); + + jPanel1.setFocusable(false); + + NewGame.setText("New Game"); + NewGame.setFocusable(false); + NewGame.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + NewGameActionPerformed(evt); + } + }); + + SettingsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Settings")); + SettingsPanel.setFocusable(false); + + PlayerColor.add(PlayerWhite); + PlayerWhite.setSelected(true); + PlayerWhite.setText("Play White"); + PlayerWhite.setFocusable(false); + + PlayerColor.add(PlayerBlack); + PlayerBlack.setText("Play Black"); + PlayerBlack.setFocusable(false); + + TimeLabel.setText("Thinking Time"); + + TimeSlider.setMajorTickSpacing(15); + TimeSlider.setMaximum(60); + TimeSlider.setMinorTickSpacing(5); + TimeSlider.setPaintLabels(true); + TimeSlider.setPaintTicks(true); + TimeSlider.setValue(5); + TimeSlider.setFocusable(false); + TimeSlider.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + TimeSliderStateChanged(evt); + } + }); + + ShowThinking.setText("Show Thinking"); + ShowThinking.setFocusable(false); + ShowThinking.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + ShowThinkingStateChanged(evt); + } + }); + + FlipBoard.setText("Flip Board"); + FlipBoard.setFocusable(false); + FlipBoard.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + FlipBoardStateChanged(evt); + } + }); + + javax.swing.GroupLayout SettingsPanelLayout = new javax.swing.GroupLayout(SettingsPanel); + SettingsPanel.setLayout(SettingsPanelLayout); + SettingsPanelLayout.setHorizontalGroup( + SettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ShowThinking, javax.swing.GroupLayout.PREFERRED_SIZE, 157, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(SettingsPanelLayout.createSequentialGroup() + .addComponent(PlayerWhite) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 104, Short.MAX_VALUE) + .addComponent(FlipBoard) + .addContainerGap()) + .addGroup(SettingsPanelLayout.createSequentialGroup() + .addComponent(TimeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(TimeSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(PlayerBlack) + ); + SettingsPanelLayout.setVerticalGroup( + SettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(SettingsPanelLayout.createSequentialGroup() + .addGroup(SettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(PlayerWhite) + .addComponent(FlipBoard)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(PlayerBlack) + .addGap(18, 18, 18) + .addGroup(SettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(TimeLabel) + .addComponent(TimeSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ShowThinking) + .addContainerGap()) + ); + + LogTextArea.setEditable(false); + LogTextArea.setVerifyInputWhenFocusTarget(false); + jScrollPane1.setViewportView(LogTextArea); + + StatusLine.setEditable(false); + StatusLine.setFocusable(false); + + Forward.setText("->"); + Forward.setDefaultCapable(false); + Forward.setFocusPainted(false); + Forward.setFocusable(false); + Forward.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + ForwardActionPerformed(evt); + } + }); + + Backward.setText("<-"); + Backward.setDefaultCapable(false); + Backward.setFocusPainted(false); + Backward.setFocusable(false); + Backward.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + BackwardActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 319, Short.MAX_VALUE) + .addComponent(StatusLine, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 319, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(NewGame) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(Backward) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(Forward)) + .addComponent(SettingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(SettingsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(NewGame) + .addComponent(Forward) + .addComponent(Backward)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(StatusLine, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + + javax.swing.GroupLayout MainPanelLayout = new javax.swing.GroupLayout(MainPanel); + MainPanel.setLayout(MainPanelLayout); + MainPanelLayout.setHorizontalGroup( + MainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(MainPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(ChessBoardPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 502, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + MainPanelLayout.setVerticalGroup( + MainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, MainPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(MainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(ChessBoardPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 502, Short.MAX_VALUE)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(MainPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(MainPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void ChessBoardMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_ChessBoardMousePressed + if (ctrl.humansTurn()) { + int sq = cbp.eventToSquare(evt); + Move m = cbp.mousePressed(sq); + if (m != null) { + ctrl.humanMove(m); + } + } + }//GEN-LAST:event_ChessBoardMousePressed + + private void FlipBoardStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_FlipBoardStateChanged + cbp.setFlipped(FlipBoard.isSelected()); + }//GEN-LAST:event_FlipBoardStateChanged + + private void NewGameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_NewGameActionPerformed + ctrl.newGame(PlayerWhite.isSelected(), ttLogSize, true); + ctrl.startGame(); + }//GEN-LAST:event_NewGameActionPerformed + + private void ShowThinkingStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_ShowThinkingStateChanged + ctrl.setMoveList(); + }//GEN-LAST:event_ShowThinkingStateChanged + + private void BackwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_BackwardActionPerformed + ctrl.takeBackMove(); + }//GEN-LAST:event_BackwardActionPerformed + + private void ForwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ForwardActionPerformed + ctrl.redoMove(); + }//GEN-LAST:event_ForwardActionPerformed + + private void TimeSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_TimeSliderStateChanged + ctrl.setTimeLimit(); + }//GEN-LAST:event_TimeSliderStateChanged + + private void ChessBoardMouseDragged(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_ChessBoardMouseDragged + if (ctrl.humansTurn()) { + cbp.mouseDragged(evt); + } + }//GEN-LAST:event_ChessBoardMouseDragged + + private void ChessBoardMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_ChessBoardMouseReleased + if (ctrl.humansTurn()) { + int sq = cbp.eventToSquare(evt); + Move m = cbp.mouseReleased(sq); + if (m != null) { + ctrl.humanMove(m); + } + } + }//GEN-LAST:event_ChessBoardMouseReleased + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton Backward; + private javax.swing.JLabel ChessBoard; + private javax.swing.JPanel ChessBoardPanel; + private javax.swing.JCheckBox FlipBoard; + private javax.swing.JButton Forward; + private javax.swing.JTextPane LogTextArea; + private javax.swing.JPanel MainPanel; + private javax.swing.JButton NewGame; + private javax.swing.JRadioButton PlayerBlack; + private javax.swing.ButtonGroup PlayerColor; + private javax.swing.JRadioButton PlayerWhite; + private javax.swing.JPanel SettingsPanel; + private javax.swing.JCheckBox ShowThinking; + private javax.swing.JTextField StatusLine; + private javax.swing.JLabel TimeLabel; + private javax.swing.JSlider TimeSlider; + private javax.swing.JPanel jPanel1; + private javax.swing.JScrollPane jScrollPane1; + // End of variables declaration//GEN-END:variables + + public void setPosition(Position pos) { + cbp.setPosition(pos); + } + + public void setSelection(int sq) { + cbp.setSelection(sq); + } + + public void setStatusString(String str) { + StatusLine.setText(str); + } + + public void setMoveListString(String str) { + moveListStr = str; + str = moveListStr + "\n" + thinkingStr; + if (!str.equals(LogTextArea.getText())) { + LogTextArea.setText(str); + } + } + + public void setThinkingString(String str) { + thinkingStr = str; + str = moveListStr + "\n" + thinkingStr; + if (!str.equals(LogTextArea.getText())) { + LogTextArea.setText(str); + } + } + + + public final int timeLimit() { + return Math.max(25, TimeSlider.getValue() * 1000); + } + + public final boolean showThinking() { + return ShowThinking.isSelected(); + } + + public void requestPromotePiece() { + runOnUIThread(new Runnable() { + public void run() { + Object[] options = { "Queen", "Rook", "Bishop", "Knight" }; + int choice = JOptionPane.showOptionDialog( + cbp, "Promote pawn to?", "Pawn Promotion", + 0, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + ctrl.reportPromotePiece(choice); + } + }); + } + + public void runOnUIThread(Runnable runnable) { + SwingUtilities.invokeLater(runnable); + } + + @Override + public boolean randomMode() { + return false; + } + + @Override + public void reportInvalidMove(Move m) { + } +} diff --git a/CuckooChess/src/gui/ChessBoardPainter.java b/CuckooChess/src/gui/ChessBoardPainter.java new file mode 100644 index 0000000..4cc784d --- /dev/null +++ b/CuckooChess/src/gui/ChessBoardPainter.java @@ -0,0 +1,292 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package gui; + +import chess.Move; +import chess.Piece; +import chess.Position; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.MouseEvent; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.InputStream; +import javax.swing.JLabel; + +/** + * Draws a graphical chess board. Also handles user interaction. + * @author petero + */ +public class ChessBoardPainter extends JLabel { + private static final long serialVersionUID = -1319250011487017825L; + private Position pos; + private int selectedSquare; + private int x0, y0, sqSize; + private boolean flipped; + private Font chessFont; + + // For piece animation during dragging + private int activeSquare; + private boolean dragging; + private int dragX; + private int dragY; + private boolean cancelSelection; + + ChessBoardPainter() { + pos = new Position(); + selectedSquare = -1; + x0 = y0 = sqSize = 0; + flipped = false; + activeSquare = -1; + } + + /** + * Set the board to a given state. + * @param pos + */ + final public void setPosition(Position pos) { + this.pos = pos; + repaint(); + } + + /** + * Set/clear the board flipped status. + * @param flipped + */ + final public void setFlipped(boolean flipped) { + this.flipped = flipped; + repaint(); + } + + /** + * Set/clear the selected square. + * @param square The square to select, or -1 to clear selection. + */ + final public void setSelection(int square) { + if (square != this.selectedSquare) { + this.selectedSquare = square; + repaint(); + } + } + + @Override + public void paint(Graphics g0) { + Graphics2D g = (Graphics2D)g0; + Dimension size = getSize(); + sqSize = (Math.min(size.width, size.height) - 4) / 8; + x0 = (size.width - sqSize * 8) / 2; + y0 = (size.height - sqSize * 8) / 2; + + boolean doDrag = (activeSquare >= 0) && dragging; + + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + final int xCrd = getXCrd(x); + final int yCrd = getYCrd(y); + g.setColor(Position.darkSquare(x, y) ? Color.GRAY : new Color(190, 190, 90)); + g.fillRect(xCrd, yCrd, sqSize, sqSize); + + int sq = Position.getSquare(x, y); + int p = pos.getPiece(sq); + if (doDrag && (sq == activeSquare)) { + // Skip this piece. It will be drawn later at (dragX,dragY) + } else { + drawPiece(g, xCrd + sqSize / 2, yCrd + sqSize / 2, p); + } + } + } + if (selectedSquare >= 0) { + int selX = Position.getX(selectedSquare); + int selY = Position.getY(selectedSquare); + g.setColor(Color.RED); + g.setStroke(new BasicStroke(3)); + g.drawRect(getXCrd(selX), getYCrd(selY), sqSize, sqSize); + } + if (doDrag) { + int p = pos.getPiece(activeSquare); + drawPiece(g, dragX, dragY, p); + } + } + + private final void drawPiece(Graphics2D g, int xCrd, int yCrd, int p) { + g.setColor(Piece.isWhite(p) ? Color.WHITE : Color.BLACK); + String ps; + switch (p) { + case Piece.EMPTY: + ps = ""; + break; + case Piece.WKING: + ps = "k"; + break; + case Piece.WQUEEN: + ps = "q"; + break; + case Piece.WROOK: + ps = "r"; + break; + case Piece.WBISHOP: + ps = "b"; + break; + case Piece.WKNIGHT: + ps = "n"; + break; + case Piece.WPAWN: + ps = "p"; + break; + case Piece.BKING: + ps = "l"; + break; + case Piece.BQUEEN: + ps = "w"; + break; + case Piece.BROOK: + ps = "t"; + break; + case Piece.BBISHOP: + ps = "v"; + break; + case Piece.BKNIGHT: + ps = "m"; + break; + case Piece.BPAWN: + ps = "o"; + break; + default: + ps = "?"; + break; + } + if (ps.length() > 0) { + FontRenderContext frc = g.getFontRenderContext(); + if ((chessFont == null) || (chessFont.getSize() != sqSize)) { + InputStream inStream = getClass().getResourceAsStream("/gui/casefont.ttf"); + try { + Font font = Font.createFont(Font.TRUETYPE_FONT, inStream); + chessFont = font.deriveFont((float)sqSize); + } catch (FontFormatException ex) { + throw new RuntimeException(); + } catch (IOException ex) { + throw new RuntimeException(); + } + } + g.setFont(chessFont); + Rectangle2D textRect = g.getFont().getStringBounds(ps, frc); + int xCent = (int)textRect.getCenterX(); + int yCent = (int)textRect.getCenterY(); + g.drawString(ps, xCrd - xCent, yCrd - yCent); + } + } + + private final int getXCrd(int x) { + return x0 + sqSize * (flipped ? 7 - x : x); + } + private final int getYCrd(int y) { + return y0 + sqSize * (flipped ? y : (7 - y)); + } + + /** + * Compute the square corresponding to the coordinates of a mouse event. + * @param evt Details about the mouse event. + * @return The square corresponding to the mouse event, or -1 if outside board. + */ + final int eventToSquare(MouseEvent evt) { + int xCrd = evt.getX(); + int yCrd = evt.getY(); + + int sq = -1; + if ((xCrd >= x0) && (yCrd >= y0) && (sqSize > 0)) { + int x = (xCrd - x0) / sqSize; + int y = 7 - (yCrd - y0) / sqSize; + if ((x >= 0) && (x < 8) && (y >= 0) && (y < 8)) { + if (flipped) { + x = 7 - x; + y = 7 - y; + } + sq = Position.getSquare(x, y); + } + } + return sq; + } + + final Move mousePressed(int sq) { + Move m = null; + cancelSelection = false; + int p = pos.getPiece(sq); + if ((selectedSquare >= 0) && (sq == selectedSquare)) { + int fromPiece = pos.getPiece(selectedSquare); + if ((fromPiece == Piece.EMPTY) || (Piece.isWhite(fromPiece) != pos.whiteMove)) { + return m; // Can't move the piece the oppenent just moved. + } + } + if ((selectedSquare < 0) && + ((p == Piece.EMPTY) || (Piece.isWhite(p) != pos.whiteMove))) { + return m; // You must click on one of your own pieces. + } + activeSquare = sq; + dragging = false; + dragX = dragY = -1; + + if (selectedSquare >= 0) { + if (sq == selectedSquare) { + cancelSelection = true; + } else { + if ((p == Piece.EMPTY) || (Piece.isWhite(p) != pos.whiteMove)) { + m = new Move(selectedSquare, sq, Piece.EMPTY); + activeSquare = -1; + setSelection(sq); + } + } + } + if (m == null) { + setSelection(-1); + } + return m; + } + + final void mouseDragged(MouseEvent evt) { + final int xCrd = evt.getX(); + final int yCrd = evt.getY(); + if (!dragging || (dragX != xCrd) || (dragY != yCrd)) { + dragging = true; + dragX = xCrd; + dragY = yCrd; + repaint(); + } + } + + final Move mouseReleased(int sq) { + Move m = null; + if (activeSquare >= 0) { + if (sq != activeSquare) { + m = new Move(activeSquare, sq, Piece.EMPTY); + setSelection(sq); + } else if (!cancelSelection) { + setSelection(sq); + } + activeSquare = -1; + repaint(); + } + return m; + } +} diff --git a/CuckooChess/src/gui/casefont.ttf b/CuckooChess/src/gui/casefont.ttf new file mode 100644 index 0000000..61c12d6 Binary files /dev/null and b/CuckooChess/src/gui/casefont.ttf differ diff --git a/CuckooChess/src/tui/Main.java b/CuckooChess/src/tui/Main.java new file mode 100644 index 0000000..4d01b9a --- /dev/null +++ b/CuckooChess/src/tui/Main.java @@ -0,0 +1,52 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package tui; + +import java.io.IOException; + +import chess.ComputerPlayer; +import chess.HumanPlayer; +import chess.Player; +import chess.TreeLogger; + +/** + * + * @author petero + */ +public class Main { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws IOException { + if ((args.length == 1) && args[0].equals("gui")) { + gui.AppletGUI.main(args); + } else if ((args.length == 1) && args[0].equals("txt")) { + Player whitePlayer = new HumanPlayer(); + ComputerPlayer blackPlayer = new ComputerPlayer(); + blackPlayer.setTTLogSize(21); + TUIGame game = new TUIGame(whitePlayer, blackPlayer); + game.play(); + } else if ((args.length == 2) && args[0].equals("tree")) { + TreeLogger.main(new String[]{args[1]}); + } else { + uci.UCIProtocol.main(false); + } + } +} diff --git a/CuckooChess/src/tui/TUIGame.java b/CuckooChess/src/tui/TUIGame.java new file mode 100644 index 0000000..ae304b1 --- /dev/null +++ b/CuckooChess/src/tui/TUIGame.java @@ -0,0 +1,216 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package tui; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; + +import uci.UCIProtocol; +import chess.ChessParseError; +import chess.ComputerPlayer; +import chess.Evaluate; +import chess.Game; +import chess.Move; +import chess.Player; +import chess.Position; +import chess.TextIO; +import chess.TwoReturnValues; + +public class TUIGame extends Game { + + public TUIGame(Player whitePlayer, Player blackPlayer) { + super(whitePlayer, blackPlayer); + } + + protected boolean handleCommand(String moveStr) { + if (super.handleCommand(moveStr)) + return true; + if (moveStr.startsWith("testsuite ")) { + String testSuiteCmd = moveStr.substring(moveStr.indexOf(" ") + 1); + return handleTestSuite(testSuiteCmd); + } else if (moveStr.equals("uci")) { + whitePlayer = null; + blackPlayer = null; + UCIProtocol.main(true); + System.exit(0); + return false; + } else if (moveStr.equals("help")) { + showHelp(); + return true; + } + + return false; + } + + private void showHelp() { + System.out.println("Enter a move, or one of the following special commands:"); + System.out.println(" new - Start a new game"); + System.out.println(" undo - Undo last half-move"); + System.out.println(" redo - Redo next half-move"); + System.out.println(" swap - Swap sides"); + System.out.println(" go - Same as swap"); + System.out.println(" list - List all moves in current game"); + System.out.println(" setpos FEN - Set a position using a FEN string"); + System.out.println(" getpos - Print current position in FEN notation"); + System.out.println(" draw rep [move] - Claim draw by repetition"); + System.out.println(" draw 50 [move] - Claim draw by 50-move rule"); + System.out.println(" draw offer move - Play move and offer draw"); + System.out.println(" draw accept - Accept a draw offer"); + System.out.println(" resign - Resign the current game"); + System.out.println(" testsuite filename maxtime"); + System.out.println(" book on|off - Turn opening book on/off"); + System.out.println(" time t - Set computer thinking time, ms"); + System.out.println(" perft d - Run perft test to depth d"); + System.out.println(" uci - Switch to uci protocol."); + System.out.println(" help - Show this help"); + System.out.println(" quit - Terminate program"); + } + + private boolean handleTestSuite(String cmd) { + LineNumberReader fr = null; + try { + int idx = cmd.indexOf(" "); + String filename = cmd.substring(0, idx); + String timeStr = cmd.substring(idx + 1, cmd.length()); + int timeLimit = Integer.parseInt(timeStr); + // System.out.printf("file:%s time:%s (%d)\n", filename, timeStr, timeLimit); + fr = new LineNumberReader(new FileReader(filename)); + String line; + Player pl = whitePlayer.isHumanPlayer() ? blackPlayer : whitePlayer; + if (pl.isHumanPlayer()) { + System.out.printf("No computer player available"); + return false; + } + ComputerPlayer cp = (ComputerPlayer)pl; + int numRight = 0; + int numTotal = 0; + while ((line = fr.readLine()) != null) { + if (line.startsWith("#") || (line.length() == 0)) { + continue; + } + int idx1 = line.indexOf(" bm "); + String fen = line.substring(0, idx1); + int idx2 = line.indexOf(";", idx1); + String bm = line.substring(idx1 + 4, idx2); + // System.out.printf("Line %3d: fen:%s bm:%s\n", fr.getLineNumber(), fen, bm); + Position testPos = TextIO.readFEN(fen); + cp.clearTT(); + TwoReturnValues ret = cp.searchPosition(testPos, timeLimit); + Move sm = ret.first; + String PV = ret.second; + Move m = new Move(sm); + String[] answers = bm.split(" "); + boolean correct = false; + for (String a : answers) { + Move am = TextIO.stringToMove(testPos, a); + if (am == null) { + throw new ChessParseError("Invalid move " + a); + } + if (am.equals(m)) { + correct = true; + break; + } + } + if (correct) { + numRight++; + } + numTotal++; + System.out.printf("%3d : %6s %6d %d %03d/%03d %s : %s\n", fr.getLineNumber(), + TextIO.moveToString(testPos, sm, false), sm.score, correct ? 1 : 0, + numRight, numTotal, bm, PV); + } + fr.close(); + } catch (NumberFormatException nfe) { + System.out.printf("Number format exception: %s\n", nfe.getMessage()); + return false; + } catch (FileNotFoundException fnfe) { + System.out.printf("File not found: %s\n", fnfe.getMessage()); + return false; + } catch (IOException ex) { + System.out.printf("IO error: %s\n", ex.getMessage()); + } catch (ChessParseError cpe) { + int lineNo = (fr == null) ? -1 : fr.getLineNumber(); + System.out.printf("Parse error, line %d: %s\n", lineNo, cpe.getMessage()); + } catch (StringIndexOutOfBoundsException e) { + int lineNo = (fr == null) ? -1 : fr.getLineNumber(); + System.out.printf("Parse error, line %d: %s\n", lineNo, e.getMessage()); + } finally { + if (fr != null) { + try { + fr.close(); + } catch (IOException ex) { + // Stupid FileReader class forces me to catch this meaningless exception + } + } + } + return true; + } + + /** + * Administrate a game between two players, human or computer. + */ + public void play() throws IOException { + handleCommand("new"); + while (true) { + // Print last move + if (currentMove > 0) { + Position prevPos = new Position(pos); + prevPos.unMakeMove(moveList.get(currentMove - 1), uiInfoList.get(currentMove - 1)); + String moveStr= TextIO.moveToString(prevPos, moveList.get(currentMove - 1), false); + if (haveDrawOffer()) { + moveStr += " (offer draw)"; + } + String msg = String.format("Last move: %d%s %s", + prevPos.fullMoveCounter, prevPos.whiteMove ? "." : "...", + moveStr); + System.out.println(msg); + } +// System.out.printf("Hash: %016x\n", pos.zobristHash()); + { + Evaluate eval = new Evaluate(); + int evScore = eval.evalPos(pos) * (pos.whiteMove ? 1 : -1); + System.out.printf("Eval: %.2f%n", evScore / 100.0); + } + + // Check game state + System.out.print(TextIO.asciiBoard(pos)); + String stateStr = getGameStateString(); + if (stateStr.length() > 0) { + System.out.printf("%s%n", stateStr); + } + if (getGameState() != GameState.ALIVE) { + activateHumanPlayer(); + } + + // Get command from current player and act on it + Player pl = pos.whiteMove ? whitePlayer : blackPlayer; + String moveStr = pl.getCommand(new Position(pos), haveDrawOffer(), getHistory()); + if (moveStr.equals("quit")) { + return; + } else { + boolean ok = processString(moveStr); + if (!ok) { + System.out.printf("Invalid move: %s\n", moveStr); + } + } + } + } +} diff --git a/CuckooChess/src/uci/EngineControl.java b/CuckooChess/src/uci/EngineControl.java new file mode 100644 index 0000000..b38461e --- /dev/null +++ b/CuckooChess/src/uci/EngineControl.java @@ -0,0 +1,435 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package uci; + +import chess.Book; +import chess.ComputerPlayer; +import chess.Move; +import chess.MoveGen; +import chess.Parameters; +import chess.Piece; +import chess.Position; +import chess.Search; +import chess.TextIO; +import chess.TranspositionTable; +import chess.Parameters.CheckParam; +import chess.Parameters.ComboParam; +import chess.Parameters.ParamBase; +import chess.Parameters.SpinParam; +import chess.Parameters.StringParam; +import chess.TranspositionTable.TTEntry; +import chess.UndoInfo; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * Control the search thread. + * @author petero + */ +public class EngineControl { + PrintStream os; + + Thread engineThread; + private final Object threadMutex; + Search sc; + TranspositionTable tt; + MoveGen moveGen; + + Position pos; + long[] posHashList; + int posHashListSize; + boolean ponder; // True if currently doing pondering + boolean onePossibleMove; + boolean infinite; + + int minTimeLimit; + int maxTimeLimit; + int maxDepth; + int maxNodes; + List searchMoves; + + // Options + int hashSizeMB = 16; + boolean ownBook = false; + boolean analyseMode = false; + boolean ponderMode = true; + + // Reduced strength variables + int strength = 1000; + long randomSeed = 0; + + /** + * This class is responsible for sending "info" strings during search. + */ + private static class SearchListener implements Search.Listener { + PrintStream os; + + SearchListener(PrintStream os) { + this.os = os; + } + + public void notifyDepth(int depth) { + os.printf("info depth %d%n", depth); + } + + public void notifyCurrMove(Move m, int moveNr) { + os.printf("info currmove %s currmovenumber %d%n", moveToString(m), moveNr); + } + + public void notifyPV(int depth, int score, int time, int nodes, int nps, boolean isMate, + boolean upperBound, boolean lowerBound, ArrayList pv) { + StringBuilder pvBuf = new StringBuilder(); + for (Move m : pv) { + pvBuf.append(" "); + pvBuf.append(moveToString(m)); + } + String bound = ""; + if (upperBound) { + bound = " upperbound"; + } else if (lowerBound) { + bound = " lowerbound"; + } + os.printf("info depth %d score %s %d%s time %d nodes %d nps %d pv%s%n", + depth, isMate ? "mate" : "cp", score, bound, time, nodes, nps, pvBuf.toString()); + } + + public void notifyStats(int nodes, int nps, int time) { + os.printf("info nodes %d nps %d time %d%n", nodes, nps, time); + } + } + + public EngineControl(PrintStream os) { + this.os = os; + threadMutex = new Object(); + setupTT(); + moveGen = new MoveGen(); + } + + final public void startSearch(Position pos, ArrayList moves, SearchParams sPar) { + setupPosition(new Position(pos), moves); + computeTimeLimit(sPar); + ponder = false; + infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); + startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes); + searchMoves = sPar.searchMoves; + } + + final public void startPonder(Position pos, List moves, SearchParams sPar) { + setupPosition(new Position(pos), moves); + computeTimeLimit(sPar); + ponder = true; + infinite = false; + startThread(-1, -1, -1, -1); + } + + final public void ponderHit() { + Search mySearch; + synchronized (threadMutex) { + mySearch = sc; + } + if (mySearch != null) { + if (onePossibleMove) { + if (minTimeLimit > 1) minTimeLimit = 1; + if (maxTimeLimit > 1) maxTimeLimit = 1; + } + mySearch.timeLimit(minTimeLimit, maxTimeLimit); + } + infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); + ponder = false; + } + + final public void stopSearch() { + stopThread(); + } + + final public void newGame() { + randomSeed = new Random().nextLong(); + tt.clear(); + } + + /** + * Compute thinking time for current search. + */ + final public void computeTimeLimit(SearchParams sPar) { + minTimeLimit = -1; + maxTimeLimit = -1; + maxDepth = -1; + maxNodes = -1; + if (sPar.infinite) { + minTimeLimit = -1; + maxTimeLimit = -1; + maxDepth = -1; + } else if (sPar.depth > 0) { + maxDepth = sPar.depth; + } else if (sPar.mate > 0) { + maxDepth = sPar.mate * 2 - 1; + } else if (sPar.moveTime > 0) { + minTimeLimit = maxTimeLimit = sPar.moveTime; + } else if (sPar.nodes > 0) { + maxNodes = sPar.nodes; + } else { + int moves = sPar.movesToGo; + if (moves == 0) { + moves = 999; + } + moves = Math.min(moves, 45); // Assume 45 more moves until end of game + if (ponderMode) { + final double ponderHitRate = 0.35; + moves = (int)Math.ceil(moves * (1 - ponderHitRate)); + } + boolean white = pos.whiteMove; + int time = white ? sPar.wTime : sPar.bTime; + int inc = white ? sPar.wInc : sPar.bInc; + final int margin = Math.min(1000, time * 9 / 10); + int timeLimit = (time + inc * (moves - 1) - margin) / moves; + minTimeLimit = (int)(timeLimit * 0.85); + maxTimeLimit = (int)(minTimeLimit * (Math.max(2.5, Math.min(4.0, moves / 2)))); + + // Leave at least 1s on the clock, but can't use negative time + minTimeLimit = clamp(minTimeLimit, 1, time - margin); + maxTimeLimit = clamp(maxTimeLimit, 1, time - margin); + } + } + + private static final int clamp(int val, int min, int max) { + if (val < min) { + return min; + } else if (val > max) { + return max; + } else { + return val; + } + } + + final private void startThread(final int minTimeLimit, final int maxTimeLimit, + int maxDepth, final int maxNodes) { + synchronized (threadMutex) {} // Must not start new search until old search is finished + sc = new Search(pos, posHashList, posHashListSize, tt); + sc.timeLimit(minTimeLimit, maxTimeLimit); + sc.setListener(new SearchListener(os)); + sc.setStrength(strength, randomSeed); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if ((searchMoves != null) && (searchMoves.size() > 0)) { + Arrays.asList(moves.m).retainAll(searchMoves); + } + final MoveGen.MoveList srchMoves = moves; + onePossibleMove = false; + if ((srchMoves.size < 2) && !infinite) { + onePossibleMove = true; + if (!ponder) { + if ((maxDepth < 0) || (maxDepth > 2)) maxDepth = 2; + } + } + tt.nextGeneration(); + final int srchmaxDepth = maxDepth; + engineThread = new Thread(new Runnable() { + public void run() { + Move m = null; + if (ownBook && !analyseMode) { + Book book = new Book(false); + m = book.getBookMove(pos); + } + if (m == null) { + m = sc.iterativeDeepening(srchMoves, srchmaxDepth, maxNodes, false); + } + while (ponder || infinite) { + // We should not respond until told to do so. Just wait until + // we are allowed to respond. + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + break; + } + } + Move ponderMove = getPonderMove(pos, m); + synchronized (threadMutex) { + if (ponderMove != null) { + os.printf("bestmove %s ponder %s%n", moveToString(m), moveToString(ponderMove)); + } else { + os.printf("bestmove %s%n", moveToString(m)); + } + engineThread = null; + sc = null; + } + } + }); + engineThread.start(); + } + + private final void stopThread() { + Thread myThread; + Search mySearch; + synchronized (threadMutex) { + myThread = engineThread; + mySearch = sc; + } + if (myThread != null) { + mySearch.timeLimit(0, 0); + infinite = false; + ponder = false; + try { + myThread.join(); + } catch (InterruptedException ex) { + throw new RuntimeException(); + } + } + } + + + private final void setupTT() { + int nEntries = hashSizeMB > 0 ? hashSizeMB * (1 << 20) / 24 : 1024; + int logSize = (int) Math.floor(Math.log(nEntries) / Math.log(2)); + tt = new TranspositionTable(logSize); + } + + private final void setupPosition(Position pos, List moves) { + UndoInfo ui = new UndoInfo(); + posHashList = new long[200 + moves.size()]; + posHashListSize = 0; + for (Move m : moves) { + posHashList[posHashListSize++] = pos.zobristHash(); + pos.makeMove(m, ui); + } + this.pos = pos; + } + + /** + * Try to find a move to ponder from the transposition table. + */ + private final Move getPonderMove(Position pos, Move m) { + if (m == null) return null; + Move ret = null; + UndoInfo ui = new UndoInfo(); + pos.makeMove(m, ui); + TTEntry ent = tt.probe(pos.historyHash()); + if (ent.type != TTEntry.T_EMPTY) { + ret = new Move(0, 0, 0); + ent.getMove(ret); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + boolean contains = false; + for (int mi = 0; mi < moves.size; mi++) + if (moves.m[mi].equals(ret)) { + contains = true; + break; + } + if (!contains) + ret = null; + } + pos.unMakeMove(m, ui); + return ret; + } + + private static final String moveToString(Move m) { + if (m == null) + return "0000"; + String ret = TextIO.squareToString(m.from); + ret += TextIO.squareToString(m.to); + switch (m.promoteTo) { + case Piece.WQUEEN: + case Piece.BQUEEN: + ret += "q"; + break; + case Piece.WROOK: + case Piece.BROOK: + ret += "r"; + break; + case Piece.WBISHOP: + case Piece.BBISHOP: + ret += "b"; + break; + case Piece.WKNIGHT: + case Piece.BKNIGHT: + ret += "n"; + break; + default: + break; + } + return ret; + } + + static void printOptions(PrintStream os) { + os.printf("option name Hash type spin default 16 min 1 max 2048%n"); + os.printf("option name OwnBook type check default false%n"); + os.printf("option name Ponder type check default true%n"); + os.printf("option name UCI_AnalyseMode type check default false%n"); + os.printf("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html%n", + ComputerPlayer.engineName); + os.printf("option name Strength type spin default 1000 min 0 max 1000\n"); + + for (String pName : Parameters.instance().getParamNames()) { + ParamBase p = Parameters.instance().getParam(pName); + switch (p.type) { + case CHECK: { + CheckParam cp = (CheckParam)p; + os.printf("optionn name %s type check default %s\n", + p.name, cp.defaultValue?"true":"false"); + break; + } + case SPIN: { + SpinParam sp = (SpinParam)p; + os.printf("option name %s type spin default %d min %d max %d\n", + p.name, sp.defaultValue, sp.minValue, sp.maxValue); + break; + } + case COMBO: { + ComboParam cp = (ComboParam)p; + os.printf("option name %s type combo default %s ", cp.name, cp.defaultValue); + for (String s : cp.allowedValues) + os.printf(" var %s", s); + os.printf("\n"); + break; + } + case BUTTON: + os.printf("option name %s type button\n", p.name); + break; + case STRING: { + StringParam sp = (StringParam)p; + os.printf("option name %s type string default %s\n", + p.name, sp.defaultValue); + break; + } + } + } + } + + final void setOption(String optionName, String optionValue) { + try { + if (optionName.equals("hash")) { + hashSizeMB = Integer.parseInt(optionValue); + setupTT(); + } else if (optionName.equals("ownbook")) { + ownBook = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("ponder")) { + ponderMode = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("uci_analysemode")) { + analyseMode = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("strength")) { + strength = Integer.parseInt(optionValue); + } else { + Parameters.instance().set(optionName, optionValue); + } + } catch (NumberFormatException nfe) { + } + } +} diff --git a/CuckooChess/src/uci/SearchParams.java b/CuckooChess/src/uci/SearchParams.java new file mode 100644 index 0000000..6e5866f --- /dev/null +++ b/CuckooChess/src/uci/SearchParams.java @@ -0,0 +1,45 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package uci; + +import chess.Move; +import java.util.ArrayList; +import java.util.List; + +/** + * Store search parameters (times, increments, max depth, etc). + * @author petero + */ +public class SearchParams { + List searchMoves; // If non-empty, search only these moves + int wTime; // White remaining time, ms + int bTime; // Black remaining time, ms + int wInc; // White increment per move, ms + int bInc; // Black increment per move, ms + int movesToGo; // Moves to next time control + int depth; // If >0, don't search deeper than this + int nodes; // If >0, don't search more nodes than this + int mate; // If >0, search for mate-in-x + int moveTime; // If >0, search for exactly this amount of time, ms + boolean infinite; + + public SearchParams() { + searchMoves = new ArrayList(); + } +} diff --git a/CuckooChess/src/uci/UCIProtocol.java b/CuckooChess/src/uci/UCIProtocol.java new file mode 100644 index 0000000..4c8a5f7 --- /dev/null +++ b/CuckooChess/src/uci/UCIProtocol.java @@ -0,0 +1,222 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package uci; + +import chess.ChessParseError; +import chess.ComputerPlayer; +import chess.Move; +import chess.Position; +import chess.TextIO; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.ArrayList; + +/** + * Handle the UCI protocol mode. + * @author petero + */ +public class UCIProtocol { + // Data set by the "position" command. + Position pos; + ArrayList moves; + + // Engine data + EngineControl engine; + + // Set to true to break out of main loop + boolean quit; + + + public static void main(boolean autoStart) { + UCIProtocol uciProt = new UCIProtocol(); + uciProt.mainLoop(System.in, System.out, autoStart); + } + + public UCIProtocol() { + pos = null; + moves = new ArrayList(); + quit = false; + } + + final public void mainLoop(InputStream is, PrintStream os, boolean autoStart) { + try { + if (autoStart) { + handleCommand("uci", os); + } + InputStreamReader inStrRead = new InputStreamReader(is); + BufferedReader inBuf = new BufferedReader(inStrRead); + String line; + while ((line = inBuf.readLine()) != null) { + handleCommand(line, os); + if (quit) { + break; + } + } + } catch (IOException ex) { + // If stream closed or other I/O error, terminate program + } + } + + final void handleCommand(String cmdLine, PrintStream os) { + String[] tokens = tokenize(cmdLine); + try { + String cmd = tokens[0]; + if (cmd.equals("uci")) { + os.printf("id name %s%n", ComputerPlayer.engineName); + os.printf("id author Peter Osterlund%n"); + EngineControl.printOptions(os); + os.printf("uciok%n"); + } else if (cmd.equals("isready")) { + initEngine(os); + os.printf("readyok%n"); + } else if (cmd.equals("setoption")) { + initEngine(os); + StringBuilder optionName = new StringBuilder(); + StringBuilder optionValue = new StringBuilder(); + if (tokens[1].endsWith("name")) { + int idx = 2; + while ((idx < tokens.length) && !tokens[idx].equals("value")) { + optionName.append(tokens[idx++].toLowerCase()); + optionName.append(' '); + } + if ((idx < tokens.length) && tokens[idx++].equals("value")) { + while ((idx < tokens.length)) { + optionValue.append(tokens[idx++].toLowerCase()); + optionValue.append(' '); + } + } + engine.setOption(optionName.toString().trim(), optionValue.toString().trim()); + } + } else if (cmd.equals("ucinewgame")) { + if (engine != null) { + engine.newGame(); + } + } else if (cmd.equals("position")) { + String fen = null; + int idx = 1; + if (tokens[idx].equals("startpos")) { + idx++; + fen = TextIO.startPosFEN; + } else if (tokens[idx].equals("fen")) { + idx++; + StringBuilder sb = new StringBuilder(); + while ((idx < tokens.length) && !tokens[idx].equals("moves")) { + sb.append(tokens[idx++]); + sb.append(' '); + } + fen = sb.toString().trim(); + } + if (fen != null) { + pos = TextIO.readFEN(fen); + moves.clear(); + if ((idx < tokens.length) && tokens[idx++].equals("moves")) { + for (int i = idx; i < tokens.length; i++) { + Move m = TextIO.uciStringToMove(tokens[i]); + if (m != null) { + moves.add(m); + } else { + break; + } + } + } + } + } else if (cmd.equals("go")) { + if (pos == null) { + try { + pos = TextIO.readFEN(TextIO.startPosFEN); + } catch (ChessParseError ex) { + throw new RuntimeException(); + } + } + initEngine(os); + int idx = 1; + SearchParams sPar = new SearchParams(); + boolean ponder = false; + while (idx < tokens.length) { + String subCmd = tokens[idx++]; + if (subCmd.equals("searchmoves")) { + while (idx < tokens.length) { + Move m = TextIO.uciStringToMove(tokens[idx]); + if (m != null) { + sPar.searchMoves.add(m); + idx++; + } else { + break; + } + } + } else if (subCmd.equals("ponder")) { + ponder = true; + } else if (subCmd.equals("wtime")) { + sPar.wTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("btime")) { + sPar.bTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("winc")) { + sPar.wInc = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("binc")) { + sPar.bInc = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("movestogo")) { + sPar.movesToGo = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("depth")) { + sPar.depth = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("nodes")) { + sPar.nodes = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("mate")) { + sPar.mate = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("movetime")) { + sPar.moveTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("infinite")) { + sPar.infinite = true; + } + } + if (ponder) { + engine.startPonder(pos, moves, sPar); + } else { + engine.startSearch(pos, moves, sPar); + } + } else if (cmd.equals("stop")) { + engine.stopSearch(); + } else if (cmd.equals("ponderhit")) { + engine.ponderHit(); + } else if (cmd.equals("quit")) { + if (engine != null) { + engine.stopSearch(); + } + quit = true; + } + } catch (ChessParseError ex) { + } catch (ArrayIndexOutOfBoundsException e) { + } catch (NumberFormatException nfe) { + } + } + + final private void initEngine(PrintStream os) { + if (engine == null) { + engine = new EngineControl(os); + } + } + + /** Convert a string to tokens by splitting at whitespace characters. */ + final String[] tokenize(String cmdLine) { + cmdLine = cmdLine.trim(); + return cmdLine.split("\\s+"); + } +} diff --git a/CuckooChess/test/uci/UCIProtocolTest.java b/CuckooChess/test/uci/UCIProtocolTest.java new file mode 100644 index 0000000..b29a4b4 --- /dev/null +++ b/CuckooChess/test/uci/UCIProtocolTest.java @@ -0,0 +1,68 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package uci; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class UCIProtocolTest { + + public UCIProtocolTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of tokenize method, of class UCIProtocol. + */ + @Test + public void testTokenize() { + System.out.println("tokenize"); + UCIProtocol uci = new UCIProtocol(); + String[] result = uci.tokenize(" a b c de \t \t fgh"); + assertEquals(5, result.length); + assertEquals("a", result[0]); + assertEquals("b", result[1]); + assertEquals("c", result[2]); + assertEquals("de", result[3]); + assertEquals("fgh", result[4]); + } +}