Skip to content

Commit 3aedb12

Browse files
authored
v2 beta (#340)
* beta start * update lock * get ticks * time since cols * fix error * fix the test again * formatting * adding clock
1 parent 92686b3 commit 3aedb12

16 files changed

+493
-312
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
- name: Test
8282
shell: bash
8383
run: |
84-
poetry run coverage run -m pytest --durations=10 -s --traceconfig --log-cli-level=DEBUG tests/
84+
poetry run coverage run -m pytest --durations=10
8585
poetry run coverage report -m
8686
8787
# - name: Archive code coverage results

.github/workflows/release.yml

-13
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@ jobs:
3232
- name: Install awpy
3333
run: |
3434
poetry install --no-interaction
35-
36-
# - name: Publish to test PyPI
37-
# run: |
38-
# poetry publish --build --repository testpypi --username __token__ --password ${{ secrets.TEST_PYPI_API_TOKEN }} -vvv
39-
40-
- name: Debug Step
41-
run: |
42-
echo "Listing directory contents"
43-
ls -la
44-
echo "Checking Poetry version"
45-
poetry --version
46-
echo "Checking installed packages"
47-
poetry show
4835
4936
- name: Publish to PyPI
5037
run: |

awpy/demo.py

+34-14
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@
1010
from demoparser2 import DemoParser # pylint: disable=E0611
1111
from loguru import logger
1212

13-
from awpy.parsers import (
13+
from awpy.parsers.clock import parse_times
14+
from awpy.parsers.events import (
1415
parse_bomb,
1516
parse_damages,
1617
parse_grenades,
1718
parse_infernos,
1819
parse_kills,
19-
parse_rounds,
2020
parse_smokes,
21-
parse_ticks,
2221
parse_weapon_fires,
2322
)
23+
from awpy.parsers.rounds import parse_rounds
24+
from awpy.parsers.ticks import parse_ticks
2425
from awpy.utils import apply_round_num
2526

2627
PROP_WARNING_LIMIT = 40
@@ -193,21 +194,40 @@ def _parse_events(self) -> None:
193194
raise ValueError(no_events_error_msg)
194195

195196
if self.parse_rounds is True:
196-
self.rounds = parse_rounds(self.parser)
197+
self.rounds = parse_rounds(
198+
self.parser, self.events
199+
) # Must pass parser for round start/end events
197200

198-
self.kills = apply_round_num(self.rounds, parse_kills(self.events))
199-
self.damages = apply_round_num(self.rounds, parse_damages(self.events))
200-
self.bomb = apply_round_num(self.rounds, parse_bomb(self.events))
201-
self.smokes = apply_round_num(
202-
self.rounds, parse_smokes(self.events), tick_col="start_tick"
201+
self.kills = parse_times(
202+
apply_round_num(self.rounds, parse_kills(self.events)), self.rounds
203203
)
204-
self.infernos = apply_round_num(
205-
self.rounds, parse_infernos(self.events), tick_col="start_tick"
204+
self.damages = parse_times(
205+
apply_round_num(self.rounds, parse_damages(self.events)), self.rounds
206206
)
207-
self.weapon_fires = apply_round_num(
208-
self.rounds, parse_weapon_fires(self.events)
207+
self.bomb = parse_times(
208+
apply_round_num(self.rounds, parse_bomb(self.events)), self.rounds
209+
)
210+
self.smokes = parse_times(
211+
apply_round_num(
212+
self.rounds, parse_smokes(self.events), tick_col="start_tick"
213+
),
214+
self.rounds,
215+
tick_col="start_tick",
216+
)
217+
self.infernos = parse_times(
218+
apply_round_num(
219+
self.rounds, parse_infernos(self.events), tick_col="start_tick"
220+
),
221+
self.rounds,
222+
tick_col="start_tick",
223+
)
224+
self.weapon_fires = parse_times(
225+
apply_round_num(self.rounds, parse_weapon_fires(self.events)),
226+
self.rounds,
227+
)
228+
self.grenades = parse_times(
229+
apply_round_num(self.rounds, parse_grenades(self.parser)), self.rounds
209230
)
210-
self.grenades = apply_round_num(self.rounds, parse_grenades(self.parser))
211231

212232
# Parse ticks
213233
if self.parse_ticks is True:

awpy/parsers/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Module for specific parsing functions."""

awpy/parsers/clock.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Module for time and clock parsing functions."""
2+
3+
import math
4+
from typing import Literal, Union
5+
6+
import pandas as pd
7+
8+
ROUND_START_DEFAULT_TIME_IN_SECS = 20
9+
FREEZE_DEFAULT_TIME_IN_SECS = 115
10+
BOMB_DEFAULT_TIME_IN_SECS = 40
11+
12+
13+
def parse_clock(
14+
seconds_since_phase_change: int,
15+
max_time_ticks: Union[Literal["start", "freeze", "bomb"], int],
16+
tick_rate: int = 64,
17+
) -> str:
18+
"""Parse the remaining time in a round or phase to a clock string.
19+
20+
Args:
21+
seconds_since_phase_change (int): The number of seconds since the phase change.
22+
max_time_ticks (Union[Literal['start', 'freeze', 'bomb'], int]): The maximum
23+
time in ticks for the phase.
24+
tick_rate (int, optional): The tick rate of the server. Defaults to 64.
25+
26+
Returns:
27+
str: The remaining time in MM:SS format.
28+
"""
29+
if max_time_ticks == "start":
30+
max_time_ticks = ROUND_START_DEFAULT_TIME_IN_SECS * tick_rate
31+
elif max_time_ticks == "freeze":
32+
max_time_ticks = FREEZE_DEFAULT_TIME_IN_SECS * tick_rate
33+
elif max_time_ticks == "bomb":
34+
max_time_ticks = BOMB_DEFAULT_TIME_IN_SECS * tick_rate
35+
36+
# Calculate the remaining time in ticks
37+
remaining_ticks = max_time_ticks - seconds_since_phase_change
38+
39+
# Convert remaining ticks to total seconds
40+
remaining_seconds = remaining_ticks / tick_rate
41+
42+
# Round up the seconds
43+
remaining_seconds = math.ceil(remaining_seconds)
44+
45+
# Calculate minutes and seconds
46+
minutes = remaining_seconds // 60
47+
seconds = remaining_seconds % 60
48+
49+
# Format as MM:SS with leading zeros
50+
return f"{int(minutes):02}:{int(seconds):02}"
51+
52+
53+
def _find_clock_time(row: pd.Series) -> str:
54+
"""Find the clock time for a row.
55+
56+
Args:
57+
row: A row from a dataframe with ticks_since_* columns.
58+
"""
59+
times = {
60+
"start": row["ticks_since_round_start"],
61+
"freeze": row["ticks_since_freeze_time_end"],
62+
"bomb": row["ticks_since_bomb_plant"],
63+
}
64+
# Filter out NA values and find the key with the minimum value
65+
min_key = min((k for k in times if pd.notna(times[k])), key=lambda k: times[k])
66+
return parse_clock(times[min_key], min_key)
67+
68+
69+
def parse_times(
70+
df: pd.DataFrame, rounds_df: pd.DataFrame, tick_col: str = "tick"
71+
) -> pd.DataFrame:
72+
"""Adds time_since_* columns to the dataframe.
73+
74+
Args:
75+
df (pd.DataFrame): The dataframe to add the time columns to.
76+
rounds_df (pd.DataFrame): The rounds dataframe.
77+
tick_col (str): The column name of the tick column.
78+
79+
Returns:
80+
pd.DataFrame: The dataframe with the timesince_* columns added.
81+
"""
82+
if tick_col not in df.columns:
83+
tick_col_missing_msg = f"{tick_col} not found in dataframe."
84+
raise ValueError(tick_col_missing_msg)
85+
86+
df_with_round_info = df.merge(rounds_df, on="round", how="left")
87+
df_with_round_info["ticks_since_round_start"] = (
88+
df_with_round_info[tick_col] - df_with_round_info["start"]
89+
)
90+
df_with_round_info["ticks_since_freeze_time_end"] = (
91+
df_with_round_info[tick_col] - df_with_round_info["freeze_end"]
92+
)
93+
df_with_round_info["ticks_since_bomb_plant"] = (
94+
df_with_round_info[tick_col] - df_with_round_info["bomb_plant"]
95+
)
96+
97+
# Apply the function to the selected columns
98+
for col in df_with_round_info.columns:
99+
if col.startswith("ticks_since_"):
100+
df_with_round_info[col] = (
101+
df_with_round_info[col]
102+
.map(lambda x: pd.NA if x < 0 else x)
103+
.astype(pd.Int64Dtype())
104+
)
105+
106+
df_with_round_info = df_with_round_info.drop(
107+
columns=[
108+
"start",
109+
"freeze_end",
110+
"end",
111+
"official_end",
112+
"winner",
113+
"reason",
114+
"bomb_plant",
115+
]
116+
)
117+
118+
df_with_round_info["clock"] = df_with_round_info.apply(_find_clock_time, axis=1)
119+
120+
return df_with_round_info

0 commit comments

Comments
 (0)