import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TTT3DWindow extends JFrame {
//Instance variables that are set once only, in the constructor
private JPanel wholeWindow; //The drawing area (the whole area of the window)
private TTTBoard3D board; //The reference to the TTTBoard3D object controlling the game
private Font messageFont; //The Font to use for the messages at the bottom
private int boardSize; //The board size - an n by n by n board
//These constants control the size and shape of the graphical board
//Change ONLY these values to adjust the board appearance.
private static int MESSAGE_HEIGHT = 40; //Height of message area at the bottom (pixels)
private static int MARGIN = 10; //Margin on all sizes of the board (pixels)
private static double GAP = 0.3; //Size of the gap between levels as a portion of one ROW height
private static double ASPECT_RATIO = 2.0; //Width of levels as a multiple of height
private static double SLANT = 0.7; //Offset of top of level relative to bottom, as portion of width
//Instance variables which change as the window is resized.
//These are recalculated every time the window is redrawn.
private int width, height; //Total size of the drawing area of the window
private int colWidth; //Width (pixels) of one square or one column
private int rowSlant; //Offset (pixels) of top of one square/row from its bottom
private int rowHeight; //Height (pixels) of one square or one row
private int leftEdge; //Coordinate of the left edge of the board (bottom left of each level)
private int topEdge; //Coordinate of the top edge of the board
private int gapSize; //The actual size of the gap between levels in pixels
//==============================================================================
//Constructor
public TTT3DWindow(int w, int h, int n) {
messageFont = new Font("Helvetica",Font.PLAIN,24);
boardSize = n;
board = new TTTBoard3D(n);
setTitle("3D Tic-tac-toe");
setSize(w,h);
wholeWindow = new graphicsPanel();
add(wholeWindow);
wholeWindow.addMouseListener(new HandleMouse());
setVisible(true);
}//TTT3DWindow constructor
//==============================================================================
//Instance methods
private void calculateSizes(int w, int h){
//Realculates all of the instance variables for the current window size.
width = w;
height = h;
int n = boardSize; //Just to keep the formulae smaller
rowHeight = (int)((height-2*MARGIN-MESSAGE_HEIGHT)/(n*n+GAP*(n-1)));
gapSize = (height-2*MARGIN-MESSAGE_HEIGHT-(n*n*rowHeight))/(n-1);
rowSlant = (int)(rowHeight*SLANT);
colWidth = (int)(rowHeight*ASPECT_RATIO);
topEdge = MARGIN; //This is just for readability later
leftEdge = width/2-(n*colWidth+n*rowSlant)/2;
}
public void drawBoard(Graphics g){
//Draw the whole board, in the given graphics environment (window)
//Make one Coordinate then change it using the 3 loops
Coordinate c = new Coordinate(0,0,0);
for(c.level=0; c.level<boardSize; c.level++)
for(c.row=0; c.row<boardSize; c.row++)
for(c.column=0; c.column<boardSize; c.column++)
//Draw that one square. Get the colour from the board.
drawSquare(c.level,c.row,c.column,board.getSquareColor(c),g);
}
private void drawSquare(int level, int row, int col, Color c, Graphics g){
//Draw one particular "square". What's tricky is that it's really
//a parallelogram so the coordinates of all four corners must be found.
//First,get the coordinate of the bottom left:
int x = getX(row,col);
int y = getY(level,row);
//Calculate the others based on that.
int[] xCoords = {x,x+colWidth,x+colWidth+rowSlant,x+rowSlant};
int[] yCoords = {y,y,y-rowHeight,y-rowHeight};
//Now fill in that area, and draw a border around it, too.
g.setColor(c);
g.fillPolygon(xCoords,yCoords,4);
g.setColor(Color.black);
g.drawPolygon(xCoords,yCoords,4);
}
private int getX(int row, int col){
//Get the X coordinate of the left edge of any row and column.
//All levels have the same X coordinate so that is not needed.
return leftEdge+col*colWidth+row*rowSlant;
}
private int getY(int level, int row){
//Get the Y coordinate of the bottom edge of any level and row.
//All columns have the same Y coordinate so that is not needed.
return (topEdge+boardSize*rowHeight)
+(boardSize-1-level)*(gapSize+boardSize*rowHeight)
-row*rowHeight;
}
private void drawMessageArea(Graphics g){
//Draw the message (as supplied by the board) at the bottom
//of the window
g.setColor(new Color(255,255,200));
g.fillRect(0,height-MESSAGE_HEIGHT,width,MESSAGE_HEIGHT);
g.setColor(Color.black);
g.setFont(messageFont);
g.drawString(board.getMessage(),MARGIN,height-12);
}
private Coordinate findClickCoordinate(int x, int y){
/* Convert the mouse position (x,y) into a (level,row,column)
* Coordinate, if possible. Return null if the mouse is
* not within a valid "square" on the board.
* This gets rather long and tricky because the "squares" are
* drawn as parallelograms to make the board look "3D".
*/
//Is it within the entire board vertically?
if(y<=getY(0,0) && y>=getY(boardSize-1,boardSize)){
//Convert y to a level and a row
int level = (getY(0,0)-y)/(boardSize*rowHeight+gapSize);
int row = (getY(level,0)-y)/rowHeight;
//Is it in a valid row (and not in the "gap" between levels)?
if(row<boardSize){
//Is it within the entire board horizontally, for that row?
if(x>=getX(row,0) && x<=getX(row+1,boardSize)){
//This gets tricky because the "squares" are parallelograms
//How far above the base of the row is the click?
int yOffset = getY(level,row)-y;
//How far to the right are the x coordinates shifted at that place?
int xOffset = yOffset*rowSlant/rowHeight;
//Where does the left edge intersect that particular y coordinate?
int xLeft = getX(row,0)+xOffset;
//Is it within the boundaries of the board at that specific Y coordinate?
if(x>=xLeft && x<xLeft+boardSize*colWidth){
//IT'S VALID! FINALLY!
//Find the column number, return the coordinates
int col = (x-xLeft)/colWidth;
return new Coordinate(level,row,col);
}//within the board at the specific Y
}//within board horizontally
}//valid row
}//within board vertically
//The default is null if any of the above didn't work
return null;
}//findClickCoordinate()
private class graphicsPanel extends JPanel {
public void paintComponent(Graphics g){
/* This is where all the drawing commands go.
* Whenever the window needs to be drawn, or redrawn,
* this method will automatically be called.
*/
//Find out the size of the available area, then
//recalculate the sizes and positions for the board
Rectangle r = g.getClipBounds();
calculateSizes(r.width,r.height);
//Draw the board once it's known how to draw it.
drawMessageArea(g);
drawBoard(g);
}//paintComponent method
}//private inner class graphicsPanel
private class HandleMouse implements MouseListener {
//The five standard methods are required. I don't want these ones:
public void mousePressed(MouseEvent e){ /*Do nothing */ }
public void mouseReleased(MouseEvent e){ /*Do nothing */ }
public void mouseEntered(MouseEvent e){ /*Do nothing */ }
public void mouseExited(MouseEvent e){ /*Do nothing */ }
//The only one we really want to pay attention to
public void mouseClicked(MouseEvent e){
Coordinate c = findClickCoordinate(e.getX(),e.getY());
if(c!=null){
board.handleClick(c); //Update the game board
repaint(); //And force it to be redrawn
}
}//mouseClicked
}//private inner class HandleMouse
}//class TTT3DWindow
No comments:
Post a Comment