Table of contents
Introduction
Just a simple linear programming problem I came across while playing Final Fantasy XV Comrades. Uses the PuLP library in Python to minimize experience needed to fulfill weapons’ leveling requirements.
Optimization
FFXV Comrades Item Optimizer
Something I was curious about while playing the game. It works, but it’s not “complete” because I want to add more features such as hosting on a dynamic webpage, calculating optimal solutions for all weapons, adding Cid buff variables, etc.
In general this was a very simple project; the only part that took some time and thought was formulating the problem as a linear programming (LP) problem which I just put through the package PuLP.
# Solving a simple linear programming problem following this tutorial:
# https://pythonhosted.org/PuLP/CaseStudies/a_blending_problem.html
# Importing the usual packages
import numpy as np
import pandas as pd
# Import PuLP modeler functions
from pulp import *
# Create the 'prob' variable to contain the problem data
prob = LpProblem("FF15 Comrades Weapon Upgrade Optimization", LpMinimize)
# Importing spreadsheet containing all upgrade item data from "FFXV Comrades Cheat Sheet 2.0"
# https://docs.google.com/spreadsheets/d/1BEGmEHH5TlnlZhSRLx1ToZzijeLm7--jB_QafNSSBCk/edit
item_data = pd.read_excel("FFXV Comrades Cheat Sheet 2.0.xlsx", sheet_name = "Items", header = 0, usecols = "B:N")
# Fill missing values with 0
item_data = item_data.fillna(0)
item_data.head()
Item | HP | MP | STR | VIT | MAG | SPI | FIR | ICE | LNG | DRK | SHT | EXP | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Accursed Coin | 0.0 | 0.0 | 2.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.02 | 0.0 | 490 |
1 | Ammonite Fossil | 0.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.00 | 0.0 | 450 |
2 | Anak Antlers | 0.0 | 0.0 | 5.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.00 | 0.0 | 890 |
3 | Anak Fetlock | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | 0.00 | 0.0 | 550 |
4 | Ancient Cloth | 0.0 | 0.0 | 3.0 | 3.0 | 5.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.00 | 0.0 | 860 |
# Creating list of items
Items = item_data["Item"].tolist()
# Creating dictionary "Items" to contained referenced variables. Also we can only have integer amounts of items used.
item_vars = LpVariable.dicts("Items", Items, 0, None, LpInteger)
# Create dictionary of all stats
itemSTATS = {}
for stat in item_data.columns[1:]:
itemSTATS[stat] = (item_data[["Item", stat]].set_index("Item").to_dict()[stat])
# Add objective function to 'prob'
prob += lpSum([itemSTATS["EXP"][i] * item_vars[i] for i in Items]), "Total experience used"
### Can put this in a loop in the final product that takes in a list [str, vit, ..., sht, exp]
# Add item-specific stat constraints
# In this case, we will use Corsesca, requiring 20 STR, 20 VIT, 20 MAG, 20 SPI to evolve at level 30 &
# 99 STR, 99 VIT to evolve at level 60
prob += lpSum([itemSTATS["STR"][i] * item_vars[i] for i in Items]) >= 20.0, "StrengthRequirement"
prob += lpSum([itemSTATS["VIT"][i] * item_vars[i] for i in Items]) >= 20.0, "VitalityRequirement"
prob += lpSum([itemSTATS["MAG"][i] * item_vars[i] for i in Items]) >= 20.0, "MagicRequirement"
prob += lpSum([itemSTATS["SPI"][i] * item_vars[i] for i in Items]) >= 20.0, "SpiritRequirement"
# The problem data is written to an .lp file
prob.writeLP("ComradesTest.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
1
# The status of the solution is printed to the screen
print("Status:", LpStatus[prob.status])
Status: Optimal
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
if v.varValue > 0:
print(v.name, "=", v.varValue)
Items_Ancient_Cloth = 1.0
Items_Dragon_Horn = 1.0
Items_Octolegs = 1.0
Items_Rough_Scale = 3.0
# The optimised objective function value is printed to the screen
print("Total experience used = ", value(prob.objective))
Total experience used = 5580.0