Skip to content

Instantly share code, notes, and snippets.

@Stagyrite
Created April 26, 2024 11:43
Show Gist options
  • Save Stagyrite/921e5e48465e92ee0fe829b4e32b2c05 to your computer and use it in GitHub Desktop.
Save Stagyrite/921e5e48465e92ee0fe829b4e32b2c05 to your computer and use it in GitHub Desktop.
a level compiler inspired by openroguelike
/*
* a level compiler inspired by openroguelike
* https://GitHub.com/tleguern/openroguelike
*/
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.logging.Logger;
import java.util.stream.IntStream;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.SEVERE;
enum LevelType {
L_CAVE, L_ROOMS, L_STATIC
}
enum Race {
R_DOG('d'), R_HUMAN('@');
private final char character;
Race(char character) {
this.character = character;
}
char getCharacter() {
return this.character;
}
}
enum TileType {
T_DOOR('+'), T_DSTAIR('>'), T_EMPTY(' '), T_USTAIR('<'), T_WALL('=');
private final char character;
TileType(char character) {
this.character = character;
}
char getCharacter() {
return this.character;
}
}
class Cave {
private static final int CAVE_STEP = 3;
private static final int MAX_RATIO = 10;
private static final int MIN_WALL = 5;
private static final int WALL_RATIO = 4;
private final Level level;
Cave(Level level) {
this.level = level;
}
private void caveInit() {
for (var y = 1; y < Level.ROWS - 1; y++) {
for (var x = 1; x < Level.COLUMNS - 1; x++) {
level.setTileType(x, y, randPick());
}
}
for (var y = 0; y < Level.ROWS; y++) {
level.setTileType(0, y, TileType.T_WALL);
level.setTileType(Level.COLUMNS - 1, y, TileType.T_WALL);
}
for (var x = 0; x < Level.COLUMNS; x++) {
level.setTileType(x, 0, TileType.T_WALL);
level.setTileType(x, Level.ROWS - 1, TileType.T_WALL);
}
}
private void caveReduceNoise() {
for (var y = 1; y < Level.ROWS - 1; y++) {
for (var x = 1; x < Level.COLUMNS - 1; x++) {
var nwall = getNWall(x, y);
if (nwall >= MIN_WALL) {
level.setTileType(x, y, TileType.T_WALL);
} else {
level.dig(x, y);
}
}
}
}
void draw() {
caveInit();
IntStream.range(0, CAVE_STEP)
.forEach(x -> caveReduceNoise());
}
private int getNWall(int x, int y) {
var nwall = 0;
for (int iy = -1; iy <= 1; iy++) {
for (int ix = -1; ix <= 1; ix++) {
if (level.isDiggable(x + ix, y + iy)) {
nwall++;
}
}
}
return nwall;
}
private TileType randPick() {
var x = OpenRoguelike.getRandom(MAX_RATIO);
TileType tileType;
if (x < WALL_RATIO) {
tileType = TileType.T_WALL;
} else {
tileType = TileType.T_EMPTY;
}
return tileType;
}
}
class Creature {
private static final String[] NAMES = {"Fido", "Idefix", "Chien", "Rex", "Medor"};
private final Race race;
private String name;
Creature(Race race) {
this.race = race;
if (race == Race.R_DOG) {
name = NAMES[OpenRoguelike.getRandom(NAMES.length)];
}
}
String getName() {
return this.name;
}
Race getRace() {
return this.race;
}
void moveAt(Tile to) {
to.setCreature(this);
}
}
class Display {
private static final Logger LOG = Logger.getLogger("Display");
private final char[][] window = new char[Level.ROWS][Level.COLUMNS];
private void addChar(int x, int y, char c) {
window[y][x] = c;
}
String print(Level level) {
for (int y = 0; y < Level.ROWS; y++) {
for (int x = 0; x < Level.COLUMNS; x++) {
tilePrint(level.getTile(x, y), x, y);
}
}
return refresh();
}
private String refresh() {
var sb = new StringBuilder();
for (var y = 0; y < Level.ROWS; y++) {
for (var x = 0; x < Level.COLUMNS; x++) {
sb.append(this.window[y][x]);
}
sb.append('\n');
}
return sb.toString();
}
private void tilePrint(Tile tile, int x, int y) {
var type = tile.getType();
addChar(x, y, type.getCharacter());
var creature = tile.getCreature();
if (creature != null) {
var race = creature.getRace();
addChar(x, y, race.getCharacter());
var debugMessage = String.format("%s @ (%d, %d)", creature.getName(), x, y);
LOG.log(FINEST, debugMessage);
}
}
}
class Level {
static final int COLUMNS = 80;
static final int ROWS = 20;
private final Tile[][] tile = new Tile[ROWS][COLUMNS];
Level() {
for (var y = 0; y < ROWS; y++) {
for (var x = 0; x < COLUMNS; x++) {
tile[y][x] = new Tile();
}
}
}
private void addStair(TileType tileType) {
var x = OpenRoguelike.getRandom(COLUMNS);
var y = OpenRoguelike.getRandom(ROWS);
if (getTileType(x, y) == TileType.T_EMPTY) {
setTileType(x, y, tileType);
} else {
addStair(tileType);
}
}
void dig(int x, int y) {
setTileType(x, y, TileType.T_EMPTY);
}
void generate(LevelType levelType, String arg) throws IOException {
switch (levelType) {
case L_CAVE:
Cave cave = new Cave(this);
cave.draw();
break;
case L_ROOMS:
Rooms rooms = new Rooms(this);
rooms.draw();
break;
case L_STATIC:
LevelStatic levelStatic = new LevelStatic(this);
levelStatic.draw(arg);
break;
}
if (levelType != LevelType.L_STATIC) {
addStair(TileType.T_USTAIR);
addStair(TileType.T_DSTAIR);
}
}
Tile getTile(int x, int y) {
return this.tile[y][x];
}
TileType getTileType(int x, int y) {
return this.tile[y][x].getType();
}
boolean isDiggable(int x, int y) {
return getTileType(x, y) == TileType.T_WALL;
}
void setTileType(int x, int y, TileType type) {
this.tile[y][x].setType(type);
}
}
class LevelStatic {
private static final Logger LOG = Logger.getLogger("LevelStatic");
private final Level level;
LevelStatic(Level level) {
this.level = level;
}
void draw(String filename) throws IOException {
var buf = new char['ـ'];
readFile(buf, filename);
for (var y = 0; y < Level.ROWS; y++) {
for (var x = 0; x < Level.COLUMNS; x++) {
var tile = buf[x + y * Level.COLUMNS];
switch (tile) {
case ' ':
level.dig(x, y);
break;
case '1':
level.setTileType(x, y, TileType.T_WALL);
break;
case '2':
level.setTileType(x, y, TileType.T_DOOR);
break;
case '3':
level.setTileType(x, y, TileType.T_DSTAIR);
break;
case '\0':
case '\r':
case '\n':
break;
default:
LOG.log(SEVERE, "{0}: unknown tile", (int) tile);
break;
}
}
}
}
private void readFile(char[] buf, String filename) throws IOException {
var clazz = getClass();
var stream = clazz.getResourceAsStream(filename);
if (stream == null) {
LOG.log(SEVERE, "{0}: file not found", filename);
} else {
try (var reader = new InputStreamReader(stream, StandardCharsets.US_ASCII)) {
if (reader.read(buf) <= 0) {
LOG.log(SEVERE, "{0}: file is empty", filename);
}
}
}
}
}
class LevelCompiler {
static void write(String filename) throws IOException {
var out = new FileWriter(filename);
try (var writer = new PrintWriter(out)) {
write(writer);
}
}
private static void write(PrintWriter out) throws IOException {
var level = new Level();
level.generate(LevelType.L_CAVE, null);
out.println("");
out.println("static char * level[] = {");
out.println("\"80 20 2 1\"");
out.println("\" c black\"");
out.println("\"# c white\"");
writeLines(level, out);
out.println("\n};");
}
private static void writeLines(Level level, PrintWriter out) {
var line = new char[Level.COLUMNS];
for (var y = 0; y < Level.ROWS; y++) {
if (y != 0) {
out.println(',');
}
for (var x = 0; x < Level.COLUMNS; x++) {
if (level.isDiggable(x, y)) {
line[x] = '#';
} else {
line[x] = ' ';
}
}
out.print("\"");
out.print(line);
out.print("\"");
}
}
}
public class OpenRoguelike {
private static final Logger LOG = Logger.getLogger("OpenRoguelike");
private static final int ARGS_LENGTH = 2;
private static final int CAVE_RATIO = 9;
private static final int DOG_POSITION = 16;
private static final int EOF = -1;
private static final int MAX_RATIO = 10;
private static final int PLAYER_POSITION = 15;
private static final Random random = new Random();
static int getRandom(int bound) {
return random.nextInt(bound);
}
static String generate(Creature dog, LevelType levelType, String arg) throws IOException {
Creature player = new Creature(Race.R_HUMAN);
Level level = new Level();
do {
level.generate(levelType, arg);
} while (level.getTileType(DOG_POSITION, DOG_POSITION) != TileType.T_EMPTY || level.getTileType(PLAYER_POSITION, PLAYER_POSITION) != TileType.T_EMPTY);
player.moveAt(level.getTile(PLAYER_POSITION, PLAYER_POSITION));
dog.moveAt(level.getTile(DOG_POSITION, DOG_POSITION));
var display = new Display();
return display.print(level);
}
static String generate(LevelType levelType, String arg) throws IOException {
var dog = new Creature(Race.R_DOG);
return generate(dog, levelType, arg);
}
static void play() throws IOException {
var console = System.console();
if (console == null) {
LOG.log(SEVERE, "no system console");
} else {
var writer = console.writer();
writer.println(generate(LevelType.L_STATIC, "town.lvl"));
var reader = console.reader();
while (reader.read() != EOF) {
writer.println(generate(randPick(), null));
}
}
}
public static void main(String[] args) throws IOException {
if (args.length > 0) {
if (args.length == ARGS_LENGTH && !"-o".equals(args[0])) {
LOG.log(SEVERE, "usage: java OpenRoguelike [-o output]");
} else {
LevelCompiler.write(args[1]);
}
} else {
play();
}
}
private static LevelType randPick() {
var x = OpenRoguelike.getRandom(MAX_RATIO);
LevelType levelType;
if (x < CAVE_RATIO) {
levelType = LevelType.L_CAVE;
} else {
levelType = LevelType.L_ROOMS;
}
return levelType;
}
}
class Rooms {
private static final int HUNDRED = 100;
private static final int MAX_COUNT = 4;
private static final int TEN = 10;
private final Level level;
private int count;
private int dir;
Rooms(Level level) {
this.level = level;
}
private static int tenPercentOf(int x) {
return TEN * x / HUNDRED;
}
void draw() {
init();
split(0, Level.COLUMNS, 0, Level.ROWS);
}
private void init() {
for (var y = 0; y < Level.ROWS; y++) {
for (var x = 0; x < Level.COLUMNS; x++) {
level.setTileType(x, y, TileType.T_WALL);
}
}
}
private void split(int minx, int x, int miny, int y) {
dir ^= 0x1;
if (count == MAX_COUNT) {
return;
}
int ly;
int lx;
if (dir == 1) {
lx = minx;
int bound = Math.max(1, y - miny);
ly = miny + OpenRoguelike.getRandom(bound);
for (; lx < x; lx++) {
level.dig(lx, ly);
}
lx = 0;
} else {
int ten = tenPercentOf(x);
ly = miny;
lx = ten + OpenRoguelike.getRandom(x - ten * 2);
for (; ly < y; ly++) {
level.dig(lx, ly);
}
ly = 0;
}
count += 1;
if (lx == 0) {
split(minx, x, miny, ly);
} else {
split(minx, lx, miny, y);
}
if (ly == 0) {
split(lx, x, miny, y);
} else {
split(minx, x, ly, y);
}
}
}
class Tile {
private Creature creature;
private TileType type;
Tile() {
this.type = TileType.T_EMPTY;
}
Creature getCreature() {
return this.creature;
}
void setCreature(Creature creature) {
this.creature = creature;
}
TileType getType() {
return this.type;
}
void setType(TileType type) {
this.type = type;
}
}
1111111111111111111111111111111111111111111111111111111111111111111111111111111
1 1
1 1
1 1
1 1
1 1111111111111 1111111111111 1
1 1 1 1 13 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1121111111 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 2 2 2 2 2 1 1
1 1 1 1 1 1 1 1 1
1 1111111111 1111111111111 1111111111111 1
1 1
1 1
1 1
1111111111111111111111111111111111111111111111111111111111111111111111111111111
@kironia
Copy link

kironia commented Apr 26, 2024

There are also levels with rooms, and here's an example.

====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
====== ================== ======================================================
               @          ======================================================
                d                                        > <
================================================================================
================================================================================
================================================================================

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment