uni

University stuff
git clone git://git.christosmarg.xyz/uni-assignments.git
Log | Files | Refs | README | LICENSE

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 }