From 9b80e6eef31b0c852c772ba6552e419856fe0aa9 Mon Sep 17 00:00:00 2001 From: Holden Rohrer Date: Tue, 31 Aug 2021 18:36:27 -0400 Subject: finished in-world portal features --- README | 5 ++ source/app.d | 132 +++++++++++++++++++++++++++-------------------- source/board.d | 111 ++++++++++++++-------------------------- source/lru.d | 63 ++++++++--------------- source/portals.d | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ source/spots.d | 119 +++++++++++++++++++------------------------ 6 files changed, 345 insertions(+), 237 deletions(-) create mode 100644 source/portals.d diff --git a/README b/README index c990be5..e415782 100644 --- a/README +++ b/README @@ -19,3 +19,8 @@ arrow keys are for movement and [space] picks up and sets down rocks. ## License This code is made available under the GPLv3 license. + +## TODO + +Escape key for the save and load text queries +Multiverse!?!? diff --git a/source/app.d b/source/app.d index 1d04fc9..7faaa75 100644 --- a/source/app.d +++ b/source/app.d @@ -2,12 +2,12 @@ import deimos.ncurses; import std.string : toStringz; import core.stdc.locale; import std.format; +import std.stdio; import board; import spots; -import std.stdio; +import portals; -void main() -{ +void main() { setlocale(LC_CTYPE, ""); // ncurses locales are weird auto stdscr = initscr(); @@ -20,19 +20,23 @@ void main() scrollok(stdscr, true); // allows scrolling in up/down arrows nonl(); // "return" goes through input - auto overlay = new OverlaySource(new RandomSource()); - auto board = new Board(overlay); + auto source = new OverlaySource(new RandomSource()); + auto portals = new Portals(source); + auto board = new Board(source); auto disp = new BoardDisplay(board, stdscr); disp.print(); int rocks = 0; - disp.status = "0"; + disp.status = "Press [space] to pick up and set down rocks"; refresh(); - outer: while (true) - { + import std.bigint : BigInt; + + BigInt[][int] portal; + int lastPortalId; + + outer: while (true) { auto c = getch(); - switch (c) - { + switch (c) { case ',': case KEY_UP: disp.up(); @@ -53,16 +57,13 @@ void main() disp.getdims(); break; case ' ': - if (overlay[disp.y, disp.x].isRock()) - { - overlay[disp.y, disp.x] = Spot(' '); + if (source[disp.y, disp.x].isRock()) { + source[disp.y, disp.x] = Spot(' '); rocks++; disp.status = format("%d", rocks); disp.print(); - } - else if (rocks > 0) - { - overlay[disp.y, disp.x] = Spot('*'); + } else if (rocks > 0 && source[disp.y, disp.x].isSpace()) { + source[disp.y, disp.x] = Spot('*'); rocks--; disp.status = format("%d", rocks); disp.print(); @@ -72,38 +73,60 @@ void main() break outer; case 's': auto filename = stdscr.readquery("Save:"); - try - { - auto file = File(filename, "wb"); - file.writefln!"%s %s"(disp.y, disp.x); - file.writefln!"%d"(rocks); - overlay.save(file); - disp.print(); - } - catch (Exception e) - { + if (filename) { + try { + auto file = File(filename, "wb"); + file.writefln!"%s %s"(disp.y, disp.x); + file.writefln!"%d"(rocks); + source.save(file); + portals.save(file); + } catch (Exception e) { + } } break; case 'l': auto filename = stdscr.readquery("Load:"); - try - { - import std.algorithm.iteration : splitter, map; - import std.conv : to; - import std.bigint; + if (filename) { + try { + import std.algorithm.iteration : splitter, map; + import std.conv : to; - auto file = File(filename); - auto coord = file.readln.splitter().map!(to!BigInt); - disp.y = coord.front; - coord.popFront; - disp.x = coord.front; - file.readf!"%d\n"(rocks); - disp.status = format("%d", rocks); - overlay.load(file); + auto file = File(filename); + auto coord = file.readln.splitter().map!(to!BigInt); + disp.y = coord.front; + coord.popFront; + disp.x = coord.front; + file.readf!"%d\n"(rocks); + disp.status = format("%d", rocks); + source.load(file); + portals.load(file); + } catch (Exception e) { + } } - catch (Exception e) - { - printw("exception!"); + disp.print(); + break; + case '/': + auto sp = source[disp.y, disp.x]; + if (sp.isSpace) { + if (rocks >= 10) { + rocks -= 10; + portals.add(disp.y, disp.x); + } + } else if (sp.isPortal) { + rocks += 10; + portals.remove(disp.y, disp.x); + } + disp.status = format("%d", rocks); + disp.print(); + break; + case 'g': + auto sp = source[disp.y, disp.x]; + if (sp.isPortal) { + auto dest = portals.get(sp.id).pair; + if (dest != null) { + disp.y = dest.y; + disp.x = dest.x; + } } disp.print(); break; @@ -113,8 +136,9 @@ void main() } } -string readquery(WINDOW* stdscr, string query) -{ +immutable char ESC = 27; + +string readquery(WINDOW* stdscr, string query) { import std.range : replicate; import std.conv : to; import std.array : appender; @@ -127,31 +151,29 @@ string readquery(WINDOW* stdscr, string query) move(height, cast(int) query.length + 1); scrollok(stdscr, false); + scope (success) + scrollok(stdscr, true); auto build = appender!(char[]); int p = cast(int) query.length + 1; - while (auto ch = getch()) - { + while (auto ch = getch()) { if (ch == '\r' || ch == EOF) break; - if (ch == '\b' || ch == KEY_BACKSPACE || ch == 127) - { - if (build[].length > 0) - { + if (ch == '\b' || ch == KEY_BACKSPACE || ch == 127) { + if (build[].length > 0) { p--; mvaddch(height, p, ' '); build.shrinkTo(build[].length - 1); move(height, p); } - } - else - { + } else if (ch == ESC) { + return null; + } else { build ~= cast(char) ch; addch(ch); p++; } } - scrollok(stdscr, true); return cast(string) build[]; } diff --git a/source/board.d b/source/board.d index 9ec996b..8460f71 100644 --- a/source/board.d +++ b/source/board.d @@ -8,84 +8,70 @@ import std.format; import std.stdio; import spots : Spot, SpotSource; -public class Board -{ +public class Board { private SpotSource _source; // uint seed; // indexed as Spot = Board[y, x] - this(SpotSource source) - { + this(SpotSource source) { _source = source; } - Spot opIndex(BigInt y, BigInt x) - { + Spot opIndex(BigInt y, BigInt x) { return _source[y, x]; } - Spot opIndex(long y, long x) - { + Spot opIndex(long y, long x) { // possible per improvements return opIndex(BigInt(y), BigInt(x)); } - PartialBoard opIndex(BigInt[2] y, BigInt[2] x) - { + PartialBoard opIndex(BigInt[2] y, BigInt[2] x) { return new PartialBoard(y, x, _source); } - PartialBoard opIndex(long[2] y, long[2] x) - { + PartialBoard opIndex(long[2] y, long[2] x) { return opIndex([BigInt(y[0]), BigInt(y[1])], [ BigInt(x[0]), BigInt(x[1]) ]); } - PartialBoard opIndex(BigInt[2] y, BigInt x) - { + PartialBoard opIndex(BigInt[2] y, BigInt x) { return opIndex(y, [x, x + 1]); } - PartialBoard opIndex(long[2] y, long x) - { + PartialBoard opIndex(long[2] y, long x) { return opIndex([BigInt(y[0]), BigInt(y[1])], BigInt(x)); } - PartialBoard opIndex(BigInt y, BigInt[2] x) - { + PartialBoard opIndex(BigInt y, BigInt[2] x) { return opIndex([y, y + 1], x); } - PartialBoard opIndex(long y, long[2] x) - { + PartialBoard opIndex(long y, long[2] x) { return opIndex(BigInt(y), [BigInt(x[0]), BigInt(x[1])]); } BigInt[2] opSlice(size_t dimension)(BigInt x, BigInt y) - if (dimension >= 0 && dimension < 2) - { + if (dimension >= 0 && dimension < 2) { return [x, y]; } long[2] opSlice(size_t dimension)(long x, long y) - if (dimension >= 0 && dimension < 2) - { + if (dimension >= 0 && dimension < 2) { return [x, y]; } // be more generic. instead of long use T where IsIntegral!T } -enum Edge -{ +enum Edge { LEFT, RIGHT, TOP, BOT, } -class BoardDisplay -{ +class BoardDisplay { private Board _board; private WINDOW* _window; private int _height, _width; @@ -93,39 +79,32 @@ class BoardDisplay BigInt x = 0; BigInt y = 0; - this(Board board, WINDOW* window) - { + this(Board board, WINDOW* window) { _board = board; _window = window; getdims(); } - public void getdims() - { + public void getdims() { getmaxyx(_window, _height, _width); } - private void center() - { + private void center() { mvprintw(_height, 0, toStringz(_status ~ " ".replicate(_width - status.length))); move(cast(int) ceil(_height / 2.0), cast(int) ceil(_width / 2.0)); } - @property string status() - { + @property string status() { return _status; } - @property status(string s) - { + @property status(string s) { _status = s; center(); } - private BigInt edge(Edge e) - { // templating?? - switch (e) - { + private BigInt edge(Edge e) { // templating?? + switch (e) { case Edge.LEFT: return BigInt(cast(int) floor(-_width / 2.0)) + x; case Edge.RIGHT: @@ -139,8 +118,7 @@ class BoardDisplay } } - void print() - { + void print() { import std.stdio; import std.datetime; @@ -149,8 +127,7 @@ class BoardDisplay center(); } - void up() - { // the cursor moves up, the map scrolls down, leaving an empty line at top + void up() { // the cursor moves up, the map scrolls down, leaving an empty line at top y--; scrl(-1); mvprintw(0, 0, toStringz(_board[edge(Edge.TOP), @@ -158,8 +135,7 @@ class BoardDisplay center(); } - void down() - { + void down() { y++; scrl(1); mvprintw(_height - 1, 0, toStringz(_board[edge(Edge.BOT) - 1, @@ -168,21 +144,18 @@ class BoardDisplay center(); } - void left() - { + void left() { x--; print(); } - void right() - { + void right() { x++; print(); } } -unittest -{ +unittest { import std.stdio; import std.datetime; import lru : LRUCache; @@ -191,47 +164,37 @@ unittest SysTime starttime = Clock.currTime(); auto random = new RandomSource(); auto source = new OverlaySource(random); - foreach (j; iota(100)) - { - foreach (i; iota(400)) - { + foreach (j; iota(100)) { + foreach (i; iota(400)) { source[BigInt(i), BigInt(0)]; } } writeln(Clock.currTime() - starttime); - foreach (j; iota(100)) - { - foreach (i; iota(400)) - { + foreach (j; iota(100)) { + foreach (i; iota(400)) { random[BigInt(i), BigInt(0)]; } } writeln(Clock.currTime() - starttime); } -class PartialBoard : Board -{ +class PartialBoard : Board { private BigInt[2] _yrange, _xrange; - this(BigInt[2] y, BigInt[2] x, SpotSource source) - { + this(BigInt[2] y, BigInt[2] x, SpotSource source) { _yrange = y; _xrange = x; super(source); } - override Spot opIndex(BigInt y, BigInt x) - { + override Spot opIndex(BigInt y, BigInt x) { return _source[y + _yrange[0], x + _xrange[0]]; } - override string toString() - { + override string toString() { auto strBuilder = appender!string; strBuilder.reserve(cast(ulong)((_yrange[1] - _yrange[0] + 2) * (_xrange[1] - _xrange[0] + 1))); - foreach (y; iota(_yrange[0], _yrange[1])) - { - foreach (x; iota(_xrange[0], _xrange[1])) - { + foreach (y; iota(_yrange[0], _yrange[1])) { + foreach (x; iota(_xrange[0], _xrange[1])) { strBuilder.put(_source[y, x].contents); } strBuilder.put("\n"); diff --git a/source/lru.d b/source/lru.d index 6d802df..2e8da73 100644 --- a/source/lru.d +++ b/source/lru.d @@ -1,22 +1,18 @@ import std.array : appender; -private struct Node(T) -{ +private struct Node(T) { Node!T* prev; Node!T* next; T data; } -private struct List(T) -{ +private struct List(T) { Node!T* head; Node!T* tail; size_t length; - invariant () - { - if (length > 0) - { + invariant () { + if (length > 0) { assert(head !is null); assert(tail !is null); } @@ -37,8 +33,7 @@ private struct List(T) } }*/ - void remove(Node!T* n) - { + void remove(Node!T* n) { if (n.next) n.next.prev = n.prev; if (n.prev) @@ -50,8 +45,7 @@ private struct List(T) length--; } - void push(Node!T* n) - { + void push(Node!T* n) { n.prev = null; n.next = head; head = n; @@ -63,12 +57,10 @@ private struct List(T) } Node!T* pop() - in - { + in { assert(tail); } - do - { + do { Node!T* node = tail; remove(node); return node; @@ -76,8 +68,7 @@ private struct List(T) } -unittest -{ +unittest { List!int l; Node!int first; l.push(&first); @@ -95,60 +86,49 @@ unittest l.pop(); } -class LRUCache(From, To) -{ +class LRUCache(From, To) { private size_t _cap; - private struct Record - { + private struct Record { To val; From key; } private Node!Record*[From] map; private List!Record queue; - this(size_t cap) - { + this(size_t cap) { _cap = cap; } - private void checksz() - { - if (queue.length > _cap) - { + private void checksz() { + if (queue.length > _cap) { auto node = queue.pop(); map.remove(node.data.key); } } - bool opBinaryRight(string op : "in")(From key) - { + bool opBinaryRight(string op : "in")(From key) { return cast(bool)(key in map); } To opIndex(From f) - in - { + in { assert(f in map); } - do - { + do { auto p = map[f]; queue.remove(p); queue.push(p); return p.data.val; } - void opIndexAssign(To t, From f) - { + void opIndexAssign(To t, From f) { auto p = f in map; Node!Record* node; bool alloc = p is null; - if (!alloc) - { + if (!alloc) { queue.remove(*p); node = *p; - } - else + } else node = new Node!Record(); node.data.val = t; node.data.key = f; @@ -159,8 +139,7 @@ class LRUCache(From, To) } } -unittest -{ +unittest { auto cache = new LRUCache!(int, int)(2); cache[0] = 1; cache[4] = 2; diff --git a/source/portals.d b/source/portals.d new file mode 100644 index 0000000..7bb5dc2 --- /dev/null +++ b/source/portals.d @@ -0,0 +1,152 @@ +import std.bigint : BigInt; +import spots : OverlaySource, Spot; +import std.stdio; +import std.format; + +struct Portal { + BigInt y; + BigInt x; + Portal* pair; + this(BigInt y, BigInt x) { + this.y = y; + this.x = x; + } + + void pairWith(ref Portal p) { + this.pair = &p; + p.pair = &this; + } + + void buildMap(ref int[Portal* ] idMap, ref int highId) { + idMap[&this] = highId++; + } + + string toString(int[Portal* ] idMap) + in { + assert(pair in idMap); + } + do { + return format!`%s, %s: %d`(y, x, idMap[pair]); + } + + void fromString(string s, Portal[int] portalsById) { + import std.conv : to; + import std.algorithm : until, findSkip; + import std.array : array; + + this.y = to!BigInt(s.until(',').array); + s.findSkip(", "); + this.x = to!BigInt(s.until(':').array); + s.findSkip(": "); + int id; + s.formattedRead!"%d"(id); + auto p = id in portalsById; + if (p) { + p.pairWith(this); // this is a REALLY big bug. Portal() is + // being copied by value into the dict, + // so it's not using its final ref. + } + + } +} + +class Portals { + private Portal[int] _portals; + private Portal* _lastPortal; + private int _nextId; + private OverlaySource _overlay; + + this(OverlaySource overlay) { + _overlay = overlay; + } + + int add(BigInt y, BigInt x) { + _portals[_nextId] = Portal(y, x); + if (_lastPortal && _lastPortal.pair == null) { + _lastPortal.pairWith(_portals[_nextId]); + } + _lastPortal = &(_portals[_nextId]); + _overlay[y, x] = Spot('/', _nextId); + return _nextId++; + } + + Portal get(int id) + in { + assert(id in _portals); + } + do { + return _portals[id]; + } + + void remove(BigInt y, BigInt x) { + auto id = _overlay[y, x].id; + auto pair = _portals[id].pair; + if (pair) { + if (_lastPortal.pair == null) { + _lastPortal.pairWith(*pair); + } else { + pair.pair = null; + } + } + _lastPortal = _portals[id].pair; + _portals.remove(id); + _overlay[y, x] = Spot(' '); + } + + immutable string terminator = "END PORTAL DATA\n"; + void save(File f) { + int id = 0; + int[Portal* ] idMap; + foreach (ref key; _portals.byKey()) { + (key in _portals).buildMap(idMap, id); + } + foreach (ref key; _portals.byKey()) { + auto portalRef = key in _portals; + f.writefln!`%d @ %s`(idMap[portalRef], portalRef.toString(idMap)); + } + + f.write(terminator); + if (_lastPortal) + f.writefln!`%d`(idMap[_lastPortal]); + } + + void load(File f) { + _nextId = 0; + foreach (char[] line; f.lines) { + if (line == terminator) + break; + + int id; + string portalText; + line.formattedRead!`%d @ %s`(id, portalText); + _portals[id] = Portal(); + _portals[id].fromString(portalText, _portals); + _nextId++; + } + int id; + if (_nextId > 0) { + f.readf("%d\n", id); + _lastPortal = &(_portals[id]); + } + } + + unittest { + import spots : RandomSource; + import std.stdio; + + auto overlay = new OverlaySource(new RandomSource()); + auto portals = new Portals(overlay); + + portals.add(BigInt(0), BigInt(0)); + portals.remove(BigInt(0), BigInt(0)); + int orig = portals.add(BigInt(0), BigInt(0)); + int sec = portals.add(BigInt(0), BigInt(1)); + assert(*(portals.get(orig).pair) == portals.get(sec)); + portals.remove(BigInt(0), BigInt(1)); + sec = portals.add(BigInt(0), BigInt(2)); + assert(*(portals.get(orig).pair) == portals.get(sec)); + orig = portals.add(BigInt(0), BigInt(3)); + portals.remove(BigInt(0), BigInt(0)); + assert(*(portals.get(orig).pair) == portals.get(sec)); + } +} diff --git a/source/spots.d b/source/spots.d index 49b9c85..a0f549e 100644 --- a/source/spots.d +++ b/source/spots.d @@ -6,94 +6,96 @@ import std.stdio; import lru : LRUCache; import std.traits : isSomeString; -private BigInt positive(BigInt n) -{ +private BigInt positive(BigInt n) { if (n >= 0) return n * 2; else return -n * 2 - 1; } -private BigInt pair(BigInt n, BigInt m) -{ +private BigInt pair(BigInt n, BigInt m) { // https://stackoverflow.com/a/919661 return (positive(n) + positive(m)) * (positive(n) + positive(m) + 1) / 2 + positive(m); } -struct Spot -{ +struct Spot { private char _contents; - this(char c) - { + private int _id; + this(char c) { _contents = c; } + this(char c, int id) { + this(c); + _id = id; + } + import std.format : format, formattedRead; - string toString() - { + string toString() { - return format!`'%c'`(_contents); + return format!`'%c' %d`(_contents, _id); } - void fromString(Range)(auto ref Range s) - { - s.formattedRead!`'%c'`(_contents); + void fromString(Range)(auto ref Range s) { + s.formattedRead!`'%c' %d`(_contents, _id); } - @property contents() - { + @property contents() { return _contents; } - bool opEqual(const Spot s) - { + @property id() { + return _id; + } + + bool opEqual(const Spot s) { return s._contents == _contents; } - bool isRock() - { + bool isSpace() { + return _contents == ' '; + } + + bool isRock() { return _contents == '*'; } + + bool isPortal() { + return _contents == '/'; + } } -interface SpotSource -{ +interface SpotSource { Spot opIndex(BigInt y, BigInt x); void save(File); void load(File); } -class RandomSource : SpotSource -{ +class RandomSource : SpotSource { uint seed; double density = .0025; private LRUCache!(BigInt[2], Spot) cache; - this() - { + this() { seed = unpredictableSeed; cache = new LRUCache!(BigInt[2], Spot)(50000); } - this(uint seed) - { + this(uint seed) { this.seed = seed; } - this(uint seed, double density) - { + this(uint seed, double density) { this.density = density; this(seed); } - private uint hash(BigInt n) - { + private uint hash(BigInt n) { CRC32 crc; crc.start(); crc.put((cast(ubyte*)&seed)[0 .. seed.sizeof]); - foreach (i; iota(n.ulongLength)) - { + foreach (i; iota(n.ulongLength)) { auto digit = n.getDigit(i); crc.put((cast(ubyte*)&digit)[0 .. digit.sizeof]); } @@ -102,8 +104,7 @@ class RandomSource : SpotSource return hash; } - public Spot opIndex(BigInt y, BigInt x) - { + public Spot opIndex(BigInt y, BigInt x) { if ([y, x] in cache) return cache[[y, x]]; auto roll = cast(double) hash(pair(y, x)) / uint.max; @@ -116,44 +117,35 @@ class RandomSource : SpotSource return ret; } - public void save(File f) - { + public void save(File f) { f.writefln!"%d %f"(seed, density); } - public void load(File f) - { + public void load(File f) { f.readf!"%d %f\n"(seed, density); cache = new LRUCache!(BigInt[2], Spot)(50000); } } -class OverlaySource : SpotSource -{ +class OverlaySource : SpotSource { private SpotSource _base; private Spot[BigInt[2]] _overlay; immutable private string terminator = "END OVERLAY\n"; - this(SpotSource base) - { + this(SpotSource base) { _base = base; } - void opIndexAssign(Spot spot, BigInt y, BigInt x) - { + void opIndexAssign(Spot spot, BigInt y, BigInt x) { Spot* p = [y, x] in _overlay; - if (p !is null && spot == _base[y, x]) - { + if (p !is null && spot == _base[y, x]) { _overlay.remove([y, x]); - } - else - { + } else { _overlay[[y, x]] = spot; } } - Spot opIndex(BigInt y, BigInt x) - { + Spot opIndex(BigInt y, BigInt x) { Spot* p = [y, x] in _overlay; if (p !is null) return *p; @@ -161,25 +153,21 @@ class OverlaySource : SpotSource return _base[y, x]; } - void save(File f) - { + void save(File f) { _base.save(f); - foreach (coord; _overlay.keys()) - { + foreach (coord; _overlay.keys()) { f.writefln!"%s %s: %s"(coord[0], coord[1], _overlay[coord]); } f.write(terminator); } - void load(File f) - { + void load(File f) { import std.conv : to; import std.algorithm; //_overlay = _overlay.init; _base.load(f); - foreach (char[] line; f.lines) - { + foreach (char[] line; f.lines) { if (line == terminator) break; @@ -189,14 +177,13 @@ class OverlaySource : SpotSource ints.popFront; coord[1] = ints.front; - auto s = new Spot(); - (*s).fromString(line.find(':').find('\'')); - _overlay[coord] = *s; + auto s = Spot(); + s.fromString(line.find(':').find('\'')); + _overlay[coord] = s; } } - unittest - { + unittest { auto base = new RandomSource(0, 0.0); auto src = new OverlaySource(base); src[BigInt(0), BigInt(0)] = Spot('*'); -- cgit