diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b883f1f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.exe diff --git a/AnalyzeBattle.ipynb b/AnalyzeBattle.ipynb deleted file mode 100644 index e1d3c90..0000000 --- a/AnalyzeBattle.ipynb +++ /dev/null @@ -1,167 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Problem 2: Analyze battle\n", - "\n", - "In this exercise, you will be analyzing the battle script output\n", - "that is created in `json` format by the `WorldOfTextCraft` executable. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from readbattle import readbattle\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get the data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "npdata = readbattle(infile='problem2a.txt')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Make a plot of the attacks per turn" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Make plot of attacks. \n", - "plt.plot(npdata[0,:,0],label=\"Arthas\")\n", - "plt.plot(npdata[1,:,0],label=\"Fordring\")\n", - "plt.plot(npdata[2,:,0],label=\"Thrall\")\n", - "plt.plot(npdata[3,:,0],label=\"Mograine\")\n", - "plt.legend()\n", - "plt.xlabel(\"Turn\")\n", - "plt.ylabel(\"Attack\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part a\n", - "\n", - "Make plots of all action types for each character: `['Attacks', 'Defends', 'Heals', 'DamageReceived', 'HealingRecieved']`. (The `Attacks` are done in the last cell as an example). Label your axes and make a legend. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part b\n", - "\n", - "Using the [matplotlib hist](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.hist.html) function, create a single plot showing histograms of the `Attacks` for each character. Label your axes and make a legend. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part c\n", - "\n", - "Repeat part b, but this time plot the `Attacks` only for turns where the character was healed. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Plot here" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4rc2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/AshenVerdict.txt b/AshenVerdict.txt deleted file mode 100644 index da3c4f8..0000000 --- a/AshenVerdict.txt +++ /dev/null @@ -1,4 +0,0 @@ -Warrior;Fordring;0;10;0; -Priest;Thrall;0;0;12; -Rogue;Mograine;20;0;0; - diff --git a/Battle.cc b/Battle.cc deleted file mode 100644 index 0482880..0000000 --- a/Battle.cc +++ /dev/null @@ -1,626 +0,0 @@ -#include "Battle.h" -#include "MatchingHelpers.h" -#include - -void Battle::splash( std::ostream & out ) const { - - out << "|--------------------------------------------|" << std::endl; - out << "|--------------------------------------------|" << std::endl; - out << "|--------------------------------------------|" << std::endl; - out << "|--------------------------------------------|" << std::endl; - out << "| __ __ _ _ |" << std::endl; - out << "| / / /\\ \\ \\___ _ __| | __| | |" << std::endl; - out << "| \\ \\/ \\/ / _ \\| '__| |/ _` | |" << std::endl; - out << "| \\ /\\ / (_) | | | | (_| | |" << std::endl; - out << "| \\/ \\/ \\___/|_| |_|\\__,_| |" << std::endl; - out << "| |" << std::endl; - out << "| __ |" << std::endl; - out << "| ___ / _| |" << std::endl; - out << "| / _ \\| |_ |" << std::endl; - out << "| | (_) | _| |" << std::endl; - out << "| \\___/|_| |" << std::endl; - out << "| |" << std::endl; - out << "| _____ _ ___ __ _ |" << std::endl; - out << "|/__ \\_____ _| |_ / __\\ __ __ _ / _| |_ |" << std::endl; - out << "| / /\\/ _ \\ \\/ / __|/ / | '__/ _` | |_| __||" << std::endl; - out << "| / / | __/> <| |_/ /__| | | (_| | _| |_ |" << std::endl; - out << "| \\/ \\___/_/\\_\\\\__\\____/_| \\__,_|_| \\__||" << std::endl; - out << "| |" << std::endl; - out << "|--------------------------------------------|" << std::endl; - out << "|--------------------------------------------|" << std::endl; - out << "|--------------------------------------------|" << std::endl; - - out << "Welcome brave traveler..." << std::endl; - out << description_ << std::endl; - - -} - -bool Battle::readPCConfiguration( std::string filename ) -{ - - if ( pcs_.size() > 0 ) { - std::cout << "Configuration already read... skipping reading from " << filename << std::endl; - return false; - } else { - std::ifstream in(filename); - - // While not the end of file, keep looping - while ( ! in.eof() ) { - std::string line; - std::getline(in,line); - - // First check if the line is empty or a comment (starts with "!") - if ( (line.size() > 0 && line[0] == '!') || line == "" ){ - continue; - } - else { - - // Otherwise, we have a valid line. - // First read the type from the first token, and create the appropriate class (Warrior, Priest, Rogue). - // Then read in the stats from the rest of the line. - std::stringstream tokens(line); - std::string entityType; - std::getline( tokens, entityType, ';' ); - - std::shared_ptr entity; - if ( entityType == "Warrior" ) { - entity = std::shared_ptr ( new Warrior() ); - } else if ( entityType == "Priest" ) { - entity = std::shared_ptr ( new Priest() ); - } else if ( entityType == "Rogue" ) { - entity = std::shared_ptr ( new Rogue() ); - } else { - std::cout << "Entity Type not recognized: " << line << ", skipping" << std::endl; - continue; - } - entity->input( tokens ); - pcs_.push_back( entity ); - std::cout << "Added entity: " << *( pcs_.back() ) << std::endl; - } - } - - in.close(); - } - return true; -} - - - -bool Battle::readNPCConfiguration( std::string filename ) -{ - if ( npcs_.size() > 0 ) { - std::cout << "Configuration already read... skipping reading from " << filename << std::endl; - return false; - } else { - std::ifstream in(filename); - while ( ! in.eof() ) { - std::string line; - std::getline(in,line); - - if ( (line.size() > 0 && line[0] == '!') || line == "" ){ - continue; - } - else { - // The first line is the description. - if ( description_ == "" ) { - description_ = line; - } else { - std::shared_ptr boss (new Boss() ); - boss->input( line ); - npcs_.push_back( boss ); - } - } - } - } - return true; -} - - -// Load a bunch of actions from a script -bool Battle::loadActionScript( std::string filename ) -{ - if ( filename == "" ) { - std::cout << "No script file input." << std::endl; - return false; - } else { - std::ifstream in( filename ); - - std::string line; - bool success = true; - while ( !in.eof() && success ) { - - std::getline( in, line ); - if ( line[0] == '!' || line == "" ) continue; - QuickAction qa; - success = parseAction( line, qa ); - - if( !success ) { - std::cout << "Error in parseAction!" << std::endl; - return false; - } - - // If the script has any PC actions, it is scripted and not user-input - coll_type::iterator pcIt = std::find_if( pcs_.begin(), pcs_.end(), MatchSource( qa.source->name() ) ); - bool found = pcIt != pcs_.end() ; - if ( found ) { - scripted_=true; - std::cout << "Scripting input for " << (*pcIt)->name() << std::endl; - } - actions_.push_back(qa); - - - - } - in.close(); - } - - return true; - -} - -// Lines look like this: -// Shemp;attack;Moe; -// Shemp;multiattack;all; -// 1 : find the Entity in the list with "name". -// 2 : find the action they are supposed to do. -// 3 : find the target. If blank, use current target. -// 4 : Set "name's" target, perform action -bool Battle::parseAction(std::string line, QuickAction & qa) -{ - - if ( line == "" ) return false; - - std::vector tokens; - std::stringstream linestream(line); - for (std::string each=""; std::getline(linestream, each, ';'); ){ - tokens.push_back(each); - } - - if ( tokens.size() < 3 ) { - std::cout << "Improper formatting of line " << line << std::endl; - return false; - } - - // Initialize both target and source to "error" (pcs_.end() is interpreted as such) - coll_type::iterator targetIt = pcs_.end(), sourceIt = pcs_.end(); - std::shared_ptr target; - std::shared_ptr source; - - // Find the iterator with name "tokens[0]" - sourceIt = find_entity( tokens[0] ); - // Check to make sure we found an iterator - if ( sourceIt == pcs_.end() ) { - std::cout << "Error processing line " << line << std::endl; - return false; - } - source = *sourceIt; - - // Special case of Bosses attacking ALL NPCs - if ( tokens[2] == "all" ) { - target = 0; - qa.source = source; - qa.action = MULTIATTACK; - return true; - } - - // Special case of attacking current target - if ( tokens[2] == "target" ) { - target = 0; - } else { - - // Find the iterator with name "tokens[2]" - targetIt = find_entity( tokens[2] ); - // Check to make sure we found an iterator - if ( targetIt == pcs_.end() || targetIt == npcs_.end() ) { - std::cout << "Error processing line " << line << std::endl; - return false; - } - target = *targetIt; - } - - // parse the action type. - ActionType actionType = N_ACTIONS; // Error code - if ( tokens[1] == "attack") actionType = ATTACK; - else if ( tokens[1] == "heal") actionType = HEAL; - else if ( tokens[1] == "defend") actionType = DEFEND; - else { - std::cout << "Invalid action " << tokens[1] << std::endl; - return false; - } - - - // Finally, push back the action - if ( target ) { - source->setTarget(target.get()); - } - qa.source = source; - qa.action = actionType ; - return true; -} - - -// This will perform all of the actions scripted in "actions_". -bool Battle::performScriptedActions( ) { - - if ( !scripted_ ) { - std::cout << "I am expecting a player script, but you didn't give me one." << std::endl; - return false; - } - - if ( !anyPCAlive() ){ - std::cout << "Alas, your party is dead." << std::endl; - return false; - } - - if ( !anyNPCAlive() ) { - std::cout << "You already won! Hooray!" << std::endl; - return false; - } - - // Special case for first attack : all NPCs will target first PC - if ( turn_ == 0 && pcs_.size() > 1 ) { - for ( coll_type::iterator inpc = npcs_.begin(); - inpc != npcs_.end(); ++inpc ) { - (*inpc)->setTarget( pcs_.begin()->get() ); - } - } - - if ( actions_.size() == 0 ){ - std::cout << "No actions to perform" << std::endl; - return false; - } - - for ( coll_type::iterator it = npcs_.begin(); it != npcs_.end(); ++it ) { - (*it)->turn_ = turn_; - } - - for ( coll_type::iterator it = pcs_.begin(); it != pcs_.end(); ++it ) { - (*it)->turn_ = turn_; - } - - for ( std::vector::iterator it = actions_.begin(); - it != actions_.end(); ++it ){ - - if ( it->source->isDead() ) { - std::cout << it->source->name() << " is dead" << std::endl; - continue; - } - - // two special cases for bosses: - // 1. If their target dies, they pick the next target. - // 2. Must check the special case of the "Boss" ability to attack everyone. - // Since we are storing pointers to the base class, we need to - // "dynamic_cast" to the derived class to access its "multi attack" - Boss * boss = dynamic_cast (it->source.get()); - if ( boss != 0 ) { - // Attack the entire party in a "MultiAttack" - if ( it->action == MULTIATTACK ) { - for ( coll_type::iterator itarget = pcs_.begin(); - itarget != pcs_.end(); ++itarget ) { - boss->multiAttack( itarget->get() ); - } - continue; - } - // If the target is dead, switch to the next in the list. - if ( boss->getTarget() != 0 && boss->getTarget()->isDead() ) { - bool bossGotTarget = setBossTarget( boss ); - if ( !bossGotTarget ) { - return false; - } - } - } - - // Now we execute normal attacks. - if ( it->action == ATTACK ) { - it->source->attack(); - } else if ( it->action == HEAL ) { - it->source->heal(); - } else if ( it->action == DEFEND ) { - it->source->defend(); - } else { - std::cout << "Incorrect action! Returning" << std::endl; - return false; - } - - if ( !anyPCAlive() ) { - std::cout << it->source->name() << " has vanquished your party." << std::endl; - return false; - } - - - if ( !anyNPCAlive() ) { - std::cout << it->source->name() << " has vanquished your foe!!!" << std::endl; - return false; - } - - } - - - ++turn_; - - return anyPCAlive() && anyNPCAlive(); - -} - - - -// This will perform all of the actions scripted in "actions_". -bool Battle::performUserActions( std::istream & in ) { - - if ( scripted_ ) { - std::cout << "PC actions already scripted, do not give me scripted actions if you do not want to use them." << std::endl; - return false; - } - - - if ( !anyPCAlive() ){ - std::cout << "Alas, your party is dead." << std::endl; - return false; - } - - if ( !anyNPCAlive() ) { - std::cout << "You already won! Hooray!" << std::endl; - return false; - } - - // Special case for first attack : all NPCs will target first PC - if ( turn_ == 0 && pcs_.size() > 1 ) { - for ( coll_type::iterator inpc = npcs_.begin(); - inpc != npcs_.end(); ++inpc ) { - (*inpc)->setTarget( pcs_.begin()->get() ); - } - } - - // Bookkeeping - if ( actions_.size() == 0 ){ - std::cout << "No actions to perform" << std::endl; - return false; - } - for ( coll_type::iterator it = npcs_.begin(); it != npcs_.end(); ++it ) { - (*it)->turn_ = turn_; - } - for ( coll_type::iterator it = pcs_.begin(); it != pcs_.end(); ++it ) { - (*it)->turn_ = turn_; - } - - // Process the boss actions - for ( std::vector::iterator it = actions_.begin(); - it != actions_.end(); ++it ){ - - if ( it->source->isDead() ) { - std::cout << it->source->name() << " is dead" << std::endl; - continue; - } - // two special cases for bosses: - // 1. If their target dies, they pick the next target. - // 2. Must check the special case of the "Boss" ability to attack everyone. - // Since we are storing pointers to the base class, we need to - // "dynamic_cast" to the derived class to access its "multi attack" - Boss * boss = dynamic_cast (it->source.get()); - if ( boss != 0 ) { - // Attack the entire party in a "MultiAttack" - if ( it->action == MULTIATTACK ) { - for ( coll_type::iterator itarget = pcs_.begin(); - itarget != pcs_.end(); ++itarget ) { - boss->multiAttack( itarget->get() ); - } - continue; - } - // If the target is dead, switch to the next in the list. - if ( boss->getTarget() != 0 && boss->getTarget()->isDead() ) { - bool bossGotTarget = setBossTarget( boss ); - if ( !bossGotTarget ) { - return false; - } - } - } - - if ( it->action == ATTACK ) { - it->source->attack(); - } else if ( it->action == HEAL ) { - it->source->heal(); - } else if ( it->action == DEFEND ) { - it->source->defend(); - } else { - std::cout << "Incorrect action! Returning" << std::endl; - return false; - } - - - if ( !anyPCAlive() ) { - std::cout << it->source->name() << " has vanquished your party." << std::endl; - return false; - } - - } - - // Process the user actions - for ( coll_type::iterator it = pcs_.begin(); it != pcs_.end(); ++it ) { - - - if ( (*it)->isDead() ) { - std::cout << (*it)->name() << " is dead" << std::endl; - continue; - } - - - std::cout << "Action for " << (*it)->name() << ": " << std::endl; - QuickAction qa; - std::string line; - in >> line; - //std::getline( in, line ); - bool success = parseAction( line, qa ); - while ( !success ) { - std::cout << "Invalid input, try again" << std::endl; - in >> line; - success = parseAction( line, qa ); - } - if ( qa.action == ATTACK ) { - (*it)->attack(); - } else if ( qa.action == HEAL ) { - (*it)->heal(); - } else if ( qa.action == DEFEND ) { - (*it)->defend(); - } else { - std::cout << "Incorrect action! Returning" << std::endl; - return false; - } - - - if ( !anyNPCAlive() ) { - std::cout << (*it)->name() << " has vanquished your foe." << std::endl; - return false; - } - - } - - - ++turn_; - - return anyPCAlive() && anyNPCAlive(); - -} - -// Set the next Boss target. It looks for the -// first character, in order, that is not dead. -bool Battle::setBossTarget( Boss * boss ) { - - Entity const * entity = boss->getTarget(); - - if ( boss == 0 ) { - std::cout << "Boss is zero, cannot set new target" << std::endl; - return false; - } - for( coll_type::const_iterator pc = pcs_.begin(); pc != pcs_.end(); ++pc ) { - if ( (*pc)->isAlive() ) { - if ( pc->get() != entity ){ - std::cout << boss->name() << " shifts their attacks to " << (*pc)->name() << std::endl; - } - boss->setTarget( pc->get() ); - return true; - } - } - - // Here there are no targets alive. - return false; - -} - -bool Battle::anyPCAlive() const { - bool anyPCAliveRet = false; - for( coll_type::const_iterator pc = pcs_.begin(); pc != pcs_.end(); ++pc ) { - if ( (*pc)->isAlive() ) anyPCAliveRet = true; - } - return anyPCAliveRet; -} - - -bool Battle::anyNPCAlive() const { - bool anyNPCAliveRet = false; - for( coll_type::const_iterator npc = npcs_.begin(); npc != npcs_.end(); ++npc ) { - if ( (*npc)->isAlive() ) anyNPCAliveRet = true; - } - return anyNPCAliveRet; -} - - -Battle::coll_type::iterator Battle::find_entity( std::string s ) -{ - // Try to find the sources and targets: - coll_type::iterator source_pc = std::find_if( pcs_.begin(), pcs_.end(), MatchSource( s ) ); - coll_type::iterator source_npc = std::find_if( npcs_.begin(), npcs_.end(), MatchSource( s ) ); - - if ( source_pc == pcs_.end() && source_npc == npcs_.end() ) { - std::cout << "No matching source for " << s << std::endl; - return pcs_.end(); - } else if ( source_pc == pcs_.end() ) { - return source_npc; - } else { - return source_pc; - } -} - - -void Battle::printStats( std::ostream & out ) { - out << "------------------------------- Turn : " << turn_ << "-------------------------------" << std::endl; - out << "-----------------------------" << std::endl; - out << " === players:" << std::endl; - // Remember! "coll_type" is just an alias for std::vector< std::shared_ptr > - for ( coll_type::const_iterator it = pcs_.begin(); - it != pcs_.end(); ++it ) { - (*it)->printStats(out); - } - out << std::endl << " === monsters:" << std::endl; - for ( coll_type::const_iterator it = npcs_.begin(); - it != npcs_.end(); ++it ) { - (*it)->printStats(out); - } - out << "-----------------------------" << std::endl; -} - -void Battle::print( std::ostream & out ) { - out << "------------------------------- Turn : " << turn_ << "-------------------------------" << std::endl; - out << "--------------" << std::endl; - out << " === players:" << std::endl; - // Remember! "coll_type" is just an alias for std::vector< std::shared_ptr > - for ( coll_type::const_iterator it = pcs_.begin(); - it != pcs_.end(); ++it ) { - out << **it << std::endl; - } - out << std::endl << " === monsters:" << std::endl; - for ( coll_type::const_iterator it = npcs_.begin(); - it != npcs_.end(); ++it ) { - out << **it << std::endl; - } - out << "--------------" << std::endl; -} - -void Battle::printActions( std::ostream & out ) { - - out << " ----- Action list ----" << std::endl; - for ( std::vector::const_iterator it = actions_.begin(); - it != actions_.end(); ++it ) { - if ( it->source == 0 ) { - std::cout << "Configuration error." << std::endl; - return; - } - out << it->source->name() << " will perform action " << it->action << " on their target : "; - if ( it->source->getTarget() != 0) { - out << it->source->getTarget()->name() << std::endl; - } else { - out << " NO TARGET!" << std::endl; - } - } - -} - - -void Battle::printLog( std::ostream & out ) const -{ - - out << "{\"Turns\":[" << std::endl; - for ( unsigned int iturn = 0; iturn <= turn_; ++iturn ){ - - out << "{\"Turn\":" << iturn << "," << std::endl; - - for ( auto i = npcs_.begin(); i != npcs_.end(); ++i ){ - (*i)->printActions(out, iturn); - out << "," << std::endl; - } - for ( auto i = pcs_.begin(); i != pcs_.end(); ++i ){ - (*i)->printActions(out, iturn); - // json does not like trailing commas - if ( i != pcs_.end() - 1) - out << "," << std::endl; - else - out << std::endl; - } - out << "}"; - if ( iturn != turn_ ) - out << "," << std::endl; - else - out << std::endl; - } - out << "]}" << std::endl; -} diff --git a/Battle.h b/Battle.h deleted file mode 100644 index 85da6aa..0000000 --- a/Battle.h +++ /dev/null @@ -1,203 +0,0 @@ -#ifndef Battle_h -#define Battle_h - - -// -------------------------------------- - /* ____ _ _ _ */ - /* | _ \ | | | | | | */ - /* | |_) | __ _| |_| |_| | ___ */ - /* | _ < / _` | __| __| |/ _ \ */ - /* | |_) | (_| | |_| |_| | __/ */ - /* |____/ \__,_|\__|\__|_|\___| */ -// -------------------------------------- -// This is the main driver of the game. -// -// It reads in two configuration files -// consisting of the player characters (PCs) -// and the non-player characters (NPCs) in order -// of attack. The battle will they play out until -// one of the parties is empty. -// -// The configuration files will all support comments in the form of an exclamation point ("!"). -// The configuration files will have fields separated by a semicolon (";") -// -// The PC configuration syntax is as follows (omit the < >, they are there for notation here only) -// ;;;;; -// : Name of the character as a string, like "Moe" -// : Type of character as a string, like "Warrior" -// : Attack power as an integer, like "20" -// : Defense power as an integer, like "20" -// : Heal power as an integer, like "20" -// Example: -// Curly;Rogue;0;20;0; -// Moe;Warrior;20;0;0; -// Larry;Priest;0;0;20; -// -// -// The NPC configuration is similar, but may have different attributes since -// they have a "multi-attack power" which attacks everyone in the party. -// -// Example: -// Shemp;Boss;20;0;0;5; -// -// -// There is then a syntax for the battle itself, called a "script". It will consist of a set of -// actions your team will apply per turn. -// ;; -// : Name of character performing action -// : Name of the action to apply (like "attack", "heal", "defend") -// : Name of the target on which to perform the action -// -// Example: -// Curly;attack;Shemp; <<--- Curly attacks Shemp -// Moe;defend;Shemp; <<--- Moe switches Shemp's target to be Moe, defends. -// Larry;heal;Moe; <<--- Larry heals moe -// -// There is also a command-line interface that will cycle through your players and ask for -// actions prompted from "std::cin" like: -// Action for Curly? -// (You can then type "attack;Shemp;") -// Action for Moe? -// (You can then type "defend;Shemp;") -// Action for Larry? -// (You can then type "heal;Moe;") -// -// -// -// -// -// -// The Boss "script" will also be similarly defined, but it will contain a description and -// the boss will attack whoever the target is. -// The target of the Boss is initially set to the first person in your configuration file. -// For instance, Shemp may attack the "first" person on your list (above, this is Moe), and then -// attack everyone on your list. This will happen once per turn. Even in the command-line interface, -// the same sequence is executed once per turn. If the "defend" method is called, however, -// the target of -// -// Shemp;attack;target <<---- Shemp will attack the first person on your list -// Shemp;attack;all <<---- Shemp will attack your entire party with "multi_attack" -// -// Suppose that your Warrior Moe casts "defend" against Shemp, then Shemp's target will be -// Moe. -// -// -------------------------------------- - - -#include -#include -#include -#include -#include -#include "Entity.h" -#include "Boss.h" -#include "Warrior.h" -#include "Rogue.h" -#include "Priest.h" - -class Battle { - public: - - - - // Create a "typedef" that sets an alias of one type to another to save typing. - typedef std::vector< std::shared_ptr > coll_type; - - Battle(): turn_(0), description_(""), userTurns_(false), scripted_(false) {} - ~Battle() {} - - // Read the configuration for this Battle. - // It will consist of the player characters, the non-player characters, - // in their order of attack. - bool readPCConfiguration ( std::string filename ); - bool readNPCConfiguration( std::string filename ); - - // Read a script of actions. It will consist of a - // list of actions per turn to execute. - // This will append to the existing actions. - bool loadActionScript (std::string filename); - // Clear the actions - void clearActions() { actions_.clear(); } - - // Are we letting the user work in turns? - void setUserTurns(bool userTurns=true) { userTurns_ = userTurns;} - bool userTurns() const { return userTurns_;} - - // Perform scripted actions - bool performScriptedActions(); - - // For user turn-by-turn action - bool performUserActions( std::istream & in = std::cin ); - - - // Print this turn - virtual void print( std::ostream & out = std::cout ); - // Print all of the stats of the players in the battle - void printStats( std::ostream & out = std::cout ); - // Print the actions queued - void printActions( std::ostream & out = std::cout ); - - // Return the description - std::string description() const { return description_; } - - - // Print the splash screen - void splash( std::ostream & out = std::cout ) const; - - // Print the logfile of the battle - void printLog( std::ostream & out) const; - - private: - - - unsigned int turn_; // What turn is it? - std::string description_; // Fun description of battle. - bool userTurns_; // Input turn-by-turn user input? By default, no. - coll_type pcs_; // Player characters - coll_type npcs_; // Non-player characters - - bool scripted_; // Is this fight scripted or user-turns? - - - // - // - // Internal workings of "Battle": - // Each turn, Entities will perform actions on each other. - // However, we don't want to have to parse the text file - // over and over again, so we store the actions in a vector. - // - // - - // Find a source named "s" - coll_type::iterator find_entity( std::string s ); - - - // This will enumerate the possible actions that can be taken - enum ActionType { - ATTACK=0,HEAL,DEFEND,MULTIATTACK,N_ACTIONS - }; - - // A private "struct" (i.e. a class with all public members) to store the source and action type - struct QuickAction{ - std::shared_ptr source; - ActionType action; - QuickAction(std::shared_ptr s=0, ActionType a=N_ACTIONS) : source(s), action(a) {} - }; - std::vector actions_; // List of scripted actions to perform per turn. - - - // Add an action - // in the format "source;action;target;" - bool parseAction( std::string, QuickAction & qa ); - - bool anyNPCAlive() const; - bool anyPCAlive() const; - - // Check if the Boss's target is dead. If so, set to the first living member - // of the party. Otherwise, return false; - bool setBossTarget( Boss * boss ); - -}; - - -#endif diff --git a/Boss.cc b/Boss.cc deleted file mode 100644 index bdac9a1..0000000 --- a/Boss.cc +++ /dev/null @@ -1,95 +0,0 @@ -#include "Boss.h" -#include -#include - -Boss::Boss( std::string name, int attackPower, int defensePower, int healPower, int mana, int multiAttackPower ) : - Entity( "Boss", name, attackPower, defensePower, healPower, mana, false) { - multiAttackPower_ = multiAttackPower; - hitPoints_ = 500; - maxHitPoints_ = 500; -}; - - - -int Boss::multiAttack( Entity * other ) { - - Entity * originalTarget = getTarget(); - - if ( other != 0 ) { - setTarget(other); - } - int ap = this->multiAttackPower_; - - if ( getTarget() != 0 ) { - std::cout << name() << " multi-attacks " << getTarget()->name() << " with attack power " << ap<< std::endl; - int retval = getTarget()->reduceHitPoints( ap ); - setTarget(originalTarget); - if ( myAttacks_.find(turn_) == myAttacks_.end() ) - myAttacks_[turn_] = action_vector(); - myAttacks_[turn_].push_back(retval); - return retval; - } else { - std::cout << name_ << " does not have a target to attack." << std::endl; - setTarget( originalTarget); - return 0; - } -}; - - -// Print to "out" -void Boss::printStats( std::ostream & out) const { - out << std::setw(12) - << name_ << " (" << std::setw(10) << className_ << "): HP=" << std::setw(5) << hitPoints_ - << ", attack=" << std::setw(5) << attackPower_ - << ", defend=" << std::setw(5) << defensePower_ - << ", heal=" << std::setw(5) << healPower_; - if ( isMagicUser() ) { - out << ", mana = " << std::setw(5) << mana_; - } - out << ", multi =" << std::setw(5) << multiAttackPower_; - if ( target_ != 0 ) { - out << ", target=" << std::setw(12) << target_->name(); - } else { - out << ", no target"; - } -} - - -// Print to "out" -void Boss::print( std::ostream & out) const { - out << std::setw(12) - << name_ << " (" << std::setw(10) << className_ << "): HP=" << std::setw(5) << hitPoints_ - << ", mana = " << std::setw(5) << mana_; - if ( target_ != 0 ) { - out << ", target=" << std::setw(12) << target_->name(); - } else { - out << ", no target"; - } -} - - - - -void Boss::input( std::string line ) -{ - std::vector tokens; - - std::stringstream linestream(line); - for (std::string each=""; std::getline(linestream, each, ';'); ){ - tokens.push_back(each); - } - if ( tokens.size() >= 6 ) { - name_ = tokens[0]; - attackPower_ = std::atoi( tokens[1].c_str() ); - defensePower_ = std::atoi( tokens[2].c_str() ); - healPower_ = std::atoi( tokens[3].c_str() ); - mana_ = std::atoi( tokens[4].c_str() ); - multiAttackPower_ = std::atoi( tokens[5].c_str() ); - - std::cout << "Input boss: " << *this << std::endl; - } else { - std::cout << "Formatting error in input: unrecognized syntax in line : " << line << std::endl; - return; - } - -} diff --git a/Boss.h b/Boss.h deleted file mode 100644 index 1d36901..0000000 --- a/Boss.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef Boss_h -#define Boss_h - -#include "Entity.h" -#include - - /* ____ */ - /* | _ \ */ - /* | |_) | ___ ___ ___ */ - /* | _ < / _ \/ __/ __| */ - /* | |_) | (_) \__ \__ \ */ - /* |____/ \___/|___/___/ */ -// -// This is the Boss class. -// Bosses are special entities that are stronger, -// and have the ability to attack the entire opposing party at once. -// -// In addition to the standard Entity interface, it also has -// an "attackAll" function that will attack all of the entities -// inside of a vector. -// -------------------------------------- - -class Boss : public Entity{ - public : - - friend class Battle; - - Boss( std::string name="", - int attackPower=0, - int healPower=0, - int defensePower=0, - int mana=0, - int multiAttackPower=0 ); - - // Default defend, heal, and attack - virtual int defend( Entity * other =0){ - return defaultDefend(other); - } - virtual int heal( Entity * other=0 ){ - return defaultHeal(other); - } - virtual int attack( Entity * other=0 ){ - return defaultAttack( other ); - } - - // Special multiattack to attack many at once. - virtual int multiAttack( Entity * other = 0); - - virtual void printStats(std::ostream &out) const; - virtual void print(std::ostream &out) const; - - - // Overload the input method. - virtual void input( std::string line ); - - protected: - action_map myMultiAttacks_; - - private : - int multiAttackPower_; // The attack power of a mob who can attack more than one opponent. - - -}; - -#endif diff --git a/Entity.cc b/Entity.cc deleted file mode 100644 index 61ef0ee..0000000 --- a/Entity.cc +++ /dev/null @@ -1,284 +0,0 @@ -#include "Entity.h" -#include -#include -#include -#include -#include - -// Constructor -Entity::Entity( std::string className, - std::string name, - unsigned int attackPower, - unsigned int defensePower, - unsigned int healPower, - unsigned int maxMana, - bool check - ): - className_(className), - name_(name), - hitPoints_(100), - maxHitPoints_(100), - mana_(maxMana), - attackPower_(attackPower), - defensePower_(defensePower), - healPower_(healPower), - target_(0) // uninitialized target -{ - if ( check ){ - // Check to make sure your powers aren't outrageous - checkPowers(); - } -} - -// Destructor -Entity::~Entity(){} - -// All of these default to "do nothing" and should be overriden in the derived classes. -int Entity::attack( Entity * other ) { return 0;} -int Entity::heal ( Entity * other ) { return 0;} -int Entity::defend( Entity * other ) { return 0;} - -void Entity::input( std::string line ) -{ - std::vector tokens; - - std::stringstream linestream(line); - for (std::string each=""; std::getline(linestream, each, ';'); ){ - tokens.push_back(each); - } - if ( tokens.size() >= 4 ) { - name_ = tokens[0]; - attackPower_ = std::atoi( tokens[1].c_str() ); - defensePower_ = std::atoi( tokens[2].c_str() ); - healPower_ = std::atoi( tokens[3].c_str() ); - } else { - std::cout << "Formatting error in input: unrecognized syntax in line : " << line << std::endl; - return; - } - checkPowers(); -} - -// Input from "in" -void Entity::input( std::istream &in ) { - std::string line; - std::getline( in, line ); - input(line); -}; - -// Print to "out" - -void Entity::print( std::ostream & out) const { - out << std::setw(12) - << name_ << " (" << std::setw(10) << className_ << "): HP=" << std::setw(5) << hitPoints_ - << ", mana = " << std::setw(5) << mana_; - if ( target_ != 0 ) { - out << ", target=" << std::setw(12) << target_->name(); - } else { - out << ", no target"; - } -} - - - -void Entity::printStats( std::ostream & out) const { - out << std::setw(12) - << name_ << " (" << std::setw(10) << className_ << "): HP=" << std::setw(5) << hitPoints_ - << ", attack=" << std::setw(5) << attackPower_ - << ", defend=" << std::setw(5) << defensePower_ - << ", heal=" << std::setw(5) << healPower_ - << ", mana = " << std::setw(5) << mana_; - if ( target_ != 0 ) { - out << ", target=" << std::setw(12) << target_->name(); - } else { - out << ", no target"; - } -} - - - -// Reduce the hit points of "this" entity by "attack", mitigated by "defensePower" -int Entity::reduceHitPoints( int attack ) { - int diff = (attack - defensePower_); - if ( diff < 0 ) - diff = 0; - else if ( diff >= hitPoints_) // Protect against "overkill" in the stats accounting. - diff = hitPoints_; - std::cout << name_ << " loses " << diff << " hit points after attack " << attack << " and defense " << defensePower_ << std::endl; - hitPoints_ -= diff; - if (hitPoints_ <= 0) { - hitPoints_ = 0; - std::cout << name_ << ", the brave " << className_ << ", has died." << std::endl; - } - if ( myReducedHitPoints_.find(turn_) == myReducedHitPoints_.end() ) - myReducedHitPoints_[turn_] = action_vector(); - myReducedHitPoints_[turn_].push_back(diff); - return diff; -} - -// Increase the hit points of "this" entity -int Entity::increaseHitPoints( int heal ) { - int healed = heal; // Protect against "overheal" in the stats accounting. - if ( hitPoints() + healed >= maxHitPoints_) - healed = maxHitPoints_ - hitPoints(); - hitPoints_ += healed; - if ( myIncreasedHitPoints_.find(turn_) == myIncreasedHitPoints_.end() ) - myIncreasedHitPoints_[turn_] = action_vector(); - myIncreasedHitPoints_[turn_].push_back(healed); - return healed; -} - - -// This will force the target of the other object to be "this" Entity. -int Entity::defaultDefend( Entity * other ) { - if ( other != 0 ) { - setTarget(other); - } - if ( getTarget() != 0 ) { - - if ( getTarget()->isDead() ) { - std::cout << name_ << " : target " << getTarget()->name() << " is already dead." << std::endl; - return 0; - } - std::cout << name_ << " defends against " << getTarget()->name() << " with defense mitigation " << defensePower() << std::endl; - getTarget()->setTarget(this); - } else { - std::cout << name_ << " does not have a target to defend." << std::endl; - } - if ( myDefends_.find(turn_) == myDefends_.end() ) - myDefends_[turn_] = action_vector(); - myDefends_[turn_].push_back( defensePower() ); - return 0; -} - - -// In a heal, we increase the hit points -int Entity::defaultHeal( Entity * other ) { - if ( mana_ < 10 ) { - std::cout << name() << " does not have enough mana." << std::endl; - return 0; - } - if ( other != 0 ) { - setTarget(other); - } - if ( getTarget() != 0 ){ - - if ( getTarget()->isDead() ) { - std::cout << name_ << " : target " << getTarget()->name() << " is already dead." << std::endl; - myHeals_[turn_].push_back(0); - return 0; - } - mana_ -= 10; - auto healed = getTarget()->increaseHitPoints( healPower_ ); - std::cout << name() << " healed " << getTarget()->name() << " with heal power " << healPower_ << " for " << healed << std::endl; - if ( myHeals_.find(turn_) == myHeals_.end() ) - myHeals_[turn_] = action_vector(); - myHeals_[turn_].push_back( healed ); - return healed; - } - else { - std::cout << name_ << " does not have a target to heal." << std::endl; - return 0; - } -}; - - -// In an attack, we reduce the hit points -int Entity::defaultAttack( Entity * other ) { - if ( other != 0 ) { - setTarget(other); - } - - if ( getTarget() != 0 ) { - if ( getTarget()->isDead() ) { - std::cout << name_ << " : target " << getTarget()->name() << " is already dead." << std::endl; - myAttacks_[turn_].push_back( 0 ); - return 0; - } - - int ap = attackPower_; - auto attacked = getTarget()->reduceHitPoints( ap ); - std::cout << name() << " attacked " << getTarget()->name() << " with attack power " << ap << " for damage " << attacked << std::endl; - if ( myAttacks_.find(turn_) == myAttacks_.end() ) - myAttacks_[turn_] = action_vector(); - myAttacks_[turn_].push_back( attacked ); - return attacked; - } else { - std::cout << name_ << " does not have a target to attack." << std::endl; - myAttacks_[turn_].push_back( 0 ); - return 0; - } -}; - -// This will check to ensure the input values are sensible -bool Entity::checkPowers() { - - isMagicUser_ = ( mana_ > 0); - if ( attackPower_ + defensePower_ + healPower_ > 20 ) { - std::cout << name_ << " : You cannot godmode here, your abilities can only sum to 20." << std::endl - << "To punish you, the gods set your hitpoints to 1 and make you feeble as a kitten." << std::endl; - hitPoints_ = 1; - attackPower_ = 0; - defensePower_ = 0; - healPower_ = 0; - return false; - } - if ( attackPower_ < 0 || defensePower_ < 0 || healPower_ < 0 ) { - std::cout << name_ << " : Your powers cannot be negative." << std::endl - << "To punish you, the gods set your hitpoints to 1 and make you feeble as a kitten." << std::endl; - hitPoints_ = 1; - attackPower_ = 0; - defensePower_ = 0; - healPower_ = 0; - return false; - } - - return true; -}; - -// Some operators to support << and >> -std::ostream & operator<<( std::ostream & out, Entity const & e){ e.print(out); return out; } -std::istream & operator>>( std::istream & in, Entity & e) {e.input(in); return in;} - - - - - - -void Entity::printActions(std::ostream & out, unsigned int iturn) const { - - auto allactions = { - std::make_pair( "Attacks", &myAttacks_ ), - std::make_pair( "Defends", &myDefends_ ), - std::make_pair( "Heals", &myHeals_ ), - std::make_pair( "DamageReceived", &myReducedHitPoints_ ), - std::make_pair( "HealingRecieved", &myIncreasedHitPoints_ ) - }; - - out << "\"" << name_ << "\":{"; - for ( auto iaction = allactions.begin(); iaction != allactions.end(); ++iaction){ - auto actionname = iaction->first; - auto actions = iaction->second; - out << "\"" << actionname << "\":["; - - // Check if there are any actions for this turn - auto p_action = actions->find(iturn); - if ( p_action != actions->end() ){ - auto actionvals = p_action->second; - for ( auto ival = actionvals.begin(); ival != actionvals.end(); ++ival ){ - out << *ival; - // json does not like trailing comma - if ( ival + 1 != actionvals.end() ) - out << ","; - } - } else { - out << 0; - } - out << "]"; - - if ( iaction != allactions.end() - 1 ){ - out << ","; - } - out << std::endl; - } - out << "}"; -} diff --git a/Entity.h b/Entity.h deleted file mode 100644 index d9c68e3..0000000 --- a/Entity.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef Entity_h -#define Entity_h - -#include -#include -#include -#include - -// -------------------------------------- -/* ______ _ _ _ */ -/* | ____| | | (_) | */ -/* | |__ _ __ | |_ _| |_ _ _ */ -/* | __| | '_ \| __| | __| | | | */ -/* | |____| | | | |_| | |_| |_| | */ -/* |______|_| |_|\__|_|\__|\__, | */ -/* __/ | */ -/* |___/ */ -// -------------------------------------- -// -// This is the main base class for the Entities -// in the game. -// -// It provides access to these -// data members: -// std::string className_; // Name of this class (like, Warrior or Druid or Rogue) -// std::string name_; // Name of this particular entity (like, Lothar the Great) -// int isMagicUser_; // Can this user use magic? -// int hitPoints_; // Number of hit points left. -// int mana_; // Number of magical "mana" points are left to perform magic -// int attackPower_; // Number of hit points inflicted if I attack -// int defensePower_; // Number of hit points mitigated if someone attacks ME -// int healPower_; // Number of hit points to heal -// Entity * target_; // This Entity's current target for action -// -// These will store the history of the results of various actions -// action_map myAttacks_; -// action_map myDefends_; -// action_map myHeals_; -// action_map myReducedHitPoints_; -// action_map myIncreasedHitPoints_; -// -// The interface should be written in the base classes: -// Attack a target: virtual int attack( Entity * target=0 ); -// Heal an ally: virtual int heal ( Entity * ally=0 ); -// Defend against a target virtual int defend( Entity * target=0 ); -// -// These should make appropriate use of the helper functions -// int reduceHitPoints( int attack ); -// int increaseHitPoints( int heal ); -// -------------------------------------- -class Entity { - - public: - - Entity( std::string className, - std::string name, - unsigned int attackPower, - unsigned int healPower, - unsigned int defensePower, - unsigned int maxMana = 0, - bool check=true - ); - virtual ~Entity(); - - // The Battle class will handle the status of everyone in the fight. - // It will perform turn-based actions. - friend class Battle; - - // For recording actions - typedef std::vector action_vector; - typedef std::map< unsigned int, action_vector > action_map; - - // All of these default to "do nothing" and should be overriden in the derived classes. - virtual int attack( Entity * target=0 ); - virtual int heal ( Entity * ally=0 ); - virtual int defend( Entity * target=0 ); - - // Getter methods - std::string className() const { return className_;} - std::string name() const { return name_; } - int attackPower() const { return attackPower_; } - int defensePower() const { return defensePower_; } - int healPower() const { return healPower_; } - int hitPoints() const { return hitPoints_; } - int mana() const { return mana_; } - bool isDead() const { return hitPoints_ <= 0; } - bool isAlive() const { return !isDead(); } - - // Input and output - virtual void input( std::string instring); - virtual void input( std::istream &in ); - virtual void print( std::ostream &out) const; - virtual void printStats( std::ostream &out) const; - friend std::ostream & operator<<( std::ostream & out, Entity const & e); - friend std::istream & operator>>( std::istream & in, Entity & e); - - - - // - // Call these functions correctly in your derived classes - // when overloading "attack", "heal", and "block" - - // Reduce the hit points of "this" entity - int reduceHitPoints( int attack ); - // Increase the hit points of "this" entity - int increaseHitPoints( int heal ); - - - // Can this user use magic? - bool isMagicUser() const { return isMagicUser_; } - - // Set my current target - void setTarget( Entity * target ) { target_ = target; } - - // Get my current target - Entity * getTarget( void ) { return target_; } - - // These will store the history of the results of various actions - action_map const & myAttacks() const { return myAttacks_;} - action_map const & myDefends() const { return myDefends_;} - action_map const & myHeals() const { return myHeals_;} - action_map const & myReducedHitPoints() const { return myReducedHitPoints_;} - action_map const & myIncreasedHitPoints() const { return myIncreasedHitPoints_;} - - // Print the actions in a json format for turn "iturn". - void printActions(std::ostream & out, unsigned int iturn) const; - - protected : - unsigned int turn_; // Turn that "this" Entity is on. - std::string className_; // Name of this class (like, Warrior or Druid or Rogue) - std::string name_; // Name of this particular entity (like, Lothar the Great) - int isMagicUser_; // Can this user use magic? - int hitPoints_; // Number of hit points left. - int maxHitPoints_; // Maximum number of hit points possible - int mana_; // Number of magical "mana" points are left to perform magic - int attackPower_; // Number of hit points inflicted if I attack - int defensePower_; // Number of hit points mitigated if someone attacks ME - int healPower_; // Number of hit points to heal - - - Entity * target_; // This Entity's current target for action - - unsigned int getTurn() const { return turn_; } // return this turn. - - bool checkPowers(); // This will check to ensure the input values are sensible - - - - // Here are some default "attack", "defend", and "heal" methods. - int defaultAttack( Entity * target = 0); - int defaultHeal ( Entity * target = 0); - int defaultDefend( Entity * target = 0); - - // These will store the history of the results of various actions - action_map myAttacks_; - action_map myDefends_; - action_map myHeals_; - action_map myReducedHitPoints_; - action_map myIncreasedHitPoints_; - - -}; - -#endif diff --git a/ExampleBattle/Boss.txt b/ExampleBattle/Boss.txt new file mode 100644 index 0000000..a5d3fcd --- /dev/null +++ b/ExampleBattle/Boss.txt @@ -0,0 +1,4 @@ +!Description: +William the Warrior, Poppy the Priest, and Rachel the Rogue enter their first dungeon. Alas, their poor friend Bob has been bitten by a zombie and is trying to kill them. Help William, Poppy, and Rachel put Bob out of his misery! +! Non-Player character. Fields are "name", attack power, heal power, defense power, mana, multi attack power +Bob;20;0;0;10;5; diff --git a/ExampleBattle/BossScript.txt b/ExampleBattle/BossScript.txt new file mode 100644 index 0000000..095a3a8 --- /dev/null +++ b/ExampleBattle/BossScript.txt @@ -0,0 +1,3 @@ +! Test battle configuration. +Bob;attack;target; +Bob;attack;all; diff --git a/ExampleBattle/PlayerActions.txt b/ExampleBattle/PlayerActions.txt new file mode 100644 index 0000000..3a0bc7e --- /dev/null +++ b/ExampleBattle/PlayerActions.txt @@ -0,0 +1,6 @@ +! A list of player actions for you to copy-and-paste. +Rachel;attack;Bob; +William;defend;Bob; +Poppy;heal;William; +Poppy;heal;Rachel; +Poppy;heal;Poppy; diff --git a/ExampleBattle/PlayerCharacters.txt b/ExampleBattle/PlayerCharacters.txt new file mode 100644 index 0000000..62bf900 --- /dev/null +++ b/ExampleBattle/PlayerCharacters.txt @@ -0,0 +1,8 @@ +! +! Test party configuration: +! Warrior, Rogue, Priest +! Fields are : +! class, name, attack power, defense power, heal power +Warrior;William;0;3;0; +Priest;Poppy;0;0;8; +Rogue;Rachel;20;0;0; diff --git a/Arthas.txt b/LichKing/Arthas.txt similarity index 100% rename from Arthas.txt rename to LichKing/Arthas.txt diff --git a/ArthasAttacks.txt b/LichKing/ArthasScript.txt similarity index 100% rename from ArthasAttacks.txt rename to LichKing/ArthasScript.txt diff --git a/LichKing/PlayerActions.txt b/LichKing/PlayerActions.txt new file mode 100644 index 0000000..8d0623b --- /dev/null +++ b/LichKing/PlayerActions.txt @@ -0,0 +1,6 @@ +! A list of player actions for you to copy-and-paste. +Mograine;attack;Arthas; +Fordring;defend;Arthas; +Nakha;heal;Mograine; +Nakha;heal;Fordring; +Nakha;heal;Nakha; diff --git a/LichKing/PlayerCharacters.txt b/LichKing/PlayerCharacters.txt new file mode 100644 index 0000000..7a8b48c --- /dev/null +++ b/LichKing/PlayerCharacters.txt @@ -0,0 +1,4 @@ +! class ; name ; attack power ; defense power ;heal power; +Warrior;Fordring;0;10;0; +Priest;Nakha;0;0;12; +Rogue;Mograine;20;0;0; \ No newline at end of file diff --git a/Makefile b/Makefile index 93de626..4ea3757 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,48 @@ -# I am a comment, and I want to say that the variable CXX will be -# the compiler to use. -CXX=g++ -# Hey!, I am comment number 2. I want to say that CXXFLAGS will be the -# options I'll pass to the compiler. -CXX_FLAGS=-Wall -std=c++11 - - -EXEC_SRC = test_entity.cc test_boss.cc test_battle.cc WorldOfTextCraft.cc -EXEC = test_entity test_boss test_battle WorldOfTextCraft - -SRCS := $(wildcard *.cc) -SRCS := $(filter-out $(EXEC_SRC), $(SRCS)) -OBJECTS = $(SRCS:.cc=.o) - - -all: $(EXEC) - -test_entity: $(OBJECTS) test_entity.cc - $(CXX) $(CXX_FLAGS) $(OBJECTS) test_entity.cc -o test_entity - -test_boss: $(OBJECTS) test_boss.cc - $(CXX) $(CXX_FLAGS) $(OBJECTS) test_boss.cc -o test_boss - -test_battle: $(OBJECTS) test_battle.cc - $(CXX) $(CXX_FLAGS) $(OBJECTS) test_battle.cc -o test_battle - -WorldOfTextCraft: $(OBJECTS) WorldOfTextCraft.cc - $(CXX) $(CXX_FLAGS) $(OBJECTS) WorldOfTextCraft.cc -o WorldOfTextCraft - - -# To obtain object files -%.o: %.cc - $(CXX) $(CXX_FLAGS) -c $< -o $@ - -# To remove generated files +# Compiler and flags +CXX := g++ +CXXFLAGS := -std=c++17 -O2 -Wall -I./interface + +# Directories +SRC_DIR := src +INC_DIR := interface +TEST_DIR := tests +OBJ_DIR := build +BIN_DIR := bin + +# File patterns +SRC_FILES := $(filter-out src/WorldOfTextCraft.cc, $(wildcard $(SRC_DIR)/*.cc)) +OBJ_FILES := $(patsubst $(SRC_DIR)/%.cc, $(OBJ_DIR)/%.o, $(SRC_FILES)) +TEST_FILES := $(wildcard $(TEST_DIR)/*.cc) +TEST_BINS := $(patsubst $(TEST_DIR)/%.cc, $(TEST_DIR)/%.exe, $(TEST_FILES)) + +# Default target +all: bin/WorldOfTextCraft.exe $(TEST_BINS) + +# Rule to build main executable +bin/WorldOfTextCraft.exe: $(OBJ_FILES) | $(BIN_DIR) + @echo "Linking $@..." + $(CXX) $(CXXFLAGS) -o $@ src/WorldOfTextCraft.cc $(OBJ_FILES) + +# Rule to build test executables +$(TEST_DIR)/%.exe: $(TEST_DIR)/%.cc $(OBJ_FILES) | $(BIN_DIR) + @echo "Linking $@..." + $(CXX) $(CXXFLAGS) -o $@ $< $(OBJ_FILES) + +# Rule to compile source files into object files +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cc | $(OBJ_DIR) + @echo "Compiling $<..." + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Create directories if they don't exist +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) + +$(BIN_DIR): + mkdir -p $(BIN_DIR) + +# Cleanup clean: - rm -f $(EXEC) $(OBJECTS) + rm -rf ./$(OBJ_DIR)/*.o ./$(BIN_DIR)/*.exe + +# Phony targets +.PHONY: all clean diff --git a/MatchingHelpers.h b/MatchingHelpers.h deleted file mode 100644 index 197242f..0000000 --- a/MatchingHelpers.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef MatchingHelpers_h -#define MatchingHelpers_h - -#include "Entity.h" -#include - -class MatchSource { -public : - MatchSource( std::string s ) : s_(s) {} - - bool operator() ( std::shared_ptr const & entity ) { - if ( entity->name() == s_ ) { - return true; - } - return false; - } - -protected : - std::string s_; -}; - - -class MatchTarget { -public : - MatchTarget( std::string s ) : s_(s) {} - bool operator() ( std::shared_ptr const & entity ) { - if ( entity->getTarget() != 0 && entity->getTarget()->name() == s_ ) { - return true; - } - return false; - } -protected : - std::string s_; -}; - - - -#endif diff --git a/Priest.h b/Priest.h deleted file mode 100644 index 9e450b2..0000000 --- a/Priest.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef Priest_h -#define Priest_h - - /* _____ _ _ */ - /* | __ \ (_) | | */ - /* | |__) | __ _ ___ ___| |_ */ - /* | ___/ '__| |/ _ \/ __| __| */ - /* | | | | | | __/\__ \ |_ */ - /* |_| |_| |_|\___||___/\__| */ - -class Priest : public Entity{ - public : - Priest( std::string name="", int healPower=0 ) : - Entity( "Priest", name, 0, 0, healPower, 100) { - }; - - // In a heal, we increase the hit points - virtual int heal( Entity * other=0 ) { - return defaultHeal( other ); - }; - - private : -}; - -#endif diff --git a/README.md b/README.md index e25a155..a3db469 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,39 @@ # WorldOfTextCraft This is a trivial turn-based role-playing fighting game, designed to teach C++. -Apologies to Blizzard Entertainment and the Three Stooges for cribbing (and butchering) their content. +Apologies to Blizzard Entertainment for cribbing (and butchering) their content. The goal was to make this "bare bones" without a complicated build so people can see for themselves what is involved in the software. -This does require C++11 because of the use of a `vector< shared_ptr >` for memory management. -There is a very trivial Makefile to create three executables: +This does require C++11 because of the use of a `std::vector>` for memory management. -- **test_entity**: test the base fighting class functionality -- **test_boss**: test the boss functionality -- **test_battle**: test a pre-scripted battle -- **WorldOfTextCraft**: the actual text-based RPG. -= -In order to compile just type `make`. +The included Makefile automatically compiles the program as follows: +- All files matching `src/%.cc` (except for the main executable WorldOfTextCraft.cc) are compiled to `build/\*.o`. +- The tests, at `tests/%.cc`, are compiled to executables `tests/%.exe` (linked with the `build/%.o` files). +- `src/WorldOfTextCraft.cc` is compiled to `/bin/WorldOfTextCraft.exe`. + +In order to compile just type `make`. To delete your compiled stuff, type `make clean`, or just manually delete the `.exe` files. ## PHY410/505 at UB ### Docker image +This program can be run using the PHY410/505 Docker image, https://hub.docker.com/r/subsuny/compphys. From this folder, just run `./runDocker.sh ubsuny/compphys:latest` (if using WSL+Ubuntu, instead run `./runDocker_wfix.sh ubsuny/compphys:latest` to fix a permissions issue). These are the same scripts that we provide in https://github.com/ubsuny/CompPhys. -If you are using the [CompPhys](https://github.com/ubsuny/CompPhys) -repository and the [docker image](https://hub.docker.com/r/subsuny/compphys) you -should add this to the `results` folder **in your host OS**: - -``` bash -cd /your/working/directory/results -git clone git@github.com:ubsuny/WorldOfTextCraft.git -``` - -so it looks like this: - -``` bash -└── results - ├── CompPhys - └── WorldOfTextCraft -``` - -Then go to the parent folder of the `results` folder: +Remember to do your work inside the mounted folder! Files outside the mounted folder will be deleted upon exiting the container. -``` bash -cd /your/working/directory/ -./runDocker.sh ubsuny/compphys:latest 1 -``` - -This will launch the docker image, so you will be in the `/results` -folder in docker. -You can then: +## Usage +For convenience, you might want to add `WorldOfTextCraft/bin` to your path, so you can access the executable from anywhere: ``` bash cd WorldOfTextCraft -make +export PATH=$PATH:$(readlink -e bin) +# Now you can call WorldOfTextCraft.exe from anywhere ``` -## Usage +To run the program: ``` bash -./WorldOfTextCraft player_config boss_config boss_script +WorldOfTextCraft.exe player_config boss_config boss_script ``` Here, we have: @@ -67,38 +45,42 @@ Here, we have: For instance: ``` bash -./WorldOfTextCraft AshenVerdict.txt Arthas.txt ArthasAttacks.txt +cd ExampleBattle +WorldOfTextCraft.exe PlayerCharacters.txt Boss.txt BossScript.txt ``` -## Syntax +You can copy-and-paste lines from PlayerScript.txt into the game to run through the example battle. Note: after your first turn, if you hit "enter", you will execute the previous command without having to type anything. -In all syntax files, lines starting with "!" will be ignored (i.e. comments) +### Configuration files -- Player configuration - - One line per party member, syntax : `type;name;attack_power;defense_power;heal_power;` +In all configuration files, lines starting with "!" will be ignored (i.e. comments) + +- Player configuration file: + - Contains a list of party members (i.e., the characters you control) and their stats. + - One line per party member, syntax: `type;name;attack_power;defense_power;heal_power;` - Example: ``` text ! - ! Test party configuration: + ! Test party configuration: ! Warrior, Rogue, Priest - ! Fields are : - ! name, entity type, attack power, heal power, defense power - Rogue;Curly;20;0;0; - Priest;Larry;0;0;8; - Warrior;Moe;0;3;0; + ! Fields are : + ! class, name, attack power, defense power, heal power + Warrior;William The Warrior;0;3;0; + Priest;Poppy The Priest;0;0;8; + Rogue;Rachel The Rogue;20;0;0; ``` -- Boss configuration : +- Boss configuration file: - First line is the description of the battle to be displayed. - Then one line per boss, syntax: `name;attack_power;heal_power;defense_power;mana;multi_attack` - Example: ``` text !Description: - Larry, Moe, and Curly are the Three Stooges. Their poor friend Shemp has been bitten by a zombie and is trying to kill them. Help Larry, Moe, and Curly put Shemp out of his misery. Nyuk Nyuk Nyuk. + William the Warrior, Poppy the Priest, and Rachel the Rogue enter their first dungeon. Alas, their poor friend Bob has been bitten by a zombie and is trying to kill them. Help William, Poppy, and Rachel put Bob out of his misert! ! Non-Player character. Fields are "name", attack power, heal power, defense power, mana, multi attack power - Shemp;20;0;0;10;5; + Bob The Boss;20;0;0;10;5; ``` - Boss script: @@ -113,7 +95,8 @@ In all syntax files, lines starting with "!" will be ignored (i.e. comments) Shemp;attack;all; ``` -- Player script: (for `test_battle` only, not for `WorldOfTextCraft`): +- Player script: + - Contains a script for player character actions. Note: this is only implemented in `test_battle.cc`, not `WorldOfTextCraft.cc`. - One line per action, syntax: `name;action;target_name` - Here, "action" can be "attack", "heal", or "defend" - "target_name" must refer to a member of the Boss party. @@ -121,18 +104,18 @@ In all syntax files, lines starting with "!" will be ignored (i.e. comments) ``` text ! Test battle configuration. - Curly;attack;Shemp; - Moe;defend;Shemp; - Larry;heal;Moe; + Rachel The Rogue;attack;Bob The Boss; + William The Warrior;defend;Bob The Boss; + Poppy The Priest;heal;William The Warrior; ``` ## Game Mechanics The game is rather simple. Initially there are three player classes, all with 100 hit points, and one Boss class, with 500 hit points. -Once a character reaches 0 hit points, it dies. All characters have a common base C++ class -"Entity", and then override the functionality in that base C++ class to be player-class-specific: - +Once a character reaches 0 hit points, it dies. + All characters have a common base C++ class +"Character", and then override the functionality in that base C++ class to be player-class-specific: - Rogue: can cast "attack", which reduces the hit points of a target by 20. - Warrior: can cast "defend", which forces the target to attack the Warrior. They have a defense mitigation modifier ("defense power") that reduces the hit points lost upon an attack. The final damage is "attack_power - defense_power". - Priest: can cast "heal", which increases the hit points of a target by 20. Starts with 100 mana. @@ -140,21 +123,23 @@ Once a character reaches 0 hit points, it dies. All characters have a common bas You can adjust the game mechanics to add player classes of your own! -## Playing WorldOfTextCraft : The Lich King Scenario -To execute "The Lich King" Scenario, execute: +## Playing WorldOfTextCraft : The Lich King Scenario +In the Lich King scenario, you fight the boss Arthas (the "Lich King" bit is just for flavor, it behaves more or less the same as Bob The Boss from the example). The configuration files are located at `WorldOfTextCraft/LichKing`. +To run the scenario, make sure `WorldOfTextCraft/bin` is in your path, and then execute: ``` bash -./WorldOfTextCraft AshenVerdict.txt Arthas.txt ArthasAttacks.txt +cd LichKing +WorldOfTextCraft.exe PlayerCharacters.txt Arthas.txt ArthasAttacks.txt ``` You will be prompted as follows: ``` text reading PC configuration -Added entity: Fordring ( Warrior): HP= 100, mana = 0, no target -Added entity: Thrall ( Priest): HP= 100, mana = 100, no target -Added entity: Mograine ( Rogue): HP= 100, mana = 0, no target +Added character: Fordring ( Warrior): HP= 100, mana = 0, no target +Added character: Tarvek ( Priest): HP= 100, mana = 100, no target +Added character: Mograine ( Rogue): HP= 100, mana = 0, no target reading NPC configuration Input boss: Arthas ( Boss): HP= 500, mana = 0, no target reading NPC action script @@ -184,7 +169,7 @@ reading NPC action script |--------------------------------------------| |--------------------------------------------| Welcome brave traveler... -You venture forth from your stronghold in Azeroth to the icy continent of Northrend, accompanied only by your fellow adventurers and your courage. You have tracked Arthas Menethil to his Frozen Throne in the plagued wastelands of Icecrown. Once a brave champion of Lordaeron, Arthas was corrupted by evil and his soul subsumed by the shaman Ner'zhul upon taking the cursed Runeblade, Frostmourne, forming the entity known as the Lich King, threatening all life in Azeroth to become mindless undead slaves of the Burning Legion. The Knights of the Ebon Blade and the Argent Crusade have now formed the Ashen Verdict, and tasked you to destroy the Lich King and end his undead plague upon Azeroth. As you reach the Frozen Throne, the Lich King's seat, you hear a voice in your head. The Lich King whispers "Young heroes, I was once like you. You have come to this place seeking to bring judgement upon the damned. But, be warned. In the end, all that awaits you is death. Only then will you understand, you've been following in my footsteps all along. So come then, you heroes! Come in all your power and glory! For in this final hour, all must serve the one... true... king... Frostmourne HUNGERS....". The battle is joined. +You venture forth from your stronghold in Azeroth to the icy continent of Northrend, accompanied only by your fellow adventurers and your courage. You have tracked Arthas Menethil to his Frozen Throne in the plagued wastelands of Icecrown. Once a brave champion of Lordaeron, Arthas was corrupted by evil and his soul subsumed by the shaman Ner'zhul upon taking the cursed Runeblade, Frostmourne, forming the character known as the Lich King, threatening all life in Azeroth to become mindless undead slaves of the Burning Legion. The Knights of the Ebon Blade and the Argent Crusade have now formed the Ashen Verdict, and tasked you to destroy the Lich King and end his undead plague upon Azeroth. As you reach the Frozen Throne, the Lich King's seat, you hear a voice in your head. The Lich King whispers "Young heroes, I was once like you. You have come to this place seeking to bring judgement upon the damned. But, be warned. In the end, all that awaits you is death. Only then will you understand, you've been following in my footsteps all along. So come then, you heroes! Come in all your power and glory! For in this final hour, all must serve the one... true... king... Frostmourne HUNGERS....". The battle is joined. Are you ready to begin? [Y/n] ``` @@ -199,7 +184,7 @@ Arthas will perform action 3 on their target : NO TARGET! -------------- === players: Fordring ( Warrior): HP= 100, mana = 0, no target - Thrall ( Priest): HP= 100, mana = 100, no target + Tarvek ( Priest): HP= 100, mana = 100, no target Mograine ( Rogue): HP= 100, mana = 0, no target === monsters: @@ -209,8 +194,8 @@ Arthas attacks Fordring with attack power 20 Fordring loses 10 hit points after attack 20 and defense 10 Arthas multi-attacks Fordring with attack power 8 Fordring loses 0 hit points after attack 8 and defense 10 -Arthas multi-attacks Thrall with attack power 8 -Thrall loses 8 hit points after attack 8 and defense 0 +Arthas multi-attacks Tarvek with attack power 8 +Tarvek loses 8 hit points after attack 8 and defense 0 Arthas multi-attacks Mograine with attack power 8 Mograine loses 8 hit points after attack 8 and defense 0 Action for Fordring: @@ -229,9 +214,9 @@ You will then have the next player. Enter the next action, and you will again see a printout of the status of your attack: ``` text -Action for Thrall: -Thrall;heal;Mograine; -Thrall heals Mograine for 12 +Action for Tarvek: +Tarvek;heal;Mograine; +Tarvek heals Mograine for 12 ``` Finally you will see your next character. @@ -250,7 +235,7 @@ This reaches the end of your turn, so the status is printed again, and it is the -------------- === players: Fordring ( Warrior): HP= 90, mana = 0, target= Arthas - Thrall ( Priest): HP= 92, mana = 90, target= Mograine + Tarvek ( Priest): HP= 92, mana = 90, target= Mograine Mograine ( Rogue): HP= 100, mana = 0, target= Arthas === monsters: @@ -260,14 +245,14 @@ Arthas attacks Fordring with attack power 20 Fordring loses 10 hit points after attack 20 and defense 10 Arthas multi-attacks Fordring with attack power 8 Fordring loses 0 hit points after attack 8 and defense 10 -Arthas multi-attacks Thrall with attack power 8 -Thrall loses 8 hit points after attack 8 and defense 0 +Arthas multi-attacks Tarvek with attack power 8 +Tarvek loses 8 hit points after attack 8 and defense 0 Arthas multi-attacks Mograine with attack power 8 Mograine loses 8 hit points after attack 8 and defense 0 Action for Fordring: ``` -You can then cycle through until your party is victorious (the Boss dies), or fails (all of you die). Then the scenario ends. To exit prematurely, simply "control-C" out. +You can then cycle through until your party is victorious (the Boss dies), or fails (all of you die). Note: after your first turn, if you hit "enter", you will execute the previous command without having to type anything. Then the scenario ends. To exit prematurely, simply "control-C" out. ## Battle Log @@ -289,7 +274,7 @@ The C++ game will create a log of actions in a "DamageReceived":[10,0], "HealingRecieved":[0] }, - "Thrall":{"Attacks":[0], # and so on + "Tarvek":{"Attacks":[0], # and so on "Defends":[0], "Heals":[8], "DamageReceived":[8], @@ -318,12 +303,12 @@ columnar like this, with the column headings printed on top: ------------ turn : 0 --------------- ==== Arthas : 26, 0, 0, 20, 0, ==== Fordring : 0, 10, 0, 10, 0, -==== Thrall : 0, 0, 8, 8, 0, +==== Tarvek : 0, 0, 8, 8, 0, ==== Mograine : 20, 0, 0, 8, 8, ------------ turn : 1 --------------- ==== Arthas : 26, 0, 0, 20, 0, ==== Fordring : 0, 10, 0, 10, 0, -==== Thrall : 0, 0, 8, 8, 0, +==== Tarvek : 0, 0, 8, 8, 0, ==== Mograine : 20, 0, 0, 8, 8, ... diff --git a/Rogue.h b/Rogue.h deleted file mode 100644 index 136b07e..0000000 --- a/Rogue.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef Rogue_h -#define Rogue_h - - - /* _____ */ - /* | __ \ */ - /* | |__) |___ __ _ _ _ ___ */ - /* | _ // _ \ / _` | | | |/ _ \ */ - /* | | \ \ (_) | (_| | |_| | __/ */ - /* |_| \_\___/ \__, |\__,_|\___| */ - /* __/ | */ - /* |___/ */ - -class Rogue : public Entity{ - public : - Rogue( std::string name="", int attackPower=0 ) : - Entity( "Rogue", name, attackPower, 0, 0) { - }; - - // In an attack, we reduce the hit points - virtual int attack( Entity * other=0 ) { - return defaultAttack(other); - }; -}; - -#endif diff --git a/Shemp.txt b/Shemp.txt deleted file mode 100644 index bfaa51a..0000000 --- a/Shemp.txt +++ /dev/null @@ -1,4 +0,0 @@ -!Description: -Larry, Moe, and Curly are the Three Stooges. Their poor friend Shemp has been bitten by a zombie and is trying to kill them. Help Larry, Moe, and Curly put Shemp out of his misery. Nyuk Nyuk Nyuk. -! Non-Player character. Fields are "name", attack power, heal power, defense power, mana, multi attack power -Shemp;20;0;0;10;5; diff --git a/ShempAttacks.txt b/ShempAttacks.txt deleted file mode 100644 index 44a5c0c..0000000 --- a/ShempAttacks.txt +++ /dev/null @@ -1,3 +0,0 @@ -! Test battle configuration. -Shemp;attack;target; -Shemp;attack;all; diff --git a/StoogesBattle.txt b/StoogesBattle.txt deleted file mode 100644 index c2f7ba8..0000000 --- a/StoogesBattle.txt +++ /dev/null @@ -1,4 +0,0 @@ -! Test battle configuration. -Curly;attack;Shemp; -Moe;defend;Shemp; -Larry;heal;Moe; diff --git a/ThreeStooges.txt b/ThreeStooges.txt deleted file mode 100644 index 5587506..0000000 --- a/ThreeStooges.txt +++ /dev/null @@ -1,8 +0,0 @@ -! -! Test party configuration: -! Warrior, Rogue, Priest -! Fields are : -! entity type, name, attack power, defense power, heal power -Rogue;Curly;20;0;0; -Priest;Larry;0;0;8; -Warrior;Moe;0;3;0; diff --git a/Warrior.h b/Warrior.h deleted file mode 100644 index 781ab92..0000000 --- a/Warrior.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef Warrior_h -#define Warrior_h - - - /* __ __ _ */ - /* \ \ / / (_) */ - /* \ \ /\ / /_ _ _ __ _ __ _ ___ _ __ */ - /* \ \/ \/ / _` | '__| '__| |/ _ \| '__| */ - /* \ /\ / (_| | | | | | | (_) | | */ - /* \/ \/ \__,_|_| |_| |_|\___/|_| */ - - -class Warrior : public Entity{ - public : - Warrior( std::string name="", int defensePower=0 ) : - Entity( "Warrior", name, 0, defensePower, 0) { - }; - - // This will force the target of the other object to be "this" Entity. - virtual int defend( Entity * other =0) { - return defaultDefend(other); - } - -}; - -#endif diff --git a/data/LichKing.png b/data/LichKing.png new file mode 100644 index 0000000..ed9aa46 Binary files /dev/null and b/data/LichKing.png differ diff --git a/data/example/Boss.txt b/data/example/Boss.txt new file mode 100644 index 0000000..5e7802d --- /dev/null +++ b/data/example/Boss.txt @@ -0,0 +1,6 @@ +!Description: +!Mercilessly butchered from http://wowwiki.wikia.com/wiki/Arthas_Menethil +You venture forth from your stronghold in Azeroth to the icy continent of Northrend, accompanied only by your fellow adventurers and your courage. You have tracked Arthas Menethil to his Frozen Throne in the plagued wastelands of Icecrown. Once a brave champion of Lordaeron, Arthas was corrupted by evil and his soul subsumed by the shaman Ner'zhul upon taking the cursed Runeblade, Frostmourne, forming the entity known as the Lich King, threatening all life in Azeroth to become mindless undead slaves of the Burning Legion. The Knights of the Ebon Blade and the Argent Crusade have now formed the Ashen Verdict, and tasked you to destroy the Lich King and end his undead plague upon Azeroth. As you reach the Frozen Throne, the Lich King's seat, you hear a voice in your head. The Lich King whispers "Young heroes, I was once like you. You have come to this place seeking to bring judgement upon the damned. But, be warned. In the end, all that awaits you is death. Only then will you understand, you've been following in my footsteps all along. So come then, you heroes! Come in all your power and glory! For in this final hour, all must serve the one... true... king... Frostmourne HUNGERS....". The battle is joined. +! Non-Player character. +!Name;AttackPower;HealPower;DefensePower;Mana;MultiAttackPower +Mallory The Boss;20;0;0;0;8; diff --git a/data/example/BossScript.txt b/data/example/BossScript.txt new file mode 100644 index 0000000..7a072cf --- /dev/null +++ b/data/example/BossScript.txt @@ -0,0 +1,3 @@ +! Test battle configuration. +Arthas;attack;target; +Arthas;attack;all; diff --git a/data/example/PlayerCharacters.txt b/data/example/PlayerCharacters.txt new file mode 100644 index 0000000..0c7ff83 --- /dev/null +++ b/data/example/PlayerCharacters.txt @@ -0,0 +1,3 @@ +Warrior;Alice The Warrior;0;10;0; +Priest;Bob The Priest;0;0;12; +Rogue;Carol The Rogue;20;0;0; diff --git a/interface/Battle.h b/interface/Battle.h new file mode 100644 index 0000000..5600603 --- /dev/null +++ b/interface/Battle.h @@ -0,0 +1,196 @@ +#ifndef Battle_h +#define Battle_h + +// -------------------------------------- +/* ____ _ _ _ */ +/* | _ \ | | | | | | */ +/* | |_) | __ _| |_| |_| | ___ */ +/* | _ < / _` | __| __| |/ _ \ */ +/* | |_) | (_| | |_| |_| | __/ */ +/* |____/ \__,_|\__|\__|_|\___| */ +// -------------------------------------- +// This is the main driver of the game. +// +// It reads in two configuration files +// consisting of the player characters (PCs) +// and the non-player characters (NPCs) in order +// of attack. The battle will they play out until +// one of the parties is empty. +// +// The configuration files will all support comments in the form of an +// exclamation point ("!"). The configuration files will have fields separated +// by a semicolon (";") +// +// The PC configuration syntax is as follows (omit the < >, they are there for +// notation here only) ;;;;; +// : Name of the character as a string, like "Moe" +// : Type of character as a string, like "Warrior" +// : Attack power as an integer, like "20" +// : Defense power as an integer, like "20" +// : Heal power as an integer, like "20" +// Example: +// Curly;Rogue;0;20;0; +// Moe;Warrior;20;0;0; +// Larry;Priest;0;0;20; +// +// +// The NPC configuration is similar, but may have different attributes since +// they have a "multi-attack power" which attacks everyone in the party. +// +// Example: +// Shemp;Boss;20;0;0;5; +// +// +// There is then a syntax for the battle itself, called a "script". It will +// consist of a set of actions your team will apply per turn. +// ;; +// : Name of character performing action +// : Name of the action to apply (like "attack", "heal", "defend") +// : Name of the target on which to perform the action +// +// Example: +// Curly;attack;Shemp; <<--- Curly attacks Shemp +// Moe;defend;Shemp; <<--- Moe switches Shemp's target to be Moe, +// defends. Larry;heal;Moe; <<--- Larry heals moe +// +// There is also a command-line interface that will cycle through your players +// and ask for actions prompted from "std::cin" like: Action for Curly? +// (You can then type "attack;Shemp;") +// Action for Moe? +// (You can then type "defend;Shemp;") +// Action for Larry? +// (You can then type "heal;Moe;") +// +// +// +// +// +// +// The Boss "script" will also be similarly defined, but it will contain a +// description and the boss will attack whoever the target is. The target of the +// Boss is initially set to the first person in your configuration file. For +// instance, Shemp may attack the "first" person on your list (above, this is +// Moe), and then attack everyone on your list. This will happen once per turn. +// Even in the command-line interface, the same sequence is executed once per +// turn. If the "defend" method is called, however, the target of +// +// Shemp;attack;target <<---- Shemp will attack the first person on +// your list Shemp;attack;all <<---- Shemp will attack your entire +// party with "multi_attack" +// +// Suppose that your Warrior Moe casts "defend" against Shemp, then Shemp's +// target will be Moe. +// +// -------------------------------------- + +#include "Boss.h" +#include "Character.h" +#include "Priest.h" +#include "Rogue.h" +#include "Warrior.h" +#include +#include +#include +#include +#include + +class Battle { +public: + // Create a "typedef" that sets an alias of one type to another to save + // typing. + typedef std::vector> CharacterVector; + + Battle() : turn_(0), description_(""), user_turns_(false), scripted_(false) {} + ~Battle() {} + + // Read the configuration for this Battle. + // It will consist of the player characters, the non-player characters, + // in their order of attack. + bool readPCConfiguration(std::string filename); + bool readNPCConfiguration(std::string filename); + + // Read a script of actions. It will consist of a + // list of actions per turn to execute. + // This will append to the existing actions. + bool loadActionScript(std::string filename); + // Clear the actions + void clearActions() { actions_.clear(); } + + // Are we letting the user work in turns? + void setUserTurns(bool user_turns = true) { user_turns_ = user_turns; } + bool user_turns() const { return user_turns_; } + + // Perform scripted actions + bool performScriptedActions(); + + // For user turn-by-turn action + bool performUserActions(std::istream &in = std::cin); + + // Print this turn + virtual void print(std::ostream &out = std::cout); + // Print all of the stats of the players in the battle + void printStats(std::ostream &out = std::cout); + // Print the actions queued + void printActions(std::ostream &out = std::cout); + + // Return the description + std::string description() const { return description_; } + + // Print the splash screen + void splash(std::ostream &out = std::cout) const; + + // Print the logfile of the battle + void printLog(std::ostream &out) const; + +private: + unsigned int turn_; // What turn is it? + std::string description_; // Fun description of battle. + bool user_turns_; // Input turn-by-turn user input? By default, no. + CharacterVector pcs_; // Player characters + CharacterVector npcs_; // Non-player characters + + bool scripted_; // Is this fight scripted or user-turns? + + std::map, std::string> last_action_; // Save last action; next turn, perform as default action if input is empty + + // + // + // Internal workings of "Battle": + // Each turn, Entities will perform actions on each other. + // However, we don't want to have to parse the text file + // over and over again, so we store the actions in a vector. + // + // + + // Find a source named "s" + CharacterVector::iterator find_character(std::string s); + + // This will enumerate the possible actions that can be taken + enum ActionType { ATTACK = 0, HEAL, DEFEND, MULTIATTACK, N_ACTIONS }; + + // A private "struct" (i.e. a class with all public members) to store the + // source and action type + struct QuickAction { + std::shared_ptr source; + ActionType action; + QuickAction(std::shared_ptr s = 0, ActionType a = N_ACTIONS) + : source(s), action(a) {} + }; + std::vector + actions_; // List of scripted actions to perform per turn. + + // Add an action + // in the format "source;action;target;" + bool parseAction(std::string, QuickAction &qa); + + bool anyNPCAlive() const; + bool anyPCAlive() const; + + // Check if the Boss's target is dead. If so, set to the first living member + // of the party. Otherwise, return false; + bool setBossTarget(Boss *boss); +}; + +#endif diff --git a/interface/Boss.h b/interface/Boss.h new file mode 100644 index 0000000..c302c5c --- /dev/null +++ b/interface/Boss.h @@ -0,0 +1,52 @@ +#ifndef Boss_h +#define Boss_h + +#include "Character.h" +#include + +/* ____ */ +/* | _ \ */ +/* | |_) | ___ ___ ___ */ +/* | _ < / _ \/ __/ __| */ +/* | |_) | (_) \__ \__ \ */ +/* |____/ \___/|___/___/ */ +// +// This is the Boss class. +// Bosses are special entities that are stronger, +// and have the ability to attack the entire opposing party at once. +// +// In addition to the standard Character interface, it also has +// an "attackAll" function that will attack all of the entities +// inside of a vector. +// -------------------------------------- + +class Boss : public Character { +public: + friend class Battle; + + Boss(std::string name = "", int attack_power = 0, int heal_power = 0, + int defense_power = 0, int mana = 0, int multi_attack_power = 0); + + // Default defend, heal, and attack + int defend(Character *other = 0) override { return defaultDefend(other); } + int heal(Character *other = 0) override { return defaultHeal(other); } + int attack(Character *other = 0) override { return defaultAttack(other); } + + // Special multiattack to attack many at once. + virtual int multiAttack(Character *other = 0); + + void printStats(std::ostream &out) const override; + void print(std::ostream &out) const override; + + // Overload the input method. + void input(std::string line) override; + +protected: + ActionMap my_multi_attacks_; + +private: + int multi_attack_power_; // The attack power of a mob who can attack more than + // one opponent. +}; + +#endif diff --git a/interface/Character.h b/interface/Character.h new file mode 100644 index 0000000..693dfed --- /dev/null +++ b/interface/Character.h @@ -0,0 +1,162 @@ +#ifndef Character_h +#define Character_h + +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------// +/* _________ .__ __ */ +/* \_ ___ \| |__ _____ ____________ _____/ |_ ___________ */ +/* / \ \/| | \\__ \\_ __ \__ \ _/ ___\ __\/ __ \_ __ \ */ +/* \ \___| Y \/ __ \| | \// __ \\ \___| | \ ___/| | \/ */ +/* \______ /___| (____ /__| (____ /\___ >__| \___ >__| */ +/* \/ \/ \/ \/ \/ \/ */ +// ----------------------------------------------------------------// +// +// This is the main base class for the Characters +// in the game. +// The class is pure virtual. You must implement the following functions in +// derived classes: +// virtual int attack(Character *target = 0) = 0; +// virtual int heal(Character *ally = 0) = 0; +// virtual int defend(Character *target = 0) = 0; +// +// Data members: +// std::string class_name_; // Name of this class (like, Warrior or Druid or +// Rogue) std::string name_; // Name of this particular character (like, +// Lothar the Great) int is_magic_user_; // Can this user use magic? int +// hit_points_; // Number of hit points left. int mana_; // Number of +// magical "mana" points are left to perform magic int attack_power_; // +// Number of hit points inflicted if I attack int defense_power_; // +// Number of hit points mitigated if someone attacks ME int heal_power_; // +// Number of hit points to heal Character * target_; // This Character's +// current target for action +// +// These will store the history of the results of various actions +// ActionMap my_attacks_; +// ActionMap my_defends_; +// ActionMap my_heals_; +// ActionMap my_reduced_hit_points_; +// ActionMap my_increased_hit_points_; +// +// The interface should be written in the base classes: +// Attack a target: virtual int attack( Character * target=0 ); +// Heal an ally: virtual int heal ( Character * ally=0 ); +// Defend against a target virtual int defend( Character * target=0 ); +// +// These should make appropriate use of the helper functions +// int reduceHitPoints( int attack ); +// int increaseHitPoints( int heal ); +// -------------------------------------- +class Character { + +public: + Character(std::string class_name, std::string name, unsigned int attack_power, + unsigned int heal_power, unsigned int defense_power, + unsigned int max_mana = 0, bool check = true); + virtual ~Character(); + + // The Battle class will handle the status of everyone in the fight. + // It will perform turn-based actions. + friend class Battle; + + // For recording actions + typedef std::vector ActionVector; + typedef std::map ActionMap; + + // All of these default to "do nothing" and should be overriden in the derived + // classes. + virtual int attack(Character *target = 0) = 0; + virtual int heal(Character *ally = 0) = 0; + virtual int defend(Character *target = 0) = 0; + + // Getter methods + std::string className() const { return class_name_; } + std::string name() const { return name_; } + int attackPower() const { return attack_power_; } + int defensePower() const { return defense_power_; } + int healPower() const { return heal_power_; } + int hitPoints() const { return hit_points_; } + int mana() const { return mana_; } + bool isDead() const { return hit_points_ <= 0; } + bool isAlive() const { return !isDead(); } + + // Input and output + virtual void input(std::string instring); + virtual void input(std::istream &in); + virtual void post_input() {}; // Hook for actions after input, i.e. after stats are set + virtual void print(std::ostream &out) const; + virtual void printStats(std::ostream &out) const; + friend std::ostream &operator<<(std::ostream &out, Character const &e); + friend std::istream &operator>>(std::istream &in, Character &e); + + // + // Call these functions correctly in your derived classes + // when overloading "attack", "heal", and "block" + + // Reduce the hit points of "this" character + int reduceHitPoints(int attack); + // Increase the hit points of "this" character + int increaseHitPoints(int heal); + + // Can this user use magic? + bool isMagicUser() const { return is_magic_user_; } + + // Set my current target + void setTarget(Character *target) { target_ = target; } + + // Get my current target + Character *getTarget(void) { return target_; } + + // These will store the history of the results of various actions + ActionMap const &myAttacks() const { return my_attacks_; } + ActionMap const &myDefends() const { return my_defends_; } + ActionMap const &myHeals() const { return my_heals_; } + ActionMap const &myReducedHitPoints() const { return my_reduced_hit_points_; } + ActionMap const &myIncreasedHitPoints() const { + return my_increased_hit_points_; + } + + // Print the actions in a json format for turn "iturn". + void printActions(std::ostream &out, unsigned int iturn) const; + +protected: + unsigned int turn_; // Turn that "this" Character is on. + std::string + class_name_; // Name of this class (like, Warrior or Druid or Rogue) + std::string name_; // Name of this particular character (like, Lothar the Great) + int is_magic_user_; // Can this user use magic? + int hit_points_; // Number of hit points left. + int max_hit_points_; // Maximum number of hit points possible + int mana_; // Number of magical "mana" points are left to perform magic + int attack_power_; // Number of hit points inflicted if I attack + int defense_power_; // Number of hit points mitigated if someone attacks me + int heal_power_; // Number of hit points to heal + + Character *target_; // This Character's current target for action + + unsigned int getTurn() const { return turn_; } // return this turn. + + bool checkPowers(); // This will check to ensure the input values are sensible + + // Here are some default "attack", "defend", and "heal" methods. + int defaultAttack(Character *target = 0); + int defaultHeal(Character *target = 0); + int defaultDefend(Character *target = 0); + + // Random number generator + std::default_random_engine gen_; + std::poisson_distribution<> poisson_; + + // These will store the history of the results of various actions + ActionMap my_attacks_; + ActionMap my_defends_; + ActionMap my_heals_; + ActionMap my_reduced_hit_points_; + ActionMap my_increased_hit_points_; +}; + +#endif diff --git a/interface/MatchingHelpers.h b/interface/MatchingHelpers.h new file mode 100644 index 0000000..9fc4674 --- /dev/null +++ b/interface/MatchingHelpers.h @@ -0,0 +1,36 @@ +#ifndef MatchingHelpers_h +#define MatchingHelpers_h + +#include "Character.h" +#include + +class MatchSource { +public: + MatchSource(std::string s) : s_(s) {} + + bool operator()(std::shared_ptr const &character) { + if (character->name() == s_) { + return true; + } + return false; + } + +protected: + std::string s_; +}; + +class MatchTarget { +public: + MatchTarget(std::string s) : s_(s) {} + bool operator()(std::shared_ptr const &character) { + if (character->getTarget() != 0 && character->getTarget()->name() == s_) { + return true; + } + return false; + } + +protected: + std::string s_; +}; + +#endif diff --git a/interface/Priest.h b/interface/Priest.h new file mode 100644 index 0000000..b728461 --- /dev/null +++ b/interface/Priest.h @@ -0,0 +1,26 @@ +#ifndef Priest_h +#define Priest_h + +#include "Character.h" + +/* _____ _ _ */ +/* | __ \ (_) | | */ +/* | |__) | __ _ ___ ___| |_ */ +/* | ___/ '__| |/ _ \/ __| __| */ +/* | | | | | | __/\__ \ |_ */ +/* |_| |_| |_|\___||___/\__| */ + +class Priest : public Character { +public: + Priest(std::string name = "", int heal_power = 0) + : Character("Priest", name, 0, 0, heal_power, 100) {}; + + // In a heal, we increase the hit points + int defend(Character *other = 0) override; + int heal(Character *other = 0) override; + int attack(Character *other = 0) override; + +private: +}; + +#endif diff --git a/interface/Rogue.h b/interface/Rogue.h new file mode 100644 index 0000000..e1a4473 --- /dev/null +++ b/interface/Rogue.h @@ -0,0 +1,27 @@ +#ifndef Rogue_h +#define Rogue_h + +#include "Character.h" + +/* _____ */ +/* | __ \ */ +/* | |__) |___ __ _ _ _ ___ */ +/* | _ // _ \ / _` | | | |/ _ \ */ +/* | | \ \ (_) | (_| | |_| | __/ */ +/* |_| \_\___/ \__, |\__,_|\___| */ +/* __/ | */ +/* |___/ */ + +class Rogue : public Character { +public: + Rogue(std::string name = "", int attack_power = 0) + : Character("Rogue", name, attack_power, 0, 0) { + }; + + int defend(Character *other = 0) override; + int heal(Character *other = 0) override; + int attack(Character *other = 0) override; + +}; + +#endif diff --git a/interface/Warrior.h b/interface/Warrior.h new file mode 100644 index 0000000..ea0d4b8 --- /dev/null +++ b/interface/Warrior.h @@ -0,0 +1,23 @@ +#ifndef Warrior_h +#define Warrior_h + +#include "Character.h" + +/* __ __ _ */ +/* \ \ / / (_) */ +/* \ \ /\ / /_ _ _ __ _ __ _ ___ _ __ */ +/* \ \/ \/ / _` | '__| '__| |/ _ \| '__| */ +/* \ /\ / (_| | | | | | | (_) | | */ +/* \/ \/ \__,_|_| |_| |_|\___/|_| */ + +class Warrior : public Character { +public: + Warrior(std::string name = "", int defense_power = 0) + : Character("Warrior", name, 0, defense_power, 0) {}; + + int defend(Character *other = 0) override; + int heal(Character *other = 0) override; + int attack(Character *other = 0) override; +}; + +#endif diff --git a/launchJupyter.sh b/launchJupyter.sh new file mode 100755 index 0000000..eacf250 --- /dev/null +++ b/launchJupyter.sh @@ -0,0 +1,7 @@ +#!/bin/bash +if [ -z $1 ]; then + JPORT=8888 +else + JPORT="${1}" +fi +jupyter-lab --ip 0.0.0.0 --port $JPORT --no-browser diff --git a/midterm.md b/midterm.md new file mode 100644 index 0000000..abcca86 --- /dev/null +++ b/midterm.md @@ -0,0 +1,247 @@ +--- +geometry: +- margin=1in +mainfont: Palatino +header-includes: +- \usepackage[document]{ragged2e} +--- + +# PHY410/505 midterm, fall 2025 +*Due date: Thursday, October 23 at 11:59pm* + +This midterm will test the technical programming aspects covered so far this semester. +We've provided some C++ code for a text-based "role playing game": __World of Textcraft__. +This is a larger program than those used in assignments so far, with several classes and tests defined in addition to the main executable. +While the program does not directly involve much physics, it covers many of the technical topics introduced in the first half of this semester. +The same program structure could be used, for example, for an N-body physics simulation, where physical interactions replace the simple hit point calculations. + +## Instructions +- PHY410: Do problems 1–3. You can do problem 4 for extra credit (it will replace your lowest scoring problem from your homework problems, if higher). +- PHY505: Do all problems, 1–4. + +Accept the assignment on GitHub Classroom here: https://classroom.github.com/classrooms/46534332-compphys-fall2025. I recommend that you commit and push your progress at regular intervals (e.g. daily), which ensures your progress is backed up. Don't worry, you can commit and/or push as many times as you like; nothing is considered "submitted" until the deadline. + +To start, you should read `README.md` to understand the structure of the program. Running `make` from the top-level directory will compile the program; try running some of the test programs under `WorldOfTextCraft/tests`. + +Upload your writeup to UBLearns and push your committed code to GitHub Classroom. Your writeup should include documentation of the code you wrote, as well as the solutions to the problems in written form. Do not work with other students. + + +### Docker container +As usual, feel free to use any environment you like, including our Docker container at `ubsuny/compphys:latest`. The usual scripts (`runDocker.sh`, `runDocker_wfix.sh`, and `launchJupyter.sh`) have been copie to this repository. + +If you use the Docker container, you might find yourself in need of multiple terminal windows: one for a Jupyter server, another for compiling and running the game. There are a few ways to do this: + +- You can connect another terminal to the container using `docker exec...` as follows: + - First, look up the "contained ID" of your running container using `docker ps` (you might need sudo). It will be a random hexademical string, for example `dc07315c5228`. + - (You can directly connect with `docker exec -it dc07315c5228 /bin/bash`. The `-it` calls Docker in interactive+TTY mode, which gives you a shell prompt, and the `/bin/bash` puts you in a bash shell. However, this is not ideal for our container!) + - However, our container makes use of an "entrypoint" script that runs every time you start the container. So, you should connect to the running container using `docker exec -it dc07315c5228 /usr/local/bin/entrypoint.sh` (or `entrypoint_wfix.sh` if you are using WSL+Ubuntu). +- You can modify the `launchJupyter.sh` script to start the server in the background. Add a `&` character at the end of the `jupyter-lab` command. +- You can open a terminal inside the JupyterLab browser window. Click the "+" as if you were making a new notebook, and the button to start the terminal is down the page. + +\newpage + + +## Problem 1: Example Battle +The configuration files for this problem are located in `WorldOfTextCraft/ExampleBattle`. + +### Problem 1a +*10 points* + +Compile the program with `make` and run through the example battle. Specifically compile everything with: + +``` bash + +cd WorldOfTextCraft +make +``` + +For convenience, you can add the `bin` folder to your `$PATH` variable, which lets you execute `WorldOfTextCraft.exe` from anywhere (otherwise, you'll have to type a full relative or absolute path to the executable, which is fine but a bit painful): + +``` bash +cd WorldOfTextCraft +export PATH=$PATH:$(readlink -e bin) +``` + +To run the example battle: +``` bash +cd WorldOfTextCraft/ExampleBattle +WorldOfTextCraft.exe PlayerCharacters.txt Boss.txt BossScript.txt problem1a.txt +``` + +For your first turn, use the actions in `ExampleBattle/PlayerScript.txt` (i.e., copy-and-paste them into the interface when prompted). **On subsequent turns, you can just hit "enter" to repeat the previous action** (or you can specify a new action, for example changing the healer's target). + +Besides running the game, this will save a battle log to `problem1a.txt`. + +Did you win? Copy the outcome to your writeup, and commit `problem1a.txt` to your repository. + + +### Problem 1b +*15 points* + +You will now update the `Rogue.h` and `Rogue.cc` files to enhance your powers (since you are the developer, this is not considered cheating ;) ). The class definition is quite short, since it inherits most of its functionality from Character.h; Rogue.h simply overloads the `attack()`, `defend()`, and `heal()` methods. For `attack()` specifically, it calls a function called `defaultAttack()` which is defined in the base class (i.e., in `Character.h` and +`Character.cc`). + +For this problem, modify the `Rogue::attack()` method as follows: + +- First, copy the contents of `defaultAttack()` into `Rogue::attack()`. +- Every third turn (i.e., for turn mod 3 == 0), write a new method to do an extra 20 damage on top of the base attack power. +- Otherwise (i.e., for turn mod 3 == 1 or 2), call `defaultAttack()`. + +Ensure that `myAttacks_` is up to date! + +After updating the Rogue class and re-compiling, run the game with the following command: +``` bash +WorldOfTextCraft.exe PlayerCharacters.txt Boss.txt BossScript.txt problem1b.txt +``` + +Hints: +- The `getTurn()` method in the `Character` base class can be used to determine which turn the Rogue is on. + +Did you win now? Copy the outcome to your writeup, and commit `problem1b.txt` to your repository. + +\newpage + + +## Problem 2: The Lich King battle +![Lich King](data/LichKing.png) + +For Problem 2, we'll run through a different scenario, the Lich King boss. The configuration files are located at `WorldOfTextCraft/LichKing`. (If you quit your terminal window after the last problem, remember to re-run the PATH command so that `WorldOfTextCraft.exe` is on your PATH.) Run through this battle with the following commands, including your updated Rogue from Problem 1b: + +``` bash +cd LichKing +WorldOfTextCraft.exe PlayerCharacters.txt Arthas.txt ArthasScript.txt problem2.txt +# You can copy-and-paste action commands from LichKing/PlayerActions.txt +``` + +Commit `problem2.txt` to your repository. Now, the actual problem involves analyzing the battle log file using python. Start the Jupyter server and open the notebook `Problem2.ipynb`. You should do the whole problem in this notebook. + +### Problem 2a +*5 points* + +The notebook reads in a battle log file using a helper script in `readbattle.py`. The script puts the data into a numpy array for you. + +For each character, make a plot visualizing the actions during the battle (`['Attacks', 'Defends', 'Heals', 'DamageReceived', 'HealingReceived']`). Specifically, the x-axis should be the turn number, and the y-axis should be the number corresponding to the action (damage done, damage mitigated, amount healed, etc.), as present in the log file. Label your axes and make a legend. + +The first plot for `Attacks` is already done as an example. + +### Problem 2b +*5 points* + +Using numpy, make a single plot showing the hit points remaining at the end of each turn (y-axis) versus turn number (x-axis) for all characters. Label the axes and draw a legend. You will have to account for both DamageReceived and HealingReceived. `np.cumsum()` may help. + +### Problem 2c +*5 points* + +Using the matplotlib [hist](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.hist.html) function, create a single plot showing histograms of the Attacks for each character. Label your +axes and make a legend. + +### Problem 2d +*10 points* +Repeat 2c, but this time plot the Attacks only for turns +where the character was healed. + +For the submission, please do the following: +- Commit the notebook to your repository. +- Export the notebook to HTML format with filename `Problem2.html`, and upload to UBLearns. + +\newpage + + +## Problem 3 +**Enter the physicist** + +The Warrior, Priest, and Rogue are clearly not getting the job done. It is time for a Physicist to enter the battle. + +### Problem 3a +*15 points* + +Let's add Isaac Newton to our party. Isaac throws apples at the Boss, which scatter elastically and do damage equal to the kinetic energy transferred to the Boss. + +Create a new class, `Physicist`, with the following specifications: + +- The Physicist is a damage dealer, so copying `Rogue.h` and `Rogue.cc` is a good starting point. +- Additionally, the Physicist has two more stats, `mana_` and `velocity_`. + - `mana_`: similar to the Priest and Boss classes. The Physicist should start with 100 mana. This does not need to be configurable. + - `apple_mass_`: this should be configurable via the configuration files. Besides adding a member variable to the class, you need to override the two `Character::input()` functions to handle one additional argument from the configuration file. (You might want to refer to the `Boss` class definition.) +- In `attack()`, calculate damage as follows. The Physicist throws an apple of mass `apple_mass_` (kg) with velocity `v = attack_power_` (m/s). The Boss has a mass of $M=200$ kg. The apple collides elastically with the Boss and bounces straight backwards (i.e. a 1D collision). The damage is equal to the kinetic energy (joules) imparted to the Boss. Write the code to calculate the kinetic energy imparted to the Boss. +- Besides creating `Physicist.h`, you will need a small addition to `Battle.cc`. + +Once you have created the new class, create a new scenario (i.e., the `.txt` configuration files) in a new folder, `LichKing2`. Copy the original `LichKing` configuration files to that folder, and add a new character to your party: + +- class = Physicist +- name = Isaac Newton +- attack power = 50 +- defense power = 0 +- heal power = 0 +- apple mass = 1.0 + +Here are some suggested commands to get started: + +``` bash +cd WorldOfTextCraft +mkdir LichKing2 +cp LichKing/*txt LichKing2 +cd LichKing2 +# Add Newton to PlayerCharacters.txt +WorldOfTextCraft.exe PlayerCharacters.txt Arthas.txt ArthasScript.txt problem3.txt +# You can copy-and-paste action commands from LichKing2/PlayerActions.txt +``` + +Did you (finally) win? Commit `problem3.txt` to your repository. + +### Problem 3b +*10 points* + +CRepeat the data analysis from Problem 2 using the log file from this battle, `LichKing2/problem3.txt`. Start by copying the notebook, `cp Problem2.ipynb Problem3.ipynb`, and remember to update the character list in the notebook. Once finished, commit the notebook to the repository and upload an HTML export of the notebook to UBLearns. + +\newpage + + +## Problem 4 +This problem is mandatory for PHY505 students. PHY410 students can also complete the problem for extra credit. + +In this problem, we will add randomness to the game. C++ has quite a few utilities for generating (pseudo)random numbers (see https://en.cppreference.com/w/cpp/numeric/random.html). In this problem, we will use the [Poisson distribution](https://en.cppreference.com/w/cpp/numeric/random/poisson_distribution.html) to randomly vary the program. + +Pseudorandom number generators are often used in programming to create a sequence of numbers that appear random, but are generated in a deterministic way. For most purposes, they can be used like real random numbers, but since the sequences are deterministic, our programs' results are fully reproducible, which is usually desired in programming (for unit tests, for example). Pseudorandom number generators are usually initialized with a "seed," which is just an integer used in some way to start the sequence. For a given seed, the generator always yields the same sequence; the seed can be varied to generate different sequences. + +We will use the `std::default_random_engine` class for our pseudorandom number generator. The random seed is obtained from `std::random_device`, which generates a true random number. Finally, in `std::poisson_distribution<>`, the pseudorandom numbers are used to sample the Poisson distribution. + +### Problem 4a +*20 points* + +Add a random number generator to the Rogue class's attack. Following closely this [example](https://en.cppreference.com/w/cpp/numeric/random/poisson_distribution.html), do the following steps to introduce random variations in the characters' actions: + +- In the Character class, add an instance of `std::default_random_engine` as a class member variable. +- In the constructor `Character::Character()`, create the generator and give it a random seed using `std::random_device`, following the example. +- In the Character class, add an instance of `std::poisson_distribution` as a class member variable. +- **Important**: you cannot initialize the `std::poisson_distribution` in the constructor, because `attack_power_` has not yet been set! Instead, initialize `poisson_` in the function `Rogue::post_input() override {...}`. This is a "hook" function declared in the base class that you can override in the derived class to run some code directly after `input()`. + - We have to use this hook because `attack_power_` is not set until `input()` is run. +- Finally, change `Rogue::attack()` to use `poisson_` to generate a random attack value, instead of using `attack_power_` directly. Make sure to preserve the modifications from Problem 1b. +- Compile with `make`. + +Once you've finished adding random numbers to the Rogue class, create a new scenario in folder `LichKing3`. Copy the configuration files from Problem 3 (`LichKing2`). (If you didn't finish Problem 3, you can copy from `LichKing` instead.) Run the scenario without any further modifications, e.g., + +``` bash +cd WorldOfTextCraft +mkdir LichKing3 +cp LichKing2/*txt LichKing3 +cd LichKing3 +WorldOfTextCraft.exe PlayerCharacters.txt Arthas.txt ArthasScript.txt problem4.txt +# You can copy-and-paste action commands from LichKing2/PlayerActions.txt +# +``` + +You should now see random values for the Rogue's damage. Commit `problem4.txt` to your repository. + + +### Problem 4b +*5 points* + +Repeat Problem 2c: start by copying the notebook, `cp Problem2.ipynb Problem4.ipynb`, and change the input to `LingKing3/problem4.txt`. Remember to update the character list. Using the matplotplib [hist](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.hist.html) function, create a single plot showing histograms of the Attacks for each character. Label your axes and make a legend. + +You should now see a Poisson distribution for the Rogue's damage. + +Upload the notebook to your repository, and upload an HTML export of the notebook to UBLearns. + + + diff --git a/midterm.pdf b/midterm.pdf new file mode 100644 index 0000000..c9dcd1d Binary files /dev/null and b/midterm.pdf differ diff --git a/runDocker.sh b/runDocker.sh new file mode 100755 index 0000000..f71679f --- /dev/null +++ b/runDocker.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Usage: runDocker.sh ubsuny/compphys:latest /bin/bash +# This script provides an example `docker run` command to launch the PHY410 docker container + +usage() { + echo "Usage: ./runDocker.sh ubsuny/compphys:latest" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + + +DOCKERHOME=/home/compphys +LOCALDIR=${PWD} # This should be your CompPhys folder that you checked out from Github +REMOTEDIR=$DOCKERHOME/$(basename $LOCALDIR) # Location inside the container where $LOCALDIR is mounted + +if [ "$(uname)" == "Darwin" ]; then + ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}') + xhost + $ip + DISPLAYHANDLE="${DISPLAY}" +else + DISPLAYHANDLE="${ip}:0" +fi + +DOCKERCMD="docker run \ + --rm \ + -it \ + -e DISPLAY=$DISPLAYHANDLE \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v $LOCALDIR:$REMOTEDIR \ + -w $DOCKERHOME \ + -p 8888:8888 \ + $1 /bin/bash" +echo $DOCKERCMD +exec $DOCKERCMD diff --git a/runDocker_wfix.sh b/runDocker_wfix.sh new file mode 100755 index 0000000..b3fad2e --- /dev/null +++ b/runDocker_wfix.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Usage: runDocker.sh ubsuny/compphys:latest /bin/bash +# This script provides an example `docker run` command to launch the PHY410 docker container + +usage() { + echo "Usage: ./runDocker.sh ubsuny/compphys:latest" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + + +DOCKERHOME=/home/compphys +LOCALDIR=${PWD} # This should be your CompPhys folder that you checked out from Github +REMOTEDIR=$DOCKERHOME/$(basename $LOCALDIR) # Location inside the container where $LOCALDIR is mounted + +# Setup display variables +if [ "$(uname)" == "Darwin" ]; then + ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}') + xhost + $ip + DISPLAYHANDLE="${DISPLAY}" +else + DISPLAYHANDLE="${ip}:0" +fi + +# Ensure group permissions on CompPhys folder +if [ "$(uname)" == "Darwin" ]; then + GRPW=$(stat -f "%p %N" . | awk '{print $1=substr($1,length($1)-2)}' | grep -c ".[2367].") +else + GRPW=$(stat -c "%a" . | grep -c ".[2367].") +fi +if [ $GRPW == 0 ]; then + echo "Adding group write permissions to CompPhys folder" + echo "chmod -R g+w ." + chmod -R g+w . +fi +unset GRPW + +DOCKERCMD="docker run \ + --entrypoint "/usr/local/bin/entrypoint_wfix.sh" \ + --rm \ + -it \ + -e DISPLAY=$DISPLAYHANDLE \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v $LOCALDIR:$REMOTEDIR \ + -w $DOCKERHOME \ + -p 8888:8888 \ + $1 \ + $(id -g $(whoami)) /bin/bash" +echo $DOCKERCMD +exec $DOCKERCMD \ No newline at end of file diff --git a/src/Battle.cc b/src/Battle.cc new file mode 100644 index 0000000..48b1381 --- /dev/null +++ b/src/Battle.cc @@ -0,0 +1,632 @@ +#include "Battle.h" +#include "MatchingHelpers.h" +#include + +void Battle::splash(std::ostream &out) const { + + out << "|--------------------------------------------|" << std::endl; + out << "|--------------------------------------------|" << std::endl; + out << "|--------------------------------------------|" << std::endl; + out << "|--------------------------------------------|" << std::endl; + out << "| __ __ _ _ |" << std::endl; + out << "| / / /\\ \\ \\___ _ __| | __| | |" << std::endl; + out << "| \\ \\/ \\/ / _ \\| '__| |/ _` | |" << std::endl; + out << "| \\ /\\ / (_) | | | | (_| | |" << std::endl; + out << "| \\/ \\/ \\___/|_| |_|\\__,_| |" << std::endl; + out << "| |" << std::endl; + out << "| __ |" << std::endl; + out << "| ___ / _| |" << std::endl; + out << "| / _ \\| |_ |" << std::endl; + out << "| | (_) | _| |" << std::endl; + out << "| \\___/|_| |" << std::endl; + out << "| |" << std::endl; + out << "| _____ _ ___ __ _ |" << std::endl; + out << "|/__ \\_____ _| |_ / __\\ __ __ _ / _| |_ |" << std::endl; + out << "| / /\\/ _ \\ \\/ / __|/ / | '__/ _` | |_| __||" << std::endl; + out << "| / / | __/> <| |_/ /__| | | (_| | _| |_ |" << std::endl; + out << "| \\/ \\___/_/\\_\\\\__\\____/_| \\__,_|_| \\__||" << std::endl; + out << "| |" << std::endl; + out << "|--------------------------------------------|" << std::endl; + out << "|--------------------------------------------|" << std::endl; + out << "|--------------------------------------------|" << std::endl; + + out << "Welcome brave traveler..." << std::endl; + out << description_ << std::endl; +} + +bool Battle::readPCConfiguration(std::string filename) { + + if (pcs_.size() > 0) { + std::cout << "Configuration already read... skipping reading from " + << filename << std::endl; + return false; + } else { + std::ifstream in(filename); + if (!in.is_open()) { + std::cerr << "[Battle::readPCConfiguration] File not found: " << filename << std::endl; + exit(1); + } + + // While not the end of file, keep looping + while (!in.eof()) { + std::string line; + std::getline(in, line); + + // First check if the line is empty or a comment (starts with "!") + if ((line.size() > 0 && line[0] == '!') || line == "") { + continue; + } else { + + // Otherwise, we have a valid line. + // First read the type from the first token, and create the appropriate + // class (Warrior, Priest, Rogue). Then read in the stats from the rest + // of the line. + std::stringstream tokens(line); + std::string character_type; + std::getline(tokens, character_type, ';'); + + std::shared_ptr character; + if (character_type == "Warrior") { + character = std::shared_ptr(new Warrior()); + } else if (character_type == "Priest") { + character = std::shared_ptr(new Priest()); + } else if (character_type == "Rogue") { + character = std::shared_ptr(new Rogue()); + } else { + std::cout << "Character Type not recognized: " << line << ", skipping" + << std::endl; + continue; + } + character->input(tokens); + character->post_input(); + pcs_.push_back(character); + std::cout << "Added character: " << *(pcs_.back()) << std::endl; + } + } + + in.close(); + } + return true; +} + +bool Battle::readNPCConfiguration(std::string filename) { + if (npcs_.size() > 0) { + std::cout << "Configuration already read... skipping reading from " + << filename << std::endl; + return false; + } else { + std::ifstream in(filename); + if (!in.is_open()) { + std::cerr << "[Battle::readNPCConfiguration] File not found: " << filename << std::endl; + exit(1); + } + while (!in.eof()) { + std::string line; + std::getline(in, line); + + if ((line.size() > 0 && line[0] == '!') || line == "") { + continue; + } else { + // The first line is the description. + if (description_ == "") { + description_ = line; + } else { + std::shared_ptr boss(new Boss()); + boss->input(line); + npcs_.push_back(boss); + } + } + } + } + return true; +} + +// Load a bunch of actions from a script +bool Battle::loadActionScript(std::string filename) { + if (filename == "") { + std::cout << "No script file input." << std::endl; + return false; + } else { + std::ifstream in(filename); + if (!in.is_open()) { + std::cerr << "[Battle::loadActionScript] File not found: " << filename << std::endl; + exit(1); + } + std::string line; + bool success = true; + while (!in.eof() && success) { + + std::getline(in, line); + if (line[0] == '!' || line == "") + continue; + QuickAction qa; + success = parseAction(line, qa); + + if (!success) { + std::cout << "Error in parseAction!" << std::endl; + return false; + } + + // If the script has any PC actions, it is scripted and not user-input + CharacterVector::iterator pcIt = std::find_if(pcs_.begin(), pcs_.end(), + MatchSource(qa.source->name())); + bool found = pcIt != pcs_.end(); + if (found) { + scripted_ = true; + std::cout << "Scripting input for " << (*pcIt)->name() << std::endl; + } + actions_.push_back(qa); + } + in.close(); + } + + return true; +} + +// Lines look like this: +// Shemp;attack;Moe; +// Shemp;multiattack;all; +// 1 : find the Character in the list with "name". +// 2 : find the action they are supposed to do. +// 3 : find the target. If blank, use current target. +// 4 : Set "name's" target, perform action +bool Battle::parseAction(std::string line, QuickAction &qa) { + + if (line == "") + return false; + + std::vector tokens; + std::stringstream linestream(line); + for (std::string each = ""; std::getline(linestream, each, ';');) { + tokens.push_back(each); + } + + if (tokens.size() < 3) { + std::cout << "Improper formatting of line " << line << std::endl; + return false; + } + + // Initialize both target and source to "error" (pcs_.end() is interpreted as + // such) + CharacterVector::iterator it_target = pcs_.end(), it_source = pcs_.end(); + std::shared_ptr target; + std::shared_ptr source; + + // Find the iterator with name "tokens[0]" + it_source = find_character(tokens[0]); + // Check to make sure we found an iterator + if (it_source == pcs_.end()) { + std::cout << "Error processing line " << line << std::endl; + return false; + } + source = *it_source; + + // Special case of Bosses attacking ALL NPCs + if (tokens[2] == "all") { + target = 0; + qa.source = source; + qa.action = MULTIATTACK; + return true; + } + + // Special case of attacking current target + if (tokens[2] == "target") { + target = 0; + } else { + + // Find the iterator with name "tokens[2]" + it_target = find_character(tokens[2]); + // Check to make sure we found an iterator + if (it_target == pcs_.end() || it_target == npcs_.end()) { + std::cout << "Error processing line " << line << std::endl; + return false; + } + target = *it_target; + } + + // parse the action type. + ActionType action_type = N_ACTIONS; // Error code + if (tokens[1] == "attack") + action_type = ATTACK; + else if (tokens[1] == "heal") + action_type = HEAL; + else if (tokens[1] == "defend") + action_type = DEFEND; + else { + std::cout << "Invalid action " << tokens[1] << std::endl; + return false; + } + + // Finally, push back the action + if (target) { + source->setTarget(target.get()); + } + qa.source = source; + qa.action = action_type; + return true; +} + +// This will perform all of the actions scripted in "actions_". +bool Battle::performScriptedActions() { + + if (!scripted_) { + std::cout << "I am expecting a player script, but you didn't give me one." + << std::endl; + return false; + } + + if (!anyPCAlive()) { + std::cout << "Alas, your party is dead." << std::endl; + return false; + } + + if (!anyNPCAlive()) { + std::cout << "You already won! Hooray!" << std::endl; + return false; + } + + // Special case for first attack : all NPCs will target first PC + if (turn_ == 0 && pcs_.size() > 1) { + for (CharacterVector::iterator inpc = npcs_.begin(); inpc != npcs_.end(); + ++inpc) { + (*inpc)->setTarget(pcs_.begin()->get()); + } + } + + if (actions_.size() == 0) { + std::cout << "No actions to perform" << std::endl; + return false; + } + + for (CharacterVector::iterator it = npcs_.begin(); it != npcs_.end(); ++it) { + (*it)->turn_ = turn_; + } + + for (CharacterVector::iterator it = pcs_.begin(); it != pcs_.end(); ++it) { + (*it)->turn_ = turn_; + } + + for (std::vector::iterator it = actions_.begin(); + it != actions_.end(); ++it) { + + if (it->source->isDead()) { + std::cout << it->source->name() << " is dead" << std::endl; + continue; + } + + // two special cases for bosses: + // 1. If their target dies, they pick the next target. + // 2. Must check the special case of the "Boss" ability to attack everyone. + // Since we are storing pointers to the base class, we need to + // "dynamic_cast" to the derived class to access its "multi attack" + Boss *boss = dynamic_cast(it->source.get()); + if (boss != 0) { + // Attack the entire party in a "MultiAttack" + if (it->action == MULTIATTACK) { + for (CharacterVector::iterator itarget = pcs_.begin(); itarget != pcs_.end(); + ++itarget) { + boss->multiAttack(itarget->get()); + } + continue; + } + // If the target is dead, switch to the next in the list. + if (boss->getTarget() != 0 && boss->getTarget()->isDead()) { + bool bossGotTarget = setBossTarget(boss); + if (!bossGotTarget) { + return false; + } + } + } + + // Now we execute normal attacks. + if (it->action == ATTACK) { + it->source->attack(); + } else if (it->action == HEAL) { + it->source->heal(); + } else if (it->action == DEFEND) { + it->source->defend(); + } else { + std::cout << "Incorrect action! Returning" << std::endl; + return false; + } + + if (!anyPCAlive()) { + std::cout << it->source->name() << " has vanquished your party." + << std::endl; + return false; + } + + if (!anyNPCAlive()) { + std::cout << it->source->name() << " has vanquished your foe!!!" + << std::endl; + return false; + } + } + + ++turn_; + + return anyPCAlive() && anyNPCAlive(); +} + +// This will perform all of the actions scripted in "actions_". +bool Battle::performUserActions(std::istream &in) { + + if (scripted_) { + std::cout << "PC actions already scripted, do not give me scripted actions " + "if you do not want to use them." + << std::endl; + return false; + } + + if (!anyPCAlive()) { + std::cout << "Alas, your party is dead." << std::endl; + return false; + } + + if (!anyNPCAlive()) { + std::cout << "You already won! Hooray!" << std::endl; + return false; + } + + // Special case for first attack : all NPCs will target first PC + if (turn_ == 0 && pcs_.size() > 1) { + for (CharacterVector::iterator inpc = npcs_.begin(); inpc != npcs_.end(); + ++inpc) { + (*inpc)->setTarget(pcs_.begin()->get()); + } + } + + // Bookkeeping + if (actions_.size() == 0) { + std::cout << "No actions to perform" << std::endl; + return false; + } + for (CharacterVector::iterator it = npcs_.begin(); it != npcs_.end(); ++it) { + (*it)->turn_ = turn_; + } + for (CharacterVector::iterator it = pcs_.begin(); it != pcs_.end(); ++it) { + (*it)->turn_ = turn_; + } + + // Process the boss actions + for (std::vector::iterator it = actions_.begin(); + it != actions_.end(); ++it) { + + if (it->source->isDead()) { + std::cout << it->source->name() << " is dead" << std::endl; + continue; + } + // two special cases for bosses: + // 1. If their target dies, they pick the next target. + // 2. Must check the special case of the "Boss" ability to attack everyone. + // Since we are storing pointers to the base class, we need to + // "dynamic_cast" to the derived class to access its "multi attack" + Boss *boss = dynamic_cast(it->source.get()); + if (boss != 0) { + // Attack the entire party in a "MultiAttack" + if (it->action == MULTIATTACK) { + for (CharacterVector::iterator itarget = pcs_.begin(); itarget != pcs_.end(); + ++itarget) { + boss->multiAttack(itarget->get()); + } + continue; + } + // If the target is dead, switch to the next in the list. + if (boss->getTarget() != 0 && boss->getTarget()->isDead()) { + bool bossGotTarget = setBossTarget(boss); + if (!bossGotTarget) { + return false; + } + } + } + + if (it->action == ATTACK) { + it->source->attack(); + } else if (it->action == HEAL) { + it->source->heal(); + } else if (it->action == DEFEND) { + it->source->defend(); + } else { + std::cout << "Incorrect action! Returning" << std::endl; + return false; + } + + if (!anyPCAlive()) { + std::cout << it->source->name() << " has vanquished your party." + << std::endl; + return false; + } + } + + // Process the user actions + for (CharacterVector::iterator it = pcs_.begin(); it != pcs_.end(); ++it) { + + if ((*it)->isDead()) { + std::cout << (*it)->name() << " is dead" << std::endl; + continue; + } + + std::cout << ">> Enter action for " << (*it)->name() << " : " << std::endl; + if (last_action_[(*it)] != "") { + std::cout << " (Default: " << last_action_[(*it)] << ")" << std::endl; + } + QuickAction qa; + std::string line; + //in >> line; + std::getline(in, line); + if (line == "") { + line = last_action_[(*it)]; + } + bool success = parseAction(line, qa); + while (!success) { + std::cout << "Invalid input, try again" << std::endl; + std::getline(in, line); + if (line == "") { + line = last_action_[(*it)]; + } + success = parseAction(line, qa); + } + if (qa.action == ATTACK) { + (*it)->attack(); + } else if (qa.action == HEAL) { + (*it)->heal(); + } else if (qa.action == DEFEND) { + (*it)->defend(); + } else { + std::cout << "Incorrect action! Returning" << std::endl; + return false; + } + + if (!anyNPCAlive()) { + std::cout << (*it)->name() << " has vanquished your foe." << std::endl; + return false; + } + last_action_[(*it)] = line; + } + + ++turn_; + + return anyPCAlive() && anyNPCAlive(); +} + +// Set the next Boss target. It looks for the +// first character, in order, that is not dead. +bool Battle::setBossTarget(Boss *boss) { + + Character const *character = boss->getTarget(); + + if (boss == 0) { + std::cout << "Boss is zero, cannot set new target" << std::endl; + return false; + } + for (CharacterVector::const_iterator pc = pcs_.begin(); pc != pcs_.end(); ++pc) { + if ((*pc)->isAlive()) { + if (pc->get() != character) { + std::cout << boss->name() << " shifts their attacks to " + << (*pc)->name() << std::endl; + } + boss->setTarget(pc->get()); + return true; + } + } + + // Here there are no targets alive. + return false; +} + +bool Battle::anyPCAlive() const { + bool any_pc_alive = false; + for (CharacterVector::const_iterator pc = pcs_.begin(); pc != pcs_.end(); ++pc) { + if ((*pc)->isAlive()) + any_pc_alive = true; + } + return any_pc_alive; +} + +bool Battle::anyNPCAlive() const { + bool any_npc_alive = false; + for (CharacterVector::const_iterator npc = npcs_.begin(); npc != npcs_.end(); + ++npc) { + if ((*npc)->isAlive()) + any_npc_alive = true; + } + return any_npc_alive; +} + +Battle::CharacterVector::iterator Battle::find_character(std::string s) { + // Try to find the sources and targets: + CharacterVector::iterator source_pc = + std::find_if(pcs_.begin(), pcs_.end(), MatchSource(s)); + CharacterVector::iterator source_npc = + std::find_if(npcs_.begin(), npcs_.end(), MatchSource(s)); + + if (source_pc == pcs_.end() && source_npc == npcs_.end()) { + std::cout << "No matching source for " << s << std::endl; + return pcs_.end(); + } else if (source_pc == pcs_.end()) { + return source_npc; + } else { + return source_pc; + } +} + +void Battle::printStats(std::ostream &out) { + out << "------------------------------- Turn : " << turn_ + << "-------------------------------" << std::endl; + out << "-----------------------------" << std::endl; + out << " === players:" << std::endl; + // Remember! "CharacterVector" is just an alias for std::vector< + // std::shared_ptr > + for (CharacterVector::const_iterator it = pcs_.begin(); it != pcs_.end(); ++it) { + (*it)->printStats(out); + } + out << std::endl << " === monsters:" << std::endl; + for (CharacterVector::const_iterator it = npcs_.begin(); it != npcs_.end(); ++it) { + (*it)->printStats(out); + } + out << "-----------------------------" << std::endl; +} + +void Battle::print(std::ostream &out) { + out << std::endl << "------------------------------- Turn : " << turn_ + << "-------------------------------" << std::endl; + out << "--------------" << std::endl; + out << " === players:" << std::endl; + // Remember! "CharacterVector" is just an alias for std::vector< + // std::shared_ptr > + for (CharacterVector::const_iterator it = pcs_.begin(); it != pcs_.end(); ++it) { + out << **it << std::endl; + } + out << std::endl << " === monsters:" << std::endl; + for (CharacterVector::const_iterator it = npcs_.begin(); it != npcs_.end(); ++it) { + out << **it << std::endl; + } + out << "--------------" << std::endl; +} + +void Battle::printActions(std::ostream &out) { + + out << " ----- Action list ----" << std::endl; + for (std::vector::const_iterator it = actions_.begin(); + it != actions_.end(); ++it) { + if (it->source == 0) { + std::cout << "Configuration error." << std::endl; + return; + } + out << it->source->name() << " will perform action " << it->action + << " on their target : "; + if (it->source->getTarget() != 0) { + out << it->source->getTarget()->name() << std::endl; + } else { + out << " NO TARGET!" << std::endl; + } + } +} + +void Battle::printLog(std::ostream &out) const { + + out << "{\"Turns\":[" << std::endl; + for (unsigned int iturn = 0; iturn <= turn_; ++iturn) { + + out << "{\"Turn\":" << iturn << "," << std::endl; + + for (auto i = npcs_.begin(); i != npcs_.end(); ++i) { + (*i)->printActions(out, iturn); + out << "," << std::endl; + } + for (auto i = pcs_.begin(); i != pcs_.end(); ++i) { + (*i)->printActions(out, iturn); + // json does not like trailing commas + if (i != pcs_.end() - 1) + out << "," << std::endl; + else + out << std::endl; + } + out << "}"; + if (iturn != turn_) + out << "," << std::endl; + else + out << std::endl; + } + out << "]}" << std::endl; +} diff --git a/src/Boss.cc b/src/Boss.cc new file mode 100644 index 0000000..c5f9d2c --- /dev/null +++ b/src/Boss.cc @@ -0,0 +1,91 @@ +#include "Boss.h" +#include +#include + +Boss::Boss(std::string name, int attack_power, int defense_power, int heal_power, + int mana, int multi_attack_power) + : Character("Boss", name, attack_power, defense_power, heal_power, mana, false) { + multi_attack_power_ = multi_attack_power; + hit_points_ = 500; + max_hit_points_ = 500; +}; + +int Boss::multiAttack(Character *other) { + + Character *originalTarget = getTarget(); + + if (other != 0) { + setTarget(other); + } + int ap = this->multi_attack_power_; + + if (getTarget() != 0) { + std::cout << name() << " multi-attacks " << getTarget()->name() + << " with attack power " << ap << std::endl; + int retval = getTarget()->reduceHitPoints(ap); + setTarget(originalTarget); + if (my_attacks_.find(turn_) == my_attacks_.end()) + my_attacks_[turn_] = ActionVector(); + my_attacks_[turn_].push_back(retval); + return retval; + } else { + std::cout << name_ << " does not have a target to attack." << std::endl; + setTarget(originalTarget); + return 0; + } +}; + +// Print to "out" +void Boss::printStats(std::ostream &out) const { + out << std::setw(12) << name_ + << " (" << std::setw(10) << class_name_ + << "): HP=" << std::setw(5) << hit_points_ + << ", attack=" << std::setw(5) << attack_power_ + << ", defend=" << std::setw(5) << defense_power_ + << ", heal=" << std::setw(5) << heal_power_; + + if (isMagicUser()) { + out << ", mana = " << std::setw(5) << mana_; + } + out << ", multi =" << std::setw(5) << multi_attack_power_; + if (target_ != 0) { + out << ", target=" << std::setw(12) << target_->name(); + } else { + out << ", no target"; + } +} + +// Print to "out" +void Boss::print(std::ostream &out) const { + out << std::setw(12) << name_ << " (" << std::setw(10) << class_name_ + << "): HP=" << std::setw(5) << hit_points_ << ", mana = " << std::setw(5) + << mana_; + if (target_ != 0) { + out << ", target=" << std::setw(12) << target_->name(); + } else { + out << ", no target"; + } +} + +void Boss::input(std::string line) { + std::vector tokens; + + std::stringstream linestream(line); + for (std::string each = ""; std::getline(linestream, each, ';');) { + tokens.push_back(each); + } + if (tokens.size() >= 6) { + name_ = tokens[0]; + attack_power_ = std::atoi(tokens[1].c_str()); + defense_power_ = std::atoi(tokens[2].c_str()); + heal_power_ = std::atoi(tokens[3].c_str()); + mana_ = std::atoi(tokens[4].c_str()); + multi_attack_power_ = std::atoi(tokens[5].c_str()); + + std::cout << "Input boss: " << *this << std::endl; + } else { + std::cout << "Formatting error in input: unrecognized syntax in line : " + << line << std::endl; + return; + } +} diff --git a/src/Character.cc b/src/Character.cc new file mode 100644 index 0000000..f15577c --- /dev/null +++ b/src/Character.cc @@ -0,0 +1,286 @@ +#include "Character.h" +#include +#include +#include +#include +#include + +// Constructor +Character::Character(std::string class_name, std::string name, + unsigned int attack_power, unsigned int defense_power, + unsigned int heal_power, unsigned int max_mana, bool check) + : class_name_(class_name), name_(name), hit_points_(100), max_hit_points_(100), + mana_(max_mana), attack_power_(attack_power), defense_power_(defense_power), + heal_power_(heal_power), target_(0) // uninitialized target +{ + if (check) { + // Check to make sure your powers aren't outrageous + checkPowers(); + } + + std::random_device rd; + gen_ = std::default_random_engine(rd()); + std::cout << "[Character::Character] DEBUG : Trying out gen_ a few times" << std::endl; + for (int i = 0; i < 10; ++i) { + std::cout << gen_() << std::endl; + } +} + +// Destructor +Character::~Character() {} + +void Character::input(std::string line) { + std::vector tokens; + + std::stringstream linestream(line); + for (std::string each = ""; std::getline(linestream, each, ';');) { + tokens.push_back(each); + } + if (tokens.size() >= 4) { + name_ = tokens[0]; + attack_power_ = std::atoi(tokens[1].c_str()); + defense_power_ = std::atoi(tokens[2].c_str()); + heal_power_ = std::atoi(tokens[3].c_str()); + } else { + std::cout << "Formatting error in input: unrecognized syntax in line : " + << line << std::endl; + return; + } + checkPowers(); +} + +// Input from "in" +void Character::input(std::istream &in) { + std::string line; + std::getline(in, line); + input(line); +}; + +// Print to "out" + +void Character::print(std::ostream &out) const { + out << std::setw(12) << name_ << " (" << std::setw(10) << class_name_ + << "): HP=" << std::setw(5) << hit_points_ << ", mana = " << std::setw(5) + << mana_; + if (target_ != 0) { + out << ", target=" << std::setw(12) << target_->name(); + } else { + out << ", no target"; + } +} + +void Character::printStats(std::ostream &out) const { + out << std::setw(12) << name_ << " (" << std::setw(10) << class_name_ + << "): HP=" << std::setw(5) << hit_points_ << ", attack=" << std::setw(5) + << attack_power_ << ", defend=" << std::setw(5) << defense_power_ + << ", heal=" << std::setw(5) << heal_power_ << ", mana = " << std::setw(5) + << mana_; + if (target_ != 0) { + out << ", target=" << std::setw(12) << target_->name(); + } else { + out << ", no target"; + } +} + +// Reduce the hit points of "this" entity by "attack", mitigated by +// "defense_power" +int Character::reduceHitPoints(int attack) { + int damage_taken = (attack - defense_power_); + if (damage_taken < 0) { + damage_taken = 0; + } else if (damage_taken >= hit_points_) { + // Protect against "overkill" in the stats accounting. + damage_taken = hit_points_; + } + std::cout << name_ << " loses " << damage_taken << " hit points after attack " + << attack << " and defense " << defense_power_ << std::endl; + hit_points_ -= damage_taken; + if (hit_points_ <= 0) { + hit_points_ = 0; + std::cout << name_ << ", the brave " << class_name_ << ", has died." + << std::endl; + } + if (my_reduced_hit_points_.find(turn_) == my_reduced_hit_points_.end()) + my_reduced_hit_points_[turn_] = ActionVector(); + my_reduced_hit_points_[turn_].push_back(damage_taken); + return damage_taken; +} + +// Increase the hit points of "this" entity +int Character::increaseHitPoints(int heal) { + int healed = heal; // Protect against "overheal" in the stats accounting. + if (hitPoints() + healed >= max_hit_points_) + healed = max_hit_points_ - hitPoints(); + hit_points_ += healed; + if (my_increased_hit_points_.find(turn_) == my_increased_hit_points_.end()) + my_increased_hit_points_[turn_] = ActionVector(); + my_increased_hit_points_[turn_].push_back(healed); + return healed; +} + +// This will force the target of the other object to be "this" Character. +int Character::defaultDefend(Character *other) { + if (other != 0) { + setTarget(other); + } + if (getTarget() != 0) { + + if (getTarget()->isDead()) { + std::cout << name_ << " : target " << getTarget()->name() + << " is already dead." << std::endl; + return 0; + } + std::cout << name_ << " defends against " << getTarget()->name() + << " with defense mitigation " << defensePower() << std::endl; + getTarget()->setTarget(this); + } else { + std::cout << name_ << " does not have a target to defend." << std::endl; + } + if (my_defends_.find(turn_) == my_defends_.end()) + my_defends_[turn_] = ActionVector(); + my_defends_[turn_].push_back(defensePower()); + return 0; +} + +// In a heal, we increase the hit points +int Character::defaultHeal(Character *other) { + if (mana_ < 10) { + std::cout << name() << " does not have enough mana." << std::endl; + return 0; + } + if (other != 0) { + setTarget(other); + } + if (getTarget() != 0) { + + if (getTarget()->isDead()) { + std::cout << name_ << " : target " << getTarget()->name() + << " is already dead." << std::endl; + my_heals_[turn_].push_back(0); + return 0; + } + mana_ -= 10; + auto healed = getTarget()->increaseHitPoints(heal_power_); + std::cout << name() << " healed " << getTarget()->name() + << " with heal power " << heal_power_ << " for " << healed + << std::endl; + if (my_heals_.find(turn_) == my_heals_.end()) + my_heals_[turn_] = ActionVector(); + my_heals_[turn_].push_back(healed); + return healed; + } else { + std::cout << name_ << " does not have a target to heal." << std::endl; + return 0; + } +}; + +// In an attack, we reduce the hit points +int Character::defaultAttack(Character *other) { + if (other != 0) { + setTarget(other); + } + + if (getTarget() != 0) { + if (getTarget()->isDead()) { + std::cout << name_ << " : target " << getTarget()->name() + << " is already dead." << std::endl; + my_attacks_[turn_].push_back(0); + return 0; + } + + int ap = attack_power_; + auto attacked = getTarget()->reduceHitPoints(ap); + std::cout << name() << " attacked " << getTarget()->name() + << " with attack power " << ap << " for damage " << attacked + << std::endl; + if (my_attacks_.find(turn_) == my_attacks_.end()) + my_attacks_[turn_] = ActionVector(); + my_attacks_[turn_].push_back(attacked); + return attacked; + } else { + std::cout << name_ << " does not have a target to attack." << std::endl; + my_attacks_[turn_].push_back(0); + return 0; + } +}; + +// This will check to ensure the input values are sensible +bool Character::checkPowers() { + + is_magic_user_ = (mana_ > 0); + if (attack_power_ + defense_power_ + heal_power_ > 20) { + std::cout + << name_ + << " : You cannot godmode here, your abilities can only sum to 20." + << std::endl + << "To punish you, the gods set your hitpoints to 1 and make you " + "feeble as a kitten." + << std::endl; + hit_points_ = 1; + attack_power_ = 0; + defense_power_ = 0; + heal_power_ = 0; + return false; + } + if (attack_power_ < 0 || defense_power_ < 0 || heal_power_ < 0) { + std::cout << name_ << " : Your powers cannot be negative." << std::endl + << "To punish you, the gods set your hitpoints to 1 and make you " + "feeble as a kitten." + << std::endl; + hit_points_ = 1; + attack_power_ = 0; + defense_power_ = 0; + heal_power_ = 0; + return false; + } + + return true; +}; + +// Some operators to support << and >> +std::ostream &operator<<(std::ostream &out, Character const &e) { + e.print(out); + return out; +} +std::istream &operator>>(std::istream &in, Character &e) { + e.input(in); + return in; +} + +void Character::printActions(std::ostream &out, unsigned int iturn) const { + + auto all_actions = {std::make_pair("Attacks", &my_attacks_), + std::make_pair("Defends", &my_defends_), + std::make_pair("Heals", &my_heals_), + std::make_pair("DamageReceived", &my_reduced_hit_points_), + std::make_pair("HealingRecieved", &my_increased_hit_points_)}; + + out << "\"" << name_ << "\":{"; + for (auto iaction = all_actions.begin(); iaction != all_actions.end(); + ++iaction) { + auto actionname = iaction->first; + auto actions = iaction->second; + out << "\"" << actionname << "\":["; + + // Check if there are any actions for this turn + auto p_action = actions->find(iturn); + if (p_action != actions->end()) { + auto action_vals = p_action->second; + for (auto ival = action_vals.begin(); ival != action_vals.end(); ++ival) { + out << *ival; + // json does not like trailing comma + if (ival + 1 != action_vals.end()) + out << ","; + } + } else { + out << 0; + } + out << "]"; + + if (iaction != all_actions.end() - 1) { + out << ","; + } + out << std::endl; + } + out << "}"; +} diff --git a/src/Priest.cc b/src/Priest.cc new file mode 100644 index 0000000..3531a0c --- /dev/null +++ b/src/Priest.cc @@ -0,0 +1,14 @@ +#include "Priest.h" + +int Priest::defend(Character* other) { + return defaultDefend(other); +} + +int Priest::heal(Character* other) { + return defaultHeal(other); +} + +int Priest::attack(Character* other) { + return defaultAttack(other); +} + diff --git a/src/Rogue.cc b/src/Rogue.cc new file mode 100644 index 0000000..529e6cb --- /dev/null +++ b/src/Rogue.cc @@ -0,0 +1,14 @@ +#include "Rogue.h" + +int Rogue::defend(Character* other) { + return defaultDefend(other); +} + +int Rogue::heal(Character* other) { + return defaultHeal(other); +} + +int Rogue::attack(Character* other) { + return defaultAttack(other); +} + diff --git a/src/Warrior.cc b/src/Warrior.cc new file mode 100644 index 0000000..31f3db1 --- /dev/null +++ b/src/Warrior.cc @@ -0,0 +1,14 @@ +#include "Warrior.h" + +int Warrior::defend(Character* other) { + return defaultDefend(other); +} + +int Warrior::heal(Character* other) { + return defaultHeal(other); +} + +int Warrior::attack(Character* other) { + return defaultAttack(other); +} + diff --git a/WorldOfTextCraft.cc b/src/WorldOfTextCraft.cc similarity index 92% rename from WorldOfTextCraft.cc rename to src/WorldOfTextCraft.cc index 2dbd14b..838bc71 100644 --- a/WorldOfTextCraft.cc +++ b/src/WorldOfTextCraft.cc @@ -1,4 +1,4 @@ -#include "Entity.h" +#include "Character.h" #include "Warrior.h" #include "Rogue.h" @@ -47,24 +47,20 @@ int main( int argc, char ** argv ) { battle.splash(); std::cout << "Are you ready to begin? [Y/n] " << std::endl; - char c; - std::cin >> c; - if ( c == 'y' || c == 'Y') { + std::string response; + std::getline(std::cin, response); + if (response == "y" || response == "Y" || response == "") { std::cout << "Excellent... let us begin..." << std::endl; battle.printActions(); - - // Now loop and FIGHT! success= true; do { battle.print(); - success = battle.performUserActions(); - } - while ( success ); + while (success); } diff --git a/test_boss.cc b/test_boss.cc deleted file mode 100644 index 7261ccd..0000000 --- a/test_boss.cc +++ /dev/null @@ -1,59 +0,0 @@ -#include "Entity.h" - -#include "Warrior.h" -#include "Rogue.h" -#include "Priest.h" -#include "Boss.h" - - -int main( int argc, char ** argv ) { - - Warrior warrior("Larry", 3); - Priest priest("Moe", 5); - Rogue rogue("Curly", 15); - Boss boss("Shemp", 15, 4, 2, 100, 8); - - unsigned i = 0; - bool done = false; - - boss.setTarget( &priest ); - warrior.setTarget( &boss ); - rogue.setTarget( &boss ); - priest.setTarget( &warrior ); - - std::vector party; - party.push_back( &warrior ); - party.push_back( &rogue ); - party.push_back( &priest ); - - - while ( ! done ) { - std::cout << "Round " << i << std::endl; - std::cout << warrior << std::endl; - std::cout << priest << std::endl; - std::cout << rogue << std::endl; - std::cout << boss << std::endl; - - if ( i % 5 == 0 ) - for ( std::vector::iterator it = party.begin(); - it != party.end(); ++it ) { - boss.multiAttack( *it ); - } - else - boss.attack(); - warrior.defend(); - rogue.attack(); - priest.heal(); - ++i; - done = (warrior.isDead() && priest.isDead() && rogue.isDead() ) || boss.isDead(); - } - - - if ( warrior.isAlive() && priest.isAlive() && rogue.isAlive()) { - std::cout << "The party wins! The gods rejoice in your ferociousness!" << std::endl; - } else { - std::cout << "Your quest has failed. May you drink and fight long in the mead halls of Valhalla" << std::endl; - } - - return 0; -}; diff --git a/tests/Boss.txt b/tests/Boss.txt new file mode 100644 index 0000000..e878df6 --- /dev/null +++ b/tests/Boss.txt @@ -0,0 +1,4 @@ +!Description: +William the Warrior, Poppy the Priest, and Rachel the Rogue enter their first dungeon. Alas, their poor friend Bob has been bitten by a zombie and is trying to kill them. Help William, Poppy, and Rachel put Bob out of his misert! +! Non-Player character. Fields are "name", attack power, heal power, defense power, mana, multi attack power +Bob;20;0;0;10;5; diff --git a/tests/BossScript.txt b/tests/BossScript.txt new file mode 100644 index 0000000..095a3a8 --- /dev/null +++ b/tests/BossScript.txt @@ -0,0 +1,3 @@ +! Test battle configuration. +Bob;attack;target; +Bob;attack;all; diff --git a/tests/PlayerCharacters.txt b/tests/PlayerCharacters.txt new file mode 100644 index 0000000..62bf900 --- /dev/null +++ b/tests/PlayerCharacters.txt @@ -0,0 +1,8 @@ +! +! Test party configuration: +! Warrior, Rogue, Priest +! Fields are : +! class, name, attack power, defense power, heal power +Warrior;William;0;3;0; +Priest;Poppy;0;0;8; +Rogue;Rachel;20;0;0; diff --git a/tests/PlayerScript.txt b/tests/PlayerScript.txt new file mode 100644 index 0000000..4f7c259 --- /dev/null +++ b/tests/PlayerScript.txt @@ -0,0 +1,4 @@ +! Test battle configuration. +Rachel;attack;Bob; +William;defend;Bob; +Poppy;heal;William; diff --git a/test_battle.cc b/tests/test_battle.cc similarity index 61% rename from test_battle.cc rename to tests/test_battle.cc index a30d780..5263637 100644 --- a/test_battle.cc +++ b/tests/test_battle.cc @@ -1,80 +1,77 @@ -#include "Entity.h" +#include "Character.h" -#include "Warrior.h" -#include "Rogue.h" -#include "Priest.h" #include "Boss.h" +#include "Priest.h" +#include "Rogue.h" +#include "Warrior.h" #include "Battle.h" -int main( int argc, char ** argv ) { +int main(int argc, char **argv) { Battle battle; - bool success = true; + bool success = true; // Read the party configurations - success = battle.readPCConfiguration( "ThreeStooges.txt" ); - if ( ! success ) { + success = battle.readPCConfiguration("PlayerCharacters.txt"); + if (!success) { std::cout << "No PC configuration read" << std::endl; return 0; } - success = battle.readNPCConfiguration( "Shemp.txt"); - if ( ! success ) { + success = battle.readNPCConfiguration("Boss.txt"); + if (!success) { std::cout << "No PC configuration read" << std::endl; return 0; } - - // Read a test sequence. - success = battle.loadActionScript("ShempAttacks.txt"); - if ( ! success ) { + // Read a test sequence. + success = battle.loadActionScript("BossScript.txt"); + if (!success) { std::cout << "No NPC script read" << std::endl; return 0; } - success = battle.loadActionScript("StoogesBattle.txt"); - if ( ! success ) { + success = battle.loadActionScript("PlayerScript.txt"); + if (!success) { std::cout << "No PC script read" << std::endl; return 0; } - battle.splash(); std::cout << "Are you ready to begin? [Y/n] " << std::endl; - char c; - std::cin >> c; - if ( c == 'y' || c == 'Y') { + std::string response; + std::getline(std::cin, response); + //std::cin >> c; + if (response == "y" || response == "Y" || response == "") { std::cout << "Excellent... let us begin..." << std::endl; } else { std::cout << "Oh well. Another time then..." << std::endl; return 0; } - - battle.printActions(); - - + battle.printActions(); // Now loop and FIGHT! do { battle.print(); std::cout << "Continue? [Y/n] " << std::endl; - std::cin >> c; - if ( c != 'y' && c != 'Y') { + std::getline(std::cin, response); + + //std::cin >> c; + if (response != "y" && response != "Y" && response != "") { std::cout << "Bye, then." << std::endl; break; - } - } - while ( battle.performScriptedActions() ); - + } + } while (battle.performScriptedActions()); std::cout << " ___ _ _ _ " << std::endl; std::cout << " / _ \\___ ___ __| | |__ _ _ ___ / \\" << std::endl; - std::cout << " / /_\\/ _ \\ / _ \\ / _` | '_ \\| | | |/ _ \\/ /" << std::endl; + std::cout << " / /_\\/ _ \\ / _ \\ / _` | '_ \\| | | |/ _ \\/ /" + << std::endl; std::cout << "/ /_\\\\ (_) | (_) | (_| | |_) | |_| | __/\\_/ " << std::endl; - std::cout << "\\____/\\___/ \\___/ \\__,_|_.__/ \\__, |\\___\\/ " << std::endl; + std::cout << "\\____/\\___/ \\___/ \\__,_|_.__/ \\__, |\\___\\/ " + << std::endl; std::cout << " |___/ " << std::endl; - - return 0; + return 0; }; diff --git a/tests/test_boss.cc b/tests/test_boss.cc new file mode 100644 index 0000000..8f2965f --- /dev/null +++ b/tests/test_boss.cc @@ -0,0 +1,60 @@ +#include "Character.h" + +#include "Boss.h" +#include "Priest.h" +#include "Rogue.h" +#include "Warrior.h" + +int main(int argc, char **argv) { + + Warrior warrior("William The Warrior", 3); + Priest priest("Poppy The Priest", 5); + Rogue rogue("Rachel The Rogue", 15); + Boss boss("Bob The Boss", 15, 4, 2, 100, 8); + + unsigned i = 0; + bool done = false; + + boss.setTarget(&priest); + warrior.setTarget(&boss); + rogue.setTarget(&boss); + priest.setTarget(&warrior); + + std::vector party; + party.push_back(&warrior); + party.push_back(&rogue); + party.push_back(&priest); + + while (!done) { + std::cout << "Round " << i << std::endl; + std::cout << warrior << std::endl; + std::cout << priest << std::endl; + std::cout << rogue << std::endl; + std::cout << boss << std::endl; + + if (i % 5 == 0) + for (std::vector::iterator it = party.begin(); + it != party.end(); ++it) { + boss.multiAttack(*it); + } + else + boss.attack(); + warrior.defend(); + rogue.attack(); + priest.heal(); + ++i; + done = (warrior.isDead() && priest.isDead() && rogue.isDead()) || + boss.isDead(); + } + + if (warrior.isAlive() && priest.isAlive() && rogue.isAlive()) { + std::cout << "The party wins! The gods rejoice in your ferociousness!" + << std::endl; + } else { + std::cout << "Your quest has failed. May you drink and fight long in the " + "mead halls of Valhalla" + << std::endl; + } + + return 0; +}; diff --git a/test_entity.cc b/tests/test_character.cc similarity index 51% rename from test_entity.cc rename to tests/test_character.cc index adb3227..1ff0a98 100644 --- a/test_entity.cc +++ b/tests/test_character.cc @@ -1,42 +1,43 @@ -#include "Entity.h" +#include "Character.h" -#include "Warrior.h" -#include "Rogue.h" #include "Priest.h" +#include "Rogue.h" +#include "Warrior.h" +int main(int argc, char **argv) { -int main( int argc, char ** argv ) { - - Warrior warrior("Larry", 10); - Priest priest("Moe", 5); - Rogue rogue("Curly", 15); + Warrior warrior("William The Warrior", 10); + Priest priest("Poppy The Priest", 5); + Rogue rogue("Rachel The Rogue", 15); unsigned i = 0; - bool done = false; + bool done = false; - rogue.setTarget( &priest ); - priest.setTarget( &warrior ); + rogue.setTarget(&priest); + priest.setTarget(&warrior); rogue.attack(); - while ( ! done ) { + while (!done) { std::cout << "Round " << i << std::endl; std::cout << warrior << std::endl; std::cout << priest << std::endl; std::cout << rogue << std::endl; - warrior.defend( &rogue ); + warrior.defend(&rogue); rogue.attack(); priest.heal(); ++i; done = warrior.isDead() || priest.isDead() || i >= 20; } - - if ( warrior.isAlive() && priest.isAlive() ) { - std::cout << "The party wins! The gods rejoice in your ferociousness!" << std::endl; + if (warrior.isAlive() && priest.isAlive()) { + std::cout << "The party wins! The gods rejoice in your ferociousness!" + << std::endl; } else { - std::cout << "Your quest has failed. May you drink and fight long in the mead halls of Valhalla" << std::endl; + std::cout << "Your quest has failed. May you drink and fight long in the " + "mead halls of Valhalla" + << std::endl; } - return 0; + return 0; };