Skip to content
This repository was archived by the owner on Jul 8, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
145 commits
Select commit Hold shift + click to select a range
be9b70c
Make sleeps smarter during the game
Nihisil Aug 20, 2018
873d2ef
Increase bot AI version
Nihisil Aug 20, 2018
19f5fd6
Fix tests
Nihisil Aug 20, 2018
821c8df
Improve hand building logic. Introduce second level ukeire
Nihisil Aug 22, 2018
ae8b30a
Add Python 3.7 to the travis
Nihisil Aug 22, 2018
2c1dc51
Improve yakuhai open hand strategy with new rules
Nihisil Aug 24, 2018
ccf7cab
Add additional rules about double east\south to the yakuhai strategy
Nihisil Aug 24, 2018
ddd449e
Add 'Formal Tempai' strategy (#45)
bogachev-pa Aug 25, 2018
b4bdf59
Various small fixes for log reproducer (#63)
Nihisil Aug 25, 2018
fd47942
Issue #48. Improve closed kan call logic
Nihisil Aug 25, 2018
f22e4a4
Remove debug code
Nihisil Aug 25, 2018
1cbe394
Issue #40. Allow to call closed kan when in riichi
Nihisil Aug 25, 2018
29485ab
Fix failed tests
Nihisil Aug 27, 2018
352a9b8
Issue #46. Improve hand building logic
Nihisil Aug 27, 2018
ed56764
Issue #47. Fix bug with dora tiles in hand building logic
Nihisil Aug 27, 2018
ef60ab3
Issue #60. Add additional value to tiles close to the dora
Nihisil Aug 27, 2018
1baddb4
Small refactoring
Nihisil Aug 27, 2018
e7bd1fc
Fix typo
Nihisil Aug 27, 2018
51ca1b5
Small refactoring of tanyao tests
Nihisil Aug 27, 2018
50c944b
Issue #54. Don't try to ruin our closed hand when we are aim for tanyao
Nihisil Aug 27, 2018
8f308e5
Don't try to aim for tanyao when there are 5+ terminal and honor tile…
Nihisil Aug 27, 2018
e9ec4fc
Fix file logging
Nihisil Aug 28, 2018
bccb248
Increase delay after agari
Nihisil Aug 28, 2018
8e06da6
Issue #69. Fix a crash after called chankan
Nihisil Aug 28, 2018
2d7c1e7
Add profile files to the gitignore
Nihisil Aug 29, 2018
486722d
Issue #67. Increase hand building speed
Nihisil Aug 29, 2018
686d705
Change delays a little bit
Nihisil Aug 29, 2018
3940256
Fix possible issues with hand building
Nihisil Aug 29, 2018
bd217a6
Improve tanyao strategy (#73)
bogachev-pa Aug 31, 2018
8b58994
Initialize shanten, ukeire, ukeire_second on init hand event
Nihisil Aug 31, 2018
53dd439
Fix
Nihisil Aug 31, 2018
7ac65cc
Add comment
Nihisil Aug 31, 2018
31f4c51
Refactor dora calculations and strategies a bit
Nihisil Aug 31, 2018
586221c
#50. Activate strategy after enemy discard as well
Nihisil Aug 31, 2018
d500980
Print closed kan meld in log as well
Nihisil Aug 31, 2018
3a2a927
Print to the log the point where we stop defence
Nihisil Sep 1, 2018
3d338ac
improve honitsu and add chinitsu strategies (#55)
bogachev-pa Aug 31, 2018
f9e92df
fixs TODOs in honitsu/chinitsu strategies
bogachev-pa Sep 1, 2018
7e1b0ce
fix agari without yaku handling
bogachev-pa Sep 1, 2018
d88b5dc
yakuhai: fix yakuhai keeping conditions
bogachev-pa Sep 1, 2018
918a9a0
implement basic chiitoitsu strategy (#57)
bogachev-pa Sep 1, 2018
6c13396
Merge pull request #78 from MahjongRepository/honitsu_chinitsu
Nihisil Sep 2, 2018
7ad7946
Merge remote-tracking branch 'origin/chiitoitsu' into dev
Nihisil Sep 2, 2018
c7f7e39
Merge branch 'dev' of github.com:MahjongRepository/tenhou-python-bot …
Nihisil Sep 2, 2018
59ced85
Fix typo
Nihisil Sep 2, 2018
eb01472
Add missed LOG_PREFIX to settings
Nihisil Sep 2, 2018
5b64905
forbid meld for chiitoitsu
bogachev-pa Sep 2, 2018
1c46b1b
fix bug with skipping first yakuhai (#79)
bogachev-pa Sep 2, 2018
b9447b9
don't skip honor pons with honitsu if we still have non-suitable tile…
bogachev-pa Sep 2, 2018
bd05483
Merge pull request #82 from MahjongRepository/yakuhai_skip_first
Nihisil Sep 3, 2018
25ab8d8
add tests for known bugs (#84, #85)
bogachev-pa Sep 3, 2018
e26dc63
add basic damaten logic (#59)
bogachev-pa Sep 2, 2018
69e1852
Merge pull request #86 from MahjongRepository/more_tests
Nihisil Sep 4, 2018
4df0253
avoid melding into agari if that's not good for hand building #85
bogachev-pa Sep 4, 2018
8256feb
Merge pull request #87 from MahjongRepository/meld_into_agari
Nihisil Sep 5, 2018
ab94009
Upgrade mahjong package to 1.1.5
Nihisil Sep 5, 2018
c464fd5
Calculate shanten for chiitoitsu only when we are with chiitoitsu str…
Nihisil Sep 5, 2018
8aa0a3f
Fix potential issue with chitoitsu
Nihisil Sep 5, 2018
19bb3fe
We will not be in tempai state after init hand event
Nihisil Sep 5, 2018
b2fd92f
Merge branch 'dev' into damaten
Nihisil Sep 5, 2018
cd0a6ed
Small refactoring
Nihisil Sep 5, 2018
7960750
Always call daburi riichi
Nihisil Sep 5, 2018
2c2d2ca
After called meld it is not daburi anymore
Nihisil Sep 5, 2018
f956ebf
add kabe and suji traps handling to damaten logic (#59)
bogachev-pa Sep 5, 2018
2b88785
Fix an issue with not correct tiles count calculation
Nihisil Sep 6, 2018
c886866
add more tests
bogachev-pa Sep 6, 2018
a77301f
refactor ukeire/ukeire2 filtration
bogachev-pa Sep 7, 2018
4079b12
Fix one more issue with not correct tiles count calculation
Nihisil Sep 7, 2018
c84a622
Fix failed tests
Nihisil Sep 7, 2018
0b1aa9c
Fix crash with chiitoitsu like hand
Nihisil Sep 7, 2018
51bb03c
Fix
Nihisil Sep 7, 2018
4255748
Rename damaten.py to riichi.py
Nihisil Sep 7, 2018
152b2a3
Decrease delay before meld call
Nihisil Sep 7, 2018
f04b6f0
Fix ukeire2 calculation
Nihisil Sep 7, 2018
700095e
Improve ukeire2 a little bit more
Nihisil Sep 7, 2018
db252b3
Calculate ukeire2 in iishanten as well
Nihisil Sep 7, 2018
c07c003
Fix for one shanten and filter ukeire borders
Nihisil Sep 7, 2018
a400a35
Fix
Nihisil Sep 7, 2018
0ddbc9e
Fix
Nihisil Sep 7, 2018
cc6dbd7
Fix bug with wrong tiles valuations
Nihisil Sep 7, 2018
3c12e9c
Remove not needed comments
Nihisil Sep 7, 2018
1bd3317
Merge pull request #83 from MahjongRepository/damaten
Nihisil Sep 7, 2018
a05365d
We don't need to calculate ukeire2 for tempai state
Nihisil Sep 7, 2018
4eb7c58
Issue #70. Basic support for decisions logger
Nihisil Sep 8, 2018
38de79c
fix tiles counting for strategy activation decisions (#89)
bogachev-pa Sep 8, 2018
8f99129
Merge pull request #93 from MahjongRepository/strategy_activation_tiles
Nihisil Sep 8, 2018
92a7b6d
Merge pull request #92 from MahjongRepository/logger
Nihisil Sep 8, 2018
1b391a3
don't call kan if it reduces ukeire (#49)
bogachev-pa Sep 8, 2018
4714d83
Merge pull request #94 from MahjongRepository/kan_ukeire
Nihisil Sep 9, 2018
630fd61
Refacatoring. Move all hand building logic to separate file
Nihisil Sep 10, 2018
8ce623c
Refactoring for open hand logic
Nihisil Sep 26, 2018
b6f7440
Remove commented code
Nihisil Sep 28, 2018
fd08f6c
Merge pull request #96 from MahjongRepository/refactoring
Nihisil Oct 19, 2018
2393360
add more yakuhai tests
bogachev-pa Nov 1, 2018
34c18c3
add tests for correcly melding sets with dora #51
bogachev-pa Nov 2, 2018
8516990
consider tiles valuation when choosing best melding option #51
bogachev-pa Nov 5, 2018
01c9766
add/restore discarding rules for yakuhai strategy #88
bogachev-pa Nov 5, 2018
199e532
Merge pull request #97 from MahjongRepository/yakuhai_strategy_upgrade
bogachev-pa Nov 5, 2018
32537a4
add tests for chosing correct wait in tempai #61
bogachev-pa Nov 6, 2018
1538533
when in tempai, choose tile to discard according to (cost * ukeire) v…
bogachev-pa Nov 9, 2018
cd78c84
prepare discard choosing logic for special tanki handling #77
bogachev-pa Nov 9, 2018
9f4a068
choose best tanki tile to wait for in tempai #77
bogachev-pa Nov 11, 2018
312f332
Upgrade requests library
Nihisil Nov 11, 2018
ffd5b82
Allow to run multiple bot instances with run.sh script
Nihisil Nov 11, 2018
750d833
fix bug in find_suji_against_self() function
bogachev-pa Nov 11, 2018
ed737e8
hand builder small code cleanup
bogachev-pa Nov 11, 2018
ef1b621
add more tests on tanki wait selection #77
bogachev-pa Nov 11, 2018
af39dca
Fix an issue with not correct number of tiles on the table when open …
Nihisil Nov 12, 2018
8501f87
Refactoring. Get rid of duplicate attributes (meld and open_sets_34)
Nihisil Nov 12, 2018
bfcf7a4
fix find_discard_options() parameter
bogachev-pa Nov 12, 2018
b3aeb50
pass only closed hand to kabe checking function
bogachev-pa Nov 12, 2018
4b4e704
consider furiten when choosing wait in tempai #61
bogachev-pa Nov 13, 2018
9f4fbf7
update yakuhai strategy logic after changing when tile is added to ta…
bogachev-pa Nov 13, 2018
1d01169
Merge pull request #100 from MahjongRepository/tempai_waiting
Nihisil Nov 14, 2018
8d8f848
Fix a crash for agari state and attempt to call kan
Nihisil Nov 18, 2018
d33ebc4
Fix a crash related to reconnect logic and called chankan
Nihisil Nov 18, 2018
65a22ee
Fix a crash with chankan and win suggestions
Nihisil Nov 18, 2018
49d24ce
don't go through wait choosing logic if there is only one option #103
bogachev-pa Nov 19, 2018
17aa81f
fix meld.called_tile check #105, #106
bogachev-pa Nov 19, 2018
fe9eba7
fix remaining tiles counting when calling melds #107
bogachev-pa Nov 22, 2018
ad5edb0
correctly determine player from who meld was called #107
bogachev-pa Nov 22, 2018
552b038
fix tests after melds accouting rework #107
bogachev-pa Nov 22, 2018
d0eed98
Merge pull request #108 from MahjongRepository/kan_tiles_count
bogachev-pa Nov 23, 2018
b445071
Reduce agari delay
Nihisil Nov 25, 2018
d559c34
Add two failed tests for second level ukeire
Nihisil Nov 27, 2018
9723dac
Create CODE_OF_CONDUCT.md
Nihisil Nov 27, 2018
d781b01
Create CODE_OF_CONDUCT.md
Nihisil Nov 27, 2018
2c5b195
fix ukeire2 calculation #109
bogachev-pa Nov 27, 2018
3f4401f
hand cost in 1-shanten consideration [DRAFT]
bogachev-pa Nov 27, 2018
71ace76
various fixes
bogachev-pa Nov 28, 2018
f52f79c
don't try to "discard" tile from 13-tile hand
bogachev-pa Nov 28, 2018
c19dc56
Fix failed tests
Nihisil Nov 29, 2018
d368f3b
Fix lint warnings
Nihisil Nov 29, 2018
1704d6b
improve atodzuke handling rules #72
bogachev-pa Dec 1, 2018
bb9a19d
Fix a bug with yakuhai strategy and tempai
Nihisil Dec 1, 2018
33b663a
Merge pull request #111 from MahjongRepository/ukeire2
Nihisil Dec 1, 2018
2b3c134
fix shanten calculation rules in regard with chiitoitsu consideration
bogachev-pa Dec 1, 2018
7eaf1d7
revise strategies order
bogachev-pa Dec 1, 2018
be3f08a
fix ukeire2 calculation when we are doing a meld
bogachev-pa Dec 2, 2018
68d31bb
Merge pull request #112 from MahjongRepository/chiitoitsu_shanten
Nihisil Dec 3, 2018
6432fb8
Improve logic to call chankan sets
Nihisil Dec 4, 2018
b1da518
Add more tests and rename chankan to shouminkan in comments
Nihisil Dec 4, 2018
2765985
Improve tests a bit
Nihisil Dec 4, 2018
dab07f4
Merge pull request #113 from MahjongRepository/chankan
Nihisil Dec 4, 2018
847f6d3
Merge branch 'master' into dev
Nihisil Feb 26, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[flake8]
max-line-length = 120
exclude = settings.py
exclude = *settings.py,tests_validate_hand.py
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ project/game/data/*
project/analytics/data/*

# temporary files
experiments
experiments

*.prof
profile.py
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
language: python
dist: xenial
sudo: true
python:
- "3.5"
- "3.6"
- "3.7"
before_script:
- cd project
install: "pip install -r project/requirements.txt"
Expand Down
5 changes: 5 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Tenhou bot code of conduct:

1. A robot may not injure a human being or, through inaction, allow a human being to come to harm.
2. A robot must obey orders given it by human beings except where such orders would conflict with the First Law.
3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.
18 changes: 10 additions & 8 deletions bin/run.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
#!/bin/sh -e

# cron will run each 5 minutes
# and will search the run process
# the cron job will be executed each 5 minutes
# and it will try to find bot process
# if there is no process, it will run it
# example of usage
# */5 * * * * bash /root/bot/bin/run.sh bot_settings_name

# */5 * * * * bash /root/bot/bin/run.sh
SETTINGS_NAME="$1"

PID=`ps -eaf | grep project/main.py | grep -v grep | awk '{print $2}'`
PID=`ps -eaf | grep "project/main.py -s ${SETTINGS_NAME}" | grep -v grep | awk '{print $2}'`

if [[ "" = "$PID" ]]; then
/root/bot/env/bin/python /root/bot/project/main.py
if [[ "" = "$PID" ]]; then
/root/bot/env/bin/python /root/bot/project/main.py -s ${SETTINGS_NAME}
else
WORKED_SECONDS=`ps -p "$PID" -o etimes=`
# if process run > 60 minutes, probably it hang and we need to kill it
if [ ${WORKED_SECONDS} -gt "3600" ]; then
# if process run > 60 minutes, probably it hangs and we need to kill it
if [[ ${WORKED_SECONDS} -gt "3600" ]]; then
kill ${PID}
fi
fi
5 changes: 3 additions & 2 deletions project/game/ai/base/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ def should_call_riichi(self):
"""
return False

def should_call_kan(self, tile, is_open_kan):
def should_call_kan(self, tile, is_open_kan, from_riichi=False):
"""
When bot can call kan or chankan this method will be called
When bot can call kan or shouminkan this method will be called
:param tile: 136 tile format
:param is_open_kan: boolean
:param from_riichi: boolean
:return: kan type (Meld.KAN, Meld.CHANKAN) or None
"""
return False
Expand Down
92 changes: 77 additions & 15 deletions project/game/ai/discard.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# -*- coding: utf-8 -*-
from mahjong.constants import AKA_DORA_LIST
from mahjong.tile import TilesConverter
from mahjong.utils import is_honor, simplify, plus_dora, is_aka_dora
from mahjong.utils import is_honor, simplify, plus_dora, is_aka_dora, is_sou, is_man, is_pin

from game.ai.first_version.strategies.main import BaseStrategy


class DiscardOption(object):
DORA_VALUE = 10000
DORA_FIRST_NEIGHBOUR = 1000
DORA_SECOND_NEIGHBOUR = 100

player = None

# in 34 tile format
tile_to_discard = None
# array of tiles that will improve our hand
waiting = None
# how much tiles will improve our hand
tiles_count = None
ukeire = None
ukeire_second = None
# number of shanten for that tile
shanten = None
# sometimes we had to force tile to be discarded
Expand All @@ -23,25 +30,45 @@ class DiscardOption(object):
valuation = None
# how danger this tile is
danger = None
# wait to ukeire map
wait_to_ukeire = None
# second level cost approximation for 1-shanten hands
second_level_cost = None

def __init__(self, player, tile_to_discard, shanten, waiting, tiles_count, danger=100):
def __init__(self, player, tile_to_discard, shanten, waiting, ukeire, danger=100, wait_to_ukeire=None):
"""
:param player:
:param tile_to_discard: tile in 34 format
:param waiting: list of tiles in 34 format
:param tiles_count: count of tiles to wait after discard
:param ukeire: count of tiles to wait after discard
"""
self.player = player
self.tile_to_discard = tile_to_discard
self.shanten = shanten
self.waiting = waiting
self.tiles_count = tiles_count
self.ukeire = ukeire
self.ukeire_second = 0
self.count_of_dora = 0
self.danger = danger
self.had_to_be_saved = False
self.had_to_be_discarded = False
self.wait_to_ukeire = wait_to_ukeire

self.calculate_value()

def __unicode__(self):
tile_format_136 = TilesConverter.to_one_line_string([self.tile_to_discard*4])
return 'tile={}, shanten={}, ukeire={}, ukeire2={}, valuation={}'.format(
tile_format_136,
self.shanten,
self.ukeire,
self.ukeire_second,
self.valuation
)

def __repr__(self):
return '{}'.format(self.__unicode__())

def find_tile_in_hand(self, closed_hand):
"""
Find and return 136 tile in closed player hand
Expand Down Expand Up @@ -69,31 +96,66 @@ def find_tile_in_hand(self, closed_hand):

return TilesConverter.find_34_tile_in_136_array(self.tile_to_discard, closed_hand)

def calculate_value(self, shanten=None):
def calculate_value(self):
# base is 100 for ability to mark tiles as not needed (like set value to 50)
value = 100
honored_value = 20

# we don't need to keep honor tiles in almost completed hand
if shanten and shanten <= 2:
honored_value = 0

if is_honor(self.tile_to_discard):
if self.tile_to_discard in self.player.valued_honors:
count_of_winds = [x for x in self.player.valued_honors if x == self.tile_to_discard]
# for west-west, east-east we had to double tile value
value += honored_value * len(count_of_winds)
else:
# suits
suit_tile_grades = [10, 20, 30, 40, 50, 40, 30, 20, 10]
# aim for tanyao
if self.player.ai.current_strategy and self.player.ai.current_strategy.type == BaseStrategy.TANYAO:
suit_tile_grades = [10, 20, 30, 50, 40, 50, 30, 20, 10]
# usual hand
else:
suit_tile_grades = [10, 20, 40, 50, 30, 50, 40, 20, 10]

simplified_tile = simplify(self.tile_to_discard)
value += suit_tile_grades[simplified_tile]

for indicator in self.player.table.dora_indicators:
indicator_34 = indicator // 4
if is_honor(indicator_34):
continue

# indicator and tile not from the same suit
if is_sou(indicator_34) and not is_sou(self.tile_to_discard):
continue

# indicator and tile not from the same suit
if is_man(indicator_34) and not is_man(self.tile_to_discard):
continue

# indicator and tile not from the same suit
if is_pin(indicator_34) and not is_pin(self.tile_to_discard):
continue

simplified_indicator = simplify(indicator_34)
simplified_dora = simplified_indicator + 1
# indicator is 9 man
if simplified_dora == 9:
simplified_dora = 0

# tile so close to the dora
if simplified_tile + 1 == simplified_dora or simplified_tile - 1 == simplified_dora:
value += DiscardOption.DORA_FIRST_NEIGHBOUR

# tile not far away from dora
if simplified_tile + 2 == simplified_dora or simplified_tile - 2 == simplified_dora:
value += DiscardOption.DORA_SECOND_NEIGHBOUR

count_of_dora = plus_dora(self.tile_to_discard * 4, self.player.table.dora_indicators)
if is_aka_dora(self.tile_to_discard * 4, self.player.table.has_open_tanyao):

tile_136 = self.find_tile_in_hand(self.player.closed_hand)
if is_aka_dora(tile_136, self.player.table.has_aka_dora):
count_of_dora += 1

value += 50 * count_of_dora
self.count_of_dora = count_of_dora
value += count_of_dora * DiscardOption.DORA_VALUE

if is_honor(self.tile_to_discard):
# depends on how much honor tiles were discarded
Expand All @@ -108,4 +170,4 @@ def calculate_value(self, shanten=None):
if value == 0:
self.had_to_be_discarded = True

self.valuation = value
self.valuation = int(value)
4 changes: 2 additions & 2 deletions project/game/ai/first_version/defence/impossible_wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def find_tiles_to_discard(self, _):

results = []
for x in HONOR_INDICES:
if self.player.total_tiles(x, self.defence.hand_34) == 4:
if self.player.total_tiles(x, self.defence.closed_hand_34) == 4:
results.append(DefenceTile(x, DefenceTile.SAFE))

if self.player.total_tiles(x, self.defence.hand_34) == 3:
if self.player.total_tiles(x, self.defence.closed_hand_34) == 3:
results.append(DefenceTile(x, DefenceTile.ALMOST_SAFE_TILE))

return results
117 changes: 87 additions & 30 deletions project/game/ai/first_version/defence/kabe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,96 @@

class Kabe(Defence):

def find_tiles_to_discard(self, _):
results = []

def find_all_kabe(self, tiles_34):
# all indices shifted to -1
kabe_matrix = [
{'indices': [1], 'blocked_tiles': [0]},
{'indices': [2], 'blocked_tiles': [0, 1]},
{'indices': [3], 'blocked_tiles': [1, 2]},
{'indices': [4], 'blocked_tiles': [2, 6]},
{'indices': [5], 'blocked_tiles': [6, 7]},
{'indices': [6], 'blocked_tiles': [7, 8]},
{'indices': [7], 'blocked_tiles': [8]},
{'indices': [1, 5], 'blocked_tiles': [3]},
{'indices': [2, 6], 'blocked_tiles': [4]},
{'indices': [3, 7], 'blocked_tiles': [5]},
{'indices': [1, 4], 'blocked_tiles': [2, 3]},
{'indices': [2, 5], 'blocked_tiles': [3, 4]},
{'indices': [3, 6], 'blocked_tiles': [4, 5]},
{'indices': [4, 7], 'blocked_tiles': [5, 6]},
{'indices': [1], 'blocked_tiles': [0], 'type': KabeTile.STRONG_KABE},
{'indices': [2], 'blocked_tiles': [0, 1], 'type': KabeTile.STRONG_KABE},
{'indices': [6], 'blocked_tiles': [7, 8], 'type': KabeTile.STRONG_KABE},
{'indices': [7], 'blocked_tiles': [8], 'type': KabeTile.STRONG_KABE},
{'indices': [0, 3], 'blocked_tiles': [2, 3], 'type': KabeTile.STRONG_KABE},
{'indices': [1, 3], 'blocked_tiles': [2], 'type': KabeTile.STRONG_KABE},
{'indices': [1, 4], 'blocked_tiles': [2, 3], 'type': KabeTile.STRONG_KABE},
{'indices': [2, 4], 'blocked_tiles': [3], 'type': KabeTile.STRONG_KABE},
{'indices': [2, 5], 'blocked_tiles': [3, 4], 'type': KabeTile.STRONG_KABE},
{'indices': [3, 5], 'blocked_tiles': [4], 'type': KabeTile.STRONG_KABE},
{'indices': [3, 6], 'blocked_tiles': [4, 5], 'type': KabeTile.STRONG_KABE},
{'indices': [4, 6], 'blocked_tiles': [5], 'type': KabeTile.STRONG_KABE},
{'indices': [4, 7], 'blocked_tiles': [5, 6], 'type': KabeTile.STRONG_KABE},
{'indices': [5, 7], 'blocked_tiles': [6], 'type': KabeTile.STRONG_KABE},
{'indices': [5, 8], 'blocked_tiles': [6, 7], 'type': KabeTile.STRONG_KABE},

{'indices': [3], 'blocked_tiles': [1, 2], 'type': KabeTile.WEAK_KABE},
{'indices': [4], 'blocked_tiles': [2, 6], 'type': KabeTile.WEAK_KABE},
{'indices': [5], 'blocked_tiles': [6, 7], 'type': KabeTile.WEAK_KABE},
{'indices': [1, 5], 'blocked_tiles': [3], 'type': KabeTile.WEAK_KABE},
{'indices': [2, 6], 'blocked_tiles': [4], 'type': KabeTile.WEAK_KABE},
{'indices': [3, 7], 'blocked_tiles': [5], 'type': KabeTile.WEAK_KABE},
]

suits = self._suits_tiles(self.defence.hand_34)
kabe_tiles_strong = []
kabe_tiles_weak = []
kabe_tiles_partial = []

suits = self._suits_tiles(tiles_34)
for x in range(0, 3):
suit = suits[x]
# "kabe" - 4 revealed tiles
kabe_tiles = []
partial_kabe_tiles = []
for y in range(0, 9):
suit_tile = suit[y]
if suit_tile == 4:
kabe_tiles.append(y)
elif suit_tile == 3:
partial_kabe_tiles.append(y)

blocked_indices = []
for matrix_item in kabe_matrix:
all_indices = len(list(set(matrix_item['indices']) - set(kabe_tiles))) == 0
if all_indices:
blocked_indices.extend(matrix_item['blocked_tiles'])
if len(list(set(matrix_item['indices']) - set(kabe_tiles))) == 0:
for tile in matrix_item['blocked_tiles']:
if matrix_item['type'] == KabeTile.STRONG_KABE:
kabe_tiles_strong.append(tile + x * 9)
else:
kabe_tiles_weak.append(tile + x * 9)

if len(list(set(matrix_item['indices']) - set(partial_kabe_tiles))) == 0:
for tile in matrix_item['blocked_tiles']:
kabe_tiles_partial.append(tile + x * 9)

kabe_tiles_unique = []
kabe_tiles_strong = list(set(kabe_tiles_strong))
kabe_tiles_weak = list(set(kabe_tiles_weak))
kabe_tiles_partial = list(set(kabe_tiles_partial))

for tile in kabe_tiles_strong:
kabe_tiles_unique.append(KabeTile(tile, KabeTile.STRONG_KABE))

for tile in kabe_tiles_weak:
if tile not in kabe_tiles_strong:
kabe_tiles_unique.append(KabeTile(tile, KabeTile.WEAK_KABE))

for tile in kabe_tiles_partial:
if tile not in kabe_tiles_strong and tile not in kabe_tiles_weak:
kabe_tiles_unique.append(KabeTile(tile, KabeTile.PARTIAL_KABE))

return kabe_tiles_unique

blocked_indices = list(set(blocked_indices))
for index in blocked_indices:
# let's find 34 tile index
tile = index + x * 9
if self.player.total_tiles(tile, self.defence.hand_34) == 4:
results.append(DefenceTile(tile, DefenceTile.SAFE))
def find_tiles_to_discard(self, _):
all_kabe = self.find_all_kabe(self.defence.closed_hand_34)

results = []

for kabe in all_kabe:
# we don't use it for defence now
if kabe.kabe_type == KabeTile.PARTIAL_KABE:
continue

if self.player.total_tiles(tile, self.defence.hand_34) == 3:
results.append(DefenceTile(tile, DefenceTile.ALMOST_SAFE_TILE))
tile = kabe.tile_34
if self.player.total_tiles(tile, self.defence.closed_hand_34) == 4:
results.append(DefenceTile(tile, DefenceTile.SAFE))

if self.player.total_tiles(tile, self.defence.closed_hand_34) == 3:
results.append(DefenceTile(tile, DefenceTile.ALMOST_SAFE_TILE))

return results

Expand Down Expand Up @@ -88,3 +132,16 @@ def _suits_tiles(self, tiles_34):
suits[suit_index][simplified_tile] += total_tiles

return suits


class KabeTile(object):
STRONG_KABE = 0
WEAK_KABE = 1
PARTIAL_KABE = 2

tile_34 = None
kabe_type = None

def __init__(self, tile_34, kabe_type):
self.tile_34 = tile_34
self.kabe_type = kabe_type
Loading