Coverage for client/base.py : 90%
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 copy import copy
6from random import Random
7from typing import Any, Optional, Sequence, TYPE_CHECKING, cast
8import logging
10# -- third party --
11from gevent import Greenlet
12import gevent
14# -- own --
15from game.base import Game, GameAbort, GameEnded, GameRunner, InputTransaction, Inputlet, Player
16from game.base import TimeLimitExceeded
18# -- typing --
19if TYPE_CHECKING:
20 from client.core import Core # noqa: F401
23# -- code --
24log = logging.getLogger('client.base')
27class ForcedKill(gevent.GreenletExit):
28 pass
31class Theone(Player):
33 def __init__(self, game: Game, uid: int):
34 Player.__init__(self)
35 self.game = game
36 self.uid = uid
38 def reveal(self, obj: Any) -> None:
39 # It's me, server will tell me what the hell these is.
40 g = self.game
41 core = cast(ClientGameRunner, g.runner).core
42 st = g.get_synctag()
43 _, raw = core.game.gamedata_of(g).gexpect([f'Sync:{st}'])
44 if isinstance(obj, (list, tuple)):
45 for o, rd in zip(obj, raw):
46 o.sync(rd)
47 else:
48 obj.sync(raw)
51class Someone(Player):
53 def __init__(self, game: Game, uid: int):
54 Player.__init__(self)
55 self.game = game
56 self.uid = uid
58 def reveal(self, ol: Any) -> None:
59 # Peer player, won't reveal.
60 self.game.get_synctag() # must sync
63class ClientGameRunner(GameRunner):
64 game: Game
65 core: Core
67 def __init__(self, core: Core, g: Game):
68 self.core = core
69 self.game = g
70 super().__init__()
72 def _run(self) -> None:
73 g = self.game
75 import base64
76 s = (id(self) % 1099511627689).to_bytes(8, byteorder='little')[:5]
77 s = base64.b32encode(s).decode('utf-8')
78 self.gr_name = f'{repr(self.game)}:{s[:5]}'
80 g.runner = self
81 g.synctag = 0
82 g.random = Random()
83 core = self.core
84 core.events.game_started.emit(g)
85 params = core.game.params_of(g)
86 items = core.game.items_of(g)
87 players = core.game.players_of(g)
89 try:
90 g.process_action(g.bootstrap(params, items, players))
91 except GameEnded as e:
92 g.winners = e.winners
93 except GameAbort:
94 pass
95 finally:
96 g.ended = True
98 core.events.client_game_finished.emit(g)
100 def pause(self, time: float) -> None:
101 core = self.core
102 if not core.options.testing: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true
103 core.runner.sleep(time)
105 def is_dropped(self, p: Player) -> bool:
106 core = self.core
107 return core.game.is_dropped(self.game, p)
109 def is_aborted(self) -> bool:
110 return False
112 def get_side(self) -> str:
113 return 'client'
115 def user_input(
116 self,
117 entities: Sequence[Any],
118 inputlet: Inputlet,
119 timeout: int = 25,
120 type: str = 'single',
121 trans: Optional[InputTransaction] = None,
122 ):
124 assert type in ('single', 'all', 'any')
125 assert not type == 'single' or len(entities) == 1
127 timeout = max(0, timeout)
129 inputlet.timeout = timeout
130 entities = list(entities)
132 if not trans:
133 with InputTransaction(inputlet.tag(), entities) as trans:
134 return self.user_input(entities, inputlet, timeout, type, trans)
136 g = self.game
137 assert isinstance(g, Game)
138 core = self.core
140 t = {'single': '', 'all': '&', 'any': '|'}[type]
141 tag = 'I{0}:{1}:'.format(t, inputlet.tag())
143 ilets = {e: copy(inputlet) for e in entities}
144 for e in entities:
145 ilets[e].actor = e
147 inputproc: Optional[Greenlet] = None
149 me = core.game.theone_of(g)
151 results = {e: None for e in entities}
153 synctags = {e: g.get_synctag() for e in entities}
154 synctags_r = {v: k for k, v in synctags.items()}
156 def get_player(e):
157 if isinstance(e, Player):
158 return e
159 elif hasattr(e, 'get_player'): 159 ↛ 164line 159 didn't jump to line 164, because the condition on line 159 was never false
160 p = e.get_player()
161 assert isinstance(p, Player), f'{e}.get_player() == {p}, not a Player'
162 return p
163 else:
164 raise Exception(f'Do not know how to process {e}')
166 e2p = {e: get_player(e) for e in entities}
167 p2e = {p: e for e, p in e2p.items()}
169 def input_func(st: str) -> None:
170 gevent.getcurrent().gr_name = 'InputFunc'
171 my = ilets[p2e[me]]
172 with TimeLimitExceeded(timeout + 1, False):
173 _, my = g.emit_event('user_input', (trans, my))
175 core.game.write(g, f'{tag}{st}', my.data())
177 try:
178 for e in entities:
179 g.emit_event('user_input_start', (trans, ilets[e]))
181 if me in p2e: # me involved
182 if not core.game.is_observe(g): 182 ↛ 193line 182 didn't jump to line 193, because the condition on line 182 was never false
183 inputproc = core.runner.spawn(input_func, synctags[p2e[me]])
184 inputproc.game = g
185 gr_current = gevent.getcurrent()
187 @inputproc.link_exception
188 def chain_failure(gr):
189 exc = Exception("input_func failed")
190 exc.__cause__ = gr.exception
191 gr_current.kill(exc)
193 orig_entities = entities[:]
194 inputany_entity = None
196 g.emit_event('user_input_begin_wait_resp', trans) # for replay speed control
197 while entities:
198 # should be [tag, <Data for Inputlet.parse>]
199 # tag likes 'RI?:ChooseOption:2345'
200 tag_, data = core.game.gamedata_of(g).gexpect([f'R{tag}{st}' for st in synctags_r])
201 st = int(tag_.split(':')[2])
202 if st not in synctags_r: 202 ↛ 203line 202 didn't jump to line 203, because the condition on line 202 was never true
203 log.warning('Unexpected sync tag: %d, expecting %s', st, list(synctags_r))
204 continue
206 e = synctags_r[st]
208 my = ilets[e]
210 try:
211 rst = my.parse(data)
212 except Exception:
213 if core.options.testing:
214 raise
215 log.exception('user_input: exception in .process()')
216 rst = None
218 rst = my.post_process(e, rst)
220 g.emit_event('user_input_finish', (trans, my, rst))
222 entities.remove(e)
223 results[e] = rst
225 # also remove from synctags
226 del synctags_r[st]
227 del synctags[e]
229 if type == 'any' and rst is not None:
230 assert not inputany_entity
231 inputany_entity = e
233 g.emit_event('user_input_end_wait_resp', trans) # for replay speed control
235 finally:
236 inputproc and [inputproc.kill(), inputproc.join()] 236 ↛ exitline 236 didn't except from function 'user_input', because the raise on line 214 wasn't executed
238 if type == 'single':
239 return results[orig_entities[0]]
241 elif type == 'any':
242 if not inputany_entity:
243 return None, None
245 return inputany_entity, results[inputany_entity]
247 elif type == 'all': 247 ↛ 251line 247 didn't jump to line 251, because the condition on line 247 was never false
248 # return OrderedDict([(i, results[i]) for i in orig_entities])
249 return {e: results[e] for e in orig_entities}
251 assert False, 'WTF?!'