Skip to content

Instantly share code, notes, and snippets.

@salamander2
Created December 18, 2012 17:03
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • Save salamander2/4329783 to your computer and use it in GitHub Desktop.
Save salamander2/4329783 to your computer and use it in GitHub Desktop.
Hexagonal Grid in Java
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
}
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;
}
}
@original21
Copy link

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

@uncannysnake
Copy link

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..

@HuelsmanRyan
Copy link

THIS CODE SAVED MY LIFE , and i am a fan of potaos

@IsaacGHoward
Copy link

good code 10/10 big tip

@HuelsmanRyan
Copy link

did you know i cant spell potaotes

@IsaacGHoward
Copy link

no way Hosea

@PaulAtreide
Copy link

Thank you very much indeed, big help.

@sfXnXf
Copy link

sfXnXf commented Dec 21, 2018

This is good, but having actual images to use for the tiles would be better.

simple operations: read tile from file, blit tile to screen, (optional: take tile from buffered image,) write tile to file.

There is all kinds of code out there to do this with square tiles, but none that I can find to do it with hex tiles.

I've looked over the source to Wesnoth and to mapy and tiled and couldn't make heads nor tails of it. Most of the code is overly complicated. Let's make things even simpler: flat orientation only.

Can anybody explain how this is to be done?

@Nihilish
Copy link

Nihilish commented Jan 2, 2020

Does anyone know how to modify this code to display an image in the hexes on click? I tried changing g2.drawString with g2.drawImage and it doesn't change anything...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment