Skip to content

Instantly share code, notes, and snippets.

@OrangoMango
Last active January 24, 2024 15:38
Show Gist options
  • Save OrangoMango/7dd92d3b41d3ca252ae789c8e8ee9cb0 to your computer and use it in GitHub Desktop.
Save OrangoMango/7dd92d3b41d3ca252ae789c8e8ee9cb0 to your computer and use it in GitHub Desktop.
Falling sand simulation made with JavaFX, inspired by the coding train challenge #180 of the coding train
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.*;
import javafx.scene.paint.Color;
import javafx.animation.*;
import javafx.util.Duration;
import javafx.scene.input.MouseButton;
import javafx.scene.input.KeyCode;
public class FallingSand extends Application{
private static final int WIDTH = 800;
private static final int HEIGHT = 800;
private static int CELL_SIZE = 5;
private int[][] grid;
private double currentHue;
private int brushSize = 11;
private double chance = 0.05;
private boolean simulationRunning = true;
@Override
public void start(Stage stage){
StackPane pane = new StackPane();
Canvas canvas = new Canvas(WIDTH, HEIGHT);
pane.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
this.grid = new int[WIDTH/CELL_SIZE][HEIGHT/CELL_SIZE];
canvas.setOnMouseDragged(e -> {
int xPos = (int)(e.getX()/CELL_SIZE);
int yPos = (int)(e.getY()/CELL_SIZE);
for (int x = -this.brushSize/2; x < this.brushSize/2; x++){
for (int y = -this.brushSize/2; y < this.brushSize/2; y++){
if (this.chance >= Math.random() || e.getButton() == MouseButton.SECONDARY){
int xp = xPos+x;
int yp = yPos+y;
if (xp >= 0 && yp >= 0 && xp < this.grid.length && yp < this.grid[0].length){
if (e.getButton() == MouseButton.PRIMARY){
if (this.grid[xp][yp] == 0) this.grid[xp][yp] = (int)(this.currentHue += 0.05);
} else if (e.getButton() == MouseButton.SECONDARY){
this.grid[xp][yp] = 0;
}
}
}
}
}
});
canvas.setOnScroll(e -> {
final int bInc = 2;
final int cInc = 1;
final double chanceInc = 0.05;
final int minB = 3;
final int minC = 5;
if (e.getDeltaY() > 0){
if (e.isControlDown()){
CELL_SIZE += cInc;
this.grid = new int[WIDTH/CELL_SIZE][HEIGHT/CELL_SIZE];
} else if (e.isAltDown()){
this.chance += chanceInc;
} else {
this.brushSize += bInc;
}
} else if (e.getDeltaY() < 0){
if (e.isControlDown()){
CELL_SIZE -= cInc;
if (CELL_SIZE >= minC) this.grid = new int[WIDTH/CELL_SIZE][HEIGHT/CELL_SIZE];
} else if (e.isAltDown()){
this.chance -= chanceInc;
} else {
this.brushSize -= bInc;
}
}
this.brushSize = Math.max(minB, this.brushSize);
CELL_SIZE = Math.max(minC, CELL_SIZE);
this.chance = Math.max(0, Math.min(1, this.chance));
});
Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), e -> update(gc)));
loop.setCycleCount(Animation.INDEFINITE);
loop.play();
Scene scene = new Scene(pane, WIDTH, HEIGHT);
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.SPACE){
this.simulationRunning = !this.simulationRunning;
}
});
stage.setScene(scene);
stage.setResizable(false);
stage.setTitle("Falling sand");
stage.show();
}
private Integer getElementAt(int x, int y){
if (x >= 0 && y >= 0 && x < this.grid.length && y < this.grid[0].length){
return this.grid[x][y];
} else {
return null;
}
}
private void update(GraphicsContext gc){
gc.clearRect(0, 0, WIDTH, HEIGHT);
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, WIDTH, HEIGHT);
for (int i = 0; i < this.grid.length; i++){
for (int j = 0; j < this.grid[i].length; j++){
if (this.grid[i][j] > 0){
Color color = Color.hsb(this.grid[i][j], 1, 1);
gc.setFill(color);
gc.fillRect(i*CELL_SIZE, j*CELL_SIZE, CELL_SIZE, CELL_SIZE);
}
}
}
// Sand logic
if (this.simulationRunning){
for (int j = this.grid[0].length-1; j >= 0; j--){
for (int i = 0; i < this.grid.length; i++){
if (this.grid[i][j] > 0){
Integer below = getElementAt(i, j+1);
if (below != null){
if (below == 0){
this.grid[i][j+1] = this.grid[i][j];
this.grid[i][j] = 0;
} else {
int dir = Math.random() > 0.5 ? 1 : -1;
Integer a = getElementAt(i+dir, j+1);
Integer b = getElementAt(i-dir, j+1);
if (a != null && a == 0){
this.grid[i+dir][j+1] = this.grid[i][j];
this.grid[i][j] = 0;
} else if (b != null && b == 0){
this.grid[i-dir][j+1] = this.grid[i][j];
this.grid[i][j] = 0;
}
}
}
}
}
}
}
gc.setFill(Color.WHITE);
gc.fillText(String.format("Bursh size: %d\nCELL_SIZE: %d\nChance: %.3f\nSimulation running: %s", this.brushSize, CELL_SIZE, this.chance, this.simulationRunning), 10, 25);
}
public static void main(String[] args){
System.out.println("LB - Add sand\nRB - Remove sand\nAlt-scroll: Chance\nControl-scroll: CELL_SIZE\nScroll: Brush size\nSPACE: pause/resume");
launch(args);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment