public
Last active

Hexagonal Grid in Java

  • Download Gist
hexgame.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
 
/**********************************
This is the main class of a Java program to play a game based on hexagonal tiles.
The mechanism of handling hexes is in the file hexmech.java.
 
Written by: M.H.
Date: December 2012
 
***********************************/
 
public class hexgame
{
private hexgame() {
initGame();
createAndShowGUI();
}
 
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new hexgame();
}
});
}
 
//constants and global variables
final static Color COLOURBACK = Color.WHITE;
final static Color COLOURCELL = Color.ORANGE;
final static Color COLOURGRID = Color.BLACK;
final static Color COLOURONE = new Color(255,255,255,200);
final static Color COLOURONETXT = Color.BLUE;
final static Color COLOURTWO = new Color(0,0,0,200);
final static Color COLOURTWOTXT = new Color(255,100,255);
final static int EMPTY = 0;
final static int BSIZE = 12; //board size.
final static int HEXSIZE = 60; //hex size in pixels
final static int BORDERS = 15;
final static int SCRSIZE = HEXSIZE * (BSIZE + 1) + BORDERS*3; //screen size (vertical dimension).
 
int[][] board = new int[BSIZE][BSIZE];
 
void initGame(){
 
hexmech.setXYasVertex(false); //RECOMMENDED: leave this as FALSE.
 
hexmech.setHeight(HEXSIZE); //Either setHeight or setSize must be run to initialize the hex
hexmech.setBorders(BORDERS);
 
for (int i=0;i<BSIZE;i++) {
for (int j=0;j<BSIZE;j++) {
board[i][j]=EMPTY;
}
}
 
//set up board here
board[3][3] = (int)'A';
board[4][3] = (int)'Q';
board[4][4] = -(int)'B';
}
 
private void createAndShowGUI()
{
DrawingPanel panel = new DrawingPanel();
 
 
//JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Hex Testing 4");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Container content = frame.getContentPane();
content.add(panel);
//this.add(panel); -- cannot be done in a static context
//for hexes in the FLAT orientation, the height of a 10x10 grid is 1.1764 * the width. (from h / (s+t))
frame.setSize( (int)(SCRSIZE/1.23), SCRSIZE);
frame.setResizable(false);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
 
 
class DrawingPanel extends JPanel
{
//mouse variables here
//Point mPt = new Point(0,0);
 
public DrawingPanel()
{
setBackground(COLOURBACK);
 
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
}
 
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setFont(new Font("TimesRoman", Font.PLAIN, 20));
super.paintComponent(g2);
//draw grid
for (int i=0;i<BSIZE;i++) {
for (int j=0;j<BSIZE;j++) {
hexmech.drawHex(i,j,g2);
}
}
//fill in hexes
for (int i=0;i<BSIZE;i++) {
for (int j=0;j<BSIZE;j++) {
//if (board[i][j] < 0) hexmech.fillHex(i,j,COLOURONE,-board[i][j],g2);
//if (board[i][j] > 0) hexmech.fillHex(i,j,COLOURTWO, board[i][j],g2);
hexmech.fillHex(i,j,board[i][j],g2);
}
}
 
//g.setColor(Color.RED);
//g.drawLine(mPt.x,mPt.y, mPt.x,mPt.y);
}
 
class MyMouseListener extends MouseAdapter { //inner class inside DrawingPanel
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
//mPt.x = x;
//mPt.y = y;
Point p = new Point( hexmech.pxtoHex(e.getX(),e.getY()) );
if (p.x < 0 || p.y < 0 || p.x >= BSIZE || p.y >= BSIZE) return;
 
//DEBUG: colour in the hex which is supposedly the one clicked on
//clear the whole screen first.
/* for (int i=0;i<BSIZE;i++) {
for (int j=0;j<BSIZE;j++) {
board[i][j]=EMPTY;
}
} */
 
//What do you want to do when a hexagon is clicked?
board[p.x][p.y] = (int)'X';
repaint();
}
} //end of MyMouseListener class
} // end of DrawingPanel class
}
hexmech.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
import java.awt.*;
import javax.swing.*;
 
/* This is a companion class to hexgame.java. It handles all of the mechanics related to hexagon grids. */
 
public class hexmech
{
/* Helpful references:
http://www.codeproject.com/Articles/14948/Hexagonal-grid-for-games-and-other-projects-Part-1
http://weblogs.java.net/blog/malenkov/archive/2009/02/hexagonal_tile.html
http://www.tonypa.pri.ee/tbw/tut25.html
*/
 
/*
#define HEXEAST 0
#define HEXSOUTHEAST 1
#define HEXSOUTHWEST 2
#define HEXWEST 3
#define HEXNORTHWEST 4
#define HEXNORTHEAST 5
*/
 
//Constants
public final static boolean orFLAT= true;
public final static boolean orPOINT= false;
public static boolean ORIENT= orFLAT; //this is not used. We're never going to do pointy orientation
 
public static boolean XYVertex=true; //true: x,y are the co-ords of the first vertex.
//false: x,y are the co-ords of the top left rect. co-ord.
 
private static int BORDERS=50; //default number of pixels for the border.
 
private static int s=0; // length of one side
private static int t=0; // short side of 30o triangle outside of each hex
private static int r=0; // radius of inscribed circle (centre to middle of each side). r= h/2
private static int h=0; // height. Distance between centres of two adjacent hexes. Distance between two opposite sides in a hex.
 
public static void setXYasVertex(boolean b) {
XYVertex=b;
}
public static void setBorders(int b){
BORDERS=b;
}
 
/** This functions takes the Side length in pixels and uses that as the basic dimension of the hex.
It calculates all other needed constants from this dimension.
*/
public static void setSide(int side) {
s=side;
t = (int) (s / 2); //t = s sin(30) = (int) CalculateH(s);
r = (int) (s * 0.8660254037844); //r = s cos(30) = (int) CalculateR(s);
h=2*r;
}
public static void setHeight(int height) {
h = height; // h = basic dimension: height (distance between two adj centresr aka size)
r = h/2; // r = radius of inscribed circle
s = (int) (h / 1.73205); // s = (h/2)/cos(30)= (h/2) / (sqrt(3)/2) = h / sqrt(3)
t = (int) (r / 1.73205); // t = (h/2) tan30 = (h/2) 1/sqrt(3) = h / (2 sqrt(3)) = r / sqrt(3)
}
 
/*********************************************************
Name: hex()
Parameters: (x0,y0) This point is normally the top left corner
of the rectangle enclosing the hexagon.
However, if XYVertex is true then (x0,y0) is the vertex of the
top left corner of the hexagon.
Returns: a polygon containing the six points.
Called from: drawHex(), fillhex()
Purpose: This function takes two points that describe a hexagon
and calculates all six of the points in the hexagon.
*********************************************************/
public static Polygon hex (int x0, int y0) {
 
int y = y0 + BORDERS;
int x = x0 + BORDERS; // + (XYVertex ? t : 0); //Fix added for XYVertex = true.
// NO! Done below in cx= section
if (s == 0 || h == 0) {
System.out.println("ERROR: size of hex has not been set");
return new Polygon();
}
 
int[] cx,cy;
 
//I think that this XYvertex stuff is taken care of in the int x line above. Why is it here twice?
if (XYVertex)
cx = new int[] {x,x+s,x+s+t,x+s,x,x-t}; //this is for the top left vertex being at x,y. Which means that some of the hex is cutoff.
else
cx = new int[] {x+t,x+s+t,x+s+t+t,x+s+t,x+t,x}; //this is for the whole hexagon to be below and to the right of this point
 
cy = new int[] {y,y,y+r,y+r+r,y+r+r,y+r};
return new Polygon(cx,cy,6);
 
/*
x=200;
poly = new Polygon();
poly.addPoint(x,y);
poly.addPoint(x+s,y);
poly.addPoint(x+s+t,y+r);
poly.addPoint(x+s,y+r+r);
poly.addPoint(x,y+r+r);
poly.addPoint(x-t,y+r);
*/
}
 
/********************************************************************
Name: drawHex()
Parameters: (i,j) : the x,y coordinates of the inital point of the hexagon
g2: the Graphics2D object to draw on.
Returns: void
Calls: hex()
Purpose: This function draws a hexagon based on the initial point (x,y).
The hexagon is drawn in the colour specified in hexgame.COLOURELL.
*********************************************************************/
public static void drawHex(int i, int j, Graphics2D g2) {
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
Polygon poly = hex(x,y);
g2.setColor(hexgame.COLOURCELL);
//g2.fillPolygon(hexmech.hex(x,y));
g2.fillPolygon(poly);
g2.setColor(hexgame.COLOURGRID);
g2.drawPolygon(poly);
}
 
/***************************************************************************
* Name: fillHex()
* Parameters: (i,j) : the x,y coordinates of the initial point of the hexagon
n : an integer number to indicate a letter to draw in the hex
g2 : the graphics context to draw on
* Return: void
* Called from:
* Calls: hex()
*Purpose: This draws a filled in polygon based on the coordinates of the hexagon.
The colour depends on whether n is negative or positive.
The colour is set by hexgame.COLOURONE and hexgame.COLOURTWO.
The value of n is converted to letter and drawn in the hexagon.
*****************************************************************************/
public static void fillHex(int i, int j, int n, Graphics2D g2) {
char c='o';
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
if (n < 0) {
g2.setColor(hexgame.COLOURONE);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURONETXT);
c = (char)(-n);
g2.drawString(""+c, x+r+BORDERS, y+r+BORDERS+4); //FIXME: handle XYVertex
//g2.drawString(x+","+y, x+r+BORDERS, y+r+BORDERS+4);
}
if (n > 0) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
c = (char)n;
g2.drawString(""+c, x+r+BORDERS, y+r+BORDERS+4); //FIXME handle XYVertex
//g2.drawString(i+","+j, x+r+BORDERS, y+r+BORDERS+4);
}
}
 
//This function changes pixel location from a mouse click to a hex grid location
/*****************************************************************************
* Name: pxtoHex (pixel to hex)
* Parameters: mx, my. These are the co-ordinates of mouse click.
* Returns: point. A point containing the coordinates of the hex that is clicked in.
If the point clicked is not a valid hex (ie. on the borders of the board, (-1,-1) is returned.
* Function: This only works for hexes in the FLAT orientation. The POINTY orientation would require
a whole other function (different math).
It takes into account the size of borders.
It also works with XYVertex being True or False.
*****************************************************************************/
public static Point pxtoHex(int mx, int my) {
Point p = new Point(-1,-1);
 
//correction for BORDERS and XYVertex
mx -= BORDERS;
my -= BORDERS;
if (XYVertex) mx += t;
 
int x = (int) (mx / (s+t)); //this gives a quick value for x. It works only on odd cols and doesn't handle the triangle sections. It assumes that the hexagon is a rectangle with width s+t (=1.5*s).
int y = (int) ((my - (x%2)*r)/h); //this gives the row easily. It needs to be offset by h/2 (=r)if it is in an even column
 
/******FIX for clicking in the triangle spaces (on the left side only)*******/
//dx,dy are the number of pixels from the hex boundary. (ie. relative to the hex clicked in)
int dx = mx - x*(s+t);
int dy = my - y*h;
 
if (my - (x%2)*r < 0) return p; // prevent clicking in the open halfhexes at the top of the screen
 
//System.out.println("dx=" + dx + " dy=" + dy + " > " + dx*r/t + " <");
//even columns
if (x%2==0) {
if (dy > r) { //bottom half of hexes
if (dx * r /t < dy - r) {
x--;
}
}
if (dy < r) { //top half of hexes
if ((t - dx)*r/t > dy ) {
x--;
y--;
}
}
} else { // odd columns
if (dy > h) { //bottom half of hexes
if (dx * r/t < dy - h) {
x--;
y++;
}
}
if (dy < h) { //top half of hexes
//System.out.println("" + (t- dx)*r/t + " " + (dy - r));
if ((t - dx)*r/t > dy - r) {
x--;
}
}
}
p.x=x;
p.y=y;
return p;
}
}

I can't understand how we can design a pointy instead of flat hexagon! in terms of logic and geometry I understand that we have to change turn all the points by pi/6 but I cant make it work with your code

Okay, very nice code, but how can i manage greater boards without having over 9000 screens? maybe some mouse listener to scroll around on it, but I'm not able to do that yet..

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.