diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34869ca --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ + +CXXFLAGS=-O2 -Wall -g + +all: main + +mine.o:: mine.h +io.o:: io.h +main.o:: io.h mine.h + +main: main.o io.o mine.o + g++ -o $@ $^ + +clean: + rm -f main main.o io.o mine.o diff --git a/io.cpp b/io.cpp new file mode 100644 index 0000000..828c39d --- /dev/null +++ b/io.cpp @@ -0,0 +1,91 @@ + +#include "io.h" + +#include + +#include +#include +#include +#include +#include + +struct IOBlock { + IOBlock *next; + ssize_t used, size; + unsigned char data[]; +}; + +IOBlock* newIOBlock() { + const ssize_t amount = 512*1024; // avoid fragmentation: 128kb is the standard M_MMAP_THRESHOLD + IOBlock* b = (IOBlock*) malloc(amount); + if (!b) abort(); + b->size = amount - sizeof(*b); + b->used = 0; + b->next = NULL; + return b; +} + +// frees block list +static void blocksToVector(IOBlock *blocks, ssize_t total, std::vector &buf) { + buf.reserve(std::max(total+1, 512*1024)); // try forcing mmap + buf.resize(total); + (&buf.at(0))[total] = '\0'; + ssize_t pos = 0; + IOBlock *prev = 0; + for (IOBlock *cur = blocks; cur; prev = cur, cur = cur->next) { + free(prev); + if (cur->used > 0) { + memcpy(&buf.at(pos), cur->data, cur->used); + pos += cur->used; + } + } + free(prev); +} + +void readAll(int fd, std::vector &buf) { + IOBlock *first = newIOBlock(); + IOBlock *cur = first; + ssize_t total = 0; + for (;;) { + ssize_t avail; + for (;;) { + avail = cur->size - cur->used; + if (0 != avail) break; + cur->next = newIOBlock(); + cur = cur->next; + } + + ssize_t len = read(fd, cur->data + cur->used, avail); + if (0 == len) break; /* eof */ + if (0 > len) { + perror("read failed"); + abort(); + } + cur->used += len; + total += len; + } + blocksToVector(first, total, buf); +} + +void readLine(int fd, std::vector &buf) { + IOBlock *first = newIOBlock(); + IOBlock *cur = first; + ssize_t total = 0; + for (;;) { + if (cur->size == cur->used) { + cur->next = newIOBlock(); + cur = cur->next; + } + + ssize_t len = read(fd, cur->data + cur->used, 1); + if (0 == len) break; /* eof */ + if (0 > len) { + perror("read failed"); + abort(); + } + if (cur->data[cur->used] == '\n' || cur->data[cur->used] == '\r') break; + cur->used += 1; + total += 1; + } + blocksToVector(first, total, buf); +} diff --git a/io.h b/io.h new file mode 100644 index 0000000..6470004 --- /dev/null +++ b/io.h @@ -0,0 +1,9 @@ +#ifndef _MINE_IO_H +#define _MINE_IO_H _MINE_IO_H + +#include + +void readAll(int fd, std::vector &buf); +void readLine(int fd, std::vector &buf); + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..54d21fa --- /dev/null +++ b/main.cpp @@ -0,0 +1,130 @@ + +#include "io.h" +#include "mine.h" + +#include + +#include +#include +#include +#include +#include +#include + +void runMoves(Mine &m, const char *moves) { + while (*moves) { +// m.show(std::cout); +// std::cout << *moves << "\n"; + switch(*moves++) { + case 'L': m.move(Mine::Left); break; + case 'R': m.move(Mine::Right); break; + case 'U': m.move(Mine::Up); break; + case 'D': m.move(Mine::Down); break; + case 'W': m.move(Mine::Wait); break; + case 'A': m.move(Mine::Abort); break; + default: break; + } + } +} + +void simulate(Mine &m, const char *moves) { + runMoves(m, moves); + m.show(std::cout); +} + +void run_testcase(std::vector &buf) { + Mine m(buf); + char *sbuf = (char*) &buf.at(0); + sbuf = strchr(sbuf, '!'); + if (!sbuf) { + std::cerr << "Not a testcase, missing '!'\n"; + exit(1); + } + char *moves = sbuf+1; + char *score = strchr(moves, '\n'); + char *state = 0; + if (score) { + if (score[-1] == '\r') score[-1] = '\0'; + *score++ = '\0'; + state = strchr(score, '\n'); + if (state) { + if (state[-1] == '\r') state[-1] = '\0'; + *state++ = '\0'; + sbuf = strchr(state, '\n'); + if (sbuf) { + // remove trailing newline + if (sbuf[-1] == '\r') sbuf[-1] = '\0'; + *sbuf++ = '\0'; + } + } + } + runMoves(m, moves); + m.show(std::cout); + if (score) { + int n = atoi(score); + if (n != m.score()) { + std::cerr << "Testcase score missmatch: expected " << n << "\n"; + exit(10); + } + } + if (state) { + if (0 == strcasecmp(state, "Win")) { + if (m.state() != Mine::Won) { + std::cerr << "Testcase result missmatch: expected to win\n"; + exit(10); + } + } else if (0 == strcasecmp(state, "Alive")) { + if (m.state() != Mine::Alive) { + std::cerr << "Testcase result missmatch: expected to be still mining\n"; + exit(10); + } + } else if (0 == strcasecmp(state, "Aborted")) { + if (m.state() != Mine::Alive && m.state() != Mine::Aborted) { + std::cerr << "Testcase result missmatch: expected to abort (alive counts too)\n"; + exit(10); + } + } else if (0 == strcasecmp(state, "Broken")) { + if (m.state() != Mine::Lost) { + std::cerr << "Testcase result missmatch: expected to break robot\n"; + exit(10); + } + } else { + std::cerr << "Unknown expected testcase result '" << state << "'\n"; + exit(1); + } + } +} + +int main(int argc, char **argv) { + if (argc == 3) { + // simulate: [file] [moves] + int fd = open(argv[1], O_RDONLY); + if (fd < 0) { + std::cerr << "opening map '" << argv[1] << "' failed\n"; + exit(1); + } + std::vector buf; + readAll(fd, buf); + close(fd); + Mine m(buf); + buf.clear(); + + simulate(m, argv[2]); + } else if (argc == 2) { + // simulate: [file with !moves on bottom] + int fd = open(argv[1], O_RDONLY); + if (fd < 0) { + std::cerr << "opening map '" << argv[1] << "' failed\n"; + exit(1); + } + std::vector buf; + readAll(fd, buf); + close(fd); + run_testcase(buf); + } else { + // solve puzzle, read map from stdin + std::cerr << "Solving not implemented yet\n"; + exit(1); + } + return 0; +} diff --git a/mine.cpp b/mine.cpp new file mode 100644 index 0000000..a36ed23 --- /dev/null +++ b/mine.cpp @@ -0,0 +1,282 @@ +#include "mine.h" + +#include + +#include +#include + +static const char cellSymbols[] = "#R*\\L. "; +static const char cellOpenSymbols[] = "#R*\\O. "; +static const char moveSymbols[] = "LRUDWA"; + +static inline char cellSymbol(Mine::Cell c, bool liftOpen) { + return (liftOpen ? cellOpenSymbols : cellSymbols)[(int) c]; +} +static inline char moveSymbol(Mine::Move m) { return moveSymbols[(int) m]; } + +Mine::Mine(const std::vector& data) +: m_cells(0), m_newRocks(0), m_width(0), m_height(0) +, m_robot(), m_lifts(), m_lambdas() +, m_nLambdas(0), m_nMoves(0), m_state(Aborted) { + if (0 == data.size()) return; + int linewidth = 0; + size_t i = 0; + if ('!' == data.at(i)) { + // skip solution on first line + for (; i < data.size() && '\n' != data[i]; ++i) ; + } + for (; i < data.size(); ++i) { + unsigned char c = data.at(i); + switch (c) { + case '#': + case 'R': + case '*': + case '\\': + case 'L': + case '.': + case ' ': + // normal cell + ++linewidth; + break; + case 'O': abort(); // no open lift allowed + break; + case '\n': + ++m_height; + m_width = std::max(m_width, linewidth); + linewidth = 0; + break; + case '\r': + // ignore + break; + case '!': // test case data, skip to enc + if (i > 0 && data[i-1] != '\n') ++m_height; + i = data.size(); + break; + } + } + if (i == data.size() && data.back() != '\n') ++m_height; + + m_newRocks = new bool[m_width]; + m_cells = new iCell[m_width * m_height]; + int x = 0, y = m_height-1; + iCell *cell = &m_cells[y*m_width]; + for (size_t i = 0; i < data.size(); ++i) { + unsigned char c = data.at(i); + switch (c) { + case '#': *cell++ = Wall; ++x; break; + case 'R': + *cell++ = Robot; + if (m_robot.valid()) abort(); + m_robot.set(x, y); + ++x; + break; + case '*': *cell++ = Rock; ++x; break; + case '\\': + *cell++ = Lambda; + m_lambdas.push_back(Position(x, y)); + ++x; + break; + case 'L': *cell++ = Lift; + m_lifts.push_back(Position(x, y)); + ++x; + break; + case '.': *cell++ = Earth; ++x; break; + case ' ': *cell++ = Space; ++x; break; + case '\n': + for ( ; x < m_width; ++x) *cell++ = Space; + if (0 == y) { + i = data.size(); + } else { + x = 0; + --y; + cell = &m_cells[y*m_width]; + } + break; + case '\r': + // ignore + break; + case '!': // test case data, skip to enc + i = data.size(); + break; + } + } + for ( ; x < m_width; ++x) *cell++ = Space; + if (!m_robot.valid()) abort(); + m_state = Alive; +} + +Mine::Mine(const Mine& other) +: m_cells(0), m_newRocks(0), m_width(other.m_width), m_height(other.m_height) +, m_robot(other.m_robot), m_lifts(other.m_lifts), m_lambdas(other.m_lambdas) +, m_nLambdas(other.m_nLambdas), m_nMoves(other.m_nMoves), m_state(other.m_state) { + m_newRocks = new bool[m_width]; + size_t n = m_width * m_height; + m_cells = new iCell[n]; + memcpy(m_cells, other.m_cells, sizeof(iCell) * n); +} + +Mine& Mine::operator=(const Mine& other) { + m_width = other.m_width; m_height = other.m_height; + m_robot = other.m_robot; m_lifts = other.m_lifts; m_lambdas = other.m_lambdas; + m_nLambdas = other.m_nLambdas; m_nMoves = other.m_nMoves; m_state = other.m_state; + + m_newRocks = new bool[m_width]; + size_t n = m_width * m_height; + m_cells = new iCell[n]; + memcpy(m_cells, other.m_cells, sizeof(iCell) * n); + + return *this; +} + +Mine::~Mine() { + delete[] m_cells; m_cells = 0; m_width = 0; m_height = 0; m_state = Aborted; + delete[] m_newRocks; m_newRocks = 0; +} + +inline static void delPos(std::vector& v, const Position& p) { + std::vector::iterator i = std::find(v.begin(), v.end(), p); + if (i == v.end()) abort(); + *i = v.back(); + v.pop_back(); +} + +void Mine::move(Move m) { + if (m_state != Alive) return; + if (m_nMoves >= m_width*m_height) { + m_state = Aborted; + return; + } + int n = 1; + switch (m) { + case Left: + n = -1; + /* fall through */ + case Right: + switch (at(m_robot.right(n))) { + case Lambda: + // collect lambda + ++m_nLambdas; + delPos(m_lambdas, m_robot.right(n)); + /* fall through */ + case Earth: + case Space: + set(m_robot, Space); + m_robot = m_robot.right(n); + set(m_robot, Robot); + break; + case Rock: + if (Space == at(m_robot.right(2*n))) { + set(m_robot.right(2*n), Rock); + set(m_robot, Space); + m_robot = m_robot.right(n); + set(m_robot, Robot); + } + break; + case Wall: break; + case Robot: break; // "impossible" + case Lift: + if (isLiftOpen()) { + set(m_robot, Space); + m_robot = m_robot.right(n); + set(m_robot, Robot); + m_state = Won; + } + } + break; + case Down: + n = -1; + /* fall through */ + case Up: + switch (at(m_robot.top(n))) { + case Lambda: + // collect lambda + ++m_nLambdas; + delPos(m_lambdas, m_robot.top(n)); + /* fall through */ + case Earth: + case Space: + set(m_robot, Space); + m_robot = m_robot.top(n); + set(m_robot, Robot); + break; + case Rock: break; + case Wall: break; + case Robot: break; // "impossible" + case Lift: + if (isLiftOpen()) { + set(m_robot, Space); + m_robot = m_robot.top(n); + set(m_robot, Robot); + m_state = Won; + } + } + break; + case Wait: break; + case Abort: + m_state = Aborted; + return; + } + ++m_nMoves; + + /* move rocks */ + bool topRow = true; + for (int y = m_height; --y >= 0; ) { + iCell *cell = &m_cells[y*m_width]; + if (y == 0) { + if (!topRow) for (int x = 0; x < m_width; ++x, ++cell) { + if (m_newRocks[x]) *cell = Rock; + } + } else { + iCell *cellNext = cell-m_width; // row below + bool nextColRock = false, thisColRock = false, hadRock = false; + for (int x = 0; x < m_width; ++x, ++cell, ++cellNext) { + thisColRock = nextColRock; nextColRock = false; + if (*cell == Rock) { + if (*cellNext == Space) { + thisColRock = true; + *cell = Space; + } else if (*cellNext == Rock) { + if (x+1 < m_width && cellNext[1] == Space && cell[1] == Space) { + *cell = Space; + nextColRock = true; + } else if (x > 0 && cellNext[1] == Space && !hadRock) { + *cell = Space; + if (Alive == m_state && y > 1 && cellNext[-m_width-1] == Robot) m_state = Lost; + m_newRocks[x-1] = true; + } + } else if (*cellNext == Lambda) { + if (x+1 < m_width && cellNext[1] == Space && cell[1] == Space) { + *cell = Space; + nextColRock = true; + } + } + hadRock = true; + } else { + hadRock = false; + } + + if (!topRow && m_newRocks[x]) *cell = Rock; + m_newRocks[x] = thisColRock; + if (Alive == m_state && thisColRock && y > 1 && cellNext[-m_width] == Robot) m_state = Lost; + } + } + topRow = false; + } +} + +void Mine::show(std::ostream &o) { + bool liftOpen = isLiftOpen(); + for (int y = m_height; --y >= 0; ) { + for (int x = 0; x < m_width; ++x) { + o << cellSymbol(at(x, y), liftOpen); + } + o << "\n"; + } + o << "Score: " << score() << "\n"; + switch (m_state) { + case Alive: o << "Still mining\n"; break; + case Won: o << "Mining complete\n"; break; + case Lost: o << "Robot broken\n"; break; + case Aborted: o << "Mining aborted\n"; break; + } +} diff --git a/mine.h b/mine.h new file mode 100644 index 0000000..63dae8d --- /dev/null +++ b/mine.h @@ -0,0 +1,107 @@ +#ifndef _MINE_MINE_H +#define _MINE_MINE_H _MINE_MINE_H + +#include +#include +#include + +class Position { +public: + int x, y; + + inline Position(int x, int y) : x(x), y(y) { } + inline Position() : x(-1), y(-1) { } + + inline bool operator==(const Position &other) { return x == other.x && y == other.y; } + + inline void set(int x, int y) { this->x = x; this->y = y; } + + inline Position left(int n = 1) const { return Position(x-n, y); } + inline Position right(int n = 1) const { return Position(x+n, y); } + inline Position top(int n = 1) const { return Position(x, y+n); } + inline Position bottom(int n = 1) const { return Position(x, y-n); } + + inline Position topleft() const { return Position(x-1, y+1); } + inline Position topright() const { return Position(x+1, y+1); } + inline Position bottomleft() const { return Position(x-1, y-1); } + inline Position bottomright() const { return Position(x+1, y-1); } + + inline bool valid() const { return x >= 0 && y >= 0; } +}; + +class Mine { +public: + enum Cell { Wall=0, Robot, Rock, Lambda, Lift, Earth, Space }; + enum Move { Left=0, Right, Up, Down, Wait, Abort }; + enum State { Alive=0, Won, Lost, Aborted }; + + Mine(const std::vector& data); + Mine(const Mine& other); + Mine& operator=(const Mine& other); + ~Mine(); + + inline int width() const { return m_width; } + inline int height() const { return m_height; } + + inline Cell at(int x, int y) const { + if (x < 0 || y < 0 || x >= m_width || y >= m_height) return Wall; + return (Cell)m_cells[y*m_width+x]; + } + + inline Cell at(const Position &pos) const { + return at(pos.x, pos.y); + } + + inline Cell operator()(int x, int y) const { + return at(x, y); + } + + inline Cell operator()(const Position &pos) const { + return at(pos.x, pos.y); + } + + inline const Position& robot() const { return m_robot; } + inline const std::vector& lifts() const { return m_lifts; } + inline bool isLiftOpen() const { return m_lambdas.size() == 0; } + inline const std::vector& lambdas() const { return m_lambdas; } + + int score() const { + switch (m_state) { + case Lost: + return 25*m_nLambdas - m_nMoves; + case Won: return 75*m_nLambdas - m_nMoves; + case Alive: + case Aborted: return 50*m_nLambdas - m_nMoves; + } + return 0; + } + inline int foundLambdas() const { return m_nLambdas; } + inline int moves() const { return m_nMoves; } + inline State state() const { return m_state; } + + void move(Move m); + + void show(std::ostream &o); + +private: + // no bound checks, writes must be valid. + inline void set(int x, int y, Cell c) { + m_cells[y*m_width+x] = c; + } + inline void set(Position pos, Cell c) { + set(pos.x, pos.y, c); + } + + typedef unsigned char iCell; + iCell *m_cells; + bool *m_newRocks; // tmp row in map update + int m_width, m_height; + + Position m_robot; + std::vector m_lifts, m_lambdas; + + int m_nLambdas, m_nMoves; + State m_state; +}; + +#endif