Skip to content
This repository has been archived by the owner on Jul 8, 2023. It is now read-only.

Commit

Permalink
Working with hand estimation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nihisil committed Jun 25, 2016
1 parent e9acd8f commit b3a5af1
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 58 deletions.
2 changes: 1 addition & 1 deletion project/bots_battle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from game.game_manager import GameManager
from mahjong.client import Client

TOTAL_HANCHANS = 10
TOTAL_HANCHANS = 100

def main():
# enable it for manual testing
Expand Down
60 changes: 34 additions & 26 deletions project/game/game_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ def play_round(self):

# win by tsumo after tile draw
if is_win:
result = self.process_the_end_of_the_round(client.player.tiles, client, None, True)
result = self.process_the_end_of_the_round(tiles=client.player.tiles,
win_tile=tile,
winner=client,
loser=None,
is_tsumo=True)
return result

# if not in riichi, let's decide what tile to discard
Expand All @@ -156,7 +160,11 @@ def play_round(self):
# TODO support multiple ron
if self.can_call_ron(other_client, tile):
# the end of the round
result = self.process_the_end_of_the_round(other_client.player.tiles, other_client, client, False)
result = self.process_the_end_of_the_round(tiles=other_client.player.tiles,
win_tile=tile,
winner=other_client,
loser=client,
is_tsumo=False)
return result

# if there is no challenger to ron, let's check can we call riichi with tile discard or not
Expand All @@ -169,7 +177,7 @@ def play_round(self):
if not len(self.tiles):
continue_to_play = False

result = self.process_the_end_of_the_round(None, None, None, False)
result = self.process_the_end_of_the_round([], 0, None, None, False)
return result

def play_game(self, total_results):
Expand All @@ -190,8 +198,8 @@ def play_game(self, total_results):
result = self.play_round()

is_game_end = result['is_game_end']
loser = result['lose_client']
winner = result['win_client']
loser = result['loser']
winner = result['winner']
if loser:
total_results[loser.id]['lose_rounds'] += 1
if winner:
Expand Down Expand Up @@ -249,15 +257,16 @@ def set_dealer(self, dealer):
# first move should be dealer's move
self.current_client = dealer

def process_the_end_of_the_round(self, win_tiles, win_client, lose_client, is_tsumo):
def process_the_end_of_the_round(self, tiles, win_tile, winner, loser, is_tsumo):
"""
Increment a round number and do a scores calculations
"""

if win_client:
logger.info('{0}: {1}'.format(
if winner:
logger.info('{0}: {1} + {2}'.format(
is_tsumo and 'Tsumo' or 'Ron',
TilesConverter.to_one_line_string(win_tiles))
TilesConverter.to_one_line_string(tiles),
TilesConverter.to_one_line_string([win_tile])),
)
else:
logger.info('Retake')
Expand All @@ -267,32 +276,34 @@ def process_the_end_of_the_round(self, win_tiles, win_client, lose_client, is_ts

hand_value = 0

if win_client:
hand_value += FinishedHand.estimate_hand_value(win_tiles,
if winner:
hand_value += FinishedHand.estimate_hand_value(tiles,
win_tile,
is_tsumo,
win_client.player.in_riichi,
winner.player.in_riichi,
winner.player.is_dealer,
False)

scores_to_pay = hand_value + self.honba_sticks * 300
riichi_bonus = self.riichi_sticks * 1000
self.riichi_sticks = 0

# if dealer won we need to increment honba sticks
if win_client.player.is_dealer:
if winner.player.is_dealer:
self.honba_sticks += 1
else:
self.honba_sticks = 0
new_dealer = self._move_position(self.dealer)
self.set_dealer(new_dealer)

# win by ron
if lose_client:
if loser:
win_amount = scores_to_pay + riichi_bonus
win_client.player.scores += win_amount
lose_client.player.scores -= scores_to_pay
winner.player.scores += win_amount
loser.player.scores -= scores_to_pay

logger.info('Win: {0} + {1:,d}'.format(win_client.player.name, win_amount))
logger.info('Lose: {0} - {1:,d}'.format(lose_client.player.name, scores_to_pay))
logger.info('Win: {0} + {1:,d}'.format(winner.player.name, win_amount))
logger.info('Lose: {0} - {1:,d}'.format(loser.player.name, scores_to_pay))
# win by tsumo
else:
scores_to_pay /= 3
Expand All @@ -301,13 +312,13 @@ def process_the_end_of_the_round(self, win_tiles, win_client, lose_client, is_ts
scores_to_pay = 100 * round(float(scores_to_pay) / 100)

win_amount = scores_to_pay * 3 + riichi_bonus
win_client.player.scores += win_amount
winner.player.scores += win_amount

for client in self.clients:
if client != win_client:
if client != winner:
client.player.scores -= scores_to_pay

logger.info('Win: {0} + {1:,d}'.format(win_client.player.name, win_amount))
logger.info('Win: {0} + {1:,d}'.format(winner.player.name, win_amount))
# retake
else:
tempai_users = 0
Expand Down Expand Up @@ -337,8 +348,6 @@ def process_the_end_of_the_round(self, win_tiles, win_client, lose_client, is_ts
else:
client.player.scores -= 3000 / (4 - tempai_users)



# if someone has negative scores,
# we need to end the game
for client in self.clients:
Expand All @@ -352,9 +361,8 @@ def process_the_end_of_the_round(self, win_tiles, win_client, lose_client, is_ts
logger.info('')

return {
'win_hand': win_tiles,
'win_client': win_client,
'lose_client': lose_client,
'winner': winner,
'loser': loser,
'is_tsumo': is_tsumo,
'is_game_end': is_game_end
}
Expand Down
55 changes: 26 additions & 29 deletions project/game/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,8 @@ def test_play_round_and_win_by_tsumo(self):
self.assertEqual(manager.round_number, 1)
self.assertEqual(result['is_tsumo'], True)
self.assertEqual(result['is_game_end'], False)
self.assertNotEqual(result['win_hand'], None)
self.assertNotEqual(result['win_client'], None)
self.assertEqual(result['lose_client'], None)
self.assertNotEqual(result['winner'], None)
self.assertEqual(result['loser'], None)

def test_play_round_and_win_by_ron(self):
game.game_manager.shuffle_seed = lambda : 0.33
Expand All @@ -163,9 +162,8 @@ def test_play_round_and_win_by_ron(self):
self.assertEqual(manager.round_number, 1)
self.assertEqual(result['is_tsumo'], False)
self.assertEqual(result['is_game_end'], False)
self.assertNotEqual(result['win_hand'], None)
self.assertNotEqual(result['win_client'], None)
self.assertNotEqual(result['lose_client'], None)
self.assertNotEqual(result['winner'], None)
self.assertNotEqual(result['loser'], None)

def test_play_round_with_retake(self):
game.game_manager.shuffle_seed = lambda : 0.01
Expand All @@ -181,25 +179,24 @@ def test_play_round_with_retake(self):
self.assertEqual(manager.round_number, 1)
self.assertEqual(result['is_tsumo'], False)
self.assertEqual(result['is_game_end'], False)
self.assertEqual(result['win_hand'], None)
self.assertEqual(result['win_client'], None)
self.assertEqual(result['lose_client'], None)
self.assertEqual(result['winner'], None)
self.assertEqual(result['loser'], None)

def test_scores_calculations_after_retake(self):
clients = [Client() for _ in range(0, 4)]
manager = GameManager(clients)
manager.init_game()
manager.init_round()

manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)

self.assertEqual(clients[0].player.scores, 25000)
self.assertEqual(clients[1].player.scores, 25000)
self.assertEqual(clients[2].player.scores, 25000)
self.assertEqual(clients[3].player.scores, 25000)

clients[0].player.in_tempai = True
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)

self.assertEqual(clients[0].player.scores, 28000)
self.assertEqual(clients[1].player.scores, 24000)
Expand All @@ -211,7 +208,7 @@ def test_scores_calculations_after_retake(self):

clients[0].player.in_tempai = True
clients[1].player.in_tempai = True
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)

self.assertEqual(clients[0].player.scores, 26500)
self.assertEqual(clients[1].player.scores, 26500)
Expand All @@ -224,7 +221,7 @@ def test_scores_calculations_after_retake(self):
clients[0].player.in_tempai = True
clients[1].player.in_tempai = True
clients[2].player.in_tempai = True
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)

self.assertEqual(clients[0].player.scores, 26000)
self.assertEqual(clients[1].player.scores, 26000)
Expand All @@ -238,7 +235,7 @@ def test_retake_and_honba_increment(self):
manager.init_round()

# no one in tempai, so honba stick should be added
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)
self.assertEqual(manager.honba_sticks, 1)

manager.honba_sticks = 0
Expand All @@ -247,13 +244,13 @@ def test_retake_and_honba_increment(self):
clients[1].player.in_tempai = True

# dealer NOT in tempai, no honba
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)
self.assertEqual(manager.honba_sticks, 0)

clients[0].player.in_tempai = True

# dealer in tempai, so honba stick should be added
manager.process_the_end_of_the_round(None, None, None, False)
manager.process_the_end_of_the_round(None, None, None, None, False)
self.assertEqual(manager.honba_sticks, 1)

def test_win_by_ron_and_scores_calculation(self):
Expand All @@ -266,7 +263,7 @@ def test_win_by_ron_and_scores_calculation(self):
loser = clients[1]

# only 1000 hand
manager.process_the_end_of_the_round(range(0, 14), winner, loser, False)
manager.process_the_end_of_the_round(range(0, 14), 0, winner, loser, False)
self.assertEqual(winner.player.scores, 26000)
self.assertEqual(loser.player.scores, 24000)

Expand All @@ -276,7 +273,7 @@ def test_win_by_ron_and_scores_calculation(self):
manager.riichi_sticks = 2
manager.honba_sticks = 2

manager.process_the_end_of_the_round(range(0, 14), winner, loser, False)
manager.process_the_end_of_the_round(range(0, 14), 0, winner, loser, False)
self.assertEqual(winner.player.scores, 28600)
self.assertEqual(loser.player.scores, 23400)
self.assertEqual(manager.riichi_sticks, 0)
Expand All @@ -288,7 +285,7 @@ def test_win_by_ron_and_scores_calculation(self):
manager.honba_sticks = 2

# if dealer won we need to increment honba sticks
manager.process_the_end_of_the_round(range(0, 14), winner, loser, False)
manager.process_the_end_of_the_round(range(0, 14), 0, winner, loser, False)
self.assertEqual(winner.player.scores, 26600)
self.assertEqual(loser.player.scores, 23400)
self.assertEqual(manager.honba_sticks, 3)
Expand All @@ -301,7 +298,7 @@ def test_win_by_tsumo_and_scores_calculation(self):
manager.riichi_sticks = 1

winner = clients[0]
manager.process_the_end_of_the_round(range(0, 14), winner, None, True)
manager.process_the_end_of_the_round(range(0, 14), 0, winner, None, True)

self.assertEqual(winner.player.scores, 26900)
self.assertEqual(clients[1].player.scores, 24700)
Expand All @@ -315,28 +312,28 @@ def test_change_dealer_after_end_of_the_round(self):
manager.init_round()

# retake. dealer is NOT in tempai, let's move a dealer position
manager.process_the_end_of_the_round([], None, None, False)
manager.process_the_end_of_the_round([], None, None, None, False)
self.assertEqual(manager.dealer, 1)

# retake. dealer is in tempai, don't move a dealer position
clients[1].player.in_tempai = True
manager.process_the_end_of_the_round([], None, None, False)
manager.process_the_end_of_the_round([], 0, None, None, False)
self.assertEqual(manager.dealer, 1)

# dealer win by ron, don't move a dealer position
manager.process_the_end_of_the_round([], clients[1], clients[0], False)
manager.process_the_end_of_the_round([], 0, None, None, False)
self.assertEqual(manager.dealer, 1)

# dealer win by tsumo, don't move a dealer position
manager.process_the_end_of_the_round([], clients[1], None, True)
manager.process_the_end_of_the_round([], 0, None, None, False)
self.assertEqual(manager.dealer, 1)

# NOT dealer win by ron, let's move a dealer position
manager.process_the_end_of_the_round([], clients[3], clients[2], False)
manager.process_the_end_of_the_round([], 0, clients[3], clients[2], False)
self.assertEqual(manager.dealer, 2)

# NOT dealer win by tsumo, let's move a dealer position
manager.process_the_end_of_the_round([], clients[1], None, True)
manager.process_the_end_of_the_round([], 0, clients[1], None, True)
self.assertEqual(manager.dealer, 3)

def test_is_game_end_by_negative_scores(self):
Expand All @@ -349,7 +346,7 @@ def test_is_game_end_by_negative_scores(self):
loser = clients[1]
loser.player.scores = 500

result = manager.process_the_end_of_the_round(range(0, 14), winner, loser, False)
result = manager.process_the_end_of_the_round(range(0, 14), 0, winner, loser, False)
self.assertEqual(loser.player.scores, -500)
self.assertEqual(result['is_game_end'], True)

Expand All @@ -367,12 +364,12 @@ def test_is_game_end_by_eight_winds(self):
# to avoid honba
client = current_dealer == 0 and 1 or 0

result = manager.process_the_end_of_the_round(range(0, 14), clients[client], None, True)
result = manager.process_the_end_of_the_round(range(0, 14), 0, clients[client], None, True)
self.assertEqual(result['is_game_end'], False)
self.assertNotEqual(manager.dealer, current_dealer)
current_dealer = manager.dealer

result = manager.process_the_end_of_the_round(range(0, 14), clients[0], None, True)
result = manager.process_the_end_of_the_round(range(0, 14), 0, clients[0], None, True)
self.assertEqual(result['is_game_end'], True)


32 changes: 30 additions & 2 deletions project/mahjong/hand.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
# -*- coding: utf-8 -*-
from mahjong.ai.agari import Agari


class FinishedHand(object):

@classmethod
def estimate_hand_value(cls, tiles, is_tsumo, is_riichi, is_open_hand):
# not implemented yet
def estimate_hand_value(cls, tiles, win_tile, is_tsumo, is_riichi, is_dealer, is_open_hand):
"""
:param tiles: array with 13 tiles in 136-tile format
:param win_tile: tile that caused win (ron or tsumo)
:return: The dictionary with hand cost or error response
{"cost": 1000, "han": 1, "fu": 30, "error": None}
{"cost": None, "han": 0, "fu": 0, "error": "Hand is not valid"}
"""
agari = Agari()
cost = None
error = None

hand_yaku = []
han = 0
fu = 0

def return_response():
return {'cost': cost, 'error': error, 'han': han, 'fu': fu}

# total_tiles = list(tiles) + [win_tile]
# if not agari.is_agari(total_tiles):
# error = 'Hand is not winning'
# return return_response()

if han == 1 and fu < 30:
error = 'Not valid han and fu'
return return_response()

return 1000

0 comments on commit b3a5af1

Please sign in to comment.