feat: Implemented hotbar

This commit is contained in:
Jakub Žitník 2025-02-19 21:33:08 +01:00
parent 2d53047e75
commit 3d4a453e1b
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
21 changed files with 453 additions and 115 deletions

View File

@ -50,15 +50,15 @@ public class Main {
switch (key) {
case 'a':
game.movePlayerLeft(screenRenderer);
screenRenderer.render(game.getWorld());
screenRenderer.render(game);
break;
case 'd':
game.movePlayerRight(screenRenderer);
screenRenderer.render(game.getWorld());
screenRenderer.render(game);
break;
case ' ':
game.movePlayerUp(screenRenderer);
screenRenderer.render(game.getWorld());
screenRenderer.render(game);
break;
case 'q':
System.out.println("Exiting game...");
@ -77,7 +77,7 @@ public class Main {
// Game loop (rendering the game)
while (isRunning[0]) {
screenRenderer.render(game.getWorld());
screenRenderer.render(game);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {

View File

@ -1,8 +1,12 @@
package cz.jzitnik.game;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.items.ToolVariant;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Getter
@ -11,31 +15,39 @@ public class Block {
private SpriteLoader.SPRITES sprite;
private Optional<Enum> spriteState = Optional.empty();
private boolean ghost = false;
private boolean isMineable = true;
private int hardness = 1;
private Optional<ItemType> tool = Optional.empty();
private List<ToolVariant> toolVariants = new ArrayList<>();
private List<Item> drops = new ArrayList<>();
public Block(String blockId, SpriteLoader.SPRITES sprite) {
this.blockId = blockId;
this.sprite = sprite;
}
public Block(String blockId, SpriteLoader.SPRITES sprite, boolean ghost) {
public Block(String blockId, SpriteLoader.SPRITES sprite, boolean ghost, boolean isMineable) {
this.blockId = blockId;
this.sprite = sprite;
this.ghost = ghost;
this.isMineable = isMineable;
}
public Block(String blockId, SpriteLoader.SPRITES sprite, int hardness) {
this.blockId = blockId;
this.sprite = sprite;
this.hardness = hardness;
}
public Block(String blockId, SpriteLoader.SPRITES sprite, int hardness, ItemType tool) {
public Block(String blockId, SpriteLoader.SPRITES sprite, int hardness, ItemType tool, List<ToolVariant> toolVariants) {
this.blockId = blockId;
this.sprite = sprite;
this.hardness = hardness;
this.tool = Optional.of(tool);
this.toolVariants = toolVariants;
}
public Block(String blockId, SpriteLoader.SPRITES sprite, int hardness, ItemType tool, List<ToolVariant> toolVariants, List<Item> drops) {
this.blockId = blockId;
this.sprite = sprite;
this.hardness = hardness;
this.tool = Optional.of(tool);
this.toolVariants = toolVariants;
this.drops = drops;
}
public void setSpriteState(Enum spriteState) {

View File

@ -1,87 +1,29 @@
package cz.jzitnik.game;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.sprites.Breaking;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.Getter;
import lombok.Setter;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
@Getter
public class Game {
private List<Block>[][] world = new ArrayList[256][512];
@Setter
private Block player;
private boolean mining = false;
private List<Item> inventory;
private Inventory inventory = new Inventory();
private Optional<Item> itemInHand = Optional.empty();
public Game() {
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 512; j++) {
world[i][j] = new ArrayList<>();
}
}
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
player = steveBlock;
Random random = new Random();
int baseHeight = 120; // Base ground level
int[] terrainHeight = new int[512];
// Generate terrain with gradual height variations
terrainHeight[0] = baseHeight;
for (int i = 1; i < 512; i++) {
int heightChange = random.nextInt(3) - 1; // -1, 0, or +1
terrainHeight[i] = Math.max(100, Math.min(140, terrainHeight[i - 1] + heightChange));
}
// Smooth terrain to avoid unnatural peaks and dips
for (int i = 2; i < 510; i++) {
terrainHeight[i] = (terrainHeight[i - 1] + terrainHeight[i] + terrainHeight[i + 1]) / 3;
}
// Generate world blocks based on terrain height
for (int i = 0; i < 512; i++) {
int hillHeight = terrainHeight[i];
world[hillHeight][i].add(new Block("grass", SpriteLoader.SPRITES.GRASS));
// Dirt layers
for (int j = 1; j <= 4; j++) {
if (hillHeight + j < 256) {
world[hillHeight + j][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT));
world[hillHeight + j][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT));
}
}
// Stone layers below dirt
for (int j = hillHeight + 5; j < 250; j++) {
world[j][i].add(new Block("stone", SpriteLoader.SPRITES.STONE, 3));
}
// Bedrock at the bottom
world[255][i].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK));
}
// Fill empty spaces with air
for (List<Block>[] lists : world) {
for (List<Block> list : lists) {
if (list.isEmpty()) {
list.add(new Block("air", SpriteLoader.SPRITES.AIR, true));
}
}
}
// Spawn player at a valid starting point
world[terrainHeight[256] - 1][256].add(steveBlock);
Generation.generateWorld(this);
}
public int calculateHardness(Block block) {
@ -156,11 +98,11 @@ public class Game {
}
int[] cords2 = getPlayerCords();
if (world[cords2[1] + 1][cords2[0]].stream().anyMatch(Block::isGhost)) {
if (world[cords2[1] + 1][cords2[0]].stream().allMatch(Block::isGhost)) {
world[cords2[1] + 1][cords2[0]].add(player);
world[cords2[1]][cords2[0]].remove(player);
screenRenderer.render(world);
screenRenderer.render(this);
}
}).start();
}
@ -172,7 +114,7 @@ public class Game {
Block breakingBlock = new Block("breaking", SpriteLoader.SPRITES.BREAKING);
world[y][x].add(breakingBlock);
screenRenderer.render(world);
screenRenderer.render(this);
int hardness = calculateHardness(world[y][x].stream().filter(block -> !block.isGhost()).toList().get(0));
@ -185,7 +127,7 @@ public class Game {
throw new RuntimeException(e);
}
breakingBlock.setSpriteState(Breaking.BreakingState.SECOND);
screenRenderer.render(world);
screenRenderer.render(this);
try {
Thread.sleep(hardness * 166L);
@ -193,7 +135,7 @@ public class Game {
throw new RuntimeException(e);
}
breakingBlock.setSpriteState(Breaking.BreakingState.THIRD);
screenRenderer.render(world);
screenRenderer.render(this);
try {
Thread.sleep(hardness * 166L);
@ -203,9 +145,28 @@ public class Game {
mining = false;
world[y][x].clear();
world[y][x].add(new Block("air", SpriteLoader.SPRITES.AIR, true));
screenRenderer.render(world);
var blocks = world[y][x];
for (Block block : blocks) {
if (!block.isMineable()) {
continue;
}
if (block.getToolVariants().isEmpty()) {
// Add to inv
block.getDrops().forEach(item -> inventory.addItem(item));
continue;
}
var toolVariants = block.getToolVariants();
if (itemInHand.isPresent() && block.getTool().isPresent() && block.getTool().get().equals(itemInHand.get().getType()) && toolVariants.contains(itemInHand.get().getToolVariant())) {
block.getDrops().forEach(item -> inventory.addItem(item));
}
}
blocks.clear();
blocks.add(new Block("air", SpriteLoader.SPRITES.AIR, true, false));
itemInHand.ifPresent(Item::use);
screenRenderer.render(this);
update(screenRenderer);
}).start();
@ -230,10 +191,11 @@ public class Game {
int endY = data[3];
return
y >= startY && y < endY && x >= startX && x < endX &&
y >= startY && y < endY - 1 && x >= startX && x < endX - 1 &&
!blocks.stream().allMatch(block -> block.getBlockId().equals("air"))
&& distanceX <= 5 && distanceY <= 5
&& !(playerX == x && playerY == y);
&& !(playerX == x && playerY == y)
&& blocks.stream().anyMatch(Block::isMineable);
}
public void update(ScreenRenderer screenRenderer) {
@ -249,7 +211,7 @@ public class Game {
world[cords2[1] + 1][cords2[0]].add(player);
world[cords2[1]][cords2[0]].remove(player);
screenRenderer.render(world);
screenRenderer.render(this);
} else {
break;
}

View File

@ -0,0 +1,111 @@
package cz.jzitnik.game;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.game.items.ItemType;
import cz.jzitnik.game.items.ToolVariant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Generation {
public static void generateWorld(Game game) {
var world = game.getWorld();
initializeWorld(world);
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
game.setPlayer(steveBlock);
int[] terrainHeight = generateTerrain();
populateWorld(world, terrainHeight);
plantTrees(world, terrainHeight);
// Spawn player at a valid starting point
world[terrainHeight[256] - 1][256].add(steveBlock);
}
private static void initializeWorld(List<Block>[][] world) {
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 512; j++) {
world[i][j] = new ArrayList<>();
}
}
}
private static int[] generateTerrain() {
Random random = new Random();
int baseHeight = 120;
int[] terrainHeight = new int[512];
terrainHeight[0] = baseHeight;
for (int i = 1; i < 512; i++) {
int heightChange = random.nextInt(3) - 1;
terrainHeight[i] = Math.max(100, Math.min(140, terrainHeight[i - 1] + heightChange));
}
for (int i = 2; i < 510; i++) {
terrainHeight[i] = (terrainHeight[i - 1] + terrainHeight[i] + terrainHeight[i + 1]) / 3;
}
return terrainHeight;
}
private static void populateWorld(List<Block>[][] world, int[] terrainHeight) {
for (int i = 0; i < 512; i++) {
int hillHeight = terrainHeight[i];
world[hillHeight][i].add(new Block("grass", SpriteLoader.SPRITES.GRASS, 1, ItemType.SHOVEL, new ArrayList<>(), List.of(new Item("Dirt block", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_DIRT))));
for (int j = 1; j <= 4; j++) {
if (hillHeight + j < 256) {
world[hillHeight + j][i].add(new Block("dirt", SpriteLoader.SPRITES.DIRT, 1, ItemType.SHOVEL, new ArrayList<>()));
}
}
for (int j = hillHeight + 5; j < 250; j++) {
world[j][i].add(new Block("stone", SpriteLoader.SPRITES.STONE, 15, ItemType.PICKAXE, Arrays.stream(ToolVariant.values()).toList()));
}
world[255][i].add(new Block("bedrock", SpriteLoader.SPRITES.BEDROCK));
}
for (List<Block>[] lists : world) {
for (List<Block> list : lists) {
if (list.isEmpty()) {
list.add(new Block("air", SpriteLoader.SPRITES.AIR, true, false));
}
}
}
}
private static void plantTrees(List<Block>[][] world, int[] terrainHeight) {
Random random = new Random();
for (int i = 10; i < 502; i += random.nextInt(20) + 20) {
int treeBase = terrainHeight[i];
if (treeBase - 3 < 0) continue;
for (int j = 0; j < 3; j++) {
if (treeBase - j >= 0) {
world[treeBase - j - 1][i].add(new Block("oak_log", SpriteLoader.SPRITES.OAK_LOG, 2, ItemType.AXE, List.of(ToolVariant.IRON)));
}
}
int leafY = treeBase - 4;
for (int layer = 0; layer < 3; layer++) {
int size = 5 - (layer * 2);
int offsetY = leafY - layer;
for (int dx = -size / 2; dx <= size / 2; dx++) {
for (int dy = -size / 2; dy <= size / 2; dy++) {
if (i + dx >= 0 && i + dx < world[0].length && offsetY >= 0) {
world[offsetY][i + dx].add(new Block("oak_leaves", SpriteLoader.SPRITES.OAK_LEAF, 1, ItemType.SHEARS, new ArrayList<>()));
}
}
}
}
}
}
}

View File

@ -0,0 +1,64 @@
package cz.jzitnik.game;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.Item;
import cz.jzitnik.tui.Sprite;
import cz.jzitnik.tui.SpriteList;
import lombok.Getter;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.List;
@Getter
public class Inventory {
private static final int INVENTORY_SIZE_PX = 470;
private List<InventoryItem> items = new ArrayList<>();
public void addItem(Item item) {
if (!item.isStackable()) {
items.add(new InventoryItem(item));
return;
}
var it = items.stream().filter(i -> i.getItem().equals(item)).toList();
if (!it.isEmpty()) {
it.get(0).setAmount(it.get(0).getAmount() + 1);
return;
}
items.add(new InventoryItem(item));
}
public void renderHotbar(StringBuilder buffer, SpriteList spriteList, Terminal terminal) {
int termWidth = terminal.getWidth();
int startLeft = (termWidth / 2) - (INVENTORY_SIZE_PX / 2);
List<Sprite> sprites = items.subList(0, Math.min(9, items.size())).stream().map(items -> spriteList.getSprite(items.getItem().getSprite())).toList();
List<String> strings = sprites.stream().map(Sprite::getSprite).toList();
for (int i = 0; i < 26; i++) {
// Empty left space
buffer.append("\033[0m ".repeat(Math.max(0, startLeft)));
if (i == 0 || i == 25) {
buffer.append("\033[38;5;231;48;5;231m▓".repeat(INVENTORY_SIZE_PX - 2));
} else {
for (int j = 0; j < 9; j++) {
buffer.append("\033[38;5;231;48;5;231m▓".repeat(2));
if (strings.size() <= j) {
buffer.append("\033[0m ".repeat(50));
continue;
}
String x = strings.get(j).split("\n")[i];
buffer.append(x);
}
}
buffer.append("\033[38;5;231;48;5;231m▓".repeat(2));
buffer.append("\033[0m\n");
}
}
}

View File

@ -94,6 +94,6 @@ public class MouseHandler {
screenRenderer.setSelectedBlock(Optional.empty());
}
screenRenderer.render(game.getWorld());
screenRenderer.render(game);
}
}

View File

@ -1,6 +1,7 @@
package cz.jzitnik.game;
import cz.jzitnik.game.sprites.*;
import cz.jzitnik.game.sprites.items.DirtItem;
import cz.jzitnik.tui.Sprite;
import cz.jzitnik.tui.SpriteList;
@ -14,7 +15,13 @@ public class SpriteLoader {
STEVE,
STONE,
BEDROCK,
BREAKING
BREAKING,
OAK_LOG,
OAK_LEAF,
// Items
ITEM_DIRT
}
public static final HashMap<SPRITES, Sprite> SPRITES_MAP = new HashMap<>();
@ -27,6 +34,9 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.STEVE, new Steve());
SPRITES_MAP.put(SPRITES.BEDROCK, new Bedrock());
SPRITES_MAP.put(SPRITES.BREAKING, new Breaking());
SPRITES_MAP.put(SPRITES.OAK_LOG, new OakLog());
SPRITES_MAP.put(SPRITES.OAK_LEAF, new OakLeaf());
SPRITES_MAP.put(SPRITES.ITEM_DIRT, new DirtItem());
}
public static SpriteList<SPRITES> load() {

View File

@ -0,0 +1,18 @@
package cz.jzitnik.game.items;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor
@Getter
@Setter
public class InventoryItem {
private int amount;
private Item item;
public InventoryItem(Item item) {
this.amount = 1;
this.item = item;
}
}

View File

@ -1,22 +1,43 @@
package cz.jzitnik.game.items;
import cz.jzitnik.tui.Sprite;
import cz.jzitnik.game.SpriteLoader;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
import java.util.Optional;
@Getter
@AllArgsConstructor
public class Item {
private String name;
private ItemType type;
private Sprite sprite;
private Optional<ToolVariant> toolVariant = Optional.empty();
private SpriteLoader.SPRITES sprite;
private boolean stackable = true;
private int durability;
private int miningDecrease = 0;
public Item(String name, ItemType type, Sprite sprite, int durability) {
public Item(String name, ItemType type, SpriteLoader.SPRITES sprite) {
this.name = name;
this.type = type;
this.sprite = sprite;
this.durability = durability;
}
public void use() {
durability--;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return stackable == item.stackable &&
durability == item.durability &&
miningDecrease == item.miningDecrease &&
Objects.equals(name, item.name) &&
type == item.type &&
Objects.equals(sprite, item.sprite);
}
}

View File

@ -2,5 +2,8 @@ package cz.jzitnik.game.items;
public enum ItemType {
PICKAXE,
SHOVEL
SHOVEL,
AXE,
SHEARS,
BLOCK
}

View File

@ -0,0 +1,7 @@
package cz.jzitnik.game.items;
public enum ToolVariant {
WOODEN,
STONE,
IRON,
}

View File

@ -6,9 +6,7 @@ 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("\033[0m ".repeat(50));
sprite.append("\n");
}
return sprite.toString();

View File

@ -3,6 +3,8 @@ package cz.jzitnik.game.sprites;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
import java.util.Objects;
public class Breaking extends Sprite {
public enum BreakingState {
FIRST,
@ -15,15 +17,15 @@ public class Breaking extends Sprite {
}
public String getSprite() {
return fix(ResourceLoader.loadResource("breaking/1.ans"));
return fix(Objects.requireNonNull(ResourceLoader.loadResource("breaking/1.ans")));
}
public String getSprite(Enum key) {
return fix(ResourceLoader.loadResource(switch (key) {
return fix(Objects.requireNonNull(ResourceLoader.loadResource(switch (key) {
case BreakingState.FIRST -> "breaking/1.ans";
case BreakingState.SECOND -> "breaking/2.ans";
case BreakingState.THIRD -> "breaking/3.ans";
default -> throw new IllegalStateException("Unexpected value: " + key);
}));
})));
}
}

View File

@ -0,0 +1,20 @@
package cz.jzitnik.game.sprites;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
import java.util.Objects;
public class OakLeaf extends Sprite {
private String fix(String x) {
return x.replaceAll("\033\\[38;5;1;48;5;16m", "\033[0m");
}
public String getSprite() {
return fix(Objects.requireNonNull(ResourceLoader.loadResource("oak_leaf.ans")));
}
public String getSprite(Enum key) {
throw new RuntimeException("Imposible state");
}
}

View File

@ -0,0 +1,14 @@
package cz.jzitnik.game.sprites;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
public class OakLog extends Sprite {
public String getSprite() {
return ResourceLoader.loadResource("oak_log.ans");
}
public String getSprite(Enum key) {
throw new RuntimeException("Imposible state");
}
}

View File

@ -0,0 +1,14 @@
package cz.jzitnik.game.sprites.items;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
public class DirtItem extends Sprite {
public String getSprite() {
return ResourceLoader.loadResource("items/dirt.ans");
}
public String getSprite(Enum key) {
throw new RuntimeException("Imposible state");
}
}

View File

@ -6,7 +6,7 @@ public class ScreenMovingCalculationProvider {
int spriteHeight = 25;
int viewXRadius = (terminalWidth / 2) / spriteWidth;
int viewYRadius = (terminalHeight / 2) / spriteHeight;
int viewYRadius = ((terminalHeight - 30) / 2) / spriteHeight;
// Ensure at least one sprite is visible
viewXRadius = Math.max(viewXRadius, 1);

View File

@ -1,6 +1,7 @@
package cz.jzitnik.tui;
import cz.jzitnik.game.Block;
import cz.jzitnik.game.Game;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@ -35,7 +36,8 @@ public class ScreenRenderer {
return null;
}
public void render(List<Block>[][] world) {
public void render(Game game) {
var world = game.getWorld();
StringBuilder main = new StringBuilder();
main.append("\033[H\033[2J");
@ -67,7 +69,7 @@ public class ScreenRenderer {
endY = Math.max(startY, endY - 1);
}
StringBuilder[] lines = new StringBuilder[(endY - startY) * 26];
StringBuilder[] lines = new StringBuilder[(endY - startY) * 25 + 1];
for (int i = 0; i < lines.length; i++) {
lines[i] = new StringBuilder();
}
@ -87,25 +89,19 @@ public class ScreenRenderer {
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("\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▓");
for (int j = 0; j < 46; j++) {
stringBuilder.append("\033[0m ");
}
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");
}
for (int i = 0; i < 50; i++) {
stringBuilder.append("\033[38;5;231;48;5;231m▓");
}
stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(50));
stringBuilder.append("\n");
sprites.add(stringBuilder.toString());
@ -113,21 +109,29 @@ public class ScreenRenderer {
String sprite = SpriteCombiner.combineSprites(sprites.toArray(String[]::new));
String[] spriteLines = sprite.split("\n");
for (int i = 0; i < spriteLines.length; i++) {
lines[counter * 25 + i].append(spriteLines[i]);
lines[counter * 25 + i].append(spriteLines[i]).append("\033[0m");
}
}
counter++;
}
for (StringBuilder line : lines) {
main.append(line.toString());
// World
for (int i = 0; i < lines.length; i++) {
main.append(lines[i]);
if (i < lines.length - 1) {
main.append("\n");
}
}
// Empty space between world and hotbar
main.append("\n\n\n");
game.getInventory().renderHotbar(main, spriteList, terminal);
System.out.println(main);
}
}

View File

@ -0,0 +1,26 @@
                                                  
                      ▒▒▒▒▒▒▒                     
                  ▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒                 
              ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒             
          ▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒         
      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒     
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒░░░  
   ▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░  
   ▒▓▓▒░░▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒░▒▒▒░░░▒▒░░  
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▓░░▒░░░▒░░▒░░▒  
   ▒░░▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒░░░▒▒▒░░▓░░░░▒▒  
   ▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒░░▒░░▒░▒░░░▒░░░▒░▒░░░░░░▒░▒▒  
   ▒▒▒▒▒▒▓▒▒░░░░░▒▓▒▒▒░▒▒▒▒░▒▒░▒▒▒░░░░░▒▒▒░░░▒░░  
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▓▒▒▒░▒▒░▒▒▒░▒▒▒░░░░▒▒  
   ▒▒▒░▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒░▒▒▒▓░░░░░▒▒▒▒░▒▒▒▓░░  
   ▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░▒▒▒▒░░░░░░▒░▒▒▒░░▒  
   ▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒░▒░░░▒░▒▒▒░░░░▒▒▒  
   ▒▒▒▒▒▒▒▒▒▒▒▓░▒▒▒▒▒▒▒░▒░▒░░░░░░░▒▒▒▓▒░░░░░▒▒▒▒  
   ▒▒▒░▒▒▒▒▒▒▒▒▓▒▒▒░▒▒▒▒▒░░▒░░▒▒▒▒░░░░░▒▒░░░░▒░░  
      ▒▒▒▒░▒▒▒▒▒▒▒▒▒░▒▒▓▒░░▒░░▒▒▒▒▒▒▒░▒░░▒▒▒▒     
          ▒▓▓░▒▒▒▒░░▒▒▒▒░▒▒░▒▒░▒░░░░░▒▒░░         
              ▒▒▒▒▒▒▒▒▒▒▒░░▒░░▒░░░▓░░             
                  ▒▒▒▒▒▒▒░▒░░░▒▒░                 
                      ▒▒░▒▒░▒                     


View File

@ -0,0 +1,26 @@
                         ░                        
                         ░                        
                         ▒                        
                         ▒                        
                      ░                           
                      ░  ▒                        
                      ░  ▒                        
                      ░  ▒                        
                      ░  ▒                        
                      ░  ▒                        
   ▒                     ▒                        
    ░░░                  ░  ▒         ░░░         
                         ░  ▒                     
                         ░                     ░  
                         ░░░░                  ░  
                      ░  ▒                     ░  
                         ░                        
                      ░  ░                        
                            ▒                     
                      ░  ▓                        
                      ░  ▓                        
                      ░  ▓                     ░  
                            ▒                     
                            ▒                     
                                                  


View File

@ -0,0 +1,26 @@
▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▒▒▓▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▓▓▓▓▒▒▒
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒ ▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒░▒▒▒▒▒▒░░░░▒░▒▒▒░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒░▒░▒▒▒▒▒▒░░ ░▒░▒▒▒░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▓▒▒▒░░░▒▒▒▒▒▒▒▒░▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒░░░▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒░░░▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒░░▒▒▒░▒▒
▒░░░▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒░░░▒▒▒░░░
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░▒▒▒▒░▒░
▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒░░░
▓▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒░▒▒░▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░
▓▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒░▒▒░▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒░▒▒▒▒▒░░░▒▒▒▒▒▒░▒░
▓▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒ ░░▒▒▒░░░░▒▒▒░▒▒▒▒▒░░░▒▒▒▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒░▒▒
▓▒▒▒▒▒░▒▒▒▒▒░▒▒▒▒▒▒▒▒▒ ░░▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒ ░░▒▒▒▒░░░▒▒▒▒▒▒▒▒░▒▒▒▒▒▒░▒▒
▒▒▒▒░░░▒▒▒▒▒░▒▒▒▒▒▒▒▒▒░▒▒▓▒▒▒░░ ▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░
▒░░░░░░▒▒▒░░░▒▒▒▒▒▒▒▒▒  ░▒▒▒▒░▒░▒▒▒▒▒▒░░░▒▒▒▒▒▒░▒░
▒░░░░▒░▒▒▒░░░▒▒▒▒▒▒▒▒▒  ░▒▒▒▒▒▒░▒▒▒▒▒▒░░░▒▒▒▒▒▒░░▒
▒░░░▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒
▓▒▒▒░░░▒▒▒▒▒▒░░░▒▒▒▒▒▒░▒▒░░░░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒ ░░
▓▒▒▒░░░▒▒▒▒▒▒░▒░▒▒▒▒▒▒░▒▒░░░░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░▒▒
▓▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒░▒▒░▒▒▒▒▒▒▒▒▒░░ ▒▒▒▒▒▒▒▒▒