I'm making a simulation in a 3D environment. So far, I have the movements of all the creatures, but it is not "smooth". I've tried quite a few things but was horribly wrong. Now I just have no idea what to do. I was thinking of implementing a vector (not vector class) but don't really know how.
import env3d.EnvObject;
import java.util.ArrayList;
abstract public class Creature extends EnvObject
{
/**
* Constructor for objects of class Creature
*/
public Creature(double x, double y, double z)
{
setX(x);
setY(y);
setZ(z);
setScale(1);
}
public void move(ArrayList<Creature> creatures, ArrayList<Creature> dead_creatures)
{
double rand = Math.random();
if (rand < 0.25) {
setX(getX()+getScale());
setRotateY(90);
} else if (rand < 0.5) {
setX(getX()-getScale());
setRotateY(270);
} else if (rand < 0.75) {
setZ(getZ()+getScale());
setRotateY(0);
} else if (rand < 1) {
setZ(getZ()-getScale());
setRotateY(180);
}
if (getX() < getScale()) setX(getScale());
if (getX() > 50-getScale()) setX(50 - getScale());
if (getZ() < getScale()) setZ(getScale());
if (getZ() > 50-getScale()) setZ(50 - getScale());
// collision detection
if (this instanceof Fox) {
for (Creature c : creatures) {
if (c.distance(this) < c.getScale()+this.getScale() && c instanceof Tux) {
dead_creatures.add(c);
}
}
}
}
}
import env3d.Env;
import java.util.ArrayList;
/**
* A predator and prey simulation. Fox is the predator and Tux is the prey.
*/
public class Game
{
private Env env;
private boolean finished;
private ArrayList<Creature> creatures;
/**
* Constructor for the Game class. It sets up the foxes and tuxes.
*/
public Game()
{
// we use a separate ArrayList to keep track of each animal.
// our room is 50 x 50.
creatures = new ArrayList<Creature>();
for (int i = 0; i < 55; i++) {
if (i < 5) {
creatures.add(new Fox((int)(Math.random()*48)+1, 1, (int)(Math.random()*48)+1));
} else {
creatures.add(new Tux((int)(Math.random()*48)+1, 1, (int)(Math.random()*48)+1));
}
}
}
/**
* Play the game
*/
public void play()
{
finished = false;
// Create the new environment. Must be done in the same
// method as the game loop
env = new Env();
// Make the room 50 x 50.
env.setRoom(new Room());
// Add all the animals into to the environment for display
for (Creature c : creatures) {
env.addObject(c);
}
// Sets up the camera
env.setCameraXYZ(25, 50, 55);
env.setCameraPitch(-63);
// Turn off the default controls
env.setDefaultControl(false);
// A list to keep track of dead tuxes.
ArrayList<Creature> dead_creatures = new ArrayList<Creature>();
// The main game loop
while (!finished) {
if (env.getKey() == 1) {
finished = true;
}
// Move each fox and tux.
for (Creature c : creatures) {
c.move(creatures, dead_creatures);
}
// Clean up of the dead tuxes.
for (Creature c : dead_creatures) {
env.removeObject(c);
creatures.remove(c);
}
// we clear the ArrayList for the next loop. We could create a new one
// every loop but that would be very inefficient.
dead_creatures.clear();
// Update display
env.advanceOneFrame();
}
// Just a little clean up
env.exit();
}
/**
* Main method to launch the program.
*/
public static void main(String args[]) {
(new Game()).play();
}
}
You haven't shown enough of your program. Basically, if you want animation to be smooth, and you want to do it yourself (as opposed to using JavaFX or something), then you need to do lots of inter-frames. So rather than advancing an entire timer tick, advance a 10th of a timer tick, move everything on a screen a tiny bit, and then advance again. You should have the background redraw happening every 10th of a second for smooth animation.
As vy32 mentioned, we need to see more of your code. But it looks like you are missing timing code.
What you probably want to do is check the time each iteration of your game loop and then sleep for a certain amount of time to achieve some desired frame rate. Otherwise your game loop will run hundreds of thousands of times a second.
Alternatively, you should be advancing your creatures by a distance that is proportional to the amount of time that has elapsed since the previous frame.
Here is an example of a very simple regulated loop ("fps" is the desired framerate):
private long frameLength = 1000000000 / fps;
public void run() {
long ns = System.nanoTime();
while (!finished) {
//Do one frame of work
step();
//Wait until the time for this frame has elapsed
try {
ns += frameLength;
Thread.sleep(Math.max(0, (ns - System.nanoTime())/10000000));
} catch (InterruptedException e) {
break;
}
}
}
It should be very easy to retrofit this into your game loop.
Related
I am trying to get my Asteroids game in Java to have the ArrayList container be removed once it's off screen. I figured out how to stop having it print when off screen, but can see in my console the array continues to grow. Not sure where to go from here.
I think the way I can get them to be removed when off screen is by either using the remove or set feature with arrayLists. Visually everything is disappearing right, but in the console my ArrayList is still growing. I thought about setting a limit of how long it can be, but not sure if there is a better way than that.
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Iterator;
public class AsteroidGame extends Frame {
private int FrameWidth = 500;
private int FrameHeight = 400;
static public void main(String[] args) {
AsteroidGame world = new AsteroidGame();
world.show();
world.run();
}
public AsteroidGame() {
setTitle("Asteroid Game0");
setSize(FrameWidth, FrameHeight);
addKeyListener(new keyDown());
addWindowListener(new CloseQuit());
}
public void run() {
while (true) {
movePieces();
repaint();
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
}
private ArrayList asteroids = new ArrayList();
private ArrayList rockets = new ArrayList();
private Station station = new Station(FrameWidth / 2, FrameHeight - 20);
public void paint(Graphics g) {
station.paint(g);
Iterator astIt = asteroids.iterator();
while (astIt.hasNext()) {
Asteroid rock = (Asteroid) astIt.next();
if (rock.y >= 400 || rock.x >= 500){
rock = null;
} else {
rock.paint(g);
}
}
Iterator rocIt = rockets.iterator();
while (rocIt.hasNext()) {
Rocket rock = (Rocket) rocIt.next();
if (rock.y >= 400 || rock.x >= 500) {
rock = null;
} else {
rock.paint(g);
}
}
}
public void movePieces() {
if (Math.random() < 0.3) {
Asteroid newRock = new Asteroid(FrameWidth * Math.random(), 20, 10 * Math.random() - 5, 3 + 3 * Math.random());
if (newRock.y >= 500 || newRock.x >= 500){
asteroids.remove(0);
} else{
asteroids.add(newRock);
}
System.out.println(asteroids.size());
}
Iterator astIt = asteroids.iterator();
while (astIt.hasNext()) {
Asteroid rock = (Asteroid) astIt.next();
if (rock.y >= 400 || rock.x >= 500) {
rock = null;
} else {
rock.move();
station.checkHit(rock);
}
}
Iterator rocIt = rockets.iterator();
while (rocIt.hasNext()) {
Rocket rock = (Rocket) rocIt.next();
if (rock.y >= 400 || rock.x >= 500) {
rock = null;
} else {
rock.move(asteroids);
}
}
}
private class gameMover extends Thread {
public void run() {
while (true) {
movePieces();
repaint();
try {
sleep(100);
} catch (Exception e) {
}
}
}
}
Change:
rock = null;
to:
astIt.remove();
Assigning null to the variable that has been assigned the value of an element of a List does absolutely nothing to either the List or the element in it; it only affects the value that the variable holds.
As an aside, nice variable name choice of rock - it is appropraite for both types of object - either an abbreviation of "rocket" or a reasonable synonym for an astroid.
Change rock = null; to asteroids.remove(rock); or astIt.remove();
and it should be fine, also no need to set the variable to null as the garbage collector will take care of it for you.
EDIT
Actually asteroids.remove(rock); will throw an exception as said in the comments of this answer, so nevermind it and use the other.
Also I think when in movePieces() you create a new rock and you check if this new rock is outside the screen, I don't think removing the first asteroid in the ArrayList is correct as you will not add the new rock (which may be right if the rock can actually randomly spawn outside the screen) but you will also remove a maybe fine working asteroid from the ArrayList (and thus from the game and the screen).
So, personally I would change that part of code to:
if (Math.random() < 0.3) {
Asteroid newRock = new Asteroid(FrameWidth * Math.random(), 20, 10 * Math.random() - 5, 3 + 3 * Math.random());
if (!(newRock.y >= 500 || newRock.x >= 500)){
asteroids.add(newRock);
}
System.out.println(asteroids.size());
}
But tell me if this works for you.
I'm making a snake game(for those who don't know) where the snake is controlled by an AI using different algorithms to catch the food. The difference with the regular game is that the snake doesn't move unless a command has been sent by the AI.
My problem is that as soon as I run the AI, the AI creates a stack of commands to be executed to catch the food but my GUI just freezes; probably because it can't keep up with the amount of repaints that the move stacks cause. Through console logs, I can see that AI and the game logic is still running.
I tried to do Thread.sleep() after each move but I guess this just makes the entire program including the GUI sleep. I also have a Timer for my paintComponent but that doesn't seem to change anything.
How can you make your program sleep so that the GUI can catch up to what's happening?
EDIT:
Ok guys, I tried your solutions and it's still not working as it should. I don't really want to just dump the code here but I'm really lost. I have a timer that should repaint on a 140 millisecond interval(that's the value of DELAY), the commands are sent on a different thread which goes to sleep after each key press for 1000 milliseconds and I call repaint() after each call to move()... Here is relevant code(the original code without my modifications here):
private void initGame() {
dots = 5;
for (int z = 0; z < dots; z++) {
x[z] = 50 - z * 10;
y[z] = 50;
}
locateApple();
for (int k = blockNb - 1; k > 0; k--) {
locateBlock(k, apple_x, apple_y);
}
if (blocks) {
locateBlock(0, apple_x, apple_y);
}
timer = new Timer(DELAY, this);
timer.start();
startAi();
}
// AI CONTROLLER
public void startAi() {
Ai theAi = new Ai();
String move = "";
switch (ai) {
case "BFS":
move = theAi.bfs(this);
break;
}
//AI returns a string where each char is a move command
autoMove(move);
}
public void autoMove(String move) {
try {
Robot robot = new Robot();
System.out.println(move);
if (move != "#No") {
Thread thread = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < move.length(); j++) {
if (move.charAt(j) == 'l') {
robot.keyPress(KeyEvent.VK_LEFT);
robot.keyRelease(KeyEvent.VK_LEFT);
}
if (move.charAt(j) == 'r') {
robot.keyPress(KeyEvent.VK_RIGHT);
robot.keyRelease(KeyEvent.VK_RIGHT);
}
if (move.charAt(j) == 'u') {
robot.keyPress(KeyEvent.VK_UP);
robot.keyRelease(KeyEvent.VK_UP);
}
if (move.charAt(j) == 'd') {
robot.keyPress(KeyEvent.VK_DOWN);
robot.keyRelease(KeyEvent.VK_DOWN);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
thread.run();
}
} catch (AWTException e) {
e.printStackTrace();
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
if (inGame) {
g.drawImage(apple, apple_x, apple_y, this);
for (int j = 0; j < blockNb; j++) {
g.drawImage(block, block_x[j], block_y[j], this);
}
for (int z = 0; z < dots; z++) {
if (z == 0) {
g.drawImage(head, x[z], y[z], this);
} else {
g.drawImage(ball, x[z], y[z], this);
}
}
Toolkit.getDefaultToolkit().sync();
} else {
// gameOver(g);
}
}
private void move() {
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
if (leftDirection) {
x[0] -= DOT_SIZE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
}
if (downDirection) {
y[0] += DOT_SIZE;
}
}
#Override
public void actionPerformed(ActionEvent e) {
if (inGame) {
repaint();
}
}
private class TAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
move();
checkApple();
checkCollision();
repaint();
}
}
EDIT 2: Also, I just wanted to point out that I tried to move without relying on a robot but to no avail.
Part 1:
Difference bettween Thread.sleep:
When you use it in main Thread(the Thread which java use to run the program)
then your whole program just freeze for that reason.
When you use A Thread for example(follow code)
new Thread(new Runnable(){
public void run(){
//do something...
while(flag==false)
Thread.sleep(a given time) //it need and try catch
else
//do your move
});
then only this Thread freeze (for a given time) or (Whatever you transform it to freeze).
In your case you can use a flag so every time a commant is hitten by user
the flag is going true and then again false to keep stopped the part of the
game you want but the main Thread your programms need to work still works
(if it is a window or anything...)
Part 2:(Basic form of your Thread)(The flag i use must be seen by all Methods)(public or private)
new Thread(new Runnable() {
public void run() {
flag=true;
while(flag==true){
if(move.equals("yes")){
//your code
move="no";
}else
Thread.sleep(20); //Sleep For a While(and then check again)
}
//That means that your Thread will run all over your game
//until game over (when game over just do the flag=false;)
//and The Thread will stop-exit
}});
*About Repaint Method(Dont call the repaint method too fast)
Call it only when the player make a move(this is the time that the frame
need to be repainted[Ok if you have .gif images in your game just dont see this]
Part 3:(When i made a similar game what i did)
Before some months i tried a similar game.The basic idea was a player who must past a labirinth so....
For each level i had one class like this...
public abstarct class Level2(){
//The game was into a panel like your.
//Here i added .setFocusable and .addKeyListener to panel
//Here it was an
public void actionListener(){
//The actionListener checked if the player found the door(for next level)
//Here it was the repaint so every time something happened repaint()
repaint();
}//End of Action Listener
//Use the paint or paintComponent what you need..
public void paint[or paintComponent](Graphics g){
//the 'squares' and the icons the had to be *repainted* again
}
//Here i had an extra class into this for the KeyListeners
//And I added the KeyListener like your game(up,down,left,right)
//I i said before i added this KeyListener to the panel
private class TAdapter extends KeyAdapter {
//KeyPressed,Released...etc
}
}
Thats the basic idea and for your game i think.
The Thread it's an extra option i can't help more you must find the way...
You should have a single "update" looped, from which your execute update commands and repaint request.
A simple approach would be to use a Swing Timer, which can trigger updates to a listener at regular intervals. It has the benefit of been triggered within the context of the EDT making it safe to update the UI from within. See How to use Swing Timers for more details
A more complex approach would be to use a Thread, which contained some kind of loop. This would perform the required updates and schedule repaint, but you'd need to insert Thread.sleep in to allow time for the updates to occur at a regular bases. The problem with this is you will need to synchronise your updates so that you don't update the model while painting is occurring as well synchronise you updates to the UI with the EDT
You need to have some sort of game scheduler loop and an understanding of how it works.
Here is some example code for java swing:
http://staticvoidgames.com/tutorials/swing/swingTimers
Another way to simplify things along with the scheduler is to make your game turn based at first. That is when the player moves 1turn (ie input) the game does all its processing and blocks on user input till the processing is done (ie single loop).
Every time your game AI outputs a new move, you need to call paint, but you also need to wait for whatever event is called when the painting is done before you output your next move, and you may want to wait like a second longer or something, play around with it
pseudo code
def fun 0 // entry point for program
// hook paint done event to func 1
call fun 2
def fun 1 // event handler method for when painting done
call fun 2
def fun 2 // function that does AI work
// do game logic
// call paint
I was wondering... Is there a way that I could subtract 32 from a number in a specific amount of time? Such as 500 mils?
If you could help out, it would be great!
Thanks!
public void update() {
x += dx;
if(this.y % 32 == 0) {
this.tileY = this.y / 32;
}
if(this.x % 32 == 0) {
this.tileX = this.x / 32;
}
System.out.println(tileX);
}
public void moveLeft () {
// subtract 32 dx in 500 ms
}
Well, here is a lovely code I've developed for you. I've added the keyword static to be able to call it from main without creating any objects, but it does not use anything from a static context.
As my comments through the code try to explain, this isn't the perfect solution, it's just a start, you may face issues such as multi-threading errors (if you decide to use a separate Thread to update the position) or slight timing issues if the body of the method takes a while to execute.
If you feel the nanosecond precision is a bit too much for your purposes, remember there is also Thread.sleep(int milis).
Here is the code (try changing the values calling moveLeft(int, int) to see the results):
public class Slider {
public static void main(String[] args) {
Thread thread = new Thread() {
#Override
public void run() {
/*
* If you are going to use something like this, beware you are multi-threading
* Make sure what you do is thread-safe
*/
moveLeft(32, 500);
}
};
thread.start();
}
public static void moveLeft(int distance, int milis) {
//time_px is how many nanoseconds the Thread can sleep until it has to move 1 dx
double time_px = (100000*milis)/distance;
if (time_px >= 1) {
//Get the milis and nanos, rounding for Thread.sleep
long time_round = (long) Math.floor(time_px);
long milis_sleep = time_round/100000;
System.out.print("Waiting " + milis_sleep + "ms ");
int nano_sleep = (int) (time_round%100000);
System.out.println(nano_sleep + "ns per dx");
for (int i=0; i<distance; i++) {
try {
Thread.sleep(milis_sleep, nano_sleep);
/*
* Your code here
* A long code here might not get you the desired result since the sleeping does
* not account for the time spent processing the code. But this is a good start
*/
System.out.println("moving 1 dx");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
else {
System.out.println("Cannot go that fast");
//If you are moving that fast (more than 1 dx per nanosecond) then you need to change this up a little.
}
}
}
Okay, so why would my frames per second drop at a random time while playing the game? and what can I do to fix it. Below im going to show the code that I have with the engine that was built.
public Engine() {
Log.d("Engine","Engine constructor");
p_view = null;
p_canvas = null;
p_thread = null;
p_running = false;
p_paused = false;
p_resume = false;
p_paintDraw = null;
p_paintFont = null;
p_numPoints = 0;
p_typeface = null;
p_preferredFrameRate = 40;
p_sleepTime = 1000 / p_preferredFrameRate;
p_pauseCount = 0;
p_group = new LinkedList<Sprite>();
}
/**
* Runnable.run thread method (MAIN LOOP)
*/
#Override
public void run() {
Log.d("Engine","Engine.run start");
ListIterator<Sprite> iter=null, iterA=null, iterB=null;
Timer frameTimer = new Timer();
int frameCount=0;
int frameRate=0;
long startTime=0;
long timeDiff=0;
while (p_running) {
// Process frame only if not paused
if (p_paused) continue;
// Calculate frame rate
frameCount++;
startTime = frameTimer.getElapsed();
if (frameTimer.stopwatch(1000)) {
frameRate = frameCount;
frameCount = 0;
//reset touch input count
p_numPoints = 0;
}
// Call abstract update method in sub-class
update();
/**
* Test for collisions in the sprite group.
* Note that this takes place outside of rendering.
*/
iterA = p_group.listIterator();
while (iterA.hasNext()) {
Sprite sprA = (Sprite)iterA.next();
if (!sprA.getAlive()) continue;
if (!sprA.getCollidable()) continue;
/*
* Improvement to prevent double collision testing
*/
if (sprA.getCollided())
continue; //skip to next iterator
//iterate the list again
iterB = p_group.listIterator();
while (iterB.hasNext()) {
Sprite sprB = (Sprite)iterB.next();
if (!sprB.getAlive()) continue;
if (!sprB.getCollidable()) continue;
/*
* Improvement to prevent double collision testing
*/
if (sprB.getCollided())
continue; //skip to next iterator
//do not collide with itself
if (sprA == sprB) continue;
/*
* Ignore sprites with the same ID? This is an important
* consideration. Decide if your game requires it or not.
*/
if (sprA.getIdentifier() == sprB.getIdentifier())
continue;
if (collisionCheck(sprA, sprB)) {
sprA.setCollided(true);
sprA.setOffender(sprB);
sprB.setCollided(true);
sprB.setOffender(sprA);
break; //exit while
}
}
}
// begin drawing
if (beginDrawing()) {
// Call abstract draw method in sub-class
draw();
/**
* Draw the group entities with transforms
*/
iter = p_group.listIterator();
while (iter.hasNext()) {
Sprite spr = (Sprite)iter.next();
if (spr.getAlive()) {
spr.animate();
spr.draw();
}
}
/**
* Print some engine debug info.
*/
int x = p_canvas.getWidth()-150;
p_canvas.drawText("ENGINE", x, 20, p_paintFont);
p_canvas.drawText(toString(frameRate) + " FPS", x, 40,
p_paintFont);
p_canvas.drawText("Pauses: " + toString(p_pauseCount),
x, 60, p_paintFont);
// done drawing
endDrawing();
}
/*
* Do some cleanup: collision notification, removing
* 'dead' sprites from the list.
*/
iter = p_group.listIterator();
Sprite spr = null;
while (iter.hasNext()) {
spr = (Sprite)iter.next();
//remove from list if flagged
if (!spr.getAlive()) {
iter.remove();
continue;
}
//is collision enabled for this sprite?
if (spr.getCollidable()) {
//has this sprite collided with anything?
if (spr.getCollided()) {
//is the target a valid object?
if (spr.getOffender() != null) {
/*
* External func call: notify game of collision
* (with validated offender)
*/
collision(spr);
//reset offender
spr.setOffender(null);
}
//reset collided state
spr.setCollided(false);
}
}
}
// Calculate frame update time and sleep if necessary
timeDiff = frameTimer.getElapsed() - startTime;
long updatePeriod = p_sleepTime - timeDiff;
if (updatePeriod > 0) {
try {
Thread.sleep( updatePeriod ); // i notice this is called when frames are low
}
catch(InterruptedException e) {}
}
}//while
Log.d("Engine","Engine.run end");
System.exit(RESULT_OK);
}
public void setFrameRate(int rate) {
p_preferredFrameRate = rate;
p_sleepTime = 1000 / p_preferredFrameRate;
}
So this is not the entire engine, but it is everything that deals with the frames Why is it randomly dropping? What I am noticing that thread.sleep is being called when the frames drop below 15. I am using a nexus 5 to test this running on android 4.4.2, and My question here is How do I stop the frames from dropping that low and having it sleep especially after calling a recreate(); method when you lose the game?
Just take out the Thread.Sleep statement altogether. Or just pass "0" into the Sleep call (if you are trying to yield cycles to other threads and processes). On each cycle of the run() loop, calculate how much time actually elapsed and update your simulation based on this value. In other words, don't try to force a hard-coded frame rate.
The device has an upperbound on frame rate (60fps on my device). So you'll likely be sleeping a little during each loop anyway.
I'm actually working on a memory game and I'm stuck at the point where I should write the gameplay-part of the game.
So:
I have an array of N card objects. Each object has an attribute called cardNum - an identifier. I think I should write an actionListener on that array, so when I flip a card, it puts the flipped card's cardNum in an array of two elements and if the two elements of the array are equal, a pair is found.
The problem is that I just don't know how to get the last flipped card's cardNum.
Any help would be appreciated.
Here's the way I tried:
private void easyGame(Card[] cards) {
int flippedCards = 0;
int card1;
while(flippedCards != 24) {
for(int i=0; i<cards.length; i++) {
if(cards[i].getIsFlipped())
flippedCards ++;
}
if(flippedCards % 2 == 0 && flippedCards > 0)
for(int i=0; i<cards.length; i++) {
card1 = getCardIndByCardNum(cards[i].getCardNum(), cards, i);
if(!cards[card1].getIsFlipped()) {
for(int j=0; j<cards.length; j++) {
if(cards[i].getIsFlipped())
cards[i].flip();
}
flippedCards = 0;
break;
}
}
}
}
The problem is that if I call this method, the game won't be drawn. May I use use threads somehow?
EDIT
Here is how I get the indexes of the clicked cards, and I call it in the UI:
private void setCardHandlers() {
for(final Card card : cards) {
card.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
clickedCardInd = getChildren().indexOf(card)-1;
}
});
}
}
Than here is how I am using it:
setOnMouseReleased(new EventHandler<MouseEvent> () {
#Override
public void handle(MouseEvent t) {
int cardIndex = clickedCardInd; // get index of what user clicked
clickedCardInd = -1;
if (cardIndex != -1 && moveRequestedFlag) { // our controller waits for the move
// get the position and report
moveRequestedFlag = false; // we handled the move
//System.out.println(cardIndex);
nextMove.setMove(cardIndex); // this will unblock controller's thread
}
}
});
It has a delay on fliping cards, also in the easyGame the requestMove method sets both indexes to the same.
I would recommend splitting you responsibilities a bit into Model/View/Controller modules, which, in simplest case would look like :
Model - your game current state and data, i.e. cards array Cards mCards = new Cards[24];
View - your UI, that can reflect current state of mCards(model) on screen in Main thread
Controller - your main game logic. This is most complex part, responsible for
requesting/handling user move,
updating mCards(model) based on user move,
Requesting UI to re-draw.
Contoroller's code (easyGame method) should run on separate thread to not block the UI.
Below I sketched a skeleton code that should fit your requirements :
class Game {
/*
* controller - main logic
*/
void startEasyGame() {
// initialize cards array, shuffle if needed
// we start with zero cards flipped
int flippedCards = 0;
// main loop
while (flippedCards != mCards.length) {
// 1. show updated UI
mBoard.showUpdatedCards();
// 2. request player move
// and block current thread to wait till move is done
// the result of the move - index of the card
int index1 = requestMove();
// temporarily flip first card face-up
mCards[index1].flip();
// show it on screen
mBoard.showUpdatedCards();
// same for second card
int index2 = requestMove();
mCards[index2].flip();
mBoard.showUpdatedCards();
// 3. check the result
if (mCards[index1].getCardNum() == mCards[index2].getCardNum()) {
// hooray, correct guess, update count
// possibly show some encouraging feedback to user
flippedCards += 2;
} else {
// incorrect, flip cards back face down
mCards[index1].flip();
mCards[index2].flip();
}
} // end of while loop
// game ended -> show score and time
mBoard.showResult();
}
}
EDIT
Extra details on how to await for result from UI thread :
int requestMove() {
// 1. show user prompt to make a move
// ...
// 2. construct latch to wait for move done on UI thread
mBoard.moveRequestedFlag = true;
NextMove nextMove = new NextMove();
mBoard.nextMove = nextMove;
// 3. await for move and get the result
return nextMove.getMove();
}
then, somewhere in UI code :
// handling card onClick somewhere on UI thread
if (mBoard.moveRequestedFlag) { // our controller waits for the move
// get the position and report
int cardIndex = ... // get index of what user clicked
mBoard.moveReqestedFlag = false; // we handled the move
mBoard.nextMove.setMove(cardIndex); // this will unblock controller's thread
}
and NextMove utility class to sync threads :
public class NextMove {
private volatile int mCardIndex;
private final CountDownLatch mMoveReady = new CountDownLatch(1);
public int getMove() throws InterruptedException {
mMoveReady.await();
return mCardIndex;
}
public synchronized void setMove(int selectedCardIndex) {
if (mMoveReady.getCount() > 0) {
mCardIndex = selectedCardIndex;
mMoveReady.countDown();
}
}
}