diff options
| author | Holden Rohrer <hr@hrhr.dev> | 2021-08-03 00:40:58 -0400 | 
|---|---|---|
| committer | Holden Rohrer <hr@hrhr.dev> | 2021-08-03 00:40:58 -0400 | 
| commit | 5f974d83677fc6d7ee229e6a2e2720a45677e4e0 (patch) | |
| tree | 6c18ef8169ee4986bee3d7b2dc6f59582d7946da /source | |
created drocks
Diffstat (limited to 'source')
| -rw-r--r-- | source/app.d | 80 | ||||
| -rw-r--r-- | source/board.d | 241 | ||||
| -rw-r--r-- | source/lru.d | 174 | ||||
| -rw-r--r-- | source/spots.d | 145 | 
4 files changed, 640 insertions, 0 deletions
| diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..4006188 --- /dev/null +++ b/source/app.d @@ -0,0 +1,80 @@ +import deimos.ncurses; +import std.string : toStringz; +import core.stdc.locale; +import std.format; +import board; +import spots; + +void main() +{ +    setlocale(LC_CTYPE, ""); // ncurses locales are weird + +    auto stdscr = initscr(); +    scope (exit) +        endwin(); +    cbreak(); // disables line buffering for input +    noecho(); // input doesn't display on-screen +    intrflush(stdscr, true); +    keypad(stdscr, true); // allows arrow keys to work +    scrollok(stdscr, true); // allows scrolling in up/down arrows +    nonl(); // "return" goes through input + +    int x; +    int y; +    getmaxyx(stdscr, y, x); + +    auto overlay = new OverlaySource(new RandomSource()); +    auto board = new Board(overlay); +    auto disp = new BoardDisplay(board, stdscr); +    disp.print(); +    int rocks = 0; +    disp.status = "0"; +    refresh(); + +    outer: while (true) +    { +        auto c = getch(); +        switch (c) +        { +        case ',': +        case KEY_UP: +            disp.up(); +            break; +        case 'o': +        case KEY_DOWN: +            disp.down(); +            break; +        case 'e': +        case KEY_RIGHT: +            disp.right(); +            break; +        case 'a': +        case KEY_LEFT: +            disp.left(); +            break; +        case KEY_RESIZE: +            disp.getdims(); +            break; +        case ' ': +            if (overlay[disp.y, disp.x].isRock()) +            { +                overlay[disp.y, disp.x] = Spot(' '); +                rocks++; +                disp.status = format("%d", rocks); +                disp.print(); +            } +            else if (rocks > 0) +            { +                overlay[disp.y, disp.x] = Spot('*'); +                rocks--; +                disp.status = format("%d", rocks); +                disp.print(); +            } +            break; +        case 'q': +            break outer; +        default: +            continue; +        } +    } +} diff --git a/source/board.d b/source/board.d new file mode 100644 index 0000000..9ec996b --- /dev/null +++ b/source/board.d @@ -0,0 +1,241 @@ +import std.bigint; +import deimos.ncurses; +import std.string : toStringz; +import std.math : floor, ceil; +import std.array : appender; +import std.range; +import std.format; +import std.stdio; +import spots : Spot, SpotSource; + +public class Board +{ +    private SpotSource _source; +    // uint seed; +    // indexed as Spot = Board[y, x] + +    this(SpotSource source) +    { +        _source = source; +    } + +    Spot opIndex(BigInt y, BigInt x) +    { +        return _source[y, x]; +    } + +    Spot opIndex(long y, long x) +    { +        // possible per improvements +        return opIndex(BigInt(y), BigInt(x)); +    } + +    PartialBoard opIndex(BigInt[2] y, BigInt[2] x) +    { +        return new PartialBoard(y, x, _source); +    } + +    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) +    { +        return opIndex(y, [x, x + 1]); +    } + +    PartialBoard opIndex(long[2] y, long x) +    { +        return opIndex([BigInt(y[0]), BigInt(y[1])], BigInt(x)); +    } + +    PartialBoard opIndex(BigInt y, BigInt[2] x) +    { +        return opIndex([y, y + 1], 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) +    { +        return [x, y]; +    } + +    long[2] opSlice(size_t dimension)(long x, long y) +            if (dimension >= 0 && dimension < 2) +    { +        return [x, y]; +    } +    // be more generic. instead of long use T where IsIntegral!T +} + +enum Edge +{ +    LEFT, +    RIGHT, +    TOP, +    BOT, +} + +class BoardDisplay +{ +    private Board _board; +    private WINDOW* _window; +    private int _height, _width; +    private string _status; +    BigInt x = 0; +    BigInt y = 0; + +    this(Board board, WINDOW* window) +    { +        _board = board; +        _window = window; +        getdims(); +    } + +    public void getdims() +    { +        getmaxyx(_window, _height, _width); +    } + +    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() +    { +        return _status; +    } + +    @property status(string s) +    { +        _status = s; +        center(); +    } + +    private BigInt edge(Edge e) +    { // templating?? +        switch (e) +        { +        case Edge.LEFT: +            return BigInt(cast(int) floor(-_width / 2.0)) + x; +        case Edge.RIGHT: +            return BigInt(cast(int) floor(_width / 2.0)) + x; +        case Edge.TOP: +            return BigInt(cast(int) floor(-_height / 2.0)) + y; +        case Edge.BOT: +            return BigInt(cast(int) floor(_height / 2.0)) + y; +        default: +            assert(0); +        } +    } + +    void print() +    { +        import std.stdio; +        import std.datetime; + +        mvprintw(0, 0, toStringz(_board[edge(Edge.TOP) .. edge(Edge.BOT), +                    edge(Edge.LEFT) .. edge(Edge.RIGHT)].toString())); +        center(); +    } + +    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), +                    edge(Edge.LEFT) .. edge(Edge.RIGHT)].toString())); +        center(); +    } + +    void down() +    { +        y++; +        scrl(1); +        mvprintw(_height - 1, 0, toStringz(_board[edge(Edge.BOT) - 1, +                    // necessary because opSlice is exclusive +                    edge(Edge.LEFT) .. edge(Edge.RIGHT)].toString())); +        center(); +    } + +    void left() +    { +        x--; +        print(); +    } + +    void right() +    { +        x++; +        print(); +    } +} + +unittest +{ +    import std.stdio; +    import std.datetime; +    import lru : LRUCache; +    import spots : RandomSource, OverlaySource; + +    SysTime starttime = Clock.currTime(); +    auto random = new RandomSource(); +    auto source = new OverlaySource(random); +    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)) +        { +            random[BigInt(i), BigInt(0)]; +        } +    } +    writeln(Clock.currTime() - starttime); +} + +class PartialBoard : Board +{ +    private BigInt[2] _yrange, _xrange; +    this(BigInt[2] y, BigInt[2] x, SpotSource source) +    { +        _yrange = y; +        _xrange = x; +        super(source); +    } + +    override Spot opIndex(BigInt y, BigInt x) +    { +        return _source[y + _yrange[0], x + _xrange[0]]; +    } + +    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])) +            { +                strBuilder.put(_source[y, x].contents); +            } +            strBuilder.put("\n"); +        } +        return strBuilder.data; +    } +} diff --git a/source/lru.d b/source/lru.d new file mode 100644 index 0000000..6d802df --- /dev/null +++ b/source/lru.d @@ -0,0 +1,174 @@ +import std.array : appender; + +private struct Node(T) +{ +    Node!T* prev; +    Node!T* next; +    T data; +} + +private struct List(T) +{ +    Node!T* head; +    Node!T* tail; +    size_t length; + +    invariant () +    { +        if (length > 0) +        { +            assert(head !is null); +            assert(tail !is null); +        } +    } + +    /*private void check() +    { +        Node!T* cur = head; +        while (true) +        { +            if (cur != head) +                assert(cur.prev); +            if (cur == tail) +                break; +            else +                assert(cur.next); +            cur = cur.next; +        } +    }*/ + +    void remove(Node!T* n) +    { +        if (n.next) +            n.next.prev = n.prev; +        if (n.prev) +            n.prev.next = n.next; +        if (head == n) +            head = head.next; +        if (tail == n) +            tail = tail.prev; +        length--; +    } + +    void push(Node!T* n) +    { +        n.prev = null; +        n.next = head; +        head = n; +        if (tail is null) +            tail = head; +        else +            head.next.prev = head; +        length++; +    } + +    Node!T* pop() +    in +    { +        assert(tail); +    } +    do +    { +        Node!T* node = tail; +        remove(node); +        return node; +    } + +} + +unittest +{ +    List!int l; +    Node!int first; +    l.push(&first); +    assert(l.head == &first); +    assert(l.tail == &first); +    Node!int second; +    l.push(&second); +    assert(l.head == &second); +    assert(l.tail == &first); +    assert(l.pop() == &first); +    assert(l.head == &second); +    assert(l.tail == &second); +    l.push(&first); +    l.pop(); +    l.pop(); +} + +class LRUCache(From, To) +{ +    private size_t _cap; +    private struct Record +    { +        To val; +        From key; +    } + +    private Node!Record*[From] map; +    private List!Record queue; +    this(size_t cap) +    { +        _cap = cap; +    } + +    private void checksz() +    { +        if (queue.length > _cap) +        { +            auto node = queue.pop(); +            map.remove(node.data.key); +        } +    } + +    bool opBinaryRight(string op : "in")(From key) +    { +        return cast(bool)(key in map); +    } + +    To opIndex(From f) +    in +    { +        assert(f in map); +    } +    do +    { +        auto p = map[f]; +        queue.remove(p); +        queue.push(p); +        return p.data.val; +    } + +    void opIndexAssign(To t, From f) +    { +        auto p = f in map; +        Node!Record* node; +        bool alloc = p is null; +        if (!alloc) +        { +            queue.remove(*p); +            node = *p; +        } +        else +            node = new Node!Record(); +        node.data.val = t; +        node.data.key = f; +        queue.push(node); +        map[f] = node; +        if (alloc) +            checksz(); +    } +} + +unittest +{ +    auto cache = new LRUCache!(int, int)(2); +    cache[0] = 1; +    cache[4] = 2; +    cache[5] = 1; +    assert(0 !in cache); +    assert(4 in cache); +    assert(5 in cache); +    assert(cache[5] == 1); // prioritizes +    cache[0] = 1; +    assert(4 !in cache); +} diff --git a/source/spots.d b/source/spots.d new file mode 100644 index 0000000..0f9d8b7 --- /dev/null +++ b/source/spots.d @@ -0,0 +1,145 @@ +import std.bigint; +import std.digest.crc; +import std.random : unpredictableSeed; +import std.range; +import lru : LRUCache; +import deimos.ncurses; + +private BigInt positive(BigInt n) +{ +    if (n >= 0) +        return n * 2; +    else +        return -n * 2 - 1; +} + +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 +{ +    private char _contents; +    this(char c) +    { +        _contents = c; +    } + +    @property contents() +    { +        return _contents; +    } + +    bool opEqual(const Spot s) +    { +        return s._contents == _contents; +    } + +    bool isRock() +    { +        return _contents == '*'; +    } +} + +interface SpotSource +{ +    Spot opIndex(BigInt y, BigInt x); +} + +class RandomSource : SpotSource +{ +    uint seed; +    double density = .0025; +    private LRUCache!(BigInt[2], Spot) cache; + +    this() +    { +        seed = unpredictableSeed; +        cache = new LRUCache!(BigInt[2], Spot)(50000); +    } + +    this(uint seed) +    { +        this.seed = seed; +    } + +    this(uint seed, double density) +    { +        this.density = density; +        this(seed); +    } + +    private uint hash(BigInt n) +    { +        CRC32 crc; +        crc.start(); +        crc.put((cast(ubyte*)&seed)[0 .. seed.sizeof]); +        foreach (i; iota(n.ulongLength)) +        { +            auto digit = n.getDigit(i); +            crc.put((cast(ubyte*)&digit)[0 .. digit.sizeof]); +        } +        auto result = crc.finish(); +        auto hash = *(cast(uint*)&result); +        return hash; +    } + +    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; +        Spot ret; +        if (roll < density) +            ret = Spot('*'); +        else +            ret = Spot(' '); +        cache[[y, x]] = ret; +        return ret; +    } +} + +class OverlaySource : SpotSource +{ +    private SpotSource _base; +    private Spot[BigInt[2]] _overlay; + +    this(SpotSource base) +    { +        _base = base; +    } + +    void opIndexAssign(Spot spot, BigInt y, BigInt x) +    { +        Spot* p = [y, x] in _overlay; +        if (p !is null && spot == _base[y, x]) +        { +            _overlay.remove([y, x]); +        } +        else +        { +            _overlay[[y, x]] = spot; +        } +    } + +    Spot opIndex(BigInt y, BigInt x) +    { +        Spot* p = [y, x] in _overlay; +        if (p !is null) +            return *p; +        else +            return _base[y, x]; +    } + +    unittest +    { +        auto base = new RandomSource(0, 0.0); +        auto src = new OverlaySource(base); +        src[BigInt(0), BigInt(0)] = Spot('*'); +        src[BigInt(0), BigInt(1)] = Spot('*'); +        assert(src[BigInt(0), BigInt(0)] == Spot('*')); +        assert(src[BigInt(0), BigInt(1)] == Spot('*')); +    } +} | 
