Skip to content

Instantly share code, notes, and snippets.

@CyberShadow
Last active January 29, 2019 15:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CyberShadow/ba1bb7f672116f5a823874d880a40687 to your computer and use it in GitHub Desktop.
Save CyberShadow/ba1bb7f672116f5a823874d880a40687 to your computer and use it in GitHub Desktop.
Pillars of Eternity party equipment optimizer
/eqp_opt
/result.json
/test_result
import std.algorithm.iteration;
import std.algorithm.searching : countUntil;
import std.format;
import std.math;
import std.range;
import std.stdio;
import ae.utils.math;
import ae.utils.meta;
import ae.utils.math.longmul;
import ae.utils.text;
enum Race
{
elf,
}
enum Class
{
fighter,
priest,
wizard,
cipher,
chanter,
ranger,
}
enum Stat
{
might,
constitution,
dexterity,
perception,
intellect,
resolve,
}
struct CharacterDefinition
{
// int level;
// Race race;
Class class_;
int[enumLength!Stat] baseStats;
}
immutable CharacterDefinition[] charDefs =
[
{
Class.cipher,
[
Stat.might : 16,
Stat.constitution : 7,
Stat.dexterity : 16, // 15 base + 1 elf
Stat.perception : 17, // 16 base + 1 elf
Stat.intellect : 19, // 18 base + 1 Old Vailia
Stat.resolve : 3,
]
},
{
Class.fighter,
[
Stat.might : 16, // 15 base + 1 human
Stat.constitution : 16,
Stat.dexterity : 11,
Stat.perception : 12,
Stat.intellect : 10,
Stat.resolve : 13, // 11 base + 1 human + 1 dyrwood
]
},
{
Class.wizard,
[
Stat.might : 12,
Stat.constitution : 10,
Stat.dexterity : 15, // 14 base + 1 elf
Stat.perception : 12, // 11 base + 1 elf
Stat.intellect : 16,
Stat.resolve : 13, // 12 base + 1 aedyr
]
},
{
Class.priest,
[
Stat.might : 14, // 13 base + 1 human
Stat.constitution : 13,
Stat.dexterity : 9,
Stat.perception : 9,
Stat.intellect : 15,
Stat.resolve : 19, // 16 base + 1 human + 1 dyrwood + 1 unwavering resolve
]
},
{
Class.chanter,
[
Stat.might : 16, // 14 base + 2 aumaua
Stat.constitution : 12, // 11 base + 1 rauatai
Stat.dexterity : 9,
Stat.perception : 14,
Stat.intellect : 17,
Stat.resolve : 10,
]
},
{
Class.ranger,
[
Stat.might : 15, // 13 base + 2 dwarf
Stat.constitution : 12, // 11 base + 1 dwarf
Stat.dexterity : 11, // 12 base - 1 dwarf
Stat.perception : 17, // 16 base + 1 Naasitaq
Stat.intellect : 13,
Stat.resolve : 10,
]
},
];
struct CharacterConfig
{
size_t[enumLength!Slot] items;
string[] serialize() { return items[].map!(item => .items[item].name[]).array; }
static CharacterConfig unserialize(string[] names) { CharacterConfig c; c.items[] = names.map!(name => cast(size_t).items.countUntil!(i => i.name == name)).array; return c; }
}
struct FixPoint(Base, Base one_)
{
enum one = one_;
Base value;
this(Base integer)
{
value = integer * one;
}
void opAssign(Base integer)
{
value = integer * one;
}
/// Create from raw value
static FixPoint fromValue(Base value) { FixPoint result; result.value = value; return result; }
T opCast(T)() const
if (is(T : real) && !is(T : long))
{
return T(value) / one;
}
typeof(this) opUnary(string op)() const
if (op == "+" || op == "-")
{
return FixPoint.fromValue(mixin(op ~ ` this.value`));
}
typeof(this) opBinary(string op)(Base value) const if (op == "+" || op == "-") { return FixPoint.fromValue(mixin(`this.value ` ~ op ~ ` (value * one)`)); }
typeof(this) opBinary(string op)(Base value) const if (op == "*" || op == "/") { return FixPoint.fromValue(mixin(`this.value ` ~ op ~ ` value`)); }
typeof(this) opBinary(string op)(FixPoint f) const if (op == "+" || op == "-") { return FixPoint.fromValue(mixin(`this.value ` ~ op ~ ` f.value`)); }
typeof(this) opBinary(string op)(FixPoint f) const if (op == "*")
{
static if (isPowerOfTwo(one))
{
enum bits = ilog2(one);
auto m = longMul(value, f.value);
return FixPoint.fromValue((m.low >> bits) | (m.high << (Base.sizeof * 8 - bits)));
}
else
{
import std.bigint;
return FixPoint.fromValue((BigInt(this.value) * f.value / one).toLong);
}
}
typeof(this) opBinary(string op)(FixPoint f) const
if (op == "/")
{
static if (isPowerOfTwo(one))
{
enum bits = ilog2(one);
return FixPoint.fromValue(longDiv(LongInt!Base(value << bits, value >> (Base.sizeof * 8 - bits)), f.value).quotient);
}
else
{
import std.bigint;
return FixPoint.fromValue((BigInt(this.value) * one / f.value).toLong);
}
}
typeof(this) opBinaryRight(string op)(Base value) const
{
return mixin(`FixPoint(value)` ~ op ~ `this`);
}
typeof(this) opOpAssign(string op)(Base value)
if (op == "+" || op == "-")
{
mixin(`this.value ` ~ op ~ `= value * one;`); return this;
}
typeof(this) opOpAssign(string op)(FixPoint f)
if (op == "+" || op == "-")
{
mixin(`this.value ` ~ op ~ `= f.value;`); return this;
}
void toString(scope void delegate(const(char)[]) sink,
FormatSpec!char fmt) const
{
// fmt.spec = 's';
// formatValue(sink, cast(real)this, fmt);
sink(fpToString(cast(real)this));
}
int opCmp(FixPoint f) const { return value < f.value ? -1 : value > f.value ? 1 : 0; }
bool opCast(T)() const if (is(T == bool)) { return !!value; }
}
alias Fixed = FixPoint!(long, 1L << 32);
unittest
{
assert(Fixed(2) * 3 == Fixed(6));
assert(Fixed(2) * Fixed(3) == Fixed(6));
assert(Fixed(6) / 2 == Fixed(3));
assert(Fixed(6) / Fixed(2) == Fixed(3));
}
alias Score = Fixed;
T statFactor(T)(T value, int statValue, T perStat)
{
return value + (statValue - 10) * perStat;
}
void maximize(T)(ref T value, T candidate) { if (candidate > value) value = candidate; }
struct Character
{
CharacterDefinition def;
CharacterConfig conf;
version(none)
int getStat(Stat stat)
{
int value = def.baseStats[stat];
version(none)
static immutable int[enumLength!Stat][enumLength!Race] bonuses =
[
Race.elf : [Stat.dexterity : 1, Stat.perception : 1],
];
foreach (item; conf.items)
value += items[item].statBonus[stat];
return value;
}
version(none)
@property int maxEndurance()
{
auto levelBase = (24 + (12 * def.level));
auto conMultiplier = (((getStat(Stat.constitution) - 10) * .05) + 1);
return cast(int)trunc(0.99 + levelBase * conMultiplier);
}
version(none)
@property int maxHealth()
{
static immutable int[enumLength!Class] multipliers =
[
Class.fighter : 5,
Class.priest : 4,
Class.wizard : 4,
Class.cipher : 4,
];
auto multiplier = multipliers[class_];
assert(multiplier, "Please add multiplier for class");
return maxEndurance * multiplier;
}
Score getScore(bool verbose)()
{
Score score;
void addPoints(string explanation)(Score points)
{
static if (verbose)
writefln("\t%-10s: %+d", explanation, points);
score += points;
}
int[enumLength!Stat] itemStats;
int[enumLength!Defense] itemDefenses;
int dr, concentration, moveSpeedBonus;
int attackSpeed = 100, recoverySpeed = 100, spellDefense = 100, healing = 100, abilityAreaOfEffect = 100, duration = 100, critMultiplier = 100;
foreach (item; conf.items)
if (item)
{
auto pitem = &items[item];
foreach (Stat stat, bonus; pitem.statBonus)
maximize(itemStats[stat], bonus);
foreach (Defense defense, bonus; pitem.defenseBonus)
maximize(itemDefenses[defense], bonus);
dr += pitem.dr;
attackSpeed += pitem.attackSpeed;
recoverySpeed += pitem.recoverySpeed;
maximize(spellDefense, 100 + pitem.spellDefense);
maximize(healing, 100 + pitem.healingBonus);
maximize(abilityAreaOfEffect, 100 + pitem.abilityAreaOfEffectBonus);
maximize(critMultiplier, 100 + pitem.critMultiplierBonus);
maximize(moveSpeedBonus, pitem.moveSpeedBonus);
if (pitem.onlyUsableBy.length && !pitem.onlyUsableBy.contains(def.class_))
addPoints!"Bad Class"(Score(-10_000));
if (pitem.wizardry)
addPoints!"Wizardry"(Score(pitem.wizardry * 20));
if (pitem.points)
addPoints!"Raw Points"(Score(pitem.points));
if (pitem.preferredClass.length && !pitem.preferredClass.contains(def.class_))
addPoints!"Pref.Class"(Score(-20));
}
auto stats = def.baseStats;
stats[] += itemStats[];
static if (verbose)
{
foreach (Stat stat, value; stats)
writefln("\t%-14s: %2d (%+d)", stat, value, itemStats[stat]);
writefln("\t--------------------------");
}
static immutable int[enumLength!Class] classAccuracies =
[
Class.fighter : 30,
Class.priest : 20,
Class.wizard : 20,
Class.cipher : 25,
Class.chanter : 25,
Class.ranger : 30,
];
auto defenses = itemDefenses;
// apply stats
{
auto weaponDamage = 100; // percent
Fixed damage = statFactor(weaponDamage, stats[Stat.might], 3);
healing += statFactor(0, stats[Stat.might], 3);
defenses[Defense.fortitude] += statFactor(0, stats[Stat.might], 2);
auto healthBonus = statFactor(100, stats[Stat.constitution], 5);
defenses[Defense.fortitude] += statFactor(0, stats[Stat.constitution], 2);
auto actionSpeed = statFactor(attackSpeed, stats[Stat.dexterity], 3);
defenses[Defense.reflex] += statFactor(0, stats[Stat.dexterity], 2);
auto interrupt = statFactor(0, stats[Stat.perception], 3);
auto accuracy = classAccuracies[def.class_] + 24 /*level*/ + statFactor(0, stats[Stat.perception], 1);
defenses[Defense.reflex] += statFactor(0, stats[Stat.perception], 1);
abilityAreaOfEffect += statFactor(0, stats[Stat.intellect], 6);
duration += statFactor(0, stats[Stat.intellect], 5);
defenses[Defense.will] += statFactor(0, stats[Stat.intellect], 2);
concentration += statFactor(0, stats[Stat.resolve], 3);
defenses[Defense.deflection] += statFactor(0, stats[Stat.resolve], 1);
defenses[Defense.will] += statFactor(0, stats[Stat.resolve], 2);
static if (verbose)
{
foreach (Defense defense, value; defenses)
writefln("\t%-14s: %2d (%+d)", defense, value, itemDefenses[defense]);
writefln("\t--------------------------");
}
enum targetDefense = 50;
auto hit0 = accuracy - targetDefense;
auto hit1 = hit0 + 100 - /*one-based*/1;
Fixed avgDamage = (
rangeIntersection(15, 50, hit0, hit1) * (damage / 2) +
rangeIntersection(50, 100, hit0, hit1) * damage +
rangeIntersection(100, int.max, hit0, hit1) * (damage * 3 / 2 * critMultiplier / 100)
);
if (def.class_ == Class.wizard)
avgDamage = avgDamage * abilityAreaOfEffect / 100; // blast
auto totalSpeed = Fixed(actionSpeed * 2 + recoverySpeed * 3) / 5;
auto dps = avgDamage * totalSpeed / 100 / 50;
addPoints!"Damage"(dps);
auto healthClassMultiplier =
def.class_ == Class.fighter ? Fixed(2) :
def.class_ == Class.wizard ? Fixed(12) / 10 : // Dangerous Implement
Fixed(1);
addPoints!"Health"(healthBonus * healthClassMultiplier);
auto healingClassMultiplier =
def.class_ == Class.fighter ? Fixed(2) :
Fixed(1);
addPoints!"Healing"(healing * healingClassMultiplier * healingClassMultiplier / 4);
foreach (Defense defense, bonus; defenses)
{
auto classMultiplier = (def.class_ == Class.fighter ? 2 : 1);
auto defenseMultiplier = defense == Defense.deflection ? 2 : 1;
addPoints!"Defense"(Score(bonus * classMultiplier * defenseMultiplier));
}
auto damageExpected = def.class_ == Class.fighter ? 40 : 20;
auto damageReceived = damageExpected - dr;
if (damageReceived < 0) damageReceived = 0;
damageReceived = damageReceived * damageReceived / 2;
addPoints!"DR"(-damageReceived * healthClassMultiplier / 3);
addPoints!"Spell Def."(spellDefense * healthClassMultiplier);
auto spellMultiplier = Fixed(
def.class_ == Class.priest ? 4 :
def.class_ == Class.cipher ? 3 :
def.class_ == Class.wizard ? 3 :
0);
auto spellEff = abilityAreaOfEffect;
spellEff = spellEff * duration / 100;
//spellEff += 5 * totalSpeed / 100;
addPoints!"Spell Eff."((spellEff - 100) * spellMultiplier / 2);
auto moveSpeedMultiplier = Fixed(
def.class_ == Class.fighter ? 2 :
def.class_ == Class.cipher ? 2 :
1);
addPoints!"Move Speed"(moveSpeedBonus * moveSpeedMultiplier);
}
if (def.class_ == Class.cipher)
score = score * 105 / 100; // give the PC preference
return score;
}
}
unittest
{
if (false)
{
Character.init.getScore!false();
Character.init.getScore!true();
}
}
enum Slot
{
head,
neck,
torso,
hand,
ring,
ring2,
feet,
belt,
}
enum Defense
{
deflection,
fortitude,
reflex,
will,
}
struct Item
{
string name;
Slot slot;
int[enumLength!Stat] statBonus;
int[enumLength!Defense] defenseBonus;
int dr;
// Various bonuses (percentages to add to the default of 100)
int attackSpeed;
int recoverySpeed;
int spellDefense;
int healingBonus;
int abilityAreaOfEffectBonus;
int critMultiplierBonus;
int wizardry;
int moveSpeedBonus;
int points; // raw points for misc. bonuses
Class[] onlyUsableBy;
Class[] preferredClass; // aesthetics
}
/*immutable*/__gshared Item[] items =
[
{}, // no item
{ "Hermit's Hat" , Slot.head , [Stat.intellect : +2], /*Immunity: Confuse*/},
{ "The Pilgrim's Lasting Vigil" , Slot.head , [Stat.resolve : +1, Stat.perception : +1], preferredClass : [Class.fighter] },
{ "Rugged Wilderness Hat" , Slot.head , [Stat.constitution : +1], /* also +2 survival */ preferredClass : [Class.wizard, Class.priest, Class.chanter, Class.ranger] },
{ "Kana's Turban" , Slot.head , [Stat.intellect : +1], preferredClass : [Class.chanter] },
{ "Company Captain's Hat" , Slot.head , [Stat.resolve : +1, Stat.dexterity : +1], onlyUsableBy : [Class.wizard] },
{ "Footpad's Hood" , Slot.head , [Stat.perception : +1], preferredClass : [Class.ranger] },
{ "Lilith's Shawl" , Slot.neck , [Stat.perception : +3], /* also Aura of Stealth */ preferredClass : [Class.ranger] },
{ "Cloak of Protection" , Slot.neck , defenseBonus : [Defense.fortitude : +9, Defense.reflex : +9, Defense.will : +9], },
{ "Minor Cloak of Protection" , Slot.neck , defenseBonus : [Defense.fortitude : +5, Defense.reflex : +5, Defense.will : +5], },
{ "Minor Cloak of Protection" , Slot.neck , defenseBonus : [Defense.fortitude : +5, Defense.reflex : +5, Defense.will : +5], },
{ "Unwavering Resolve" , Slot.neck , [Stat.intellect : +2, Stat.resolve : +1 ], preferredClass : [Class.priest] },
{ "Fulvano's Amulet" , Slot.neck , defenseBonus : [Defense.reflex : +5], healingBonus : +25, },
{ "Drinking Horn of Moderation" , Slot.neck , [Stat.intellect : +2] },
{ "Cape of Deflection" , Slot.neck , defenseBonus : [Defense.deflection : +5] },
{ "Torc of the Falcon's Eyes" , Slot.neck , [Stat.perception : +2] },
{ "Cloak of an Eothasian Priest", Slot.neck , defenseBonus : [Defense.will : +9] },
{ "Shroud of Mourning" , Slot.neck , [Stat.resolve : +3], /* Corrode-Proofed*/ /*Freeze-Proofed*/ },
{ "Jack of Wide Waters" , Slot.torso, [Stat.might : +2], dr : 8, recoverySpeed : -20, /* Survivor */},
{ "Exceptional Plate Armor" , Slot.torso, dr : 16, recoverySpeed : -50, },
{ "Exceptional Plate Armor" , Slot.torso, dr : 16, recoverySpeed : -50, },
{ "Exceptional Plate Armor" , Slot.torso, dr : 16, recoverySpeed : -50, },
{ "Aloth's Leather Armor" , Slot.torso, [Stat.might : +2], dr : 10, recoverySpeed : -30, abilityAreaOfEffectBonus : +10, preferredClass : [Class.wizard] },
{ "Durance's Robe" , Slot.torso, [Stat.constitution : +2], dr : 7, recoverySpeed : -15, preferredClass : [Class.priest] },
{ "Saint's War Armor" , Slot.torso, [Stat.constitution : +2], dr : 11, recoverySpeed : -35, /*TODO: +3 Pierce DR*/ healingBonus : +10 /*TODO: Second Chance*/ },
{ "Blaidh Golan" , Slot.torso, dr : 9, recoverySpeed : -25, /*TODO: Break Out */ /*TODO: Preservation */ },
{ "Rundl'r Finery" , Slot.torso, [Stat.intellect : +2], dr : 7, recoverySpeed : -15, /*Lore-Giving*/ },
{ "Fulvano's Gloves" , Slot.hand , [Stat.dexterity : +2], },
{ "Bracers of Enduring" , Slot.hand , [Stat.constitution : +2, Stat.resolve : +1], },
{ "Spiderfingers" , Slot.hand , [Stat.perception : +1], /* Minor Spellbind: Restore Light Endurance */ },
{ "Gauntlets of Ogre Might" , Slot.hand , [Stat.might : +2], /* Minor Spellbind: Restore Light Endurance */ },
{ "Celebrant's Gloves" , Slot.hand , abilityAreaOfEffectBonus : +10, /* Spelldefense: Holy Meditation */ },
{ "Rabbit Fur Gloves" , Slot.hand , critMultiplierBonus : +10, },
{ "Gauntlets of Swift Action" , Slot.hand , attackSpeed : +15, },
{ "Ring of Changing Heart" , Slot.ring , [Stat.resolve : +3], },
{ "Bartender's Ring" , Slot.ring , defenseBonus : [Defense.will : +5], },
{ "Minor Ring of Deflection" , Slot.ring , defenseBonus : [Defense.deflection : +5], },
{ "Ring of Deflection" , Slot.ring , defenseBonus : [Defense.deflection : +9], },
{ "Minor Ring of Protection" , Slot.ring , defenseBonus : [Defense.fortitude : +5, Defense.reflex : +5, Defense.will : +5], },
{ "Minor Ring of Protection" , Slot.ring , defenseBonus : [Defense.fortitude : +5, Defense.reflex : +5, Defense.will : +5], },
{ "Minor Ring of Protection" , Slot.ring , defenseBonus : [Defense.fortitude : +5, Defense.reflex : +5, Defense.will : +5], },
{ "Ring of Protection " , Slot.ring , defenseBonus : [Defense.fortitude : +9, Defense.reflex : +9, Defense.will : +9], },
{ "Frigid Claim" , Slot.ring , /*TODO: burn DR bonus*/ spellDefense : +10, },
{ "Ring of the Selonan" , Slot.ring , onlyUsableBy : [Class.wizard], wizardry : 1 },
{ "Sigil of the Arcane" , Slot.ring , [Stat.intellect : +1], /*TODO: Major Spellbind: Arcane Dampener*/ },
{ "Serel's Ring" , Slot.ring , [Stat.resolve : +2] },
{ "Gathbin Family Signet" , Slot.ring , abilityAreaOfEffectBonus : +10 },
{ "Ring of Thorns" , Slot.ring , [Stat.dexterity : +3], defenseBonus : [Defense.reflex : +5], /*Preservation*/ },
{ "Belt of Bountiful Healing" , Slot.belt , healingBonus : +25, },
{ "Blunting Belt" , Slot.belt , dr : +5 /*piercing/slashing only*/, },
{ "Girdle of Eoten Constitution", Slot.belt , [Stat.constitution : +3], },
{ "Sentinel's Girdle" , Slot.belt , [Stat.might : +3], /*Indomitable*/ /*Minor Spellbind: Watchful Presence*/},
{ "Belt of the Royal Deadfire Cannoneer", Slot.belt , points : 50, onlyUsableBy : [Class.priest] },
{ "Fulvano's Boots" , Slot.feet , [Stat.constitution : +1], },
{ "Boots of Evasion" , Slot.feet , defenseBonus : [Defense.reflex : +2], },
{ "Boots of Speed" , Slot.feet , moveSpeedBonus : +3, },
];
import ae.utils.json;
import ae.utils.meta;
import std.algorithm.iteration;
import std.parallelism;
import std.random;
import std.range;
import std.stdio;
import common;
Score search()
{
Score bestScore;
int hits; // How many times we found the exact same result as the current best
enum hitLimit = 20;
foreach (thread; totalCPUs.iota.parallel)
{
Character[6] chars;
foreach (i; 0 .. 6)
chars[i].def = charDefs[i];
Score localBestScore;
auto itemLocations = new size_t*[items.length];
auto itemOrder = iota(1, items.length).array;
Score fullScore()
{
Score score;
foreach (c; 0 .. chars.length)
score += chars[c].getScore!false();
return score;
}
while (hits < hitLimit)
{
foreach (ref c; chars)
c.conf = CharacterConfig.init;
itemLocations[] = null;
bool found;
do
{
found = false;
itemOrder.randomShuffle;
foreach (item; itemOrder)
{
auto oldScore = fullScore();
auto pitem = &items[item];
auto itemLocation = itemLocations[item];
if (itemLocation)
assert(*itemLocation == item);
Score bestCharScore; size_t* bestLocation;
foreach (c; 0 .. chars.length)
{
Slot slotMin = pitem.slot;
Slot slotMax = pitem.slot;
if (slotMax == Slot.ring)
slotMax = Slot.ring2;
slotMax++;
foreach (slot; slotMin .. slotMax)
{
auto location = &chars[c].conf.items[slot];
auto oldItem = *location;
if (itemLocation) *itemLocation = oldItem;
*location = item;
auto newScore = fullScore();
if (itemLocation) *itemLocation = item;
*location = oldItem;
auto charScore = newScore - oldScore;
if (bestCharScore < charScore)
{
bestCharScore = charScore;
bestLocation = location;
}
}
}
if (bestCharScore)
{
found = true;
auto oldItem = *bestLocation;
// If already equipped, swap with new location
if (itemLocation)
*itemLocation = oldItem;
// Update location
itemLocations[item] = bestLocation;
// Unequip item in that slot, if any
if (*bestLocation)
itemLocations[*bestLocation] = itemLocation; // update with new location of old item when swapping
// Equip item
*bestLocation = item;
}
}
} while (found);
Score score;
foreach (ref c; chars)
score += c.getScore!false();
if (score >= localBestScore)
{
localBestScore = score;
synchronized
{
if (score > bestScore)
{
bestScore = score;
writeln("=========================================================================");
writeln("New best score: ", bestScore);
foreach (ref c; chars)
{
writeln(c.def.class_);
foreach (Slot slot, item; c.conf.items)
writefln("\t%-10s: %s", slot, items[item].name);
writefln("\t--------------------------");
auto charScore = c.getScore!true();
writefln("\t%-10s: %d", "Total", charScore);
writeln();
}
chars[].map!(c => c.conf.serialize).array.toPrettyJson.toFile("result.json");
hits = 0;
}
else
if (score == bestScore)
{
if (hits++ >= hitLimit)
return bestScore;
else
writefln("Hit (%d/%d)", hits, hitLimit);
}
}
}
}
}
return bestScore;
}
void findEnchantment()
{
Score bestScore;
foreach (item; 1 .. items.length)
if (items[item].name == "Jack of Wide Waters")
if (items[item].statBonus == Item.init.statBonus)
foreach (stat; Stat.init .. enumLength!Stat)
{
enum bonus = +2;
writefln("Checking %s : %s (%+d)...", items[item].name, stat, bonus);
items[item].statBonus[stat] = bonus;
auto score = search();
if (score > bestScore)
{
bestScore = score;
writefln("NEW BEST! %s : %s (%+d) - %s", items[item].name, stat, bonus, score);
}
items[item].statBonus[stat] = 0;
}
}
void main()
{
search();
}
import std.algorithm.iteration;
import std.file;
import std.range;
import std.stdio;
import ae.utils.json;
import common;
void main()
{
auto chars = readText("result.json").jsonParse!(string[][]).enumerate.map!(p => Character(charDefs[p[0]], CharacterConfig.unserialize(p[1]))).array;
Score total;
foreach (ref c; chars)
{
writeln(c.def.class_);
auto charScore = c.getScore!true();
writefln("\t%-10s: %d", "Total", charScore);
total += charScore;
writeln();
}
writeln("Total: ", total);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment