/**
* Program Name: Othello.java
* Purpose: Showing how to AWT to write Othello
* Since: 2005/05/23
* Modify Date: 2005/05/24
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class Othello extends JFrame implements ActionListener {
private OX oxBoard;
private MenuItem black, white;
private Othello() {
super("Othello");
Menu m;
MenuBar mb;
add(oxBoard = new OX(this));
CloseWindow close = new CloseWindow(this, true);
setMenuBar(mb = new MenuBar());
mb.add(m = new Menu("遊戲")).add(new MenuItem("新遊戲")).addActionListener(this);
m.add(black = new MenuItem("電腦下黑方")).addActionListener(this);
m.add(white = new MenuItem("電腦下白方")).addActionListener(this);
m.add(new MenuItem("結束")).addActionListener(close);
mb.add(new Menu("說明")).add(new MenuItem("關於本遊戲")).addActionListener(this);
addWindowListener(close);
pack();
setResizable(false);
setVisible(true);
}
public static void main(String argv[]) {
new Othello();
}
// implements the ActionListener interface
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("關於本遊戲")) {
new ErrorDialog(this,"程式設計黑白棋(蘋果花)範例.\n作者:俞旭昇於暨南大學資管系");
} else if (command.equals("新遊戲")) {
oxBoard.newGame();
} else if (command.equals("ˇ電腦下黑方")) {
oxBoard.setBlackPlayer(0);
black.setLabel("電腦下黑方");
} else if (command.equals("電腦下黑方")) {
oxBoard.setBlackPlayer(1);
black.setLabel("ˇ電腦下黑方");
} else if (command.equals("ˇ電腦下白方")) {
oxBoard.setWhitePlayer(0);
white.setLabel("電腦下白方");
} else if (command.equals("電腦下白方")) {
oxBoard.setWhitePlayer(1);
white.setLabel("ˇ電腦下白方");
}
}
}
class OX extends Component implements MouseListener, MouseMotionListener, Runnable {
private int[] board; // 盤面狀況,表達有邊框的10*10盤面
private int turn, diskdiff; // 現在哪方可下, 與敵方的子數差異
private OX parent; // 由哪一個盤面變化而來
private double val = -1000000; // 估計此盤面的優勢狀況
private int hashval; // for hashtable
private int[] legals; // 儲存此盤面可以下的著手
public static final int EMPTY = 0x00; // 空格
public static final int BLACK = 0x01; // 黑子
public static final int WHITE = 0x02; // 白子
public static final int STONE = 0x03; // 上面兩個 or
public static final int BOUND = 0x04; // 邊界
public static final int ADEMP = 0x08; // 是否鄰接子的空點
private static final Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR); // 箭頭游標
private static final Cursor hintCursor = new Cursor(Cursor.HAND_CURSOR); // 手形游標
private static final Cursor thinkCursor = new Cursor(Cursor.WAIT_CURSOR); // 漏斗游標
private static Dimension mySize = new Dimension(600,400); // 固定畫面的大小為寬600,高400
private static JFrame top; // 包含此元件的最上層Frame
private static Thread thinking; // 計算中的Thread
private static final byte[] directions = {1,-1,10,-10,9,-9,11,-11}; // 一維陣列下的8個方向
private static final int HASHSIZE = 63999979; // 小於64M的最大質數
public static int whoPlayBlack, whoPlayWhite;
public static final int HUMAN = 0, COMPUTER = 1;
private static int newboard[] = { // 遊戲開始的最初畫面
BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,
BOUND,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,ADEMP,ADEMP,ADEMP,ADEMP,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,ADEMP,WHITE,BLACK,ADEMP,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,ADEMP,BLACK,WHITE,ADEMP,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,ADEMP,ADEMP,ADEMP,ADEMP,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,BOUND,
BOUND,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,BOUND,
BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND,BOUND};
public OX(JFrame p) {
addMouseListener(this);
addMouseMotionListener(this);
top = p;
board = new int[100];
System.arraycopy(newboard, 0, board, 0, 100);
turn = BLACK;
legals = new int[] {34,43,56,65};
}
// 複製p的狀態
public OX(OX p) {
board = new int[100];
System.arraycopy(p.board, 0, board, 0, 100);
turn = p.turn;
diskdiff = p.diskdiff;
val = -1000000;
}
public void setBlackPlayer(int who) {
if (whoPlayBlack == who) return;
if (whoPlayBlack == 0 && thinking == null && (hasLegal(turn) || hasLegal(turn^STONE))) {
(thinking = new Thread(this)).start();
}
whoPlayBlack = who;
}
public void setWhitePlayer(int who) {
if (whoPlayWhite == who) return;
if (whoPlayWhite == 0 && thinking == null && (hasLegal(turn) || hasLegal(turn^STONE))) {
(thinking = new Thread(this)).start();
}
whoPlayWhite = who;
}
// 檢查pos是否合法
boolean isLegal(int pos) {
return isLegal(turn, pos);
}
// 檢查side這個顏色,能否下在pos
boolean isLegal(int side, int pos) {
int opp = side^STONE;
for (int i = 0, scan; i < 8; i++) {
scan = pos+directions[i];
if (board[scan] == opp) {
for (scan+=directions[i]; board[scan] == opp; scan+=directions[i]);
if ((board[scan] & side) != 0) { // 可夾住對方
return true;
}
}
}
return false;
}
// 檢查side是否有合法的著手可下
boolean hasLegal(int side) {
for (int i=11; i < 89; i++) {
if ((board[i]==ADEMP) && isLegal(side, i)) {
return true;
}
}
return false;
}
// 下在pos,並改變盤面結構. 若pos為0, 表示此著手為pass
boolean addMove(int pos) {
int opp = turn^STONE;
if (pos != 0) { // 0 表示pass
int legal = diskdiff;
for (int i = 0, scan; i < 8; i++) {
scan = pos+directions[i];
if (board[scan] == opp) { // 此方向緊鄰著敵方的子
// 跳過連續的敵方子
for (scan += directions[i]; board[scan] == opp; scan+=directions[i]);
if (board[scan] == turn) { // 可夾住對方
// 將所有敵方子變成我方子
for (int c = pos+directions[i]; c!=scan ;board[c]=turn, c+=directions[i], diskdiff+=2);
}
}
}
if (diskdiff==legal) { // 如果都沒有吃到
return false;
}
diskdiff++;
board[pos] = turn;
for (int i = 0; i < 8; i++) { // 設定此點旁的空點為ADEMP
if (board[pos+directions[i]] == EMPTY) {
board[pos+directions[i]] = ADEMP;
}
}
}
turn = opp; // 換對方下了
diskdiff = -diskdiff;
hashval=(hashval*64+(pos-11))%HASHSIZE;
return true;
}
// Thread的進入點
public void run() {
setCursor(thinkCursor);
for (;;) { // 當敵方需pass時,我方一直下
if (turn==BLACK && whoPlayBlack == HUMAN) { // 先檢查是否改由人下
break;
}
if (turn==WHITE && whoPlayWhite == HUMAN) { // 先檢查是否改由人下
break;
}
addMove(best());
repaint(); // ask winder manager to call paint() in another thread
if (turn==BLACK && whoPlayBlack==HUMAN && hasLegal(turn)) { // 人可以下了
break;
}
if (turn==WHITE && whoPlayWhite==HUMAN && hasLegal(turn)) { // 人可以下了
break;
}
if (!hasLegal(turn) && !hasLegal(turn^STONE)) { // 對手和自己都不能下了
new ErrorDialog(top, "Game Over");
break;
}
if (!hasLegal(turn)) {
addMove(0);
}
}
setCursor(normalCursor);
thinking = null;
}
// The following 2 methods implement the MouseMotionListener interface
public void mouseDragged(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {
if (thinking != null) return;
int row = e.getY()/40;
int col = e.getX()/40;
if (row >= 8 || col >= 8) {
setCursor(normalCursor);
return; // 超過邊界
}
int pos = row*10 + col + 11;
if (board[pos]==ADEMP && isLegal(turn, pos)) {
setCursor(hintCursor);
} else {
setCursor(normalCursor);
}
}
// The following 5 methods implement the MouseListener interface
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
int row = e.getY()/40;
int col = e.getX()/40;
if (row >= 8 || col >= 8) return; // 超過邊界
if (thinking != null) return; // 電腦思考中
int pos = row*10+col+11;
if (board[pos] == ADEMP && addMove(pos)) { // 此位置可以下
repaint();
if (hasLegal(turn)) {
if ((turn==WHITE && whoPlayWhite==COMPUTER) || (turn==BLACK && whoPlayBlack==COMPUTER)) { // let computer play
(thinking = new Thread(this)).start();
}
} else {
if (!hasLegal(turn^STONE)) { // 雙方都不能下
new ErrorDialog(top, "Game Over");
return;
}
addMove(0); // 對方不能下,force pass
}
}
}
// 棋力強弱關鍵的求值函數
private void eval() {
val = diskdiff;
}
private void alphaBeta(int level) {
if (legals == null) {
findLegals();
}
for (int i=0; i<legals.length; i++) {
OX tmp = new OX(this);
tmp.addMove(legals[i]);
if (level<1) {
tmp.eval();
} else {
tmp.alphaBeta(level-1);
}
// alphaBeta cut
if (val < -tmp.val) {
val = -tmp.val;
for (OX p = parent; p != null;) {
if (val >= -p.val) { // 對手不會選擇這條路的
return;
}
// 往上跳兩層
p = p.parent;
if (p != null) p = p.parent;
}
}
}
}
private void findLegals() {
int count = 0;
int[] tmp = new int[60];
for (int i=11; i<89; i++) {
if (board[i]==ADEMP && isLegal(turn, i)) {
tmp[count++] = i;
}
}
legals = new int[count];
System.arraycopy(tmp, 0, legals, 0, count);
}
private int best() {
int bestMove = 0;
findLegals();
val = -100000000;
for (int i=0; i<legals.length; i++) {
OX tmp = new OX(this);
tmp.addMove(legals[i]);
tmp.alphaBeta(3);
if (-tmp.val > val) {
bestMove = legals[i];
val = -tmp.val;
}
}
return bestMove;
}
// override paint() defined in Component
public void paint(Graphics g) {
int black, white;
black = white = 0;
for (int i = 0; i <= 8; i++) { // draw grids
g.drawLine(0, i*40, 320, i*40);
g.drawLine(i*40, 0, i*40, 320);
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
int pos = i*10 + j + 11;
if ((board[pos] & BLACK) != 0) {
g.fillOval(j*40,i*40,40,40);
black++;
} else if ((board[pos] & WHITE) != 0) {
g.drawOval(j*40,i*40,40,40);
white++;
}
}
}
g.drawString("BLACK:"+black, 400, 100);
g.drawString( "WHITE:"+white, 400, 150);
}
public void newGame() {
System.arraycopy(newboard, 0, board, 0, 100);
turn = BLACK;
hashval = diskdiff = 0;
if (thinking != null) {
try {
thinking.join();
} catch(Exception epp) {}
}
if (whoPlayBlack == COMPUTER) {
(thinking = new Thread(this)).start();
}
repaint();
}
// override getPreferredSize defined in java.lang.Component,
// so that the Component has proper size on screen
public Dimension getPreferredSize() {
return mySize;
}
// override hashCode() in java.lang.Object
public int hashCode() {
return hashval;
}
public boolean equals(Object o) {
if (!(o instanceof OX)) return false;
OX t = (OX) o;
for (int i=11; i<89; i++) {
if (board[i] != t.board[i]) return false;
}
return true;
}
}
// WindowAdapter implements the WindowLister interface
// We extends WindowAdapter to reduce the line numer of code
class CloseWindow extends WindowAdapter implements ActionListener {
private Window target;
private boolean exit;
public CloseWindow(Window target, boolean exit) {
this.target = target;
this.exit = exit;
}
public CloseWindow(Window target) {
this.target = target;
}
public void windowClosing(WindowEvent e) {
target.dispose();
if (exit) System.exit(0);
}
public void actionPerformed(ActionEvent e) {
target.dispose();
if (exit) System.exit(0);
}
}
class AddConstraint {
public static void addConstraint(Container container, Component component,
int grid_x, int grid_y, int grid_width, int grid_height,
int fill, int anchor, double weight_x, double weight_y,
int top, int left, int bottom, int right) {
GridBagConstraints c = new GridBagConstraints();
c.gridx = grid_x; c.gridy = grid_y;
c.gridwidth = grid_width; c.gridheight = grid_height;
c.fill = fill; c.anchor = anchor;
c.weightx = weight_x; c.weighty = weight_y;
c.insets = new Insets(top,left,bottom,right);
((GridBagLayout)container.getLayout()).setConstraints(component,c);
container.add(component);
}
}
class ErrorDialog extends JDialog {
public ErrorDialog(JFrame parent, String all[]) {
this(parent, all, null);
}
public ErrorDialog(JFrame parent, String all[], String msg) {
super(parent,"",true);
StringBuffer sb = new StringBuffer();
for (int i=0; i<all.length; i++) {
sb.append(all[i]);
sb.append('\n');
}
if (msg!=null) {
sb.append(msg);
}
setup(parent, sb.toString());
}
public ErrorDialog(JFrame parent, String message) {
super(parent,"",true);
setup(parent, message);
}
private void setup(JFrame parent, String message) {
this.getContentPane().setLayout(new GridBagLayout());
int row=0, col=0, i, width=0;
Font font = new Font("Serif", Font.PLAIN, 16);
char c=' ';
for (i=0; i<message.length(); i++) {
c = message.charAt(i);
if (c=='\n') {
row++;
if (width>col) {
col = width;
}
width=0;
} else if (c=='\t') {
width += 7-width%7;
} else {
if (c>0x00FF) {
width+=2;
} else {
width++;
}
}
}
if (c!='\n') {
row++;
if (width>col) {
col = width;
}
}
col++;
// 希望視窗出來不要太大或太小
row = (row>24) ? 24 : row;
if (row<5) {
row=5;
}
if (col<20) {
col = 20;
}
TextArea tx = new TextArea(message,row,col);
tx.setEditable(false);
tx.setFont(font);
AddConstraint.addConstraint(this.getContentPane(), tx, 0, 0, 1, 1,
GridBagConstraints.BOTH,
GridBagConstraints.NORTHWEST,
1,1,0,0,0,0);
Button b = new Button("確定");
b.setFont(font);
AddConstraint.addConstraint(this.getContentPane(), b, 0, 1, 1, 1,
GridBagConstraints.HORIZONTAL,
GridBagConstraints.CENTER,
1,0,0,0,0,0);
CloseWindow cw = new CloseWindow(this);
this.addWindowListener(cw);
b.addActionListener(cw);
pack();
setVisible(true);
}
}