def resign(user_id: int, game_id: int) -> None: """Ends the game due to one player's resignation""" game = Game.get(game_id) # If there is no moves in the game, just cancel it. if not game.raw_moves: end_game.delay(game_id, '-', 'Game canceled.', update_stats=False) return user_white = user_id == game.white_user.id result: str reason: str if user_white: result = "0-1" reason = "White resigned. Black won." else: result = "1-0" reason = "Black resigned. White won." end_game.delay(game_id, result, reason)
def get_rating_changes(game_id: int) -> Dict[str, RatingChange]: '''Returns rating changes for game in dict. Example: {"w": RatingChange, "b": RatingChange}''' game = Game.get(game_id) r_white = game.white_user.rating r_black = game.black_user.rating R_white = 10**(r_white / 400) R_black = 10**(r_black / 400) R_sum = R_white + R_black E_white = R_white / R_sum E_black = R_black / R_sum k_factor_white = game.white_user.k_factor k_factor_black = game.black_user.k_factor rating_change_white = RatingChange.from_formula(k_factor_white, E_white) rating_change_black = RatingChange.from_formula(k_factor_black, E_black) return {"w": rating_change_white, "b": rating_change_black}
def on_disconnect(user_id: int, game_id: int) -> None: '''Schedules on_disconnect_timed_out_task, adds it to database. Emits 'opp_disconnected' to the opponent''' eta = datetime.utcnow() + timedelta(seconds=DISCONNECT_TIME_OUT) task = on_disconnect_timed_out.apply_async( args=(user_id, game_id), eta=eta, ) game = Game.get(game_id) if game.draw_offer_sender: # We aren't checking user_id != draw_offer_sender # It'll be checked in decline_draw_offer func decline_draw_offer.delay(user_id, game_id) is_user_white = user_id == game.white_user.id opp_sid: Optional[int] with rom.util.EntityLock(game, 10, 10): if is_user_white: game.white_disconnect_timed_out_task_id = task.id game.white_disconnect_timed_out_task_eta = eta opp_sid = game.black_user.sid else: game.black_disconnect_timed_out_task_id = task.id game.black_disconnect_timed_out_task_eta = eta opp_sid = game.white_user.sid game.save() if opp_sid: sio.emit( 'opp_disconnected', {'wait_time': DISCONNECT_TIME_OUT}, room=opp_sid, )
def make_draw_offer(user_id: int, game_id: int): '''Makes draw offer, if it's possible.''' game = Game.get(game_id) with rom.util.EntityLock(game, 10, 10): if game.get_moves_cnt() == 0: # Do not make draw offer, if game isn't started. return if game.draw_offer_sender and game.draw_offer_sender != user_id: # Accept draw offer, if it's already exist accept_draw_offer.delay(user_id, game_id) elif game.draw_offer_sender: return game.draw_offer_sender = user_id game.save() opp_sid: str if user_id == game.white_user.id: opp_sid = game.black_user.sid else: opp_sid = game.white_user.sid sio.emit('draw_offer', room=opp_sid)
def make_move(user_id: int, game_id: int, move_san: str) -> None: '''Updates game state by user's move. Calls end_game(...) if the game is ended.''' request_datetime = datetime.utcnow() game = Game.get(game_id) if game.is_finished or\ user_id not in (game.white_user.id, game.black_user.id): return board = game.get_board() is_user_white = user_id == game.white_user.id if (is_user_white and board.turn == chess.BLACK) or\ (not is_user_white and board.turn == chess.WHITE): return try: with rom.util.EntityLock(game, 10, 10): board.push_san(move_san) game.append_move(move_san) if game.first_move_timed_out_task_id: revoke(game.first_move_timed_out_task_id) game.first_move_timed_out_task_id = None if is_user_white: revoke(game.white_time_is_up_task_id) else: revoke(game.black_time_is_up_task_id) if game.draw_offer_sender and game.draw_offer_sender != user_id: # Decline draw offer only if it was asked by the opp # This call is waiting because of an entity lock decline_draw_offer.delay(user_id, game_id) game.draw_offer_sender = None if is_user_white: if game.get_moves_cnt() != 1: game.white_clock -=\ request_datetime - game.last_move_datetime eta = datetime.utcnow() + game.black_clock task = on_time_is_up.apply_async( args=(game.black_user.id, game_id), eta=eta, ) game.black_time_is_up_task_id = task.id game.black_time_is_up_task_eta = eta else: if game.get_moves_cnt() != 1: game.black_clock -=\ request_datetime - game.last_move_datetime eta = datetime.utcnow() + game.white_clock task = on_time_is_up.apply_async( args=(game.white_user.id, game_id), eta=eta, ) game.white_time_is_up_task_id = task.id game.white_time_is_up_task_eta = eta game.last_move_datetime = request_datetime if board.fullmove_number == 1: # and board.turn == chess.BLACK sio.emit('first_move_waiting', {'wait_time': FIRST_MOVE_TIME_OUT}, room=game.black_user.sid) eta = \ datetime.utcnow() + timedelta(seconds=FIRST_MOVE_TIME_OUT) task = on_first_move_timed_out.\ apply_async((game_id, ), eta=eta) game.first_move_timed_out_task_id = task.id game.first_move_timed_out_task_eta = eta game.save() data = { 'san': move_san, 'black_clock': int(game.black_clock.total_seconds()), 'white_clock': int(game.white_clock.total_seconds()) } sio.emit('game_updated', data, room=game_id) # DO NOT REMOVE NEXT STRING. SIO CAN'T EMIT TO SPECTATORS WITHOUT # THIS :/ data = data sio.emit('game_updated', data, room=game.black_user.sid) sio.emit('game_updated', data, room=game.white_user.sid) result = board.result() if result != '*': reason: str if result == '1/2-1/2': reason = "Draw" elif result == '1-0': reason = "Checkmate. White won." else: reason = "Checkmate. Black won." end_game.delay(game_id, result, reason) except ValueError: pass
def end_game(game_id: int, result: str, reason: str, update_stats=True) -> None: '''Marks game as finished, emits 'game_ended' signal to users, closes the room, recalculates ratings and k-factors if update_stats is True''' game = Game.get(game_id) if game.is_finished: return if game.first_move_timed_out_task_id: revoke(game.first_move_timed_out_task_id) if game.white_disconnect_timed_out_task_id: revoke(game.white_disconnect_timed_out_task_id) if game.black_disconnect_timed_out_task_id: revoke(game.black_disconnect_timed_out_task_id) if game.white_time_is_up_task_id: revoke(game.white_time_is_up_task_id) if game.black_time_is_up_task_id: revoke(game.black_time_is_up_task_id) data = {'result': result} sio.emit('game_ended', data, room=game_id) # Emit to spectators data['reason'] = reason sio.emit('game_ended', data, room=game.white_user.sid) sio.emit('game_ended', data, room=game.black_user.sid) with rom.util.EntityLock(game, 10, 10): game.is_finished = 1 game.result = result with rom.util.EntityLock(game.white_user, 10, 10): game.white_user.cur_game_id = None game.white_user.save() with rom.util.EntityLock(game.black_user, 10, 10): game.black_user.cur_game_id = None game.black_user.save() game.save() if update_stats is False: return rating_changes = get_rating_changes(game_id) with rom.util.EntityLock(game.white_user, 10, 10): game.white_user.games_played += 1 game.white_user.save() with rom.util.EntityLock(game.black_user, 10, 10): game.black_user.games_played += 1 game.black_user.save() if result == "1-0": update_rating.delay(game.white_user.id, rating_changes["w"].win) update_rating.delay(game.black_user.id, rating_changes["b"].lose) elif result == "1/2-1/2": update_rating.delay(game.white_user.id, rating_changes["w"].draw) update_rating.delay(game.black_user.id, rating_changes["b"].draw) elif result == "0-1": update_rating.delay(game.white_user.id, rating_changes["w"].lose) update_rating.delay(game.black_user.id, rating_changes["b"].win) update_k_factor.delay(game.white_user.id) update_k_factor.delay(game.black_user.id)
def reconnect(user_id: int, game_id: int) -> None: '''Sends game info to reconnected player Emits 'opp_reconnected' to the opponent.''' game = Game.get(game_id) next_to_move = game.get_next_to_move() is_user_white = user_id == game.white_user.id if is_user_white: # Not ".delay()", because of bad emition order send_game_info(game_id, game.white_user.sid, True) if game.white_disconnect_timed_out_task_id: revoke(game.white_disconnect_timed_out_task_id) with rom.util.EntityLock(game, 10, 10): game.white_disconnect_timed_out_task_id = None game.save() sio.emit('opp_reconnected', room=game.black_user.sid) else: send_game_info.delay(game_id, game.black_user.sid, True) if game.black_disconnect_timed_out_task_id: revoke(game.black_disconnect_timed_out_task_id) with rom.util.EntityLock(game, 10, 10): game.black_disconnect_timed_out_task_id = None game.save() sio.emit('opp_reconnected', room=game.white_user.sid) if is_user_white: if game.first_move_timed_out_task_id and\ next_to_move == chess.WHITE: wait_time = (game.first_move_timed_out_task_eta - datetime.utcnow()).seconds sio.emit( 'first_move_waiting', {'wait_time': wait_time}, room=game.white_user.sid, ) elif game.first_move_timed_out_task_id and\ next_to_move == chess.BLACK: wait_time = (game.first_move_timed_out_task_eta - datetime.utcnow()).seconds sio.emit( 'first_move_waiting', {'wait_time': wait_time}, room=game.black_user.sid, ) if is_user_white and game.black_disconnect_timed_out_task_id: wait_time = (game.black_disconnect_timed_out_task_eta - datetime.utcnow()).seconds sio.emit( 'opp_disconnected', {'wait_time': wait_time}, room=game.white_user.sid, ) elif not is_user_white and game.white_disconnect_timed_out_task_id: wait_time = (game.white_disconnect_timed_out_task_eta - datetime.utcnow()).seconds sio.emit( 'opp_disconnected', {'wait_time': wait_time}, room=game.black_user.sid, )
def test_total_time_and_black_clock_and_white_clock(self): game = Game() game.save() self.used_game_ids.append(game.id) total_time = timedelta(seconds=30, microseconds=13231) game.total_time = total_time black_clock = timedelta(seconds=310, microseconds=321312) game.black_clock = black_clock white_clock = timedelta(seconds=312, microseconds=31231) game.white_clock = white_clock game.save() game.refresh() self.assertEqual(game.total_time, total_time) self.assertEqual(game.black_clock, black_clock) self.assertEqual(game.white_clock, white_clock)