import deimos.ncurses; import std.string : toStringz; import core.stdc.locale; import std.format; import board; import spots; import std.stdio; 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 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; 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); } catch (Exception e) { // TODO: log the error in a user-visible way } disp.print(); break; case 'l': auto filename = stdscr.readquery("Load:"); try { import std.algorithm.iteration : splitter, map; import std.conv : to; import std.bigint; 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); } catch (Exception e) { printw("exception!"); } disp.print(); break; default: continue; } } } import std.range.interfaces : InputRange; class Reader : InputRange!char { private int _front; private bool _empty; // Might could be implemented with a larger getnstr this() { _front = getch(); } char moveFront() in { assert(!_empty); } do { auto ret = front(); popFront(); return ret; } @property char front() in { assert(!_empty); } do { return cast(char) _front; } void popFront() in { assert(!_empty); } do { _front = getch(); if (_front == -1) _empty = true; } @property bool empty() { return _empty; } int opApply(scope int delegate(char) run) { for (; !_empty; popFront()) { if (run(front())) return 1; } return 0; } int opApply(scope int delegate(size_t, char) run) { for (size_t i = 0; !_empty; popFront(), i++) { if (run(i, front())) return 1; } return 0; } } string readquery(WINDOW* stdscr, string query) { import std.range : replicate; import std.conv : to; import std.array : appender; int height; int width; getmaxyx(stdscr, height, width); mvprintw(height, 0, toStringz(query ~ " ".replicate(width - query.length))); move(height, cast(int) query.length + 1); echo(); scrollok(stdscr, false); auto build = appender!string; auto reader = new Reader(); while (!reader.empty && reader.front != '\r') { build ~= reader.front; reader.popFront; } noecho(); scrollok(stdscr, true); return build.data; }