I am creating a simple game of 15 puzzle (Please see here: https://en.wikipedia.org/wiki/15_puzzle). I have an array of 4 by 4 buttons and want to know the best way to handle a user clicking on a button and moving around the numbers.
I have started off by creating a JavaFX gridpane to display the buttons as well as a HashMap (suggested by multiple people) to store a mapping between the displayed number (1-15) and the button at that location.
I populate the map with class objects "Btn"
public class Btn {
private int ID;
private int x;
private int y;
public Button button;
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
Btn btn = new Btn(count, j, i, new Button("" + count));
map.put(count, btn);
btn.button.setMinSize(100, 100);
btn.button.setOnAction(e -> {
if (isMoveLegal(btn) == true) {
move(btn);
}
});
count++;
grid.add(btn.button, j, i);
}
}
So far it has worked quite well. In the isMoveLegal() method I check if the empty field is above, left, right, or below the clicked button. Which gets quite complicated because of the HashMaps nature of not preserving any order. But my real problem lies in the move() method. Since there is no order, I can't just switch 2 map nodes or can I?
if (MoveDir.equals("Right")) {
map.get((btn.getYs()+1)*4 - (4-btn.getXs())).button.setText("" + map.get(btn.getID()).getID());
map.get(btn.getID()).button.setText("" + map.get((btn.getYs()+1)*4 - (4-btn.getXs())).getID());
int tempID = map.get(btn.getID()).getID();
map.get(btn.getID()).setID(16);
map.get((btn.getYs()+1)*4 - (4-btn.getXs())).setID(tempID);
// System.out.println(map.get(btn.getID()).getID());
// System.out.println(map.get((btn.getYs()+1)* 4 - (4-btn.getXs())).getID());
map.get(btn.getID()).setYs(map.get(btn.getID()).getYs()+1);
map.get((btn.getYs()+1)* 4 - (4-btn.getXs())).setYs(map.get((btn.getYs()+1)* 4 - (4-btn.getXs())).getYs()-1);
This is what I have come up with so far, which extremely complicated and tedious. To quickly sum it up, I first switch the labels of the buttons, then I change the ID variables of the 2 buttons, and then I change the cord variables (x & y) to indicate their new location in map/grid.
I'm pretty sure I have gone way too far with this and there is a simpler solution to all of this but I am just unsure what that would be. Should I ditch the HashMap entirely and just use an object array of Btns?
Thanks for the help.
The problem you're facing is that your UI and your data structure are too tightly coupled. You need to separate them, give them their own responsibilities, and use communication (method calls) between them to make things happen.
For example, the UI should only be responsible for displaying the numbers and allowing the user to click a number to perform a move. Using a gridpane of buttons seems reasonable for this. Each button only needs to know it's x,y coordinate.
The data model should be a 2-dimensional array that contains the number at that location. For example:
+-+-+-+
|4|1|8|
+-+-+-+
|2|3|7|
+-+-+-+
| |5|6|
+-+-+-+
(You can figure out how to represent the empty space. Maybe a null, or the number '0' or '-1')
The data model can be wrapped in a data object that handles questions about and manipulations to the data model. For example, one method it might contain is getTheNumberAtLocation(x,y) which the buttons will call, and will return the number at that location. The buttons will use this to determine what number they should show.
Another method it might contain is isThereASpaceNextToLocation(x,y). This would return whether or not a space exists next to the location given by the x,y. The button will call this method to determine whether or not it can be clicked.
Finally, you could have a moveLocationToEmptySpace(x,y) which will manipulate the data to put the number at the current location into the empty space. After that, all the buttons should call getTheNumberAtLocation(x,y) to update the number they are showing.
By arranging your code like this you've nicely separated the concern of the UI (show text, handle button clicks) from the concern of maintaining the game state (checking if a move is valid, performing a move, determining the state of the board). Now when you write code it will be easier to write because each method will be responsible for a small, manageable piece of logic.
To summarise:
Gridpane (4x4)
-> Button
-> label [calls GameData.getTheNumberAtLocation]
-> click [calls GameData.isThereASpaceNextToLocation, moveLocationToEmptySpace]
GameData
-> Array (4x4) (private)
-> Integer (1-15)
-> getTheNumberAtLocation(x,y)
-> isThereASpaceNextToLocation(x,y)
-> moveLocationToEmptySpace(x,y)
Related
I'm working on a 2D game for android so performance is a real issue and a must. In this game there might occur a lot of collisions between any objects and I don't want to check in bruteforce o(n^2) whether any gameobject collides with another one. In order to reduce the possible amount of collision checks I decided to use spatial hashing as broadphase algorithm becouse it seems quite simple and efficient - dividing the scene on rows and columns and checking collisions between objects residing only in the same grid element.
Here's the basic concept I quickly scratched:
public class SpatialHashGridElement
{
HashSet<GameObject> gameObjects = new HashSet<GameObject>();
}
static final int SPATIAL_HASH_GRID_ROWS = 4;
static final int SPATIAL_HASH_GRID_COLUMNS = 5;
static SpatialHashGridElement[] spatialHashGrid = new SpatialHashGridElement[SPATIAL_HASH_GRID_ROWS * SPATIAL_HASH_GRID_COLUMNS];
void updateGrid()
{
float spatialHashGridElementWidth = screenWidth / SPATIAL_HASH_GRID_COLUMNS;
float spatialHashGridElementHeight = screenHeight / SPATIAL_HASH_GRID_ROWS;
for(SpatialHashGridElement e : spatialHashGrid)
e.gameObjects.clear();
for(GameObject go : displayList)
{
for(int i = 0; i < go.vertices.length/3; i++)
{
int row = (int) Math.abs(((go.vertices[i*3 + 1] / spatialHashGridElementHeight) % SPATIAL_HASH_GRID_ROWS));
int col = (int) Math.abs(((go.vertices[i*3 + 0] / spatialHashGridElementWidth) % SPATIAL_HASH_GRID_COLUMNS));
if(!spatialHashGrid[row * SPATIAL_HASH_GRID_COLUMNS + col].gameObjects.contains(go))
spatialHashGrid[row * SPATIAL_HASH_GRID_COLUMNS + col].gameObjects.add(go);
}
}
}
The code isn't probably of the highest quality so if you spot anything to improve please don't hesitate to tell me but the most worrying problem that arises currently is that in 2 grid cells there might be same collision pairs checked. Worst case example (assuming none of the objects spans more than 2 cells):
Here we have 2 gameObjects colliding(red and blue). Each of them resides in 4 cells => therefore in each cell there will be the same pair to check.
I can't come up with some efficient approach to remove the possibility of duplicate pairs without a need to filter the grid after creating it in updateGrid(). Is there some brilliant way to detect that some collision pair has been already inserted even during the updateGrid function? I will be very grateful for any tips!
I'm trying to explain my idea using some pseudo-code (C# language elements):
public partial class GameObject {
// ...
Set<GameObject> collidedSinceLastTick = new HashSet<GameObject>();
public boolean collidesWith(GameObject other) {
if (collidedSinceLastTick.contains(other)) {
return true; // or even false, see below
}
boolean collided = false;
// TODO: your costly logic here
if (collided) {
collidedSinceLastTick.add(other);
// maybe return false if other actions depend on a GameObject just colliding once per tick
}
return collided;
}
// ...
}
HashSet and .hashCode() both can be tuned in some cases. Maybe you could even remove displayList and "hold" everything in spatialHashGrid to reduce the memory foot-print a little bit. Of course do that only if you don't need special access to displayList - in XML's DocumentObjectModel objects can be accessed by a path throught the tree, and "hot spots" can be accessed by ID where the ID has to be assigned explicitely. For serializing (saving game state or whatever) it should not be an issue to iterate through spatialHashGrid performance-wise (it's a bit slower than serializing the gameObject set because you may have to suppress duplicates - using Java serialization it even does not save the same object twice using the default settings, saving just a reference after the first occurence of an object).
i am using the import becker.robots file for this assignment!
I must create a class DistributeBot (extended RobotSE) which will put down a number of 'things' in the shape of 2 squares.
When the main method is then created, a single call to a method (which i must create myself using stepwise refinement and also contains a parameter) called this.putThings();
The method i am to create should have a single variable parameter to define the size of the squares! ie a parameter of (4) will make a 4x4 square of 'Things'.
The robot should set out the Things one line at a time from left to right! (once one line is displayed, it should move back to the LHS before displaying the next line)
I think i will be able to complete the scenario without problem whenever i am certain on how to create the method with the parameter i have specified.
Assumptions.
1. Starting position of the robot will always be the same. ie starting at 1, 1.
2. There will always be enough 'things' in the robots backpack to display the two squares.
Anyone have an idea how I would go about setting up this method initially with the variable parameter?
If you have your method starting
Public void MakeSquare(int size)
{
for(int i = 0; i < size; i++)
{
for(int x = 0; x < size; x++)
{
//drop thing
//move right
}
for(int y = 0; y < size; y++)
{
//move left
}
//move up
}
}
Whilst reading the question you should try and break it down into its simplest parts.
start with Build a square that is the most abstract form of the scenario and so makes your method, then break it down
//I want to move up until I hit size limit
for each number in "the size you want"
//I want to move right and drop a thing, until I hit size limit
for each number in "the size you want"
drop a thing
move one right
endloop
//I want to move back to the LHS until I hit size limit
for each number in "the size you want"
move one left
endloop
//then make the move up
move up
endlood
If you make this method and call it twice you will make the 2 squares as required)
I basically checked out a book from the Library and started learning Java. I'm trying to code a little score calculator for my golf league and this site has been a lof of help! So thanks for even being here!
Now to the question:
I have a 9 labels, created with NetBeans GUI, with names like jLabel_Hole1, jLabel_Hole2, ...
If a user selects the radio option to play the front nine those labels have number 1 - 9 and if they change it to the "Back Nine" then they should display 10 - 18. I can manually set each label to the new value on a selection change but I wanted to know if there was a more elegant way and if so if one of you could be kind enough to explain how it works.
Here is the code that I want to try and truncate:
TGL.jLbl_Hole1.setText("10");
TGL.jLbl_Hole2.setText("11");
TGL.jLbl_Hole3.setText("12");
TGL.jLbl_Hole4.setText("13");
TGL.jLbl_Hole5.setText("14");
TGL.jLbl_Hole6.setText("15");
TGL.jLbl_Hole7.setText("16");
TGL.jLbl_Hole8.setText("17");
TGL.jLbl_Hole9.setText("18");
I've read some things about String being immutable and maybe it's just a limitation but I would think there has to be way and I just can't imagine it.
Thanks.
Basically, rather then creating a individual label for each hole, you should create an array of labels, where each element in the array represents a individual hole.
So instead of...
TGL.jLbl_Hole1.setText("10");
TGL.jLbl_Hole2.setText("11");
TGL.jLbl_Hole3.setText("12");
TGL.jLbl_Hole4.setText("13");
TGL.jLbl_Hole5.setText("14");
TGL.jLbl_Hole6.setText("15");
TGL.jLbl_Hole7.setText("16");
TGL.jLbl_Hole8.setText("17");
TGL.jLbl_Hole9.setText("18");
You would have...
for (JLabel label : TGL.holeLables) {
lable.setText(...);
}
A better solution would be to hide the labels from the developer and simply provide a setter...
TGL.setHoleText(hole, text); // hole is int and text is String
Internally to your TGL class, you have two choices...
If you've used the form editor in Netbeans, you're going to have to place the components that Netbeans creates into your own array...
private JLabel[] holes;
//...//
// Some where after initComponents is called...
holes = new JLabel[9];
holes[0] = jLbl_Hole1;
// There other 8 holes...
Then you would simply provide a setter and getter methods that can update or return the value...
public void setHole(int hole, String text) {
if (hole >= 0 && hole < holes.length) {
holes[hole].setText(text);
}
}
public String getHole() {
String text = null;
if (hole >= 0 && hole < holes.length) {
text = holes[hole].getText();
}
return text;
}
Take a closer look at the Arrays tutorial for more details...
I've never found a Java GUI-generator to provide code that's any good. I may be wrong--there may be a good one, but I always prefer to position and name them myself. So,
/**
* The JLabels for the holes on the golf course.
* <p>
* holeLabels[0][i] are for the outward holes, 1-9.
* holeLabels[1][i] are for the inward holes, 10-18.
*/
private JLabel[][] holeLabels;
/**
* The starts of the outward and inward ranges of holes.
*/
private static final int[] holeStart = {1, 10};
// Later
holeLabels = new JLabel[2][9];
for(final int i = 0; i < holeLabels.length; i++) {
for (final int j = 0; j < holeLabels[i].length; j++) {
holeLabel[i][j] = new JLabel();
holeLabel[i][j].setText(Integer.toString(holeStart[i] + j));
}
}
Interestingly, holeLabels.length is 2. holeLabels is an array of 2 arrays of 9 ints. i goes from 0 to 1, and j goes from 0 to 8, so the text computation works. The reason I did things this way is so you can easily place the labels in an appropriate GridLayout later.
I am generating my world (random, infinite and 2d) in sections that are x by y, when I reach the end of x a new section is formed. If in section one I have hills, how can I make it so that in section two those hills will continue? Is there some kind of way that I could make this happen?
So it would look something like this
1221
1 = generated land
2 = non generated land that will fill in the two ones
I get this now:
Is there any way to make this flow better?
This seems like just an algorithm issue. Your generation mechanism needs a start point. On the initial call it would be say 0, on subsequent calls it would be the finishing position of the previous "chunk".
If I was doing this, I'd probably make the height of the next point plus of minus say 0-3 from the previous, using some sort of distribution - e.g. 10% of the time it's +/1 3, 25% of the time it is +/- 2, 25% of the time it is 0 and 40% of the time it is +/- 1.
If I understood your problem correctly, here is a solution:
If you generated the delta (difference) between the hills and capped at a fixed value (so changes are never too big), then you can carry over the value of the last hill from the previous section when generating the new one and apply the first randomly genenarted delta (of the new section) to the carried-over hill size.
If you're generating these "hills" sequentially, I would create an accessor method that provides the continuation of said hill with a value to begin the next section. It seems that you are creating a random height for the hill to be constrained by some value already when drawing a hill in a single section. Extend that functionality with this new accessor method.
My take on a possible implementation of this.
public class DrawHillSection {
private int index;
private int x[50];
public void drawHillSection() {
for( int i = 0; i < 50; i++) {
if (i == 0) {
getPreviousHillSectionHeight(index - 1)
}
else {
...
// Your current implementation to create random
// height with some delta-y limit.
...
}
}
}
public void getPreviousHillSectionHeight(int index)
{
return (x[49].height);
}
}
I am creating a game using a 10x10 2D array. The player starts at the top left hand corner indicated as "P" and the objective is to get the player to avoid obstacles to get to the treasure indicated as "T" located in the lower right corner.
How would I go about making the player move about the grid using commands Up/Down/Left/Right?
Would I use a for loop to count through the elements in the array to designate the move?
Here is what I have so far:
import java.util.Scanner;
import java.util.Random;
public class Adventure {
public static void main(String[] args) {
char grid[][]= new char[10][10];
Scanner move = new Scanner(System.in);
System.out.println("Here is the current game board:");
System.out.println("-------------------------------");
for(int i=0; i<grid.length; i++) {
for(int j=0; j<grid.length; j++) {
double random = Math.random();
if(random <=.05) {
grid[i][j]='*';
}
else if(random > .06 && random <= .15) {
grid[i][j]='X';
}
else {
grid[i][j]='.';
}
grid[0][0]='P';
grid[9][9]='T';
System.out.print(grid[i][j]);
}
System.out.println("");
}
System.out.print("Enter your move (U/D/L/R)>");
}
}
you should keep track of the current position of the player and just update those variables.
initial values would be (0,0) as you said.
int px = 0;
int py = 0;
when a move is made, update the variables accordingly:
grid[px][py] = <empty cell>;
switch (move) {
case 'u': py += 1; break;
case 'r': px += 1; break;
...
}
grid[px][py] = 'P';
of course you shouldn't just updated the values "blindly", you should insert some validation logic to follow the rules of the game:
if (grid[px][py] != <obstacle> )
// update player coordinates...
Looks like you're using row-major ordering, judging from the way your board prints out. Based on that, here's what you'll need to do:
First, you need to store the player's position somewhere. Right now it's hardcoded to 0,0.
Second, you need to read in the player's move. That will have to happen in a loop, where you get a move, check if the move is allowed, perform the move, and display the results.
Third, you need to be able to calculate the new position based on the move. Up means row -= 1. Right means column += 1. Etc.
Given the new coordinates, you need to make sure the move is valid. At the very least, you have to stop them from walking off the board, but you may also prevent them from entering a square with an obstacle, etc.
Once you know that the move is valid, you have to update the variables you're storing the current coordinates in.
At the end of the loop, you'll need to redraw the board.
That's the basic gist of it. Right now you are doing everything in main(), and that's okay, but if it were me I would start to split things out into separate methods, like InitializeBoard(), GetNextMove(), CheckIfMoveIsValid(int r, int c), and so on. That way, main() becomes a high-level view of your game loop, and the guts of the different operations are compartmentalized and more easy to deal with. This will require storing off things like your game board into class variables rather than local variables, which should actually make things like obstacle detection easier than it would be currently.
All of the above answers are great. Here are a few suggestions I would make:
Instead of a char two-dimensional array, I would make a custom object, such as Space, and define a two-dimensional array of Spaces (eg, Space[][]). There are a few reasons for this:
You can define a space in a variety of ways (rather than just 1 character). For example, Space[i][j].hasTreasure() can return a boolean to let you know whether or not you found the treasure.
If you want to add functionality later, its as easy as adding an attribute to your Space class. Again, you are not limited to one character here.
More to your question of movement, I would also recommend a few things. Similar to redneckjedi's suggestion of a CheckIfMoveIsValid() method, I would pass the grid and move direction as parameters and return a boolean. To ensure that you do not end up with ArrayIndexOutOfBounds issues, I would also suggest adding a row/column of walls on each side. I would widen the grid out to 12x12 and put a strip of obstacle-type blocks around the outside. This will ensure that you cannot step outside of the grid as hitting a wall will always return 'false' on a valid move.