From f0f10470784e1eba0095dc335d75ea0e52c5b8a9 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Mon, 17 Feb 2025 22:16:35 +0100 Subject: [PATCH] initial commit --- .gitignore | 38 +++++ .idea/.gitignore | 3 + .idea/encodings.xml | 7 + .idea/misc.xml | 14 ++ .idea/vcs.xml | 6 + pom.xml | 33 ++++ src/main/java/cz/jzitnik/Main.java | 73 +++++++++ src/main/java/cz/jzitnik/game/Block.java | 18 +++ src/main/java/cz/jzitnik/game/Game.java | 150 ++++++++++++++++++ .../java/cz/jzitnik/game/SpriteLoader.java | 33 ++++ .../java/cz/jzitnik/game/sprites/Air.java | 20 +++ .../java/cz/jzitnik/game/sprites/Bedrock.java | 14 ++ .../java/cz/jzitnik/game/sprites/Dirt.java | 14 ++ .../java/cz/jzitnik/game/sprites/Grass.java | 14 ++ .../java/cz/jzitnik/game/sprites/Steve.java | 23 +++ .../java/cz/jzitnik/game/sprites/Stone.java | 14 ++ .../java/cz/jzitnik/tui/ResourceLoader.java | 22 +++ .../java/cz/jzitnik/tui/ScreenRenderer.java | 72 +++++++++ src/main/java/cz/jzitnik/tui/Sprite.java | 6 + .../java/cz/jzitnik/tui/SpriteCombiner.java | 67 ++++++++ src/main/java/cz/jzitnik/tui/SpriteList.java | 33 ++++ src/main/resources/textures/bedrock.ans | 26 +++ src/main/resources/textures/dirt.ans | 26 +++ src/main/resources/textures/grass.ans | 26 +++ src/main/resources/textures/steve.ans | 23 +++ src/main/resources/textures/stone.ans | 26 +++ 26 files changed, 801 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/cz/jzitnik/Main.java create mode 100644 src/main/java/cz/jzitnik/game/Block.java create mode 100644 src/main/java/cz/jzitnik/game/Game.java create mode 100644 src/main/java/cz/jzitnik/game/SpriteLoader.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Air.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Bedrock.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Dirt.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Grass.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Steve.java create mode 100644 src/main/java/cz/jzitnik/game/sprites/Stone.java create mode 100644 src/main/java/cz/jzitnik/tui/ResourceLoader.java create mode 100644 src/main/java/cz/jzitnik/tui/ScreenRenderer.java create mode 100644 src/main/java/cz/jzitnik/tui/Sprite.java create mode 100644 src/main/java/cz/jzitnik/tui/SpriteCombiner.java create mode 100644 src/main/java/cz/jzitnik/tui/SpriteList.java create mode 100644 src/main/resources/textures/bedrock.ans create mode 100644 src/main/resources/textures/dirt.ans create mode 100644 src/main/resources/textures/grass.ans create mode 100644 src/main/resources/textures/steve.ans create mode 100644 src/main/resources/textures/stone.ans diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..039a9d1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..11a0ca7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + cz.jzitnik + twodcraft + 1.0-SNAPSHOT + + + 22 + 22 + UTF-8 + + + + + org.projectlombok + lombok + 1.18.36 + provided + + + + + org.jline + jline-reader + 3.20.0 + + + + \ No newline at end of file diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java new file mode 100644 index 0000000..6084c7b --- /dev/null +++ b/src/main/java/cz/jzitnik/Main.java @@ -0,0 +1,73 @@ +package cz.jzitnik; + +import cz.jzitnik.game.Game; +import cz.jzitnik.game.SpriteLoader; +import cz.jzitnik.tui.ScreenRenderer; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +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(); // Switch to raw mode to capture keys immediately + var spriteList = SpriteLoader.load(); + var screenRenderer = new ScreenRenderer(spriteList); + var game = new Game(); + + final boolean[] isRunning = {true}; + + Thread inputThread = new Thread(() -> { + try { + while (isRunning[0]) { + int key = terminal.reader().read(); + switch (key) { + case 'a': + game.movePlayerLeft(screenRenderer); + screenRenderer.render(game.getWorld()); + break; + case 'd': + game.movePlayerRight(screenRenderer); + screenRenderer.render(game.getWorld()); + break; + case ' ': + game.movePlayerUp(screenRenderer); + screenRenderer.render(game.getWorld()); + break; + case 'm': + System.out.println("Mine pressed"); + 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]) { + screenRenderer.render(game.getWorld()); + try { + Thread.sleep(1000); // Control game loop speed + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + terminal.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/cz/jzitnik/game/Block.java b/src/main/java/cz/jzitnik/game/Block.java new file mode 100644 index 0000000..7b8bb31 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/Block.java @@ -0,0 +1,18 @@ +package cz.jzitnik.game; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class Block { + private String blockId; + private SpriteLoader.SPRITES sprite; + private boolean ghost; + + public Block(String blockId, SpriteLoader.SPRITES sprite) { + this.blockId = blockId; + this.sprite = sprite; + this.ghost = false; + } +} diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java new file mode 100644 index 0000000..1c0abcc --- /dev/null +++ b/src/main/java/cz/jzitnik/game/Game.java @@ -0,0 +1,150 @@ +package cz.jzitnik.game; + +import cz.jzitnik.tui.ScreenRenderer; +import lombok.Getter; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class Game { + private List[][] world = new ArrayList[50][50]; + private Block player; + + public Game() { + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 50; j++) { + world[i][j] = new ArrayList<>(); + } + } + + Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE); + world[10][2].add(new Block("grass", SpriteLoader.SPRITES.GRASS)); + player = steveBlock; + + for (int i = 0; i < 50; i++) { + world[11][i].add(new Block("grass", SpriteLoader.SPRITES.GRASS)); + world[12][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT)); + world[13][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT)); + world[14][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT)); + + for (int j = 15; j < 49; j++) { + world[j][i].add(new Block("stone", SpriteLoader.SPRITES.STONE)); + } + + world[49][i].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK)); + } + + for (int i = 0; i < world.length; i++) { + for (int j = 0; j < world[i].length; j++) { + if (world[i][j].isEmpty()) { + world[i][j].add(new Block("air", SpriteLoader.SPRITES.AIR, true)); // Fill with air + } + } + } + + world[10][0].add(steveBlock); + } + + private 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]) { + if (block.getBlockId().equals("steve")) { + return new int[]{j, i}; + } + } + } + } + return null; + } + + public void movePlayerRight(ScreenRenderer screenRenderer) { + int[] cords = getPlayerCords(); + + if (world[cords[1]][cords[0] + 1].stream().anyMatch(block -> !block.isGhost())) { + return; + } + + world[cords[1]][cords[0] + 1].add(player); + world[cords[1]][cords[0]].remove(player); + + + new Thread(() -> { + while (true) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + int[] cords2 = getPlayerCords(); + + if (world[cords2[1] + 1][cords2[0]].stream().anyMatch(Block::isGhost)) { + world[cords2[1] + 1][cords2[0]].add(player); + world[cords2[1]][cords2[0]].remove(player); + + screenRenderer.render(world); + } else { + break; + } + } + }).start(); + } + + public void movePlayerLeft(ScreenRenderer screenRenderer) { + int[] cords = getPlayerCords(); + + if (world[cords[1]][cords[0] - 1].stream().anyMatch(block -> !block.isGhost())) { + return; + } + + world[cords[1]][cords[0] - 1].add(player); + world[cords[1]][cords[0]].remove(player); + + new Thread(() -> { + while (true) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + int[] cords2 = getPlayerCords(); + + if (world[cords2[1] + 1][cords2[0]].stream().anyMatch(Block::isGhost)) { + world[cords2[1] + 1][cords2[0]].add(player); + world[cords2[1]][cords2[0]].remove(player); + + screenRenderer.render(world); + } else { + break; + } + } + }).start(); + } + + public void movePlayerUp(ScreenRenderer screenRenderer) { + int[] cords = getPlayerCords(); + + if (world[cords[1] - 1][cords[0]].stream().anyMatch(block -> !block.isGhost()) || world[cords[1] + 1][cords[0]].stream().anyMatch(Block::isGhost)) { + return; + } + + world[cords[1] - 1][cords[0]].add(player); + world[cords[1]][cords[0]].remove(player); + + new Thread(() -> { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + int[] cords2 = getPlayerCords(); + + if (world[cords2[1] + 1][cords2[0]].stream().anyMatch(Block::isGhost)) { + world[cords2[1] + 1][cords2[0]].add(player); + world[cords2[1]][cords2[0]].remove(player); + + screenRenderer.render(world); + } + }).start(); + } +} diff --git a/src/main/java/cz/jzitnik/game/SpriteLoader.java b/src/main/java/cz/jzitnik/game/SpriteLoader.java new file mode 100644 index 0000000..0571222 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/SpriteLoader.java @@ -0,0 +1,33 @@ +package cz.jzitnik.game; + +import cz.jzitnik.game.sprites.*; +import cz.jzitnik.tui.Sprite; +import cz.jzitnik.tui.SpriteList; + +import java.util.HashMap; + +public class SpriteLoader { + public enum SPRITES { + AIR, + DIRT, + GRASS, + STEVE, + STONE, + BEDROCK + } + + public static final HashMap SPRITES_MAP = new HashMap<>(); + + static { + SPRITES_MAP.put(SPRITES.AIR, new Air()); + SPRITES_MAP.put(SPRITES.DIRT, new Dirt()); + SPRITES_MAP.put(SPRITES.GRASS, new Grass()); + SPRITES_MAP.put(SPRITES.STONE, new Stone()); + SPRITES_MAP.put(SPRITES.STEVE, new Steve()); + SPRITES_MAP.put(SPRITES.BEDROCK, new Bedrock()); + } + + public static SpriteList load() { + return new SpriteList<>(SPRITES.class, SPRITES_MAP); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Air.java b/src/main/java/cz/jzitnik/game/sprites/Air.java new file mode 100644 index 0000000..9491f4c --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Air.java @@ -0,0 +1,20 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.Sprite; + +public class Air extends Sprite { + public String getSprite() { + StringBuilder sprite = new StringBuilder(); + for (int i = 0; i < 25; i++) { + for (int j = 0; j < 50; j++) { + sprite.append("\033[0m "); + } + sprite.append("\n"); + } + return sprite.toString(); + } + + public String getSprite(Enum e) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Bedrock.java b/src/main/java/cz/jzitnik/game/sprites/Bedrock.java new file mode 100644 index 0000000..aa1c1b8 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Bedrock.java @@ -0,0 +1,14 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.ResourceLoader; +import cz.jzitnik.tui.Sprite; + +public class Bedrock extends Sprite { + public String getSprite() { + return ResourceLoader.loadResource("bedrock.ans"); + } + + public String getSprite(Enum key) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Dirt.java b/src/main/java/cz/jzitnik/game/sprites/Dirt.java new file mode 100644 index 0000000..80f5bea --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Dirt.java @@ -0,0 +1,14 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.ResourceLoader; +import cz.jzitnik.tui.Sprite; + +public class Dirt extends Sprite { + public String getSprite() { + return ResourceLoader.loadResource("dirt.ans"); + } + + public String getSprite(Enum key) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Grass.java b/src/main/java/cz/jzitnik/game/sprites/Grass.java new file mode 100644 index 0000000..3e0e61b --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Grass.java @@ -0,0 +1,14 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.ResourceLoader; +import cz.jzitnik.tui.Sprite; + +public class Grass extends Sprite { + public String getSprite() { + return ResourceLoader.loadResource("grass.ans"); + } + + public String getSprite(Enum key) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Steve.java b/src/main/java/cz/jzitnik/game/sprites/Steve.java new file mode 100644 index 0000000..26bb8b4 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Steve.java @@ -0,0 +1,23 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.ResourceLoader; +import cz.jzitnik.tui.Sprite; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class Steve extends Sprite { + private String fix(String x) { + var arr = x.replaceAll("\033\\[38;5;1;48;5;16m", "\033[0m").split("\n"); + arr = Arrays.copyOf(arr, arr.length - 1); // Remove the last line + return ("\033[0m ".repeat(50) + "\n").repeat(3) + Arrays.stream(arr).map(y -> "\033[0m ".repeat(12) + y + " " + "\033[0m ".repeat(12) + "\n").collect(Collectors.joining()); + } + + public String getSprite() { + return fix(ResourceLoader.loadResource("steve.ans")); + } + + public String getSprite(Enum e) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/game/sprites/Stone.java b/src/main/java/cz/jzitnik/game/sprites/Stone.java new file mode 100644 index 0000000..9c7e94a --- /dev/null +++ b/src/main/java/cz/jzitnik/game/sprites/Stone.java @@ -0,0 +1,14 @@ +package cz.jzitnik.game.sprites; + +import cz.jzitnik.tui.ResourceLoader; +import cz.jzitnik.tui.Sprite; + +public class Stone extends Sprite { + public String getSprite() { + return ResourceLoader.loadResource("stone.ans"); + } + + public String getSprite(Enum key) { + throw new RuntimeException("Imposible state"); + } +} diff --git a/src/main/java/cz/jzitnik/tui/ResourceLoader.java b/src/main/java/cz/jzitnik/tui/ResourceLoader.java new file mode 100644 index 0000000..ddfe8c0 --- /dev/null +++ b/src/main/java/cz/jzitnik/tui/ResourceLoader.java @@ -0,0 +1,22 @@ +package cz.jzitnik.tui; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class ResourceLoader { + public static String loadResource(String fileName) { + try (InputStream inputStream = ResourceLoader.class.getClassLoader().getResourceAsStream("textures/"+fileName)) { + if (inputStream == null) { + // If the file is not found, return null + return null; + } + + byte[] bytes = inputStream.readAllBytes(); + return new String(bytes, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/cz/jzitnik/tui/ScreenRenderer.java b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java new file mode 100644 index 0000000..92caf08 --- /dev/null +++ b/src/main/java/cz/jzitnik/tui/ScreenRenderer.java @@ -0,0 +1,72 @@ +package cz.jzitnik.tui; + +import cz.jzitnik.game.Block; +import lombok.AllArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +public class ScreenRenderer { + private SpriteList spriteList; + + private int[] getPlayerCords(List[][] world) { + for (int i = 0; i < world.length; i++) { + for (int j = 0; j < world[i].length; j++) { + if (world[i][j].stream().anyMatch(x -> x.getBlockId().equals("steve"))) { + return new int[]{j, i}; + } + } + } + + return null; + } + + public void render(List[][] world) { + StringBuilder main = new StringBuilder(); + main.append("\033[H\033[2J"); + + int[] cords = getPlayerCords(world); + if (cords == null) return; + + int playerX = cords[0]; + int playerY = cords[1]; + int viewXRadius = 5; + int viewUpRadius = 4; + int viewDownRadius = 3; + + // Calculate visible area boundaries + int startX = Math.max(0, playerX - viewXRadius); + int endX = Math.min(world[0].length, playerX + viewXRadius + 1); + int startY = Math.max(0, playerY - viewUpRadius); + int endY = Math.min(world.length, playerY + viewDownRadius + 1); + + StringBuilder[] lines = new StringBuilder[(endY - startY) * 25 + 1]; + for (int i = 0; i < lines.length; i++) { + lines[i] = new StringBuilder(); + } + + int counter = 0; + for (int y = startY; y < endY; y++) { + for (int x = startX; x < endX; x++) { + List blocks = world[y][x]; + String sprite = SpriteCombiner.combineSprites(blocks.stream() + .map(block -> spriteList.getSprite(block.getSprite()).getSprite()) + .toArray(String[]::new)); + + String[] spriteLines = sprite.split("\n"); + + for (int i = 0; i < spriteLines.length; i++) { + lines[counter * 25 + i].append(spriteLines[i]); + } + } + counter++; + } + + for (StringBuilder line : lines) { + main.append(line.toString()); + main.append("\n"); + } + + System.out.println(main); + } +} diff --git a/src/main/java/cz/jzitnik/tui/Sprite.java b/src/main/java/cz/jzitnik/tui/Sprite.java new file mode 100644 index 0000000..faef08b --- /dev/null +++ b/src/main/java/cz/jzitnik/tui/Sprite.java @@ -0,0 +1,6 @@ +package cz.jzitnik.tui; + +public abstract class Sprite> { + public abstract String getSprite(); + public abstract String getSprite(E key); +} diff --git a/src/main/java/cz/jzitnik/tui/SpriteCombiner.java b/src/main/java/cz/jzitnik/tui/SpriteCombiner.java new file mode 100644 index 0000000..68ff754 --- /dev/null +++ b/src/main/java/cz/jzitnik/tui/SpriteCombiner.java @@ -0,0 +1,67 @@ +package cz.jzitnik.tui; + +public class SpriteCombiner { + public static String combineSprites(String[] sprites) { + if (sprites == null || sprites.length == 0) { + return ""; + } + + String combinedSprite = sprites[0]; + + for (int i = 1; i < sprites.length; i++) { + combinedSprite = combineTwoSprites(combinedSprite, sprites[i]); + } + + return combinedSprite; + } + + + private static String combineTwoSprites(String sprite1, String sprite2) { + String[] rows1 = sprite1.split("\n"); + String[] rows2 = sprite2.split("\n"); + + StringBuilder combinedSprite = new StringBuilder(); + + for (int i = 0; i < 25; i++) { + String row1 = rows1[i]; + String row2 = rows2[i]; + StringBuilder combinedRow = new StringBuilder(); + + int cursor1 = 0; + int cursor2 = 0; + + while (cursor2 < row2.length()) { + String color1 = extractColorCode(row1, cursor1); + char pixel1 = row1.charAt(cursor1 + color1.length()); + String color2 = extractColorCode(row2, cursor2); + char pixel2 = row2.charAt(cursor2 + color2.length()); + + if (color2.equals("\033[0m") && pixel2 == ' ') { + combinedRow.append(color1).append(pixel1); + } else { + combinedRow.append(color2).append(pixel2); + } + + cursor1 += color1.length() + 1; + cursor2 += color2.length() + 1; + } + + combinedSprite.append(combinedRow).append("\n"); + } + + return combinedSprite.toString(); + } + private static String extractColorCode(String row, int index) { + StringBuilder colorCode = new StringBuilder(); + + if (row.charAt(index) == '\033') { + while (index < row.length() && row.charAt(index) != 'm') { + colorCode.append(row.charAt(index)); + index++; + } + colorCode.append('m'); + } + + return colorCode.toString(); + } +} diff --git a/src/main/java/cz/jzitnik/tui/SpriteList.java b/src/main/java/cz/jzitnik/tui/SpriteList.java new file mode 100644 index 0000000..c482847 --- /dev/null +++ b/src/main/java/cz/jzitnik/tui/SpriteList.java @@ -0,0 +1,33 @@ +package cz.jzitnik.tui; + +import java.util.EnumMap; +import java.util.HashMap; + +public class SpriteList> { + private final EnumMap sprites; + + // Constructor that takes an Enum class and a HashMap + public SpriteList(Class enumClass, HashMap 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"); + } + Sprite value = initialMap.get(key); + sprites.put(key, value); + } + } + + public Sprite getSprite(E key) { + return sprites.get(key); + } + + public void setSprite(E key, Sprite value) { + if (!sprites.containsKey(key)) { + throw new IllegalArgumentException("Invalid key: " + key); + } + sprites.put(key, value); + } +} diff --git a/src/main/resources/textures/bedrock.ans b/src/main/resources/textures/bedrock.ans new file mode 100644 index 0000000..d4e4ad9 --- /dev/null +++ b/src/main/resources/textures/bedrock.ans @@ -0,0 +1,26 @@ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + \ No newline at end of file diff --git a/src/main/resources/textures/dirt.ans b/src/main/resources/textures/dirt.ans new file mode 100644 index 0000000..887e98a --- /dev/null +++ b/src/main/resources/textures/dirt.ans @@ -0,0 +1,26 @@ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒ +▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒ + \ No newline at end of file diff --git a/src/main/resources/textures/grass.ans b/src/main/resources/textures/grass.ans new file mode 100644 index 0000000..7d566f6 --- /dev/null +++ b/src/main/resources/textures/grass.ans @@ -0,0 +1,26 @@ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒░▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▓▒▒▒▒▒▒░░░▒▒▓▒▒░▒▓▓▒▒▒▒░▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒░▒▒▒▒▒▒ +▒▒▒▒▒▒▓▒▒▒▒▒▒░▒▒▒▒░▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▒▒▒▓▓▒▒░▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▓▓▓░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▓▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒ +▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒░▒░░░▒▒ +▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒░░░▒▒▒░░░▒▒▒▒▒▒▒░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▓▒▒ +▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒ + \ No newline at end of file diff --git a/src/main/resources/textures/steve.ans b/src/main/resources/textures/steve.ans new file mode 100644 index 0000000..21f96ed --- /dev/null +++ b/src/main/resources/textures/steve.ans @@ -0,0 +1,23 @@ +                          +        ▒▒▒▒▒▒▒▒▒▒        +       ▒ ▓▒▓▒▒▓▒▓ ▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒ ░░░░ ▒▒▒       +          ░▒▒▒▒▒          +           ▓▒▒▒           +                          + ▒▓▓▒▒▓            ▒▒▒▒▒▒ + ▒▓▓▒▒▓            ▒▒▒▒▒▒ + ▒▒▒▒▒▓            ▒▒▒▒▒▒ + ▒▒▒▒▓▓            ▒▒▒▒▒▒ + ▒▒▒▒▒▓▒░░░░░░░░   ▒▒▒▒▒▒ + ▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒░ ▒▒▒▒▓▒ +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▒▒▒▒▒▒▒▒▒▒▒▒       +       ▓▓▓▓▓▓▓▓▓▓▓▓       + \ No newline at end of file diff --git a/src/main/resources/textures/stone.ans b/src/main/resources/textures/stone.ans new file mode 100644 index 0000000..c86fd0f --- /dev/null +++ b/src/main/resources/textures/stone.ans @@ -0,0 +1,26 @@ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + \ No newline at end of file