Skip to content

Commit

Permalink
Merge pull request #86 from SaltieRL/boost_fixes
Browse files Browse the repository at this point in the history
Change boost wasted + boost used to use new algorithm
  • Loading branch information
Sciguymjm authored Oct 10, 2018
2 parents b6e8088 + b7961cf commit ceab0c3
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 43 deletions.
4 changes: 4 additions & 0 deletions api/stats/player_stats.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ message Boost {
// Number of the large boosts taken on the enemy side
optional int32 num_stolen_boosts = 9;
optional float average_boost_level = 10;
// Wasted boost from big boosts
optional float wasted_big = 11;
// Wasted boost from small boosts
optional float wasted_small = 12;
}

message Distance {
Expand Down
34 changes: 25 additions & 9 deletions carball/analysis/stats/boost/boost.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@
from ....generated.api import game_pb2
from ....generated.api.player_pb2 import Player
from ....generated.api.stats.player_stats_pb2 import PlayerStats
from ....json_parser.game import Game

from ....json_parser.game import Game, BOOST_PER_SECOND

logger = logging.getLogger(__name__)


class BoostStat(BaseStat):

field_constants = FieldConstants()

def calculate_player_stat(self, player_stat_map: Dict[str, PlayerStats], game: Game, proto_game: game_pb2.Game,
player_map: Dict[str, Player], data_frame: pd.DataFrame):
for player_key, stats in player_stat_map.items():

proto_boost = stats.boost
player_name = player_map[player_key].name
player_data_frame = data_frame[player_name]
player_data_frame['delta'] = game.frames.delta
proto_boost.boost_usage = self.get_player_boost_usage(player_data_frame)

proto_boost.wasted_usage = self.get_player_boost_usage_max_speed(player_data_frame)
Expand All @@ -38,8 +38,23 @@ def calculate_player_stat(self, player_stat_map: Dict[str, PlayerStats], game: G
if 'boost_collect' not in player_data_frame:
logger.warning('%s did not collect any boost', player_key)
else:

previous_frames = player_data_frame.loc[
player_data_frame.index[player_data_frame.boost_collect.fillna(False)] - 1]
# any boost in tank when big is collected is wasted
wasted_big = previous_frames.boost.sum() / 255 * 100

previous_frames = player_data_frame.loc[
player_data_frame.index[~player_data_frame.boost_collect.fillna(True)] - 1]
# delta is the +- of a full tank of boost they would have if there was no limit on boost
delta = ((previous_frames.boost + 30.5) - 255)
# we only want when the delta > 0 since that is wasted boost
wasted_small = delta[delta > 0].sum() / 255 * 100

collection = self.get_player_boost_collection(player_data_frame)
proto_boost.wasted_collection = self.get_player_boost_waste(proto_boost.boost_usage, collection)
proto_boost.wasted_collection = wasted_big + wasted_small
proto_boost.wasted_big = wasted_big
proto_boost.wasted_small = wasted_small

if 'small' in collection and collection['small'] is not None:
proto_boost.num_small_boosts = collection['small']
Expand All @@ -51,9 +66,10 @@ def calculate_player_stat(self, player_stat_map: Dict[str, PlayerStats], game: G

@staticmethod
def get_player_boost_usage(player_dataframe: pd.DataFrame) -> np.float64:
_diff = -player_dataframe.boost.diff()
boost_usage = _diff[_diff > 0].sum() / 255 * 100
return boost_usage
return (BOOST_PER_SECOND * (player_dataframe.delta * player_dataframe.boost_active)).sum() / 255 * 100
# _diff = -player_dataframe.boost.diff()
# boost_usage = _diff[_diff > 0].sum() / 255 * 100
# return boost_usage

@staticmethod
def get_average_boost_level(player_dataframe: pd.DataFrame) -> np.float64:
Expand Down Expand Up @@ -91,11 +107,11 @@ def get_time_with_max_boost(data_frame: pd.DataFrame, player_dataframe: pd.DataF
return sum_deltas_by_truthy_data(data_frame, player_dataframe.boost > 252.45) # 100% visible boost

@staticmethod
def get_time_with_low_boost(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64: # less than 25
def get_time_with_low_boost(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64: # less than 25
return sum_deltas_by_truthy_data(data_frame, player_dataframe.boost < 63.75) # 25 / 100 * 255

@staticmethod
def get_time_with_zero_boost(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64: # at 0 boost
def get_time_with_zero_boost(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64: # at 0 boost
return sum_deltas_by_truthy_data(data_frame, player_dataframe.boost == 0)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion carball/json_parser/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

logger = logging.getLogger(__name__)

BOOST_PER_SECOND = 80 # boost used per second out of 255
BOOST_PER_SECOND = 80 * 1/.93 # boost used per second out of 255
DATETIME_FORMATS = [
'%Y-%m-%d %H-%M-%S',
'%Y-%m-%d:%H-%M'
Expand Down
5 changes: 3 additions & 2 deletions carball/tests/game_creation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ def test(analysis: AnalysisManager):
ratio = (player.stats.positional_tendencies.time_in_front_ball +
player.stats.positional_tendencies.time_behind_ball) / player.time_in_game
local.assertEqual(True, ratio > 0.99)
local.assertGreater(player.stats.positional_tendencies.time_in_front_ball, 0)
local.assertGreater(player.stats.positional_tendencies.time_behind_ball, 0)
# local.assertGreater(player.stats.positional_tendencies.time_in_front_ball, 0)
# local.assertGreater(player.stats.positional_tendencies.time_behind_ball, 0)
local.assertGreater(player.time_in_game, 0)
local.assertGreater(player.stats.speed.time_at_slow_speed, 0)
local.assertGreater(player.stats.boost.average_boost_level, 0)
local.assertGreater(player.stats.boost.wasted_collection, -1)

run_analysis_test_on_replay(test)

Expand Down
29 changes: 26 additions & 3 deletions carball/tests/stats/boost_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,38 @@ def test(analysis: AnalysisManager, boost_value):
proto_game = analysis.get_protobuf_data()
player = proto_game.players[0]
boost = player.stats.boost
self.assertAlmostEqual(boost.boost_usage, boost_value,
delta=9) # TODO: Figgure out a way to calculate boost in a more accurate manner
print("Predicted usage: {}, actual: {}".format(boost.boost_usage, boost_value))
self.assertAlmostEqual(boost.boost_usage, boost_value, delta=1)
# self.assertGreater(boost.average_boost_level, 0)
print(analysis)

run_analysis_test_on_replay(test, get_specific_replays()["BOOST_USED"] + get_specific_replays()["0_BOOST_USED"],
answers=get_specific_answers()["BOOST_USED"] +
get_specific_answers()["0_BOOST_USED"])

def test_boost_feathered(self):
def test(analysis: AnalysisManager, boost_value):
proto_game = analysis.get_protobuf_data()
player = proto_game.players[0]
boost = player.stats.boost
print("Predicted usage: {}, actual: {}".format(boost.boost_usage, boost_value))
self.assertAlmostEqual(boost.boost_usage, boost_value, delta=3)
# self.assertGreater(boost.average_boost_level, 0)

run_analysis_test_on_replay(test, get_specific_replays()["BOOST_FEATHERED"],
answers=get_specific_answers()["BOOST_FEATHERED"])

def test_boost_wasted_collection(self):
def test(analysis: AnalysisManager, boost_value):
proto_game = analysis.get_protobuf_data()
player = proto_game.players[0]
boost = player.stats.boost
self.assertAlmostEqual(boost.wasted_collection, boost_value, delta=1)
# self.assertGreater(boost.average_boost_level, 0)
print(analysis)

# run_analysis_test_on_replay(test, get_specific_replays()["BOOST_WASTED_COLLECTION"],
# answers=get_specific_answers()["BOOST_WASTED_COLLECTION"])

def test_0_used(self):
def test(analysis: AnalysisManager, boost_value):
proto_game = analysis.get_protobuf_data()
Expand Down
103 changes: 75 additions & 28 deletions carball/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ def run_analysis_test_on_replay(unit_test_func: Callable, replay_list=None, answ
:param replay_list: list of replay urls
:return:
"""

def wrapper(replay_file_path, json_file_path, answer=None):
analysis_manager = analyze_replay_file(replay_file_path, json_file_path)
if answer is not None:
unit_test_func(analysis_manager, answer)
else:
unit_test_func(analysis_manager)

run_tests_on_list(wrapper, replay_list, answers=answers)


Expand All @@ -79,7 +81,9 @@ def get_full_replay_list():
'https://cdn.discordapp.com/attachments/493849514680254468/496153554977816576/BOTS_JOINING_AND_LEAVING.replay',
'https://cdn.discordapp.com/attachments/493849514680254468/496153569981104129/BOTS_NO_POSITION.replay',
'https://cdn.discordapp.com/attachments/493849514680254468/496153605074845734/ZEROED_STATS.replay',
'https://cdn.discordapp.com/attachments/493849514680254468/496180938968137749/FAKE_BOTS_SkyBot.replay'
'https://cdn.discordapp.com/attachments/493849514680254468/496180938968137749/FAKE_BOTS_SkyBot.replay',
'https://cdn.discordapp.com/attachments/493849514680254468/497149910999891969/NEGATIVE_WASTED_COLLECTION.replay',
'https://cdn.discordapp.com/attachments/493849514680254468/497191273619259393/WASTED_BOOST_WHILE_SUPER_SONIC.replay'
]


Expand All @@ -93,63 +97,106 @@ def get_raw_replays():
"0_SAVES": ["https://cdn.discordapp.com/attachments/493849514680254468/495735137133264897/0_SAVES.replay"],
"1_AERIAL": ["https://cdn.discordapp.com/attachments/493849514680254468/495735149133168651/1_AERIAL.replay"],
"1_DEMO": ["https://cdn.discordapp.com/attachments/493849514680254468/495735163117109249/1_DEMO.replay"],
"1_DOUBLE_JUMP": ["https://cdn.discordapp.com/attachments/493849514680254468/495735176761049103/1_DOUBLE_JUMP.replay"],
"1_EPIC_SAVE": ["https://cdn.discordapp.com/attachments/493849514680254468/495735191537713153/1_EPIC_SAVE.replay"],
"1_DOUBLE_JUMP": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735176761049103/1_DOUBLE_JUMP.replay"],
"1_EPIC_SAVE": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735191537713153/1_EPIC_SAVE.replay"],
"1_JUMP": ["https://cdn.discordapp.com/attachments/493849514680254468/495735203323576321/1_JUMP.replay"],
"1_NORMAL_SAVE": ["https://cdn.discordapp.com/attachments/493849514680254468/495735215767945234/1_NORMAL_SAVE.replay"],
"1_NORMAL_SAVE": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735215767945234/1_NORMAL_SAVE.replay"],
# Boost
"12_BOOST_PAD_0_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735228422291456/12_BOOST_PAD_0_USED.replay"],
"12_BOOST_PAD_45_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735240321662986/12_BOOST_PAD_45_USED.replay"],
"100_BOOST_PAD_0_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735252233224193/100_BOOST_PAD_0_USED.replay"],
"100_BOST_PAD_100_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735264262488065/100_BOOST_PAD_100_USED.replay"],
"NO_BOOST_PAD_0_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735276338020372/NO_BOOST_PAD_0_USED.replay"],
"NO_BOOST_PAD_33_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/495735288254169109/NO_BOOST_PAD_33_USED.replay"],
"12_AND_100_BOOST_PADS_0_USED": ["https://cdn.discordapp.com/attachments/493849514680254468/496071113768697859/12_AND_100_BOOST_PADS_0_USED.replay"],
"12_BOOST_PAD_0_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735228422291456/12_BOOST_PAD_0_USED.replay"],
"12_BOOST_PAD_45_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735240321662986/12_BOOST_PAD_45_USED.replay"],
"100_BOOST_PAD_0_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735252233224193/100_BOOST_PAD_0_USED.replay"],
"100_BOOST_PAD_100_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735264262488065/100_BOOST_PAD_100_USED.replay"],
"NO_BOOST_PAD_0_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735276338020372/NO_BOOST_PAD_0_USED.replay"],
"NO_BOOST_PAD_33_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735288254169109/NO_BOOST_PAD_33_USED.replay"],
"12_AND_100_BOOST_PADS_0_USED": [
"https://cdn.discordapp.com/attachments/493849514680254468/496071113768697859/12_AND_100_BOOST_PADS_0_USED.replay"],
"WASTED_BOOST_WHILE_SUPER_SONIC": [
"https://cdn.discordapp.com/attachments/493849514680254468/497191273619259393/WASTED_BOOST_WHILE_SUPER_SONIC.replay"],
"CALCULATE_USED_BOOST_WITH_DEMO": [
"https://cdn.discordapp.com/attachments/493849514680254468/497189284651204609/CALCULATE_USED_BOOST_WITH_DEMO.replay"],
"CALCULATE_USED_BOOST_DEMO_WITH_FLIPS": [
"https://cdn.discordapp.com/attachments/493849514680254468/497189968397991937/CALCULATE_USED_BOOST_DEMO_WITH_FLIPS.replay"],
"MORE_THAN_100_BOOST": [
"https://cdn.discordapp.com/attachments/493849514680254468/497190406472204288/MORE_THAN_100_BOOST.replay"],
"USE_BOOST_AFTER_GOAL": [
"https://cdn.discordapp.com/attachments/493849514680254468/497190907309850634/USE_BOOST_AFTER_GOAL.replay"
],
# Kickoffs
"STRAIGHT_KICKOFF_GOAL": ["https://cdn.discordapp.com/attachments/493849514680254468/495735301604376576/Straight_Kickoff_Goal.replay"],
"KICKOFF_NO_TOUCH": ["https://cdn.discordapp.com/attachments/493849514680254468/496034430943756289/NO_KICKOFF.replay"],
"3_KICKOFFS": ["https://cdn.discordapp.com/attachments/493849514680254468/496034443442782208/3_KICKOFFS_4_SHOTS.replay"],
"STRAIGHT_KICKOFF_GOAL": [
"https://cdn.discordapp.com/attachments/493849514680254468/495735301604376576/Straight_Kickoff_Goal.replay"],
"KICKOFF_NO_TOUCH": [
"https://cdn.discordapp.com/attachments/493849514680254468/496034430943756289/NO_KICKOFF.replay"],
"3_KICKOFFS": [
"https://cdn.discordapp.com/attachments/493849514680254468/496034443442782208/3_KICKOFFS_4_SHOTS.replay"],
# hits
"4_SHOTS": ["https://cdn.discordapp.com/attachments/493849514680254468/496034443442782208/3_KICKOFFS_4_SHOTS.replay"],
"KICKOFF_3_HITS": ["https://cdn.discordapp.com/attachments/493849514680254468/496072257928691743/KICKOFF_3_HITS.replay"],
"MID_AIR_PASS": ["https://cdn.discordapp.com/attachments/493849514680254468/495887162928267314/MID_AIR_PASS_GOAL.replay"],
"HIGH_AIR_PASS": ["https://cdn.discordapp.com/attachments/493849514680254468/495887164425633802/HIGH_AIR_PASS_GOAL.replay"],
"GROUND_PASS": ["https://cdn.discordapp.com/attachments/493849514680254468/495887165570678794/GROUNDED_PASS_GOAL.replay"],
"PINCH_GROUND": ["https://cdn.discordapp.com/attachments/493849514680254468/495887167932071947/PINCH_GROUNDED_GOAL.replay"],
"4_SHOTS": [
"https://cdn.discordapp.com/attachments/493849514680254468/496034443442782208/3_KICKOFFS_4_SHOTS.replay"],
"KICKOFF_3_HITS": [
"https://cdn.discordapp.com/attachments/493849514680254468/496072257928691743/KICKOFF_3_HITS.replay"],
"MID_AIR_PASS": [
"https://cdn.discordapp.com/attachments/493849514680254468/495887162928267314/MID_AIR_PASS_GOAL.replay"],
"HIGH_AIR_PASS": [
"https://cdn.discordapp.com/attachments/493849514680254468/495887164425633802/HIGH_AIR_PASS_GOAL.replay"],
"GROUND_PASS": [
"https://cdn.discordapp.com/attachments/493849514680254468/495887165570678794/GROUNDED_PASS_GOAL.replay"],
"PINCH_GROUND": [
"https://cdn.discordapp.com/attachments/493849514680254468/495887167932071947/PINCH_GROUNDED_GOAL.replay"],
}


def get_specific_replays():
raw_map = get_raw_replays()
return {
# BOOSTS
"0_BOOST_COLLECTED": raw_map["NO_BOOST_PAD_0_USED"] + raw_map["NO_BOOST_PAD_33_USED"] + raw_map["KICKOFF_NO_TOUCH"],
"0_BOOST_COLLECTED": raw_map["NO_BOOST_PAD_0_USED"] + raw_map["NO_BOOST_PAD_33_USED"] + raw_map[
"KICKOFF_NO_TOUCH"],
"1_SMALL_PAD": raw_map["12_BOOST_PAD_0_USED"] + raw_map["12_BOOST_PAD_45_USED"],
"1_LARGE_PAD": raw_map["100_BOOST_PAD_0_USED"] + raw_map["100_BOST_PAD_100_USED"],
"0_BOOST_USED": raw_map["12_BOOST_PAD_0_USED"] + raw_map["100_BOOST_PAD_0_USED"] + raw_map["NO_BOOST_PAD_0_USED"] + raw_map["KICKOFF_NO_TOUCH"],
"BOOST_USED": raw_map["12_BOOST_PAD_45_USED"] + raw_map["100_BOST_PAD_100_USED"] + raw_map["NO_BOOST_PAD_33_USED"], # [45, 100, 33]

"1_LARGE_PAD": raw_map["100_BOOST_PAD_0_USED"] + raw_map["100_BOOST_PAD_100_USED"],
"0_BOOST_USED": raw_map["12_BOOST_PAD_0_USED"] + raw_map["100_BOOST_PAD_0_USED"] + raw_map[
"NO_BOOST_PAD_0_USED"] + raw_map["KICKOFF_NO_TOUCH"],
"BOOST_USED": raw_map["12_BOOST_PAD_45_USED"] +
raw_map["100_BOOST_PAD_100_USED"] +
raw_map["NO_BOOST_PAD_33_USED"] +
raw_map["CALCULATE_USED_BOOST_WITH_DEMO"] +
raw_map["CALCULATE_USED_BOOST_DEMO_WITH_FLIPS"] +
raw_map["USE_BOOST_AFTER_GOAL"] +
raw_map["WASTED_BOOST_WHILE_SUPER_SONIC"],
"BOOST_FEATHERED": raw_map["MORE_THAN_100_BOOST"],
"BOOST_WASTED_USAGE": raw_map["WASTED_BOOST_WHILE_SUPER_SONIC"],
"BOOST_WASTED_COLLECTION": raw_map["MORE_THAN_100_BOOST"],
# HITS
"HITS": raw_map["4_SHOTS"] + raw_map["KICKOFF_3_HITS"] + raw_map["12_BOOST_PAD_45_USED"] +
raw_map["MID_AIR_PASS"] + raw_map["HIGH_AIR_PASS"] + raw_map["GROUND_PASS"] +
raw_map["1_NORMAL_SAVE"] + raw_map["1_EPIC_SAVE"] + raw_map["1_AERIAL"],
# + raw_map["PINCH_GROUND"], TODO: Fix pinches to create 2 hits 1 for each person on same frame
# + raw_map["PINCH_GROUND"], TODO: Fix pinches to create 2 hits 1 for each person on same frame
"SHOTS": raw_map["4_SHOTS"] + raw_map["12_BOOST_PAD_45_USED"] +
raw_map["1_EPIC_SAVE"] + raw_map["1_NORMAL_SAVE"],
"PASSES": raw_map["MID_AIR_PASS"] + raw_map["HIGH_AIR_PASS"] + raw_map["GROUND_PASS"],
"AERIALS": raw_map["1_EPIC_SAVE"] + raw_map["1_AERIAL"] + raw_map["HIGH_AIR_PASS"] + raw_map["MID_AIR_PASS"],
}


def get_specific_answers():
specific_replays = get_specific_replays()
return {
# Boost
"0_BOOST_USED": [0] * len(specific_replays["0_BOOST_USED"]),
"BOOST_USED": [45, 100, 33],
"BOOST_USED": [45, 100, 33, 33.33 + 33.33 + 12.15, 33.33, 33.33, 0],
"BOOST_WASTED_USAGE": [33.33],
"BOOST_WASTED_COLLECTION": [6.2],
"BOOST_FEATHERED": [100.0],
# Hits
"HITS": [4, 3, 1, 2, 9, 2, 4, 4, 4],
"SHOTS": [3, 0, 2, 1],
"PASSES": [1, 1, 1],
"AERIALS": [0, 1, 2, 0]
}

0 comments on commit ceab0c3

Please sign in to comment.