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

View File

@ -2,6 +2,7 @@ package cz.jzitnik.game;
import cz.jzitnik.tui.ScreenRenderer; import cz.jzitnik.tui.ScreenRenderer;
import lombok.Getter; import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -19,6 +20,7 @@ public class Game {
} }
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE); Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
world[9][0].add(new Block("grass", SpriteLoader.SPRITES.GRASS));
player = steveBlock; player = steveBlock;
for (int i = 0; i < 50; i++) { for (int i = 0; i < 50; i++) {
@ -45,7 +47,7 @@ public class Game {
world[10][0].add(steveBlock); world[10][0].add(steveBlock);
} }
private int[] getPlayerCords() { public int[] getPlayerCords() {
for (int i = 0; i < world.length; i++) { for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) { for (int j = 0; j < world[i].length; j++) {
for (Block block : world[i][j]) { for (Block block : world[i][j]) {
@ -148,15 +150,25 @@ public class Game {
}).start(); }).start();
} }
public void mine(ScreenRenderer screenRenderer) { public void mine(ScreenRenderer screenRenderer, int x, int y) {
int[] cords = getPlayerCords(); 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; return;
} }
world[cords[1] + 1][cords[0]].clear(); if (world[y][x].stream().allMatch(Block::isGhost)) {
world[cords[1] + 1][cords[0]].add(new Block("air", SpriteLoader.SPRITES.AIR, true)); return;
}
world[y][x].clear();
world[y][x].add(new Block("air", SpriteLoader.SPRITES.AIR, true));
screenRenderer.render(world); screenRenderer.render(world);
new Thread(() -> { new Thread(() -> {
@ -175,33 +187,4 @@ public class Game {
} }
}).start(); }).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; package cz.jzitnik.tui;
import cz.jzitnik.game.Block; import cz.jzitnik.game.Block;
import lombok.AllArgsConstructor; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
@AllArgsConstructor @RequiredArgsConstructor
public class ScreenRenderer { public class ScreenRenderer {
private SpriteList spriteList; private SpriteList spriteList;
private Terminal terminal; 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) { private int[] getPlayerCords(List<Block>[][] world) {
for (int i = 0; i < world.length; i++) { for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) { for (int j = 0; j < world[i].length; j++) {
@ -50,9 +63,9 @@ public class ScreenRenderer {
if (visibleWidth % 2 != 0) { if (visibleWidth % 2 != 0) {
endX = Math.max(startX, endX - 1); endX = Math.max(startX, endX - 1);
} }
/*if (visibleHeight % 2 != 0) { if (visibleHeight % 2 != 0) {
endY = Math.max(startY, endY - 1); endY = Math.max(startY, endY - 1);
}*/ }
StringBuilder[] lines = new StringBuilder[(endY - startY) * 26]; StringBuilder[] lines = new StringBuilder[(endY - startY) * 26];
for (int i = 0; i < lines.length; i++) { for (int i = 0; i < lines.length; i++) {
@ -63,9 +76,36 @@ public class ScreenRenderer {
for (int y = startY; y < endY; y++) { for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) { for (int x = startX; x < endX; x++) {
List<Block> blocks = world[y][x]; List<Block> blocks = world[y][x];
String sprite = SpriteCombiner.combineSprites(blocks.stream() List<String> sprites = new ArrayList<>(blocks.stream()
.map(block -> spriteList.getSprite(block.getSprite()).getSprite()) .map(block -> spriteList.getSprite(block.getSprite()).getSprite()).toList());
.toArray(String[]::new));
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"); String[] spriteLines = sprite.split("\n");