move code into mhapi package

This commit is contained in:
Bryce Allen
2015-03-20 21:10:38 -05:00
parent e92b5c71ad
commit 661a055d5c
7 changed files with 34 additions and 33 deletions

0
mhapi/__init__.py Normal file
View File

178
mhapi/db.py Executable file
View File

@@ -0,0 +1,178 @@
"""
Module for accessing the sqlite monster hunter db from
"""
import string
import sqlite3
class Quest(object):
def __init__(self, quest_row, quest_rewards):
self._row = quest_row
self._rewards = quest_rewards
self._template = string.Template(
"$name ($hub $stars* $rank)"
+ "\n Goal: $goal"
+ "\n Sub : $sub_goal"
)
self.id = quest_row["_id"]
self.name = quest_row["name"]
self.stars = quest_row["stars"]
self.hub = quest_row["hub"]
self.goal = quest_row["goal"]
self.sub_goal = quest_row["sub_goal"]
self.rank = get_rank(self.hub, self.stars)
def __unicode__(self):
return self._template.substitute(self.__dict__)
def get_rank(hub, stars):
if hub == "Caravan":
if stars < 6:
return "LR"
elif stars < 10:
return "HR"
return "G"
if hub == "Guild":
if stars < 4:
return "LR"
elif stars < 8:
return "HR"
return "G"
class MHDB(object):
def __init__(self, path, use_cache=True):
self.conn = sqlite3.connect(path)
self.conn.row_factory = sqlite3.Row
self.use_cache = use_cache
self.cache = {}
def _get_memoized(self, key, query, *args):
if self.use_cache:
if key in self.cache:
v = self.cache[key].get(args)
if v is not None:
return v
else:
self.cache[key] = {}
cursor = self.conn.execute(query, args)
v = cursor.fetchall()
if self.use_cache:
self.cache[key][args] = v
return v
def get_item_names(self):
v = self._get_memoized("item_names", """
SELECT name FROM items
WHERE type IN ('Bone', 'Flesh', 'Sac/Fluid')
""")
return v
def get_item(self, item_id):
v = self._get_memoized("item", """
SELECT * FROM items
WHERE _id=?
""", item_id)
return v[0] if v else None
def get_item_by_name(self, name):
v = self._get_memoized("item", """
SELECT * FROM items
WHERE name=?
""", name)
return v[0] if v else None
def search_item_name(self, term, item_type):
"""
Search for items containing @term somewhere in the name. Returns
list of matching items.
Not memoized.
"""
cursor = self.conn.execute("""
SELECT * FROM items
WHERE name LIKE ?
AND type = ?
""", ("%%%s%%" % term, item_type))
return cursor.fetchall()
def get_monster_by_name(self, name):
v = self._get_memoized("monster", """
SELECT * FROM monsters
WHERE name=?
""", name)
return v[0] if v else None
def get_monster(self, monster_id):
v = self._get_memoized("monster", """
SELECT * FROM monsters
WHERE _id=?
""", monster_id)
return v[0] if v else None
def get_quest(self, quest_id):
v = self._get_memoized("quest", """
SELECT * FROM quests
WHERE _id=?
""", quest_id)
return v[0] if v else None
def get_quest_rewards(self, quest_id):
v = self._get_memoized("quest_rewards", """
SELECT * FROM quest_rewards
WHERE quest_id=?
""", quest_id)
return v
def get_monster_rewards(self, monster_id, rank=None):
q = """
SELECT * FROM hunting_rewards
WHERE monster_id=?
"""
if rank is not None:
q += "AND rank=?"
v = self._get_memoized("monster_rewards", q, monster_id, rank)
else:
v = self._get_memoized("monster_rewards", q, monster_id)
return v
def get_quest_monsters(self, quest_id):
v = self._get_memoized("quest_monsters", """
SELECT monster_id FROM monster_to_quest
WHERE quest_id=? AND unstable='no'
""", quest_id)
return v
def get_item_quest_objects(self, item_id):
"""
Get a list of quests that provide the specified item in quest
reqards. Returns a list of quest objects, which encapsulate the
quest details and the list of rewards.
"""
cursor = self.conn.execute("""
SELECT DISTINCT quest_id FROM quest_rewards
WHERE item_id=?
""", (item_id,))
rows = cursor.fetchall()
quests = []
for r in rows:
quest_row = self.get_quest(r["quest_id"])
rewards_rows = self.get_quest_rewards(r["quest_id"])
quests.append(Quest(quest_row, rewards_rows))
return quests
def get_item_monsters(self, item_id):
v = self._get_memoized("item_monsters", """
SELECT DISTINCT monster_id, rank FROM hunting_rewards
WHERE item_id=?
""", item_id)
return v

415
mhapi/rewards.py Executable file
View File

@@ -0,0 +1,415 @@
#!/usr/bin/env python
from __future__ import print_function
import codecs
from mhapi.db import MHDB
from mhapi import stats
SKILL_CARVING = "carving"
SKILL_CAP = "cap"
SKILL_NONE = None
STRAT_KILL = "kill"
STRAT_CAP = "cap"
STRAT_SHINY = "shiny"
def get_utf8_writer(writer):
return codecs.getwriter("utf8")(writer)
def _format_range(min_v, max_v):
if min_v == max_v:
return "%5.2f" % min_v
else:
return "%5.2f to %5.2f" % (min_v, max_v)
def find_item(db, item_name, err_out):
item_row = db.get_item_by_name(item_name)
if item_row is None:
print("Item '%s' not found. Listing partial matches:" % item_name,
file=err_out)
terms = item_name.split()
for term in terms:
if len(term) < 2:
# single char terms aren't very useful, too many results
continue
print("= Matching term '%s'" % term, file=err_out)
rows = db.search_item_name(term, "Flesh")
for row in rows:
print(" ", row["name"], file=err_out)
return None
return item_row
class QuestReward(object):
def __init__(self, reward, fixed_rewards):
self.slot = reward["reward_slot"]
self.stack_size = reward["stack_size"]
self.percentage = reward["percentage"]
self.item_id = reward["item_id"]
self.fixed_rewards = fixed_rewards[self.slot]
self.skill_delta = 0
self.evs = self._calculate_ev()
def expected_value(self, luck_skill=stats.LUCK_SKILL_NONE,
cap_skill=None, carving_skill=None):
return self.evs[luck_skill]
def expected_values(self):
return self.evs
def _calculate_ev(self):
if self.percentage == 100:
# fixed reward, always one draw regardless of luck skill
evs = [1 * self.percentage * self.stack_size] * 3
self.skill_delta = 0
else:
# variable reward, expected number of draws depends on luck skill
counts = [stats.quest_reward_expected_c(self.slot, skill)
for skill in xrange(stats.LUCK_SKILL_NONE,
stats.LUCK_SKILL_GREAT+1)]
evs = [((count - self.fixed_rewards)
* self.stack_size * self.percentage)
for count in counts]
self.skill_delta = evs[-1] - evs[0]
return evs
def print(self, out, indent=2):
out.write("%s%20s %d %5.2f / 100"
% (" " * indent, self.slot, self.stack_size,
self.evs[0]))
out.write(" (%2d each)" % self.percentage)
if self.skill_delta:
out.write(" %s" % " ".join("%0.2f" % i for i in self.evs[1:]))
out.write("\n")
class QuestItemExpectedValue(object):
"""
Calculate the expected value for an item across all rewards for a quest.
@param item_id: database id of item
@param quest_rewards: list of rows from quest_rewards table for a single
quest
"""
def __init__(self, item_id, quest_rewards):
self.item_id = item_id
self.fixed_rewards = dict(A=0, B=0, Sub=0)
self.total_reward_p = dict(A=0, B=0, Sub=0)
# dict mapping slot name to list of lists
# of the form (slot, list_of_expected_values).
self.slot_rewards = dict(A=[], B=[], Sub=[])
self.total_expected_values = [0, 0, 0]
self._set_rewards(quest_rewards)
def is_sub(self):
"""Item is available from sub quest"""
return len(self.slot_rewards["Sub"]) > 0
def is_main(self):
"""Item is available from main quest"""
return (len(self.slot_rewards["A"]) > 0
or len(self.slot_rewards["B"]) > 0)
def expected_value(self, luck_skill=stats.LUCK_SKILL_NONE,
cap_skill=None, carving_skill=None):
return self.total_expected_values[luck_skill]
def check_totals(self, outfile):
# sanity check values from the db
for slot in self.total_reward_p.keys():
if self.total_reward_p[slot] not in (0, 100):
print("WARNING: bad total p for %s = %d"
% (slot, self.total_reward_p[slot]), file=outfile)
def _set_rewards(self, rewards):
# preprocessing step - figure out how many fixed rewards there
# are, which we need to know in order to figure out how many
# chances there are to get other rewards.
for reward in rewards:
slot = reward["reward_slot"]
if reward["percentage"] == 100:
self.fixed_rewards[slot] += 1
else:
self.total_reward_p[slot] += reward["percentage"]
for reward in rewards:
if reward["item_id"] != self.item_id:
continue
self._add_reward(reward)
def _add_reward(self, r):
reward = QuestReward(r, self.fixed_rewards)
self.slot_rewards[reward.slot].append(reward)
evs = reward.expected_values()
for i in xrange(len(evs)):
self.total_expected_values[i] += evs[i]
def print(self, out, indent=2):
for slot in ("A", "B", "Sub"):
for qr in self.slot_rewards[slot]:
qr.print(out, indent)
class HuntReward(object):
def __init__(self, reward):
self.condition = reward["condition"]
self.stack_size = reward["stack_size"]
self.percentage = reward["percentage"]
self.item_id = reward["item_id"]
self.cap = False
self.kill = False
self.shiny = False
self.skill = SKILL_NONE
self.evs = self._calculate_evs()
def expected_value(self, strategy, luck_skill=None,
cap_skill=stats.CAP_SKILL_NONE,
carving_skill=stats.CARVING_SKILL_NONE):
if strategy == STRAT_CAP:
if not self.cap:
return 0
elif strategy == STRAT_KILL:
if not self.kill:
return 0
elif strategy == STRAT_SHINY:
if not self.shiny:
return 0
else:
raise ValueError("strategy must be STRAT_CAP or STRAT_KILL")
if self.skill == SKILL_CAP:
return self.evs[cap_skill]
elif self.skill == SKILL_CARVING:
return self.evs[carving_skill]
else:
return self.evs[0]
def print(self, out, indent=2):
out.write("%s%20s %d %5.2f / 100"
% (" " * indent, self.condition,
self.stack_size, self.evs[0]))
out.write(" (%2d each)" % self.percentage)
if len(self.evs) > 1:
out.write(" " + " ".join("%0.2f" % i for i in self.evs[1:]))
out.write("\n")
def _calculate_evs(self):
if self.condition == "Body Carve":
self.skill = SKILL_CARVING
self.cap = False
self.kill = True
counts = [
3 + stats.carve_delta_expected_c(skill)
for skill in xrange(stats.CARVING_SKILL_PRO,
stats.CARVING_SKILL_GOD+1)
]
elif self.condition == "Body Carve (Apparent Death)":
# assume one carve, it's dangerous to try for two
counts = [1]
self.cap = True
self.kill = True
elif self.condition == "Tail Carve":
self.skill = SKILL_CARVING
self.cap = True
self.kill = True
counts = [
1 + stats.carve_delta_expected_c(skill)
for skill in xrange(stats.CARVING_SKILL_PRO,
stats.CARVING_SKILL_GOD+1)
]
elif self.condition == "Capture":
self.skill = SKILL_CAP
self.cap = True
self.kill = False
counts = [
stats.capture_reward_expected_c(skill)
for skill in xrange(stats.CAP_SKILL_NONE,
stats.CAP_SKILL_GOD+1)
]
else:
counts = [1]
if self.condition.startswith("Shiny"):
# don't include shiny in total, makes it easier to
# calculate separately since shinys are variable by
# monster
self.cap = False
self.kill = False
self.shiny = True
elif self.condition.startswith("Break"):
self.cap = True
self.kill = True
else:
raise ValueError("Unknown condition: '%s'"
% self.condition)
evs = [(i * self.stack_size * self.percentage) for i in counts]
return evs
class HuntItemExpectedValue(object):
"""
Calculate the expected value for an item from hunting a monster, including
all ways of getting the item.
@param item_id: database id of item
@param hunt_rewards: list of rows from hunt_rewards table for a single
monster and rank
"""
def __init__(self, item_id, hunt_rewards):
self.item_id = item_id
self.matching_rewards = []
self._set_rewards(hunt_rewards)
def expected_value(self, strategy, luck_skill=None,
cap_skill=stats.CAP_SKILL_NONE,
carving_skill=stats.CARVING_SKILL_NONE):
ev = 0
for reward in self.matching_rewards:
ev += reward.expected_value(strategy,
luck_skill=luck_skill,
cap_skill=cap_skill,
carving_skill=carving_skill)
return ev
def print(self, out, indent=2):
for hr in self.matching_rewards:
hr.print(out, indent)
def _set_rewards(self, rewards):
for reward in rewards:
if reward["item_id"] != self.item_id:
continue
self._add_reward(reward)
def _add_reward(self, r):
reward = HuntReward(r)
self.matching_rewards.append(reward)
def print_monsters_and_rewards(db, item_row, out):
item_id = item_row["_id"]
monsters = db.get_item_monsters(item_id)
for m in monsters:
mid = m["monster_id"]
rank = m["rank"]
monster = db.get_monster(mid)
reward_rows = db.get_monster_rewards(mid, rank)
hunt_item = HuntItemExpectedValue(item_id, reward_rows)
out.write("%s %s\n" % (monster["name"], rank))
hunt_item.print(out, indent=2)
kill_ev = [0, 0]
kill_ev[0] = hunt_item.expected_value(STRAT_KILL)
kill_ev[1] = hunt_item.expected_value(STRAT_KILL,
carving_skill=stats.CARVING_SKILL_GOD)
cap_ev = [0, 0]
cap_ev[0] = hunt_item.expected_value(STRAT_CAP)
cap_ev[1] = hunt_item.expected_value(STRAT_CAP,
cap_skill=stats.CAP_SKILL_GOD)
shiny_ev = hunt_item.expected_value(STRAT_SHINY)
out.write(" %20s\n" % "= Totals")
out.write(" %20s %s / 100\n"
% ("Kill", _format_range(*kill_ev)))
out.write(" %20s %s / 100\n"
% ("Cap", _format_range(*cap_ev)))
if shiny_ev:
out.write(" %20s %5.2f / 100\n" % ("Shiny", shiny_ev))
out.write("\n")
def print_quests_and_rewards(db, item_row, out):
"""
Get a list of the quests for acquiring a given item and the probability
of getting the item, depending on cap or kill and luck skills.
"""
item_id = item_row["_id"]
quests = db.get_item_quest_objects(item_id)
print_monsters_and_rewards(db, item_row, out)
if not quests:
return
for q in quests:
out.write(unicode(q) + "\n")
out.write(" %20s" % "= Quest\n")
quest = QuestItemExpectedValue(item_id, q._rewards)
quest.check_totals(out)
quest.print(out, indent=2)
monsters = db.get_quest_monsters(q.id)
quest_ev = quest.expected_value()
cap_ev = [quest_ev, quest_ev]
kill_ev = [quest_ev, quest_ev]
shiny_ev = 0
for m in monsters:
mid = m["monster_id"]
monster = db.get_monster(mid)
has_item = False
reward_rows = db.get_monster_rewards(mid, q.rank)
hunt_item = HuntItemExpectedValue(item_id, reward_rows)
kill_ev[0] += hunt_item.expected_value(STRAT_KILL)
kill_ev[1] += hunt_item.expected_value(STRAT_KILL,
carving_skill=stats.CARVING_SKILL_GOD)
cap_ev[0] += hunt_item.expected_value(STRAT_CAP)
cap_ev[1] += hunt_item.expected_value(STRAT_CAP,
cap_skill=stats.CAP_SKILL_GOD)
shiny_ev = hunt_item.expected_value(STRAT_SHINY)
if kill_ev[0] == 0 and cap_ev[0] == 0 and shiny_ev == 0:
continue
out.write(" %20s\n" % ("= " + monster["name"] + " " + q.rank))
hunt_item.print(out, indent=2)
out.write(" %20s\n" % "= Totals")
out.write(" %20s %s / 100\n"
% ("Kill", _format_range(*kill_ev)))
out.write(" %20s %s / 100\n"
% ("Cap", _format_range(*cap_ev)))
if shiny_ev:
out.write(" %20s %5.2f / 100\n" % ("Shiny", shiny_ev))
out.write("\n")
if __name__ == '__main__':
import sys
import os
import os.path
if len(sys.argv) != 2:
print("Usage: %s 'item name'" % sys.argv[0])
sys.exit(os.EX_USAGE)
item_name = sys.argv[1]
out = get_utf8_writer(sys.stdout)
err_out = get_utf8_writer(sys.stderr)
# TODO: doesn't work if script is symlinked
db_path = os.path.dirname(sys.argv[0])
db_path = os.path.join(db_path, "..", "db", "mh4u.db")
db = MHDB(db_path)
item_row = find_item(db, item_name, err_out)
if item_row is None:
sys.exit(os.EX_DATAERR)
print_quests_and_rewards(db, item_row, out)

261
mhapi/stats.py Executable file
View File

@@ -0,0 +1,261 @@
#!/usr/bin/env python
# Calculate probability of getting at least one of a monster part from one
# line in the quest rewards. Also calculates expected value of part
# counts (N), and probabilities of getting a certain number of rewards (C).
#
# usage: mhprob.py reward_percent fixed_rewards gaurenteed_rewards \
# [extend_percent]
#
# reward_percent - chance of getting the monster part in %
# fixed_rewards - number of rewards in the reward list with 100% chance and
# are not the item you are looking for. Takes away from
# the total possible draw attempts for what you want.
# Default 1 which is typical for line A in many quests.
# gaurenteed_rewards - minimum number of quest rewards in the line,
# including any fixed rewards. In Tri (see link
# below) this is 3 for line A and 1 for line B.
# Defaults to 3.
# extend_percent - chance of getting one more reward in the line in %,
# default 69
#
# You can use http://kiranico.com to get reward percent and fixed rewards
# values.
#
# For extend percent, use the default unless you have the Lucky Cat or
# Ultra Lucky Cat food skills or Good Luck or Great Luck armor skills:
#
# normal extra reward %: 69
# good luck extra reward %: 81
# great luck extra %: 90
#
# Can also be used to calculate chance of getting a part from carving,
# using extend_percent=0, fixed_rewards=0, and gaurenteed_rewards=3 (or
# 4 for monsters with 4 carves).
#
# Source:
# http://www.gamefaqs.com/wii/943655-monster-hunter-tri/faqs/60448
# Not sure if there are differences in 3U or 4U.
#
# Example Plain dangerous in 3U, has 2 fixed rewards in A, one in B, hardhorns
# are 5% both A and B:
# A: ./mhprop.py 5 2 3 69
# B: ./mhprop.py 5 1 1 69
# For great luck, replace 69 with 90
CAP_SKILL_NONE = 0
CAP_SKILL_EXPERT = 1
CAP_SKILL_MASTER = 2
CAP_SKILL_GOD = 3
LUCK_SKILL_NONE = 0
LUCK_SKILL_GOOD = 1
LUCK_SKILL_GREAT = 2
QUEST_A = "A"
QUEST_B = "B"
QUEST_SUB = "Sub"
CARVING_SKILL_NONE = 0
CARVING_SKILL_PRO = 0 # prevent knockbacks but no extra carves
CARVING_SKILL_FELYNE_LOW = 1
CARVING_SKILL_FELYNE_HI = 2
CARVING_SKILL_CELEBRITY = 3
CARVING_SKILL_GOD = 4
import sys
def _quest_reward_p(reward_percent, reward_count):
"""
Propability of getting at least one item from @reward_count draws
with a @reward_percent chance.
"""
fail_percent = (100 - reward_percent)
return 1.0 - (fail_percent / 100.0)**reward_count
def _reward_count_p(reward_count, min_rewards, max_rewards, extend_percent):
"""
Probability of getting a certain number of rewards for a given chance
@extend_percent of getting one more drop.
"""
if reward_count == min_rewards:
return (100 - extend_percent) / 100.0
p = 1.0
extend_p = extend_percent / 100.0
stop_p = 1.0 - extend_p
for i in xrange(min_rewards+1, reward_count+1):
p *= extend_p
if reward_count < max_rewards:
p *= stop_p
return p
def quest_reward_p(reward_percent, min_rewards, max_rewards, extend_percent=69):
"""
Probability of getting at least one of the item, given the item has
@reward_percent chance, @min_rewards minimum number of attempts,
@max_rewards max attempts, and @extend_percent chance of getting each
extra attempt.
"""
p = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
p += (_reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
* _quest_reward_p(reward_percent, reward_count))
return p * 100
def reward_expected_c(min_rewards, max_rewards, extend_percent):
"""
Expected value for number of rewards, if @min_rewards are gauranteed
and there is an @extend_percent chance of getting one more each time
with at most @max_rewards.
"""
total_p = 0.0
expected_attempts = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
p = _reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
expected_attempts += p * reward_count
#print "P(C = %d) = %0.4f" % (reward_count, p)
total_p += p
assert abs(total_p - 1.0) < .00001, "total = %f" % total_p
return expected_attempts
def quest_reward_expected_c(line=QUEST_A, luck_skill=LUCK_SKILL_NONE):
"""
Expected number of rewards from specified quest line with given skills.
Note: if the quest has fixed rewards that aren't the desired item, it will
reduce the expected count for the desired item. Just subtract the number
of fixed items from the output to get the actual value.
"""
if luck_skill == LUCK_SKILL_NONE:
extend_p = 69
elif luck_skill == LUCK_SKILL_GOOD:
extend_p = 81
elif luck_skill == LUCK_SKILL_GREAT:
extend_p = 90
else:
raise ValueError()
# TODO: not sure if these are correct for 4U, but I've
# heard that 3U had 4/2 gaurenteed for A/B.
if line == QUEST_A:
min_c = 4
max_c = 8
elif line == QUEST_B:
min_c = 2
max_c = 8
elif line == QUEST_SUB:
min_c = 1
max_c = 4
else:
raise ValueError()
return reward_expected_c(min_c, max_c, extend_p)
def capture_reward_expected_c(cap_skill=CAP_SKILL_NONE):
"""
Expected value for number of capture rewards given the specified
capture skill (none by default).
"""
if cap_skill == CAP_SKILL_NONE:
min_c = 2
max_c = 3
extend_p = 69
elif cap_skill == CAP_SKILL_EXPERT:
return 3
elif cap_skill == CAP_SKILL_MASTER:
min_c = 3
max_c = 4
extend_p = 69
elif cap_skill == CAP_SKILL_GOD:
return 4
else:
raise ValueError()
return reward_expected_c(min_c, max_c, extend_p)
def carve_delta_expected_c(carve_skill):
"""
Expected value for the number of extra carves with the given skill.
Word on the street is that since Tri the felyne skills do not stack with
the armor skills, i.e. if you have Carving Celebrity plus you get at
least one extra carves and the felyne skills do nothing.
"""
if carve_skill == CARVING_SKILL_CELEBRITY:
# Description: Increases the number of carving chances by one and prevents knockbacks while carving.
return 1
elif carve_skill == CARVING_SKILL_GOD:
# Description: Increases the number of carving chances by one (maybe more) and prevents knockbacks while carving.
# TODO: max 2 and 50% extend is a guess, find the actual values
min_c = 1
max_c = 2
extend_p = 50
elif carve_skill == CARVING_SKILL_FELYNE_LOW:
min_c = 0
max_c = 1
extend_p = 25
elif carve_skill == CARVING_SKILL_FELYNE_HI:
min_c = 0
max_c = 1
extend_p = 50
elif carve_skill in (CARVING_SKILL_NONE, CARVING_SKILL_PRO):
# Description: Prevents knockbacks from attacks while carving.
return 0
else:
raise ValueError()
return reward_expected_c(min_c, max_c, extend_p)
if __name__ == '__main__':
# in percent
reward_percent = int(sys.argv[1])
if len(sys.argv) > 2:
fixed_rewards = int(sys.argv[2])
else:
fixed_rewards = 1
if len(sys.argv) > 3:
guarenteed_rewards = int(sys.argv[3])
else:
guarenteed_rewards = 3
if len(sys.argv) > 4:
extend_percent = int(sys.argv[4])
else:
extend_percent = 69
min_rewards = guarenteed_rewards - fixed_rewards
max_rewards = 8 - fixed_rewards
if min_rewards < 0:
print "Error: fixed_rewards (%d) must be less than or equal to " \
"guaranteeed_rewards (%d)" % (fixed_rewards, guarenteed_rewards)
sys.exit(1)
total_p = 0.0
expected_attempts = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
p = _reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
expected_attempts += p * reward_count
# probability of getting @reward_count rewards that could be the
# desired item
print "P(C = %d) = %0.4f" % (reward_count, p)
total_p += p
# expected value for number of rewards that could be the desired item
print "E(C) = %0.2f" % expected_attempts
# math check, make sure all possibilities add up to 1, allowing for
# some floating point precision loss.
assert abs(total_p - 1.0) < .00001, "total = %f" % total_p
p_at_least_one = quest_reward_p(reward_percent, min_rewards, max_rewards,
extend_percent)
expected = expected_attempts * reward_percent / 100.0
print "P(N > 0) = %0.2f%%" % p_at_least_one
print "E(N) = %0.4f" % expected

0
mhapi/web/__init__.py Normal file
View File

8
mhapi/web/fcgi.py Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python
from flup.server.fcgi import WSGIServer
from mhapi.web.wsgi import application
if __name__ == '__main__':
WSGIServer(application).run()

122
mhapi/web/wsgi.py Executable file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python
import os
import logging
import threading
from webob import Request, Response, exc
from mhapi.db import MHDB
from mhapi import rewards
DB_VERSION = "20150313"
PREFIX = "/mhapi/"
# good for testing, use 1 hour = 3600 for deployment
MAX_AGE = "60"
logging.basicConfig(filename="/tmp/reward_webapp.log", level=logging.INFO)
class ThreadLocalDB(threading.local):
def __init__(self, path):
threading.local.__init__(self)
self._db = MHDB(path)
def __getattr__(self, name):
return getattr(self._db, name)
class App(object):
def __init__(self):
self.web_path = os.path.dirname(__file__)
self.project_path = os.path.abspath(os.path.join(self.web_path,
"..", ".."))
db_path = os.path.join(self.project_path, "db", "mh4u.db")
self.db = ThreadLocalDB(db_path)
log_path = os.path.join(self.project_path, "web.log")
self.log = logging.getLogger("reward_webapp")
self.log.info("app started")
def __call__(self, environ, start_response):
req = Request(environ)
resp = Response()
resp.charset = "utf8"
if req.path_info in ("", "/", "/index.html"):
resp = self.index(req, resp)
elif req.path_info == PREFIX + "rewards":
resp = self.find_item_rewards(req, resp)
elif req.path_info == PREFIX + "item_name_list":
resp = self.get_all_names(req, resp)
else:
resp = exc.HTTPNotFound()
return resp(environ, start_response)
def index(self, req, resp):
resp.cache_control = "max-age=86400"
html_path = os.path.join(self.web_path, "index.html")
resp.content_type = "text/html"
resp.body = open(html_path, "rb").read()
return resp
def find_item_rewards(self, req, resp):
version = "1"
etag = DB_VERSION + version
resp.cache_control = "public, max-age=" + MAX_AGE
resp.etag = etag
if etag in req.if_none_match:
return exc.HTTPNotModified()
resp.content_type = "text/plain"
item_name = req.params.get("item_name", "").strip()
if not item_name:
resp.body = "Please enter an item name"
else:
item_row = rewards.find_item(self.db, item_name, resp.body_file)
if item_row is not None:
rewards.print_quests_and_rewards(self.db, item_row,
resp.body_file)
return resp
def get_all_names(self, req, resp):
version = "2"
etag = DB_VERSION + version
resp.cache_control = "public, max-age=" + MAX_AGE
resp.etag = etag
if etag in req.if_none_match:
return exc.HTTPNotModified()
resp.content_type = "application/json"
resp.body_file.write("[")
items = self.db.get_item_names()
first = True
for item in items:
if first:
first = False
else:
resp.body_file.write(", ")
resp.body_file.write('"')
resp.body_file.write(item["name"])
resp.body_file.write('"')
resp.body_file.write("]")
return resp
application = App()
if __name__ == '__main__':
from wsgiref.simple_server import make_server
httpd = make_server("", 8080, application)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print "^C"