feat: Rendering room

We have a room!
This commit is contained in:
2025-12-15 17:08:38 +01:00
parent 3c5d46b879
commit 2623cd1328
19 changed files with 325 additions and 8 deletions

4
.gitignore vendored
View File

@@ -36,4 +36,6 @@ build/
.vscode/ .vscode/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
logs

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.config;
import cz.jzitnik.annotations.Config;
import lombok.Getter;
@Config
@Getter
public class RoomSizeConfig {
private final int roomHeight = 450;
private final int roomWidth = 450;
}

View File

@@ -0,0 +1,6 @@
package cz.jzitnik.events;
import cz.jzitnik.utils.events.Event;
public class FullRoomDraw implements Event {
}

View File

@@ -1,9 +1,7 @@
package cz.jzitnik.events.handlers; package cz.jzitnik.events.handlers;
import com.googlecode.lanterna.TextCharacter;
import com.googlecode.lanterna.TextColor; import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.graphics.TextGraphics; import com.googlecode.lanterna.graphics.TextGraphics;
import com.googlecode.lanterna.screen.Screen;
import cz.jzitnik.annotations.EventHandler; import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.events.RerenderScreen;
@@ -13,10 +11,12 @@ import cz.jzitnik.ui.pixels.Empty;
import cz.jzitnik.ui.pixels.Pixel; import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.AbstractEventHandler; import cz.jzitnik.utils.events.AbstractEventHandler;
import lombok.extern.slf4j.Slf4j;
import java.awt.*; import java.awt.*;
import java.io.IOException; import java.io.IOException;
@Slf4j
@EventHandler(RerenderScreen.class) @EventHandler(RerenderScreen.class)
public class CliHandler extends AbstractEventHandler<RerenderScreen> { public class CliHandler extends AbstractEventHandler<RerenderScreen> {
@InjectState @InjectState
@@ -43,7 +43,7 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
for (int y = start.getRow(); y <= end.getRow(); y++) { for (int y = start.getRow(); y <= end.getRow(); y++) {
for (int x = start.getColumn(); x <= end.getColumn(); x++) { for (int x = start.getColumn(); x <= end.getColumn(); x++) {
Pixel pixel = buffer[y][x]; Pixel pixel = buffer[y][x];
TextColor color = pixel.getClass().equals(Empty.class) ? TextColor.ANSI.BLACK : pixel.getColor(); TextColor color = pixel.getClass().equals(Empty.class) ? new TextColor.RGB(4, 4, 16) : pixel.getColor();
drawPixel(tg, x, y, color); drawPixel(tg, x, y, color);
} }
@@ -57,8 +57,8 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
} }
} }
private static void drawPixel(TextGraphics tg, int x, int y, TextColor color) { private void drawPixel(TextGraphics tg, int x, int y, TextColor color) {
tg.setForegroundColor(color); tg.setForegroundColor(color);
tg.setCharacter(x, y, '█'); // full block character tg.setCharacter(x, y, '█');
} }
} }

View File

@@ -0,0 +1,99 @@
package cz.jzitnik.events.handlers;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.screen.TerminalScreen;
import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.FullRoomDraw;
import cz.jzitnik.events.RerenderScreen;
import cz.jzitnik.game.GameState;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.states.RenderState;
import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState;
import cz.jzitnik.ui.pixels.ColoredPixel;
import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.AbstractEventHandler;
import cz.jzitnik.utils.events.EventManager;
import lombok.extern.slf4j.Slf4j;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@EventHandler(FullRoomDraw.class)
public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
public FullRoomDrawHandler(DependencyManager dm) {
super(dm);
}
@InjectState
private GameState gameState;
@InjectState
private ScreenBuffer screenBuffer;
@InjectDependency
private ResourceManager resourceManager;
@InjectState
private TerminalState terminalState;
@InjectDependency
private EventManager eventManager;
@InjectState
private RenderState renderState;
@Override
public void handle(FullRoomDraw event) {
log.debug("Rendering full room");
TerminalScreen terminalScreen = terminalState.getTerminalScreen();
List<RerenderScreen.ScreenPart> partsToRerender = new ArrayList<>();
var buffer = screenBuffer.getBuffer();
BufferedImage room = resourceManager.getResource(ResourceManager.Resource.ROOM1);
TerminalSize terminalSize = terminalScreen.getTerminalSize();
int terminalHeight = terminalSize.getRows();
int terminalWidth = terminalSize.getColumns();
int width = room.getWidth();
int height = room.getHeight();
int startX = (terminalWidth - (width * 2)) / 2;
int startY = (terminalHeight - height) / 2;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = room.getRGB(x, y);
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = pixel & 0xff;
Pixel pixel1 = new ColoredPixel(new TextColor.RGB(red, green, blue));
buffer[y + startY][x * 2 + startX] = pixel1;
buffer[y + startY][x * 2 + 1 + startX] = pixel1;
}
}
partsToRerender.add(new RerenderScreen.ScreenPart(
new TerminalPosition(startX, startY),
new TerminalPosition(startY + height - 1, (startX + height - 1) * 2)
));
if (renderState.isFirstRender()) {
eventManager.emitEvent(RerenderScreen.full(terminalSize));
renderState.setFirstRender(false);
} else {
eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new)));
}
}
}

View File

@@ -4,6 +4,7 @@ import com.googlecode.lanterna.TerminalSize;
import cz.jzitnik.annotations.EventHandler; import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.FullRoomDraw;
import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.events.RerenderScreen;
import cz.jzitnik.events.TerminalResizeEvent; import cz.jzitnik.events.TerminalResizeEvent;
import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.ScreenBuffer;
@@ -12,7 +13,9 @@ import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.AbstractEventHandler; import cz.jzitnik.utils.events.AbstractEventHandler;
import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.events.EventManager;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@EventHandler(TerminalResizeEvent.class) @EventHandler(TerminalResizeEvent.class)
public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalResizeEvent> { public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalResizeEvent> {
public TerminalResizeEventHandler(DependencyManager dm) { public TerminalResizeEventHandler(DependencyManager dm) {
@@ -39,6 +42,6 @@ public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalRes
} }
screenBuffer.setBuffer(buffer); screenBuffer.setBuffer(buffer);
eventManager.emitEvent(RerenderScreen.full(size)); eventManager.emitEvent(new FullRoomDraw());
} }
} }

View File

@@ -0,0 +1,19 @@
package cz.jzitnik.game;
import cz.jzitnik.game.objects.GameObject;
import java.util.ArrayList;
import java.util.List;
public class GameRoom {
private GameRoom left;
private GameRoom right;
private GameRoom up;
private GameRoom down;
private List<GameObject> objects;
public GameRoom() {
objects = new ArrayList<>();
}
}

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.game;
import cz.jzitnik.annotations.State;
import cz.jzitnik.game.utils.RoomCords;
@State
public class GameState {
private GameRoom currentRoom;
private RoomCords playerCords;
}

View File

@@ -0,0 +1,41 @@
package cz.jzitnik.game;
import cz.jzitnik.annotations.Dependency;
import cz.jzitnik.annotations.injectors.InjectDependency;
import lombok.AllArgsConstructor;
import lombok.Getter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@Dependency
public class ResourceManager {
@InjectDependency
private ClassLoader classLoader;
@AllArgsConstructor
@Getter
public enum Resource {
ROOM1("rooms/1.png");
private final String path; // Field must be final for enum
}
private HashMap<Resource, BufferedImage> resourceCache = new HashMap<>();
public BufferedImage getResource(Resource resource) {
InputStream is = classLoader.getResourceAsStream("textures/" + resource.getPath());
if (is == null) {
throw new RuntimeException("Image not found in resources!");
}
try {
return ImageIO.read(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.game.exceptions;
public class InvalidCoordinatesException extends RuntimeException {
public InvalidCoordinatesException(String message) {
super(message);
}
public InvalidCoordinatesException() {
super();
}
}

View File

@@ -0,0 +1,4 @@
package cz.jzitnik.game.items;
public abstract class Item {
}

View File

@@ -0,0 +1,10 @@
package cz.jzitnik.game.objects;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.ui.pixels.Pixel;
public final class Chest extends GameObject {
public Chest(Pixel[][] texture, RoomCords cords) {
super(texture, cords);
}
}

View File

@@ -0,0 +1,14 @@
package cz.jzitnik.game.objects;
import cz.jzitnik.game.utils.Renderable;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.ui.pixels.Pixel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public sealed abstract class GameObject implements Renderable permits Chest {
private Pixel[][] texture;
private RoomCords cords;
}

View File

@@ -0,0 +1,7 @@
package cz.jzitnik.game.utils;
import cz.jzitnik.ui.pixels.Pixel;
public interface Renderable {
Pixel[][] getTexture();
}

View File

@@ -0,0 +1,27 @@
package cz.jzitnik.game.utils;
import cz.jzitnik.config.RoomSizeConfig;
import cz.jzitnik.game.exceptions.InvalidCoordinatesException;
import lombok.Getter;
@Getter
public class RoomCords {
private final RoomSizeConfig roomSizeConfig;
private int x;
private int y;
public RoomCords(int x, int y, RoomSizeConfig roomSizeConfig) {
this.roomSizeConfig = roomSizeConfig;
updateCords(x, y);
}
public void updateCords(int x, int y) {
if (x >= roomSizeConfig.getRoomWidth() || y >= roomSizeConfig.getRoomHeight()) {
throw new InvalidCoordinatesException();
}
this.x = x;
this.y = y;
}
}

View File

@@ -0,0 +1,12 @@
package cz.jzitnik.states;
import cz.jzitnik.annotations.State;
import lombok.Getter;
import lombok.Setter;
@State
@Getter
@Setter
public class RenderState {
private boolean isFirstRender = true;
}

View File

@@ -86,7 +86,7 @@ public class EventManager extends Thread {
try { try {
handler.handle(typedEvent); handler.handle(typedEvent);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(System.err); log.debug(e.toString());
} }
}); });
} }

View File

@@ -0,0 +1,40 @@
<configuration>
<appender name="GENERAL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/general.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- File name pattern for rolled logs -->
<fileNamePattern>logs/general.%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<!-- Max size before rotation -->
<maxFileSize>3MB</maxFileSize>
<!-- Max history (how many days to keep) -->
<maxHistory>30</maxHistory>
<!-- Total size cap -->
<totalSizeCap>30MB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ERROR_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="GENERAL_FILE" />
<appender-ref ref="ERROR_CONSOLE" />
</root>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB