Setting up a Timeline with a for loop in JavaFX - loops

I'm trying to write an animated sort for a class I'm in. My goal is to have the sort displayed as vertical white bars that will be rendered on the canvas I've created.
I've attempted to accomplish this using a Timeline and Keyframes, but the output I get is a blank white canvas that after a few moments outputs the finished and sorted array.
I've resorted to just shuffling around code with no success, and can't find any helpful examples online. I'm hoping someone with some more experience with JavaFX animation can help me learn more about how to properly set this up!
Note that in the code below the array created with the random method is commented out because when used it makes the program hang for long time. The code will output in a reasonable time with a smaller array.
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Project06 extends Application
{
final int SCALE = 16;
final int WIDTH = 64;
final int HEIGHT = 32;
int[][] pixels;
Timeline sortLoop;
int[] myArray = {32,28,22,20,16,13,10,9,5,3};
#Override
public void start(Stage primaryStage) throws Exception
{
pixels = new int[WIDTH][HEIGHT];
int[] myArray = new int[WIDTH];
/*
Random rand = new Random();
for (int i = 0; i < WIDTH; i++)
{
myArray[i] = rand.nextInt((HEIGHT) + 1);
}
*/
Canvas display = new Canvas (WIDTH*SCALE, HEIGHT*SCALE);
GraphicsContext gc = display.getGraphicsContext2D();
GridPane grid = new GridPane();
grid.add(display, 0, 0);
primaryStage.setTitle("Sort");
primaryStage.setScene(new Scene(grid, WIDTH*SCALE, HEIGHT*SCALE));
primaryStage.show();
sortLoop = new Timeline();
sortLoop.setCycleCount(Timeline.INDEFINITE);
// Sort array
for (int i = 0; i < myArray.length - 1; i++)
{
if (myArray[i] > myArray[i + 1])
{
int swap = myArray[i];
myArray[i] = myArray[i + 1];
myArray[i + 1] = swap;
i = -1;
}
// Clear screen by zeroing out pixel array
for (int k = 0; k < WIDTH; k++)
{
for (int j = 0; j < HEIGHT; j++)
{
pixels[k][j] = 0;
}
}
// Draw array with vertical bars (assign values to canvas array)
for (int k = 0; k < myArray.length; k++)
{
for (int j = (HEIGHT - 1); j > ((HEIGHT - myArray[k]) - 1); j--)
{
pixels[k][j] = 1;
}
}
KeyFrame kf = new KeyFrame(Duration.seconds(1), actionEvent -> {
// Render canvas
for (int k = 0; k < WIDTH; k++)
{
for (int j = 0; j < HEIGHT; j++)
{
if (pixels[k][j] == 1)
{
gc.setFill(Color.WHITE);
gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
}
else
{
gc.setFill(Color.BLACK);
gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
}
}
}
});
sortLoop.getKeyFrames().add(kf);
sortLoop.play();
}
}
public static void main(String[] args)
{
launch(args);
}
}

First, you are setting the timepoint for all your key frames to the same value (one second), so they all happen simultaneously. You need each key frame to have a different timepoint, so they happen sequentially.
Second, you sort the array, and then the event handlers for the keyframes reference the array. Since the event handlers for the keyframes are executed later, they only see the sorted array. You need to actually manipulate the array in the event handlers for the key frames.
Third, you play the timeline multiple times. You only need to play it once. Additionally, you set the cycle count to INDEFINITE: I think you only need to animate the sort once. (If you actually want it to repeat, you would need to set the array back to its original order at the beginning of the timeline.)
Finally, you have some issues in the implementation of the actual sort algorithm. This is why your application hangs when you randomly populate the array. I won't address those issues here, because the question is about how to perform the animation.
The following code performs a step-by-step animation. It's probably not the correctly-implemented sort, so you won't see the animation you necessarily expect, but it at least repaints the effect of the swaps in an animation:
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class AnimatedSort extends Application
{
final int SCALE = 16;
final int WIDTH = 64;
final int HEIGHT = 32;
int[][] pixels;
Timeline sortLoop;
int[] myArray = {32,28,22,20,16,13,10,9,5,3};
#Override
public void start(Stage primaryStage) throws Exception
{
pixels = new int[WIDTH][HEIGHT];
// int[] myArray = new int[WIDTH];
//
//
// Random rand = new Random();
// for (int i = 0; i < WIDTH; i++)
// {
// myArray[i] = rand.nextInt((HEIGHT) + 1);
// }
Canvas display = new Canvas (WIDTH*SCALE, HEIGHT*SCALE);
GraphicsContext gc = display.getGraphicsContext2D();
GridPane grid = new GridPane();
grid.add(display, 0, 0);
primaryStage.setTitle("Sort");
primaryStage.setScene(new Scene(grid, WIDTH*SCALE, HEIGHT*SCALE));
primaryStage.show();
sortLoop = new Timeline();
// sortLoop.setCycleCount(Timeline.INDEFINITE);
// Sort array
for (int index = 0; index < myArray.length - 1; index++)
{
final int i = index ;
KeyFrame kf = new KeyFrame(Duration.seconds(i+1), actionEvent -> {
if (myArray[i] > myArray[i + 1])
{
int swap = myArray[i];
myArray[i] = myArray[i + 1];
myArray[i + 1] = swap;
// i = -1;
}
// Clear screen by zeroing out pixel array
for (int k = 0; k < WIDTH; k++)
{
for (int j = 0; j < HEIGHT; j++)
{
pixels[k][j] = 0;
}
}
// Draw array with vertical bars (assign values to canvas array)
for (int k = 0; k < myArray.length; k++)
{
for (int j = (HEIGHT - 1); j > ((HEIGHT - myArray[k]) - 1); j--)
{
pixels[k][j] = 1;
}
}
// Render canvas
for (int k = 0; k < WIDTH; k++)
{
for (int j = 0; j < HEIGHT; j++)
{
if (pixels[k][j] == 1)
{
gc.setFill(Color.WHITE);
gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
}
else
{
gc.setFill(Color.BLACK);
gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
}
}
}
});
sortLoop.getKeyFrames().add(kf);
}
sortLoop.play();
}
public static void main(String[] args)
{
launch(args);
}
}

The time property of KeyFrame denotes the time since the start of the animation, not the time since the previously added KeyFrame. You need to position. You need to pass different times to the KeyFrame constructor.
Furthermore you "sort" a fresh array instead of using the field. Also your pixels array contains the last value from the start.
Better save the swaps to a data structure and use a Timeline to render the changes step by step:
final int SCALE = 16;
final int WIDTH = 64;
final int HEIGHT = 32;
Timeline sortLoop;
int[] myArray = {32, 28, 22, 20, 16, 13, 10, 9, 5, 3};
private void renderBar(GraphicsContext gc, int barIndex, double canvasHeight, int barHeight) {
double rectHeight = barHeight * SCALE;
gc.fillRect(SCALE * barIndex, canvasHeight - rectHeight, SCALE, rectHeight);
}
#Override
public void start(Stage primaryStage) throws Exception {
int[] myArray = this.myArray.clone(); // copy field (you may also simply delete this declaration, if you never reuse the initial array)
class Swap {
final int lowerIndex, value1, value2;
public Swap(int lowerIndex, int value1, int value2) {
this.lowerIndex = lowerIndex;
this.value1 = value1;
this.value2 = value2;
}
}
final double canvasHeight = HEIGHT * SCALE;
Canvas display = new Canvas(WIDTH * SCALE, canvasHeight);
GraphicsContext gc = display.getGraphicsContext2D();
primaryStage.setTitle("Sort");
primaryStage.setScene(new Scene(new Group(display)));
primaryStage.show();
// render initial state
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, display.getWidth(), canvasHeight);
gc.setFill(Color.BLACK);
for (int i = 0; i < myArray.length; i++) {
renderBar(gc, i, canvasHeight, myArray[i]);
}
// Sort array & save changes
List<Swap> swaps = new ArrayList<>();
for (int i = 0; i < myArray.length - 1; i++) {
if (myArray[i] > myArray[i + 1]) {
int swap = myArray[i];
myArray[i] = myArray[i + 1];
myArray[i + 1] = swap;
swaps.add(new Swap(i, myArray[i], swap));
i = -1;
} else {
// no change
swaps.add(null);
}
}
sortLoop = new Timeline();
sortLoop.setCycleCount(swaps.size());
final Iterator<Swap> iter = swaps.iterator();
sortLoop.getKeyFrames().add(new KeyFrame(Duration.seconds(1), evt -> {
Swap swap = iter.next();
if (swap != null) {
// clear old area
gc.setFill(Color.WHITE);
gc.fillRect(SCALE * swap.lowerIndex, 0, SCALE * 2, canvasHeight);
// draw new bars
gc.setFill(Color.BLACK);
renderBar(gc, swap.lowerIndex, canvasHeight, swap.value1);
renderBar(gc, swap.lowerIndex+1, canvasHeight, swap.value2);
}
}));
sortLoop.play();
}

Related

Need help on solving this Arrays problem using java

Make an array of integer (score) with 10 members. Randomize the
content with value between 0-100. For each of the member of array,
visualize the value using “-” for each ten. For example: score[0] = 55 will be visualized as “-----" (Using Java).
public class w9lab1 {
public static void main(String args[]) {
double[] temperature = new double[7];
for (int i = 0; i < 7; i++) {
temperature[i] = Math.random()*100;
}
for (int i = 0; i < 7; i++) {
System.out.println(temperature[i]);
}
double totalTemperature = 0;
for (int i = 0; i < 7 ; i++) {
totalTemperature += temperature[i];
}
double maxTemperature = temperature[0];
for (int i = 1; i < 7; i++){
if (temperature[i] > maxTemperature){
maxTemperature = temperature[i];
}
}
System.out.println("Temperatur maximum adalah " + maxTemperature);
}
}
import java.util.Random;
import java.util.Arrays;
public class w9lab1 {
public static void main(String[] args) {
Random random = new Random();
int[] score = new int[10];
for (int i = 0; i < score.length; i++) {
score[i] = random.nextInt(101);
for (int n = 1; n <= score[i] / 10; n++)
System.out.print('-');
System.out.println();
}
System.out.println(Arrays.toString(score));
}
}

2d array of type Space

I'm trying to make a 9x9 grid of Spaces with 1-10 int values. I'm using the java n-ide app, and am getting a successful compilation, but it's not printing any values.
class Space {
int one = 1;
int two = 2;
...
int ten = 10;
}
class green {
Space[][] board = new Space[9][9];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
board[i][j] = new Space();
System.out.println(board[i][j].one);
}
}
}
I think you can just refactor your Space class to just hold a primitive integer value:
class Space {
private int value;
private static final String MSG = "Space values must be between 1 and 10 inclusive";
public Space() { }
public Space(int value) {
// prevent spaces from being created with illegal values
if (value < 1 || value > 10) {
throw new IllegalArgumentException(MSG);
}
this.value = value;
}
public int getValue() {
return value;
}
}
Then, in your consuming class, use the Space class:
class Green {
private Space[][] board = new Space[9][9];
for (int i=0; i < board.length; i++) {
for (int j=0; j < board[i].length; j++) {
// maybe get a value from somewhere and use it below
board[i][j] = new Space();
}
}
}
Regarding your exact question about values not printing, the bigger problem than above is that you don't have any logic for assigning values.

How do I make my object disappear when it hits my character in my game? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I'm trying to make a game where my character (Homer Simpson) has to collect falling objects (donuts) and avoid falling toxic-tanks. Right now my score increments every time he gets a donut, but I wish to make the donut disappear afterwards. Can anyone help me with that?
This is my code so far:
PImage bg2; //background image
PImage homer; //image of homer
homer homer1;
int numberOfToxic = 3; //number of falling toxic tanks
int numberOfDonut = 3; //number of falling donuts
int score; //the score
Toxic[] toxic; //array for the toxic that will fall
PImage t;
Donut[] donut; //array for the donuts that will fall
PImage d;
void setup() {
size(600,500); //size of the window
bg2 = loadImage("bg2.jpg"); //uploading the background image
homer = loadImage("homer.gif"); //uploading the character image
t = loadImage("toxictank.png");
d = loadImage("donut.png");
PFont louiseFont;
louiseFont = loadFont("chalk.vlw"); //loading the font I've chosen
textFont(louiseFont); //the current font being used in the game
toxic = new Toxic[numberOfToxic];
for (int i = 0; i < numberOfToxic; i++) {
toxic[i] = new Toxic();
}
donut = new Donut[numberOfDonut];
for(int i = 0; i < numberOfDonut; i++) {
donut[i] = new Donut();
}
score = 0; //the score starts at 0
}
void draw() {
background(bg2);
homer1 = new homer(mouseX-70, 350, 140, 150, homer); //mouseX makes the hero move on the x axis and 350 defines where it is on the y axis. -70 center the mouse on the image/hero
homer1.drawHomer(); //call the function homer(hero)
//Making the taxictanks fall
for(int i = 0; i < toxic.length; i++) {
toxic[i].update();
toxic[i].drawToxic();
if(toxic[i].position.y > 500) {
toxic[i].reset();
}
}
//Making the donuts fall
for(int i = 0; i < donut.length; i++) {
donut[i].update2();
donut[i].drawDonut();
if(donut[i].position2.y > 500) {
donut[i].reset2();
}
//Collecting points if Homer eats donuts
if(abs(donut[i].position2.y - homer1.y) <= 2 && abs(donut[i].position2.x-40 - homer1.x)<=40) {
score ++;
println("ok");
}
}
fill(#0D128B); //color of text
textSize(20); //size of text
text("SCORE: " + nf(score, 1), 20, 40); //score points - tells how to use the text in the game
}
//class with height, width x, y positions and the hero image
class homer {
int x;
int y;
int hWidth;
int hHeight;
PImage homer;
homer(int x, int y, int hWidth, int hHeight, PImage homer) {
this.x = x;
this.y = y;
this.hWidth = hWidth;
this.hHeight = hHeight;
this.homer = homer;
}
void drawHomer() {
image(this.homer, this.x, this.y, this.hWidth, this.hHeight);
}
}
class Toxic { //all the variables for toxic
PImage t;
PVector position;
float speed;
float size;
//constructor of toxic
Toxic() {
t = loadImage("toxictank.png");
position = new PVector(random(width), random(height));
speed = 4;
size = 20;
}
void update() {
position.y += speed;
}
void drawToxic() {
for(int i = 1; i < 3; i++) {
image(t, position.x, position.y);
}
}
void reset() {
position.x = random(width);
position.y = 0 - 50;
speed = 4;
size = 20;
}
}
class Donut { //all variables for donut
PImage d;
PVector position2;
float speed2;
float size2;
boolean falling;
int timeToDisplay;
int fallingSpeed;
Donut() { //constructor of donut
d = loadImage("donut.png");
position2 = new PVector(random(width), random(height));
speed2 = 4;
size2 = 40;
falling = false;
timeToDisplay = (int)random(2.60);
fallingSpeed = (int)random(2.5);
}
void update2() {
position2.y += speed2;
}
void drawDonut() {
for(int i = 1; i < 3; i++) {
image(d, position2.x, position2.y);
}
}
void reset2() {
position2.x = random(width);
position2.y = 0 - 50;
speed2 = 4;
size2 = 20;
}
}
Just set the variables in the instance of Donut so that it goes back to the top of the screen, or isn't drawn at all. You already have a reset2() function that does that. Something like this:
if(abs(donut[i].position2.y - homer1.y) <= 2 && abs(donut[i].position2.x-40 - homer1.x)<=40) {
score ++;
donut[i].reset2();
println("ok");
}

Change fill color of cell when clicked in Processing

I've managed to print the co-ordinates of a cell when it's clicked.
Now I'm looking for help and advice to use the this data in order to change the cell colour to red - unless the cell selected is a base cell, I want those to remain as a constant. Here's the code I'm working with:
Tile[][] tileArray = new Tile[10][10];
int rows = 10;
int cols = 10;
int[] base = { 4,4, 4,5, 4,6, 4,7 };
void setup() {
size(500, 500);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
tileArray[i][j] = new Tile(i*50, j*50);
}
}
}
void draw() {
for (int i=0; i<base.length; i+=2) {
fill(0, 0, 255);
rect(base[i]*50, base[i+1]*50, 50, 50);
}
}
void mousePressed() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
println (mouseX/50 +"," + mouseY/50);
}
}
}
class Tile {
Tile(int x, int y) {
fill(255);
stroke(0);
rect(x, y, 50, 50);
}
}
EDIT:
I've added the functionality which I am looking for, but I assume that I have not stored my base cell data correctly. As you can still click on the base cell and an active cell is drawing underneath. Here is the new code:
Tile[][] tiles;
int gridSize = 10;
int tileSize = 50;
void setup() {
size (450, 400);
generateGrid();
}
void draw() {
background (255);
display();
}
public void generateGrid() {
tiles = new Tile[gridSize][gridSize];
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
tiles[i][j] = new Tile((i*50), (j*50), tileSize, tileSize);
}
}
}
public void display() {
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
tiles[i][j].display();
}
}
}
void mousePressed() {
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
println (mouseX/50 +"," + mouseY/50);
}
}
int mx = mouseX/50;
int my = mouseY/50;
tiles[mx][my].active = !tiles[mx][my].active;
// tiles[mx][my].base = !tiles[mx][my].base;
println(tiles[mx][my].active);
}
class Tile {
int tx, ty, tw, th;
int[] baseCell = { 4, 2, 4, 3, 4, 4, 4, 5 };
color col_default = color(255);
color col_base = color(0, 0, 255);
color col_active = color(255, 100, 50);
boolean active = false;
boolean base = false;
Tile (int itx, int ity, int itw, int ith) {
tx = itx;
ty = ity;
tw = itw;
th = ith;
}
void display() {
stroke(0);
fill(col_default);
if ( active ) fill (col_active);
rect(tx, ty, tw, th);
for (int i=0; i<baseCell.length; i+=2) {
fill(col_base);
rect(baseCell[i]*50, baseCell[i+1]*50, 50, 50);
}
}
}
Your Tile class doesn't really make sense right now. It doesn't really make sense to draw to the screen from a constructor. Instead, move that logic into a draw() function inside the Tile class.
Your Tile class also has to keep track of its state: whether it's a base cell or not, or any other kind of cell it can be. You might do this with an enum, or an int, or a set of booleans. That part is completely up to you.
Then in your Tile.draw() function, you should check that state to decide what color to draw. From the sketch's draw() function, simply loop over each Tile and tell it to draw itself.
Finally, when you click, simply set the state of the Tile that you clicked on.

How to draw a dynamic Grid with XNA

I'm trying to draw grid using the XNA framework, this grid should have a fixed dimension, during the execution of XNA, but should be given to the user the opportunity to customize it before launch the game page (I'm building my app with the silverlight/xna template).
Does anyone has a suggestion on how achieve this goal?
Thank you
Set a tileSize, and then draw a texture over the size of grid you want.
Here is some reworked code. This is how I would start with generating a tilemap, by using a 2d array.
int tileSize = 32;
Vector2 position = Vector2.Zero;
Texture2D gridTexture;
int[,] map = new int[,]
{
{1, 1, 0,},
{0, 1, 1,},
{1, 1, 0,},
};
Then add something like this to your draw function:
for (int i = 0; i <= map.GetUpperBound(0); i++)
{
for (int j = 0; j <= map.GetUpperBound(1); j++)
{
int textureId = map[i, j];
if (textureId != 0)
{
Vector2 texturePosition = new Vector2(i * tileSize, j * tileSize) + position;
//Here you would typically index to a Texture based on the textureId.
spriteBatch.Draw(gridTexture, texturePosition, null, Color.White, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0f);
}
}
}
ContentManager contentManager;
GameTimer timer;
SpriteBatch spriteBatch;
LifeGrid life;
int tileSize = 32;
Vector2 position = Vector2.Zero;
Texture2D gridTexture;
int[,] map;
public GamePage()
{
InitializeComponent();
// Get the content manager from the application
contentManager = (Application.Current as App).Content;
// Create a timer for this page
timer = new GameTimer();
//timer.UpdateInterval = TimeSpan.FromTicks(333333);
timer.UpdateInterval = TimeSpan.Zero;
timer.Update += OnUpdate;
timer.Draw += OnDraw;
List<Position> p = new List<Position>();
p.Add(new Position(1,1));
p.Add(new Position(1,4));
p.Add(new Position(1,5));
p.Add(new Position(1,6));
p.Add(new Position(1,7));
this.life = new LifeGrid(10, 10, p);
map = new int[,]{{1, 1, 0,},{0, 1, 1,},{1, 1, 0,},};
// LayoutUpdated += new EventHandler(GamePage_LayoutUpdated);
}
/// <summary>
/// Allows the page to draw itself.
/// </summary>
private void OnDraw(object sender, GameTimerEventArgs e)
{
// SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
// SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.Black);
// Draw the sprite
spriteBatch.Begin();
for (int i = 0; i <= map.GetUpperBound(0); i++)
{
for (int j = 0; j <= map.GetUpperBound(1); j++)
{
int textureId = map[i, j];
if (textureId != 0)
{
Vector2 texturePosition = new Vector2(i * tileSize, j * tileSize) + position;
//Here you would typically index to a Texture based on the textureId.
spriteBatch.Draw(gridTexture, texturePosition, null, Color.White, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0f);
}
}
}
spriteBatch.End();
}

Resources