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