#include #include #include #include #include #include #include #include #include #include #include typedef enum { Empty = 0, Floor = 1, Wall = 2 } TileType; typedef enum { Forward = 0, TurnRight = 1, TurnLeft = 2 } Command; typedef enum { FacingUp = 0, FacingRight = 90, FacingDown = 180, FacingLeft = 270 } Facing; typedef enum { Face1, Face2, Face3, Face4, Face5, Face6 } CubeFace; typedef struct { int x; int y; } FaceStartCoord; std::unordered_map bounds; /** * Each face origin on the 2D map */ void initBounds() { bounds[Face1].x = 50; bounds[Face1].y = 0; bounds[Face2].x = 100; bounds[Face2].y = 0; bounds[Face3].x = 50; bounds[Face3].y = 50; bounds[Face4].x = 0; bounds[Face4].y = 100; bounds[Face5].x = 50; bounds[Face5].y = 100; bounds[Face6].x = 0; bounds[Face6].y = 150; } // how many tiles per face const long FACE_SIZE = 50; class Tile { public: Tile(int p_x, int p_y, TileType t) { x = p_x; y = p_y; // if FACE_SIZE = 50 -> coord 75 in the map is coordinate 25 within the tile face_x = x % FACE_SIZE; face_y = y % FACE_SIZE; m_type = t; } Tile() { x = -1; y = -1; face_x = -1; face_y = -1; m_type = TileType::Empty; } void print(); void printFace(); // Coordinates in the 2D map int x; int y; // Coordinates within the face surface (0...FACE_SIZE) int face_x; int face_y; // ID of the cube face this tile sits on CubeFace m_face; // type of data in this tile TileType m_type; }; void Tile::printFace() { if (m_type == TileType::Empty) { std::cout << " "; return; } switch (m_face) { case Face1: std::cout << "1"; break; case Face2: std::cout << "2"; break; case Face3: std::cout << "3"; break; case Face4: std::cout << "4"; break; case Face5: std::cout << "5"; break; case Face6: std::cout << "6"; break; default: throw "no face"; } } class Player { public: Tile* m_loc; int m_facing; void turn(Command c); void walk(Tile* map, int map_w, int map_h); }; Facing intToFacing(int f) { switch (f) { case 0: return FacingUp; case 90: return FacingRight; case 180: return FacingDown; case 270: return FacingLeft; default: throw "Invalid int to facing conversion"; } } void Player::turn(Command c) { switch (c) { case TurnLeft: m_facing -= 90; if (m_facing < 0) { m_facing = 270; } break; case TurnRight: m_facing += 90; if (m_facing > 270) { m_facing = 0; } break; default: throw "Invalid turn direction"; } switch (m_facing) { case FacingLeft: std::cout << "facing left" << std::endl; break; case FacingRight: std::cout << "facing right" << std::endl; break; case FacingUp: std::cout << "facing up" << std::endl; break; case FacingDown: std::cout << "facing down" << std::endl; break; default: throw "Invalid facing direction"; } } /** * When crossing the edge of one face, returns the ID of the * face onto which we move */ CubeFace teleportEdge(CubeFace src, Facing dir) { switch (src) { case Face1: switch (dir) { case FacingUp: return Face6; case FacingRight: return Face2; case FacingDown: return Face3; case FacingLeft: return Face4; } case Face2: switch (dir) { case FacingUp: return Face6; case FacingRight: return Face5; case FacingDown: return Face3; case FacingLeft: return Face1; } break; case Face3: switch (dir) { case FacingUp: return Face1; case FacingRight: return Face2; case FacingDown: return Face5; case FacingLeft: return Face4; } break; case Face4: switch (dir) { case FacingUp: return Face3; case FacingRight: return Face5; case FacingDown: return Face6; case FacingLeft: return Face1; } break; case Face5: switch (dir) { case FacingUp: return Face3; case FacingRight: return Face2; case FacingDown: return Face6; case FacingLeft: return Face4; } break; case Face6: switch (dir) { case FacingUp: return Face4; case FacingRight: return Face5; case FacingDown: return Face2; case FacingLeft: return Face1; } break; default: throw "invalid src face"; } throw "should not be here"; } /** * Handles mapping coordinates and direction when moving from * one cube face to another. Modifies the given tile to match a tile at the * dest coordinates, and changes the facing parameter to account for the shift */ void handleFaceTransition(Tile& tile, Facing& f, CubeFace src, CubeFace dest) { // When mirroring, do: abs(coord - (FACE_SIZE-1)) to get, ie. // 49 -> 0, and 33 -> 16 int s = FACE_SIZE-1; switch (src) { case Face1: switch (dest) { case Face2: f = FacingRight; tile.face_x = 0; break; case Face3: f = FacingDown; tile.face_y = 0; break; case Face4: f = FacingRight; tile.face_x = 0; tile.face_y = abs(tile.face_y - s); break; case Face5: throw "Impossible move"; case Face6: f = FacingRight; tile.face_y = tile.face_x; tile.face_x = 0; break; } break; case Face2: switch (dest) { case Face1: f = FacingLeft; tile.face_x = s; break; case Face3: f = FacingLeft; tile.face_y = tile.face_x; tile.face_x = s; break; case Face4: throw "Impossible move"; case Face5: f = FacingRight; tile.face_y = abs(tile.face_y - s); tile.face_x = s; break; case Face6: f = FacingUp; tile.face_y = s; break; } break; case Face3: switch (dest) { case Face1: f = FacingUp; tile.face_y = s; break; case Face2: f = FacingUp; tile.face_x = tile.face_y; tile.face_y = s; break; case Face4: f = FacingDown; tile.face_x = tile.face_y; tile.face_y = 0; break; case Face5: f = FacingDown; tile.face_y = 0; break; case Face6: throw "Impossible move"; } break; case Face4: switch (dest) { case Face1: f = FacingRight; tile.face_y = abs(tile.face_y - s);; tile.face_x = 0; break; case Face2: throw "Impossible move"; case Face3: f = FacingRight; tile.face_y = tile.face_x; tile.face_x = 0; break; case Face5: f = FacingRight; tile.face_x = 0; break; case Face6: f = FacingDown; tile.face_y = 0; break; } break; case Face5: switch (dest) { case Face1: throw "Impossible move"; case Face2: f = FacingRight; tile.face_x = 0; break; case Face3: f = FacingUp; tile.face_y = s; break; case Face4: f = FacingLeft; tile.face_x = s; break; case Face6: f = FacingLeft; tile.face_y = tile.face_x; tile.face_x = s; break; } break; case Face6: switch (dest) { case Face1: f = FacingDown; tile.face_x = tile.face_y; tile.face_y = 0; break; case Face2: f = FacingDown; tile.face_y = 0; break; case Face3: throw "Impossible move"; case Face4: f = FacingUp; tile.face_y = s; break; case Face5: f = FacingUp; tile.face_x = tile.face_y; tile.face_y = s; break; } break; default: throw "invalid src face"; } // Calculate new 2D coordinates based on face coordinates tile.x = bounds[dest].x + tile.face_x; tile.y = bounds[dest].y + tile.face_y; } void Player::walk(Tile* map, int map_w, int map_h) { int offset_x = 0; int offset_y = 0; switch (m_facing) { case FacingLeft: offset_x = -1; break; case FacingRight: offset_x = 1; break; case FacingUp: offset_y = -1; break; case FacingDown: offset_y = 1; break; default: throw "Invalid facing direction when moving"; } Tile search = *m_loc; std::cout << "walking from : " << search.face_x << ", " << search.face_y << std::endl; static int foo = 0; foo++; if (foo == 16) { std::cout << "asd" << std::endl; } if (offset_x != 0) { int new_x = search.x + offset_x; int new_face_x = search.face_x + offset_x; if (new_face_x < 0 || new_face_x >= FACE_SIZE) { Facing f = intToFacing(m_facing); Tile t = search; CubeFace newface = teleportEdge(search.m_face, f); handleFaceTransition(t, f, search.m_face, newface); search = t; } else { search.x += offset_x; search.face_x += offset_x; } if (search.x < 0 || search.x >= map_w || search.y < 0 || search.y >= map_h) { throw "walked out of bounds"; } if (search.m_type == Empty) { throw "Should not walk into empty without warping"; } } else if (offset_y != 0) { int new_y = search.y + offset_y; int new_face_y = search.face_y + offset_y; if (new_face_y < 0 || new_face_y >= FACE_SIZE) { Facing f = intToFacing(m_facing); Tile t = search; CubeFace newface = teleportEdge(search.m_face, f); handleFaceTransition(t, f, search.m_face, newface); search = t; } else { search.y += offset_y; search.face_y += offset_y; } if (search.x < 0 || search.x > map_w || search.y < 0 || search.y > map_h) { throw "walked out of bounds"; } if (search.m_type == Empty) { throw "Should not walk into empty without warping"; } } search = map[search.y * map_w + search.x]; if (search.m_type == Floor) { m_loc = &map[search.y * map_w + search.x]; std::cout << "New coords: face_x: " << m_loc->face_x << ", face_y: " << m_loc->face_y << ", face: " << m_loc->m_face+1 << " after moving: (" << offset_x << ", " << offset_y << ")" << std::endl; } else { std::cout << "not moving to invalid tile: "; search.print(); std::cout << std::endl; } } void Tile::print() { switch (m_type) { case TileType::Empty: std::cout << " "; return; case TileType::Wall: std::cout << "#"; return; case TileType::Floor: std::cout << "."; return; default: std::cout << "!"; return; } } std::vector split(std::string s, std::string delimiter) { size_t pos_start = 0, pos_end, delim_len = delimiter.length(); std::string token; std::vector res; while ((pos_end = s.find (delimiter, pos_start)) != std::string::npos) { token = s.substr (pos_start, pos_end - pos_start); pos_start = pos_end + delim_len; res.push_back (token); } res.push_back (s.substr (pos_start)); return res; } /** * Print the map, optionally showing the palyer on it */ void printMap(Tile* map, int w, int h, Player* p, bool printFaces) { for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (p != NULL && p->m_loc->x == x && p->m_loc->y == y) { std::cout << "@"; } else { if (printFaces) { map[y * w + x].printFace(); } else { map[y * w + x].print(); } } } std::cout << " | " << y << std::endl; } } void printCommands(std::vector& commands) { int moves = 0; for (int i = 0; i < commands.size(); ++i) { Command& c = commands[i]; switch (c) { case Forward: moves++; if (i == commands.size()-1) { std::cout << "move forward: " << moves << std::endl; } break; case TurnLeft: std::cout << "move forward: " << moves << std::endl; std::cout << "turn left" << std::endl; moves = 0; break; case TurnRight: std::cout << "move forward: " << moves << std::endl; std::cout << "turn right" << std::endl; moves = 0; break; } } } /** * Go through the map from top to bottom, left to right, and find first * tile that is open floor */ Tile* findFirst(Tile* map, int w, int h) { for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Tile* t = &map[y * w + x]; if (t->m_type == TileType::Floor) { return t; } } } std::cout << "did not find starting tile" << std::endl; return NULL; } int main(int argc, char** argv) { if (argc < 2) { std::cout << "invalid input" << std::endl; return 1; } initBounds(); const std::string fpath = argv[1]; struct stat st; int ret = stat(fpath.c_str(), &st); if (ret != 0) { std::cout << "failed statting: " << fpath << ", " << strerror(errno) << std::endl; return 1; } FILE* fin = fopen(fpath.c_str(), "r"); if (!fin) { std::cout << "Failed opening: " << argv[1] << std::endl; return 1; } char* buf = new char[st.st_size]; memset(buf, 0, st.st_size); size_t r = fread(buf, 1, st.st_size, fin); fclose(fin); if (r != st.st_size) { std::cout << "read: " << r << "/" << st.st_size << " bytes" << std::endl; delete[] buf; return 1; } std::vector lines; std::string l; for (size_t t = 0; t < st.st_size; ++t) { if (buf[t] == '\n') { lines.push_back(l); l = std::string(); continue; } l += buf[t]; } std::cout << "there are: " << lines.size() << " lines" << std::endl; delete[] buf; std::vector maplines; std::vector walklines; for (auto& line : lines) { line.erase(std::remove(line.begin(), line.end(), '\n'), line.cend()); if (line.size() == 0) { continue; } if (line[0] == '.' || line[0] == '#' || line[0] == ' ') { maplines.push_back(line); } else { walklines.push_back(line); } } if (walklines.size() > 1) { std::cout << "walk lines size: " << walklines.size() << std::endl; return 1; } std::string directions = walklines[0]; int map_width = 0; int map_height = maplines.size(); for (auto& w : maplines) { if (w.size() > map_width) { map_width = w.size(); } } // construct w*h map Tile* map = new Tile[map_width * map_height]; for (int y = 0; y < map_height; ++y) { for (int x = 0; x < map_width; ++x) { Tile t = Tile(x, y, TileType::Empty); // hard code cube faces because fuck if I know how to re-wrap // the UV map from arbitrary input if (y < 50) { if (x < 100) { t.m_face = Face1; } else { t.m_face = Face2; } } else if (y < 100) { t.m_face = Face3; } else if (y < 150) { if (x < 50) { t.m_face = Face4; } else { t.m_face = Face5; } } else { t.m_face = Face6; } map[y * map_width + x] = t; } } // Assign each tile a type int row = 0; for (auto& l : maplines) { int column = 0; for (auto& c : l) { Tile* t = &map[row * map_width + column]; if (c == ' ') { t->m_type = TileType::Empty; } else if (c == '#') { t->m_type = TileType::Wall; } else { t->m_type = TileType::Floor; } column++; } row++; } // construct command list from input std::vector commands; std::stringstream ss; for (int i = 0; i < directions.size(); ++i) { char c = directions[i]; if (std::isdigit(c)) { ss << c; // handle last instruction being a move without turning if (i == directions.size()-1) { int count = 0; ss >> count; for (int j = 0; j < count; ++j) { commands.push_back(Forward); } } } else { bool isleft = false; if (c == 'L') { isleft = true; } else if (c == 'R') { isleft = false; } else { std::cout << "invalid direction: " << c << std::endl; return 1; } int count = 0; ss >> count; for (int j = 0; j < count; ++j) { commands.push_back(Forward); } commands.push_back(isleft ? TurnLeft : TurnRight); ss = std::stringstream(); } } Tile* start = findFirst(map, map_width, map_height); if (start == NULL || start->m_type != TileType::Floor || start->x < 0 || start->x >= map_width || start->y < 0 || start->y >= map_height) { std::cout << "invalid start tile" << std::endl; return 1; } Player p; p.m_loc = start; p.m_facing = FacingRight; printMap(map, map_width, map_height, &p, false); std::cout << "starting tile x: " << start->x << ", y: " << start->y << std::endl; for (auto& c : commands) { switch (c) { case Forward: p.walk(map, map_width, map_height); break; case TurnLeft: case TurnRight: p.turn(c); break; } } printMap(map, map_width, map_height, &p, false); std::cout << "player is at: x: " << p.m_loc->x+1 << ", y: " << p.m_loc->y+1 << std::endl; int facing = 0; switch (p.m_facing) { case FacingLeft: std::cout << "Facing left (2)" << std::endl; facing = 2; break; case FacingRight: std::cout << "Facing right (0)" << std::endl; facing = 0; break; case FacingUp: std::cout << "Facing up (3)" << std::endl; facing = 3; break; case FacingDown: std::cout << "Facing down (1)" << std::endl; facing = 1; break; } long long password = (1000 * (p.m_loc->y+1)) + (4 * (p.m_loc->x+1)) + facing; std::cout << "password: " << password << std::endl; delete[] map; return 0; }