Last active
January 24, 2024 15:38
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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