feat: Add minimum ringers required in events
Implement Hopcroft-Karp algorithm to determine maximal bipartite matching to determine if every role in an event can be filled without ringers.master
parent
ee4f3715ab
commit
69df0a49e4
|
@ -38,12 +38,49 @@ class Event(app_db.BaseModel):
|
||||||
UniqueConstraint("team_id", "name", "start_time"),
|
UniqueConstraint("team_id", "name", "start_time"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_maximum_matching(self):
|
||||||
|
players_teams_roles = app_db.db.session.query(
|
||||||
|
PlayerTeamRole
|
||||||
|
).join(
|
||||||
|
PlayerTeam
|
||||||
|
).join(
|
||||||
|
PlayerEvent,
|
||||||
|
PlayerTeam.player_id == PlayerEvent.player_id
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == self.team_id
|
||||||
|
).where(
|
||||||
|
PlayerTeam.player_id == PlayerEvent.player_id
|
||||||
|
).where(
|
||||||
|
PlayerEvent.event_id == self.id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
role_map = {}
|
||||||
|
for roles in players_teams_roles:
|
||||||
|
if roles.player_team_id not in role_map:
|
||||||
|
role_map[roles.player_team_id] = []
|
||||||
|
role_map[roles.player_team_id].append(roles.role)
|
||||||
|
import sys
|
||||||
|
print(role_map, file=sys.stderr)
|
||||||
|
|
||||||
|
required_roles = [
|
||||||
|
PlayerTeamRole.Role.PocketScout,
|
||||||
|
PlayerTeamRole.Role.FlankScout,
|
||||||
|
PlayerTeamRole.Role.PocketSoldier,
|
||||||
|
PlayerTeamRole.Role.Roamer,
|
||||||
|
PlayerTeamRole.Role.Demoman,
|
||||||
|
PlayerTeamRole.Role.Medic,
|
||||||
|
]
|
||||||
|
graph = BipartiteGraph(role_map, required_roles)
|
||||||
|
return graph.hopcroft_karp()
|
||||||
|
|
||||||
def get_discord_content(self):
|
def get_discord_content(self):
|
||||||
start_timestamp = int(self.start_time.timestamp())
|
start_timestamp = int(self.start_time.timestamp())
|
||||||
players = list(self.players)
|
players = list(self.players)
|
||||||
# players with a role should be sorted first
|
# players with a role should be sorted first
|
||||||
players.sort(key=lambda p: p.role is not None, reverse=True)
|
players.sort(key=lambda p: p.role is not None, reverse=True)
|
||||||
players_info = []
|
players_info = []
|
||||||
|
matchings = self.get_maximum_matching()
|
||||||
|
ringers_needed = 6 - matchings
|
||||||
|
|
||||||
for player in players:
|
for player in players:
|
||||||
player_info = "- "
|
player_info = "- "
|
||||||
|
@ -60,6 +97,10 @@ class Event(app_db.BaseModel):
|
||||||
|
|
||||||
players_info.append(player_info)
|
players_info.append(player_info)
|
||||||
|
|
||||||
|
ringers_needed_msg = ""
|
||||||
|
if ringers_needed > 0:
|
||||||
|
ringers_needed_msg = f" **({ringers_needed} ringer(s) needed)**"
|
||||||
|
|
||||||
return "\n".join([
|
return "\n".join([
|
||||||
f"# {self.name}",
|
f"# {self.name}",
|
||||||
"",
|
"",
|
||||||
|
@ -67,6 +108,7 @@ class Event(app_db.BaseModel):
|
||||||
"",
|
"",
|
||||||
f"<t:{start_timestamp}:f>",
|
f"<t:{start_timestamp}:f>",
|
||||||
"\n".join(players_info),
|
"\n".join(players_info),
|
||||||
|
f"Max bipartite matching size: {matchings}" + ringers_needed_msg,
|
||||||
"",
|
"",
|
||||||
"[Confirm attendance here]" +
|
"[Confirm attendance here]" +
|
||||||
f"(https://availabili.tf/team/id/{self.team.id}/events/{self.id})",
|
f"(https://availabili.tf/team/id/{self.team.id}/events/{self.id})",
|
||||||
|
@ -130,17 +172,20 @@ class EventSchema(spec.BaseModel):
|
||||||
created_at=model.created_at,
|
created_at=model.created_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
class EventPlayersSchema(spec.BaseModel):
|
#class EventPlayersSchema(spec.BaseModel):
|
||||||
players: list["PlayerEventRolesSchema"]
|
# players: list["PlayerEventRolesSchema"]
|
||||||
|
#
|
||||||
@classmethod
|
# @classmethod
|
||||||
def from_model(cls, model: Event) -> "EventPlayersSchema":
|
# def from_model(cls, model: Event) -> "EventPlayersSchema":
|
||||||
return cls(
|
# return cls(
|
||||||
players=[PlayerEventRolesSchema.from_model(player) for player in model.players],
|
# players=[PlayerEventRolesSchema.from_model(player) for player in model.players],
|
||||||
roles=[RoleSchema.from_model(player.role.role) for player in model.players if player.role],
|
# roles=[RoleSchema.from_model(player.role.role) for player in model.players if player.role],
|
||||||
)
|
# )
|
||||||
|
|
||||||
|
|
||||||
from models.team import Team
|
from models.team import Team
|
||||||
from models.player_event import PlayerEvent
|
from models.player_event import PlayerEvent, PlayerEventRolesSchema
|
||||||
from models.team_integration import TeamDiscordIntegration
|
from models.team_integration import TeamDiscordIntegration
|
||||||
|
from models.player_team import PlayerTeam
|
||||||
|
from models.player_team_role import PlayerTeamRole
|
||||||
|
from utils.bipartite_graph import BipartiteGraph
|
||||||
|
|
Loading…
Reference in New Issue