3
3
import random
4
4
import logging
5
5
from copy import deepcopy
6
- from typing import Any , Dict , Tuple , List
6
+ from typing import Any , Dict , Tuple , List , Union
7
7
from zulip_bots .test_lib import BotTestCase
8
8
import operator
9
9
import random
@@ -16,13 +16,15 @@ def __init__(self, message: str) -> None:
16
16
def __str__ (self ) -> str :
17
17
return self .message
18
18
19
+
19
20
class SamePlayerMove (Exception ):
20
21
def __init__ (self , message : str ) -> None :
21
22
self .message = message
22
23
23
24
def __str__ (self ) -> str :
24
25
return self .message
25
26
27
+
26
28
class GameAdapter (object ):
27
29
'''
28
30
Class that serves as a template to easily
@@ -40,9 +42,9 @@ def __init__(
40
42
model : Any ,
41
43
gameMessageHandler : Any ,
42
44
rules : str ,
43
- max_players : int = 2 ,
44
- min_players : int = 2 ,
45
- supports_computer : bool = False
45
+ max_players : int = 2 ,
46
+ min_players : int = 2 ,
47
+ supports_computer : bool = False
46
48
) -> None :
47
49
self .game_name = game_name
48
50
self .bot_name = bot_name
@@ -155,16 +157,17 @@ def confirm_invitation_declined(self, game_id: str) -> str:
155
157
host = self .invites [game_id ]['host' ]
156
158
return 'Declined invitation to play **{}** from @**{}**.' .format (self .game_name , self .get_username_by_email (host ))
157
159
158
- def send_message (self , to : str , content : str , is_private : bool , subject : str = '' ) -> None :
160
+ def send_message (self , to : str , content : str , is_private : bool , subject : str = '' , widget_content : str = '' ) -> None :
159
161
self .bot_handler .send_message (dict (
160
162
type = 'private' if is_private else 'stream' ,
161
163
to = to ,
162
164
content = content ,
163
- subject = subject
165
+ subject = subject ,
166
+ widget_content = widget_content
164
167
))
165
168
166
- def send_reply (self , original_message : Dict [str , Any ], content : str ) -> None :
167
- self .bot_handler .send_reply (original_message , content )
169
+ def send_reply (self , original_message : Dict [str , Any ], content : str , widget_content : str = '' ) -> None :
170
+ self .bot_handler .send_reply (original_message , content , widget_content )
168
171
169
172
def usage (self ) -> str :
170
173
return '''
@@ -280,7 +283,8 @@ def command_start_game_with(self, message: Dict[str, Any], sender: str, content:
280
283
def command_start_game (self , message : Dict [str , Any ], sender : str , content : str ) -> None :
281
284
if message ['type' ] == 'private' :
282
285
if self .is_single_player :
283
- self .send_reply (message , 'You are not allowed to play games in private messages.' )
286
+ self .send_reply (
287
+ message , 'You are not allowed to play games in private messages.' )
284
288
return
285
289
else :
286
290
self .send_reply (
@@ -309,7 +313,7 @@ def command_accept(self, message: Dict[str, Any], sender: str, content: str) ->
309
313
game_id , '@**{}** has accepted the invitation.' .format (self .get_username_by_email (sender )))
310
314
self .start_game_if_ready (game_id )
311
315
312
- def create_game_lobby (self , message : Dict [str , Any ], users : List [str ]= []) -> None :
316
+ def create_game_lobby (self , message : Dict [str , Any ], users : List [str ] = []) -> None :
313
317
if self .is_game_in_subject (message ['subject' ], message ['display_recipient' ]):
314
318
self .send_reply (message , 'There is already a game in this stream.' )
315
319
return
@@ -379,14 +383,16 @@ def command_decline(self, message: Dict[str, Any], sender: str, content: str) ->
379
383
def command_quit (self , message : Dict [str , Any ], sender : str , content : str ) -> None :
380
384
game_id = self .get_game_id_by_email (sender )
381
385
if message ['type' ] == 'private' and self .is_single_player :
382
- self .send_reply (message , 'You are not allowed to play games in private messages.' )
386
+ self .send_reply (
387
+ message , 'You are not allowed to play games in private messages.' )
383
388
return
384
389
if game_id is '' :
385
390
self .send_reply (
386
391
message , 'You are not in a game. Type `help` for all commands.' )
387
392
sender_avatar = "!avatar({})" .format (sender )
388
393
sender_name = self .get_username_by_email (sender )
389
- self .cancel_game (game_id , reason = '{} **{}** quit.' .format (sender_avatar , sender_name ))
394
+ self .cancel_game (
395
+ game_id , reason = '{} **{}** quit.' .format (sender_avatar , sender_name ))
390
396
391
397
def command_join (self , message : Dict [str , Any ], sender : str , content : str ) -> None :
392
398
if not self .is_user_not_player (sender , message ):
@@ -417,7 +423,8 @@ def command_play(self, message: Dict[str, Any], sender: str, content: str) -> No
417
423
self .start_game (game_id )
418
424
else :
419
425
self .send_reply (
420
- message , 'Join {} more players to start the game' .format (self .max_players - num_players )
426
+ message , 'Join {} more players to start the game' .format (
427
+ self .max_players - num_players )
421
428
)
422
429
423
430
def command_leaderboard (self , message : Dict [str , Any ], sender : str , content : str ) -> None :
@@ -451,13 +458,13 @@ def get_sorted_player_statistics(self) -> List[Tuple[str, Dict[str, int]]]:
451
458
reverse = True
452
459
)
453
460
454
- def send_invite (self , game_id : str , user_email : str , message : Dict [str , Any ]= {}) -> None :
461
+ def send_invite (self , game_id : str , user_email : str , message : Dict [str , Any ] = {}) -> None :
455
462
self .invites [game_id ].update ({user_email .lower (): 'p' })
456
463
self .send_message (user_email , self .alert_new_invitation (game_id ), True )
457
464
if message != {}:
458
465
self .send_reply (message , self .confirm_new_invitation (user_email ))
459
466
460
- def cancel_game (self , game_id : str , reason : str = '' ) -> None :
467
+ def cancel_game (self , game_id : str , reason : str = '' ) -> None :
461
468
if game_id in self .invites .keys ():
462
469
self .broadcast (game_id , 'Game cancelled.\n ' + reason )
463
470
del self .invites [game_id ]
@@ -497,7 +504,7 @@ def get_formatted_game_object(self, game_id: str) -> str:
497
504
instance .stream , instance .subject )
498
505
return object
499
506
500
- def join_game (self , game_id : str , user_email : str , message : Dict [str , Any ]= {}) -> None :
507
+ def join_game (self , game_id : str , user_email : str , message : Dict [str , Any ] = {}) -> None :
501
508
if len (self .get_players (game_id )) >= self .max_players :
502
509
if message != {}:
503
510
self .send_reply (message , 'This game is full.' )
@@ -507,7 +514,7 @@ def join_game(self, game_id: str, user_email: str, message: Dict[str, Any]={}) -
507
514
game_id , '@**{}** has joined the game' .format (self .get_username_by_email (user_email )))
508
515
self .start_game_if_ready (game_id )
509
516
510
- def get_players (self , game_id : str , parameter : str = 'a' ) -> List [str ]:
517
+ def get_players (self , game_id : str , parameter : str = 'a' ) -> List [str ]:
511
518
if game_id in self .invites .keys ():
512
519
players = [] # type: List[str]
513
520
if (self .invites [game_id ]['subject' ] == '###private###' and 'p' in parameter ) or 'p' not in parameter :
@@ -590,7 +597,7 @@ def change_game_subject(
590
597
game_id : str ,
591
598
stream_name : str ,
592
599
subject_name : str ,
593
- message : Dict [str , Any ]= {}
600
+ message : Dict [str , Any ] = {}
594
601
) -> None :
595
602
if self .get_game_instance_by_subject (stream_name , subject_name ) is not None :
596
603
if message != {}:
@@ -645,7 +652,7 @@ def get_user_cache(self) -> Dict[str, Any]:
645
652
self .user_cache = json .loads (user_cache_str )
646
653
return self .user_cache
647
654
648
- def verify_users (self , users : List [str ], message : Dict [str , Any ]= {}) -> List [str ]:
655
+ def verify_users (self , users : List [str ], message : Dict [str , Any ] = {}) -> List [str ]:
649
656
verified_users = []
650
657
failed = False
651
658
for u in users :
@@ -687,7 +694,7 @@ def is_game_in_subject(self, subject_name: str, stream_name: str) -> bool:
687
694
self .get_game_instance_by_subject (
688
695
subject_name , stream_name ) is not None
689
696
690
- def is_user_not_player (self , user_email : str , message : Dict [str , Any ]= {}) -> bool :
697
+ def is_user_not_player (self , user_email : str , message : Dict [str , Any ] = {}) -> bool :
691
698
user = self .get_user_by_email (user_email )
692
699
if user == {}:
693
700
if message != {}:
@@ -713,20 +720,20 @@ def generate_game_id(self) -> str:
713
720
id += valid_characters [random .randrange (0 , len (valid_characters ))]
714
721
return id
715
722
716
- def broadcast (self , game_id : str , content : str , include_private : bool = True ) -> bool :
723
+ def broadcast (self , game_id : str , content : str , include_private : bool = True , widget_content : str = '' ) -> bool :
717
724
if include_private :
718
725
private_recipients = self .get_players (game_id , parameter = 'p' )
719
726
if private_recipients is not None :
720
727
for user in private_recipients :
721
- self .send_message (user , content , True )
728
+ self .send_message (user , content , True , '' , widget_content )
722
729
if game_id in self .invites .keys ():
723
730
if self .invites [game_id ]['subject' ] != '###private###' :
724
731
self .send_message (
725
- self .invites [game_id ]['stream' ], content , False , self .invites [game_id ]['subject' ])
732
+ self .invites [game_id ]['stream' ], content , False , self .invites [game_id ]['subject' ], widget_content )
726
733
return True
727
734
if game_id in self .instances .keys ():
728
735
self .send_message (
729
- self .instances [game_id ].stream , content , False , self .instances [game_id ].subject )
736
+ self .instances [game_id ].stream , content , False , self .instances [game_id ].subject , widget_content )
730
737
return True
731
738
return False
732
739
@@ -774,7 +781,7 @@ def __init__(self, gameAdapter: GameAdapter, is_private: bool, subject: str, gam
774
781
self .board = self .model .current_board
775
782
self .turn = random .randrange (0 , len (players )) - 1
776
783
self .current_draw = {} # type: Dict[str, bool]
777
- self .current_messages = [] # type: List[str]
784
+ self .current_messages = [] # type: List[Union[ str, Tuple[str, str]] ]
778
785
self .is_changing_subject = False
779
786
780
787
def start (self ) -> None :
@@ -828,20 +835,21 @@ def handle_message(self, content: str, player_email: str) -> None:
828
835
if self .gameAdapter .is_single_player :
829
836
self .broadcast ('It\' s your turn' )
830
837
else :
831
- user_turn_avatar = "!avatar({})" .format (self .players [self .turn ])
838
+ user_turn_avatar = "!avatar({})" .format (
839
+ self .players [self .turn ])
832
840
if self .gameAdapter .gameMessageHandler .get_player_color (self .turn ) is None :
833
841
self .broadcast ('{} It\' s **{}**\' s turn.' .format (
834
- user_turn_avatar ,
835
- self .gameAdapter .get_username_by_email (
836
- self .players [self .turn ])))
842
+ user_turn_avatar ,
843
+ self .gameAdapter .get_username_by_email (
844
+ self .players [self .turn ])))
837
845
else :
838
846
self .broadcast ('{} It\' s **{}**\' s ({}) turn.' .format (
839
847
user_turn_avatar ,
840
848
self .gameAdapter .get_username_by_email (
841
849
self .players [self .turn ]),
842
850
self .gameAdapter .gameMessageHandler .get_player_color (self .turn )))
843
851
844
- def broadcast (self , content : str ) -> None :
852
+ def broadcast (self , content : str , widget_content : str = '' ) -> None :
845
853
self .gameAdapter .broadcast (self .game_id , content )
846
854
847
855
def check_draw (self ) -> bool :
@@ -866,7 +874,11 @@ def make_move(self, content: str, is_computer: bool) -> None:
866
874
return
867
875
except BadMoveException as e :
868
876
self .broadcast (e .message )
869
- self .broadcast (self .parse_current_board ())
877
+ current_board_message = self .parse_current_board
878
+ if isinstance (current_board_message , tuple ):
879
+ self .broadcast (
880
+ current_board_message [0 ], current_board_message [1 ])
881
+ self .broadcast (str (current_board_message ))
870
882
return
871
883
if not is_computer :
872
884
self .current_messages .append (self .gameAdapter .gameMessageHandler .alert_move_message (
@@ -918,16 +930,24 @@ def next_turn(self) -> None:
918
930
user_turn_avatar = "!avatar({})" .format (self .players [self .turn ])
919
931
self .current_messages .append ('{} It\' s **{}**\' s ({}) turn.' .format (
920
932
user_turn_avatar ,
921
- self .gameAdapter .get_username_by_email (self .players [self .turn ]),
933
+ self .gameAdapter .get_username_by_email (
934
+ self .players [self .turn ]),
922
935
self .gameAdapter .gameMessageHandler .get_player_color (self .turn )
923
936
))
924
937
self .broadcast_current_message ()
925
938
if self .players [self .turn ] == self .gameAdapter .email :
926
939
self .make_move ('' , True )
927
940
928
941
def broadcast_current_message (self ) -> None :
929
- content = '\n \n ' .join (self .current_messages )
930
- self .broadcast (content )
942
+ # if there are no widgets, send all the messages all at once
943
+ if len (list (filter (lambda x : isinstance (x , tuple ), self .current_messages ))) == 0 :
944
+ self .broadcast ("\n \n " .join (str (m ) for m in self .current_messages ))
945
+ else :
946
+ for message in self .current_messages :
947
+ if isinstance (message , tuple ):
948
+ self .broadcast (message [0 ], message [1 ])
949
+ else :
950
+ self .broadcast (str (message ))
931
951
self .current_messages = []
932
952
933
953
def parse_current_board (self ) -> Any :
@@ -942,7 +962,8 @@ def end_game(self, winner: str) -> None:
942
962
else :
943
963
winner_avatar = "!avatar({})" .format (winner )
944
964
winner_name = self .gameAdapter .get_username_by_email (winner )
945
- self .broadcast ('{} **{}** won! :tada:' .format (winner_avatar , winner_name ))
965
+ self .broadcast (
966
+ '{} **{}** won! :tada:' .format (winner_avatar , winner_name ))
946
967
for u in self .players :
947
968
values = {'total_games' : 1 , 'games_won' : 0 ,
948
969
'games_lost' : 0 , 'games_drawn' : 0 }
0 commit comments