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