/* Ataxx playing java applet/application Copyright © 1998 By Danny Sadinoff Released under GNU Public License version 2 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. or go to http://www.gnu.org */ import java.applet.*; import java.awt.Frame; import java.awt.*; import java.util.*; import java.io.*; import java.awt.event.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.lang.Math; class AtaxxHistory implements Serializable, Observer{ Board startingBoard; Vector moveVector = new Vector(); String filename = "AtaxxHistory.jso"; AtaxxHistory(Board board){ board.addObserver(this); } AtaxxHistory(Board board, String fname_arg){ board.addObserver(this); filename = fname_arg; } public void update( Observable o, Object closure){ Move m = (Move)closure; Board b = (Board)o; if( m==null){ startingBoard = (Board)(b.clone()); System.out.print(startingBoard); } else{ add(m); } } void add(Move m){ moveVector.addElement((Object)m); // write(); } public String toString(){ return startingBoard.toString() + moveVector.toString(); } void write(){ try{ FileOutputStream ostream = new FileOutputStream(filename); ObjectOutputStream oos = new ObjectOutputStream(ostream); oos.writeObject(this); oos.flush(); ostream.flush(); oos.close(); ostream.close(); }catch( IOException ioe){ ioe.printStackTrace(); } } } /* Board notifications will be either notifyObservers() or notifyObservers(Move M), where m is the move just made. We want a world here is that the history of the game can be rebuilt from the start state and the sequence of moves. This necessitates "passes" as a Move. */ class Board extends Observable implements Serializable{ static public final int FIRST_PLAYER = 0; static public final int EMPTY = -1; static public final int BLOCK = -2; int playerToGo = FIRST_PLAYER; int numPlayers = 2; int width; int height; int emptySquares; private int [][]field; int playerCount[]; boolean moovedYet = false; public Object clone(){ return this;} Board(int width_arg, int height_arg){ width = width_arg; height= height_arg; field = new int[width][height]; for( int r = 0; r < height; r++) for( int c = 0; c < width; c++) field[c][r] = EMPTY; playerCount = new int[numPlayers]; emptySquares = height*width; set(0,0,FIRST_PLAYER); set(width-1,height-1,FIRST_PLAYER); set(width-1,0,FIRST_PLAYER+1); set(0,height-1,FIRST_PLAYER+1); setBlock(width/2,height/2); } public String toString(){ return ("{ Board:\n\tplayerToGo = "+playerToGo+ "\n\tnumPlayers = "+numPlayers+ "\n\twidth = "+width+ "\n\theight = "+height+ "\n\temptySquares = "+emptySquares+ "\n\tfield= "+field+ "\n\tplayerCount= "+playerCount+"\n}\n"); } void set(int col, int row, int newValue){ int oldValue = field[col][row]; if ( oldValue == newValue) return; switch(oldValue) { case BLOCK: break; case EMPTY: emptySquares--; break; default: playerCount[oldValue]--; } switch(newValue){ case BLOCK: break; case EMPTY: emptySquares++; break; default: playerCount[newValue]++; } field[col][row] = newValue; setChanged(); } void setBlock(int col, int row){ set(col, row, BLOCK); } void clear(int col, int row){ set(col, row, EMPTY); } private boolean isAnOpponent(int player){ return (player >= FIRST_PLAYER && player != playerToGo); } boolean gameOver(){ if( 0 == emptySquares) return true; boolean oneColorInPlay = false; for( int pl = FIRST_PLAYER; pl < numPlayers; pl++) if( playerCount[pl] > 0 ){ if( oneColorInPlay ) return false; oneColorInPlay = true; } return true; } String winnerString(){ int max = -1; int maxingPlayersCount =0; int [] maxingPlayers = new int[numPlayers]; for( int pl = FIRST_PLAYER ; pl < numPlayers; pl++) { if( playerCount[pl] > max ){ max = playerCount[pl]; maxingPlayers[0] = pl; maxingPlayersCount =1; } else if( playerCount[pl] == max ){ maxingPlayers[maxingPlayersCount++] = pl; } } if( maxingPlayersCount == 1){ return BoardCanvas.names[maxingPlayers[0]] + " Won"; } else if (maxingPlayersCount >1){ StringBuffer buf = new StringBuffer("It's a "+maxingPlayers+"-way tie, between "); for( int pl = 0; pl < maxingPlayersCount-1; pl++){ buf.append(BoardCanvas.names[maxingPlayers[pl]]+", "); } buf.append("and "+BoardCanvas.names[maxingPlayers[maxingPlayersCount-1]]); return buf.toString(); }else throw new IllegalArgumentException("Aiee. < 1 winners!\n"); } private void advancePlayer(){ playerToGo = (1+playerToGo) % numPlayers; } boolean mustPass(){ for( int mt_row = 0; mt_row < height; mt_row++) for( int mt_col = 0; mt_col < width; mt_col++) { if( field[mt_col][mt_row] != EMPTY ) continue; for( int h = -2; h <= 2; h++) for( int v = -2; v <= 2; v++){ int col = mt_col + h; int row = mt_row + v; if( col < 0 || col >= width || row < 0 || row >= height) continue; if( playerToGo == field[col][row]) return false; } } return true; } void applyMove(Move m) throws IllegalMoveException{ if( !moovedYet ){ // only send the board-setup notification once notifyObservers(); moovedYet = true; } if( m.player != playerToGo ) throw new IllegalMoveException( "It's "+BoardCanvas.names[playerToGo].toString()+"'s turn."); if( m.isPass) { if( !mustPass()) throw new IllegalMoveException( "Player must have no legal moves to pass."); } else{ int player = field[m.from_col][m.from_row]; if( player != playerToGo ) throw new IllegalMoveException( "It's "+BoardCanvas.names[playerToGo].toString()+"'s turn."); if( field[m.to_col][m.to_row] != EMPTY ) throw new IllegalMoveException( "You can only move to a clear space"); int dist = Math.max(Math.abs(m.to_col - m.from_col), Math.abs(m.to_row - m.from_row)); if( dist > 2 ) throw new IllegalMoveException( "You're trying to jump too far"); if( dist == 2 ) clear(m.from_col,m.from_row); set(m.to_col,m.to_row,player); for( int h = -1; h <= 1; h++) for( int v = -1; v <= 1; v++){ int col = m.to_col + h; int row = m.to_row + v; if( col < 0 || col >= width || row < 0 || row >= height) continue; if( isAnOpponent(field[col][row])) set(col,row,player); } } advancePlayer(); notifyObservers(m); } int getVal(int col, int row ){ return field[col][row]; } } class IllegalMoveException extends Exception{ IllegalMoveException(){super();} IllegalMoveException(String s){super(s);} } class Move implements Serializable{ int player; boolean isPass; int from_col, from_row, to_col, to_row; Move(int player_arg ){ // pass player = player_arg; isPass = true; } Move(int player_arg, int fc, int fr, int tc, int tr){ player = player_arg; isPass = false; from_col = fc; from_row = fr; to_col = tc; to_row = tr; } public String toString(){ if( isPass ) return "Player #"+player+" passes."; else return ("Player #"+player+" moves ("+from_col+", "+from_row+ ") -> ("+to_col+", "+to_row+")"); } } interface StatusReporter{ void setStatus(String s); } interface BoardDragReporter{ void dragged(Move m, Board board); } class BoardCanvas extends Canvas implements Observer{ Board board; static final int boardMargin =5; static final double squareMargin = 0.1; Dimension dim; StatusReporter sr = null; BoardDragReporter bdr = null; static final Color[] colors = { Color.red, Color.blue, Color.green, Color.orange, }; static final String[] names = { "Red", "Blue", "Green", "Orange", }; BoardCanvas(Board b) { board = b; board.addObserver(this); MouseAdapter mAdapter = new MouseAdapter(){ boolean dragging = false; int dragStartCol, dragStartRow; // public void mouseClicked(MouseEvent e){ // if ( null == sr ) // return; // int size = (int)((float)Math.min(dim.width - 2*boardMargin, dim.height - 2*boardMargin) / // (float)Math.max(board.width,board.height)) ; // int col = (e.getX() - boardMargin)/ size; // int row = (e.getY() - boardMargin)/ size; // sr.setStatus("click at ("+col + ", "+row+")"); // } public void mousePressed(MouseEvent e){ int size = (int)((float)Math.min(dim.width - 2*boardMargin, dim.height - 2*boardMargin) / (float)Math.max(board.width,board.height)) ; dragStartCol = (e.getX() - boardMargin)/ size; dragStartRow = (e.getY() - boardMargin)/ size; if( dragStartCol >=0 && dragStartCol < board.width && dragStartRow >=0 && dragStartRow < board.height) dragging = true; } public void mouseReleased(MouseEvent e){ int size = (int)((float)Math.min(dim.width - 2*boardMargin, dim.height - 2*boardMargin) / (float)Math.max(board.width,board.height)) ; if ( !dragging ) return; if ( null == bdr ) return; int dragEndCol = (e.getX() - boardMargin)/ size; int dragEndRow = (e.getY() - boardMargin)/ size; if( dragEndCol >=0 && dragEndCol < board.width && dragEndRow >=0 && dragEndRow < board.height) bdr.dragged(new Move(board.playerToGo, dragStartCol, dragStartRow, dragEndCol, dragEndRow), board); dragging = false; } }; addMouseListener(mAdapter); } /* the board has changed, so we have to repaint it. */ /* perhaps someday we'll refine this so that only the affected areas are repainted */ public void update( Observable o, Object closure ){ repaint(); } void setStatusReporter( StatusReporter sr_arg ) { sr = sr_arg; } void setBoardDragReporter( BoardDragReporter bdr_arg ) { bdr = bdr_arg; } public void paint( Graphics g){ dim = getSize(); g.setColor(Color.black); int size = (int)((float)Math.min(dim.width - 2*boardMargin, dim.height - 2*boardMargin) / (float)Math.max(board.width,board.height)) ; int right = boardMargin + size* board.width; int bottom = boardMargin + size* board.height; for( int c = 0; c < board.width; c++) { for( int r = 0; r < board.height; r++) { int val; switch(val = board.getVal(c,r)){ case Board.EMPTY: break; case Board.BLOCK: g.setColor(Color.darkGray); g.fillRoundRect((int)(boardMargin+ size *( squareMargin + c)), (int)(boardMargin+ size *( squareMargin + r)), (int)(size - 2*squareMargin*size), (int)(size - 2*squareMargin*size), (int)(2*squareMargin*size), (int)(2*squareMargin*size)); break; default: g.setColor(colors[val]); g.fillOval((int)(boardMargin + size*(squareMargin + c)), (int)(boardMargin + size*(squareMargin + r)), (int)(size - 2*squareMargin*size), (int)(size - 2*squareMargin*size)); } } } g.setColor(Color.black); //draw border g.drawRect(boardMargin,boardMargin, size* board.width, size* board.height); // draw gridlines for( int c = 0; c < board.width; c++) { g.drawLine(boardMargin + c *size, boardMargin, boardMargin + c *size, bottom); } for( int r = 0; r < board.height; r++) { g.drawLine(boardMargin, boardMargin + r *size, right, boardMargin + r *size); } } } public class Ataxx extends Applet{ BoardCanvas bCanvas; AtaxxHistory history; TextField status; public Ataxx(){this(6);} public Ataxx( int boardDim ){ Board b = new Board(boardDim, boardDim); history = new AtaxxHistory(b); bCanvas = new BoardCanvas(b); bCanvas.setVisible(true); status = new TextField( "It's "+BoardCanvas.names[bCanvas.board.playerToGo].toString()+"'s turn."); bCanvas.setStatusReporter(new StatusReporter(){ public void setStatus(String s){ status.setText(s); }}); bCanvas.setBoardDragReporter(new BoardDragReporter(){ public void dragged(Move move, Board board){ if( board.gameOver()) return; status.setText("Trying move "+move.toString()); try{ board.applyMove(move); if( board.gameOver()){ status.setText(board.winnerString()); } else { StringBuffer buf = new StringBuffer(); if( board.mustPass()){ buf.append(BoardCanvas.names[board.playerToGo].toString()+" must pass. "); board.applyMove(new Move(board.playerToGo)); // pass } buf.append("It's "+BoardCanvas.names[board.playerToGo].toString()+"'s turn."); status.setText(buf.toString()); } } catch( IllegalMoveException ime ){ status.setText( ime.getMessage()); } }}); Button quitBut = new Button("quit"); quitBut.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ System.exit(0); } }); } public void init(){ setLayout(new GridLayout(0,1)); add( bCanvas ); add( status ); setVisible(true); } public static void main(String [] argv){ final Frame f = new Frame("Ataxx"); f.setBounds(10,10,300,600); f.addWindowListener( new WindowAdapter(){ public void windowClosed(WindowEvent e){ System.exit(0); } public void windowClosing(WindowEvent e){ f.dispose(); } }); Ataxx a = new Ataxx(6); f.add(a); a.init(); f.setVisible(true); } }