基于我以前的问题中提供的建议,我想发布另一个源文件,它实际上实现了所有的战斗机制以及实际的进化过程。
与前面的问题一样,我对各种反馈感兴趣--关于设计、实现、算法、表示、风格、整洁、正确的C++11用法,或者任何您认为可以使此工作更好的反馈--作为为支持职务申请而提交的令人印象深刻的代码示例。
整个代码以及规则都可以在http://www.mz1net.com/code-sample上看到--这也是声明类并更好地描述类的“MechArena.h”可用的地方。
#include <algorithm>
#include <exception>
#include "MechArena.h"
// COMPONENT SET ========================================================================
// ======================================================================================
// The no-param constructor initialises the component list with random components.
template<typename T>
Components<T>::Components (size_t size)
{
for (size_t m = 0; m < size; m++)
{
T component = Components<T>::randomComponent();
this->components.push_back(component);
}
}
// ======================================================================================
// The 'crossover' constructor creates a new Components instance that randomly mixes the
// components used by the two provided parents component sets. Some components will also
// be randomly switched to a random component due to mutation.
template<typename T>
Components<T>::Components (const Components<T>& c1, const Components<T>& c2)
{
size_t c1Size = c1.components.size();
size_t c2Size = c2.components.size();
if (c1Size != c2Size)
{
throw std::exception("Crossover is impossible between component sets that do not have the same size.");
}
// For each component in the set, decide randomly which parent's component the new
// component set will inherit. For optimisation, we request one random number and use
// its bits to decide on the respective component slots.
int r = rand();
for (size_t m = 0; m < c1Size; m++)
{
int mask = 1 << m;
this->components.push_back( (r & mask) ? c1.components.at(m) : c2.components.at(m) );
}
// Mutation:
for (T& component : this->components)
{
if (rand()%1000 <= 50)
{
component = Components::randomComponent();
}
}
}
// ======================================================================================
template<typename T>
const std::vector<T>& Components<T>::getList() const
{
return this->components;
}
// ======================================================================================
template<typename T>
std::ostream& operator<< (std::ostream& os, const Components<T>& c)
{
for (T component : c.getList())
{
os << componentName(component) << '\n';
}
return os;
}
// ======================================================================================
// For each component type (armor/weapon/reactor/aux), we specialise the
// 'randomComponent' method so that it works with the correct enumeration:
ArmorComponent Components<ArmorComponent>::randomComponent()
{
int r = rand() % COMP_ARMOR_LAST;
return static_cast<ArmorComponent>(r);
}
// ======================================================================================
WeaponComponent Components<WeaponComponent>::randomComponent()
{
int r = rand() % COMP_WEAPON_LAST;
return static_cast<WeaponComponent>(r);
}
// ======================================================================================
ReactorComponent Components<ReactorComponent>::randomComponent()
{
int r = rand() % COMP_REACTOR_LAST;
return static_cast<ReactorComponent>(r);
}
// ======================================================================================
AuxComponent Components<AuxComponent>::randomComponent()
{
int r = rand() % COMP_AUX_LAST;
return static_cast<AuxComponent>(r);
}
// WEAPON ===============================================================================
// ======================================================================================
Weapon::Weapon (Mech& owner, WeaponComponent component) :
owner(owner),
component(component)
{
switch (component)
{
case COMP_WEAPON_LASER_HEAVY:
this->hits = 30;
this->cooldown = 5;
this->hitType = HT_PULSE;
break;
case COMP_WEAPON_LASER_FAST:
this->hits = 10;
this->cooldown = 2;
this->hitType = HT_PULSE;
break;
case COMP_WEAPON_MISSILE_LAUNCHER:
this->hits = 50;
this->cooldown = 4;
this->hitType = HT_BLAST;
break;
default:
throw std::exception("Cannot setup a Weapon - there are no data related to the provided component.");
}
this->readyIn = this->cooldown;
this->calibration = false;
}
// ======================================================================================
// Applies a reactor component to the weapon, so that the weapon can alter
// its values (cooldown, hit amount, etc.) based on the particular component.
void Weapon::applyReactorComponent (ReactorComponent component)
{
// Heavy Laser: Reduce cooldown with Heat Sink.
if (this->component == COMP_WEAPON_LASER_HEAVY && component == COMP_REACTOR_HEAT_SINK)
{
this->cooldown--;
}
// Missile Launcher: Reduce cooldown with Advanced Lock-On System.
if (this->component == COMP_WEAPON_MISSILE_LAUNCHER && component == COMP_REACTOR_LOCK_ON)
{
this->cooldown--;
}
// Fast Laser: Increase hit amount with Predictive Scanners.
if (this->component == COMP_WEAPON_LASER_FAST && component == COMP_REACTOR_SCANNERS)
{
this->hits = int (this->hits * 1.5);
}
}
// ======================================================================================
// Makes the weapon act in the mech's combat turn. Reduces the weapon's cooldown, and in
// case that this makes the weapon able to attack this turn, it attacks the provided opponent.
void Weapon::act (Mech& opponent)
{
this->readyIn--;
if (this->readyIn > 0) return;
// For missile-based weapons, check that the mech can spend a missile:
if (!this->requiresMissile() || this->owner.spendMissile())
{
opponent.receiveHit(this->hitType, this->hits, this->calibration);
this->readyIn = this->cooldown;
}
}
// ======================================================================================
bool Weapon::requiresMissile()
{
return (this->component == COMP_WEAPON_MISSILE_LAUNCHER);
}
// ======================================================================================
void Weapon::reset() {
this->readyIn = this->cooldown;
}
// MECH =================================================================================
// ======================================================================================
// The no-param constructor creates a new mech with a random component setup.
Mech::Mech() :
armorComponents(3),
weaponComponents(4),
reactorComponents(3),
auxComponents(2)
{
this->initID();
this->initCombatValues();
}
// ======================================================================================
// The crossover constructor creates a new crossover mech that is based on the two provided
// parents. The new mech randomly mixes components of its parents, and some components can
// also be randomly switched to another due to mutation.
Mech::Mech (const Mech& m1, const Mech& m2) :
armorComponents (m1.armorComponents, m2.armorComponents),
weaponComponents (m1.weaponComponents, m2.weaponComponents),
reactorComponents (m1.reactorComponents, m2.reactorComponents),
auxComponents (m1.auxComponents, m2.auxComponents)
{
this->initID();
this->initCombatValues();
}
// ======================================================================================
// Provides the mech with a unique ID.
// This needs to be called by all the constructors.
void Mech::initID()
{
static int ID = 0;
this->ID = ++ID;
}
// ======================================================================================
// Calculates all the mech's combat values based on its component setup.
// This needs to be called by all the constructors.
void Mech::initCombatValues()
{
this->maxArmor = 100;
this->armorAutoRepair = 0;
this->pulseAbsorb = 0;
this->interceptor = false;
this->repairBots = false;
this->maxMissiles = 0;
this->maxCountermeasures = 0;
this->maxNanobots = 0;
this->score = 0;
// Armor:
for (ArmorComponent component : this->armorComponents.getList())
{
switch (component)
{
case COMP_ARMOR_BONUS:
this->maxArmor += 35;
break;
case COMP_ARMOR_REPAIR_AUTO:
this->armorAutoRepair += 5;
break;
case COMP_ARMOR_REPAIR_BOTS:
this->repairBots = true;
this->nanobots += 1;
break;
case COMP_ARMOR_MISSILE_INTERCEPTOR:
this->interceptor = true;
this->countermeasures += 1;
break;
case COMP_ARMOR_LASER_ABSORB:
this->pulseAbsorb += 2;
break;
default:
throw std::exception("Cannot setup the mech with the provided armor component.");
}
}
// Weapons:
for (WeaponComponent component : this->weaponComponents.getList())
{
this->weapons.push_back( Weapon(*this, component) );
}
// Reactor:
for (ReactorComponent component : this->reactorComponents.getList())
{
// Let all the weapons update their internal values based on the reactor component:
for (std::vector<Weapon>::iterator weaponIt = this->weapons.begin(); weaponIt != this->weapons.end(); weaponIt++)
{
weaponIt->applyReactorComponent(component);
}
}
// Auxiliary:
for (AuxComponent component : this->auxComponents.getList())
{
switch (component)
{
case COMP_AUX_MISSILES:
this->maxMissiles += 4;
break;
case COMP_AUX_COUNTERMEASURES:
this->maxCountermeasures += 2;
break;
case COMP_AUX_NANOBOTS:
this->maxNanobots += 3;
break;
default:
throw std::exception("Cannot setup the mech with the provided auxiliary component.");
}
}
}
// ======================================================================================
// Executes a match between the two provided mechs and returns the match result.
// This is a static method that expects both the match participants as parameters.
MatchResult Mech::match (Mech& m1, Mech& m2)
{
m1.resetCombatValues();
m2.resetCombatValues();
int turn = 0;
static const int turnLimit = 25;
// Each turn:
while (turn < turnLimit)
{
m1.actCombatTurn(m2);
m2.actCombatTurn(m1);
bool mech1Dead = !m1.isAlive();
bool mech2Dead = !m2.isAlive();
if (mech1Dead && mech2Dead) return MATCH_RESULT_DRAW;
if (mech1Dead) return MATCH_RESULT_MECH_2_WINS;
if (mech2Dead) return MATCH_RESULT_MECH_1_WINS;
turn++;
}
return MATCH_RESULT_DRAW;
}
// ======================================================================================
// Allows the mech to take all the actions that it can take in its combat turn:
// use or cool down its weapons, auto-repair, use nanobots to repair, etc.
void Mech::actCombatTurn (Mech& opponent)
{
this->autoRepair();
this->botRepair();
this->attack(opponent);
}
// ======================================================================================
// Executes the mech's auto-repairs.
void Mech::autoRepair()
{
this->armor = std::min(this->armor + this->armorAutoRepair, this->maxArmor);
}
// ======================================================================================
// Makes the mech use its nanobot repair module in its combat turn, when appropriate.
void Mech::botRepair()
{
// Check that the mech has the repair
// module and also a nanobot to spare.
if (!this->repairBots) return;
if (this->nanobots <= 0) return;
// Do not repair unless needed.
const int nanobotRepairAmount = 20;
if (this->armor > this->maxArmor - nanobotRepairAmount) return;
// Repair and spend the nanobot.
this->armor += nanobotRepairAmount;
this->nanobots--;
}
// ======================================================================================
// Makes the mech act with all its weapons in its combat turn.
void Mech::attack (Mech& opponent)
{
for (Weapon& weapon : this->weapons)
{
weapon.act(opponent);
}
}
// ======================================================================================
// Makes the mech receive a combat hit. This reduces the mech's armor.
// The mech is allowed to use all its protection mechanisms (countermeasures, laser absorbs).
// For calibrated hits, the hit destroys a missile, a countermeasure, or a nanobot.
void Mech::receiveHit (HitType hitType, int hits, bool calibration)
{
// Pulse hits: Reduce the hit amount by our 'laser absorb' value.
if (hitType == HT_PULSE)
{
this->armor -= (hits - this->pulseAbsorb);
}
// Blast hits: Counter them with a countermeasure, if available.
if (hitType == HT_BLAST)
{
if (this->interceptor && this->countermeasures > 0)
{
this->countermeasures--;
}
else
{
this->armor -= hits;
}
}
// For calibrated hits, destroy a
// missile/countermeasure/nanobot.
if (calibration)
{
if (this->missiles > 0)
{
this->missiles--;
}
else if (this->countermeasures > 0)
{
this->countermeasures--;
}
else if (this->nanobots > 0)
{
this->nanobots--;
}
}
}
// ======================================================================================
bool Mech::isAlive() const
{
return (this->armor > 0);
}
// ======================================================================================
// Makes the mech spend a missile. Returns 'true' when the mech had a missile to spare.
// Returns 'false' when the mech was out of missiles.
bool Mech::spendMissile()
{
if (this->missiles > 0)
{
this->missiles--;
return true;
}
return false;
}
// ======================================================================================
// Restores all the mech's combat status values (armor, weapon cooldowns, missiles, etc.)
// to their initial values, so that it can start a new combat match.
void Mech::resetCombatValues()
{
this->armor = this->maxArmor;
this->missiles = this->maxMissiles;
this->countermeasures = this->maxCountermeasures;
this->nanobots = this->maxNanobots;
for (Weapon& weapon : this->weapons)
{
weapon.reset();
}
}
// ======================================================================================
void Mech::addScore (int point)
{
this->score += point;
}
// ======================================================================================
void Mech::resetScore()
{
this->score = 0;
}
// ======================================================================================
int Mech::getScore() const
{
return this->score;
}
// ======================================================================================
std::ostream& operator<< (std::ostream& os, const Mech& m)
{
os << "MECH #" << m.ID << " SPECIFICATIONS:\n";
os << "=========================\n";
os << " [ARMOR]:\n";
os << m.armorComponents << '\n';
os << " [WEAPON]:\n";
os << m.weaponComponents << '\n';
os << " [REACTOR]:\n";
os << m.reactorComponents << '\n';
os << " [AUX]:\n" ;
os << m.auxComponents << '\n';
return os;
}
// POPULATION ===========================================================================
// ======================================================================================
Population::Population (size_t size)
: size(size)
{
// The population size has to be an even number, so that we will later
// be able to pair up all the mechs with an opponent in a combat round.
if (size % 2 > 0) {
throw std::exception("The population size has to be an even number.");
}
// Fill the population with random mechs.
this->mechs.reserve(size);
for (size_t m = 0; m < size; m++)
{
this->mechs.push_back( std::unique_ptr<Mech> ( new Mech() ) );
}
this->sorted = true;
}
// ======================================================================================
void Population::createNextGeneration()
{
this->runMatches();
this->prune();
this->procreate();
}
// ======================================================================================
// Resets the all the mechs' scores and executes several match rounds.
void Population::runMatches()
{
for (auto& mech : this->mechs)
{
mech->resetScore();
}
const static int rounds = 40;
for (int m = 0; m < rounds; m++)
{
this->runMatchRound();
}
}
// ======================================================================================
// Randomly pairs all the mechs up with an opponent and makes the pairs execute a match.
void Population::runMatchRound()
{
// Create an index queue that will contain indices [0 .. size] into the mech vector in
// a randomised order, then use this queue to pair up the mechs into their matches.
std::vector<size_t> queue;
for (size_t m = 0; m < this->mechs.size(); m++)
{
queue.push_back(m);
}
std::random_shuffle(queue.begin(), queue.end());
while (!queue.empty())
{
size_t index1 = *queue.rbegin(); queue.pop_back();
size_t index2 = *queue.rbegin(); queue.pop_back();
Mech& mech1 = *this->mechs[index1];
Mech& mech2 = *this->mechs[index2];
// Execute the match:
MatchResult matchResult = Mech::match(mech1, mech2);
switch (matchResult)
{
case MATCH_RESULT_MECH_1_WINS:
mech1.addScore(5);
break;
case MATCH_RESULT_MECH_2_WINS:
mech2.addScore(5);
break;
case MATCH_RESULT_DRAW:
mech1.addScore(1);
mech2.addScore(1);
break;
}
}
this->sorted = false;
}
// ======================================================================================
// Calculates a score threshold based on the arithmetic mean between all the
// scores the population, and releases all the mechs that scored below this limit.
void Population::prune()
{
int scoreTotal = 0;
for (auto& mech : this->mechs) {
scoreTotal += mech->getScore();
}
int scoreThreshold = scoreTotal / this->mechs.size();
// For all the mechs below the score threshold, release the mech and reset its pointer in the
// population to indicate that it needs to be replaced with a new mech in the procreation step.
for (auto& mech : this->mechs)
{
if (mech->getScore() < scoreThreshold)
{
mech.reset();
}
}
}
// ======================================================================================
// Replaces all the mechs that were released in the 'prune' step with new mechs. Each survivor's
// score is used as the relative chance that it will be selected as a parent to a new mech.
void Population::procreate()
{
// Build up the parent pool – a container that we will use to randomly select
// parents. The parents are represented by their indices into the mech list.
WeightedSet<size_t> parents;
for (size_t index = 0; index < this->mechs.size(); index++)
{
if (this->mechs[index] != nullptr)
{
parents.insert(index, this->mechs[index]->getScore());
}
}
// For each mech that was released:
for (auto& mech : this->mechs)
{
if (mech != nullptr) continue;
Mech& parent1 = *this->mechs[ parents.random() ];
Mech& parent2 = *this->mechs[ parents.random() ];
mech = std::unique_ptr<Mech> ( new Mech (parent1, parent2) );
}
}
// ======================================================================================
// Returns the mech with the N-th best score.
const Mech& Population::getMech (size_t index)
{
if (index >= this->mechs.size())
{
throw std::exception("The provided mech index is out of bounds.");
}
this->sort();
return *this->mechs.at(index);
}
// ======================================================================================
// Sorts the mechs, winners to losers.
void Population::sort()
{
if (this->sorted) return;
std::sort(this->mechs.begin(), this->mechs.end(),
[](const std::unique_ptr<Mech>& m1, const std::unique_ptr<Mech>& m2)
{
return m1->getScore() > m2->getScore();
}
);
this->sorted = true;
}
// ======================================================================================
size_t Population::getSize() const
{
return this->size;
}
// WEIGHTED SET =========================================================================
// ======================================================================================
template<typename T>
void WeightedSet<T>::insert (T val, int w)
{
this->values[ this->maxValue() + w ] = val;
}
// ======================================================================================
// Returns a random element from the set, with the elements' weights
// serving as relative probabilities that the element will be selected.
template<typename T>
T WeightedSet<T>::random()
{
if (this->values.empty())
{
throw std::exception("An empty set cannot be queried for a random value");
}
// Generate a random number and use the cumulative weights map to
// locate the first element with its cumulative weight greater than the number.
int r = rand() % this->maxValue();
std::map<int, T>::iterator it = this->values.upper_bound(r);
if (it == this->values.end())
{
throw std::exception("WeightedSet integrity failure.");
}
return it->second;
}
// ======================================================================================
template<typename T>
int WeightedSet<T>::maxValue() const
{
return (this->values.empty()) ? 0 : this->values.crbegin()->first;
}
// COMPONENT NAMES ======================================================================
// ======================================================================================
// Functions that provides translations between the Armor/Weapon/Reactor/AuxComponent
// enumeration values and their human-readable component names.
template<>
std::string componentName (ArmorComponent ac)
{
switch (ac)
{
case COMP_ARMOR_BONUS: return "Thermo-Carbon-60 Armor Plates";
case COMP_ARMOR_REPAIR_AUTO: return "Auto-Replicative Fibres";
case COMP_ARMOR_REPAIR_BOTS: return "Nanobot Repair Module";
case COMP_ARMOR_MISSILE_INTERCEPTOR: return "AIMS: Anti-Missile System";
case COMP_ARMOR_LASER_ABSORB: return "Inverse EM Field";
default:
throw std::exception("The provided ArmorComponent does not have a textual description.");
}
}
// ======================================================================================
template<>
std::string componentName (WeaponComponent wc)
{
switch (wc)
{
case COMP_WEAPON_LASER_FAST: return "Black Adder: Fast Laser";
case COMP_WEAPON_LASER_HEAVY: return "Obliterator: Heavy Laser";
case COMP_WEAPON_MISSILE_LAUNCHER: return "Trebuchet: Missile Launcher";
default:
throw std::exception("The provided WeaponComponent does not have a textual description.");
}
}
// ======================================================================================
template<>
std::string componentName (ReactorComponent rc)
{
switch (rc)
{
case COMP_REACTOR_HEAT_SINK: return "White Noise Heat Sink";
case COMP_REACTOR_LOCK_ON: return "Advanced Missile Lock-on System";
case COMP_REACTOR_SCANNERS: return "Predictive Scanners";
case COMP_REACTOR_CALIBRATION: return "Explosive Laser Calibration Module";
default:
throw std::exception("The provided ReactorComponent does not have a textual description.");
}
}
// ======================================================================================
template<>
std::string componentName (AuxComponent ac)
{
switch (ac) {
case COMP_AUX_MISSILES: return "Ammo: Missiles";
case COMP_AUX_COUNTERMEASURES: return "Ammo: Countermeasures";
case COMP_AUX_NANOBOTS: return "Extra: Nanobots";
default:
throw std::exception("The provided AuxComponent does not have a textual description.");
}
}发布于 2014-01-13 18:58:38
不要喜欢所有的开关(我想你这样做是为了速度(怀疑它有什么不同)。
我会进行几次测试,看看在用例场景中,与虚拟函数相比,使用开关是否确实有很大的不同。
但是它(开关)使代码变得混乱,难以划分(维护/修复),我会使用类来封装意义和防止bug。它也使bug更有可能发生。
宁愿抛出std::runtime_error而不是std::exception。它缩小了需要在运行时捕获的类型。如果你真的要加快速度,那么异常可能比虚拟函数调用更昂贵(但是要测量)。
我更喜欢把康斯特放在右边(有一个角落的情况很重要)。但社区对此仍存在分歧,所以你要么接受,要么放弃:
std::ostream& operator<< (std::ostream& os, const Components<T>& c)
I prefer:
std::ostream& operator<< (std::ostream& os, Components<T> const& c)
// ^^^^^^
// Its a const ref.它也使阅读类型更容易。当您从右到左读取类型时,const总是绑定到左边(除非它是第一项,那么在绑定的右边)。
// Components<T> const&
// ^ Reference to
// ^^^^^ const
// ^^^^^^^^^^^^^ Components<T>
char const * const data = "Plop";
char const * const& data1 = data;
// ^ Reference to
// ^^^^^ Const
// ^ Pointer to
// ^^^^^ Const
// ^^^^ charhttps://codereview.stackexchange.com/questions/39141
复制相似问题