@ -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 |
@ -0,0 +1,91 @@ | |||
#include "io.h" | |||
#include <algorithm> | |||
#include <errno.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
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<unsigned char> &buf) { | |||
buf.reserve(std::max<ssize_t>(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<unsigned char> &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<unsigned char> &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); | |||
} |
@ -0,0 +1,9 @@ | |||
#ifndef _MINE_IO_H | |||
#define _MINE_IO_H _MINE_IO_H | |||
#include <vector> | |||
void readAll(int fd, std::vector<unsigned char> &buf); | |||
void readLine(int fd, std::vector<unsigned char> &buf); | |||
#endif |
@ -0,0 +1,130 @@ | |||
#include "io.h" | |||
#include "mine.h" | |||
#include <iostream> | |||
#include <fcntl.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
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<unsigned char> &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<unsigned char> 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<unsigned char> 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; | |||
} |
@ -0,0 +1,282 @@ | |||
#include "mine.h" | |||
#include <algorithm> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
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<unsigned char>& 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<int>(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<Position>& v, const Position& p) { | |||
std::vector<Position>::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; | |||
} | |||
} |
@ -0,0 +1,107 @@ | |||
#ifndef _MINE_MINE_H | |||
#define _MINE_MINE_H _MINE_MINE_H | |||
#include <vector> | |||
#include <list> | |||
#include <ostream> | |||
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<unsigned char>& 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<Position>& lifts() const { return m_lifts; } | |||
inline bool isLiftOpen() const { return m_lambdas.size() == 0; } | |||
inline const std::vector<Position>& 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<Position> m_lifts, m_lambdas; | |||
int m_nLambdas, m_nMoves; | |||
State m_state; | |||
}; | |||
#endif |