Skip to content

Commit

Permalink
Merge pull request #35 from EboMike/cleanup
Browse files Browse the repository at this point in the history
Added new datapoints that were requested.
  • Loading branch information
spookybear0 authored Mar 30, 2024
2 parents 80f8d4d + 7518013 commit 8eb441d
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 13 deletions.
2 changes: 2 additions & 0 deletions assets/html/game/scorecard_sm5.html
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ <h2 class="{{ team.class_name }}_team_header">{{ team.name }} ({{ team.score }})
<th>Time in game</th>
<th>You zapped</th>
<th>Zapped you</th>
<th>Hit ratio</th>
<th>You missiled</th>
<th>Missiled you</th>
<th><a href="https://lfstats.com/pages/aboutSM5#:~:text=Lose%203%20lives-,MVP%20Points,-All%20Players" target="_blank">MVP Points</a></th>
Expand All @@ -312,6 +313,7 @@ <h2 class="{{ team.class_name }}_team_header">{{ team.name }} ({{ team.score }})
<td class="scorecard_player_stat"><canvas class="mini-chart" id="time_in_game_{{player.entity_end_id}}"></canvas></td>
<td class="scorecard_player_stat">{{ player.you_zapped }}</td>
<td class="scorecard_player_stat">{{ player.zapped_you }}</td>
<td class="scorecard_player_stat">{{ player.hit_ratio }}</td>
<td class="scorecard_player_stat">{{ player.you_missiled }}</td>
<td class="scorecard_player_stat">{{ player.missiled_you }}</td>
<td class="scorecard_player_stat">{{ player.mvp_points }}</td>
Expand Down
10 changes: 7 additions & 3 deletions assets/html/game/sm5.html
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,11 @@ <h2 style="font-size: 20px;" class="team_score {{ team.css_class }}">{{ team.ele
<th title="Current rating after game has been played"><p>Rating</p></th>
{% endif %}
<th><p>Score</p></th>
<th><p>Lives</p></th>
<th><p>Positive Score</p></th>
<th><p>Points/Minute</p></th>
<th><p>Lives Left</p></th>
<th><p>Alive</p></th>
<th><p>Shots</p></th>
<th><p>Shots Left</p></th>
<th><p>Accuracy</p></th>
<th><p>K/D</p></th>
<th><p>States</p></th>
Expand All @@ -303,7 +305,7 @@ <h2 style="font-size: 20px;" class="team_score {{ team.css_class }}">{{ team.ele
{% for player in team_rosters[team] %}
{% set entity = player.entity_start %}
{% set entity_end = player.entity_end %}
{% set sm5_stats = get_sm5stats(entity) %}
{% set sm5_stats = player_stats[entity_end.id] %}

<tr>
<td class="role fixed-column" style="width: 30px"><p><img src="/assets/sm5/roles/{{entity.role|string|lower}}.png" alt="role image" width="30" height="30"></p></td>
Expand All @@ -320,6 +322,8 @@ <h2 style="font-size: 20px;" class="team_score {{ team.css_class }}">{{ team.ele
{% else %}
<td title="Scorecard"><a href="/game/sm5/{{game.id}}/scorecard/{{entity_end.id}}">{{ entity_end.score }}</a></td>
{% endif %}
<td><p>{{ gross_positive_points[entity_end.id] }}</p></td>
<td><p>{{ points_per_minute[entity_end.id] }}</p></td>
<td><p>{{ sm5_stats.lives_left }}</p></td>
<td class="scorecard_player_stat"><canvas class="mini-chart" id="time_in_game_{{entity_end.id}}"></canvas></td>
<td><p>{{ sm5_stats.shots_left }}</p></td>
Expand Down
2 changes: 1 addition & 1 deletion db/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class EventType(Enum):
PENALTY = "0600"
ACHIEVEMENT = "0900" # Arguments: "(entity 1)", " completes an achievement!"
REWARD = "0902" # Arguments: "(entity 1)", " earns a reward!"
BASE_AWARDED = "0B03" # (technically #0B03 in hex)
BASE_AWARDED = "0B03" # (technically #0B03 in hex) Arguments: "(entity 1)", " is awarded ", "(entity 2)"

# laserball events

Expand Down
27 changes: 22 additions & 5 deletions handlers/game/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@
from db.types import Team
from sanic import exceptions
from helpers.statshelper import sentry_trace, get_sm5_team_score_graph_data, get_sm5_player_alive_times, \
get_player_state_distribution, get_player_state_distribution_pie_chart
get_player_state_distribution, get_player_state_distribution_pie_chart, get_points_per_minute, \
get_sm5_score_components, get_sm5_gross_positive_score
from numpy import arange
from typing import List, Optional


async def get_entity_end(entity) -> Optional[EntityEnds]:
return await EntityEnds.filter(entity=entity).first()

async def get_sm5stats(entity) -> Optional[SM5Stats]:
return await SM5Stats.filter(entity=entity).first()

async def get_laserballstats(entity) -> Optional[LaserballStats]:
return await LaserballStats.filter(entity=entity).first()

Expand Down Expand Up @@ -50,6 +48,23 @@ async def game_index(request: Request, type: str, id: int) -> str:
all_players if player
}

points_per_minute = {
player.entity_end.id: get_points_per_minute(player.entity_end) for player in all_players
}

player_stats = {
player.entity_end.id: await SM5Stats.filter(entity=player.entity_start).first() for player in all_players
}

score_components = {
player.entity_end.id: await get_sm5_score_components(game, player_stats[player.entity_end.id],
player.entity_start) for player in all_players
}

gross_positive_points = {
player.entity_end.id: get_sm5_gross_positive_score(score_components[player.entity_end.id]) for player in all_players
}

uptime_values = {
player.entity_end.id: get_player_state_distribution_pie_chart(
await get_player_state_distribution(player.entity_start, player.entity_end,
Expand Down Expand Up @@ -80,8 +95,10 @@ async def game_index(request: Request, type: str, id: int) -> str:
scores=scores,
game=game,
time_in_game_values=time_in_game_values,
player_stats=player_stats,
uptime_values=uptime_values,
get_sm5stats=get_sm5stats,
gross_positive_points=gross_positive_points,
points_per_minute=points_per_minute,
score_chart_labels=[t for t in arange(0, 900000 // 1000 // 60 + 0.5, 0.5)],
score_chart_data=score_chart_data,
win_chance_before_game=win_chance_before_game,
Expand Down
7 changes: 7 additions & 0 deletions handlers/game/scorecard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from numpy import arange
from sanic import Request

from helpers.formattinghelper import create_ratio_string
from helpers.gamehelper import SM5_STATE_LABEL_MAP, SM5_STATE_COLORS, get_players_from_team
from shared import app
from typing import List
Expand All @@ -20,6 +21,11 @@ def _chart_values(values: list[int]) -> str:
return "[%s]" % ", ".join([str(value) for value in values])



def _calc_ratio(numerator: int, divisor: int) -> float:
return float(numerator) / float(divisor) if divisor else 0.0


def _chart_strings(values: list[str]) -> str:
"""Creates a string to be used in JavaScript for a list of strings.
Expand Down Expand Up @@ -120,6 +126,7 @@ async def scorecard(request: Request, type: str, id: int, entity_end_id: int) ->
"mvp_points": "%.2f" % await player_sm5_stats[player.id].mvp_points(),
"you_zapped": await count_zaps(game, entity_start.entity_id, player.entity_id),
"zapped_you": await count_zaps(game, player.entity_id, entity_start.entity_id),
"hit_ratio": create_ratio_string(_calc_ratio(await count_zaps(game, entity_start.entity_id, player.entity_id), await count_zaps(game, player.entity_id, entity_start.entity_id))),
"you_missiled": await count_missiles(game, entity_start.entity_id, player.entity_id),
"missiled_you": await count_missiles(game, player.entity_id, entity_start.entity_id),
"state_distribution": get_player_state_distribution_pie_chart(await get_player_state_distribution(player, player_entity_ends[player.id],
Expand Down
30 changes: 30 additions & 0 deletions helpers/formattinghelper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Various functions to format values."""


def create_ratio_string(ratio: float) -> str:
"""Converts a ratio to a string, such as "1:2" or "3:4"."""
if ratio == 0.0:
return "0:0"

factor = 1

if ratio < 0.0:
ratio *= -1.0
factor = -1

while True:
if ratio >= 0.95:
return "%.2g:%d" % (ratio, factor)

if ratio < 0.12:
ratio *= 10
factor *= 10
elif ratio < 0.22:
ratio *= 5
factor *= 5
elif ratio < 0.4:
ratio *= 3
factor *= 3
else:
ratio *= 2
factor *= 2
19 changes: 18 additions & 1 deletion helpers/statshelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def get_sm5_score_components(game: SM5Game, stats: SM5Stats, entity_start:
Each key is a component ("Missiles", "Nukes", etc), and the value is the amount of
points - positive or negative - the player got for all these."""
bases_destroyed = await (game.events.filter(type=EventType.DESTROY_BASE).
bases_destroyed = await (game.events.filter(Q(type=EventType.DESTROY_BASE) | Q(type=EventType.BASE_AWARDED)).
filter(arguments__filter={"0": entity_start.entity_id}).count())

# Scores taken from https://www.iplaylaserforce.com/games/space-marines-sm5/
Expand All @@ -55,6 +55,23 @@ async def get_sm5_score_components(game: SM5Game, stats: SM5Stats, entity_start:
}


def get_sm5_gross_positive_score(score_components: dict[str, int]) -> int:
"""Returns the gross positive SM5 score (i.e. the score with all the negative components taken out.
Args:
score_components: The score components as returned by get_sm5_score_components().
Returns:
The positive part of the score (i.e. without penalties like getting zapped or missiling their own team)."""
return sum([
value for value in score_components.values() if value > 0
])


def get_points_per_minute(entity: EntityEnds) -> int:
"""Returns the points per minute scored for the duration the player was in the game."""
return int(entity.score * 60000 / entity.time) if entity.time > 0 else 0


def get_sm5_kd_ratio(stats: SM5Stats) -> float:
"""Returns the K/D for a player.
Expand Down
5 changes: 5 additions & 0 deletions tests/helpers/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ async def create_destroy_base_event(time_millis: int, destroying_entity_id: str,
["4", str(time_millis), EventType.DESTROY_BASE, destroying_entity_id, " destroys ", base_entity_str])


async def create_award_base_event(time_millis: int, destroying_entity_id: str, base_entity_str: str) -> Events:
return await create_event_from_data(
["4", str(time_millis), EventType.BASE_AWARDED, destroying_entity_id, " is awarded ", base_entity_str])


async def create_mission_end_event(time_millis) -> Events:
return await create_event_from_data(["4", str(time_millis), EventType.MISSION_END, "* Mission End *"])

Expand Down
25 changes: 25 additions & 0 deletions tests/helpers/formattinghelper_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest

from helpers.formattinghelper import create_ratio_string


class TestFormattingHelper(unittest.TestCase):
def test_create_ratio_string(self):
self.assertEqual("1:1", create_ratio_string(1.0))
self.assertEqual("0:0", create_ratio_string(0.0))
self.assertEqual("1:-1", create_ratio_string(-1.0))
self.assertEqual("5:1", create_ratio_string(5.0))
self.assertEqual("5.5:1", create_ratio_string(5.5))
self.assertEqual("10:1", create_ratio_string(10.0))
self.assertEqual("1:10", create_ratio_string(0.1))
self.assertEqual("1:5", create_ratio_string(0.2))
self.assertEqual("1:3", create_ratio_string(0.333333))
self.assertEqual("1:2", create_ratio_string(0.5))
self.assertEqual("1.3:2", create_ratio_string(0.6666666))
self.assertEqual("1:100", create_ratio_string(0.01))
self.assertEqual("1:300", create_ratio_string(0.00333333))



if __name__ == '__main__':
unittest.main()
38 changes: 35 additions & 3 deletions tests/helpers/statshelper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from db.sm5 import SM5Game, SM5Stats
from db.types import Team
from helpers.statshelper import count_zaps, get_sm5_kd_ratio, get_sm5_score_components, \
get_sm5_single_player_score_graph_data, get_sm5_single_team_score_graph_data, get_sm5_team_score_graph_data
get_sm5_single_player_score_graph_data, get_sm5_single_team_score_graph_data, get_sm5_team_score_graph_data, \
get_sm5_gross_positive_score, get_points_per_minute
from tests.helpers.environment import setup_test_database, ENTITY_ID_1, ENTITY_ID_2, get_sm5_game_id, \
teardown_test_database, create_destroy_base_event, add_entity, get_red_team, get_green_team, add_sm5_score
teardown_test_database, create_destroy_base_event, add_entity, get_red_team, get_green_team, add_sm5_score, \
create_award_base_event


class TestStatsHelper(unittest.IsolatedAsyncioTestCase):
Expand All @@ -29,6 +31,9 @@ async def test_get_sm5_score_components(self):
await game.events.add(await create_destroy_base_event(time_millis=20000,
destroying_entity_id=ENTITY_ID_1,
base_entity_str="@reactor"))
await game.events.add(await create_award_base_event(time_millis=20000,
destroying_entity_id=ENTITY_ID_1,
base_entity_str="@yellow_base"))
entity_start, entity_end = await add_entity(entity_id=ENTITY_ID_1, team=get_red_team())

stats = SM5Stats(
Expand All @@ -44,14 +49,41 @@ async def test_get_sm5_score_components(self):
self.assertDictEqual({
"Missiles": 1500,
"Zaps": 500,
"Bases": 2002,
"Bases": 3003,
"Nukes": 3500,
"Zap own team": -1100,
"Missiled own team": -6500,
"Got zapped": -340,
"Got missiled": -2300,
}, await get_sm5_score_components(game, stats, entity_start))

def test_get_sm5_gross_positive_score(self):
net_score = get_sm5_gross_positive_score({
"Zaps": 3000,
"Forgot to take out the trash": -200,
"Nukes": 500,
"Got missiled": -400,
})

# The final score should only be the positive parts (3000 and 500).
self.assertEqual(3500, net_score)

async def test_get_points_per_minute(self):
entity, entity_end = await add_entity(ENTITY_ID_1, team=get_red_team(), end_time_millis=900000, score=3000)

points_per_minute = get_points_per_minute(entity_end)

# 3000 points in 15 minutes.
self.assertEqual(200, points_per_minute)

async def test_get_points_per_minute_zero_time(self):
entity, entity_end = await add_entity(ENTITY_ID_1, team=get_red_team(), end_time_millis=0, score=3000)

points_per_minute = get_points_per_minute(entity_end)

self.assertEqual(0, points_per_minute)


async def test_get_kd_ratio(self):
stats = SM5Stats(
shot_opponent=10,
Expand Down

0 comments on commit 8eb441d

Please sign in to comment.