feat: Started implementing pig and health and hunger

This commit is contained in:
Jakub Žitník 2025-02-28 18:05:33 +01:00
parent 91882fabc0
commit e8e9d826bb
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
19 changed files with 283 additions and 118 deletions

View File

@ -1,16 +1,16 @@
package cz.jzitnik;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.threads.HealthRegenerationThread;
import cz.jzitnik.game.threads.HungerDrainThread;
import cz.jzitnik.game.threads.InputHandlerThread;
import cz.jzitnik.game.ui.*;
import cz.jzitnik.tui.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;
import java.io.IOException;
import java.util.Optional;
public class Main {
@ -29,101 +29,28 @@ public class Main {
var screenRenderer = new ScreenRenderer(spriteList, terminal);
var game = new Game();
MouseHandler mouseHandler = new MouseHandler(game, terminal, screenRenderer);
final boolean[] isRunning = {true};
Thread inputThread = new Thread(() -> {
try {
while (isRunning[0]) {
int key = terminal.reader().read();
Thread inputHandlerThread = new InputHandlerThread(game, terminal, screenRenderer, isRunning);
Thread healingThread = new HealthRegenerationThread(game.getPlayer());
Thread hungerDrainThread = new HungerDrainThread(game.getPlayer());
// Check for mouse event sequence
if (key == 27) { // ESC
key = terminal.reader().read();
if (key == '[') {
key = terminal.reader().read();
if (key == 'M') {
MouseEvent mouseEvent = terminal.readMouseEvent();
switch (game.getWindow()) {
case WORLD -> mouseHandler.handle(mouseEvent);
case INVENTORY -> InventoryClickHandler.click(mouseEvent, terminal, screenRenderer, game, Optional.empty(), Optional.empty());
case CRAFTING_TABLE -> game.getGameStates().craftingTable.click(mouseEvent, terminal, screenRenderer);
case CHEST -> ((Chest) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX].stream().filter(i -> i.getBlockId().equals("chest")).toList().getFirst().getData()).click(game, mouseEvent, terminal, screenRenderer);
case FURNACE -> ((Furnace) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX].stream().filter(i -> i.getBlockId().equals("furnace")).toList().getFirst().getData()).click(game, mouseEvent, terminal, screenRenderer);
}
}
}
}
healingThread.start();
hungerDrainThread.start();
inputHandlerThread.start();
switch (key) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
game.changeSlot(key - 49, screenRenderer);
break;
case 'a':
game.movePlayerLeft(screenRenderer);
screenRenderer.render(game);
break;
case 'd':
game.movePlayerRight(screenRenderer);
screenRenderer.render(game);
break;
case ' ':
game.movePlayerUp(screenRenderer);
screenRenderer.render(game);
break;
case 'e':
if (game.getWindow() != Window.WORLD) {
game.getInventory().setSelectedItemInv(-1);
CloseHandler.handle(game.getWindow(), game);
game.setWindow(Window.WORLD);
} else {
game.setWindow(Window.INVENTORY);
}
screenRenderer.render(game);
break;
case 'q':
System.out.println("Exiting game...");
isRunning[0] = false;
break;
default:
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
inputThread.start();
// Game loop (rendering the game)
while (isRunning[0]) {
if (game.getWindow() == Window.WORLD) {
// Rerender the game only when default window
screenRenderer.render(game);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Disable mouse tracking on exit
terminal.trackMouse(Terminal.MouseTracking.Off);
terminal.close();
} catch (IOException e) {
e.printStackTrace();
} catch (IOException | InterruptedException _) {
}
}
}

View File

@ -238,9 +238,12 @@ public class Game {
world[cords2[1]][cords2[0]].add(player.getPlayerBlock1());
world[cords2[1] + 1][cords2[0]].add(player.getPlayerBlock2());
world[cords2[1]][cords2[0]].remove(player.getPlayerBlock2());
player.addFalling();
screenRenderer.render(this);
} else {
player.fell();
screenRenderer.render(this);
break;
}
}

View File

@ -7,7 +7,30 @@ import lombok.Setter;
@Setter
public class Player {
private int health = 10;
private int hunger = 6;
private int hunger = 10;
private int fallDistance = 0;
private Block playerBlock1;
private Block playerBlock2;
public synchronized void heal() {
if (hunger > 3 && health < 10) {
health = Math.min(health + 1, 10);
}
}
public synchronized void drainHunger() {
if (hunger > 0) {
hunger--;
}
}
public void addFalling() {
fallDistance++;
}
public void fell() {
int damage = Math.max(fallDistance - 3, 0);
health = Math.max(0, health - damage);
fallDistance = 0;
}
}

View File

@ -49,7 +49,9 @@ public class SpriteLoader {
ITEM_CHEST,
HEART,
HUNGER
HUNGER,
PIG,
}
public static final HashMap<SPRITES, Sprite> SPRITES_MAP = new HashMap<>();
@ -91,6 +93,8 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.HEART, new Heart());
SPRITES_MAP.put(SPRITES.HUNGER, new Hunger());
SPRITES_MAP.put(SPRITES.PIG, new Pig());
}
public static SpriteList<SPRITES> load() {

View File

@ -0,0 +1,25 @@
package cz.jzitnik.game.sprites;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
public class Pig extends Sprite {
public enum PigState{
LEFT,
RIGHT,
}
public String getSprite() {
return getSprite(PigState.RIGHT);
}
public String getSprite(Enum e) {
return ResourceLoader.loadResource(
switch (e) {
case PigState.LEFT -> "mobs/pigrev.ans";
case PigState.RIGHT -> "mobs/pig.ans";
default -> throw new IllegalStateException("Unexpected value: " + e);
}
);
}
}

View File

@ -4,7 +4,7 @@ import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
public class SimpleSprite extends Sprite {
private String resource;
private final String resource;
public SimpleSprite(String resource) {
this.resource = resource;

View File

@ -0,0 +1,21 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Player;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class HealthRegenerationThread extends Thread {
private final Player player;
@Override
public void run() {
while (true) {
try {
Thread.sleep(4000); // Heal every 4 seconds
player.heal();
} catch (InterruptedException e) {
break;
}
}
}
}

View File

@ -0,0 +1,21 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Player;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class HungerDrainThread extends Thread {
private final Player player;
@Override
public void run() {
while (true) {
try {
Thread.sleep(30000);
player.drainHunger();
} catch (InterruptedException e) {
break;
}
}
}
}

View File

@ -0,0 +1,89 @@
package cz.jzitnik.game.threads;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.ui.*;
import cz.jzitnik.tui.MouseHandler;
import cz.jzitnik.tui.ScreenRenderer;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import java.io.IOException;
import java.util.Optional;
public class InputHandlerThread extends Thread {
private final Game game;
private final Terminal terminal;
private final ScreenRenderer screenRenderer;
private final MouseHandler mouseHandler;
private final boolean[] isRunning;
public InputHandlerThread(Game game, Terminal terminal, ScreenRenderer screenRenderer, boolean[] isRunning) {
this.game = game;
this.terminal = terminal;
this.screenRenderer = screenRenderer;
this.mouseHandler = new MouseHandler(game, terminal, screenRenderer);
this.isRunning = isRunning;
}
@Override
public void run() {
try {
while (isRunning[0]) {
int key = terminal.reader().read();
if (key == 27) { // ESC
key = terminal.reader().read();
if (key == '[') {
key = terminal.reader().read();
if (key == 'M') {
MouseEvent mouseEvent = terminal.readMouseEvent();
switch (game.getWindow()) {
case WORLD -> mouseHandler.handle(mouseEvent);
case INVENTORY -> InventoryClickHandler.click(mouseEvent, terminal, screenRenderer, game, Optional.empty(), Optional.empty());
case CRAFTING_TABLE -> game.getGameStates().craftingTable.click(mouseEvent, terminal, screenRenderer);
case CHEST -> ((Chest) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX]
.stream().filter(i -> i.getBlockId().equals("chest")).toList().getFirst().getData())
.click(game, mouseEvent, terminal, screenRenderer);
case FURNACE -> ((Furnace) game.getWorld()[game.getGameStates().clickY][game.getGameStates().clickX]
.stream().filter(i -> i.getBlockId().equals("furnace")).toList().getFirst().getData())
.click(game, mouseEvent, terminal, screenRenderer);
}
}
}
}
switch (key) {
case '1', '2', '3', '4', '5', '6', '7', '8', '9' -> game.changeSlot(key - 49, screenRenderer);
case 'a' -> {
game.movePlayerLeft(screenRenderer);
screenRenderer.render(game);
}
case 'd' -> {
game.movePlayerRight(screenRenderer);
screenRenderer.render(game);
}
case ' ' -> {
game.movePlayerUp(screenRenderer);
screenRenderer.render(game);
}
case 'e' -> {
if (game.getWindow() != Window.WORLD) {
game.getInventory().setSelectedItemInv(-1);
CloseHandler.handle(game.getWindow(), game);
game.setWindow(Window.WORLD);
} else {
game.setWindow(Window.INVENTORY);
}
screenRenderer.render(game);
}
case 'q' -> {
System.out.println("Exiting game...");
isRunning[0] = false;
}
default -> {}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -24,7 +24,7 @@ public class Chest {
int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
var inventory = game.getInventory();
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
List<String> sprites = game.getInventory().getSprites(items, spriteList, inventory.getSelectedItemInv() - 50);
@ -63,7 +63,7 @@ public class Chest {
int y = mouseEvent.getY();
int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE + 5;
int heightPixels = ROW_AMOUNT * (CELL_HEIGHT) - 10;
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
if (x > moveLeft && x <= moveLeft + widthPixels && y > 0 && y <= heightPixels && mouseEvent.getType() == MouseEvent.Type.Pressed) {
if (mouseEvent.getType() != MouseEvent.Type.Pressed) return;

View File

@ -56,7 +56,7 @@ public class CraftingTable {
int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
var inventory = game.getInventory();
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
List<String> sprites = game.getInventory().getSprites(items, spriteList, inventory.getSelectedItemInv() - 50);

View File

@ -34,7 +34,7 @@ public class Furnace {
public void render(Game game, StringBuilder buffer, Terminal terminal, SpriteList spriteList) {
int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
var inventory = game.getInventory();
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
List<String> sprites = game.getInventory().getSprites(items, spriteList, inventory.getSelectedItemInv() - 50);
@ -113,7 +113,7 @@ public class Furnace {
int x = mouseEvent.getX();
int y = mouseEvent.getY();
int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
if (x > moveLeft && x <= moveLeft + CELL_WIDTH + BORDER_SIZE && y > 0 && y < CELL_HEIGHT && mouseEvent.getType() == MouseEvent.Type.Pressed) {
InventoryItem selectedItem = game.getInventory().getSelectedItemNo(Optional.of(items));

View File

@ -12,10 +12,10 @@ import static cz.jzitnik.game.ui.Inventory.INVENTORY_SIZE_PX;
public class Healthbar {
public static void render(StringBuilder buffer, SpriteList spriteList, Terminal terminal, Game game) {
int termWidth = terminal.getWidth();
int startLeft = (termWidth / 2) - (INVENTORY_SIZE_PX / 2);
int startLeft = Math.max(0, (termWidth / 2) - (INVENTORY_SIZE_PX / 2));
int heartSize = 9 * 20;
int moveLeft = INVENTORY_SIZE_PX - (heartSize * 2);
int moveLeft = Math.max(0, INVENTORY_SIZE_PX - (heartSize * 2));
String[] spriteOn = spriteList.getSprite(SpriteLoader.SPRITES.HEART).getSprite(Heart.HeartState.ON).split("\n");
String[] spriteOff = spriteList.getSprite(SpriteLoader.SPRITES.HEART).getSprite(Heart.HeartState.OFF).split("\n");

View File

@ -147,7 +147,7 @@ public class Inventory {
int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2;
int heightPixels = ROW_AMOUNT * (25 + 1);
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));
int moveTop = moveTopCustom.orElse((terminal.getHeight() / 2) - (heightPixels / 2));
List<String> sprites = getSprites(items, spriteList, selectedItemInv);

View File

@ -27,11 +27,11 @@ public class InventoryClickHandler {
}
private static int calculateMoveLeft(Terminal terminal) {
return (terminal.getWidth() / 2) - ((COLUMN_AMOUNT * (50 + 4) + 2) / 2);
return Math.max(0, (terminal.getWidth() / 2) - ((COLUMN_AMOUNT * (50 + 4) + 2) / 2));
}
private static int calculateMoveTop(Terminal terminal) {
return (terminal.getHeight() / 2) - ((ROW_AMOUNT * (25 + 1)) / 2);
return Math.max(0, (terminal.getHeight() / 2) - ((ROW_AMOUNT * (25 + 1)) / 2));
}
private static boolean handleCraftingTableClick(MouseEvent mouseEvent, int x, int y, Inventory inventory, ScreenRenderer screenRenderer, Game game, int moveLeft, int moveTop, Optional<InventoryItem[]> i) {

View File

@ -74,7 +74,6 @@ public class ScreenRenderer {
int visibleWidth = endX - startX;
int visibleHeight = endY - startY;
// If the width is odd, reduce viewXRadius by 1
if (visibleWidth % 2 != 0) {
endX = Math.max(startX, endX - 1);
}
@ -100,22 +99,7 @@ public class ScreenRenderer {
);
if (selectedBlock.isPresent() && selectedBlock.get().get(0) == x && selectedBlock.get().get(1) == y) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50));
stringBuilder.append("\n");
for (int i = 0; i < 23; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[0m ".repeat(46));
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\n");
}
stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50));
stringBuilder.append("\n");
StringBuilder stringBuilder = getStringBuilder();
sprites.add(stringBuilder.toString());
}
@ -151,4 +135,24 @@ public class ScreenRenderer {
System.out.println(main);
}
private static StringBuilder getStringBuilder() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50));
stringBuilder.append("\n");
for (int i = 0; i < 23; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[0m ".repeat(46));
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\033[38;5;231;48;5;231m▓");
stringBuilder.append("\n");
}
stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50));
stringBuilder.append("\n");
return stringBuilder;
}
}

View File

@ -6,14 +6,12 @@ import java.util.HashMap;
public class SpriteList<E extends Enum<E>> {
private final EnumMap<E, Sprite> sprites;
// Constructor that takes an Enum class and a HashMap
public SpriteList(Class<E> enumClass, HashMap<E, Sprite> initialMap) {
sprites = new EnumMap<>(enumClass);
// Initialize with values from the provided HashMap
for (E key : enumClass.getEnumConstants()) {
if (!initialMap.containsKey(key)) {
throw new RuntimeException("TODO: Missing sprite");
throw new RuntimeException("Error: Missing sprite: " + key);
}
Sprite value = initialMap.get(key);
sprites.put(key, value);

View File

@ -0,0 +1,25 @@
                                                  
                                                  
                                                  
                                                  
                                                  
                                                  
                                                  
                                         
                                         
                          
                          
                          
                         
                        
                         
                         
                               
                               
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@ -0,0 +1,25 @@