I'm currently recreating a Civilization game in Processing. I'm planning to implement the feature in a which a given unit can see every possible move it can make with a given number of hexes it is allowed to move. All possible endpoints are marked with red circles. However, units cannot move through mountains or bodies of water. I'm trying to approach this by finding out every possible combination of moves I can make without the unit going into a mountain or body of water but I can't figure out how I can determine every combination.
There are 6 directions that any unit can go in, north-east, north, north-west, south-east, south, south-west. The max number of movements I'm assigning to any unit would probably go up to 6. Any higher and I'm afraid processing may become to slow every time I move a unit.
I'm trying to recreate this:
What I'm hoping the result will look like with two possible movements (without the black arrows):
Raw version of that image:
Here is the code I use to draw the hex grid. Immediately after drawing each individual hex, its center's x coords and y coords are stored in xHexes and yHexes respectively. Also, immediately after generating the type of tile (e.g. grass, beach), the type of tile is also stored in an array named hexTypes. Therefore, I can get the x and y coords and type of hex of any hex I want on the map just by referencing its index.
Code used to draw a single hexagon:
beginShape();
for (float a = PI/6; a < TWO_PI; a += TWO_PI/6) {
float vx = x + cos(a) * gs*2;
float vy = y + sin(a) * gs*2;
vertex(vx, vy);
}
x is the x coord for centre of hexagon
y is the y coord for centre of hexagon
gs = radius of hexagon
Code used to tesselate hex over the window creating a hex grid:
void redrawMap() {
float xChange = 1.7;
float yChange = 6;
for (int y = 0; y < ySize/hexSize; y++) {
for (int x = 0; x < xSize/hexSize; x++) {
if (x % 2 == 1) {
// if any part of this hexagon being formed will be visible on the window and not off the window.
if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
drawHex(x*hexSize*xChange, y*hexSize*yChange, hexSize);
}
// only record and allow player to react with it if the entire tile is visible on the window
if (x*hexSize*xChange < width && int(y*hexSize*yChange) < height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange));
}
} else {
if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
drawHex(x*hexSize*xChange, y*hexSize*yChange+(hexSize*3), hexSize);
}
if (x*hexSize*xChange < width && int(y*hexSize*yChange+(hexSize*3)) < height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange+(hexSize*3)));
}
}
}
}
}
hexSize is a user-specified size for each hexagon, determining the number of hexagons that will be on the screen.
This answer will help you get to this (green is plains, red is hills and blue is water, also please don't flame my terrible grid):
Note that there is no pathfinding in this solution, only some very simple "can I get there" math. I'll include the full code of the sketch at the end so you can reproduce what I did and test it yourself. One last thing: this answer doesn't use any advanced design pattern, but it assume that you're confortable with the basics and Object Oriented Programming. If I did something which you're not sure you understand, you can (and should) ask about it.
Also: this is a proof of concept, not a "copy and paste me" solution. I don't have your code, so it cannot be that second thing anyway, but as your question can be solved in a bazillion manners, this is only one which I deliberately made as simple and visual as possible so you can get the idea and run with it.
First, I strongly suggest that you make your tiles into objects. First because they need to carry a lot of information (what's on each tile, how hard they are to cross, maybe things like resources or yield... I don't know, but there will be a lot of stuff).
The Basics
I organized my global variables like this:
// Debug
int unitTravelPoints = 30; // this is the number if "travel points" currently being tested, you can change it
// Golbals
float _tileSize = 60;
int _gridWidth = 10;
int _gridHeight = 20;
ArrayList<Tile> _tiles = new ArrayList<Tile>(); // all the tiles
ArrayList<Tile> _canTravel = new ArrayList<Tile>(); // tiles you can currently travel to
The basics being that I like to be able to change my grid size on the fly, but that's just a detail. What's next is to choose a coordinate system for the grid. I choose the simplest one as I didn't want to bust my brain on something complicated, but you may want to adapt this to another coordinate system. I choose the offset coordinate type of grid: my "every second row" is half a tile offset. So, instead of having this:
I have this:
The rest is just adjusting the spatial coordinates of the tiles so it doesn't look too bad, but their coordinates stays the same:
Notice how I consider that the spatial coordinates and the grid coordinates are two different things. I'll mostly use the spatial coordinates for the proximity checks, but that's because I'm lazy, because you could make a nice algorithm which do the same thing without the spatial coordinates and it would probably be less costly.
What about the travel points? Here's how I decided to work: your unit has a finite amount of "travel points". Here there's no unit, but instead a global variable unitTravelPoints which will do the same thing. I decided to work with this scale: one normal tile is worth 10 travel points. So:
Plains: 10 points
Hills: 15 points
Water: 1000 points (this is impassable terrain but without going into the details)
I'm not going to go into the details of drawing a grid, but that's mostly because your algorithm looks way better than mine on this front. On the other hand, I'll spend some time on explaining how I designed the Tiles.
The Tiles
We're entering OOP: they are Drawable. Drawable is a base class which contains some basic info which every visible thing should have: a position, and an isVisible setting which can be turned off. And a method to draw it, which I call Render() since draw() is already taken by Processing:
class Drawable {
PVector position;
boolean isVisible;
public Drawable() {
position = new PVector(0, 0);
isVisible = true;
}
public void Render() {
// If you forget to overshadow the Render() method you'll see this error message in your console
println("Error: A Drawable just defaulted to the catch-all Render(): '" + this.getClass() + "'.");
}
}
The Tile will be more sophisticated. It'll have more basic informations: row, column, is it currently selected (why not), a type like plains or hills or water, a bunch of neighboring tiles, a method to draw itself and a method to know if the unit can travel through it:
class Tile extends Drawable {
int row, column;
boolean selected = false;
TileType type;
ArrayList<Tile> neighbors = new ArrayList<Tile>();
Tile(int row, int column, TileType type) {
super(); // this calls the parent class' constructor
this.row = row;
this.column = column;
this.type = type;
// the hardcoded numbers are all cosmetics I included to make my grid looks less awful, nothing to see here
position.x = (_tileSize * 1.5) * (column + 1);
position.y = (_tileSize * 0.5) * (row + 1);
// this part checks if this is an offset row to adjust the spatial coordinates
if (row % 2 != 0) {
position.x += _tileSize * 0.75;
}
}
// this method looks recursive, but isn't. It doesn't call itself, but it calls it's twin from neighbors tiles
void FillCanTravelArrayList(int travelPoints, boolean originalTile) {
if (travelPoints >= type.travelCost) {
// if the unit has enough travel points, we add the tile to the "the unit can get there" list
if (!_canTravel.contains(this)) {
// well, only if it's not already in the list
_canTravel.add(this);
}
// then we check if the unit can go further
for (Tile t : neighbors) {
if (originalTile) {
t.FillCanTravelArrayList(travelPoints, false);
} else {
t.FillCanTravelArrayList(travelPoints - type.travelCost, false);
}
}
}
}
void Render() {
if (isVisible) {
// the type knows which colors to use, so we're letting the type draw the tile
type.Render(this);
}
}
}
The Tile Types
The TileType is a strange animal: it's a real class, but it's never used anywhere. That's because it's a common root for all tile types, which will inherit it's basics. The "City" tile may need very different variables than, say, the "Desert" tile. But both need to be able to draw themselves, and both need to be owned by the tiles.
class TileType {
// cosmetics
color fill = color(255, 255, 255);
color stroke = color(0);
float strokeWeight = 2;
// every tile has a "travelCost" variable, how much it cost to travel through it
int travelCost = 10;
// while I put this method here, it could have been contained in many other places
// I just though that it made sense here
void Render(Tile tile) {
fill(fill);
if (tile.selected) {
stroke(255);
} else {
stroke(stroke);
}
strokeWeight(strokeWeight);
DrawPolygon(tile.position.x, tile.position.y, _tileSize/2, 6);
textAlign(CENTER, CENTER);
fill(255);
text(tile.column + ", " + tile.row, tile.position.x, tile.position.y);
}
}
Each tile type can be custom, now, yet each tile is... just a tile, whatever it's content. Here are the TileType I used in this demonstration:
// each different tile type will adjust details like it's travel cost or fill color
class Plains extends TileType {
Plains() {
this.fill = color(0, 125, 0);
this.travelCost = 10;
}
}
class Water extends TileType {
// here I'm adding a random variable just to show that you can custom those types with whatever you need
int numberOfFishes = 10;
Water() {
this.fill = color(0, 0, 125);
this.travelCost = 1000;
}
}
class Hill extends TileType {
Hill() {
this.fill = color(125, 50, 50);
this.travelCost = 15;
}
}
Non-class methods
I added a mouseClicked() method so we can select a hex to check how far from it the unit can travel. In your game, you would have to make it so when you select a unit all these things fall into place, but as this is just a proof of concept the unit is imaginary and it's location is wherever you click.
void mouseClicked() {
// clearing the array which contains tiles where the unit can travel as we're changing those
_canTravel.clear();
for (Tile t : _tiles) {
// select the tile we're clicking on (and nothing else)
t.selected = IsPointInRadius(t.position, new PVector(mouseX, mouseY), _tileSize/2);
if (t.selected) {
// if a tile is selected, check how far the imaginary unit can travel
t.FillCanTravelArrayList(unitTravelPoints, true);
}
}
}
At last, I added 2 "helper methods" to make things easier:
// checks if a point is inside a circle's radius
boolean IsPointInRadius(PVector center, PVector point, float radius) {
// simple math, but with a twist: I'm not using the square root because it's costly
// we don't need to know the distance between the center and the point, so there's nothing lost here
return pow(center.x - point.x, 2) + pow(center.y - point.y, 2) <= pow(radius, 2);
}
// draw a polygon (I'm using it to draw hexagons, but any regular shape could be drawn)
void DrawPolygon(float x, float y, float radius, int npoints) {
float angle = TWO_PI / npoints;
beginShape();
for (float a = 0; a < TWO_PI; a += angle) {
float sx = x + cos(a) * radius;
float sy = y + sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
How Travel is calculated
Behind the scenes, that's how the program knows where the unit can travel: in this example, the unit has 30 travel points. Plains cost 10, hills cost 15. If the unit has enough points left, the tile is marked as "can travel there". Every time a tile is in travel distance, we also check if the unit can get further from this tile, too.
Now, if you're still following me, you may ask: how do the tiles know which other tile is their neighbor? That's a great question. I suppose that an algorithm checking their coordinates would be the best way to handle this, but as this operation will happen only once when we create the map I decided to take the easy route and check which tiles were the close enough spatially:
void setup() {
// create the grid
for (int i=0; i<_gridWidth; i++) {
for (int j=0; j<_gridHeight; j++) {
int rand = (int)random(100);
if (rand < 20) {
_tiles.add(new Tile(j, i, new Water()));
} else if (rand < 50) {
_tiles.add(new Tile(j, i, new Hill()));
} else {
_tiles.add(new Tile(j, i, new Plains()));
}
}
}
// detect and save neighbor tiles for every Tile
for (Tile currentTile : _tiles) {
for (Tile t : _tiles) {
if (t != currentTile) {
if (IsPointInRadius(currentTile.position, t.position, _tileSize)) {
currentTile.neighbors.add(t);
}
}
}
}
}
Complete code for copy-pasting
Here's the whole thing in one place so you can easily copy and paste it into a Processing IDE and play around with it (also, it includes how I draw my awful grid):
// Debug
int unitTravelPoints = 30; // this is the number if "travel points" currently being tested, you can change it
// Golbals
float _tileSize = 60;
int _gridWidth = 10;
int _gridHeight = 20;
ArrayList<Tile> _tiles = new ArrayList<Tile>();
ArrayList<Tile> _canTravel = new ArrayList<Tile>();
void settings() {
// this is how to make a window size's dynamic
size((int)(((_gridWidth+1) * 1.5) * _tileSize), (int)(((_gridHeight+1) * 0.5) * _tileSize));
}
void setup() {
// create the grid
for (int i=0; i<_gridWidth; i++) {
for (int j=0; j<_gridHeight; j++) {
int rand = (int)random(100);
if (rand < 20) {
_tiles.add(new Tile(j, i, new Water()));
} else if (rand < 50) {
_tiles.add(new Tile(j, i, new Hill()));
} else {
_tiles.add(new Tile(j, i, new Plains()));
}
}
}
// detect and save neighbor tiles for every Tile
for (Tile currentTile : _tiles) {
for (Tile t : _tiles) {
if (t != currentTile) {
if (IsPointInRadius(currentTile.position, t.position, _tileSize)) {
currentTile.neighbors.add(t);
}
}
}
}
}
void draw() {
background(0);
// show the tiles
for (Tile t : _tiles) {
t.Render();
}
// show how far you can go
for (Tile t : _canTravel) {
fill(0, 0, 0, 0);
if (t.selected) {
stroke(255);
} else {
stroke(0, 255, 0);
}
strokeWeight(5);
DrawPolygon(t.position.x, t.position.y, _tileSize/2, 6);
}
}
class Drawable {
PVector position;
boolean isVisible;
public Drawable() {
position = new PVector(0, 0);
isVisible = true;
}
public void Render() {
// If you forget to overshadow the Render() method you'll see this error message in your console
println("Error: A Drawable just defaulted to the catch-all Render(): '" + this.getClass() + "'.");
}
}
class Tile extends Drawable {
int row, column;
boolean selected = false;
TileType type;
ArrayList<Tile> neighbors = new ArrayList<Tile>();
Tile(int row, int column, TileType type) {
super(); // this calls the parent class' constructor
this.row = row;
this.column = column;
this.type = type;
// the hardcoded numbers are all cosmetics I included to make my grid looks less awful, nothing to see here
position.x = (_tileSize * 1.5) * (column + 1);
position.y = (_tileSize * 0.5) * (row + 1);
// this part checks if this is an offset row to adjust the spatial coordinates
if (row % 2 != 0) {
position.x += _tileSize * 0.75;
}
}
// this method looks recursive, but isn't. It doesn't call itself, but it calls it's twin from neighbors tiles
void FillCanTravelArrayList(int travelPoints, boolean originalTile) {
if (travelPoints >= type.travelCost) {
// if the unit has enough travel points, we add the tile to the "the unit can get there" list
if (!_canTravel.contains(this)) {
// well, only if it's not already in the list
_canTravel.add(this);
}
// then we check if the unit can go further
for (Tile t : neighbors) {
if (originalTile) {
t.FillCanTravelArrayList(travelPoints, false);
} else {
t.FillCanTravelArrayList(travelPoints - type.travelCost, false);
}
}
}
}
void Render() {
if (isVisible) {
// the type knows which colors to use, so we're letting the type draw the tile
type.Render(this);
}
}
}
class TileType {
// cosmetics
color fill = color(255, 255, 255);
color stroke = color(0);
float strokeWeight = 2;
// every tile has a "travelCost" variable, how much it cost to travel through it
int travelCost = 10;
// while I put this method here, it could have been contained in many other places
// I just though that it made sense here
void Render(Tile tile) {
fill(fill);
if (tile.selected) {
stroke(255);
} else {
stroke(stroke);
}
strokeWeight(strokeWeight);
DrawPolygon(tile.position.x, tile.position.y, _tileSize/2, 6);
textAlign(CENTER, CENTER);
fill(255);
text(tile.column + ", " + tile.row, tile.position.x, tile.position.y);
}
}
// each different tile type will adjust details like it's travel cost or fill color
class Plains extends TileType {
Plains() {
this.fill = color(0, 125, 0);
this.travelCost = 10;
}
}
class Water extends TileType {
// here I'm adding a random variable just to show that you can custom those types with whatever you need
int numberOfFishes = 10;
Water() {
this.fill = color(0, 0, 125);
this.travelCost = 1000;
}
}
class Hill extends TileType {
Hill() {
this.fill = color(125, 50, 50);
this.travelCost = 15;
}
}
void mouseClicked() {
// clearing the array which contains tiles where the unit can travel as we're changing those
_canTravel.clear();
for (Tile t : _tiles) {
// select the tile we're clicking on (and nothing else)
t.selected = IsPointInRadius(t.position, new PVector(mouseX, mouseY), _tileSize/2);
if (t.selected) {
// if a tile is selected, check how far the imaginary unit can travel
t.FillCanTravelArrayList(unitTravelPoints, true);
}
}
}
// checks if a point is inside a circle's radius
boolean IsPointInRadius(PVector center, PVector point, float radius) {
// simple math, but with a twist: I'm not using the square root because it's costly
// we don't need to know the distance between the center and the point, so there's nothing lost here
return pow(center.x - point.x, 2) + pow(center.y - point.y, 2) <= pow(radius, 2);
}
// draw a polygon (I'm using it to draw hexagons, but any regular shape could be drawn)
void DrawPolygon(float x, float y, float radius, int npoints) {
float angle = TWO_PI / npoints;
beginShape();
for (float a = 0; a < TWO_PI; a += angle) {
float sx = x + cos(a) * radius;
float sy = y + sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
Hope it'll help. Have fun!
You will have to use similar algorithms we use on pathfinding. you create a stack or queue that will hold a class storing the position of the hex and the number of moves left from that point, initially you insert your starting position with the number of moves you have and mark that hex as done ( to not re-use a position you have already been on ), then you pop an element, and you insert every neighbor of that hex with a number of moves -1. when you insert the hexes with zero moves, those are your endpoints. And before inserting any hex check if it's not already done.
I hope I was clear, your question was a bit vague but I tried to give you an idea of how these solutions are usually done, also I think your question fits more into algorithms rather then processing
Best of luck
This is a follow-up post to my previous question, here. I got a remarkable response to instead of using array data tracking, to use matrixes. Now, the code here works just as planned (as in, the rectangles somewhat most of the time get filled in properly with white), but it's very inconsistent. When holding the left or right mouse button the colors phase over each other in a battle of randomness, and I don't know nearly that much about why this is happening. Just for reference, I'm using Java in Processing 3.
This is a result that I made with the project. As you can see, it looks fine.
Except for that jitter when hovering over a rect, and that more than not the rectangles are not being filled in half the time. And plus, the hover color is cycling almost randomly.
int cols, rows;
int scl = 20;
boolean[][] matrix = new boolean[scl+1][scl+1];
void setup() {
size(400, 400);
int w = 400;
int h = 400;
cols = w / scl;
rows = h / scl;
}
void draw() {
background(255);
for (int x = 0; x < cols; x++) {
for (int y = 0; y < rows; y++) {
int xpos = x*scl;
int ypos = y*scl;
stroke(55);
if ((mouseX >= xpos && mouseX <= xpos+scl) &&
(mouseY >= ypos && mouseY <= ypos+scl)) {
fill(75);
if (mousePressed == true) {
println("Clicked at: " + xpos + " and " + ypos);
if (!matrix[xpos/scl][ypos/scl]) {
matrix[xpos/scl][ypos/scl] = true;
} else {
matrix[xpos/scl][ypos/scl] = false;
}
fill(100);
//here is the desired location for the fill to remain constant even
//after unclicking and leaving hover
}
println("Mouse at: " + xpos + " and " + ypos);
} else {
fill(50);
}
if (matrix[x][y]) {
//fill(204, 102, 0);
fill(240);
rect(xpos, ypos, scl, scl);
}
rect(xpos, ypos, scl, scl);
}
}
}
Remeber that Processing fires the draw() function 60 times per second.
So your check for whether the mouse is pressed is happening 60 times per second. That means you're toggling the state of whatever cell the mouse is in 60 times per second.
To fix that problem, you might switch to using the event functions like mousePressed() instead of constantly polling every frame.
From the reference:
int value = 0;
void draw() {
fill(value);
rect(25, 25, 50, 50);
}
void mousePressed() {
if (value == 0) {
value = 255;
} else {
value = 0;
}
}
As for certain cells being skipped over, that's because when you move the mouse, it doesn't actually go through every pixel. It "jumps" from frame to frame. Those jumps are usually small enough that humans don't notice it, but they're large enough that it's skipping over cells.
One solution to this is to use the pmouseX and pmouseY variables to calculate a line from the previous mouse position to the current mouse position, and fill in any cells that would have been hit along the way.
Title, I want to make my program shoot a ball when you press the button once, and then it keeps going and you can't do anything until it finishes. Current code for firing below.
public void act()
{
// Steps for calculating the launch speed and angle
ProjectileWorld myWorld = (ProjectileWorld) getWorld();
double shotStrength = myWorld.getSpeedValue();
double shotAngle = myWorld.getAngleValue();
// Checks to see if the space key is pressed, if it is, the projectile is fired
String key = Greenfoot.getKey();
if ("space".equals(key))
{
//xSpeed and ySpeed changed as requirements say
xSpeed = (int)(shotStrength*Math.cos(shotAngle));
ySpeed = -(int)(shotStrength*Math.sin(shotAngle));
actualX += xSpeed;
actualY += ySpeed;
setLocation ((int) actualX, (int) actualY);
}
right now this makes it so the ball only moves when I hold spacebar, and since that's the case, I can let go of the space bar and change the shotStrength and shotAngle while the ball is still in the air
The key here is to change the state of your 'act' method's context to 'moving ball'.
There are a number of ways to accomplish this, but basically it depends on how act() is called.
my first thought is to do something like this:
public enum ActionMode{ MovingBall, Shooting, Waiting, Etc }
public void act()
{
// Steps for calculating the launch speed and angle
ProjectileWorld myWorld = (ProjectileWorld) getWorld();
double shotStrength = myWorld.getSpeedValue();
double shotAngle = myWorld.getAngleValue();
// Checks to see if the space key is pressed, if it is, the projectile is fired
String key = Greenfoot.getKey();
if ("space".equals(key)){
mode = ActionMode.MovingBall;
}
while(mode==ActionMode.MovingBall){
//xSpeed and ySpeed changed as requirements say
xSpeed = (int)(shotStrength*Math.cos(shotAngle));
ySpeed = -(int)(shotStrength*Math.sin(shotAngle));
actualX += xSpeed;
actualY += ySpeed;
setLocation ((int) actualX, (int) actualY);
if( ballHasFinishedMoving(actualX, actualY) ){
mode==ActionMode.Waiting;
}
}
...
}
private boolean ballHasFinishedMoving( int actualX, int actualY ){
logic to determine if ball has finished.
}
I found this question that deals with the same issue. The provided answers work, but I need to change it slightly for my case. Below is the answer I went with:
double theta = Math.atan2(pointerY - height / 2, pointerX - width / 2);
if(theta<0)
theta = Math.PI - theta;
int whichSlice = 0;
double sliceSize = Math.PI*2 / 4;
double sliceStart;
for(int i=1; i<=4; i++) {
sliceStart = i*sliceSize;
if(theta < sliceStart) {
whichSlice = i;
break;
}
}
In my case, I need to rotate the quadrants by 45 degrees. Below is an example; red is what this code does, while green is what I want:
I've tried various code alterations, but still can't figure it out.
EDIT:
First off, create your circle in it's own desperate JComponent, and add it's own listeners - basically create a class for this circle, make the circle itself receive mouse events, and MAKE SURE THAT THE CIRCLE OCCUPIES THE ENTIRE RECTANGLE OF THE JCOMPONENT - it must be touching all edges (I will be using this.getHeight() and this must return the height of the bounding box of the circle)!!!
Fixed code below to support such a case, in addition to support y axis which increases downwards:
Step 1:
Check if we are inside the circle.
Step 2:
Check if we are above/below the diagonal lines (note: equations for diagonal lines are y = x, and y = -x)
Point pointWeAreChecking;
Point centerOfCircle;
double radius;
if(Math.pow(Math.pow(pointWeAreChecking.x-centerOfCircle.x , 2) + Math.pow(pointWeAreChecking.y-centerOfCircle.y , 2), 0.5) <= radius)
{
//Means we are in circle.
if(pointWeAreChecking.y>pointWeAreChecking.x)
{
//Means it is either in 2 or 3 (it is below y = -x line)
if(pointWeAreChecking.y>-pointWeAreChecking.x + this.getHeight()){
//We are in 2.
}else
{
//We are in 3.
}
}else
{
if(pointWeAreChecking.y>-pointWeAreChecking.x + this.getHeight())
{
//We are in 4.
}else
{
//We are in 2.
}
}
}