Commits

David Stone committed 44093ad

Allowed stats to be accessed via their enumerated name rather than specially named functions. This allows more generic code, with easy constructs such as looping over every stat (or any subset).

  • Participants
  • Parent commits ec4aeab

Comments (0)

Files changed (40)

build_scripts/sources.py

 ai_sources += prepend_dir('cryptography', ['hex.cpp', 'md5.cpp', 'rijndael.cpp', 'sha2.cpp'])
 ai_sources += evaluate_sources
 ai_sources += prepend_dir('move', ['collection.cpp', 'container.cpp', 'power.cpp', 'shared.cpp', 'use_move.cpp'])
-ai_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp'])
+ai_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp', 'stats.cpp'])
 ai_sources += prepend_dir('team_predictor', ['detailed_stats.cpp', 'estimate.cpp', 'load_stats.cpp', 'multiplier.cpp', 'team_predictor.cpp'])
 ai_sources += ev_optimizer_sources
 ai_sources += prepend_dir('type', ['collection.cpp', 'effectiveness.cpp', 'type.cpp'])
 predict_sources = ['ability.cpp', 'damage.cpp', 'gender.cpp', 'heal.cpp', 'item.cpp', 'invalid_settings_file.cpp', 'phazing_in_same_pokemon.cpp', 'status.cpp', 'variable.cpp', 'weather.cpp']
 predict_sources += team_sources
 predict_sources += prepend_dir('move', ['collection.cpp', 'container.cpp', 'power.cpp', 'shared.cpp'])
-predict_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp'])
+predict_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp', 'stats.cpp'])
 predict_sources += prepend_dir('clients/', ['invalid_team_file_format.cpp'])
 predict_sources += prepend_dir('clients/pokemon_lab', ['conversion.cpp', 'read_team_file.cpp'])
 predict_sources += prepend_dir('clients/pokemon_online', ['conversion.cpp', 'read_team_file.cpp'])
 test_sources += ['ability.cpp', 'damage.cpp', 'gender.cpp', 'heal.cpp', 'item.cpp', 'invalid_settings_file.cpp', 'phazing_in_same_pokemon.cpp', 'status.cpp', 'variable.cpp', 'weather.cpp']
 test_sources += team_sources
 test_sources += prepend_dir('move', ['collection.cpp', 'container.cpp', 'power.cpp', 'shared.cpp'])
-test_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp'])
+test_sources += prepend_dir('stat', ['ev.cpp', 'invalid_ev.cpp', 'invalid_stat.cpp', 'nature.cpp', 'stat.cpp', 'stats.cpp'])
 test_sources += ev_optimizer_sources
 test_sources += prepend_dir('clients/', ['invalid_team_file_format.cpp'])
 test_sources += prepend_dir('clients/pokemon_lab', ['conversion.cpp', 'read_team_file.cpp', 'write_team_file.cpp'])

source/ability.cpp

 void Ability::activate_on_switch (ActivePokemon & switcher, ActivePokemon & other, Weather & weather) {
 	switch (switcher.ability().name) {
 		case DOWNLOAD: {
-			calculate_defense (other);
-			calculate_special_defense (other, weather);
-			switcher.stat_boost((other.def().stat >= other.spd().stat) ? Stat::SPA : Stat::ATK, 1);
+			calculate_defense(other, weather);
+			calculate_special_defense(other, weather);
+			switcher.stat_boost((other.stat(Stat::DEF).stat >= other.stat(Stat::SPD).stat) ? Stat::SPA : Stat::ATK, 1);
 			break;
 		}
 		case DRIZZLE:
 	
 	if (move.is_switch())
 		return true;
-	if (user.hp().stat == 0 or (other.hp().stat == 0 and false))
+	if (user.is_fainted() or (other.is_fainted() and false))
 		return false;
 
 	bool execute = !(is_blocked_due_to_status (user, move) or

source/clients/battle.cpp

 	std::cerr << pokemon.to_string() << '\n';
 	assert(move_damage);
 	Rational const change(visible_damage, max_visible_hp_change(team));
-	auto const damage = pokemon.hp().max * change;
+	auto const damage = pokemon.stat(Stat::HP).max * change;
 	updated_hp.direct_damage(team.is_me(), pokemon, damage);
 	move_damage = false;
 }
 	return max_visible_hp_change(changer.is_me(), changer.replacement());
 }
 unsigned GenericBattle::max_visible_hp_change(bool const my_pokemon, Pokemon const & changer) const {
-	return my_pokemon ? changer.hp().max : max_damage_precision();
+	return my_pokemon ? changer.stat(Stat::HP).max : max_damage_precision();
 }
 
 bool GenericBattle::is_valid_hp_change(Party changer, unsigned remaining_hp, int received_change) const {
 		auto const new_hp = updated_hp.get(team.is_me(), pokemon);
 		if (tm_estimate == new_hp)
 			return;
-		auto const reported_hp = new_hp * pokemon.hp().max / max_visible_hp_change(team.is_me(), pokemon);
+		auto const reported_hp = new_hp * pokemon.stat(Stat::HP).max / max_visible_hp_change(team.is_me(), pokemon);
 		unsigned const min_value = (tm_estimate == 0) ? 0 : (tm_estimate - 1);
 		unsigned const max_value = tm_estimate + 1;
 		assert(max_value > tm_estimate);
 			std::cerr << "Uh oh! " + pokemon.to_string() + " has the wrong HP! The server reports ";
 			if (!team.is_me())
 				std::cerr << "approximately ";
-			std::cerr << reported_hp << " HP remaining, but TM thinks it has " << pokemon.hp().stat << ".\n";
+			std::cerr << reported_hp << " HP remaining, but TM thinks it has " << pokemon.stat(Stat::HP).stat << ".\n";
 			std::cerr << "max_visible_hp_change: " << max_visible_hp_change(team.is_me(), pokemon) << '\n';
-			std::cerr << "pokemon.hp.max: " << pokemon.hp().max << '\n';
+			std::cerr << "pokemon.hp.max: " << pokemon.stat(Stat::HP).max << '\n';
 			std::cerr << "new_hp: " << new_hp << '\n';
 			std::cerr << "tm_estimate: " << tm_estimate << '\n';
 			assert(false);
 		
 		constexpr bool damage_is_known = true;
 		std::cerr << "First\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 
 		register_damage();
 		call_move(*first, *last, weather, variable(*first), damage_is_known);
 		std::cerr << "Second\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 		normalize_hp(*last);
 		std::cerr << "Third\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 
 		register_damage();
 		call_move(*last, *first, weather, variable(*last), damage_is_known);
 		std::cerr << "Fourth\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 		normalize_hp(*first);
 		std::cerr << "Fifth\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 
 		register_damage();
 		endofturn (*first, *last, weather);
 		std::cerr << "Sixth\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 		normalize_hp ();
 		std::cerr << "Seventh\n";
-		std::cerr << "AI HP: " << ai.pokemon().hp().stat << '\n';
-		std::cerr << "Foe HP: " << foe.pokemon().hp().stat << '\n';
+		std::cerr << "AI HP: " << ai.pokemon().stat(Stat::HP).stat << '\n';
+		std::cerr << "Foe HP: " << foe.pokemon().stat(Stat::HP).stat << '\n';
 		
 		// I only have to check if the foe fainted because if I fainted, I have
 		// to make a decision to replace that Pokemon. I update between each

source/clients/pokemon_lab/outmessage.cpp

 		write_int(simulator_cast<ID<Moves>>(move.name).value());
 		write_int (3);		// Replace this with real PP-ups logic later
 	});
-	write_int (pokemon.hp().iv);
-	write_int (pokemon.hp().ev.value());
-	write_int (pokemon.atk().iv);
-	write_int (pokemon.atk().ev.value());
-	write_int (pokemon.def().iv);
-	write_int (pokemon.def().ev.value());
-	write_int (pokemon.spe().iv);
-	write_int (pokemon.spe().ev.value());
-	write_int (pokemon.spa().iv);
-	write_int (pokemon.spa().ev.value());
-	write_int (pokemon.spd().iv);
-	write_int (pokemon.spd().ev.value());
+	static constexpr auto stats = {
+		Stat::HP, Stat::ATK, Stat::DEF, Stat::SPE, Stat::SPA, Stat::SPD
+	};
+	for (auto const stat : stats) {
+		write_int(pokemon.stat(stat).iv);
+		write_int(pokemon.stat(stat).ev.value());
+	}
 }
 
 void OutMessage::write_move (uint32_t field_id, uint8_t move_index, uint8_t target) {

source/clients/pokemon_lab/read_team_file.cpp

 
 #include "read_team_file.hpp"
 
+#include <map>
 #include <string>
 
 #include <boost/property_tree/ptree.hpp>
 
 #include "../../pokemon/pokemon.hpp"
 
-#include "../../stat/invalid_stat.hpp"
 #include "../../stat/stat.hpp"
 
 #include "../../string_conversions/conversion.hpp"
 }
 
 static Stat & lookup_stat (Pokemon & pokemon, std::string const & name) {
-	if (name == "HP")
-		return pokemon.hp();
-	else if (name == "Atk")
-		return pokemon.atk();
-	else if (name == "Def")
-		return pokemon.def();
-	else if (name == "Spd")
-		return pokemon.spe();
-	else if (name == "SpAtk")
-		return pokemon.spa();
-	else if (name == "SpDef")
-		return pokemon.spd();
-	else
-		throw InvalidStat (name);
+	static std::map<std::string, Stat::Stats> const stats = {
+		{ "HP", Stat::HP },
+		{ "Atk", Stat::ATK },
+		{ "Def", Stat::DEF },
+		{ "SpAtk", Stat::SPA },
+		{ "SpDef", Stat::SPD },
+		{ "Spd", Stat::SPE }
+	};
+	return pokemon.stat(stats.at(name));
 }
 
 static void load_stats (Pokemon & pokemon, boost::property_tree::ptree const & pt) {

source/clients/pokemon_lab/write_team_file.cpp

 }
 
 void write_stats (Pokemon const & pokemon, boost::property_tree::ptree & pt) {
-	write_stat(pokemon.hp(), "HP", pt);
-	write_stat(pokemon.atk(), "Atk", pt);
-	write_stat(pokemon.def(), "Def", pt);
-	write_stat(pokemon.spe(), "Spd", pt);
-	write_stat(pokemon.spa(), "SpAtk", pt);
-	write_stat(pokemon.spd(), "SpDef", pt);
+	static std::pair<Stat::Stats, std::string> const stats [] = {
+		{ Stat::HP, "HP" },
+		{ Stat::ATK, "Atk" },
+		{ Stat::DEF, "Def" },
+		{ Stat::SPE, "Spd" },
+		{ Stat::SPA, "SpAtk" },
+		{ Stat::SPD, "SpDef" }
+	};
+	for (auto const & stat : stats) {
+		write_stat(pokemon.stat(stat.first), stat.second, pt);
+	}
 }
 
 void write_pokemon (Pokemon const & pokemon, boost::property_tree::ptree & pt) {

source/clients/pokemon_online/outmessage.cpp

 		write_int (0);
 		++number_of_moves;
 	}
-	write_byte(pokemon.hp().iv);
-	write_byte(pokemon.atk().iv);
-	write_byte(pokemon.def().iv);
-	write_byte(pokemon.spe().iv);
-	write_byte(pokemon.spa().iv);
-	write_byte(pokemon.spd().iv);
-
-	write_byte(pokemon.hp().ev.value());
-	write_byte(pokemon.atk().ev.value());
-	write_byte(pokemon.def().ev.value());
-	write_byte(pokemon.spe().ev.value());
-	write_byte(pokemon.spa().ev.value());
-	write_byte(pokemon.spd().ev.value());
+	static constexpr auto stats = {
+		Stat::HP, Stat::ATK, Stat::DEF, Stat::SPE, Stat::SPA, Stat::SPD
+	};
+	for (auto const stat : stats) {
+		write_byte(pokemon.stat(stat).iv);
+	}
+	for (auto const stat : stats) {
+		write_byte(pokemon.stat(stat).ev.value());
+	}
 }
 
 enum Choice {

source/clients/pokemon_online/read_team_file.cpp

 #include <boost/property_tree/xml_parser.hpp>
 
 #include "conversion.hpp"
+#include "stat_order.hpp"
 
 #include "../../ability.hpp"
 #include "../../item.hpp"
 }
 
 Stat & lookup_stat(Pokemon & pokemon, unsigned n) {
-	switch (n) {
-		case 0:
-			return pokemon.hp();
-		case 1:
-			return pokemon.atk();
-		case 2:
-			return pokemon.def();
-		case 3:
-			return pokemon.spa();
-		case 4:
-			return pokemon.spd();
-		case 5:
-			return pokemon.spe();
-		default:
-			throw InvalidStat(std::to_string(n));
+	if (n >= stat_order.size()) {
+		throw InvalidStat(std::to_string(n));
 	}
+	return pokemon.stat(stat_order[n]);
 }
 
 bool is_real_pokemon(boost::property_tree::ptree const & pt) {

source/clients/pokemon_online/stat_order.hpp

+// Pokemon Online stat order
+// Copyright (C) 2012 David Stone
+//
+// This file is part of Technical Machine.
+//
+// Technical Machine is free software: you can redistribute it and / or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef POKEMON_ONLINE__STAT_ORDER_HPP_
+#define POKEMON_ONLINE__STAT_ORDER_HPP_
+
+#include <array>
+#include "../../stat/stat.hpp"
+
+namespace technicalmachine {
+namespace po {
+
+constexpr std::array<Stat::Stats, 6> stat_order {{
+	Stat::HP, Stat::ATK, Stat::DEF, Stat::SPA, Stat::SPD, Stat::SPE
+}};
+
+}	// namespace po
+}	// namespace technicalmachine
+#endif	// POKEMON_ONLINE__STAT_ORDER_HPP_

source/clients/pokemon_online/write_team_file.cpp

 #include <boost/property_tree/xml_parser.hpp>
 
 #include "conversion.hpp"
+#include "stat_order.hpp"
 
 #include "../../team.hpp"
 
 }
 
 void write_stats (Pokemon const & pokemon, boost::property_tree::ptree & pt) {
-	pt.add("DV", pokemon.hp().iv);
-	pt.add("DV", pokemon.atk().iv);
-	pt.add("DV", pokemon.def().iv);
-	pt.add("DV", pokemon.spa().iv);
-	pt.add("DV", pokemon.spd().iv);
-	pt.add("DV", pokemon.spe().iv);
-	pt.add("EV", pokemon.hp().ev.value());
-	pt.add("EV", pokemon.atk().ev.value());
-	pt.add("EV", pokemon.def().ev.value());
-	pt.add("EV", pokemon.spa().ev.value());
-	pt.add("EV", pokemon.spd().ev.value());
-	pt.add("EV", pokemon.spe().ev.value());
+	for (auto const stat : stat_order) {
+		pt.add("DV", pokemon.stat(stat).iv);
+	}
+	for (auto const stat : stat_order) {
+		pt.add("EV", pokemon.stat(stat).ev.value());
+	}
 }
 
 void write_blank_stats (boost::property_tree::ptree & pt) {

source/clients/updated_hp.cpp

 
 UpdatedHP::UpdatedHP(Team const & team) {
 	for (auto const & pokemon : team.all_pokemon()) {
-		add(team.is_me(), pokemon, pokemon.hp().max);
+		add(team.is_me(), pokemon, pokemon.stat(Stat::HP).max);
 	}
 }
 

source/damage.cpp

 
 unsigned capped_damage(ActivePokemon const & attacker, Team const & defender, Weather const & weather, Variable const & variable) {
 	unsigned damage = uncapped_damage (attacker, defender, weather, variable);
-	Stat const & hp = defender.pokemon().hp();
+	Stat const & hp = defender.pokemon().stat(Stat::HP);
 	if (damage >= hp.stat) {
 		damage = hp.stat;
 		if (attacker.move().cannot_ko() or defender.pokemon().cannot_be_koed())
 		case Moves::Dragon_Rage:
 			return 40;
 		case Moves::Endeavor:
-			return static_cast<unsigned> (std::max(defender.pokemon().hp().stat - attacker.hp().stat, 0));
+			return static_cast<unsigned> (std::max(defender.pokemon().stat(Stat::HP).stat - attacker.stat(Stat::HP).stat, 0));
 		case Moves::Fissure:
 		case Moves::Guillotine:
 		case Moves::Horn_Drill:
 		case Moves::Sheer_Cold:
-			return defender.pokemon().hp().max;
+			return defender.pokemon().stat(Stat::HP).max;
 		case Moves::Night_Shade:
 		case Moves::Seismic_Toss:
 			return attacker.level();
 		case Moves::SonicBoom:
 			return 20;
 		case Moves::Super_Fang:
-			return defender.pokemon().hp().stat / 2;
+			return defender.pokemon().stat(Stat::HP).stat / 2;
 		default:
 			return regular_damage(attacker, defender, weather, variable);
 	}
 	// (a / b) / c == a / (b * c)
 	// See: http://math.stackexchange.com/questions/147771/rewriting-repeated-integer-division-with-multiplication
 	return attacker.move().is_physical() ?
-		Rational(attacker.atk().stat, 50u * defender.def().stat * weakening_from_status(attacker)) :
-		Rational(attacker.spa().stat, 50u * defender.spd().stat);
+		Rational(attacker.stat(Stat::ATK).stat, 50u * defender.stat(Stat::DEF).stat * weakening_from_status(attacker)) :
+		Rational(attacker.stat(Stat::SPA).stat, 50u * defender.stat(Stat::SPD).stat);
 }
 
 unsigned weakening_from_status (Pokemon const & attacker) {

source/endofturn.cpp

 			break;
 	}
 	if (pokemon.leech_seeded()) {
-		unsigned const n = pokemon.hp().stat;
+		unsigned const n = pokemon.stat(Stat::HP).stat;
 		drain(pokemon, Rational(1, 8));
 		if (!foe.is_fainted()) {
 			if (pokemon.ability().damages_leechers ())
-				foe.apply_damage(n - pokemon.hp().stat);
+				foe.apply_damage(n - pokemon.stat(Stat::HP).stat);
 			else
-				foe.apply_healing(n - pokemon.hp().stat);
+				foe.apply_healing(n - pokemon.stat(Stat::HP).stat);
 		}
 	}
 	switch (pokemon.status().name()) {

source/evaluate/evaluate.cpp

 		score += ingrain;
 	score += pokemon.magnet_rise.turns_remaining * magnet_rise;
 	if (pokemon.active_substitute)
-		score += substitute + substitute_hp * pokemon.active_substitute.hp / pokemon.hp().max;
+		score += substitute + substitute_hp * pokemon.active_substitute.hp / pokemon.stat(Stat::HP).max;
 	score += Stage::dot_product(pokemon.stage, stage);
 	return score;
 }
 }
 
 int64_t Evaluate::score_active_pokemon(ActivePokemon const & pokemon) const {
-	if (pokemon.hp().stat == 0) {
+	if (pokemon.stat(Stat::HP).stat == 0) {
 		return 0;
 	}
 	int64_t score = 0;
 
 
 int64_t Evaluate::win (Team const & team) {
-	if (team.all_pokemon().size() == 1 and team.pokemon().hp().stat == 0)
+	if (team.all_pokemon().size() == 1 and team.pokemon().stat(Stat::HP).stat == 0)
 		return team.is_me() ? -victory : victory;
 	return 0;
 }

source/evaluate/expectiminimax.cpp

 	calculate_speed (ai, weather);
 	calculate_speed (foe, weather);
 
-	if (ai.pokemon().hp().stat == 0 or foe.pokemon().hp().stat == 0)
+	if (ai.pokemon().stat(Stat::HP).stat == 0 or foe.pokemon().stat(Stat::HP).stat == 0)
 		return replace (ai, foe, ai_scores, foe_scores, weather, depth, evaluate, best_move, first_turn);
 	else if (ai.pokemon().switch_decision_required())
 		return initial_move_then_switch_branch(ai, foe, ai_scores, foe_scores, weather, depth, evaluate, best_move, first_turn);
 	bool const speed_tie = (first == nullptr);
 	unsigned const tabs = first_turn ? 0 : 2;
 	int64_t alpha = -Evaluate::victory - 1;
-	auto const ai_break_out = [& ai]() { return ai.pokemon().hp().stat != 0; };
+	auto const ai_break_out = [& ai]() { return ai.pokemon().stat(Stat::HP).stat != 0; };
 	ai.all_pokemon().for_each_replacement(ai_break_out, [&]() {
 		if (verbose or first_turn)
 			std::cout << std::string(tabs, '\t') + "Evaluating switching to " + ai.all_pokemon().at_replacement().to_string() + "\n";
 		int64_t beta = Evaluate::victory + 1;
 		auto const foe_break_out = [& foe, & alpha, & beta]() {
-			return beta <= alpha or foe.pokemon().hp().stat != 0;
+			return beta <= alpha or foe.pokemon().stat(Stat::HP).stat != 0;
 		};
 		foe.all_pokemon().for_each_replacement(foe_break_out, [&]() {
 			beta = (speed_tie) ?
 int64_t fainted (Team first, Team last, MoveScores & ai_scores, MoveScores & foe_scores, Weather weather, unsigned depth, Evaluate const & evaluate) {
 	// Use pokemon() instead of at_replacement() because it checks whether the
 	// current Pokemon needs to be replaced because it fainted.
-	if (first.pokemon().hp().stat == 0) {
+	if (first.pokemon().stat(Stat::HP).stat == 0) {
 		switchpokemon (first, last, weather);
 		if (Evaluate::win (first) != 0 or Evaluate::win (last) != 0)
 			return Evaluate::win (first) + Evaluate::win (last);
 	}
-	if (last.pokemon().hp().stat == 0) {
+	if (last.pokemon().stat(Stat::HP).stat == 0) {
 		switchpokemon (last, first, weather);
 		if (Evaluate::win (first) != 0 or Evaluate::win (last) != 0)
 			return Evaluate::win (first) + Evaluate::win (last);
 void heal(Pokemon & member, Rational const & rational, bool positive) {
 	if (member.is_fainted())
 		return;
-	unsigned const hp_healed = member.hp().max * rational;
+	unsigned const hp_healed = member.stat(Stat::HP).max * rational;
 	if (positive) {
 		member.apply_healing(std::max(hp_healed, 1u));
 	}

source/move/power.cpp

 
 #include <algorithm>
 #include <cassert>
+#include <numeric>
 
 #include "move.hpp"
 #include "moves.hpp"
 	switch (attacker.move().name) {
 		case Moves::Crush_Grip:
 		case Moves::Wring_Out:
-			return 120u * defender.hp().stat / defender.hp().max + 1;
+			return 120u * defender.hp_ratio() + 1;
 		case Moves::Eruption:
 		case Moves::Water_Spout:
-			return 150u * attacker.hp().stat / attacker.hp().max;
+			return 150u * attacker.hp_ratio();
 		case Moves::Flail:
 		case Moves::Reversal: {
-			unsigned const k = 64u * attacker.hp().stat / attacker.hp().max;
+			unsigned const k = 64u * attacker.hp_ratio();
 			if (k <= 1)
 				return 200;
 			else if (k <= 5)
 		case Moves::Low_Kick:
 			return defender.power_of_mass_based_moves();
 		case Moves::Gyro_Ball: {
-			unsigned const uncapped_power = 25u * defender.spe().stat / attacker.spe().stat + 1;
+			unsigned const uncapped_power = 25u * defender.stat(Stat::SPE).stat / attacker.stat(Stat::SPE).stat + 1;
 			return std::min(uncapped_power, 150u);
 		}
 		case Moves::Ice_Ball:
 		case Moves::Rollout:
 			return attacker.move().momentum_move_power();
 		case Moves::Hidden_Power: {
-			unsigned const u = second_lowest_bit (attacker.hp()) * (1 << 0);	// 1
-			unsigned const v = second_lowest_bit (attacker.atk()) * (1 << 1);	// 2
-			unsigned const w = second_lowest_bit (attacker.def()) * (1 << 2);	// 4
-			unsigned const x = second_lowest_bit (attacker.spe()) * (1 << 3);	// 8
-			unsigned const y = second_lowest_bit (attacker.spa()) * (1 << 4);	// 16
-			unsigned const z = second_lowest_bit (attacker.spd()) * (1 << 5);	// 32
-			return (u + v + w + x + y + z) * 40 / 63 + 30;
+			static constexpr std::array<std::pair<Stat::Stats, unsigned>, 6> stat_and_position {{
+				{ Stat::HP, 0 },
+				{ Stat::ATK, 1 },
+				{ Stat::DEF, 2 },
+				{ Stat::SPE, 3 },
+				{ Stat::SPA, 4 },
+				{ Stat::SPD, 5 }
+			}};
+			auto const sum = [&](unsigned value, std::pair<Stat::Stats, unsigned> const & stat) {
+				return value + second_lowest_bit(attacker.stat(stat.first)) * (1u << stat.second);
+			};
+			return std::accumulate(std::begin(stat_and_position), std::end(stat_and_position), 0u, sum) * 40 / 63 + 30;
 		}
 		case Moves::Magnitude:
 			return variable.value();
 		case Moves::Revenge:
 			return attacker.damaged();
 		case Moves::Brine:
-			return defender.hp().stat <= defender.hp().max / 2;
+			return defender.stat(Stat::HP).stat <= defender.stat(Stat::HP).max / 2;
 		case Moves::Facade:
 			return attacker.status().boosts_facade();
 		case Moves::Ice_Ball:

source/move/use_move.cpp

 			user.activate_rampage();
 			break;
 		case Moves::Pain_Split:
-			equalize(user.hp(), target.hp());
+			equalize(user.stat(Stat::HP), target.stat(Stat::HP));
 			break;
 		case Moves::Perish_Song:
 			user.activate_perish_song();
 			break;
 		case Moves::Present:
 			if (variable.present_heals()) {
-				Stat & hp = target.hp();
+				Stat & hp = target.stat(Stat::HP);
 				hp.stat += 80;
 				hp.stat = std::min(hp.stat, hp.max);
 			}
 }
 
 void belly_drum(ActivePokemon & user) {
-	Stat & hp = user.hp();
+	Stat & hp = user.stat(Stat::HP);
 	if (hp.stat > hp.max / 2 and hp.stat > 1) {
 		hp.stat -= hp.max / 2;
 		user.stat_boost(Stat::ATK, 12);
 void curse(ActivePokemon & user, ActivePokemon & target) {
 	if (is_type(user, Type::Ghost) and !user.ability().blocks_secondary_damage()) {
 		if (!target.is_cursed()) {
-			user.indirect_damage(user.hp().max / 2);
+			user.indirect_damage(user.stat(Stat::HP).max / 2);
 			target.curse();
 		}
 	}
 }
 
 void rest(Pokemon & user) {
-	Stat & hp = user.hp();
+	Stat & hp = user.stat(Stat::HP);
 	if (hp.stat != hp.max) {
 		hp.stat = hp.max;
 		user.status().rest();
 }
 
 void struggle(Pokemon & user) {
-	user.apply_damage(user.hp().max / 4);
+	user.apply_damage(user.stat(Stat::HP).max / 4);
 }
 
 void swap_items(Pokemon & user, Pokemon & target) {

source/pokemon/active_pokemon.cpp

 	enduring = true;
 }
 
+bool ActivePokemon::is_fainted() const {
+	return get_pokemon().is_fainted();
+}
+
 void ActivePokemon::faint() {
-	get_pokemon().hp().stat = 0;
 	get_pokemon().faint();
 }
 
 		return false;
 }
 
-Stat const & ActivePokemon::hp() const {
-	return get_pokemon().hp();
-}
-Stat & ActivePokemon::hp() {
-	return get_pokemon().hp();
+Rational ActivePokemon::hp_ratio() const {
+	return get_pokemon().current_hp();
 }
 
-Stat const & ActivePokemon::atk() const {
-	return get_pokemon().atk();
-}
-Stat & ActivePokemon::atk() {
-	return get_pokemon().atk();
-}
-
-Stat const & ActivePokemon::def() const {
-	return get_pokemon().def();
-}
-Stat & ActivePokemon::def() {
-	return get_pokemon().def();
-}
-
-Stat const & ActivePokemon::spa() const {
-	return get_pokemon().spa();
-}
-Stat & ActivePokemon::spa() {
-	return get_pokemon().spa();
-}
-
-Stat const & ActivePokemon::spd() const {
-	return get_pokemon().spd();
-}
-Stat & ActivePokemon::spd() {
-	return get_pokemon().spd();
-}
-
-Stat const & ActivePokemon::spe() const {
-	return get_pokemon().spe();
-}
-Stat & ActivePokemon::spe() {
-	return get_pokemon().spe();
-}
-
-int ActivePokemon::current_stage(Stat::Stats const stat) const {
-	return stage[stat];
+int ActivePokemon::current_stage(Stat::Stats const stat_index) const {
+	return stage[stat_index];
 }
 
 unsigned ActivePokemon::positive_stat_boosts() const {
 	return stage.accumulate(positive_values);
 }
 
-void ActivePokemon::stat_boost(Stat::Stats const stat, int const n) {
-	stage.boost(stat, n);
+void ActivePokemon::stat_boost(Stat::Stats const stat_index, int const n) {
+	stage.boost(stat_index, n);
 }
 
 void ActivePokemon::stat_boost_physical(int n) {
 void ActivePokemon::use_substitute() {
 	if (!get_pokemon().can_use_substitute())
 		return;
-	bool const created = active_substitute.create(hp().max);
+	bool const created = active_substitute.create(stat(Stat::HP).max);
 	if (created) {
-		indirect_damage(hp().max / 4);
+		indirect_damage(stat(Stat::HP).max / 4);
 	}
 }
 
 	if (fainted) {
 		faint();
 	}
-	else if (hp().stat == 0) {
-		hp().stat = 1;
+	else if (stat(Stat::HP).stat == 0) {
+		stat(Stat::HP).stat = 1;
 	}
 }
 

source/pokemon/active_pokemon.hpp

 		void activate_encore();
 		void increment_encore();
 		void endure();
+		bool is_fainted() const;
 		void faint();
 		bool flash_fire_is_active() const;
 		void activate_flash_fire();
 		bool slow_start_is_active() const;
 		bool sport_is_active(Move const & foe_move) const;
 
-		Stat const & hp() const;
-		Stat & hp();
-		Stat const & atk() const;
-		Stat & atk();
-		Stat const & def() const;
-		Stat & def();
-		Stat const & spa() const;
-		Stat & spa();
-		Stat const & spd() const;
-		Stat & spd();
-		Stat const & spe() const;
-		Stat & spe();
+		template<typename... Args>
+		Stat const & stat(Args && ... args) const {
+			return get_pokemon().stat(std::forward<Args>(args)...);
+		}
+		template<typename... Args>
+		Stat & stat(Args && ... args) {
+			return get_pokemon().stat(std::forward<Args>(args)...);
+		}
+		Rational hp_ratio() const;
 		int current_stage(Stat::Stats stat) const;
 		unsigned positive_stat_boosts() const;
 		template<Stat::Stats stat, typename... Args>

source/pokemon/pokemon.cpp

 	#if defined TECHNICALMACHINE_POKEMON_USE_NICKNAMES
 	nickname(set_nickname);
 	#endif
-	m_hp(species, Stat::HP),
-	m_atk(species, Stat::ATK),
-	m_def(species, Stat::DEF),
-	m_spa(species, Stat::SPA),
-	m_spd(species, Stat::SPD),
-	m_spe(species, Stat::SPE),
+	stats(species),
 
 	m_name(species),
 	m_gender(set_gender),
 }
 
 void Pokemon::calculate_initial_hp () {
-	m_hp.calculate_initial_hp(level());
+	stat(Stat::HP).calculate_initial_hp(level());
 }
 
 void Pokemon::remove_switch() {
 }
 
 void Pokemon::correct_error_in_hp(unsigned const correct_hp_stat) {
-	m_hp.stat = correct_hp_stat;
+	stat(Stat::HP).stat = correct_hp_stat;
 }
 
 Rational Pokemon::current_hp() const {
-	return Rational(hp().stat, hp().max);
+	return Rational(stat(Stat::HP).stat, stat(Stat::HP).max);
 }
 
 unsigned Pokemon::apply_damage(unsigned damage) {
-	damage = std::min(damage, static_cast<unsigned>(m_hp.stat));
-	m_hp.stat -= damage;
+	auto & hp = stat(Stat::HP);
+	damage = std::min(damage, static_cast<unsigned>(hp.stat));
+	hp.stat -= damage;
 	return damage;
 }
 
 void Pokemon::apply_healing(unsigned const amount) {
 	// Should be no risk of overflow. hp.stat has to be at least 16 bits, and no
 	// healing will be anywhere close to that number.
-	assert(hp().stat + amount >= hp().stat);
-	m_hp.stat += amount;
-	m_hp.stat = std::min(hp().stat, hp().max);
+	auto & hp = stat(Stat::HP);
+	assert(hp.stat + amount >= amount);
+	hp.stat += amount;
+	hp.stat = std::min(hp.stat, hp.max);
 }
 
 bool Pokemon::can_confuse_with_chatter() const {
 }
 
 bool Pokemon::can_use_substitute() const {
-	return hp().stat > hp().max / 4;
+	return stat(Stat::HP).stat > stat(Stat::HP).max / 4;
 }
 
 bool is_alternate_form(Species first, Species second) {
 	return m_nature;
 }
 
-Stat const & Pokemon::hp() const {
-	return m_hp;
+Stat const & Pokemon::stat(Stat::Stats const index_stat) const {
+	return stats[index_stat];
 }
-Stat & Pokemon::hp() {
-	return m_hp;
-}
-Stat const & Pokemon::atk() const {
-	return m_atk;
-}
-Stat & Pokemon::atk() {
-	return m_atk;
-}
-Stat const & Pokemon::def() const {
-	return m_def;
-}
-Stat & Pokemon::def() {
-	return m_def;
-}
-Stat const & Pokemon::spa() const {
-	return m_spa;
-}
-Stat & Pokemon::spa() {
-	return m_spa;
-}
-Stat const & Pokemon::spd() const {
-	return m_spd;
-}
-Stat & Pokemon::spd() {
-	return m_spd;
-}
-Stat const & Pokemon::spe() const {
-	return m_spe;
-}
-Stat & Pokemon::spe() {
-	return m_spe;
+Stat & Pokemon::stat(Stat::Stats const index_stat) {
+	return stats[index_stat];
 }
 
 Status const & Pokemon::status() const {
 	return m_will_be_replaced;
 }
 void Pokemon::faint() {
+	stat(Stat::HP).stat = 0;
 	m_will_be_replaced = true;
 }
 void Pokemon::reset_replacing() {
 	return static_cast<hash_type>(m_name) + number_of_species *
 			(m_item.name + Item::END *
 			(m_status.hash() + Status::max_hash() *
-			((hp().stat - 1u) + hp().max *	// - 1 because you can't have 0 HP
+			((stat(Stat::HP).stat - 1u) + stat(Stat::HP).max *	// - 1 because you can't have 0 HP
 			(seen.hash() + seen.max_hash() *
 			move.hash()))));
 }
 
 Pokemon::hash_type Pokemon::max_hash() const {
-	return number_of_species * Item::END * Status::max_hash() * hp().max * seen.max_hash() * move.max_hash();
+	return number_of_species * Item::END * Status::max_hash() * stat(Stat::HP).max * seen.max_hash() * move.max_hash();
 }
 
 bool operator== (Pokemon const & lhs, Pokemon const & rhs) {
 	return lhs.move == rhs.move and
 			lhs.m_name == rhs.m_name and
 			lhs.m_status == rhs.m_status and
-			lhs.hp().stat == rhs.hp().stat and
+			lhs.stat(Stat::HP).stat == rhs.stat(Stat::HP).stat and
 			lhs.m_item == rhs.m_item and
 			lhs.seen == rhs.seen;
 }
 	return iv % 2;
 }
 
-constexpr unsigned hidden_power_type_helper(unsigned const iv, unsigned const n) {
-	return lowest_bit(iv) << n;
-}
-
 }	// unnamed namespace
 
 Type::Types Pokemon::calculate_hidden_power_type() const {
-	unsigned const a = hidden_power_type_helper(hp().iv, 0);
-	unsigned const b = hidden_power_type_helper(atk().iv, 1);
-	unsigned const c = hidden_power_type_helper(def().iv, 2);
-	unsigned const d = hidden_power_type_helper(spe().iv, 3);
-	unsigned const e = hidden_power_type_helper(spa().iv, 4);
-	unsigned const f = hidden_power_type_helper(spd().iv, 5);
-	unsigned const index = (a + b + c + d + e + f) * 15 / 63;
+	static constexpr std::pair<Stat::Stats, unsigned> modifiers[] = {
+		{ Stat::HP, 0 },
+		{ Stat::ATK, 1 },
+		{ Stat::DEF, 2 },
+		{ Stat::SPE, 3 },
+		{ Stat::SPA, 4 },
+		{ Stat::SPD, 5 }
+	};
+	auto const sum = [&](unsigned value, std::pair<Stat::Stats, unsigned> const & pair) {
+		return value + (lowest_bit(stat(pair.first).iv) << pair.second);
+	};
+	auto const index = std::accumulate(std::begin(modifiers), std::end(modifiers), 0u, sum) * 15 / 63;
 	constexpr static Type::Types lookup [] = {
 		Type::Fighting,
 		Type::Flying,
 		Type::Dragon,
 		Type::Dark
 	};
-	return lookup [index];
+	return lookup[index];
 }
 
 unsigned Pokemon::power_of_mass_based_moves() const {

source/pokemon/pokemon.hpp

 #include "../move/collection.hpp"
 
 #include "../stat/nature.hpp"
-#include "../stat/stat.hpp"
+#include "../stat/stats.hpp"
 
 #include "../type/collection.hpp"
 
 		Item & item();
 		Nature const & nature() const;
 		Nature & nature();
-		Stat const & hp() const;
-		Stat & hp();
+		Stat const & stat(Stat::Stats index_stat) const;
+		Stat & stat(Stat::Stats index_stat);
 		Rational current_hp() const;
-		Stat const & atk() const;
-		Stat & atk();
-		Stat const & def() const;
-		Stat & def();
-		Stat const & spa() const;
-		Stat & spa();
-		Stat const & spd() const;
-		Stat & spd();
-		Stat const & spe() const;
-		Stat & spe();
 		Status const & status() const;
 		Status & status();
 		TypeCollection const & type() const;
 		friend bool illegal_inequality_check(Pokemon const & lhs, Pokemon const & rhs);
 
 		MoveCollection move;
-		TypeCollection current_type;
 		
 	private:
 		#if defined TECHNICALMACHINE_POKEMON_USE_NICKNAMES
 		std::string nickname;
 		#endif
+		TypeCollection current_type;
 
-		Stat m_hp;
-		Stat m_atk;
-		Stat m_def;
-		Stat m_spa;
-		Stat m_spd;
-		Stat m_spe;
+		Stats stats;
 
 		Species m_name;
 		Item m_item;

source/stat/stat.cpp

 #include "stat.hpp"
 
 #include <cstdint>
+#include <type_traits>
 
 #include "nature.hpp"
 
 
 unsigned initial_generic_stat(Stat const & stat, unsigned level);
 
-Rational attack_ability_modifier(Pokemon const & attacker, bool slow_start, Weather const & weather);
-Rational attack_item_modifier(Pokemon const & attacker);
+template<Stat::Stats stat>
+Rational ability_modifier(ActivePokemon const & pokemon, Weather const & weather);
+template<>
+Rational ability_modifier<Stat::ATK>(ActivePokemon const & attacker, Weather const & weather) {
+	switch (attacker.ability().name) {
+		case Ability::FLOWER_GIFT:
+			return weather.sun() ? Rational(3, 2) : Rational(1);
+		case Ability::GUTS:
+			return (!attacker.status().is_clear()) ? Rational(3, 2) : Rational(1);
+		case Ability::HUSTLE:
+			return Rational(3, 2);
+		case Ability::HUGE_POWER:
+		case Ability::PURE_POWER:
+			return Rational(2);
+		case Ability::SLOW_START:
+			return attacker.slow_start_is_active() ? Rational(1, 2) : Rational(1);
+		default:
+			return Rational(1);
+	}
+}
+template<>
+Rational ability_modifier<Stat::SPA>(ActivePokemon const & pokemon, Weather const & weather) {
+	return pokemon.ability().boosts_special_attack(weather) ? Rational(3, 2) : Rational(1);
+}
+template<>
+Rational ability_modifier<Stat::DEF>(ActivePokemon const & defender, Weather const &) {
+	return defender.ability().boosts_defense(defender.status()) ? Rational(3, 2) : Rational(1);
+}
+template<>
+Rational ability_modifier<Stat::SPD>(ActivePokemon const & pokemon, Weather const & weather) {
+	return pokemon.ability().boosts_special_defense(weather) ? Rational(3, 2) : Rational(1);
+}
+template<>
+Rational ability_modifier<Stat::SPE>(ActivePokemon const & pokemon, Weather const & weather) {
+	switch (pokemon.ability().name) {
+		case Ability::CHLOROPHYLL:
+			return weather.sun() ? Rational(2) : Rational(1);
+		case Ability::SWIFT_SWIM:
+			return weather.rain() ? Rational(2) : Rational(1);
+		case Ability::UNBURDEN:
+			return pokemon.item().was_lost() ? Rational(2) : Rational(1);
+		case Ability::QUICK_FEET:
+			return (!pokemon.status().is_clear()) ? Rational(3, 2) : Rational(1);
+		case Ability::SLOW_START:
+			return pokemon.slow_start_is_active() ? Rational(1, 2) : Rational(1);
+		default:
+			return Rational(1);
+	}
+}
 
-Rational special_attack_ability_modifier(Ability const & ability, Weather const & weather);
-Rational special_attack_item_modifier(Pokemon const & attacker);
 
-Rational defense_ability_modifier(Pokemon const & pokemon);
-Rational defense_item_modifier(Pokemon const & defender);
+template<Stat::Stats stat>
+Rational item_modifier(Pokemon const & pokemon);
+template<>
+Rational item_modifier<Stat::ATK>(Pokemon const & attacker) {
+	switch (attacker.item().name) {
+		case Item::CHOICE_BAND:
+			return Rational(3, 2);
+		case Item::LIGHT_BALL:
+			return attacker.is_boosted_by_light_ball() ? Rational(2) : Rational(1);
+		case Item::THICK_CLUB:
+			return attacker.is_boosted_by_thick_club() ? Rational(2, 1) : Rational(1);
+		default:
+			return Rational(1);
+	}
+}
+template<>
+Rational item_modifier<Stat::SPA>(Pokemon const & attacker) {
+	switch (attacker.item().name) {
+		case Item::SOUL_DEW:
+			return attacker.is_boosted_by_soul_dew() ? Rational(3, 2) : Rational(1);
+		case Item::CHOICE_SPECS:
+			return Rational(3, 2);
+		case Item::DEEPSEATOOTH:
+			return attacker.is_boosted_by_deepseatooth() ? Rational(2) : Rational(1);
+		case Item::LIGHT_BALL:
+			return attacker.is_boosted_by_light_ball() ? Rational(2) : Rational(1);
+		default:
+			return Rational(1);
+	}
+}
+template<>
+Rational item_modifier<Stat::DEF>(Pokemon const & defender) {
+	return (defender.item().name == Item::METAL_POWDER and defender.is_boosted_by_metal_powder()) ?
+		Rational(3, 2) :
+		Rational(1);
+}
+template<>
+Rational item_modifier<Stat::SPD>(Pokemon const & defender) {
+	switch (defender.item().name) {
+		case Item::DEEPSEASCALE:
+			return defender.is_boosted_by_deepseascale() ? Rational(2) : Rational(1);
+		case Item::METAL_POWDER:
+			return defender.is_boosted_by_metal_powder() ? Rational(3, 2) : Rational(1);
+		case Item::SOUL_DEW:
+			return defender.is_boosted_by_soul_dew() ? Rational(3, 2) : Rational(1);
+		default:
+			return Rational(1);
+	}
+}
+template<>
+Rational item_modifier<Stat::SPE>(Pokemon const & pokemon) {
+	switch (pokemon.item().name) {
+		case Item::QUICK_POWDER:
+			return pokemon.is_boosted_by_quick_powder() ? Rational(2) : Rational(1);
+		case Item::CHOICE_SCARF:
+			return Rational(3, 2);
+		case Item::MACHO_BRACE:
+		case Item::POWER_ANKLET:
+		case Item::POWER_BAND:
+		case Item::POWER_BELT:
+		case Item::POWER_BRACER:
+		case Item::POWER_LENS:
+		case Item::POWER_WEIGHT:
+			return Rational(1, 2);
+		default:
+			return Rational(1);
+	}
+}
 
-Rational special_defense_ability_modifier(Ability const & ability, Weather const & weather);
-Rational special_defense_item_modifier(Pokemon const & defender);
 Rational special_defense_sandstorm_boost(ActivePokemon const & defender, Weather const & weather);
 
-Rational speed_ability_modifier(ActivePokemon const & pokemon, Weather const & weather);
-Rational speed_item_modifier(Pokemon const & pokemon);
 unsigned paralysis_speed_divisor (Pokemon const & pokemon);
 unsigned tailwind_speed_multiplier (Team const & team);
 
+template<Stat::Stats stat>
+class StatTraits;
+
+template<>
+class StatTraits<Stat::ATK> {
+	public:
+		static constexpr bool is_physical = true;
+		static constexpr Stat::Stats other = Stat::DEF;
+};
+template<>
+class StatTraits<Stat::DEF> {
+	public:
+		static constexpr bool is_physical = true;
+		static constexpr Stat::Stats other = Stat::ATK;
+};
+template<>
+class StatTraits<Stat::SPA> {
+	public:
+		static constexpr bool is_physical = false;
+};
+template<>
+class StatTraits<Stat::SPD> {
+	public:
+		static constexpr bool is_physical = false;
+};
+template<>
+class StatTraits<Stat::SPE> {
+	public:
+		static constexpr bool is_physical = false;
+};
+
+template<Stat::Stats stat>
+typename std::enable_if<StatTraits<stat>::is_physical, unsigned>::type
+calculate_initial_stat(ActivePokemon const & pokemon) {
+	constexpr auto other = StatTraits<stat>::other;
+	return !pokemon.power_trick_is_active() ? initial_stat<stat>(pokemon) : initial_stat<other>(pokemon);
+}
+template<Stat::Stats stat>
+typename std::enable_if<!StatTraits<stat>::is_physical, unsigned>::type
+calculate_initial_stat(ActivePokemon const & pokemon) {
+	return initial_stat<stat>(pokemon);
+}
+
+template<Stat::Stats stat>
+void calculate_common_offensive_stat(ActivePokemon & pokemon, Weather const & weather) {
+	auto attack = calculate_initial_stat<stat>(pokemon);
+	attack *= pokemon.stage_modifier<stat>(pokemon.critical_hit());
+
+	attack *= ability_modifier<stat>(pokemon, weather);
+	attack *= item_modifier<stat>(pokemon);
+	
+	pokemon.stat(stat).stat = std::max(attack, 1u);
+}
+
 }	// unnamed namespace
 
 Stat::Stat (Species name, Stats stat_name) :
 	stat = max;
 }
 
+template<Stat::Stats stat>
+unsigned initial_stat(Pokemon const & pokemon) {
+	return initial_generic_stat(pokemon.stat(stat), pokemon.level()) * pokemon.nature().boost<stat>();
+}
 template<>
 unsigned initial_stat<Stat::HP>(Pokemon const & pokemon) {
-	return (pokemon.hp().base > 1) ? (initial_generic_stat(pokemon.hp(), pokemon.level()) + pokemon.level() + 5) : 1;
+	Stat const & hp = pokemon.stat(Stat::HP);
+	return (hp.base > 1) ? (initial_generic_stat(hp, pokemon.level()) + pokemon.level() + 5) : 1;
 }
-template<>
-unsigned initial_stat<Stat::ATK>(Pokemon const & pokemon) {
-	return initial_generic_stat(pokemon.atk(), pokemon.level()) * pokemon.nature().boost<Stat::ATK>();
-}
-template<>
-unsigned initial_stat<Stat::SPA>(Pokemon const & pokemon) {
-	return initial_generic_stat(pokemon.spa(), pokemon.level()) * pokemon.nature().boost<Stat::SPA>();
-}
-template<>
-unsigned initial_stat<Stat::DEF>(Pokemon const & pokemon) {
-	return initial_generic_stat(pokemon.def(), pokemon.level()) * pokemon.nature().boost<Stat::DEF>();
-}
-template<>
-unsigned initial_stat<Stat::SPD>(Pokemon const & pokemon) {
-	return initial_generic_stat(pokemon.spd(), pokemon.level()) * pokemon.nature().boost<Stat::SPD>();
-}
-template<>
-unsigned initial_stat<Stat::SPE>(Pokemon const & pokemon) {
-	return initial_generic_stat(pokemon.spe(), pokemon.level()) * pokemon.nature().boost<Stat::SPE>();
-}
+template unsigned initial_stat<Stat::ATK>(Pokemon const & pokemon);
+template unsigned initial_stat<Stat::SPA>(Pokemon const & pokemon);
+template unsigned initial_stat<Stat::DEF>(Pokemon const & pokemon);
+template unsigned initial_stat<Stat::SPD>(Pokemon const & pokemon);
+template unsigned initial_stat<Stat::SPE>(Pokemon const & pokemon);
 
 void calculate_attacking_stat (ActivePokemon & attacker, Weather const & weather) {
 	if (attacker.move().is_physical())
 }
 
 void calculate_attack(ActivePokemon & attacker, Weather const & weather) {
-	attacker.atk().stat = !attacker.power_trick_is_active() ? initial_stat<Stat::ATK>(attacker) : initial_stat<Stat::DEF>(attacker);
-
-	attacker.atk().stat *= attacker.stage_modifier<Stat::ATK>(attacker.critical_hit());
-
-	attacker.atk().stat *= attack_ability_modifier(attacker, attacker.slow_start_is_active(), weather);
-	attacker.atk().stat *= attack_item_modifier(attacker);
-	
-	if (attacker.atk().stat == 0)
-		attacker.atk().stat = 1;
+	calculate_common_offensive_stat<Stat::ATK>(attacker, weather);
 }
 
 void calculate_special_attack (ActivePokemon & attacker, Weather const & weather) {
-	attacker.spa().stat = initial_stat<Stat::SPA>(attacker);
-
-	attacker.spa().stat *= attacker.stage_modifier<Stat::SPA>(attacker.critical_hit());
-
-	attacker.spa().stat *= special_attack_ability_modifier(attacker.ability(), weather);
-	attacker.spa().stat *= special_attack_item_modifier(attacker);
-
-	if (attacker.spa().stat == 0)
-		attacker.spa().stat = 1;
+	calculate_common_offensive_stat<Stat::SPA>(attacker, weather);
 }
 
 void calculate_defending_stat (ActivePokemon const & attacker, ActivePokemon & defender, Weather const & weather) {
 	if (attacker.move().is_physical())
-		calculate_defense(defender, attacker.critical_hit(), attacker.move().is_self_KO());
+		calculate_defense(defender, weather, attacker.critical_hit(), attacker.move().is_self_KO());
 	else
 		calculate_special_defense(defender, weather, attacker.critical_hit());
 }
 
-void calculate_defense (ActivePokemon & defender, bool ch, bool is_self_KO) {
-	defender.def().stat = !defender.power_trick_is_active() ? initial_stat<Stat::DEF>(defender) : initial_stat<Stat::ATK>(defender);
+void calculate_defense (ActivePokemon & defender, Weather const & weather, bool ch, bool is_self_KO) {
+	constexpr auto stat = Stat::DEF;
+	auto defense = calculate_initial_stat<stat>(defender);
 
-	defender.def().stat *= defender.stage_modifier<Stat::DEF>(ch);
+	defense *= defender.stage_modifier<stat>(ch);
 	
-	defender.def().stat *= defense_ability_modifier(defender);
-	defender.def().stat *= defense_item_modifier(defender);
+	defense *= ability_modifier<stat>(defender, weather);
+	defense *= item_modifier<stat>(defender);
 	
 	if (is_self_KO)
-		defender.def().stat /= 2;
+		defense /= 2;
 
-	if (defender.def().stat == 0)
-		defender.def().stat = 1;
+	defender.stat(stat).stat = std::max(defense, 1u);
 }
 
 void calculate_special_defense (ActivePokemon & defender, Weather const & weather, bool ch) {
-	defender.spd().stat = initial_stat<Stat::SPD>(defender);
+	constexpr auto stat = Stat::SPD;
+	auto defense = calculate_initial_stat<stat>(defender);
 	
-	defender.spd().stat *= defender.stage_modifier<Stat::SPD>(ch);
+	defense *= defender.stage_modifier<Stat::SPD>(ch);
 
-	defender.spd().stat *= special_defense_ability_modifier(defender.ability(), weather);	
-	defender.spd().stat *= special_defense_item_modifier(defender);
+	defense *= ability_modifier<Stat::SPD>(defender, weather);	
+	defense *= item_modifier<Stat::SPD>(defender);
 	
-	defender.spd().stat *= special_defense_sandstorm_boost(defender, weather);
+	defense *= special_defense_sandstorm_boost(defender, weather);
 	
-	if (defender.spd().stat == 0)
-		defender.spd().stat = 1;
+	defender.stat(stat).stat = std::max(defense, 1u);
 }
 
 void calculate_speed (Team & team, Weather const & weather) {
+	constexpr auto stat = Stat::SPE;
 	auto & pokemon = team.pokemon();
-	pokemon.spe().stat = initial_stat<Stat::SPE>(pokemon);
+	auto speed = calculate_initial_stat<stat>(pokemon);
 	
-	pokemon.spe().stat *= pokemon.stage_modifier<Stat::SPE>();
+	speed *= pokemon.stage_modifier<stat>();
 
-	pokemon.spe().stat *= speed_ability_modifier(pokemon, weather);
-	pokemon.spe().stat *= speed_item_modifier(pokemon);
+	speed *= ability_modifier<stat>(pokemon, weather);
+	speed *= item_modifier<stat>(pokemon);
 	
-	pokemon.spe().stat /= paralysis_speed_divisor (pokemon);
+	speed /= paralysis_speed_divisor (pokemon);
 	
-	pokemon.spe().stat *= tailwind_speed_multiplier (team);
+	speed *= tailwind_speed_multiplier (team);
 
-	if (pokemon.spe().stat == 0)
-		pokemon.spe().stat = 1;
+	pokemon.stat(stat).stat = std::max(speed, 1u);
 }
 
 void order (Team & team1, Team & team2, Weather const & weather, Team* & faster, Team* & slower) {
 }
 
 void faster_pokemon (Team & team1, Team & team2, Weather const & weather, Team* & faster, Team* & slower) {
-	if (team1.pokemon().spe().stat > team2.pokemon().spe().stat) {
+	auto const speed1 = team1.pokemon().stat(Stat::SPE).stat;
+	auto const speed2 = team2.pokemon().stat(Stat::SPE).stat;
+	if (speed1 > speed2) {
 		faster = &team1;
 		slower = &team2;
 	}
-	else if (team1.pokemon().spe().stat < team2.pokemon().spe().stat) {
+	else if (speed1 < speed2) {
 		faster = &team2;
 		slower = &team1;
 	}
 	return (2u * stat.base + stat.iv + stat.ev.points()) * level / 100 + 5;
 }
 
-Rational attack_ability_modifier(Pokemon const & attacker, bool slow_start, Weather const & weather) {
-	switch (attacker.ability().name) {
-		case Ability::FLOWER_GIFT:
-			return weather.sun() ? Rational(3, 2) : Rational(1);
-		case Ability::GUTS:
-			return (!attacker.status().is_clear()) ? Rational(3, 2) : Rational(1);
-		case Ability::HUSTLE:
-			return Rational(3, 2);
-		case Ability::HUGE_POWER:
-		case Ability::PURE_POWER:
-			return Rational(2);
-		case Ability::SLOW_START:
-			return slow_start ? Rational(1, 2) : Rational(1);
-		default:
-			return Rational(1);
-	}
-}
-
-Rational attack_item_modifier(Pokemon const & attacker) {
-	switch (attacker.item().name) {
-		case Item::CHOICE_BAND:
-			return Rational(3, 2);
-		case Item::LIGHT_BALL:
-			return attacker.is_boosted_by_light_ball() ? Rational(2) : Rational(1);
-		case Item::THICK_CLUB:
-			return attacker.is_boosted_by_thick_club() ? Rational(2, 1) : Rational(1);
-		default:
-			return Rational(1);
-	}
-}
-
-Rational special_attack_ability_modifier(Ability const & ability, Weather const & weather) {
-	return !ability.boosts_special_attack(weather) ? Rational(1) : Rational(3, 2);
-}
-
-Rational special_attack_item_modifier(Pokemon const & attacker) {
-	switch (attacker.item().name) {
-		case Item::SOUL_DEW:
-			return attacker.is_boosted_by_soul_dew() ? Rational(3, 2) : Rational(1);
-		case Item::CHOICE_SPECS:
-			return Rational(3, 2);
-		case Item::DEEPSEATOOTH:
-			return attacker.is_boosted_by_deepseatooth() ? Rational(2) : Rational(1);
-		case Item::LIGHT_BALL:
-			return attacker.is_boosted_by_light_ball() ? Rational(2) : Rational(1);
-		default:
-			return Rational(1);
-	}
-}
-
-Rational defense_ability_modifier(Pokemon const & defender) {
-	return defender.ability().boosts_defense(defender.status()) ? Rational(3, 2) : Rational(1);
-}
-
-Rational defense_item_modifier(Pokemon const & defender) {
-	return (defender.item().name == Item::METAL_POWDER and defender.is_boosted_by_metal_powder()) ?
-		Rational(3, 2) :
-		Rational(1);
-}
-
-Rational special_defense_ability_modifier(Ability const & ability, Weather const & weather) {
-	return ability.boosts_special_defense(weather) ? Rational(3, 2) : Rational(1);
-}
-
-Rational special_defense_item_modifier(Pokemon const & defender) {
-	switch (defender.item().name) {
-		case Item::DEEPSEASCALE:
-			return defender.is_boosted_by_deepseascale() ? Rational(2) : Rational(1);
-		case Item::METAL_POWDER:
-			return defender.is_boosted_by_metal_powder() ? Rational(3, 2) : Rational(1);
-		case Item::SOUL_DEW:
-			return defender.is_boosted_by_soul_dew() ? Rational(3, 2) : Rational(1);
-		default:
-			return Rational(1);
-	}
-}
-
 Rational special_defense_sandstorm_boost(ActivePokemon const & defender, Weather const & weather) {
 	return (is_type(defender, Type::Rock) and weather.sand()) ? Rational(3, 2) : Rational(1);
 }
 
-Rational speed_ability_modifier(ActivePokemon const & pokemon, Weather const & weather) {
-	switch (pokemon.ability().name) {
-		case Ability::CHLOROPHYLL:
-			return weather.sun() ? Rational(2) : Rational(1);
-		case Ability::SWIFT_SWIM:
-			return weather.rain() ? Rational(2) : Rational(1);
-		case Ability::UNBURDEN:
-			return pokemon.item().was_lost() ? Rational(2) : Rational(1);
-		case Ability::QUICK_FEET:
-			return (!pokemon.status().is_clear()) ? Rational(3, 2) : Rational(1);
-		case Ability::SLOW_START:
-			return pokemon.slow_start_is_active() ? Rational(1, 2) : Rational(1);
-		default:
-			return Rational(1);
-	}
-}
-
-Rational speed_item_modifier(Pokemon const & pokemon) {
-	switch (pokemon.item().name) {
-		case Item::QUICK_POWDER:
-			return pokemon.is_boosted_by_quick_powder() ? Rational(2) : Rational(1);
-		case Item::CHOICE_SCARF:
-			return Rational(3, 2);
-		case Item::MACHO_BRACE:
-		case Item::POWER_ANKLET:
-		case Item::POWER_BAND:
-		case Item::POWER_BELT:
-		case Item::POWER_BRACER:
-		case Item::POWER_LENS:
-		case Item::POWER_WEIGHT:
-			return Rational(1, 2);
-		default:
-			return Rational(1);
-	}
-}
-
 unsigned paralysis_speed_divisor (Pokemon const & pokemon) {
 	return pokemon.status().lowers_speed(pokemon.ability()) ? 4 : 1;
 }

source/stat/stat.hpp

 		void calculate_initial_hp (uint8_t const level);
 };
 
+inline constexpr std::initializer_list<Stat::Stats> regular_stats() {
+	return { Stat::HP, Stat::ATK, Stat::DEF, Stat::SPA, Stat::SPD, Stat::SPE };
+}
 inline constexpr std::initializer_list<Stat::Stats> boostable_stats() {
 	return { Stat::ATK, Stat::DEF, Stat::SPA, Stat::SPD, Stat::SPE, Stat::ACC, Stat::EVA };
 }
 
 template<Stat::Stats>
 unsigned initial_stat(Pokemon const & pokemon);
+extern template unsigned initial_stat<Stat::ATK>(Pokemon const & pokemon);
+extern template unsigned initial_stat<Stat::SPA>(Pokemon const & pokemon);
+extern template unsigned initial_stat<Stat::DEF>(Pokemon const & pokemon);
+extern template unsigned initial_stat<Stat::SPD>(Pokemon const & pokemon);
+extern template unsigned initial_stat<Stat::SPE>(Pokemon const & pokemon);
+
 
 void calculate_attacking_stat (ActivePokemon & attacker, Weather const & weather);
 void calculate_attack(ActivePokemon & attacker, Weather const & weather);
 void calculate_special_attack(ActivePokemon & attacker, Weather const & weather);
 
 void calculate_defending_stat (ActivePokemon const & attacker, ActivePokemon & defender, Weather const & weather);
-void calculate_defense (ActivePokemon & defender, bool ch = false, bool is_self_KO = false);
+void calculate_defense (ActivePokemon & defender, Weather const & weather, bool ch = false, bool is_self_KO = false);
 void calculate_special_defense (ActivePokemon & defender, Weather const & weather, bool ch = false);
 
 void calculate_speed (Team & team, Weather const & weather);

source/stat/stats.cpp

+// All 'normal' stats that a Pokemon has
+// Copyright (C) 2012 David Stone
+//
+// This file is part of Technical Machine.
+//
+// Technical Machine is free software: you can redistribute it and / or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "stats.hpp"
+
+namespace technicalmachine {
+
+Stats::Stats(Species const species):
+	stats({{
+		Stat(species, Stat::HP),
+		Stat(species, Stat::ATK),
+		Stat(species, Stat::DEF),
+		Stat(species, Stat::SPA),
+		Stat(species, Stat::SPD),
+		Stat(species, Stat::SPE)
+	}}) {
+}
+
+// The indexing requires a +1 because I have the first stat in the array, HP,
+// set to -1 in the enum. This allowed me to index stat boosting without having
+// to offset the index.
+Stat const & Stats::operator[](Stat::Stats const stat) const {
+	return stats[static_cast<size_t>(stat + 1)];
+}
+Stat & Stats::operator[](Stat::Stats const stat) {
+	return stats[static_cast<size_t>(stat + 1)];
+}
+
+
+}	// namespace technicalmachine

source/stat/stats.hpp

+// All 'normal' stats that a Pokemon has
+// Copyright (C) 2012 David Stone
+//
+// This file is part of Technical Machine.
+//
+// Technical Machine is free software: you can redistribute it and / or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef STAT__STATS_HPP_
+#define STAT__STATS_HPP_
+
+#include <array>
+#include "stat.hpp"
+#include "../pokemon/species_forward.hpp"
+
+namespace technicalmachine {
+
+class Stats {
+	public:
+		Stats(Species species);
+		Stat const & operator[](Stat::Stats stat) const;
+		Stat & operator[](Stat::Stats stat);
+	private:
+		std::array<Stat, 6> stats;
+};
+
+}	// namespace technicalmachine
+#endif	// STAT__STATS_HPP_

source/switch.cpp

 	auto & pokemon = switcher.pokemon();
 	pokemon.reset_switch();
 
-	if (pokemon.hp().stat > 0) {
+	if (pokemon.stat(Stat::HP).stat > 0) {
 		pokemon.switch_pokemon();
 	}
 	else {
 			return;
 	}
 	EntryHazards::apply(switcher, weather);
-	if (pokemon.hp().stat > 0)
+	if (pokemon.stat(Stat::HP).stat > 0)
 		Ability::activate_on_switch (pokemon, other.pokemon(), weather);
 	pokemon.switch_in();
 }
 			}
 			output += std::to_string(stat.ev.value()) + " " + stat_name;
 		};
-		add_stat(member.hp(), "HP");
-		add_stat(member.atk(), "Atk");
-		add_stat(member.def(), "Def");
-		add_stat(member.spa(), "SpA");
-		add_stat(member.spd(), "SpD");
-		add_stat(member.spe(), "Spe");
+		static std::pair<Stat::Stats, std::string> const stats [] = {
+			{ Stat::HP, "HP" },
+			{ Stat::ATK, "Atk" },
+			{ Stat::DEF, "Def" },
+			{ Stat::SPA, "SpA" },
+			{ Stat::SPD, "SpD" },
+			{ Stat::SPE, "Spe" },
+		};
+		for (auto const stat : stats) {
+			add_stat(member.stat(stat.first), stat.second);
+		}
 		output += '\n';
 		member.move.for_each_regular_move([& output](Move const & move) {
 			output += "\t- " + move.to_string() + '\n';

source/team_predictor/ev_optimizer/combine.cpp

 	auto const & defensive = d.container.at(it->first);
 	auto const & offensive = o.container.at(it->first);
 	auto const & speed = s.container.at(it->first);
-	pokemon.hp().ev.set_value(defensive.hp);
+	pokemon.stat(Stat::HP).ev.set_value(defensive.hp);
 	pokemon.calculate_initial_hp();
-	pokemon.atk().ev.set_value(offensive.attack);
-	pokemon.def().ev.set_value(defensive.defense);
-	pokemon.spa().ev.set_value(offensive.special_attack);
-	pokemon.spd().ev.set_value(defensive.special_defense);
-	pokemon.spe().ev.set_value(speed);
+	pokemon.stat(Stat::ATK).ev.set_value(offensive.attack);
+	pokemon.stat(Stat::DEF).ev.set_value(defensive.defense);
+	pokemon.stat(Stat::SPA).ev.set_value(offensive.special_attack);
+	pokemon.stat(Stat::SPD).ev.set_value(defensive.special_defense);
+	pokemon.stat(Stat::SPE).ev.set_value(speed);
 	pokemon.nature().name = it->first;
 }
 

source/team_predictor/ev_optimizer/defensive.cpp

 
 unsigned defensive_evs_available(Pokemon const & pokemon) {
 	constexpr unsigned max_total_evs = 510;
-	return max_total_evs - pokemon.atk().ev.value() + pokemon.spa().ev.value() + pokemon.spe().ev.value();
+	auto used_evs = 0u;
+	for (auto const stat : { Stat::ATK, Stat::SPA, Stat::SPE }) {
+		used_evs += pokemon.stat(stat).ev.value();
+	}
+	return max_total_evs - used_evs;
 }
 
 Divided divide_natures(DefensiveEVs::BestPerNature const & container) {

source/team_predictor/ev_optimizer/defensive_data_point.cpp

 
 void DataPoint::update_pokemon(Pokemon & pokemon) const {
 	pokemon.nature() = nature;
-	pokemon.hp().ev.set_value(hp);
-	pokemon.def().ev.set_value(defense);
-	pokemon.spd().ev.set_value(special_defense);
+	pokemon.stat(Stat::HP).ev.set_value(hp);
+	pokemon.stat(Stat::DEF).ev.set_value(defense);
+	pokemon.stat(Stat::SPD).ev.set_value(special_defense);
 }
 
 bool DataPoint::affects_defensive_stat(bool const boost) const {

source/team_predictor/ev_optimizer/ev_optimizer.cpp

 constexpr unsigned max_evs = 508;
 
 unsigned ev_sum(Pokemon const & pokemon) {
-	return pokemon.hp().ev.value() + pokemon.atk().ev.value() + pokemon.def().ev.value()
-			+ pokemon.spa().ev.value() + pokemon.spd().ev.value() + pokemon.spe().ev.value();
+	auto const ev_sum = [&](unsigned const sum, Stat::Stats const stat) {
+		return sum + pokemon.stat(stat).ev.value();
+	};
+	return std::accumulate(std::begin(regular_stats()), std::end(regular_stats()), 0u, ev_sum);
 }
 
 void add_non_full_stats(std::vector<Stat *> & stats, Stat & stat);
 	// the process in case a stat is overfilled.
 	while (ev_sum(pokemon) < max_evs) {
 		std::vector<Stat *> stats;
-		add_non_full_stats(stats, pokemon.hp());
-		add_non_full_stats(stats, pokemon.atk());
-		add_non_full_stats(stats, pokemon.def());
-		add_non_full_stats(stats, pokemon.spa());
-		add_non_full_stats(stats, pokemon.spd());
-		add_non_full_stats(stats, pokemon.spe());
-	
+		for (auto const stat : regular_stats()) {
+			add_non_full_stats(stats, pokemon.stat(stat));
+		}
 		unsigned const extra_evs = max_evs - ev_sum(pokemon);
 		std::vector<uint8_t> shuffled(extra_evs + stats.size() - 1, 1);
 		std::fill(std::begin(shuffled), std::begin(shuffled) + static_cast<int>(stats.size()) - 1, 0);

source/team_predictor/ev_optimizer/offensive.cpp

 		remove_individual_unused(container, [](Container::iterator it) {
 			return !Nature(it->first).lowers_stat<Stat::ATK>();
 		});
-		pokemon.atk().ev.set_value(0);
+		pokemon.stat(Stat::ATK).ev.set_value(0);
 	}
 	bool const is_special = has_special_move(pokemon);
 	if (!is_special) {
 			return (is_physical and !Nature(it->first).lowers_stat<Stat::SPA>())
 					or Nature(it->first).boosts_stat<Stat::SPA>();
 		});
-		pokemon.spa().ev.set_value(0);
+		pokemon.stat(Stat::SPA).ev.set_value(0);
 	}
 	if (!is_physical and !is_special) {
 		pokemon.nature().name = Nature::CALM;
 	for (auto it = std::begin(container); it != std::end(container);) {
 		OffensiveStats & stats = it->second;
 		pokemon.nature() = it->first;
-		boost::optional<unsigned> const atk = reset_stat<Stat::ATK>(pokemon, pokemon.atk().ev, initial_atk);
-		boost::optional<unsigned> const spa = reset_stat<Stat::SPA>(pokemon, pokemon.spa().ev, initial_spa);
+		boost::optional<unsigned> const atk = reset_stat<Stat::ATK>(pokemon, pokemon.stat(Stat::ATK).ev, initial_atk);
+		boost::optional<unsigned> const spa = reset_stat<Stat::SPA>(pokemon, pokemon.stat(Stat::SPA).ev, initial_spa);
 		if (atk and spa) {
 			stats.attack = *atk;
 			stats.special_attack = *spa;

source/team_predictor/ev_optimizer/single_classification_evs.cpp

 
 namespace technicalmachine {
 namespace {
+constexpr auto max_evs = 252u;
 Nature::Natures nature_boost_convert(SingleClassificationEVs::NatureBoost nature, bool physical);
 SingleClassificationEVs::NatureBoost nature_boost_convert(Nature nature);
 
 
 template<bool physical>
 void set_ev(Pokemon & pokemon, unsigned const defensive_ev) {
-	Stat & stat = physical ? pokemon.def() : pokemon.spd();
+	auto & stat = physical ? pokemon.stat(Stat::DEF) : pokemon.stat(Stat::SPD);
 	stat.ev.set_value(defensive_ev);
 }
 
 
 template<bool physical>
 std::vector<SingleClassificationEVs> equal_defensiveness(Pokemon pokemon) {
-	unsigned const initial_product = pokemon.hp().max * initial_stat<from_physical(physical)>(pokemon);
+	unsigned const initial_product = pokemon.stat(Stat::HP).max * initial_stat<from_physical(physical)>(pokemon);
 	std::vector<SingleClassificationEVs> result;
 	static std::initializer_list<Nature> const natures = {
 		nature_boost_convert(SingleClassificationEVs::Boost, physical),
 	};
 	for (Nature const nature : natures) {
 		pokemon.nature() = nature;
-		for (unsigned hp_ev = 0; hp_ev <= 252; hp_ev += 4) {
-			pokemon.hp().ev.set_value(hp_ev);
+		for (unsigned hp_ev = 0; hp_ev <= max_evs; hp_ev += 4) {
+			pokemon.stat(Stat::HP).ev.set_value(hp_ev);
 			unsigned const hp = initial_stat<Stat::HP>(pokemon);
 			unsigned defensive_ev = 0;
 			set_ev<physical>(pokemon, defensive_ev);
-			while (initial_stat<from_physical(physical)>(pokemon) * hp < initial_product and defensive_ev <= 252 - 4) {
+			while (initial_stat<from_physical(physical)>(pokemon) * hp < initial_product and defensive_ev <= max_evs - 4) {
 				defensive_ev += 4;
 				set_ev<physical>(pokemon, defensive_ev);
 			}

source/team_predictor/ev_optimizer/speed.cpp

 	for (Nature::Natures nature = static_cast<Nature::Natures>(0); nature != Nature::END; nature = static_cast<Nature::Natures>(nature + 1)) {
 		pokemon.nature().name = nature;
 		for (unsigned ev = 0; ev <= 252; ++ev) {
-			pokemon.spe().ev.set_value(ev);
+			pokemon.stat(Stat::SPE).ev.set_value(ev);
 			if (initial_stat<Stat::SPE>(pokemon) >= speed) {
 				container.insert(std::make_pair(nature, ev));
 				break;

source/team_predictor/predictor.cpp

 		PokemonInputValues(PokemonInputs const & inputs):
 			species(inputs.species()),
 			nature(inputs.nature()),
-			hp(inputs.hp()),
-			atk(inputs.atk()),
-			def(inputs.def()),
-			spa(inputs.spa()),
-			spd(inputs.spd()),
-			spe(inputs.spe()),
+			stats({{
+				inputs.hp(),
+				inputs.atk(),
+				inputs.def(),
+				inputs.spa(),
+				inputs.spd(),
+				inputs.spe()
+			}}),
 			moves(inputs.moves())
 			{
 		}
 		void add_to_team(Team & team) const {
 			team.add_pokemon(species, 100, Gender(), item, ability, nature);
 			Pokemon & pokemon = team.replacement();
-			pokemon.hp().ev.set_value(hp);
+			for (auto const stat : regular_stats()) {
+				pokemon.stat(stat).ev.set_value(stats[static_cast<size_t>(stat + 1)]);
+			}
 			pokemon.calculate_initial_hp();
-			pokemon.atk().ev.set_value(atk);
-			pokemon.def().ev.set_value(def);
-			pokemon.spa().ev.set_value(spa);
-			pokemon.spd().ev.set_value(spd);
-			pokemon.spe().ev.set_value(spe);
 			for (auto const move : moves) {
 				pokemon.move.add(move);
 			}
 		Item item;
 		Ability ability;
 		Nature nature;
-		unsigned hp;
-		unsigned atk;
-		unsigned def;
-		unsigned spa;
-		unsigned spd;
-		unsigned spe;
+		std::array<unsigned, 6> stats;
 		std::vector<Moves> moves;
 };