Joust
<< Rock Paper Scissors | LabTrailIndex | Fancy Joust >>
A game I learned about from Berkeley's Professor Dan Garcia's GamesCrafters Site Two knights on a chess board that move like chess, but as they leave a square, it is "burned" (uninhabitable for either player). Players alternate moving their knight trying to avoid being the loser who is left with no place to go. Click here for a working demo
This lab is set up so you can discover how to program conditional statements (more commonly called "if" statements). I have some starter code with graphics and fancy things like arrays and loops (something that we will be learning about formally very soon--think of this as an introduction to see how terribly wonderful and useful they can be). Your job will be to finish writing some vital boolean methods that will make the code work.
Under the hood
Square
objects start out either black or white (like a chess board) and become red when they are "burned" It draws itself, so it needs to know if it is highlighted. Write theisBurned
method by checking what the color is.- Each player has a
Knight
(the player's piece) which needs to keep track of its location, what color it is. - The game's
Board
has a collection ofSquare
objects and twoKnight
objects (one for each player. It also keeps track of whose turn it is, what are legal moves, and whether the game is over (and who the winner is). Here you have to write two methods, both involving the possible moves. You'll need to use theisAvailable
method. You also need to look at certain squares. The knight can either move one over and two across, or two over and one across. You might getindexOutOfBounds
runtime errors if you forget to see if you are looking for a square that is off the board. Thank goodness Java uses 'short circut' conditional statements. Considerif (c<0 && isAvailable(grid[r][c])
If c is less than 0, it won't go to the isAvailable method. Good thing, since it would cause a runtime error. Once the first part of the && is false, that is enough to know the whole expression is false. - The
Joust
Applet handles the user's activity. If the user is hovering over a valid move, then that particularSquare
is told it is highlighted. If the user clicks on a valid move, then the currentKnight
burns the current square, and moved to the new location,. If it is not a game winning move, and the current player switches.
Square.java
Complete the isBurned
method
import java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; public class Square { /** * constants */ public static final int black=0; public static final int white=1; public static final int burned=2; /** * instance fields */ private Color[] colors= {Color.DARK_GRAY, Color.LIGHT_GRAY, Color.RED}; private int color; private boolean highlighted; private Rectangle box; /** * constructors */ public Square(){ setColor(Square.white); setHighlighted(false); box=null; } public Square(int color) { this(); //use the default constructor to initialize this.setColor(color); } public Square(int color, Rectangle box) { this(); //use the default constructor to initialize this.setColor(color); this.box=box; } /** * Access Methods */ public int getColor() { return color; } public void setColor(int color) { this.color = color; } /** * other methods */ public void draw(Graphics g){ if (box==null) return; int w=2;//width of outline int x= (int)box.getX(); int y= (int)box.getY(); int size = (int)box.getHeight(); if (highlighted){ g.setColor(Color.MAGENTA); }else{ g.setColor(Color.BLACK); } g.fillRect(x, y, size, size); g.setColor(colors[color]); g.fillRect(x+w, y+w, size-2*w, size-2*w); } public boolean isHighlighted() { return highlighted; } public void setHighlighted(boolean highlighted) { this.highlighted = highlighted; } /** * given screen location x,y sets the highlight on or off * @param x screen coord * @param y screen coord * @return the Square if highlighted, otherwise null */ public Square select(int x, int y){ this.setHighlighted(box.contains(x,y)); if(this.isHighlighted()) return this; else return null; } public boolean isBurned(){ //your code here } }
Knight.java
import java.awt.Color; import java.awt.Font; import java.awt.Graphics; public class Knight { /** * instance fields */ private int row, col; private String color; public static final String black="\u2658"; //the unicode filled knight letter public static final String white="\u265e"; //the unicode open knight letter /** * constructors */ public Knight() { color=Knight.white; row=0; col=0; } public Knight(String color){ this(); //use default constructor to initialize fields this.color=color; } public Knight(String color, int row, int col){ this(color); //use other constructor to initialize fields this.row=row; this.col=col; } public void draw(Graphics g, Board board){ int size = board.getSquareSize(); int offset = (int)(size/5.0); int x=board.getLeft()+col*size; int y=board.getTop()+(row)*size; Font font=new Font("Arial Unicode MS", 0, size);//Arial is the most complete for WinXP g.setFont(font); g.setColor(Color.YELLOW); g.drawString(this.getColor(), x, y+size-offset); } public boolean isWhite() { return color==Knight.white; } public boolean isBlack() { return !isWhite(); } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getCol() { return col; } public void setCol(int col) { this.col = col; } public String getColor(){ return color; } public void setColor(String color){ this.color=color; } }
Board.java
Complete the isLegalMove
and hasMovesLeft
methods
import java.awt.Graphics; import java.awt.Rectangle; import java.awt.image.BufferedImage; /** * A Joust game board... free to use for educational use * @author Chris Thiel, OFMCap * 30 Sept 2012 * */ public class Board { private int squareSize, top,left; private Square[][] grid; private Square selectedSquare; private int selectedRow, selectedCol; private Knight[] player; private Knight currentPlayer; private String message; public Board(int rows, int cols) { this.setSquareSize(70); grid=new Square[rows][cols]; setTop(20); setLeft(50); setSelectedSquare(null); setSelectedRow(-1); setSelectedCol(-1); setMessage("White's turn"); for (int r=0; r<getNumRows();r++) for (int c=0; c< getNumCols();c++){ int color=(r+c+1)%2;//alternate color int x=left + c*squareSize; int y=top + r*squareSize; Rectangle rectangle=new Rectangle(x,y,squareSize,squareSize); grid[r][c]=new Square(color, rectangle); } player = new Knight[2]; int rand=(int)(getNumCols()*Math.random()); player[0]=new Knight(Knight.white, getNumRows()-1,rand); rand=(int)(getNumCols()*Math.random()); player[1]=new Knight(Knight.black,0,rand); setCurrentPlayer(player[0]); } /** * access methods * */ public int getNumRows(){ return grid.length; } public int getNumCols(){ return grid[0].length; } public Square getSquare(int row, int col){ return grid[row][col]; } public int getTop() { return top; } public void setTop(int top) { this.top = top; } public int getLeft() { return left; } public void setLeft(int left) { this.left = left; } public int getSquareSize() { return squareSize; } public void setSquareSize(int squareSize) { this.squareSize = squareSize; } public Knight getCurrentPlayer() { return currentPlayer; } /** * Check if the square at r,c is burned * or occupied by one of the players * @param r * @param c * @return is the square is availible */ public boolean isAvailable(int r, int c){ if (r<0 || c<0 || r>=getNumRows() || c>=getNumCols()) return false; if (grid[r][c].isBurned()) return false; for(int i=0;i<2;i++) if (player[i].getRow()==r && player[i].getCol()==c) return false; return true; } public void setCurrentPlayer(Knight currentPlayer) { this.currentPlayer = currentPlayer; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Square getSelectedSquare() { return selectedSquare; } public void setSelectedSquare(Square selectedSquare) { this.selectedSquare = selectedSquare; } public int getSelectedRow() { return selectedRow; } public void setSelectedRow(int selectedRow) { this.selectedRow = selectedRow; } public int getSelectedCol() { return selectedCol; } public void setSelectedCol(int selectedCol) { this.selectedCol = selectedCol; } /** * other methods */ public void draw(Graphics g){ for (int r=0; r<getNumRows();r++) for (int c=0; c< getNumCols();c++){ grid[r][c].draw(g); } player[0].draw(g, this); player[1].draw(g, this); } public void setHighlight(int x, int y){ for (int r=0; r<getNumRows();r++) for (int c=0; c< getNumCols();c++){ Square s=grid[r][c].select(x, y); if (s!=null) { setSelectedSquare(s); setSelectedRow(r); setSelectedCol(c); } } message="("+selectedRow+", "+selectedCol+") is "; if (isLegalMove(currentPlayer, selectedRow,selectedCol)) message+="legal"; else message+="NOT legal"; } public String getSelected(){ return "("+selectedRow+", "+selectedCol+")"; } public String whoseTurn(){ if (! gameOver() && currentPlayer.isWhite()) return "Whites turn"; if (! gameOver() && currentPlayer.isBlack()) return "Black's turn"; if (hasMovesLeft(player[0])) return "White wins"; return "Black Wins"; } public void move(){ if(gameOver()) return; if (isLegalMove(currentPlayer, selectedRow, selectedCol)){ grid[currentPlayer.getRow()][currentPlayer.getCol()].setColor(Square.burned); currentPlayer.setRow(selectedRow); currentPlayer.setCol(selectedCol); if (!gameOver() ){ if (currentPlayer==player[0]) currentPlayer=player[1]; else currentPlayer=player[0]; } } message=whoseTurn(); } /** * Checks if both players have any * availible Moves left * @return whether the game is over */ public boolean gameOver(){ for (int i=0;i<2;i++) if (!hasMovesLeft(player[i])) return true; return false; } /** * Using a series of conditional statements * checks to see if the square at r,c * is a valid place to move this knight * @param board * @param r the row in question * @param c the column in question * @return whether the knight can validly go to the square */ public boolean isLegalMove(Knight player, int r, int c) { //your code here } public boolean hasMovesLeft(Knight player){ // your code here } }
JoustApplet.java
import java.applet.Applet; import java.applet.AudioClip; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; /** * A applet to hold the Joust Board * and deal with user input * Free for educational use. * @author Chris Thiel, OFMCap * @version 30 Sept 2012 * */ public class JoustApplet extends Applet implements MouseMotionListener, MouseListener { /** * Instance fields for the Game * */ private Board board; private Rectangle restartBtn; /** * These instance fields are for getting rid of flicker * on the Windows platform-- the Applet will draw the * picture in memory before putting it on the screen */ private Image virtualMem; private Graphics gBuffer; private Font font;//The font for our messages private String message; public void init() { board=new Board(8,8); this.restartBtn = new Rectangle (670, 350, 80,40); this.addMouseListener(this); this.addMouseMotionListener(this); font = new Font("Helvetica", Font.BOLD, 18); message="Joust! "+"White"+"'s move"; } /** * On loading the Applet we read the sound and image files * from the java archive (jar file), and play the sounds * to initialize them. */ public void paint (Graphics g) { //make a new buffer in case the applet size changed virtualMem = createImage(getWidth(),getHeight()); gBuffer = virtualMem.getGraphics(); gBuffer.setColor(Color.WHITE); gBuffer.fillRect(0, 0, this.getWidth(), this.getHeight()); gBuffer.setColor(Color.BLACK); gBuffer.setFont(font); gBuffer.drawString(message, 625, 20); gBuffer.drawString(board.getMessage(), 625, 40); if (board.gameOver()){ gBuffer.setColor(Color.BLUE); gBuffer.fillRect(restartBtn.x, restartBtn.y, restartBtn.width, restartBtn.height); gBuffer.setColor(Color.YELLOW); gBuffer.drawString("Restart", restartBtn.x+10, restartBtn.y+25); } board.draw(gBuffer); g.drawImage(virtualMem,0,0,this);//set new display to Screen } @Override public void mouseClicked(MouseEvent e) {} @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) { board.move(); if(board.gameOver()){ if (restartBtn.contains(e.getPoint())){ board=new Board(8,8); repaint(); } } repaint(); } @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} @Override public void mouseDragged(MouseEvent e) {} @Override public void mouseMoved(MouseEvent e) { int x=e.getX(); int y=e.getY(); board.setHighlight(x, y); message=board.whoseTurn(); repaint(); } public void update(Graphics g) { paint(g); //get rid of flicker with this method } }
Now what?
Once you have it going, you can customize it:
- Change the colors or the appearance of the Knights
- Make the "Misere" version (where the one who can't move is the winner)
- Change the size of the board to 5 by 5 board or a rectangular board
- The demo has some fancy images and sounds, which you may wish to incorporate in your final project. See Fancy Joust