Last active
January 29, 2019 15:43
-
-
Save CyberShadow/ba1bb7f672116f5a823874d880a40687 to your computer and use it in GitHub Desktop.
Pillars of Eternity party equipment optimizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/eqp_opt | |
/result.json | |
/test_result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, }, | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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