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