diff options
Diffstat (limited to '2015/src')
| -rw-r--r-- | 2015/src/day22.cpp | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/2015/src/day22.cpp b/2015/src/day22.cpp new file mode 100644 index 0000000..85b7259 --- /dev/null +++ b/2015/src/day22.cpp | |||
| @@ -0,0 +1,560 @@ | |||
| 1 | #include <algorithm> | ||
| 2 | #include <fstream> | ||
| 3 | #include <iostream> | ||
| 4 | #include <list> | ||
| 5 | #include <memory> | ||
| 6 | #include <sstream> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #undef NDEBUG | ||
| 10 | #include <cassert> | ||
| 11 | |||
| 12 | using namespace std; | ||
| 13 | |||
| 14 | #if 0 | ||
| 15 | auto& debug = cerr; | ||
| 16 | #else | ||
| 17 | struct NullBuffer : streambuf { | ||
| 18 | int overflow(int chr) override { return chr; } | ||
| 19 | }; | ||
| 20 | NullBuffer nullBuffer; | ||
| 21 | ostream debug{ &nullBuffer }; | ||
| 22 | #endif | ||
| 23 | |||
| 24 | using Mana = int; | ||
| 25 | using HitPoints = int; | ||
| 26 | using Damage = int; | ||
| 27 | using Health = int; | ||
| 28 | using Armor = int; | ||
| 29 | |||
| 30 | class Player; | ||
| 31 | class Boss; | ||
| 32 | class Spell; | ||
| 33 | |||
| 34 | shared_ptr<Spell> chooseRandomSpell(Player& player); | ||
| 35 | |||
| 36 | class Spell { | ||
| 37 | protected: | ||
| 38 | Spell(string_view name, Mana mana) | ||
| 39 | : name_(name) | ||
| 40 | , mana_(mana) | ||
| 41 | { | ||
| 42 | } | ||
| 43 | |||
| 44 | public: | ||
| 45 | Spell(const Spell&) = default; | ||
| 46 | Spell(Spell&&) = delete; | ||
| 47 | Spell& operator=(const Spell&) = delete; | ||
| 48 | Spell& operator=(Spell&&) = delete; | ||
| 49 | virtual ~Spell() = default; | ||
| 50 | |||
| 51 | virtual void Play(Player& player, Boss& boss) = 0; | ||
| 52 | |||
| 53 | virtual int Timer() | ||
| 54 | { | ||
| 55 | return 0; | ||
| 56 | } | ||
| 57 | |||
| 58 | const string name_; | ||
| 59 | const Mana mana_; | ||
| 60 | }; | ||
| 61 | |||
| 62 | class Player { | ||
| 63 | public: | ||
| 64 | // NOLINTBEGIN | ||
| 65 | Player(Mana mana, HitPoints hit_points) | ||
| 66 | : mana_(mana) | ||
| 67 | , hit_points_(hit_points) | ||
| 68 | { | ||
| 69 | } | ||
| 70 | |||
| 71 | void AddMana(Mana mana) | ||
| 72 | { | ||
| 73 | assert(mana > 0); | ||
| 74 | mana_ += mana; | ||
| 75 | } | ||
| 76 | |||
| 77 | void SubMana(Mana mana) | ||
| 78 | { | ||
| 79 | assert(mana > 0); | ||
| 80 | mana_ -= mana; | ||
| 81 | } | ||
| 82 | |||
| 83 | void AddArmor(Armor armor) | ||
| 84 | { | ||
| 85 | assert(armor > 0); | ||
| 86 | armor_ += armor; | ||
| 87 | } | ||
| 88 | |||
| 89 | void SubArmor(Armor armor) | ||
| 90 | { | ||
| 91 | assert(armor > 0); | ||
| 92 | armor_ -= armor; | ||
| 93 | } | ||
| 94 | |||
| 95 | void AddHitPoints(HitPoints hit_points) | ||
| 96 | { | ||
| 97 | assert(hit_points > 0); | ||
| 98 | hit_points_ += hit_points; | ||
| 99 | } | ||
| 100 | |||
| 101 | void SubHitPoints(HitPoints hit_points) | ||
| 102 | { | ||
| 103 | assert(hit_points > 0); | ||
| 104 | hit_points_ -= hit_points; | ||
| 105 | } | ||
| 106 | // NOLINTEND | ||
| 107 | |||
| 108 | void Damage(Damage damage) | ||
| 109 | { | ||
| 110 | SubHitPoints(max(1, damage - armor_)); | ||
| 111 | } | ||
| 112 | |||
| 113 | [[nodiscard]] bool CanCastSpell(const shared_ptr<Spell>& spell) const | ||
| 114 | { | ||
| 115 | return mana_ >= spell->mana_; | ||
| 116 | } | ||
| 117 | |||
| 118 | [[nodiscard]] bool HasLost() const | ||
| 119 | { | ||
| 120 | return hit_points_ <= 0; | ||
| 121 | } | ||
| 122 | |||
| 123 | Mana mana_; | ||
| 124 | HitPoints hit_points_; | ||
| 125 | Armor armor_{}; | ||
| 126 | }; | ||
| 127 | |||
| 128 | class Spells { | ||
| 129 | public: | ||
| 130 | Spells() = default; | ||
| 131 | |||
| 132 | void AddSpell(const shared_ptr<Spell>& spell, Player& player, Boss& boos) | ||
| 133 | { | ||
| 134 | debug << "Player casts " << spell->name_ << endl; | ||
| 135 | player.SubMana(spell->mana_); | ||
| 136 | |||
| 137 | if ( spell->Timer() == 0 ) { | ||
| 138 | spell->Play(player, boos); | ||
| 139 | } | ||
| 140 | else { | ||
| 141 | effects_.push_back(spell); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | bool TryAddRandomSpell(Player& player, Boss& boss) | ||
| 146 | { | ||
| 147 | while ( true ) { | ||
| 148 | auto randomSpell = chooseRandomSpell(player); | ||
| 149 | if ( randomSpell == nullptr ) { | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | auto iter = ranges::find_if(effects_, [&randomSpell](const auto& effect) { return effect->name_ == randomSpell->name_; }); | ||
| 154 | if ( iter != effects_.end() ) { | ||
| 155 | continue; | ||
| 156 | } | ||
| 157 | |||
| 158 | AddSpell(randomSpell, player, boss); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | void Play(Player& player, Boss& boss) | ||
| 163 | { | ||
| 164 | for ( const auto& effect: effects_ ) { | ||
| 165 | effect->Play(player, boss); | ||
| 166 | } | ||
| 167 | |||
| 168 | effects_.remove_if([](const auto& effect) { | ||
| 169 | return effect->Timer() == 0; | ||
| 170 | }); | ||
| 171 | } | ||
| 172 | |||
| 173 | private: | ||
| 174 | list<shared_ptr<Spell>> effects_; | ||
| 175 | }; | ||
| 176 | |||
| 177 | class Boss { | ||
| 178 | static auto | ||
| 179 | split(const string& line, char sep = ' ') | ||
| 180 | { | ||
| 181 | vector<string> parts; | ||
| 182 | stringstream input{ line }; | ||
| 183 | |||
| 184 | for ( string part; getline(input, part, sep); ) { | ||
| 185 | parts.emplace_back(part); | ||
| 186 | } | ||
| 187 | |||
| 188 | return parts; | ||
| 189 | } | ||
| 190 | |||
| 191 | public: | ||
| 192 | explicit Boss(string_view filename) | ||
| 193 | { | ||
| 194 | fstream input{ filename }; | ||
| 195 | |||
| 196 | for ( string line; getline(input, line); ) { | ||
| 197 | auto parts = Boss::split(line); | ||
| 198 | |||
| 199 | if ( parts[0] == "Hit" ) { | ||
| 200 | hit_points_ = stoi(parts[2]); | ||
| 201 | } | ||
| 202 | else if ( parts[0] == "Damage:" ) { | ||
| 203 | damage_ = stoi(parts[1]); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | Boss(HitPoints hit_points, Damage damage) // NOLINT | ||
| 209 | : hit_points_(hit_points) | ||
| 210 | , damage_(damage) | ||
| 211 | { | ||
| 212 | } | ||
| 213 | |||
| 214 | void Play(Player& player) const | ||
| 215 | { | ||
| 216 | debug << "Boss attacks for " << damage_ << " damage" << endl; | ||
| 217 | player.Damage(damage_); | ||
| 218 | } | ||
| 219 | |||
| 220 | void SubHitPoints(HitPoints hit_points) | ||
| 221 | { | ||
| 222 | assert(hit_points > 0); // NOLINT | ||
| 223 | hit_points_ -= hit_points; | ||
| 224 | } | ||
| 225 | |||
| 226 | [[nodiscard]] bool HasLost() const | ||
| 227 | { | ||
| 228 | return hit_points_ <= 0; | ||
| 229 | } | ||
| 230 | |||
| 231 | HitPoints hit_points_; | ||
| 232 | Damage damage_; | ||
| 233 | }; | ||
| 234 | |||
| 235 | ostream& | ||
| 236 | operator<<(ostream& ostream, const Player& player) | ||
| 237 | { | ||
| 238 | ostream << "- Player has " << player.hit_points_ << " hit points, " << player.armor_ << " armor, " << player.mana_ << " mana"; | ||
| 239 | return ostream; | ||
| 240 | } | ||
| 241 | |||
| 242 | ostream& | ||
| 243 | operator<<(ostream& ostream, const Boss& boss) | ||
| 244 | { | ||
| 245 | ostream << "- Boss has " << boss.hit_points_ << " hit points"; | ||
| 246 | return ostream; | ||
| 247 | } | ||
| 248 | |||
| 249 | class MagicMissile : public Spell { | ||
| 250 | public: | ||
| 251 | MagicMissile() | ||
| 252 | : Spell("Magic Missile", Mana(Cost)) | ||
| 253 | { | ||
| 254 | } | ||
| 255 | |||
| 256 | void Play(Player& /*player*/, Boss& boss) override | ||
| 257 | { | ||
| 258 | boss.SubHitPoints(4); | ||
| 259 | |||
| 260 | debug << "Magic Missile deals 4 damage" << endl; | ||
| 261 | } | ||
| 262 | |||
| 263 | static const int Cost = 53; | ||
| 264 | }; | ||
| 265 | |||
| 266 | class Drain : public Spell { | ||
| 267 | public: | ||
| 268 | Drain() | ||
| 269 | : Spell("Drain", Mana(Cost)) | ||
| 270 | { | ||
| 271 | } | ||
| 272 | |||
| 273 | void Play(Player& player, Boss& boss) override | ||
| 274 | { | ||
| 275 | player.AddHitPoints(2); | ||
| 276 | boss.SubHitPoints(2); | ||
| 277 | debug << "Drain deals 2 damage, and heals 2 hit points" << endl; | ||
| 278 | } | ||
| 279 | |||
| 280 | static const int Cost = 73; | ||
| 281 | }; | ||
| 282 | |||
| 283 | class Effect : public Spell { | ||
| 284 | protected: | ||
| 285 | Effect(string_view name, Mana mana, int timer) // NOLINT | ||
| 286 | : Spell(name, mana) | ||
| 287 | , timer_(timer) | ||
| 288 | { | ||
| 289 | } | ||
| 290 | |||
| 291 | public: | ||
| 292 | int Timer() final | ||
| 293 | { | ||
| 294 | return timer_; | ||
| 295 | } | ||
| 296 | |||
| 297 | protected: | ||
| 298 | int timer_; | ||
| 299 | }; | ||
| 300 | |||
| 301 | class Shield : public Effect { | ||
| 302 | public: | ||
| 303 | explicit Shield(Player& player) | ||
| 304 | : Effect("Shield", Mana(Cost), RunTime) | ||
| 305 | , player_(player) | ||
| 306 | { | ||
| 307 | debug << "Increasing Armor by " << Armor << endl; | ||
| 308 | player_.AddArmor(Armor); | ||
| 309 | } | ||
| 310 | |||
| 311 | ~Shield() override | ||
| 312 | { | ||
| 313 | debug << "Decreasing Armor by " << Armor << endl; | ||
| 314 | player_.SubArmor(Armor); | ||
| 315 | } | ||
| 316 | |||
| 317 | void Play(Player& /*player*/, Boss& /*boss*/) override | ||
| 318 | { | ||
| 319 | timer_--; | ||
| 320 | debug << "Shields timer is now " << timer_ << endl; | ||
| 321 | } | ||
| 322 | |||
| 323 | static const Armor Armor = 7; | ||
| 324 | static const int Cost = 113; | ||
| 325 | static const int RunTime = 6; | ||
| 326 | |||
| 327 | private: | ||
| 328 | Player& player_; | ||
| 329 | }; | ||
| 330 | |||
| 331 | class Poison : public Effect { | ||
| 332 | public: | ||
| 333 | Poison() | ||
| 334 | : Effect("Poison", Mana(Cost), RunTime) | ||
| 335 | { | ||
| 336 | } | ||
| 337 | |||
| 338 | void Play(Player& /*player*/, Boss& boss) override | ||
| 339 | { | ||
| 340 | timer_--; | ||
| 341 | |||
| 342 | debug << name_ << " deals 3 damage; its timer is now " << timer_ << endl; | ||
| 343 | boss.SubHitPoints(3); | ||
| 344 | } | ||
| 345 | |||
| 346 | static const int Cost = 173; | ||
| 347 | static const int RunTime = 6; | ||
| 348 | }; | ||
| 349 | |||
| 350 | class Recharge : public Effect { | ||
| 351 | public: | ||
| 352 | Recharge() | ||
| 353 | : Effect("Recharge", Mana(Cost), RunTime) | ||
| 354 | { | ||
| 355 | } | ||
| 356 | |||
| 357 | ~Recharge() override | ||
| 358 | { | ||
| 359 | debug << "Recharge wears off" << endl; | ||
| 360 | } | ||
| 361 | |||
| 362 | void Play(Player& player, Boss& /*boss*/) override | ||
| 363 | { | ||
| 364 | static const Mana AdditionalMana = 101; | ||
| 365 | |||
| 366 | timer_--; | ||
| 367 | player.AddMana(AdditionalMana); | ||
| 368 | debug << name_ << " provides 101 mana; its timer is now " << timer_ << endl; | ||
| 369 | } | ||
| 370 | |||
| 371 | static const int Cost = 229; | ||
| 372 | static const int RunTime = 5; | ||
| 373 | }; | ||
| 374 | |||
| 375 | shared_ptr<Spell> | ||
| 376 | chooseRandomSpell(Player& player) | ||
| 377 | { | ||
| 378 | if ( player.mana_ < min({ MagicMissile::Cost, Drain::Cost, Shield::Cost, Poison::Cost, Recharge::Cost }) ) { | ||
| 379 | return nullptr; | ||
| 380 | } | ||
| 381 | |||
| 382 | while ( true ) { | ||
| 383 | static const int NumSpells = 5; | ||
| 384 | |||
| 385 | auto selection = rand() % NumSpells; | ||
| 386 | |||
| 387 | if ( selection == 0 && player.mana_ >= MagicMissile::Cost ) { | ||
| 388 | return make_shared<MagicMissile>(); | ||
| 389 | } | ||
| 390 | if ( selection == 1 && player.mana_ >= Drain::Cost ) { | ||
| 391 | return make_shared<Drain>(); | ||
| 392 | } | ||
| 393 | if ( selection == 2 && player.mana_ >= Shield::Cost ) { | ||
| 394 | return make_shared<Shield>(player); | ||
| 395 | } | ||
| 396 | if ( selection == 3 && player.mana_ >= Poison::Cost ) { | ||
| 397 | return make_shared<Poison>(); | ||
| 398 | } | ||
| 399 | if ( selection == 4 && player.mana_ >= Recharge::Cost ) { | ||
| 400 | return make_shared<Recharge>(); | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | |||
| 405 | void | ||
| 406 | battle1() | ||
| 407 | { | ||
| 408 | Player player(250, 10); | ||
| 409 | Boss boss(13, 8); | ||
| 410 | Spells spells; | ||
| 411 | |||
| 412 | // ---------------------- | ||
| 413 | |||
| 414 | cout << "\n-- Player turn --\n"; | ||
| 415 | cout << player << '\n' | ||
| 416 | << boss << endl; | ||
| 417 | |||
| 418 | spells.Play(player, boss); | ||
| 419 | spells.AddSpell(make_shared<Poison>(), player, boss); | ||
| 420 | |||
| 421 | // ---------------------- | ||
| 422 | |||
| 423 | cout << "\n-- Boss turn --\n"; | ||
| 424 | cout << player << '\n' | ||
| 425 | << boss << endl; | ||
| 426 | |||
| 427 | spells.Play(player, boss); | ||
| 428 | boss.Play(player); | ||
| 429 | |||
| 430 | // ---------------------- | ||
| 431 | |||
| 432 | cout << "\n-- Player turn --\n"; | ||
| 433 | cout << player << '\n' | ||
| 434 | << boss << endl; | ||
| 435 | |||
| 436 | spells.Play(player, boss); | ||
| 437 | spells.AddSpell(make_shared<MagicMissile>(), player, boss); | ||
| 438 | |||
| 439 | // ---------------------- | ||
| 440 | |||
| 441 | cout << "\n-- Boss turn --\n"; | ||
| 442 | cout << player << '\n' | ||
| 443 | << boss << endl; | ||
| 444 | |||
| 445 | spells.Play(player, boss); | ||
| 446 | |||
| 447 | if ( boss.HasLost() ) { | ||
| 448 | cout << "Boss killed" << endl; | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 452 | void | ||
| 453 | battle2() | ||
| 454 | { | ||
| 455 | Player player(250, 10); | ||
| 456 | Boss boss(14, 8); | ||
| 457 | Spells spells; | ||
| 458 | |||
| 459 | // ---------------------- | ||
| 460 | |||
| 461 | cout << "\n-- Player turn --\n"; | ||
| 462 | cout << player << '\n' | ||
| 463 | << boss << endl; | ||
| 464 | |||
| 465 | spells.Play(player, boss); | ||
| 466 | spells.AddSpell(make_shared<Recharge>(), player, boss); | ||
| 467 | |||
| 468 | // ---------------------- | ||
| 469 | |||
| 470 | cout << "\n-- Boss turn --\n"; | ||
| 471 | cout << player << '\n' | ||
| 472 | << boss << endl; | ||
| 473 | |||
| 474 | spells.Play(player, boss); | ||
| 475 | boss.Play(player); | ||
| 476 | |||
| 477 | // ---------------------- | ||
| 478 | |||
| 479 | cout << "\n-- Player turn --\n"; | ||
| 480 | cout << player << '\n' | ||
| 481 | << boss << endl; | ||
| 482 | |||
| 483 | spells.Play(player, boss); | ||
| 484 | spells.AddSpell(make_shared<Shield>(player), player, boss); | ||
| 485 | |||
| 486 | // ---------------------- | ||
| 487 | |||
| 488 | cout << "\n-- Boss turn --\n"; | ||
| 489 | cout << player << '\n' | ||
| 490 | << boss << endl; | ||
| 491 | |||
| 492 | spells.Play(player, boss); | ||
| 493 | boss.Play(player); | ||
| 494 | |||
| 495 | // ---------------------- | ||
| 496 | |||
| 497 | cout << "\n-- Player turn --\n"; | ||
| 498 | cout << player << '\n' | ||
| 499 | << boss << endl; | ||
| 500 | |||
| 501 | spells.Play(player, boss); | ||
| 502 | spells.AddSpell(make_shared<Drain>(), player, boss); | ||
| 503 | |||
| 504 | // ---------------------- | ||
| 505 | |||
| 506 | cout << "\n-- Boss turn --\n"; | ||
| 507 | cout << player << '\n' | ||
| 508 | << boss << endl; | ||
| 509 | |||
| 510 | spells.Play(player, boss); | ||
| 511 | boss.Play(player); | ||
| 512 | |||
| 513 | // ---------------------- | ||
| 514 | |||
| 515 | cout << "\n-- Player turn --\n"; | ||
| 516 | cout << player << '\n' | ||
| 517 | << boss << endl; | ||
| 518 | |||
| 519 | spells.Play(player, boss); | ||
| 520 | spells.AddSpell(make_shared<Poison>(), player, boss); | ||
| 521 | |||
| 522 | // ---------------------- | ||
| 523 | |||
| 524 | cout << "\n-- Boss turn --\n"; | ||
| 525 | cout << player << '\n' | ||
| 526 | << boss << endl; | ||
| 527 | |||
| 528 | spells.Play(player, boss); | ||
| 529 | boss.Play(player); | ||
| 530 | |||
| 531 | // ---------------------- | ||
| 532 | |||
| 533 | cout << "\n-- Player turn --\n"; | ||
| 534 | cout << player << '\n' | ||
| 535 | << boss << endl; | ||
| 536 | |||
| 537 | spells.Play(player, boss); | ||
| 538 | spells.AddSpell(make_shared<MagicMissile>(), player, boss); | ||
| 539 | |||
| 540 | // ---------------------- | ||
| 541 | |||
| 542 | cout << "\n-- Boss turn --\n"; | ||
| 543 | cout << player << '\n' | ||
| 544 | << boss << endl; | ||
| 545 | |||
| 546 | spells.Play(player, boss); | ||
| 547 | boss.Play(player); | ||
| 548 | |||
| 549 | if ( boss.HasLost() ) { | ||
| 550 | cout << "Boss killed" << endl; | ||
| 551 | } | ||
| 552 | |||
| 553 | cout << "End of Battle" << endl; | ||
| 554 | } | ||
| 555 | |||
| 556 | int | ||
| 557 | main() | ||
| 558 | { | ||
| 559 | battle2(); | ||
| 560 | } | ||
