ncshoot-sp.c - src - toys - clsr.net

#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/select.h>
#include "container-macros/alist.h"

#define FPS 30 /* default: 30 */
#define BOTTOM_BAR 3 /* default: 3 */
#define MOB_TICKS (FPS/1) /* how often mobs spawn, default: 1*FPS (1/sec) */
#define MOB_CHANCE 2 /* rand()%MOB_CHANCE must be 0 to spawn, default: 2 */
#define SHOOTER_HEALTH 20 /* health of a shooter mob, default: 20 */
#define SHOOTER_HIT 20 /* score for hitting a shooter mob, default: 20 */
#define SHOOTER_KILL 50 /* score for killing a shooter mob, default: 50 */
#define BOMB_HEALTH 30 /* health of a bomb mob, default: 30 */
#define BOMB_HIT 10 /* score for hitting a bomb mob, default: 10 */
#define BOMB_KILL 20 /* score for killing a bomb mob, default: 20 */
#define HEALTH_MAX 100 /* maximum (and starting) amount of health, default: 100 */
#define HEALTH_TICKS 5 /* how often health regens, default: 30 */
#define HEALTH_REGEN 0 /* how much health regens, default: 0 */
#define ENERGY_MAX 100 /* maximum amount of energy, default: 100 */
#define ENERGY_TICKS 5 /* how often energy regens, default: 5 */
#define ENERGY_REGEN 1 /* how much energy regens, default: 1 */
#define SHIELD_INIT 1 /* initial shield stack, default: 1 */
#define SHIELD_INC 1 /* how much shield to stack on an existing shield at once, default: 1 */
#define SHIELD_MAX 5 /* how many shields can be stacked on one square, default: 5 */
#define SHIELD_ENERGY 20 /* how much energy a shield costs, default: 20 */
#define BULLET_ENERGY 10 /* how much energy a bullet costs, default: 10 */
#define BULLET_DAMAGE 10 /* how much damage a bullet does, default: 10 */

enum which { ent_player, ent_bullet, ent_shield, ent_mob };
enum direction { dir_none, dir_up, dir_right, dir_down, dir_left, dir_uprt, dir_dnrt, dir_uplt, dir_dnlt };
enum action { action_none, action_up, action_right, action_down, action_left, action_shoot, action_shield };

struct keys {
	int up;
	int right;
	int down;
	int left;
	int shoot;
	int shield;
};

struct player {
	struct keys keys;
	int color;
	int bgcolor;
	int x, y;
	int health, energy;
	enum action action;
	enum direction direction;
	int score;
};

struct bullet {
	int x, y;
	int color;
	enum direction direction;
	int counter;
};

struct shield {
	int x, y;
	int color, bgcolor;
	int health;
};

struct mob {
	int x, y;
	int color;
	char ch;
	int health;
};

union entities {
	struct player player;
	struct bullet bullet;
	struct shield shield;
	struct mob mob;
};

struct entity {
	enum which which;
	union entities entity;
};

ALIST_PROTO(struct entity, entity_v);
ALIST(struct entity, entity_v);

void init(void);
void quit(void);
void newmob(void);
void genmob(struct mob *m);
void mobstart(struct mob *m);
void mob_action(struct entity *e);
void shooter(struct entity *e);
void bomb(struct entity *e);
void mob_shoot(struct mob *m, enum direction dir);
enum direction to_dir(int dx, int dy);
bool to_xy(enum direction dir, int *x, int *y);
bool oob(int x, int y);
enum direction rand_dir(void);
void default_players(struct player *p1, struct player *p2);
void do_shoot(struct player *p);
void do_shield(struct player *p);
void entity_move(struct entity *p, enum direction dir);
void bullet_move(struct entity *bullet);
char bullet_char(enum direction dir);
struct entity *entity_at(int x, int y);
void entity_remove(struct entity *e);
void add_score(int color, int points);
enum action to_act(struct player *p, int c);
void mainloop(void);
void draw(void);
int main(void);

entity_v *entities;
int lost = -1;
bool quit_done = false;

void init(void)
{
	atexit(quit);

	initscr();
	start_color();
	cbreak();
	noecho();
	nonl();
	nodelay(stdscr, TRUE);
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);

	init_pair(5, COLOR_GREEN, COLOR_BLACK);

	srand(time(NULL));

	entities = entity_v_new();
}

void quit(void)
{
	if (!quit_done) {
		entity_v_free(entities);

		nl();
		echo();
		nocbreak();
		endwin();

		quit_done = true;
	}
}

void newmob(void)
{
	struct mob m;
	union entities ents;
	struct entity e;

	genmob(&m);
	ents.mob = m;
	e.which = ent_mob;
	e.entity = ents;

	entity_v_insert(entities, e, -1);
}

void genmob(struct mob *m)
{
	switch (rand() % 5) {
		case 0:
		case 1:
			m->health = SHOOTER_HEALTH;
			m->ch = 'o';
			m->color = COLOR_PAIR(5);
			break;
		case 2:
		case 3:
		case 4:
			m->health = BOMB_HEALTH;
			m->ch = '*';
			m->color = COLOR_PAIR(5);
			break;
		default:
			exit(2); /* shouldn't happen */
	}
	do {
		mobstart(m);
	} while (entity_at(m->x, m->y));
}

void mobstart(struct mob *m)
{
	switch (rand() % 4) {
		case 0:
			m->x = 0;
			m->y = rand() % LINES - BOTTOM_BAR;
			break;
		case 1:
			m->x = COLS - 1;
			m->y = rand() % LINES - BOTTOM_BAR;
			break;
		case 2:
			m->x = rand() % COLS;
			m->y = 0;
			break;
		case 3:
			m->x = rand() % COLS;
			m->y = LINES - BOTTOM_BAR - 1;
			break;
		default: /* shouldn't happen */
			exit(3);
	}
}

void mob_action(struct entity *e)
{
	if (e->entity.mob.health <= 0) {
		entity_remove(e);
	} else {
		switch (e->entity.mob.ch) {
			case 'o': /* shooter */
				shooter(e);
				break;
			case '*': /* bomb */
				bomb(e);
				break;
			default: /* shouldn't happen */
				exit(3);
		}
	}
}

void shooter(struct entity *e)
{
	struct mob *m = &e->entity.mob;

	if (rand() % 3) {
		entity_move(e, rand_dir());
	} else {
		mob_shoot(m, rand_dir());
	}
}

void bomb(struct entity *e)
{
	struct mob *m = &e->entity.mob, tmp_mob;
	struct player *p;
	struct player *min_p = NULL;
	int min_d, d, i, dx, dy;
	enum direction dir;

	for (i=0; i<entities->len; ++i) {
		if (entities->arr[i].which == ent_player) {
			p = &entities->arr[i].entity.player;
			d = (m->x - p->x) * (m->x - p->x) + (m->y - p->y) * (m->y - p->y);
			if (!min_p || d < min_d) {
				min_d = d;
				min_p = p;
			}
		}
	}

	if (min_p) {
		dx = m->x - min_p->x;
		dy = m->y - min_p->y;

		/*if (dx < 3 && dy < 3) {*/
		if ((dx * dx + dy * dy) < 9) {
			/* WARNING: multiple alist inserts, mob may have been realloc'd somewhere else */
			tmp_mob = *m;
			mob_shoot(&tmp_mob, dir_up);
			mob_shoot(&tmp_mob, dir_right);
			mob_shoot(&tmp_mob, dir_down);
			mob_shoot(&tmp_mob, dir_left);
			mob_shoot(&tmp_mob, dir_uprt);
			mob_shoot(&tmp_mob, dir_dnrt);
			mob_shoot(&tmp_mob, dir_uplt);
			mob_shoot(&tmp_mob, dir_dnlt);
			entity_remove(e);
		} else {
			dir = to_dir(-dx, -dy);
			/*if (rand() % 2) {*/
			entity_move(e, dir);
			/*} else {
				shooter(e);
			}*/
		}
	}
}

void mob_shoot(struct mob *m, enum direction dir)
{
	struct bullet bullet;
	union entities ents;
	struct entity entity;

	bullet.x = m->x;
	bullet.y = m->y;
	bullet.counter = 0;
	bullet.color = m->color;
	bullet.direction = dir;

	ents.bullet = bullet;
	entity.which = ent_bullet;
	entity.entity = ents;
	entity_v_insert(entities, entity, -1);
}

enum direction to_dir(int dx, int dy)
{
	if (dx) {
		if (dy) {
			if (dx > 0) {
				if (dy > 0) {
					return dir_dnrt;
				} else {
					return dir_uprt;
				}
			} else {
				if (dy > 0) {
					return dir_dnlt;
				} else {
					return dir_uplt;
				}
			}
		} else {
			if (dx > 0) {
				return dir_right;
			} else {
				return dir_left;
			}
		}
	} else if (dy) {
		if (dy > 0) {
			return dir_down;
		} else {
			return dir_up;
		}
	}

	return dir_none;
}

bool to_xy(enum direction dir, int *x, int *y)
{
	switch (dir) {
		case dir_up:
			*y -= 1;
			break;
		case dir_right:
			*x += 1;
			break;
		case dir_down:
			*y += 1;
			break;
		case dir_left:
			*x -= 1;
			break;
		case dir_uprt:
			*x += 1;
			*y -= 1;
			break;
		case dir_dnrt:
			*x += 1;
			*y += 1;
			break;
		case dir_uplt:
			*x -= 1;
			*y -= 1;
			break;
		case dir_dnlt:
			*x -= 1;
			*y += 1;
			break;
		case dir_none:
			return false;
	}
	return true;
}

bool oob(int x, int y)
{
	return x < 0 || x >= COLS || y < 0 || y >= LINES - BOTTOM_BAR;
}

enum direction rand_dir(void)
{
	switch (rand() % 8) {
		case 0:
			return dir_up;
		case 1:
			return dir_right;
		case 2:
			return dir_down;
		case 3:
			return dir_left;
		case 4:
			return dir_uprt;
		case 5:
			return dir_dnrt;
		case 6:
			return dir_uplt;
		case 7:
			return dir_dnlt;
	}
	exit(7);
}

void default_players(struct player *p1, struct player *p2)
{
	p1->keys.up = 'w';
	p1->keys.right = 'd';
	p1->keys.down = 's';
	p1->keys.left = 'a';
	p1->keys.shoot = 'e';
	p1->keys.shield = 'q';
	p1->health = HEALTH_MAX;
	p1->energy = 0;
	init_pair(1, COLOR_RED, COLOR_BLACK);
	init_pair(2, COLOR_BLACK, COLOR_RED);
	p1->color = COLOR_PAIR(1);
	p1->bgcolor = COLOR_PAIR(2);
	p1->x = COLS / 2;
	p1->y = (LINES - BOTTOM_BAR - 1) / 2;
	p1->action = action_none;
	p1->direction = dir_none;
	p1->score = 0;

	p2->keys.up = '\0';
	p2->keys.right = '\0';
	p2->keys.down = '\0';
	p2->keys.left = '\0';
	p2->keys.shoot = '\0';
	p2->keys.shield = '\0';
	p2->health = HEALTH_MAX;
	p2->energy = 0;
	init_pair(3, COLOR_BLUE, COLOR_BLACK);
	init_pair(4, COLOR_BLACK, COLOR_BLUE);
	p2->color = COLOR_PAIR(3);
	p2->bgcolor = COLOR_PAIR(4);
	p2->x = COLS - 1;
	p2->y = LINES - 1;
	p2->action = action_none;
	p2->direction = dir_none;
	p2->score = 0;
}

void do_shoot(struct player *p)
{
	struct bullet bullet;
	union entities ents;
	struct entity entity;

	if (p->energy < BULLET_ENERGY) {
		p->direction = dir_none;
		p->action = action_none;
		return;
	}

	p->energy -= BULLET_ENERGY;

	bullet.x = p->x;
	bullet.y = p->y;
	bullet.counter = 0;
	bullet.color = p->color;
	bullet.direction = p->direction;

	ents.bullet = bullet;
	entity.which = ent_bullet;
	entity.entity = ents;

	p->direction = dir_none;
	p->action = action_none;

	entity_v_insert(entities, entity, -1);
}

void do_shield(struct player *p)
{
	struct shield shield;
	union entities ents;
	struct entity entity;
	struct entity *e;

	if (p->energy < SHIELD_ENERGY) {
		p->direction = dir_none;
		p->action = action_none;
		return;
	}

	shield.x = p->x;
	shield.y = p->y;
	shield.color = p->color;
	shield.bgcolor = p->bgcolor;
	shield.health = SHIELD_INIT;
	to_xy(p->direction, &shield.x, &shield.y);

	e = entity_at(shield.x, shield.y);
	if (e) {
		if (e->which == ent_shield && e->entity.shield.health < SHIELD_MAX && e->entity.shield.color == p->color) {
			e->entity.shield.health += SHIELD_INC;
			if (e->entity.shield.health > SHIELD_MAX) {
				e->entity.shield.health = SHIELD_MAX;
			}
			p->energy -= SHIELD_ENERGY;
		}
	/*} else if (shield.x >= 0 && shield.x < COLS && shield.y >= 0 && shield.y < LINES -	BOTTOM_BAR - 1) {*/
	} else if (!oob(shield.x, shield.y)) {
		ents.shield = shield;
		entity.which = ent_shield;
		entity.entity = ents;
		entity_v_insert(entities, entity, -1);
		p->energy -= SHIELD_ENERGY;
	}

	p->direction = dir_none;
	p->action = action_none;
}

void entity_move(struct entity *e, enum direction dir)
{
	int x, y;
	int dx = 0;
	int dy = 0;

	switch (e->which) {
		case ent_player:
			x = e->entity.player.x;
			y = e->entity.player.y;
			break;
		case ent_bullet:
			++e->entity.bullet.counter;
			x = e->entity.bullet.x;
			y = e->entity.bullet.y;
			break;
		case ent_mob:
			x = e->entity.mob.x;
			y = e->entity.mob.y;
			break;
		case ent_shield:
			return;
	}

	to_xy(dir, &dx, &dy);

	switch (e->which) {
		case ent_player:
			/*if (x + dx >= 0 && x + dx < COLS && y + dy >= 0 && y + dy < LINES - BOTTOM_BAR) {*/
			if (!oob(x+dx, y+dy)) {
				if (!entity_at(x+dx, y+dy)) {
					e->entity.player.x += dx;
					e->entity.player.y += dy;
				}
			}
			e->entity.player.direction = dir_none;
			e->entity.player.action = action_none;
			break;
		case ent_bullet:
			e->entity.bullet.x += dx;
			e->entity.bullet.y += dy;
			break;
		case ent_mob:
			/*if (x + dx >= 0 && x + dx < COLS && y + dy >= 0 && y + dy < LINES - BOTTOM_BAR) {*/
			if (!oob(x+dx, y+dy)) {
				if (!entity_at(x+dx, y+dy)) {
					e->entity.mob.x += dx;
					e->entity.mob.y += dy;
				}
			}
			break;
		case ent_shield:
			return;
	}
}

void bullet_move(struct entity *bullet)
{
	struct entity *e;
	struct bullet *p;
	enum direction dir;

	p = &bullet->entity.bullet;
	++p->counter;
	dir = p->direction;

	to_xy(dir, &p->x, &p->y);
	if (oob(p->x, p->y)) {
		entity_remove(bullet);
		return;
	} else {
		e = entity_at(p->x, p->y);
	}

	if (e) {
		switch (e->which) {
			case ent_player:
				if (e->entity.player.color != p->color) {
					add_score(p->color, e->entity.player.health<=0 ? 100 : BULLET_DAMAGE);
					e->entity.player.health -= BULLET_DAMAGE;
					entity_remove(bullet);
				}
				break;
			case ent_bullet:
				if (e->entity.bullet.color != p->color) {
					entity_remove(bullet);
					entity_remove(e);
				}
				break;
			case ent_shield:
				e->entity.shield.health -= 1;
				entity_remove(bullet);
				break;
			case ent_mob:
				if (e->entity.mob.color != p->color) {
					e->entity.mob.health -= BULLET_DAMAGE;
					if (e->entity.mob.ch == 'o') {
						add_score(p->color, e->entity.mob.health<=0 ? SHOOTER_KILL : SHOOTER_HIT);
					} else if (e->entity.mob.ch == '*') {
						add_score(p->color, e->entity.mob.health<=0 ? BOMB_KILL : BOMB_HIT);
					}
					entity_remove(bullet);
				} else if (p->counter > 1) {
					e->entity.mob.health -= BULLET_DAMAGE;
					entity_remove(bullet);
				}
				break;
		}
	}
}

char bullet_char(enum direction dir)
{
	switch (dir) {
		case dir_up:
		case dir_down:
			return '|';
		case dir_right:
		case dir_left:
			return '-';
		case dir_uprt:
		case dir_dnlt:
			return '/';
		case dir_uplt:
		case dir_dnrt:
			return '\\';
		case dir_none:
			return '*';
	}
	return '*';
}

struct entity *entity_at(int x, int y)
{
	int i;
	struct entity *e;

	for (i=0; i<entities->len; ++i) {
		e = &entities->arr[i];
		switch (e->which) {
			case ent_player:
				if (e->entity.player.x == x && e->entity.player.y == y) {
					return e;
				}
				break;
			case ent_bullet:
				if (e->entity.bullet.x == x && e->entity.bullet.y == y) {
					return e;
				}
				break;
			case ent_shield:
				if (e->entity.shield.x == x && e->entity.shield.y == y) {
					return e;
				}
				break;
			case ent_mob:
				if (e->entity.mob.x == x && e->entity.mob.y == y) {
					return e;
				}
		}
	}

	return NULL;
}

void entity_remove(struct entity *e)
{
	int i;


	/*i = e - entities->arr;
	if (i < entities->len) {
		entity_v_pop(entities, i);
	} else {*/
	for (i=0; i<entities->len; ++i) {
		if ((&entities->arr[i]) == e) {
			entity_v_pop(entities, i);
			return;
		}
	}
	/*}*/
}

void add_score(int color, int points)
{
	int i;

	for (i=0; i<entities->len; ++i) {
		if (entities->arr[i].which == ent_player && entities->arr[i].entity.player.color == color) {
			entities->arr[i].entity.player.score += points;
		}
	}
}

enum action to_act(struct player *p, int c)
{
	if (p->keys.up == c) {
		return action_up;
	} else if (p->keys.right == c) {
		return action_right;
	} else if (p->keys.down == c) {
		return action_down;
	} else if (p->keys.left == c) {
		return action_left;
	} else if (p->keys.shoot == c) {
		return action_shoot;
	} else if (p->keys.shield == c) {
		return action_shield;
	} else {
		return action_none;
	}
}

void mainloop(void)
{
	int counter;
	int c, i;
	enum action act;
	struct player *player;
	struct entity *entity;
	struct timeval tv;

	for (counter=1; ; ++counter) {
		while ((c=getch()) != ERR) {
			if (c == '\x1b') {
				return;
			}

			for (i=0; i<entities->len; ++i) {
				if (entities->arr[i].which != ent_player) {
					continue;
				}

				player = &entities->arr[i].entity.player;
				act = to_act(player, c);

				if (act != action_none && (player->action == action_none || player->action == action_shoot || player->action == action_shield)) {
					if ((player->action == action_shoot || player->action == action_shield) && player->direction == dir_none) {
						switch (act) {
							case action_up:
								player->direction = dir_up;
								break;
							case action_right:
								player->direction = dir_right;
								break;
							case action_down:
								player->direction = dir_down;
								break;
							case action_left:
								player->direction = dir_left;
								break;
							/* rest are invalid */
							case action_shoot:
							case action_shield:
							case action_none:
								player->action = action_none;
								player->direction = dir_none;
								break;
						}
					} else if (player->action != action_shoot && player->action != action_shield) {
						player->action = act;
					}
					break;
				}
			}
		}

		for (i=0; i<entities->len; ++i) {
			entity = &entities->arr[i];
			switch (entity->which) {
				case ent_player:
					player = &entity->entity.player;
					if (player->health <= 0) {
						lost = player->color;
						return;
					}
					if (counter % HEALTH_TICKS == 0 && player->health < HEALTH_MAX) {
						player->health += HEALTH_REGEN;
						if (player->health > HEALTH_MAX) {
							player->health = HEALTH_MAX;
						}
					}
					if (counter % ENERGY_TICKS == 0 && player->energy < ENERGY_MAX) {
						player->energy += ENERGY_REGEN;
						if (player->energy > ENERGY_MAX) {
							player->energy = ENERGY_MAX;
						}
					}
					switch (player->action) {
						case action_up:
							entity_move(entity, dir_up);
							break;
						case action_right:
							entity_move(entity, dir_right);
							break;
						case action_down:
							entity_move(entity, dir_down);
							break;
						case action_left:
							entity_move(entity, dir_left);
							break;
						case action_shoot:
							if (player->direction != dir_none) {
								do_shoot(player);
							}
							break;
						case action_shield:
							if (player->direction != dir_none) {
								do_shield(player);
							}
							break;
						case action_none:
							break;
					}
					break;
				case ent_bullet:
					bullet_move(entity);
					break;
				case ent_shield:
					if (entity->entity.shield.health < 1) {
						entity_remove(entity);
					}
					break;
				case ent_mob:
					if (counter % FPS == 0 || counter % FPS == FPS/3 || counter % FPS == FPS*2/3) {
						mob_action(entity);
					}
					break;
			}
		}

		if (counter % MOB_TICKS == 0 && rand() % MOB_CHANCE == 0) {
			newmob();
		}

		draw();

		tv.tv_sec = 0;
		tv.tv_usec = 1000000 / FPS;
		select(0, NULL, NULL, NULL, &tv);
	}
}

void draw(void)
{
	int i, j;
	struct entity *e;
	struct player *p;
	struct bullet *b;
	struct shield *s;
	struct mob *m;
	int plnum = 0;
	char score[16];

	erase();

	for (i=0; i<COLS; ++i) {
		mvaddch(LINES - BOTTOM_BAR, i, ACS_HLINE);
	}

	for (i=0; i<entities->len; ++i) {
		e = &entities->arr[i];

		switch (e->which) {
			case ent_player:
				p = &e->entity.player;
				mvaddch(p->y, p->x, '@' | p->color);
				mvaddstr(LINES - BOTTOM_BAR + 1, COLS/2*plnum, "Health: ");
				for (j=0; j<HEALTH_MAX/2; ++j) {
					addch(j>p->health/2 ? ' ' : '|' | p->color);
				}
				mvaddstr(LINES - BOTTOM_BAR + 2, COLS/2*plnum, "Energy: ");
				for (j=0; j<ENERGY_MAX/2; ++j) {
					addch(j>p->energy/2 ? ' ' : '|' | p->color);
				}
				sprintf(score, " Score: %d", p->score);
				addstr(score);
				++plnum;
				break;
			case ent_bullet:
				b = &e->entity.bullet;
				mvaddch(b->y, b->x, bullet_char(b->direction) | b->color);
				break;
			case ent_shield:
				s = &e->entity.shield;
				/*mvaddch(s->y, s->x, (s->health + '0') | s->bgcolor);*/
				mvaddch(s->y, s->x, (s->health<10 ? s->health+'0' : s->health-10+'a') | s->bgcolor);
				break;
			case ent_mob:
				m = &e->entity.mob;
				mvaddch(m->y, m->x, m->ch | m->color);
		}
	}

	move(LINES - 1, COLS - 1);
	refresh();
}

int main(void)
{
	struct player p1, p2;
	struct entity p1e;
	union entities p1u;
	int i;

	init();

	if (COLS < 20 || LINES < 12) {
		fputs("Screen too small!", stderr);
		return 1;
	}

	default_players(&p1, &p2);
	p1e.which = ent_player;
	p1u.player = p1;
	p1e.entity = p1u;
	entity_v_insert(entities, p1e, -1);

	mainloop();

	for (i=0; i<entities->len; ++i) {
		if (entities->arr[i].which == ent_player) {
			if (entities->arr[i].entity.player.color == p1.color) {
				p1.score = entities->arr[i].entity.player.score;
			}
		}
	}

	quit();

	printf("Score: %d\n", p1.score);

	return 0;
}