Coverage for server/parts/matching.py : 98%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2from __future__ import annotations
4# -- stdlib --
5from typing import Dict, List, Set, TYPE_CHECKING, Tuple
6import logging
8# -- third party --
9from typing_extensions import TypedDict
11# -- own --
12from server.endpoint import Client
13from server.utils import command
14from utils.misc import LoopBreaker
15import wire
17# -- typing --
18if TYPE_CHECKING:
19 from server.core import Core # noqa: F401
22# -- code --
23log = logging.getLogger('Matching')
26class MatchingAssocOnClient(TypedDict):
27 modes: List[str]
30def Au(self: Matching, u: Client) -> MatchingAssocOnClient:
31 return u._[self]
34class Matching(object):
35 def __init__(self, core: Core):
36 self.core = core
38 core.events.user_state_transition += self.handle_user_state_transition
39 core.events.core_initialized += self.handle_core_initialized
41 D = core.events.client_command
42 D[wire.StartMatching] += self._start_matching
44 self.outstanding: Dict[str, Set[Client]] = {}
46 def __repr__(self) -> str:
47 return self.__class__.__name__
49 def handle_core_initialized(self, ev: Core) -> Core:
50 from thb import modes
51 order = list(modes)
52 order.sort(key=lambda v: modes[v].n_persons, reverse=True)
54 for m in order:
55 self.outstanding[m] = set()
57 return ev
59 def handle_user_state_transition(self, ev: Tuple[Client, str, str]) -> Tuple[Client, str, str]:
60 c, f, t = ev
62 if t in ('room', 'ready', 'game'):
63 self._clear(c)
65 elif t == 'lobby':
66 c.write(wire.StartMatching(modes=Au(self, c)['modes']))
68 elif t == 'connected':
69 assoc: MatchingAssocOnClient = {
70 'modes': [],
71 }
72 c._[self] = assoc
74 return ev
76 # ----- Public Methods -----
77 def do_match(self) -> None:
78 core = self.core
79 from thb import modes
81 for loop in LoopBreaker(False):
82 for m in self.outstanding:
83 candidates = []
84 avail = self.outstanding[m]
85 cls = modes[m]
86 if len(avail) < cls.n_persons:
87 continue
89 for _ in range(cls.n_persons):
90 u = avail.pop()
91 assert core.lobby.state_of(u) == 'lobby'
92 candidates.append(u)
94 for u in candidates:
95 self._clear(u)
97 g = core.room.create_game(cls, "匹配的游戏", {})
98 for u in candidates:
99 core.room.join_game(g, u)
101 loop.cont()
102 break
104 # ----- Methods -----
105 def _clear(self, u: Client) -> None:
106 Au(self, u)['modes'] = []
107 for m, s in self.outstanding.items():
108 s.discard(u)
110 # ----- Client Commands -----
111 @command('lobby')
112 def _start_matching(self, u: Client, ev: wire.StartMatching) -> None:
113 from thb import modes
115 self._clear(u)
116 filtered = list(set(modes) & set(ev.modes))
117 Au(self, u)['modes'] = filtered
118 u.write(wire.StartMatching(modes=filtered))
119 if not filtered: 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 return
122 for m in filtered:
123 self.outstanding.setdefault(m, set()).add(u)
125 self.do_match()