Merge branch 'master' of peisongxiao.com:~/cs246/a5

This commit is contained in:
2024-07-17 12:01:01 -04:00
32 changed files with 544 additions and 145 deletions

View File

@ -117,9 +117,8 @@ feature proc_args(int argc, char **argv,
if (result & FEATURE_SEED) { if (result & FEATURE_SEED) {
std::istringstream iss {seed}; std::istringstream iss {seed};
unsigned int tmp; unsigned int tmp;
iss >> tmp;
if (!iss.good()) if (!(iss >> tmp))
return FEATURE_PANIC_SEED; return FEATURE_PANIC_SEED;
rng = std::make_unique<RNG>(tmp); rng = std::make_unique<RNG>(tmp);

View File

@ -18,8 +18,10 @@ game_status CC3K::run() {
case main_menu: { case main_menu: {
auto tmp = curr_menu->run(in); auto tmp = curr_menu->run(in);
if (tmp == -2) if (tmp == -2) {
gresult.status = terminated;
return terminated; return terminated;
}
if (tmp != -1) { if (tmp != -1) {
curr_menu = nullptr; curr_menu = nullptr;

View File

@ -9,12 +9,12 @@ character::character(RNG *rng, const feature enabled_features,
rng{rng}, enabled_features{enabled_features}, rng{rng}, enabled_features{enabled_features},
race{nrace}, HP{STARTING_HP[race]}, pos{pos}, race{nrace}, HP{STARTING_HP[race]}, pos{pos},
ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[race]}, ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[race]},
base_hit_rate{1, 1}, base_hit_rate_reset{1, 1} {} base_hit_rate{STARTING_HR[race]} {}
void character::start_turn() { void character::start_turn() {
ATK = STARTING_ATK[race]; ATK = STARTING_ATK[race];
DEF = STARTING_DEF[race]; DEF = STARTING_DEF[race];
base_hit_rate = base_hit_rate_reset; base_hit_rate = STARTING_HR[race];
} }
enum race character::get_race() const { enum race character::get_race() const {
@ -29,12 +29,12 @@ void character::set_pos(const position &npos) {
pos = npos; pos = npos;
} }
void character::apply_effect(potion *effect) { void character::apply_effect(std::unique_ptr<potion> effect) {
insert_effect(effect); insert_effect(std::move(effect));
} }
void character::insert_effect(potion *effect) { void character::insert_effect(std::unique_ptr<potion> effect) {
effects.push_back(effect); effects.push_back(std::move(effect));
for (int i = effects.size() - 1; i > 0 && for (int i = effects.size() - 1; i > 0 &&
effect->get_priority() < effects[i - 1]->get_priority(); --i) effect->get_priority() < effects[i - 1]->get_priority(); --i)
@ -42,17 +42,17 @@ void character::insert_effect(potion *effect) {
} }
result character::calc_effects() { result character::calc_effects() {
potion_list tmp; potion_own_list tmp;
tmp.reserve(effects.size()); tmp.reserve(effects.size());
for (auto p : effects) { for (size_t i = 0; i < effects.size(); ++i) {
p->apply(this->race, HP, ATK, DEF, base_hit_rate); effects[i]->apply(this->race, HP, ATK, DEF, base_hit_rate);
if (HP <= 0) if (HP <= 0)
return result::died; return result::died;
if (p->get_duration() != 0) if (effects[i]->get_duration() != 0)
tmp.push_back(p); tmp.push_back(std::move(effects[i]));
} }
tmp.shrink_to_fit(); tmp.shrink_to_fit();
@ -63,12 +63,12 @@ result character::calc_effects() {
} }
void character::discard_level_effects() { void character::discard_level_effects() {
potion_list tmp; potion_own_list tmp;
tmp.reserve(effects.size()); tmp.reserve(effects.size());
for (auto p : effects) for (size_t i = 0; i < effects.size(); ++i)
if (p->get_duration() > 0) if (effects[i]->get_duration() > 0)
tmp.push_back(p); tmp.push_back(std::move(effects[i]));
tmp.shrink_to_fit(); tmp.shrink_to_fit();

View File

@ -26,7 +26,7 @@ public:
virtual long_result get_hit(character *ch, const int tATK, virtual long_result get_hit(character *ch, const int tATK,
const fraction &hit_rate) = 0; const fraction &hit_rate) = 0;
virtual void apply_effect(potion *effect); virtual void apply_effect(std::unique_ptr<potion> effect);
// override for different types // override for different types
virtual void print(output *out) = 0; virtual void print(output *out) = 0;
@ -56,12 +56,11 @@ protected:
int ATK; int ATK;
int DEF; int DEF;
fraction base_hit_rate; fraction base_hit_rate;
fraction base_hit_rate_reset;
potion_list effects; potion_own_list effects;
private: private:
void insert_effect(potion *effect); void insert_effect(std::unique_ptr<potion> effect);
}; };
int calc_dmg(const int ATK, const int DEF); int calc_dmg(const int ATK, const int DEF);

View File

@ -4,6 +4,7 @@
#include <ncurses.h> #include <ncurses.h>
#include <string> #include <string>
#include "position.h" #include "position.h"
#include "fraction.h"
static const int INF = 0x3F3F3F3F; static const int INF = 0x3F3F3F3F;
@ -41,27 +42,33 @@ enum game_command : int {game_command_terminate = 0,
}; };
// Character generation related // Character generation related
static const int RACE_CNT = 12; static const int RACE_CNT = 13;
enum race : int {rshade = 0, rdrow, rvampire, rtroll, enum race : int {rshade = 0, rdrow, rvampire, rtroll,
rgoblin, rhuman, rdwarf, relf, rgoblin, rhuman, rdwarf, relf,
rorc, rmerchant, rdragon, rhalfling rorc, rmerchant, rdragon, rhalfling,
rt_800
}; };
static const char CHAR_REP[RACE_CNT] = { static const char CHAR_REP[RACE_CNT] = {
's', 'd', 'v', 't', 'g', 'H', 'W', 'E', 'O', 'M', 'D', 'L' 's', 'd', 'v', 't', 'g', 'H', 'W', 'E', 'O', 'M', 'D', 'L', 't'
}; };
static const int MAX_HP[RACE_CNT] = { static const int MAX_HP[RACE_CNT] = {
125, 150, INF, 120, 110, 140, 100, 140, 180, 30, 150, 100 125, 150, INF, 120, 110, 140, 100, 140, 180, 30, 150, 100, 500
}; };
static const int STARTING_HP[RACE_CNT] = { static const int STARTING_HP[RACE_CNT] = {
125, 150, 50, 120, 110, 140, 100, 140, 180, 30, 150, 100 125, 150, 50, 120, 110, 140, 100, 140, 180, 30, 150, 100, 500
}; };
static const int STARTING_ATK[RACE_CNT] = { static const int STARTING_ATK[RACE_CNT] = {
25, 25, 25, 25, 15, 20, 20, 30, 30, 70, 20, 15 25, 25, 25, 25, 15, 20, 20, 30, 30, 70, 20, 15, 40
}; };
static const int STARTING_DEF[RACE_CNT] = { static const int STARTING_DEF[RACE_CNT] = {
25, 15, 25, 15, 20, 20, 30, 10, 25, 5, 20, 20 25, 15, 25, 15, 20, 20, 30, 10, 25, 5, 20, 20, 50
};
static const fraction STARTING_HR[RACE_CNT] = {
{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},
{1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2},
{1, 1}
}; };

View File

@ -19,6 +19,7 @@ cursor::cursor() {
cursor::~cursor() { cursor::~cursor() {
endwin(); endwin();
refresh();
} }
int cursor::getcmd() const { int cursor::getcmd() const {

View File

@ -9,17 +9,18 @@
#include "enemies/merchant.h" #include "enemies/merchant.h"
#include "enemies/orc.h" #include "enemies/orc.h"
void new_dragon(RNG *rng, std::unique_ptr<enemy_base> &p, std::unique_ptr<enemy_base> new_dragon(RNG *rng, const position &pos,
const position &pos, const position &fallback, const position &fallback,
const feature enabled_features, int which_room) { const feature enabled_features,
int which_room) {
const position nil{0, 0}; const position nil{0, 0};
if (pos != nil) if (pos != nil)
p = std::make_unique<dragon>(rng, enabled_features, return std::make_unique<dragon>(rng, enabled_features,
pos, which_room); pos, which_room);
else else
p = std::make_unique<dragon>(rng, enabled_features, return std::make_unique<dragon>(rng, enabled_features,
fallback, which_room); fallback, which_room);
} }
const int EXCNT = 6; const int EXCNT = 6;
@ -44,9 +45,9 @@ enum race get_normal_race(RNG *rng) {
return CHOICES[rng->rand_under(CNT)]; return CHOICES[rng->rand_under(CNT)];
} }
void new_enemy(RNG *rng, std::unique_ptr<enemy_base> &p, std::unique_ptr<enemy_base> new_enemy(RNG *rng, const position &pos,
const position &pos, const feature enabled_features, const feature enabled_features,
int which_room) { int which_room) {
using std::make_unique; using std::make_unique;
enum race r; enum race r;
@ -56,34 +57,40 @@ void new_enemy(RNG *rng, std::unique_ptr<enemy_base> &p,
else else
r = get_normal_race(rng); r = get_normal_race(rng);
p = nullptr;
switch (r) { switch (r) {
case rdwarf: case rdwarf:
p = make_unique<dwarf>(rng, enabled_features, pos, which_room); return make_unique<dwarf>(rng, enabled_features,
pos, which_room);
break; break;
case rhuman: case rhuman:
p = make_unique<human>(rng, enabled_features, pos, which_room); return make_unique<human>(rng, enabled_features,
pos, which_room);
break; break;
case relf: case relf:
p = make_unique<elf>(rng, enabled_features, pos, which_room); return make_unique<elf>(rng, enabled_features,
pos, which_room);
break; break;
case rorc: case rorc:
p = make_unique<orc>(rng, enabled_features, pos, which_room); return make_unique<orc>(rng, enabled_features,
pos, which_room);
break; break;
case rmerchant: case rmerchant:
p = make_unique<merchant>(rng, enabled_features, pos, which_room); return make_unique<merchant>(rng, enabled_features,
pos, which_room);
break; break;
case rhalfling: case rhalfling:
p = make_unique<halfling>(rng, enabled_features, pos, which_room); return make_unique<halfling>(rng, enabled_features,
pos, which_room);
break; break;
default: default:
break; break;
} }
return nullptr;
} }

View File

@ -4,12 +4,13 @@
#include <memory> #include <memory>
#include "enemy.h" #include "enemy.h"
void new_dragon(RNG *rng, std::unique_ptr<enemy_base> &p, std::unique_ptr<enemy_base> new_dragon(RNG *rng, const position &pos,
const position &pos, const position &fallback, const position &fallback,
const feature enabled_features, int which_room); const feature enabled_features,
int which_room);
void new_enemy(RNG *rng, std::unique_ptr<enemy_base> &p, std::unique_ptr<enemy_base> new_enemy(RNG *rng, const position &pos,
const position &pos, const feature enabled_features, const feature enabled_features,
int which_room); int which_room);
#endif #endif

View File

@ -20,6 +20,9 @@ long_result elf::attack(character *ch) {
if (res1.res == result::died) if (res1.res == result::died)
return res1; return res1;
if (ch->get_race() == rdrow)
return res1;
auto res2 = ch->get_hit(this, ATK, base_hit_rate); auto res2 = ch->get_hit(this, ATK, base_hit_rate);
if (res1.res == miss && res2.res == miss) if (res1.res == miss && res2.res == miss)

View File

@ -8,9 +8,7 @@ enemy_base::enemy_base(RNG *rng, const feature enabled_features,
const enum race &nrace, const position &pos, const enum race &nrace, const position &pos,
const int gen_room_num, std::string abbrev): const int gen_room_num, std::string abbrev):
character{rng, enabled_features, nrace, pos}, character{rng, enabled_features, nrace, pos},
room_num{gen_room_num}, abbrev{abbrev} { room_num{gen_room_num}, abbrev{abbrev} {}
base_hit_rate_reset = {1, 2};
}
int enemy_base::get_room_num() const { int enemy_base::get_room_num() const {
return room_num; return room_num;
@ -88,27 +86,21 @@ long_result enemy_base::get_hit(character *ch, const int tATK,
return {miss, "PC tried to hit " + abbrev + " but missed. "}; return {miss, "PC tried to hit " + abbrev + " but missed. "};
} }
void enemy_base::dies(level *lvl, character *pc) { int enemy_base::dies(level *lvl) {
auto &elist = lvl->get_elist();
for (size_t i = 0; i < elist.size(); ++i)
if (elist[i] == this) {
elist.erase(elist.begin() + i);
break;
}
if (race == race::rdragon) { if (race == race::rdragon) {
return; return 0;
} else if (race == race::rmerchant) { } else if (race == race::rmerchant) {
lvl->add_gold({GOLD_MERCHANT, pos}); lvl->add_gold({GOLD_MERCHANT, pos});
return 0;
} else if (race == race::rhuman) { } else if (race == race::rhuman) {
lvl->add_gold({GOLD_NORMAL, pos}); lvl->add_gold({GOLD_NORMAL, pos});
auto plist = lvl->get_available_around_all(pos); auto plist = lvl->get_available_around_all(pos);
lvl->add_gold({GOLD_NORMAL, lvl->add_gold({GOLD_NORMAL,
rng->get_rand_in_vector(plist)}); rng->get_rand_in_vector(plist)});
return 0;
} }
((player_base *)pc)->add_gold(rand_gold_drop(rng)); return rand_gold_drop(rng);
} }
enemy_base *get_enemy_at(const position &pos, const enemy_list &elist) { enemy_base *get_enemy_at(const position &pos, const enemy_list &elist) {

View File

@ -26,7 +26,7 @@ public:
virtual std::string get_abbrev() const override; virtual std::string get_abbrev() const override;
virtual void dies(level *lvl, character *pc); virtual int dies(level *lvl);
}; };
typedef std::vector<enemy_base *> enemy_list; typedef std::vector<enemy_base *> enemy_list;

View File

@ -15,7 +15,7 @@ game::game(const enum race starting_race,
the_world = false; the_world = false;
// TODO: add the other races // TODO: add the other races
init_player(rng, player, enabled_features, starting_race); player = init_player(rng, enabled_features, starting_race);
if (enabled_features & FEATURE_EXTRA_LEVELS) if (enabled_features & FEATURE_EXTRA_LEVELS)
max_level = rng->rand_between(MIN_LEVEL_CNT, MAX_LEVEL_CNT + 1); max_level = rng->rand_between(MIN_LEVEL_CNT, MAX_LEVEL_CNT + 1);
@ -73,8 +73,13 @@ character *game::move_enemies() {
if (player->is_dead()) if (player->is_dead())
return ch; return ch;
if (ch->is_dead()) if (ch->is_dead()) {
ch->dies(levels[curr_level].get(), player.get()); int g = ch->dies(levels[curr_level].get());
levels[curr_level]->erase_enemy(ch);
if (g)
levels[curr_level]->add_gold({g, ch->get_pos()});
}
msg += res.msg; msg += res.msg;
} }
@ -105,6 +110,8 @@ game_result game::run() {
std::to_string(curr_turn + 1) + std::to_string(curr_turn + 1) +
" turns!"}; " turns!"};
player->start_turn();
player->discard_level_effects(); player->discard_level_effects();
++curr_level; ++curr_level;
@ -112,6 +119,9 @@ game_result game::run() {
if (curr_level == levels.size()) if (curr_level == levels.size())
new_level(); new_level();
if (enabled_features & FEATURE_REVISIT)
player->set_pos(levels[curr_level]->get_up_stairs());
break; break;
} }
@ -123,6 +133,8 @@ game_result game::run() {
std::to_string(curr_turn + 1) + std::to_string(curr_turn + 1) +
" turns! Coward!"}; " turns! Coward!"};
player->start_turn();
player->discard_level_effects(); player->discard_level_effects();
--curr_level; --curr_level;
@ -142,13 +154,15 @@ game_result game::run() {
case unknown: case unknown:
case fine: case fine:
case applied_nothing: case applied_nothing:
++curr_turn;
return {in_game, ""}; return {in_game, ""};
default: default:
player->start_turn();
break; break;
} }
++curr_turn;
player->calc_effects(); player->calc_effects();
if (player->is_dead()) if (player->is_dead())
@ -164,7 +178,6 @@ game_result game::run() {
killer->get_race_name()}; killer->get_race_name()};
} }
++curr_turn;
return {in_game, ""}; return {in_game, ""};
} }

View File

@ -7,6 +7,9 @@ level::level(character *player, RNG *rng, const feature enabled_features):
player{player} { player{player} {
auto tiles = map.get_room_list(); auto tiles = map.get_room_list();
for (size_t i = 0; i < tiles.size(); ++i)
remove_from_list(tiles[i], player->get_pos());
for (size_t i = 0; i < tiles.size(); ++i) for (size_t i = 0; i < tiles.size(); ++i)
remove_from_list(tiles[i], map.get_down_stairs()); remove_from_list(tiles[i], map.get_down_stairs());
@ -26,6 +29,9 @@ level::level(const std::string &map_data, character *player, RNG *rng,
map{player, map_data, rng, enabled_features}, player{player} { map{player, map_data, rng, enabled_features}, player{player} {
auto tiles = map.get_room_list(); auto tiles = map.get_room_list();
for (size_t i = 0; i < tiles.size(); ++i)
remove_from_list(tiles[i], player->get_pos());
for (size_t i = 0; i < tiles.size(); ++i) for (size_t i = 0; i < tiles.size(); ++i)
remove_from_list(tiles[i], map.get_down_stairs()); remove_from_list(tiles[i], map.get_down_stairs());
@ -66,18 +72,25 @@ void level::gen_enemies(RNG *rng, std::vector<position_list> &tiles) {
spots.push_back(dhoard[i].pos + MOVE[i]); spots.push_back(dhoard[i].pos + MOVE[i]);
} }
pelist.push_back(nullptr); auto pos = spots.size() ? rng->get_rand_in_vector(spots) :
new_dragon(rng, pelist[i], rng->get_rand_in_vector(spots), dhoard[i].pos;
dhoard[i].pos, enabled_features, pelist.push_back(new_dragon(rng, pos, dhoard[i].pos,
map.which_room(dhoard[i].pos)); enabled_features,
map.which_room(dhoard[i].pos)));
int room = map.which_room(pos);
remove_from_list(tiles[room], pos);
if (!tiles[room].size())
tiles.erase(tiles.begin() + room);
elist.push_back(pelist[i].get()); elist.push_back(pelist[i].get());
} }
for (int i = dhoard.size(); i < cnt; ++i) { for (int i = dhoard.size(); i < cnt; ++i) {
pelist.push_back(nullptr);
auto p = get_rand_pos(rng, tiles); auto p = get_rand_pos(rng, tiles);
new_enemy(rng, pelist[i], p, enabled_features, map.which_room(p)); pelist.push_back(new_enemy(rng, p, enabled_features,
map.which_room(p)));
elist.push_back(pelist[i].get()); elist.push_back(pelist[i].get());
} }
} }
@ -123,10 +136,8 @@ void level::gen_potions(RNG *rng, std::vector<position_list> &tiles) {
pplist.reserve(cnt); pplist.reserve(cnt);
for (int i = 0; i < cnt; ++i) { for (int i = 0; i < cnt; ++i) {
pplist.push_back(nullptr); pplist.push_back(new_potion((potion_type)rng->rand_under(max_type),
new_potion(pplist[i], get_rand_pos(rng, tiles)));
(potion_type)rng->rand_under(max_type),
get_rand_pos(rng, tiles));
plist.push_back(pplist[i].get()); plist.push_back(pplist[i].get());
} }
} }
@ -172,6 +183,10 @@ bool level::is_available_all(const position &pos) const {
if (!map.is_available(pos)) if (!map.is_available(pos))
return false; return false;
if (!(enabled_features & FEATURE_DOORS) &&
map.which_room(pos) == -1)
return false;
if (player->get_pos() == pos) if (player->get_pos() == pos)
return false; return false;
@ -222,14 +237,35 @@ position level::get_down_stairs() const {
return map.get_down_stairs(); return map.get_down_stairs();
} }
enemy_list &level::get_elist() { const enemy_list &level::get_elist() {
return elist; return elist;
} }
potion_list &level::get_plist() { const potion_list &level::get_plist() {
return plist; return plist;
} }
gold_list &level::get_glist() { gold_list &level::get_glist() {
return glist; return glist;
} }
std::unique_ptr<potion> level::detach_potion(size_t idx) {
if (idx >= plist.size())
return nullptr;
auto tmp = std::move(pplist[idx]);
plist.erase(plist.begin() + idx);
pplist.erase(pplist.begin() + idx);
return tmp;
}
void level::erase_enemy(character *ch) {
if (ch == nullptr)
return;
for (size_t i = 0; i < elist.size(); ++i)
if (elist[i] == ch) {
elist.erase(elist.begin() + i);
pelist.erase(pelist.begin() + i);
}
}

View File

@ -45,11 +45,14 @@ public:
position get_down_stairs() const; position get_down_stairs() const;
// you can delete the pointers to the stuff // you can delete the pointers to the stuff
enemy_list &get_elist(); const enemy_list &get_elist();
potion_list &get_plist(); const potion_list &get_plist();
gold_list &get_glist(); gold_list &get_glist();
void add_gold(gold g); void add_gold(gold g);
void erase_enemy(character *ch);
std::unique_ptr<potion> detach_potion(size_t idx);
private: private:
// every gen will delete the positions in tiles // every gen will delete the positions in tiles
void gen_potions(RNG *rng, std::vector<position_list> &tiles); void gen_potions(RNG *rng, std::vector<position_list> &tiles);

17
src/output.cc Normal file
View File

@ -0,0 +1,17 @@
#include "output.h"
#include "constants.h"
output::output():
contents{DISPLAY_BUFFER_SIZE, 0} {
for (int i = 0; i < DISPLAY_BUFFER_SIZE; ++i)
contents.push_back(0);
}
void output::clear() {
contents.clear();
contents.reserve(DISPLAY_BUFFER_SIZE);
for (int i = 0; i < DISPLAY_BUFFER_SIZE; ++i)
contents.push_back(0);
}

35
src/output.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#include <ncurses.h>
#include <vector>
#include "position.h"
#include "cursor.h"
class output {
protected:
// use an array of ints to keep track of what's on the screen
// This will have a bit of buffer for the last line
// just in case it overflows
std::vector<int> contents;
public:
output();
virtual ~output() = default;
// Only call this to refresh the entire output
virtual void render() = 0;
// Clears the contents buffer
virtual void clear();
virtual void print_char(const position &pos, const char ch,
const int attrs =
A_NORMAL | COLOR_PAIR(COLOR_WHITE)) = 0;
virtual void print_str(const position &pos, const std::string &str,
const int attrs =
A_NORMAL | COLOR_PAIR(COLOR_WHITE)) = 0;
// default arguments are to be set in the base class's declaration
};
#endif

View File

@ -0,0 +1,100 @@
#include "console_output.h"
#include <string>
#include <utility>
#include <ncurses.h>
#include "../constants.h"
console_output::console_output(std::ostream &cout): out{cout} {}
/* Attributes
black 30 40
red 31 41
green 32 42
yellow 33 43
blue 34 44
magenta 35 45
cyan 36 46
white 37 47
reset 0 (everything back to normal)
bold/bright 1 (often a brighter shade of the same colour)
underline 4
inverse 7 (swap foreground and background colours)
bold/bright off 21
underline off 24
inverse off 27
Format:
\033[X;Ym
X Y are numbers from above
*/
std::string console_output::get_code(const int attrs) {
if (attrs < 255)
return "\033[0m";
std::string result = "\033[0m\033[";
if (attrs & A_BOLD)
result += "1;";
if (attrs & A_UNDERLINE)
result += "4;";
if (attrs & A_STANDOUT)
result += "7;";
if ((attrs & COLOR_PAIR(COLOR_WHITE)) == COLOR_PAIR(COLOR_WHITE))
result += "37;";
else if ((attrs & COLOR_PAIR(COLOR_CYAN)) == COLOR_PAIR(COLOR_CYAN))
result += "36;";
else if ((attrs & COLOR_PAIR(COLOR_MAGENTA)) == COLOR_PAIR(COLOR_MAGENTA))
result += "35;";
else if ((attrs & COLOR_PAIR(COLOR_BLUE)) == COLOR_PAIR(COLOR_BLUE))
result += "34;";
else if ((attrs & COLOR_PAIR(COLOR_YELLOW)) == COLOR_PAIR(COLOR_YELLOW))
result += "33;";
else if ((attrs & COLOR_PAIR(COLOR_RED)) == COLOR_PAIR(COLOR_RED))
result += "31;";
else if ((attrs & COLOR_PAIR(COLOR_GREEN)) == COLOR_PAIR(COLOR_GREEN))
result += "32;";
else if ((attrs & COLOR_PAIR(COLOR_BLACK_ON_WHITE)) == COLOR_BLACK_ON_WHITE)
result += "30;47;";
result[result.length() - 1] = 'm';
return result;
}
void console_output::render() {
out << "\x1B[2J\x1B[H";
for (std::size_t idx = 0; idx < contents.size(); ++idx) {
if (idx % DISPLAY_WIDTH == 0 && idx)
out << std::endl;
out << get_code(contents[idx])
<< (char)(contents[idx] ? contents[idx] : ' ');
}
out << std::endl;
}
void console_output::print_char(const position &pos, const char ch,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
contents[pos.y * DISPLAY_WIDTH + pos.x] = attrs | ch;
}
void console_output::print_str(const position &pos, const std::string &str,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
int head = pos.y * DISPLAY_WIDTH + pos.x;
for (std::size_t i = 0; i < str.length(); ++i)
contents[i + head] = attrs | str[i];
}

View File

@ -0,0 +1,21 @@
#ifndef __CONSOLE_OUTPUT_H__
#define __CONSOLE_OUTPUT_H__
#include <iostream>
#include "../output.h"
class console_output final : public output {
private:
std::ostream &out;
std::string get_code(const int attrs);
public:
console_output(std::ostream &cout);
void render() override;
void print_char(const position &pos,
const char ch, const int attrs) override;
void print_str(const position &pos,
const std::string &str, const int attrs) override;
};
#endif

View File

@ -0,0 +1,38 @@
#include "curses_output.h"
#include "../constants.h"
curses_output::curses_output(cursor *new_curse):
curse{new_curse} {}
void curses_output::render() {
curse->show();
}
void curses_output::clear() {
curse->clear();
}
void curses_output::print_char(const position &pos, const char ch,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
curse->print_char(pos, ch, attrs);
}
void curses_output::print_str(const position &pos, const std::string &str,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
position tmp = pos;
for (std::size_t i = 0; i < str.length(); ++i) {
curse->print_char(tmp, str[i], attrs);
tmp += {1, 0};
if (tmp.x >= DISPLAY_WIDTH)
tmp = {0, tmp.y + 1};
}
}

View File

@ -0,0 +1,23 @@
#ifndef __CURSES_OUTPUT_H__
#define __CURSES_OUTPUT_H__
#include <utility>
#include <memory>
#include "../cursor.h"
#include "../output.h"
class curses_output final : public output {
private:
cursor *curse;
public:
curses_output(cursor *new_curse);
void render() override;
void clear() override;
void print_char(const position &pos,
const char ch, const int attrs) override;
void print_str(const position &pos,
const std::string &str, const int attrs) override;
};
#endif

38
src/output/file_output.cc Normal file
View File

@ -0,0 +1,38 @@
#include "file_output.h"
#include <utility>
#include "../constants.h"
file_output::file_output(std::ofstream &&ofs):
out{std::move(ofs)} {}
void file_output::render() {
for (std::size_t idx = 0; idx < contents.size(); ++idx) {
if (idx % DISPLAY_WIDTH == 0 && idx)
out << std::endl;
out << (char)(contents[idx] ? contents[idx] : ' ');
}
out << std::endl;
}
void file_output::print_char(const position &pos, const char ch,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
contents[pos.y * DISPLAY_WIDTH + pos.x] = ch;
}
void file_output::print_str(const position &pos, const std::string &str,
const int attrs) {
if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT)
return;
int head = pos.y * DISPLAY_WIDTH + pos.x;
for (std::size_t i = 0; i < str.length(); ++i)
contents[i + head] = str[i];
}

20
src/output/file_output.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __FILE_OUTPUT_H__
#define __FILE_OUTPUT_H__
#include <fstream>
#include "../output.h"
class file_output final : public output {
private:
std::ofstream out;
public:
file_output(std::ofstream &&ofs);
void render() override;
void print_char(const position &pos,
const char ch, const int attrs) override;
void print_str(const position &pos,
const std::string &str, const int attrs) override;
};
#endif

View File

@ -6,35 +6,40 @@
#include "player/shade.h" #include "player/shade.h"
#include "player/troll.h" #include "player/troll.h"
#include "player/vampire.h" #include "player/vampire.h"
#include "player/t_800.h"
void init_player(RNG *rng, std::unique_ptr<player_base> &pc, std::unique_ptr<player_base> init_player(RNG *rng,
const feature enabled_features, const enum race &r) { const feature enabled_features, const enum race &r) {
using std::make_unique; using std::make_unique;
pc = nullptr;
switch (r) { switch (r) {
case rgoblin: case rgoblin:
pc = make_unique<goblin>(rng, enabled_features); return make_unique<goblin>(rng, enabled_features);
break; break;
case rdrow: case rdrow:
pc = make_unique<drow>(rng, enabled_features); return make_unique<drow>(rng, enabled_features);
break; break;
case rshade: case rshade:
pc = make_unique<shade>(rng, enabled_features); return make_unique<shade>(rng, enabled_features);
break; break;
case rtroll: case rtroll:
pc = make_unique<troll>(rng, enabled_features); return make_unique<troll>(rng, enabled_features);
break; break;
case rvampire: case rvampire:
pc = make_unique<vampire>(rng, enabled_features); return make_unique<vampire>(rng, enabled_features);
break;
case rt_800:
return make_unique<t_800>(rng, enabled_features);
break; break;
default: default:
break; break;
} }
return nullptr;
} }

View File

@ -4,7 +4,7 @@
#include <memory> #include <memory>
#include "player.h" #include "player.h"
void init_player(RNG *rng, std::unique_ptr<player_base> &pc, std::unique_ptr<player_base> init_player(RNG *rng,
const feature enabled_features, const enum race &r); const feature enabled_features, const enum race &r);
#endif #endif

View File

@ -32,15 +32,21 @@ int player_base::get_HP() const {
return this->HP; return this->HP;
} }
long_result player_base::apply(potion *p) { long_result player_base::apply(std::unique_ptr<potion> p) {
if (p == nullptr) if (p == nullptr)
return {result::applied_nothing, return {result::applied_nothing,
"PC tried to breathe the magic in the air. "}; "PC tried to breathe the magic in the air. "};
apply_effect(p); std::string name = p->get_name();
apply_effect(std::move(p));
if (race == rt_800)
return {result::applied,
"PC gets rusty joints (-50 HP). "};
return {result::applied, return {result::applied,
(std::string)"PC applied potion of " + p->get_name() + ". "}; "PC applied potion of " + name + ". "};
} }
long_result player_base::attack(character *ch) { long_result player_base::attack(character *ch) {
@ -62,8 +68,15 @@ long_result player_base::move(level *lvl,
} else if ((tmp = get_enemy_at(p, lvl->get_elist())) != nullptr) { } else if ((tmp = get_enemy_at(p, lvl->get_elist())) != nullptr) {
auto res = attack((character *)tmp); auto res = attack((character *)tmp);
if (tmp->is_dead()) if (tmp->is_dead()) {
tmp->dies(lvl, this); int g = tmp->dies(lvl);
lvl->erase_enemy(tmp);
if (g)
res.msg += "PC gains " +
std::to_string(g) +
" pieces of gold. ";
}
return res; return res;
} }
@ -112,19 +125,18 @@ long_result player_base::interpret_command(level *lvl, game_command cmd) {
} else if (cmd >= move_north && cmd <= move_southwest) { } else if (cmd >= move_north && cmd <= move_southwest) {
auto res = move(lvl, pos + MOVE[cmd - move_north]); auto res = move(lvl, pos + MOVE[cmd - move_north]);
if (res.res == result::moved)
start_turn();
gold g = get_gold_at(pos, lvl->get_glist()); gold g = get_gold_at(pos, lvl->get_glist());
gold_cnt += g.amount; gold_cnt += g.amount;
if (g.amount)
res.msg += "PC picked up " + std::to_string(g.amount) +
" pieces of gold. ";
return res; return res;
} else if (cmd >= apply_north && cmd <= apply_southwest) { } else if (cmd >= apply_north && cmd <= apply_southwest) {
auto res = apply(get_potion_at(pos + MOVE[cmd - apply_north], size_t idx = get_potion_at(pos + MOVE[cmd - apply_north],
lvl->get_plist())); lvl->get_plist());
auto res = apply(lvl->detach_potion(idx));
if (res.res == result::applied)
start_turn();
return res; return res;
} else if (cmd == apply_panic) { } else if (cmd == apply_panic) {
return {result::fine, return {result::fine,
@ -133,29 +145,29 @@ long_result player_base::interpret_command(level *lvl, game_command cmd) {
enemy_base *tmp = get_enemy_at(pos + MOVE[cmd - attack_north], enemy_base *tmp = get_enemy_at(pos + MOVE[cmd - attack_north],
lvl->get_elist()); lvl->get_elist());
if (tmp != nullptr)
start_turn();
auto res = attack((character *)tmp); auto res = attack((character *)tmp);
if (tmp != nullptr && tmp->is_dead()) if (tmp != nullptr && tmp->is_dead()) {
tmp->dies(lvl, this); int g = tmp->dies(lvl);
lvl->erase_enemy(tmp);
if (g)
res.msg += "PC gains " +
std::to_string(g) +
" pieces of gold. ";
}
return res; return res;
} else if (cmd == up_stairs) { } else if (cmd == up_stairs) {
if (lvl->get_up_stairs() == pos && if (lvl->get_up_stairs() == pos &&
enabled_features & FEATURE_REVISIT) { enabled_features & FEATURE_REVISIT)
start_turn();
return {go_up, "PC went up the stairs. "}; return {go_up, "PC went up the stairs. "};
}
return {result::fine, return {result::fine,
"PC tried to fly through the ceiling. "}; "PC tried to fly through the ceiling. "};
} else if (cmd == down_stairs) { } else if (cmd == down_stairs) {
if (lvl->get_down_stairs() == pos) { if (lvl->get_down_stairs() == pos)
start_turn();
return {go_down, "PC went down the stairs. "}; return {go_down, "PC went down the stairs. "};
}
return {result::fine, return {result::fine,
"PC tried to dig through the floor. "}; "PC tried to dig through the floor. "};

View File

@ -8,13 +8,13 @@ enum game_command : int;
class player_base: public character { class player_base: public character {
protected: protected:
int gold_cnt; int gold_cnt;
potion_list potions; potion_own_list potions;
public: public:
player_base(RNG *rng, const feature enabled_features, player_base(RNG *rng, const feature enabled_features,
const enum race &nrace); const enum race &nrace);
virtual long_result move(level *lvl, const position &p); virtual long_result move(level *lvl, const position &p);
virtual long_result apply(potion *p); virtual long_result apply(std::unique_ptr<potion> p);
virtual long_result attack(character *ch) override; virtual long_result attack(character *ch) override;
virtual long_result get_hit(character *ch, const int tATK, virtual long_result get_hit(character *ch, const int tATK,

15
src/player/t_800.cc Normal file
View File

@ -0,0 +1,15 @@
#include "t_800.h"
#include "../constants.h"
t_800::t_800(RNG *rng, const feature enabled_features):
player_base{rng, enabled_features, race::rt_800} {}
const char *t_800::get_race_name() const {
return "T-800";
}
void t_800::apply_effect(std::unique_ptr<potion> p) {
HP -= RUSTY;
return;
}

15
src/player/t_800.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __T_800_H__
#define __T_800_H__
#include "../player.h"
class potion;
class t_800 final: public player_base {
static const int RUSTY = 50;
public:
t_800(RNG *rng, const feature enabled_features);
const char *get_race_name() const override;
void apply_effect(std::unique_ptr<potion> p) override;
};
#endif

View File

@ -47,15 +47,10 @@ void potion::print(output *out) {
out->print_char(pos, 'P', COLOR_PAIR(COLOR_GREEN)); out->print_char(pos, 'P', COLOR_PAIR(COLOR_GREEN));
} }
potion *get_potion_at(const position &pos, potion_list &plist) { size_t get_potion_at(const position &pos, const potion_list &plist) {
potion *ret = nullptr;
for (size_t i = 0; i < plist.size(); ++i) for (size_t i = 0; i < plist.size(); ++i)
if (plist[i]->get_pos() == pos) { if (plist[i]->get_pos() == pos)
ret = plist[i]; return i;
plist.erase(plist.begin() + i);
return ret;
}
return ret; return plist.size();
} }

View File

@ -2,6 +2,7 @@
#define __POTION_H__ #define __POTION_H__
#include <vector> #include <vector>
#include <memory>
#include "fraction.h" #include "fraction.h"
#include "output.h" #include "output.h"
@ -40,7 +41,8 @@ public:
}; };
typedef std::vector<potion *> potion_list; typedef std::vector<potion *> potion_list;
typedef std::vector<std::unique_ptr<potion> > potion_own_list;
potion *get_potion_at(const position &pos, potion_list &plist); size_t get_potion_at(const position &pos, const potion_list &plist);
#endif #endif

View File

@ -8,34 +8,35 @@
#include "potions/wound_atk.h" #include "potions/wound_atk.h"
#include "potions/wound_def.h" #include "potions/wound_def.h"
void new_potion(std::unique_ptr<potion> &pp, potion_type type, std::unique_ptr<potion> new_potion(potion_type type, const position &pos) {
const position &pos) {
switch (type) { switch (type) {
case restore_health: case restore_health:
pp = std::make_unique<class restore_health>(pos); return std::make_unique<class restore_health>(pos);
break; break;
case boost_atk: case boost_atk:
pp = std::make_unique<class boost_atk>(pos); return std::make_unique<class boost_atk>(pos);
break; break;
case boost_def: case boost_def:
pp = std::make_unique<class boost_def>(pos); return std::make_unique<class boost_def>(pos);
break; break;
case poison_health: case poison_health:
pp = std::make_unique<class poison_health>(pos); return std::make_unique<class poison_health>(pos);
break; break;
case wound_atk: case wound_atk:
pp = std::make_unique<class wound_atk>(pos); return std::make_unique<class wound_atk>(pos);
break; break;
case wound_def: case wound_def:
pp = std::make_unique<class wound_def>(pos); return std::make_unique<class wound_def>(pos);
break; break;
default: default:
break; break;
} }
return nullptr;
} }

View File

@ -6,7 +6,6 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
void new_potion(std::unique_ptr<potion> &pp, potion_type type, std::unique_ptr<potion> new_potion(potion_type type, const position &pos);
const position &pos);
#endif #endif