Skip to content

Commit

Permalink
Merge branch 'master' into bumps
Browse files Browse the repository at this point in the history
  • Loading branch information
Sciguymjm authored Oct 15, 2020
2 parents 04044a1 + 3e66f17 commit 4305e0e
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 15 deletions.
2 changes: 1 addition & 1 deletion CARBALL_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0
5
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ cd carball/
python init.py
```

##### Mac
In MacOS Catalina, zsh replaced bash as the default shell, which may cause permission issues when trying to run `install-protoc.sh` in the above fashion. Simply invoking bash should resolve this issue, like so:
```
git clone https://github.com/SaltieRL/carball
cd carball/
bash ./_travis/install-protoc.sh
python init.py
```
Apple's decision to replace bash as the default shell may foreshadow the removal of bash in a future version of MacOS. In such a case, Homebrew users can [install protoc](http://google.github.io/proto-lens/installing-protoc.html) by replacing `bash ./travis/install-protoc.sh` with `brew install protobuf`.


## Examples / Usage
One of the main data structures used in carball is the pandas.DataFrame, to learn more, see [its wiki page](https://github.com/SaltieRL/carball/wiki/data_frame).

Expand Down
8 changes: 8 additions & 0 deletions api/stats/player_stats.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ message PlayerStats {
optional CarryDribbles ball_carries = 13;
optional CumulativeKickoffStats kickoff_stats = 14;
optional api.stats.DropshotStats dropshot_stats = 15;
optional DemoStats demo_stats = 16;
}

message Controller {
optional bool is_keyboard = 1;
optional float analogue_steering_input_percent = 2;
optional float analogue_throttle_input_percent = 3;
optional float time_ballcam = 4;
optional float time_handbrake = 5;
}

// Stats for carrying
Expand All @@ -100,3 +103,8 @@ message CumulativeKickoffStats {
optional int32 num_time_first_touch = 7;
optional float average_boost_used = 8; // The average amount of boost used over all kickoff events
}

message DemoStats {
optional int32 num_demos_inflicted = 1;
optional int32 num_demos_taken = 2;
}
19 changes: 11 additions & 8 deletions carball/analysis/analysis_manager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging
import time
from typing import Dict, Callable
from typing import Dict, Callable, Union

import pandas as pd
import json
import os
import gzip

from google.protobuf.json_format import _Printer
from typing.io import IO
Expand Down Expand Up @@ -116,20 +117,22 @@ def write_proto_out_to_file(self, file: IO):
raise IOError("Proto files must be binary use open(path,\"wb\")")
ProtobufManager.write_proto_out_to_file(file, self.protobuf_game)

def write_pandas_out_to_file(self, file: IO):
def write_pandas_out_to_file(self, file: Union[IO, gzip.GzipFile]):
"""
Writes the pandas data to the specified file, as bytes.
Writes the pandas data to the specified file, as bytes. File may be a GzipFile object to compress the data
frame.
NOTES:
The data is written as bytes (i.e. in binary), and the buffer mode must be 'wb'.
E.g. open(file_name, 'wb')
E.g. gzip.open(file_name, 'wb')
The file will NOT be human-readable.
:param file: The file object (or a buffer).
"""

if 'b' not in file.mode:
raise IOError("Proto files must be binary use open(path,\"wb\")")
if isinstance(file.mode, str) and 'b' not in file.mode:
raise IOError("Data frame files must be binary use open(path,\"wb\")")
if isinstance(file.mode, int) and file.mode != gzip.WRITE:
raise IOError("Gzip compressed data frame files must be opened in WRITE mode.")
if self.df_bytes is not None:
file.write(self.df_bytes)
elif not self.should_store_frames:
Expand Down Expand Up @@ -229,7 +232,7 @@ def _get_game_metadata(self, game: Game, proto_game: game_pb2.Game) -> Dict[str,
for player in game.players:
player_proto = proto_game.players.add()
ApiPlayer.create_from_player(player_proto, player, self.id_creator)
player_map[str(player.online_id)] = player_proto
player_map[player.online_id] = player_proto

return player_map

Expand Down
4 changes: 2 additions & 2 deletions carball/analysis/events/kickoff_detection/kickoff_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def get_kickoffs_from_game(game: Game, proto_game: game_pb2, id_creation:Callabl
summed_time = smaller_data_frame['game']['delta'][frame:end_frame].sum()
if summed_time > 0:
cur_kickoff.touch_time = summed_time
logger.error("STRAIGHT TIME " + str(time))
logger.error("SUM TIME" + str(summed_time))
logger.info("STRAIGHT TIME " + str(time))
logger.info("SUM TIME" + str(summed_time))
sum_vs_adding_diff = time - summed_time


Expand Down
17 changes: 17 additions & 0 deletions carball/analysis/stats/controls/controls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from logging import getLogger
from typing import Dict

import numpy as np
import pandas as pd

from ....analysis.stats.stats import BaseStat
from ....analysis.stats.utils.pandas_utils import sum_deltas_by_truthy_data
from ....generated.api import game_pb2
from ....generated.api.player_pb2 import Player
from ....generated.api.stats.player_stats_pb2 import PlayerStats
Expand All @@ -20,6 +22,7 @@ def calculate_player_stat(self, player_stat_map: Dict[str, PlayerStats], game: G
for player_key, stats in player_map.items():
try:
player_name = player_map[player_key].name
player_data_frame = data_frame[player_name].copy()

steering_percentage = self.get_analogue_percentage("steer", data_frame, player_name)
throttle_percentage = self.get_analogue_percentage("throttle", data_frame, player_name)
Expand All @@ -29,10 +32,24 @@ def calculate_player_stat(self, player_stat_map: Dict[str, PlayerStats], game: G
controller_stats.is_keyboard = is_keyboard
controller_stats.analogue_steering_input_percent = throttle_percentage
controller_stats.analogue_throttle_input_percent = steering_percentage
if 'ball_cam' in player_data_frame:
time_ballcam = self.get_ballcam_duration(data_frame, player_data_frame)
controller_stats.time_ballcam = time_ballcam
if 'handbrake' in player_data_frame:
time_handbrake = self.get_handbrake_duration(data_frame, player_data_frame)
controller_stats.time_handbrake = time_handbrake
except KeyError as e:
logger.warning('Player never pressed control %s', e)

def get_analogue_percentage(self, column: str, data_frame: pd.DataFrame, player_name: str):
total_frames = len(data_frame[player_name][column])
count = (data_frame[player_name][column] == 0).sum() + (data_frame[player_name][column] == 128).sum() + (data_frame[player_name][column] == 255).sum() + data_frame[player_name][column].isna().sum()
return 100 - ((count * 100) / total_frames)

@staticmethod
def get_ballcam_duration(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64:
return sum_deltas_by_truthy_data(data_frame, player_dataframe.ball_cam)

@staticmethod
def get_handbrake_duration(data_frame: pd.DataFrame, player_dataframe: pd.DataFrame) -> np.float64:
return sum_deltas_by_truthy_data(data_frame, player_dataframe.handbrake)
Empty file.
38 changes: 38 additions & 0 deletions carball/analysis/stats/demos/demos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging
from typing import Dict

import numpy as np
import pandas as pd
from carball.analysis.constants.field_constants import FieldConstants

from carball.analysis.stats.utils.pandas_utils import sum_deltas_by_truthy_data
from ....analysis.stats.stats import BaseStat
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.actor.boost import BOOST_PER_SECOND
from ....json_parser.game import Game

logger = logging.getLogger(__name__)


class DemoStat(BaseStat):
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):
player_demo_counts = {}
player_got_demoed_counts = {}
for demo in game.demos:
attacker = demo['attacker'].online_id
victim = demo['victim'].online_id
if attacker not in player_demo_counts:
player_demo_counts[attacker] = 1
else:
player_demo_counts[attacker] += 1
if victim not in player_got_demoed_counts:
player_got_demoed_counts[victim] = 1
else:
player_got_demoed_counts[victim] += 1
for player in player_demo_counts:
player_stat_map[player].demo_stats.num_demos_inflicted = player_demo_counts[player]
for player in player_got_demoed_counts:
player_stat_map[player].demo_stats.num_demos_taken = player_got_demoed_counts[player]
4 changes: 3 additions & 1 deletion carball/analysis/stats/stats_list.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import List

from carball.analysis.stats.demos.demos import DemoStat
from carball.analysis.stats.dribbles.ball_carry import CarryStat
from carball.analysis.stats.kickoffs.kickoff_stat import KickoffStat
from carball.analysis.stats.possession.per_possession import PerPossessionStat
Expand Down Expand Up @@ -42,7 +43,8 @@ def get_player_stats() -> List[BaseStat]:
SpeedTendencies(),
RumbleItemStat(),
KickoffStat(),
DropshotStats()
DropshotStats(),
DemoStat()
]

@staticmethod
Expand Down
8 changes: 7 additions & 1 deletion carball/json_parser/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ def __repr__(self):
else:
return '%s: %s' % (self.__class__.__name__, self.name)

def _get_player_id(self, online_id):
if type(online_id) == dict:
return online_id['online_id']
return online_id

def create_from_actor_data(self, actor_data: dict, teams: List['Team'], objects: List[str]):
self.name = actor_data['name']
if 'Engine.PlayerReplicationInfo:bBot' in actor_data and actor_data['Engine.PlayerReplicationInfo:bBot']:
Expand All @@ -57,7 +62,8 @@ def create_from_actor_data(self, actor_data: dict, teams: List['Team'], objects:

else:
actor_type = list(actor_data["Engine.PlayerReplicationInfo:UniqueId"]['remote_id'].keys())[0]
self.online_id = actor_data["Engine.PlayerReplicationInfo:UniqueId"]['remote_id'][actor_type]
self.online_id = self._get_player_id(actor_data["Engine.PlayerReplicationInfo:UniqueId"]
['remote_id'][actor_type])
try:
self.score = actor_data["TAGame.PRI_TA:MatchScore"]
except KeyError:
Expand Down
9 changes: 9 additions & 0 deletions carball/tests/export_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from tempfile import NamedTemporaryFile

import gzip
import pytest

from carball.analysis.analysis_manager import AnalysisManager
Expand All @@ -22,6 +23,14 @@ def test(analysis: AnalysisManager):

run_analysis_test_on_replay(test, get_raw_replays()["DEFAULT_3_ON_3_AROUND_58_HITS"], cache=replay_cache)

def test_gzip_export(self, replay_cache):
def test(analysis: AnalysisManager):
with NamedTemporaryFile(mode='wb') as f:
gzip_file = gzip.GzipFile(mode='wb', fileobj=f)
analysis.write_pandas_out_to_file(gzip_file)

run_analysis_test_on_replay(test, get_raw_replays()["DEFAULT_3_ON_3_AROUND_58_HITS"], cache=replay_cache)

def test_unicode_names(self, replay_cache):
def test(analysis: AnalysisManager):
with NamedTemporaryFile(mode='wb') as f:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ numpy==1.18.2
protobuf==3.6.1
pandas==1.0.3
xlrd==1.1.0
boxcars-py==0.1.3
boxcars-py==0.1.*
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def run(self):
version=version_string,
packages=setuptools.find_packages(),
include_package_data=True,
install_requires=['pandas==0.24.2', 'protobuf==3.6.1', 'xlrd==1.1.0', 'numpy==1.17.0', 'boxcars-py==0.1.3'],
install_requires=['pandas==1.0.3', 'protobuf==3.6.1', 'xlrd==1.1.0', 'numpy==1.18.2', 'boxcars-py==0.1.*'],
url='https://github.com/SaltieRL/carball',
keywords=['rocket-league'],
license='Apache 2.0',
Expand Down

3 comments on commit 4305e0e

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carball Benchmarks short_sample

Benchmark suite Current: 4305e0e Previous: 3e66f17 Ratio
carball/tests/benchmarking/benchmarking.py::test_short_sample 0.9724857256532954 iter/sec (stddev: 0.014489486537814192) 0.9068065034710597 iter/sec (stddev: 0.00924296428449896) 0.93

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carball Benchmarks short_dropshot

Benchmark suite Current: 4305e0e Previous: 3e66f17 Ratio
carball/tests/benchmarking/benchmarking.py::test_short_dropshot 0.6707021716982366 iter/sec (stddev: 0.01816230047862237) 0.6936401698877517 iter/sec (stddev: 0.0052686922532796596) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carball Benchmarks intensive_oce_rlcs

Benchmark suite Current: 4305e0e Previous: 3e66f17 Ratio
carball/tests/benchmarking/benchmarking.py::test_intensive_oce_rlcs 0.0484132228330891 iter/sec (stddev: 0.3029039001351704) 0.06058107089228804 iter/sec (stddev: 0.18968435257771193) 1.25

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.