Skip to content

Commit 51ed3f0

Browse files
authored
Merge pull request #19 from cpp-gamedev/issue-17
Finish Base Game Features
2 parents 506dd9d + a96ce80 commit 51ed3f0

File tree

7 files changed

+187
-61
lines changed

7 files changed

+187
-61
lines changed

src/anim.cpp

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
#include <algorithm>
2-
#include <filesystem>
3-
#include <iostream>
4-
#include <numeric>
5-
#include <string>
6-
#include <vector>
7-
81
#include <str_format/str_format.hpp>
92

3+
#include <array>
4+
105
#include "models.hpp"
116
#include "utils.hpp"
127

@@ -46,19 +41,50 @@ void print_splash_screen(const std::filesystem::path& assets_dir)
4641

4742
std::cout << '\n' << std::setfill(' ') << std::setw(19);
4843

49-
for (const char& c : "copyright (c) 2021 cpp-gamedev")
44+
utils::slow_print("copyright (c) 2021 cpp-gamedev", std::chrono::milliseconds{50});
45+
}
46+
47+
void print_move_table(const models::Pokemon& pkmn)
48+
{
49+
// +----------------------------------------------------------+
50+
// | MOVE POWER ACCURACY |
51+
// | 1. move1 100 90 |
52+
// | first move description |
53+
// | 2. move2 90 50 |
54+
// | second move description |
55+
// | 3. move3 120 10 |
56+
// | third move description |
57+
// | 4. move4 30 70 |
58+
// | fourth move description |
59+
// +----------------------------------------------------------+
60+
61+
constexpr int width = 60;
62+
utils::Color border_color = utils::Color::YELLOW;
63+
std::string border = utils::style(std::string("|"), border_color);
64+
std::string horizontal_line = utils::style(std::string("+").append(std::string(width - 2, '-')).append("+"), border_color);
65+
66+
std::cout << horizontal_line << '\n';
67+
std::cout << border << std::string(4, ' ') << "MOVE" << std::string(32, ' ') << "POWER" << std::string(4, ' ') << "ACCURACY" << ' ' << border << '\n';
68+
69+
for (std::size_t i = 0; i < pkmn.move_set.size(); ++i)
5070
{
51-
std::cout << c;
52-
utils::sleep(100);
71+
// move name, power and accuracy
72+
std::string name = kt::format_str("{}. {}", i + 1, pkmn.move_set[i].name);
73+
std::cout << border << ' ' << utils::style(name, utils::Color::GREEN) << std::setfill(' ') << std::setw(width - 7 - name.length())
74+
<< utils::style(std::to_string(pkmn.move_set[i].power), utils::Color::RED) << std::setfill(' ') << std::setw(21)
75+
<< utils::style(std::to_string(pkmn.move_set[i].accuracy), utils::Color::CYAN) << ' ' << border << '\n';
76+
// flavor text
77+
std::cout << border << std::string(4, ' ') << pkmn.move_set[i].flavor_text << std::setfill(' ') << std::setw(width + 4 - pkmn.move_set[i].flavor_text.length())
78+
<< border << '\n';
5379
}
80+
81+
std::cout << horizontal_line << '\n';
5482
}
5583

56-
std::vector<models::Pokemon> load_main_menu(utils::Manifest manifest)
84+
std::array<models::Pokemon, 2> load_main_menu(const utils::Manifest& manifest)
5785
{
5886
// 1. set difficulty
59-
int selection{};
60-
utils::print_enum_table({"easy", "moderate", "hard"}, "difficulty");
61-
selection = utils::get_user_input<int>(">>> ");
87+
int selection{utils::validate_user_input({"easy", "moderate", "hard"}, "(1/2) SET DIFFICULTY")};
6288
auto difficulty = (selection == 1) ? models::Difficulty::EASY : (selection == 2) ? models::Difficulty::MODERATE : models::Difficulty::HARD;
6389

6490
// 2. instantiate all available pokemons
@@ -71,17 +97,15 @@ std::vector<models::Pokemon> load_main_menu(utils::Manifest manifest)
7197
std::vector<std::string> names{};
7298
names.reserve(pkmns.size());
7399
std::for_each(pkmns.begin(), pkmns.end(), [&names](models::Pokemon& pkmn) { names.push_back(pkmn.name); });
74-
utils::print_enum_table(names, "pokemons");
75-
selection = utils::get_user_input<int>(">>> ");
100+
selection = utils::validate_user_input(names, "(2/2) CHOOSE A POKEMON");
76101
models::Pokemon player = pkmns[selection - 1];
77102

78103
// 4. remove selection from pkmns, so that player won't fight against his doppelganger
79104
pkmns.erase(std::remove_if(pkmns.begin(), pkmns.end(), [player](models::Pokemon pkmn) { return player.id == pkmn.id; }), pkmns.end());
80-
81105
return {player, pkmns.size() > 1 ? utils::random_choice(pkmns) : pkmns[0]};
82106
}
83107

84-
void print_frame(const models::Pokemon& pkmn1, const models::Pokemon& pkmn2)
108+
int print_frame(const models::Pokemon& pkmn1, const models::Pokemon& pkmn2)
85109
{
86110
std::string healthbars{};
87111
std::string sprites{};
@@ -94,20 +118,34 @@ void print_frame(const models::Pokemon& pkmn1, const models::Pokemon& pkmn2)
94118
if (padding > 30)
95119
padding -= 2 * (padding - 30);
96120

121+
// gather healthbars
97122
for (std::size_t i = 0; i < 3; i++)
98123
healthbars.append(healthbar1[i]).append(std::string(padding, ' ')).append(healthbar2[i]).append("\n");
99124

100-
std::cout << healthbars;
101-
125+
// gather sprites
102126
for (std::size_t i = 0; i < 20; i++)
103127
{
104128
std::string tmp = pkmn1.sprite[i]; // strip new line
105129
tmp.erase(std::remove(tmp.begin(), tmp.end(), '\n'), tmp.end());
106130
sprites.append(tmp).append(std::string(20, ' ')).append(pkmn2.sprite[i]);
107131
}
108132

109-
std::cout << sprites;
133+
int answer{};
134+
std::vector<int> choices(pkmn1.move_set.size());
135+
std::iota(choices.begin(), choices.end(), 1);
136+
137+
// read and return user-selected move
138+
while (std::find(choices.begin(), choices.end(), answer) == choices.end())
139+
{
140+
utils::clear_screen();
141+
std::cout << healthbars;
142+
std::cout << sprites;
143+
print_move_table(pkmn1);
144+
answer = utils::get_user_input<int>(">>> ");
145+
std::cin.clear();
146+
std::cin.ignore(utils::str_max, '\n');
147+
}
110148

111-
return;
149+
return answer;
112150
}
113151
} // namespace anim

src/anim.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#pragma once
22

3-
#include<string>
4-
#include<vector>
3+
#include <array>
4+
#include <string>
5+
#include <vector>
56

67
#include "models.hpp"
78

@@ -11,7 +12,9 @@ std::vector<std::string> gen_healthbar(const models::Pokemon& pkmn);
1112

1213
void print_splash_screen(const std::filesystem::path& assets_dir);
1314

14-
std::vector<models::Pokemon> load_main_menu(utils::Manifest manifest);
15+
void print_move_table(const models::Pokemon& pkmn);
1516

16-
void print_frame(const models::Pokemon& pkmn1, const models::Pokemon& pkmn2);
17+
std::array<models::Pokemon, 2> load_main_menu(const utils::Manifest& manifest);
18+
19+
int print_frame(const models::Pokemon& pkmn1, const models::Pokemon& pkmn2);
1720
} // namespace anim

src/main.cpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
#include <algorithm>
2-
#include <filesystem>
3-
#include <iostream>
4-
#include <numeric>
5-
#include <string>
6-
71
#include "anim.hpp"
82
#include "models.hpp"
93
#include "utils.hpp"
104

5+
using namespace std::chrono_literals;
6+
117
using namespace anim;
128
using namespace models;
139
using namespace utils;
@@ -23,14 +19,23 @@ int main()
2319
clear_screen();
2420

2521
auto pkmns = load_main_menu(manifest);
26-
sleep(1000);
22+
auto& [player, ai] = pkmns;
2723
clear_screen();
2824

29-
std::cout << "TODO: init game loop" << '\n';
30-
sleep(1000);
31-
clear_screen();
25+
while (player.hp > 0 && ai.hp > 0)
26+
{
27+
player.make_move(ai, print_frame(player, ai));
3228

33-
print_frame(pkmns[0], pkmns[1]);
29+
if (ai.hp > 0)
30+
{
31+
sleep(1000ms);
32+
ai.make_move(player, random_range<std::size_t>(1, 4));
33+
clear_screen();
34+
}
35+
}
36+
37+
clear_screen();
38+
slow_print((ai.hp == 0) ? "You Won :)" : "You Lost :(", 50ms);
3439

3540
return EXIT_SUCCESS;
3641
}

src/models.cpp

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
#include <algorithm>
2-
#include <numeric>
3-
#include <vector>
4-
51
#include <str_format/str_format.hpp>
62
#include <dumb_json/json.hpp>
73

@@ -18,14 +14,24 @@ std::vector<std::string> Pokemon::read_asset(std::string ext)
1814

1915
void Pokemon::configure_move_set()
2016
{
21-
this->move_set = utils::random_choices(this->all_moves, 4);
17+
this->move_set = utils::random_choices(this->all_moves, 3);
18+
// ensure that at least one move within the move set will be able to deal damage
19+
auto iterator = std::find_if(this->all_moves.begin(), this->all_moves.end(), [&](const Move& move) { return move.power > 0; });
20+
if (iterator != this->all_moves.end())
21+
{
22+
this->move_set.push_back(*iterator);
23+
}
24+
2225

2326
for (Move& move : this->move_set)
2427
{
25-
if (move.power >= 20 && move.accuracy > 0)
28+
// NOTE: flavor_text can never be longer than 54 chars, or else it breaks the table formatting in print_move_table @anim.cpp
29+
30+
if (move.power > 0 && move.accuracy > 0)
2631
{
2732
move.type = MoveType::ATTACK;
2833
move.power += this->difficulty == Difficulty::EASY ? -20 : (this->difficulty == Difficulty::MODERATE) ? 0 : 20;
34+
move.power = abs(move.power);
2935
move.flavor_text = kt::format_str("{} deals {} points in damage.", move.name, move.power);
3036
}
3137
else if (move.accuracy == 0)
@@ -39,14 +45,18 @@ void Pokemon::configure_move_set()
3945
{
4046
move.type = MoveType::HEAL;
4147
move.accuracy = 100;
42-
move.power = utils::random_range<int>(4, 9) * 10;
48+
move.power = utils::random_range<int>(2, 5) * 10;
4349
move.flavor_text = kt::format_str("Increase your HP by {} points.", move.power);
4450
}
4551
}
4652
}
4753

4854
Pokemon::Pokemon(int id, std::filesystem::path assets_dir, Difficulty difficulty) : assets_dir{assets_dir}, id{id}, difficulty{difficulty}
4955
{
56+
// base level ranges
57+
constexpr int min = 15;
58+
constexpr int max = 33;
59+
5060
this->sprite = read_asset("txt");
5161
auto lines = read_asset("json");
5262
this->json_str = std::accumulate(lines.begin(), lines.end(), std::string(""));
@@ -55,8 +65,8 @@ Pokemon::Pokemon(int id, std::filesystem::path assets_dir, Difficulty difficulty
5565
{
5666
this->id = id;
5767
this->name = json["name"].as<std::string>();
58-
this->level = json["level"].as<int>();
59-
this->hp = json["hp"].as<int>();
68+
this->level = utils::random_range<int>(max * static_cast<int>(difficulty) - min, max * static_cast<int>(difficulty));
69+
this->hp = 2 * this->level * utils::random_range<double>(0.8, 1.2);
6070
this->max_hp = this->hp;
6171
this->atk = json["atk"].as<int>();
6272
this->def = json["def"].as<int>();
@@ -83,4 +93,44 @@ Pokemon::Pokemon(int id, std::filesystem::path assets_dir, Difficulty difficulty
8393
this->configure_move_set();
8494
}
8595
}
96+
97+
void Pokemon::make_move(Pokemon& pkmn, std::size_t index)
98+
{
99+
std::string msg{};
100+
const Move& move = this->move_set[index - 1];
101+
102+
switch (move.type)
103+
{
104+
case MoveType::ATTACK:
105+
if (utils::random_range<int>(0, 100) <= move.accuracy)
106+
{
107+
int damage = std::ceil(move.power * (this->atk * 100) / (100 * pkmn.def));
108+
pkmn.hp -= damage;
109+
pkmn.hp = (pkmn.hp < 0) ? 0 : pkmn.hp;
110+
msg = kt::format_str("{} used {} and inflicts {} points in damage!", this->name, move.name, damage);
111+
}
112+
else
113+
{
114+
msg = kt::format_str("{} missed his target!", this->name);
115+
}
116+
break;
117+
case MoveType::HEAL:
118+
this->hp += move.power;
119+
this->hp = (this->hp > this->max_hp) ? this->max_hp : this->hp;
120+
msg = kt::format_str("{} increased his HP by {} points", this->name, move.power);
121+
break;
122+
case MoveType::BOOST_ATK:
123+
this->atk += move.power;
124+
msg = kt::format_str("{} increased his ATTACK by {}%!", this->name, move.power);
125+
break;
126+
case MoveType::BOOST_DEF:
127+
this->def += move.power;
128+
msg = kt::format_str("{} increased his DEFENSE by {}%!", this->name, move.power);
129+
break;
130+
default:
131+
break;
132+
}
133+
134+
utils::slow_print(msg, std::chrono::milliseconds{50});
135+
}
86136
} // namespace models

src/models.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct Move
2929

3030
enum class Difficulty
3131
{
32+
NONE,
3233
EASY,
3334
MODERATE,
3435
HARD
@@ -57,5 +58,7 @@ struct Pokemon
5758
Difficulty difficulty{};
5859

5960
Pokemon(int id, std::filesystem::path assets_dir, Difficulty difficulty = Difficulty::MODERATE);
61+
62+
void make_move(Pokemon& pkmn, std::size_t index);
6063
};
6164
} // namespace models

0 commit comments

Comments
 (0)