Engine.cc (6972B)
1 #include "Engine.hpp" 2 3 enum Color { 4 WALL = 1, 5 PATH, 6 POTTER, 7 GNOME, 8 TRAAL, 9 LAST 10 }; 11 12 Engine::Engine(const char *mapfile, const char *scorefile) 13 { 14 if (!load_map(mapfile)) 15 throw "load_map failed: " + std::string(mapfile); 16 if (!init_curses()) 17 throw "init_curses failed"; 18 if (!init_entities()) 19 throw "init_entities failed"; 20 if (!init_score(scorefile)) 21 throw "init_score failed: " + std::string(scorefile); 22 f_running = 1; 23 } 24 25 26 Engine::~Engine() 27 { 28 player = nullptr; 29 for (auto&& e : entities) 30 delete e; 31 delete score; 32 map.clear(); 33 colors.clear(); 34 (void)delwin(gw); 35 (void)endwin(); 36 } 37 38 /* Private methods */ 39 40 bool 41 Engine::load_map(const char *mapfile) 42 { 43 std::ifstream f; 44 std::vector<char> row; 45 char c; 46 47 f.exceptions(std::ifstream::badbit); 48 f.open(mapfile); 49 if (!f.is_open()) 50 return false; 51 while (f.get(c)) { 52 if (f.eof()) 53 break; 54 row.push_back(c); 55 if (c == '\n') { 56 /* XXX: h != hprev */ 57 w = row.size(); 58 map.push_back(row); 59 row.clear(); 60 } 61 } 62 f.close(); 63 h = map.size(); 64 65 return true; 66 } 67 68 /* Initialize curses(3) environment */ 69 bool 70 Engine::init_curses() 71 { 72 int wr, wc, wy, wx; 73 74 if (!initscr()) 75 return false; 76 noecho(); 77 cbreak(); 78 curs_set(false); 79 /* Allow arrow key presses. */ 80 keypad(stdscr, true); 81 /* ESC has a small delay after it's pressed, so remove it. */ 82 set_escdelay(0); 83 /* Don't wait for a keypress, just continue if there's nothing. */ 84 timeout(1000); 85 86 xmax = getmaxx(stdscr); 87 ymax = getmaxy(stdscr); 88 89 wr = h; 90 wc = w; 91 wy = CENTER(ymax, wr); 92 wx = CENTER(xmax, wc); 93 if ((gw = newwin(wr, wc, wy, wx)) == NULL) 94 return false; 95 box(gw, 0, 0); 96 wxmax = getmaxx(gw); 97 wymax = getmaxy(gw); 98 99 colors.push_back(COLOR_BLUE); /* Wall */ 100 colors.push_back(COLOR_RED); /* Path */ 101 colors.push_back(COLOR_CYAN); /* Potter */ 102 colors.push_back(COLOR_GREEN); /* Gnome */ 103 colors.push_back(COLOR_YELLOW); /* Traal */ 104 105 start_color(); 106 use_default_colors(); 107 for (int i = 1; i < Color::LAST; i++) 108 (void)init_pair(i, colors[i-1], -1); 109 110 return true; 111 } 112 113 bool 114 Engine::collides_with_wall(int x, int y) 115 { 116 if (x < w && y < h) 117 return map[y][x] == '*'; 118 return true; 119 } 120 121 void 122 Engine::calc_pos(int *x, int *y) 123 { 124 do { 125 *x = rand() % w; 126 *y = rand() % h; 127 /* 128 * Don't spawn an enemy at the same coordinates with 129 * another entity. 130 */ 131 for (const auto& e : entities) 132 if (*x == e->get_x() && *y == e->get_y()) 133 continue; 134 } while (collides_with_wall(*x, *y)); 135 } 136 137 bool 138 Engine::init_entities() 139 { 140 int i, x, y, type; 141 142 srand(time(nullptr)); 143 144 calc_pos(&x, &y); 145 entities.push_back(new Potter(x, y, Movable::Direction::DOWN, 'P')); 146 for (i = 0; i < nenemies; i++) { 147 calc_pos(&x, &y); 148 /* 149 * Randomly choose whether we'll create a `Gnome` or 150 * `Traal` enemy. 151 */ 152 switch (type = rand() % 2) { 153 case 0: 154 entities.push_back(new Gnome(x, y, 155 Movable::Direction::DOWN, 'G')); 156 break; 157 case 1: 158 entities.push_back(new Traal(x, y, 159 Movable::Direction::DOWN, 'T')); 160 break; 161 } 162 } 163 player = (Potter *)entities[0]; 164 165 return true; 166 } 167 168 bool 169 Engine::init_score(const char *scorefile) 170 { 171 score = new Score(scorefile); 172 173 return true; 174 } 175 176 void 177 Engine::ctrl_menu() 178 { 179 WINDOW *opts; 180 int wr, wc, wy, wx; 181 182 wc = 32; 183 wr = 9; 184 wx = CENTER(xmax, wc); 185 wy = CENTER(ymax, wr); 186 if ((opts = newwin(wr, wc, wy, wx)) == NULL) 187 return; 188 werase(opts); 189 box(opts, 0, 0); 190 191 mvwprintw(opts, 1, 1, "Up Move up"); 192 mvwprintw(opts, 2, 1, "Down Move down"); 193 mvwprintw(opts, 3, 1, "Left Move left"); 194 mvwprintw(opts, 4, 1, "Right Move right"); 195 mvwprintw(opts, 5, 1, "ESC Quit"); 196 mvwprintw(opts, 7, 1, "Press any key to quit the menu"); 197 198 wrefresh(opts); 199 (void)wgetch(opts); 200 werase(opts); 201 wrefresh(opts); 202 (void)delwin(opts); 203 } 204 205 void 206 Engine::calc_dist(std::map<int, int>& dists, int ex, int ey, int dir) 207 { 208 int px, py, dx, dy, d; 209 210 px = player->get_x(); 211 py = player->get_y(); 212 dx = ex - px; 213 dy = ey - py; 214 d = floor(sqrt(dx * dx + dy * dy)); 215 dists.insert(std::pair<int, int>(d, dir)); 216 } 217 218 /* Public methods */ 219 220 void 221 Engine::kbd_input() 222 { 223 int key, dir, newx, newy; 224 225 newx = player->get_x(); 226 newy = player->get_y(); 227 228 switch (key = getch()) { 229 case KEY_LEFT: 230 newx--; 231 dir = Movable::Direction::LEFT; 232 break; 233 case KEY_RIGHT: 234 newx++; 235 dir = Movable::Direction::RIGHT; 236 break; 237 case KEY_UP: 238 newy--; 239 dir = Movable::Direction::UP; 240 break; 241 case KEY_DOWN: 242 newy++; 243 dir = Movable::Direction::DOWN; 244 break; 245 case 'c': 246 ctrl_menu(); 247 return; 248 case ESC: /* FALLTHROUGH */ 249 /* FIXME: what? */ 250 f_running = 0; 251 default: 252 return; 253 } 254 255 if (!collides_with_wall(newx, newy)) 256 player->set_newpos(dir, wxmax, wymax); 257 } 258 259 /* FIXME: move asynchronously */ 260 void 261 Engine::enemies_move() 262 { 263 std::map<int, int> dists; 264 int ex, ey; 265 auto distcmp = [](const std::pair<int, int>& a, const std::pair<int, int>& b) { 266 return a.first < b.second; 267 }; 268 269 for (const auto& e : entities) { 270 if (e == player) 271 continue; 272 ex = e->get_x(); 273 ey = e->get_y(); 274 dists.clear(); 275 /* West */ 276 if (!collides_with_wall(ex - 1, ey)) 277 calc_dist(dists, ex - 1, ey, Movable::Direction::LEFT); 278 /* East */ 279 if (!collides_with_wall(ex + 1, ey)) 280 calc_dist(dists, ex + 1, ey, Movable::Direction::RIGHT); 281 /* North */ 282 if (!collides_with_wall(ex, ey - 1)) 283 calc_dist(dists, ex, ey - 1, Movable::Direction::UP); 284 /* South */ 285 if (!collides_with_wall(ex, ey + 1)) 286 calc_dist(dists, ex, ey + 1, Movable::Direction::DOWN); 287 288 if (!dists.empty()) { 289 auto min = std::min_element(dists.begin(), 290 dists.end(), distcmp); 291 e->set_newpos(min->second, wxmax, wymax); 292 } 293 } 294 } 295 296 void 297 Engine::collisions() 298 { 299 int px, py, ex, ey; 300 301 px = player->get_x(); 302 py = player->get_y(); 303 304 for (const auto& e : entities) { 305 ex = e->get_x(); 306 ey = e->get_y(); 307 if (e != player && px == ex && py == ey) { 308 /* TODO: increase score */ 309 } 310 } 311 } 312 313 void 314 Engine::upd_score() 315 { 316 } 317 318 void 319 Engine::redraw() 320 { 321 char msg_score[] = "Score: %d"; 322 char msg_opts[] = "c Controls"; 323 int color; 324 325 /* TODO: print more info */ 326 werase(gw); 327 erase(); 328 mvprintw(0, 0, "Potter: (%d, %d)", player->get_x(), player->get_y()); 329 mvprintw(0, CENTER(xmax, strlen(msg_score)), msg_score, 10); 330 mvprintw(0, xmax - strlen(msg_opts), msg_opts); 331 mvhline(1, 0, ACS_HLINE, xmax); 332 wattron(gw, A_REVERSE); 333 for (const auto& row : map) { 334 for (const auto& c : row) { 335 if (c == '*') 336 color = COLOR_PAIR(Color::WALL); 337 else if (c == ' ') 338 color = COLOR_PAIR(Color::PATH); 339 wattron(gw, color); 340 waddch(gw, c); 341 wattroff(gw, color); 342 } 343 } 344 for (const auto& e : entities) { 345 if (dynamic_cast<Potter *>(e) != nullptr) 346 color = COLOR_PAIR(Color::POTTER); 347 else if (dynamic_cast<Gnome *>(e) != nullptr) 348 color = COLOR_PAIR(Color::GNOME); 349 else if (dynamic_cast<Traal *>(e) != nullptr) 350 color = COLOR_PAIR(Color::TRAAL); 351 wattron(gw, color); 352 mvwaddch(gw, e->get_y(), e->get_x(), e->get_sym()); 353 wattroff(gw, color); 354 } 355 wattroff(gw, A_REVERSE); 356 refresh(); 357 wrefresh(gw); 358 } 359 360 bool 361 Engine::is_running() 362 { 363 return f_running; 364 }