Kalahiprogramm
Allikas: Lambda
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * * The program stub for playing Kalah against a computer. * * Computer performs only trivial moves: always the first available move. * * This program is intended to be used as a basis for building a sensibly playing * computer opponent: see the end part of the file. * * Copyright Tanel Tammet, 2007 * This code is in public domain: do whatever you like with this code. * * Uses some UI visual design ideas (colors, button settings) for checkers by D.Eck * */ /* Some assumptions/observations: The position in a game is stored in an integer array from 0 to 13, with stores being 6 and 13: 12 11 10 9 8 7 13 6 0 1 2 3 4 5 Several options are selected by the human player and then stored in the variables: int nrInHouse : the initial nr of seeds in houses lowerSmaller : whether the position array is visualised as above, or vice versa humanLower : human has the lower array of houses humanStarts : human starts the game nextHuman : the next move is done by human, not computer */ public class Kalah { // UI variables JFrame window; JPanel mainPanel; JPanel messagePanel; Board board; JPanel buttonPanel; JButton newGameButton; JButton resignButton; JLabel message; JLabel bmessage1; JLabel bmessage2; JLabel bmessage3; JComboBox nrCombo; JComboBox humanCombo; JComboBox rowCombo; JCheckBox houseNrsBox; // convenient global variables int nrInHouse = 0; boolean lowerSmaller=true; boolean humanLower=true; boolean humanStarts=true; // ongoing game variables boolean gameInProgress=true; boolean nextHuman=true; int[] position; String compMoveText; String humanMoveText; String lastMoveText; public static void main(String[] args) { Kalah kalah = new Kalah(); // create an instance of Kalah, just to keep everything non-static kalah.initGame(); // create position and fill it kalah.makeUserInterface(); // make user interface components and show out } public void initGame() { int i; position = new int[14]; // create the position to be filled for(i=0;i<position.length;i++) position[i]=0; // initially fill position with zeros } public void makeUserInterface() { /* Create the window, then components and add them to the window */ Color bgColor = new Color(0,150,0); window = new JFrame("Kalah"); mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.setPreferredSize( new Dimension(500,300) ); mainPanel.setBackground(bgColor); // Dark green background. board = new Board(); buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout()); buttonPanel.setBackground(bgColor); messagePanel = new JPanel(); messagePanel.setLayout(new FlowLayout()); messagePanel.setBackground(bgColor); resignButton = new JButton("Resign"); resignButton.addActionListener(board); newGameButton = new JButton("New"); newGameButton.addActionListener(board); String[] numberInHouse = { "3", "4", "5", "6" }; nrCombo= new JComboBox(numberInHouse); String[] humanStart = { "You", "Comp" }; humanCombo= new JComboBox(humanStart); String[] rowStart = { "Lower", "Upper" }; rowCombo= new JComboBox(rowStart); bmessage1 = new JLabel("start on",JLabel.CENTER); bmessage1.setFont(new Font("Serif", Font.BOLD, 12)); bmessage1.setForeground(Color.black); bmessage2 = new JLabel("row with seeds",JLabel.CENTER); bmessage2.setFont(new Font("Serif", Font.BOLD, 12)); bmessage2.setForeground(Color.black); buttonPanel.add(humanCombo); buttonPanel.add(bmessage1); buttonPanel.add(rowCombo); buttonPanel.add(bmessage2); buttonPanel.add(nrCombo); buttonPanel.add(newGameButton); buttonPanel.add(resignButton); houseNrsBox = new JCheckBox("show nrs"); houseNrsBox.setSelected(false); houseNrsBox.setBackground(bgColor); houseNrsBox.addActionListener(board); messagePanel.add(houseNrsBox); message = new JLabel(" ",JLabel.CENTER); message.setFont(new Font("Serif", Font.BOLD, 14)); message.setForeground(Color.green); message.setBounds(10,160,300,30); messagePanel.add(message); mainPanel.add(BorderLayout.CENTER,board); mainPanel.add(BorderLayout.NORTH,messagePanel); mainPanel.add(BorderLayout.SOUTH,buttonPanel); // initialise UI board.gameOver("Press 'New' to start the game!"); // set window dimensions and properties window.setContentPane(mainPanel); window.pack(); Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize(); window.setLocation( (screensize.width - window.getWidth())/2, (screensize.height - window.getHeight())/2 ); window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); window.setResizable(false); window.setVisible(true); } /** * * This (internal) panel displays a kalah board and handles mouse- and keypresses * */ class Board extends Canvas implements ActionListener, MouseListener { /** * Constructor. Basic initialisation. */ Board() { Color bgColor = new Color(0,150,0); setBackground(bgColor); addMouseListener(this); } /** * Respond to user's click on one of the two buttons. */ public void actionPerformed(ActionEvent evt) { Object src = evt.getSource(); if (src == newGameButton) doNewGame(); else if (src == resignButton) doResign(); else if (src == houseNrsBox) repaint(); } /** * Start a new game */ void doNewGame() { if (gameInProgress == true) { // This should not be possible, but it doens't hurt to check. message.setText("Finish the current game first!"); return; } message.setText("Make your move."); gameInProgress = true; nrInHouse=3+nrCombo.getSelectedIndex(); if (humanCombo.getSelectedIndex()==0) humanStarts=true; else humanStarts=false; if (rowCombo.getSelectedIndex()==0) { humanLower=humanStarts; } else { humanLower=!humanStarts; } newGameButton.setEnabled(false); nrCombo.setEnabled(false); humanCombo.setEnabled(false); rowCombo.setEnabled(false); resignButton.setEnabled(true); lastMoveText=null; initPosition(nrInHouse); if (houseNrsBox.isSelected()) System.out.println("------"); if (houseNrsBox.isSelected()) printPosition(); if (humanStarts) { nextHuman=true; } else { nextHuman=false; doMachineMove(); } repaint(); } /* * Initialise position array to correct nr * */ void initPosition(int n) { int i; position[6]=0; position[13]=0; for(i=0;i<6;i++) position[i]=n; for(i=7;i<13;i++) position[i]=n; } /** * Human player resigns. Game ends. Machine wins. */ void doResign() { if (gameInProgress == false) { message.setText("There is no game in progress!"); return; } gameOver("You resign. Computer wins."); } /** * The game ends. */ void gameOver(String str) { message.setText(str); newGameButton.setEnabled(true); nrCombo.setEnabled(true); humanCombo.setEnabled(true); rowCombo.setEnabled(true); resignButton.setEnabled(false); gameInProgress = false; if (houseNrsBox.isSelected()) printPosition(); if (houseNrsBox.isSelected()) System.out.println(str); } /** * This is called by mousePressed() when a player clicks on the * house. */ void doClickHouse(int n) { //message.setText("Click the house you want to move from."); //System.out.println(n); if (position[n]>0) doHumanMove(n); else message.setText("You cannot move from the empty house!"); repaint(); } /** * This does all the position drawing * */ public void paint(Graphics g) { int x,n; Color inside = new Color(255,255,255); Color bground = new Color(210,210,210); Color numColor = new Color(20,20,20); Font numFont = new Font("SansSerif", Font.BOLD, 20); Font numSmallFont = new Font("SansSerif", Font.BOLD, 16); Font numMicroFont = new Font("SansSerif", Font.PLAIN, 10); // create empty board g.setColor(bground); g.fillRoundRect(30,50,440,100,10,10); g.setColor(inside); g.fillOval(47,75,50,50); g.fillOval(400,75,50,50); for(x=117,n=0; n<6; n++, x=x+46) { g.fillOval(x,59,35,35); g.fillOval(x,105,35,35); } // fill board with numbers, show house nrs if required g.setColor(numColor); g.setFont(numFont); if (lowerSmaller) { g.drawString(nrToStr(position[13]),62,107); g.drawString(nrToStr(position[6]),415,107); for(x=129,n=0; n<6; n++, x=x+46) { g.setFont(numSmallFont); g.drawString(nrToStr(position[n]),x,129); g.setFont(numMicroFont); if (houseNrsBox.isSelected()) g.drawString(n+"",x,150); } for(x=129,n=12; n>6; n--, x=x+46) { g.setFont(numSmallFont); g.drawString(nrToStr(position[n]),x,83); g.setFont(numMicroFont); if (houseNrsBox.isSelected()) g.drawString(n+"",x,60); } } else { g.drawString(nrToStr(position[6]),62,107); g.drawString(nrToStr(position[13]),415,107); g.setFont(numSmallFont); for(x=129,n=7; n<13; n++, x=x+46) { g.setFont(numSmallFont); g.drawString(nrToStr(position[n]),x,129); g.setFont(numMicroFont); if (houseNrsBox.isSelected()) g.drawString(n+"",x,150); } for(x=129,n=5; n>=0; n--, x=x+46) { g.setFont(numSmallFont); g.drawString(nrToStr(position[n]),x,83); g.setFont(numMicroFont); if (houseNrsBox.isSelected()) g.drawString(n+"",x,60); } } //System.out.println("gameInProgress: "+gameInProgress+" nextHuman:"+nextHuman+" lastMoveText:"+lastMoveText ); if (gameInProgress && nextHuman && lastMoveText!=null) { if (houseNrsBox.isSelected()) message.setText("Last move was "+lastMoveText); else message.setText("Make your move"); } else if (gameInProgress && !nextHuman) { message.setText("Computer thinking ..."); message.revalidate(); } } /** * Small helper fun used by paint() * */ public String nrToStr(int n) { if (n<1) return ""; else return n+""; } /* * Position printing on console * */ void printPosition() { int i; System.out.println("Position: "); for(i=13;i>6;i--) { System.out.print(nrToPrintStr(position[i])+" "); } System.out.println(); System.out.print(" "); for(i=0;i<7;i++) { System.out.print(nrToPrintStr(position[i])+" "); } System.out.println(); } /** * Small helper fun used by printPosition() * */ public String nrToPrintStr(int n) { if (n<10) return " "+n; else return ""+n; } /** * Respond to a user click on the board. If no game is in progress, show * an error message. Otherwise, find the house that the user * clicked and call doClickHouse(n) to handle it. */ /* position array visualisation: lowerSmaller: 12 11 10 9 8 7 13 6 0 1 2 3 4 5 !lowerSmaller: 5 4 3 2 1 0 6 13 7 8 9 10 11 12 */ public void mousePressed(MouseEvent evt) { if (gameInProgress == false) message.setText("Click on the 'New' button to start a new game."); else if (!nextHuman) { message.setText("Computer's turn!"); } else { int col = (evt.getX() - 113) / 46; //System.out.println(evt.getX()+"|"+evt.getY()+"|"+col); if (humanLower && col>=0 && col<6 && evt.getY()>100 && evt.getY()<150) { if (lowerSmaller) { doClickHouse(col); } else { doClickHouse(col+7); } } else if (!humanLower && col>=0 && col<6 && evt.getY()>50 && evt.getY()<100) { if (lowerSmaller) { doClickHouse(12-col); } else { doClickHouse(5-col); } } } } /** * No actions registered * */ public void mouseReleased(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } /* *************************************************** * * Making human and machine moves: improve here!! * * calcMove() below is the actual function to be improved * *****************************************************/ /** * doHumanMove performs the move of the user (mouse click) * and then calls doMachineMove() which computes machine move * * NB! In case the next move should also be done by human, * doMachineMove() is not called: instead the human should click again * */ void doHumanMove(int n) { boolean onemore; int res; if (!gameInProgress) return; humanMoveText=n+"("+position[n]+")"; lastMoveText=humanMoveText; onemore=doMove(n); if (houseNrsBox.isSelected()) System.out.println("Human: "+humanMoveText); if (houseNrsBox.isSelected()) printPosition(); if (!onemore) nextHuman=false; else nextHuman=true; if (checkEnd()) { res=humanResult(); if (res==0) gameOver("Game ended with a draw"); else if (res>0) gameOver("You won the game with "+res+" points!"); else { gameOver("I won the game with "+(0-res)+" points!"); } } repaint(); if (!nextHuman) doMachineMove(); // here the machine calculates its move! } /** * doMachineMove() performs machine move. It first checks the position, * then calls the calcMove() which actually calculates the machine move, then performs the move, * does printout etc. * * NB! If the machine can (should) make several moves in a row, then all these moves * are performed separately inside a loop here. calcMove() will always just give one single move, * not a sequence of moves! * */ void doMachineMove() { boolean onemore=true; int i; int res; int n=0; if (!gameInProgress) return; // check if game already ended: just in case if (checkEnd()) { res=humanResult(); if (res==0) { gameOver("Game ended with a draw"); onemore=false; } else if (res>0) { gameOver("You won the game with "+res+" points!"); onemore=false; } else { gameOver("I won the game with "+(0-res)+" points!"); onemore=false; } } // Show that computer is thinking if (houseNrsBox.isSelected()) System.out.println("Computer thinking ..."); nextHuman=false; message.setText("Computer thinking ..."); message.revalidate(); repaint(); // calculate one or more moves: currently just the first legal move is taken compMoveText=null; while(onemore) { // find a possible move (one must be available, otherwise game should have ended) n = calcMove(); if (compMoveText==null) compMoveText=""; else compMoveText=compMoveText+"-"; compMoveText=compMoveText+n+"("+position[n]+")"; // actually do the move onemore=doMove(n); if (checkEnd()) { res=humanResult(); if (res==0) { gameOver("Game ended with a draw"); onemore=false; } else if (res>0) { gameOver("You won the game with "+res+" points!"); onemore=false; } else { gameOver("I won the game with "+(0-res)+" points!"); onemore=false; } } } // print and show the move nextHuman=true; lastMoveText=compMoveText; if (houseNrsBox.isSelected()) System.out.println("Comp : "+compMoveText); if (houseNrsBox.isSelected()) printPosition(); repaint(); // after machine calcs move, this is simply painted, after that system waits // for human to make a move by clicking } /** ======================================= * This is the function you have to improve: currently it * just takes the first legal move. * * NB! calcMove() should give a single move, not a sequence of moves: * if new machine moves should be performed immediately after calcMove(); * then these are taken care of by doMachineMove(), which will repeatedly call * calcMove() and then perform the move found. * ========================================= */ int calcMove() { int i; int n=-1; // if no moves available, returns -1 // do a fake thinking pause: DO NOT DO THIS FOR REAL COMPUTATION! try { Thread.sleep(500); } catch (InterruptedException e) { } // find a possible move for the machine, assuming game has not ended if (humanLower) { for(i=7;i<13;i++) { if (position[i]!=0) { n=i; break; } } } else { for(i=0;i<6;i++) { if (position[i]!=0) { n=i; break; } } } return n; } /** * This is a general move-performer for both sides (human and comp) * */ boolean doMove(int n) { int i,s,j,d; int oppStore; //System.out.println("called doMove "+n); s=position[n]; if (s<1) { message.setText("Cannot move from empty house!"); repaint(); return false; } if (n<6) oppStore=13; // lower row player has store 6 and oppStore 13 else oppStore=6; // upper row player vice versa //System.out.println("oppStore: "+oppStore); // do the normal move: seed following houses, incl own store position[n]=0; for(i=n+1; s>0; i++) { if (i>13) i=0; if (i!=oppStore) { position[i]++; s--; } } i--; // now i is the house where the last seed was dropped //for(d=0;d<14;d++) System.out.print(d+":"+position[d]+" "); if (i==6 || i==13) { // cannot land in oppStore anyway // landed in store: new move! //System.out.println("new move!"); return true; } else { // normal case: no more moves //System.out.println("normal case, no more moves"); // check for capture!! if (position[i]==1) { // last seed landed in an empty house if (oppStore==13 && i<6) { // ... of the player, who has lower row, capture! //System.out.println("capture by lower row"); position[i]=0; j=12-i; // opposite house position[6]=position[6]+1+position[j]; position[j]=0; } else if (oppStore==6 && i>6) { // ... of the player, who has upper row, capture! //System.out.println("capture by upper row"); position[i]=0; j=12-i; // opposite house position[13]=position[13]+1+position[j]; position[j]=0; } } // we did not land in a store, no more moves return false; } } /** * This checks whether game has ended * */ boolean checkEnd() { if (position[0]==0 && position[1]==0 && position[2]==0 && position[3]==0 && position[4]==0 && position[5]==0) return true; if (position[7]==0 && position[8]==0 && position[9]==0 && position[10]==0 && position[11]==0 && position[12]==0) return true; return false; } /** * This calcs a human score, assuming game has ended * (checks this and otherwise gives -1000) * */ int humanResult() { int lr=0; int ur=0; int hum=0; int opp=0; if (!checkEnd()) return -1000; lr=position[0]+position[1]+position[2]+position[3]+ position[4]+position[5]+position[6]; ur=position[7]+position[8]+position[9]+position[10]+ position[11]+position[12]+position[13]; if (lr==ur) return 0; if (humanLower) return lr-ur; else return (ur-lr); } } // end class Board } // end class