feat: Implemented mouse events and block breaking

This commit is contained in:
Jakub Žitník 2025-02-18 23:01:06 +01:00
parent b4641c5c86
commit a359bedd3a
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
4 changed files with 179 additions and 59 deletions

View File

@ -1,8 +1,10 @@
package cz.jzitnik;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.MouseHandler;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.tui.ScreenRenderer;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
@ -11,18 +13,22 @@ import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
// Set up terminal with JLine
Terminal terminal = TerminalBuilder.terminal();
terminal.enterRawMode();
terminal.trackMouse(Terminal.MouseTracking.Any);
if (!terminal.hasMouseSupport()) {
System.out.println("Error: This terminal does not support mouse.");
System.exit(1);
}
var spriteList = SpriteLoader.load();
var screenRenderer = new ScreenRenderer(spriteList, terminal);
var game = new Game();
final boolean[] isRunning = {true};
MouseHandler mouseHandler = new MouseHandler(game, terminal, screenRenderer);
// Enable mouse tracking
terminal.writer().print("\033[?1003h\n"); // Enable mouse move events
terminal.writer().flush();
final boolean[] isRunning = {true};
Thread inputThread = new Thread(() -> {
try {
@ -35,14 +41,8 @@ public class Main {
if (key == '[') {
key = terminal.reader().read();
if (key == 'M') {
int buttonCode = terminal.reader().read() - 32;
int x = terminal.reader().read() - 32;
int y = terminal.reader().read() - 32;
if (buttonCode == 0) {
game.handleMouseClick(x, y);
//screenRenderer.render(game.getWorld());
}
MouseEvent mouseEvent = terminal.readMouseEvent();
mouseHandler.handle(mouseEvent);
}
}
}
@ -60,9 +60,6 @@ public class Main {
game.movePlayerUp(screenRenderer);
screenRenderer.render(game.getWorld());
break;
case 'm':
game.mine(screenRenderer);
break;
case 'q':
System.out.println("Exiting game...");
isRunning[0] = false;
@ -89,8 +86,7 @@ public class Main {
}
// Disable mouse tracking on exit
terminal.writer().print("\033[?1003l\n");
terminal.writer().flush();
terminal.trackMouse(Terminal.MouseTracking.Off);
terminal.close();
} catch (IOException e) {

View File

@ -2,6 +2,7 @@ package cz.jzitnik.game;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
@ -19,6 +20,7 @@ public class Game {
}
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
world[9][0].add(new Block("grass", SpriteLoader.SPRITES.GRASS));
player = steveBlock;
for (int i = 0; i < 50; i++) {
@ -45,7 +47,7 @@ public class Game {
world[10][0].add(steveBlock);
}
private int[] getPlayerCords() {
public int[] getPlayerCords() {
for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) {
for (Block block : world[i][j]) {
@ -148,15 +150,25 @@ public class Game {
}).start();
}
public void mine(ScreenRenderer screenRenderer) {
public void mine(ScreenRenderer screenRenderer, int x, int y) {
int[] cords = getPlayerCords();
if (world[cords[1] + 1][cords[0]].stream().allMatch(Block::isGhost)) {
int playerX = cords[0];
int playerY = cords[1];
int distanceX = Math.abs(playerX - x);
int distanceY = Math.abs(playerY - y);
if (distanceX > 5 || distanceY > 5) {
return;
}
world[cords[1] + 1][cords[0]].clear();
world[cords[1] + 1][cords[0]].add(new Block("air", SpriteLoader.SPRITES.AIR, true));
if (world[y][x].stream().allMatch(Block::isGhost)) {
return;
}
world[y][x].clear();
world[y][x].add(new Block("air", SpriteLoader.SPRITES.AIR, true));
screenRenderer.render(world);
new Thread(() -> {
@ -175,33 +187,4 @@ public class Game {
}
}).start();
}
public void handleMouseClick(int mouseX, int mouseY) {
/*int[] cords = getPlayerCords();
if (cords == null) return;
int playerX = cords[0];
int playerY = cords[1];
int viewXRadius = 5;
int viewUpRadius = 4;
int viewDownRadius = 3;
// Convert mouse coordinates to world coordinates
int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
int blockY = startY + (mouseY / 25); // 25 lines high per sprite
// Check if within bounds
if (blockY >= startY && blockY < endY && blockX >= startX && blockX < endX) {
List<Block> blocks = world[blockY][blockX];
if (!blocks.isEmpty()) {
Block topBlock = blocks.get(blocks.size() - 1); // Get topmost block
System.out.println("Mouse clicked on block: " + topBlock.getBlockId() +
" at World Coordinates: [" + blockX + ", " + blockY + "]");
} else {
System.out.println("No block at the clicked position.");
}
} else {
System.out.println("Clicked outside the visible area.");
}*/
}
}

View File

@ -0,0 +1,101 @@
package cz.jzitnik.game;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.AllArgsConstructor;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@AllArgsConstructor
public class MouseHandler {
private Game game;
private Terminal terminal;
private ScreenRenderer screenRenderer;
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getButton() == MouseEvent.Button.Button1 && mouseEvent.getType() == MouseEvent.Type.Pressed) {
leftClick(mouseEvent);
return;
}
if (mouseEvent.getType() == MouseEvent.Type.Moved) {
move(mouseEvent);
}
}
private void leftClick(MouseEvent mouseEvent) {
int mouseX = mouseEvent.getX();
int mouseY = mouseEvent.getY();
int[] cords = game.getPlayerCords();
if (cords == null) return;
int playerX = cords[0];
int playerY = cords[1];
int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);
int startX = data[0];
int endX = data[1];
int startY = data[2];
int endY = data[3];
int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
int blockY = startY + (mouseY / 25); // 25 lines high per sprite
if (blockY == playerX && blockY == playerY) {
return;
}
if (blockY >= startY && blockY < endY && blockX >= startX && blockX < endX) {
List<Block> blocks = game.getWorld()[blockY][blockX];
System.out.println(blockX);
System.out.println(blockY);
if (!blocks.isEmpty()) {
game.mine(screenRenderer, blockX, blockY);
}
}
}
private void move(MouseEvent mouseEvent) {
int mouseX = mouseEvent.getX();
int mouseY = mouseEvent.getY();
int[] cords = game.getPlayerCords();
if (cords == null) return;
int playerX = cords[0];
int playerY = cords[1];
int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(), terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);
int startX = data[0];
int endX = data[1];
int startY = data[2];
int endY = data[3];
int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
int blockY = startY + (mouseY / 25); // 25 lines high per sprite
if (blockY == playerX && blockY == playerY) {
return;
}
List<Block> blocks = game.getWorld()[blockY][blockX];
if (blockY >= startY && blockY < endY && blockX >= startX && blockX < endX && !blocks.stream().allMatch(block -> block.getBlockId().equals("air"))) {
List<Integer> list = new ArrayList<>();
list.add(blockX);
list.add(blockY);
screenRenderer.setSelectedBlock(Optional.of(list));
} else {
screenRenderer.setSelectedBlock(Optional.empty());
}
screenRenderer.render(game.getWorld());
}
}

View File

@ -1,16 +1,29 @@
package cz.jzitnik.tui;
import cz.jzitnik.game.Block;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@AllArgsConstructor
@RequiredArgsConstructor
public class ScreenRenderer {
private SpriteList spriteList;
private Terminal terminal;
public ScreenRenderer(SpriteList spriteList, Terminal terminal) {
this.spriteList = spriteList;
this.terminal = terminal;
}
@Getter
@Setter
private Optional<List<Integer>> selectedBlock = Optional.empty();
private int[] getPlayerCords(List<Block>[][] world) {
for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) {
@ -50,9 +63,9 @@ public class ScreenRenderer {
if (visibleWidth % 2 != 0) {
endX = Math.max(startX, endX - 1);
}
/*if (visibleHeight % 2 != 0) {
if (visibleHeight % 2 != 0) {
endY = Math.max(startY, endY - 1);
}*/
}
StringBuilder[] lines = new StringBuilder[(endY - startY) * 26];
for (int i = 0; i < lines.length; i++) {
@ -63,9 +76,36 @@ public class ScreenRenderer {
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
List<Block> blocks = world[y][x];
String sprite = SpriteCombiner.combineSprites(blocks.stream()
.map(block -> spriteList.getSprite(block.getSprite()).getSprite())
.toArray(String[]::new));
List<String> sprites = new ArrayList<>(blocks.stream()
.map(block -> spriteList.getSprite(block.getSprite()).getSprite()).toList());
if (selectedBlock.isPresent() && selectedBlock.get().get(0) == x && selectedBlock.get().get(1) == y) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 50; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
}
stringBuilder.append("\n");
for (int i = 0; i < 23; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
for (int j = 0; j < 48; j++) {
stringBuilder.append("\033[0m ");
}
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\n");
}
for (int i = 0; i < 50; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
}
stringBuilder.append("\n");
sprites.add(stringBuilder.toString());
}
String sprite = SpriteCombiner.combineSprites(sprites.toArray(String[]::new));
String[] spriteLines = sprite.split("\n");