mjai 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/mjai +9 -0
- data/bin/mjai-shanten +9 -0
- data/bin/mjai-tsumogiri +9 -0
- data/lib/mjai/action.rb +41 -0
- data/lib/mjai/active_game.rb +230 -0
- data/lib/mjai/archive.rb +46 -0
- data/lib/mjai/archive_player.rb +47 -0
- data/lib/mjai/context.rb +34 -0
- data/lib/mjai/file_converter.rb +86 -0
- data/lib/mjai/furo.rb +57 -0
- data/lib/mjai/game.rb +357 -0
- data/lib/mjai/hora.rb +528 -0
- data/lib/mjai/jsonizable.rb +171 -0
- data/lib/mjai/mentsu.rb +46 -0
- data/lib/mjai/mjai_command.rb +93 -0
- data/lib/mjai/mjson_archive.rb +25 -0
- data/lib/mjai/pai.rb +138 -0
- data/lib/mjai/player.rb +340 -0
- data/lib/mjai/puppet_player.rb +14 -0
- data/lib/mjai/shanten_analysis.rb +273 -0
- data/lib/mjai/shanten_player.rb +102 -0
- data/lib/mjai/tcp_client_game.rb +63 -0
- data/lib/mjai/tcp_game_server.rb +205 -0
- data/lib/mjai/tcp_player.rb +66 -0
- data/lib/mjai/tenhou_archive.rb +412 -0
- data/lib/mjai/tenpai_analysis.rb +62 -0
- data/lib/mjai/tsumogiri_player.rb +20 -0
- data/lib/mjai/validation_error.rb +7 -0
- data/lib/mjai/with_fields.rb +18 -0
- data/share/html/css/style.css +77 -0
- data/share/html/css/style.scss +106 -0
- data/share/html/images/README.txt +1 -0
- data/share/html/images/b_1_1.gif +0 -0
- data/share/html/images/b_1_2.gif +0 -0
- data/share/html/images/b_5_1.gif +0 -0
- data/share/html/images/b_5_2.gif +0 -0
- data/share/html/images/b_8_1.gif +0 -0
- data/share/html/images/b_8_2.gif +0 -0
- data/share/html/images/b_9_1.gif +0 -0
- data/share/html/images/b_9_2.gif +0 -0
- data/share/html/images/blank.png +0 -0
- data/share/html/images/c_c_1.gif +0 -0
- data/share/html/images/c_c_2.gif +0 -0
- data/share/html/images/c_c_3.gif +0 -0
- data/share/html/images/c_c_4.gif +0 -0
- data/share/html/images/c_e_1.gif +0 -0
- data/share/html/images/c_e_2.gif +0 -0
- data/share/html/images/c_e_3.gif +0 -0
- data/share/html/images/c_e_4.gif +0 -0
- data/share/html/images/c_n_1.gif +0 -0
- data/share/html/images/c_n_2.gif +0 -0
- data/share/html/images/c_n_3.gif +0 -0
- data/share/html/images/c_n_4.gif +0 -0
- data/share/html/images/c_s_1.gif +0 -0
- data/share/html/images/c_s_2.gif +0 -0
- data/share/html/images/c_s_3.gif +0 -0
- data/share/html/images/c_s_4.gif +0 -0
- data/share/html/images/c_w_1.gif +0 -0
- data/share/html/images/c_w_2.gif +0 -0
- data/share/html/images/c_w_3.gif +0 -0
- data/share/html/images/c_w_4.gif +0 -0
- data/share/html/images/dice.gif +0 -0
- data/share/html/images/p_bk_0.gif +0 -0
- data/share/html/images/p_bk_1.gif +0 -0
- data/share/html/images/p_bk_2.gif +0 -0
- data/share/html/images/p_bk_3.gif +0 -0
- data/share/html/images/p_bk_4.gif +0 -0
- data/share/html/images/p_bk_5.gif +0 -0
- data/share/html/images/p_bk_6.gif +0 -0
- data/share/html/images/p_bk_7.gif +0 -0
- data/share/html/images/p_ji_c_0.gif +0 -0
- data/share/html/images/p_ji_c_1.gif +0 -0
- data/share/html/images/p_ji_c_2.gif +0 -0
- data/share/html/images/p_ji_c_3.gif +0 -0
- data/share/html/images/p_ji_c_4.gif +0 -0
- data/share/html/images/p_ji_c_5.gif +0 -0
- data/share/html/images/p_ji_c_6.gif +0 -0
- data/share/html/images/p_ji_c_7.gif +0 -0
- data/share/html/images/p_ji_e_0.gif +0 -0
- data/share/html/images/p_ji_e_1.gif +0 -0
- data/share/html/images/p_ji_e_2.gif +0 -0
- data/share/html/images/p_ji_e_3.gif +0 -0
- data/share/html/images/p_ji_e_4.gif +0 -0
- data/share/html/images/p_ji_e_5.gif +0 -0
- data/share/html/images/p_ji_e_6.gif +0 -0
- data/share/html/images/p_ji_e_7.gif +0 -0
- data/share/html/images/p_ji_h_0.gif +0 -0
- data/share/html/images/p_ji_h_1.gif +0 -0
- data/share/html/images/p_ji_h_2.gif +0 -0
- data/share/html/images/p_ji_h_3.gif +0 -0
- data/share/html/images/p_ji_h_4.gif +0 -0
- data/share/html/images/p_ji_h_5.gif +0 -0
- data/share/html/images/p_ji_h_6.gif +0 -0
- data/share/html/images/p_ji_h_7.gif +0 -0
- data/share/html/images/p_ji_n_0.gif +0 -0
- data/share/html/images/p_ji_n_1.gif +0 -0
- data/share/html/images/p_ji_n_2.gif +0 -0
- data/share/html/images/p_ji_n_3.gif +0 -0
- data/share/html/images/p_ji_n_4.gif +0 -0
- data/share/html/images/p_ji_n_5.gif +0 -0
- data/share/html/images/p_ji_n_6.gif +0 -0
- data/share/html/images/p_ji_n_7.gif +0 -0
- data/share/html/images/p_ji_s_0.gif +0 -0
- data/share/html/images/p_ji_s_1.gif +0 -0
- data/share/html/images/p_ji_s_2.gif +0 -0
- data/share/html/images/p_ji_s_3.gif +0 -0
- data/share/html/images/p_ji_s_4.gif +0 -0
- data/share/html/images/p_ji_s_5.gif +0 -0
- data/share/html/images/p_ji_s_6.gif +0 -0
- data/share/html/images/p_ji_s_7.gif +0 -0
- data/share/html/images/p_ji_w_0.gif +0 -0
- data/share/html/images/p_ji_w_1.gif +0 -0
- data/share/html/images/p_ji_w_2.gif +0 -0
- data/share/html/images/p_ji_w_3.gif +0 -0
- data/share/html/images/p_ji_w_4.gif +0 -0
- data/share/html/images/p_ji_w_5.gif +0 -0
- data/share/html/images/p_ji_w_6.gif +0 -0
- data/share/html/images/p_ji_w_7.gif +0 -0
- data/share/html/images/p_ms1_0.gif +0 -0
- data/share/html/images/p_ms1_1.gif +0 -0
- data/share/html/images/p_ms1_2.gif +0 -0
- data/share/html/images/p_ms1_3.gif +0 -0
- data/share/html/images/p_ms1_4.gif +0 -0
- data/share/html/images/p_ms1_5.gif +0 -0
- data/share/html/images/p_ms1_6.gif +0 -0
- data/share/html/images/p_ms1_7.gif +0 -0
- data/share/html/images/p_ms2_0.gif +0 -0
- data/share/html/images/p_ms2_1.gif +0 -0
- data/share/html/images/p_ms2_2.gif +0 -0
- data/share/html/images/p_ms2_3.gif +0 -0
- data/share/html/images/p_ms2_4.gif +0 -0
- data/share/html/images/p_ms2_5.gif +0 -0
- data/share/html/images/p_ms2_6.gif +0 -0
- data/share/html/images/p_ms2_7.gif +0 -0
- data/share/html/images/p_ms3_0.gif +0 -0
- data/share/html/images/p_ms3_1.gif +0 -0
- data/share/html/images/p_ms3_2.gif +0 -0
- data/share/html/images/p_ms3_3.gif +0 -0
- data/share/html/images/p_ms3_4.gif +0 -0
- data/share/html/images/p_ms3_5.gif +0 -0
- data/share/html/images/p_ms3_6.gif +0 -0
- data/share/html/images/p_ms3_7.gif +0 -0
- data/share/html/images/p_ms4_0.gif +0 -0
- data/share/html/images/p_ms4_1.gif +0 -0
- data/share/html/images/p_ms4_2.gif +0 -0
- data/share/html/images/p_ms4_3.gif +0 -0
- data/share/html/images/p_ms4_4.gif +0 -0
- data/share/html/images/p_ms4_5.gif +0 -0
- data/share/html/images/p_ms4_6.gif +0 -0
- data/share/html/images/p_ms4_7.gif +0 -0
- data/share/html/images/p_ms5_0.gif +0 -0
- data/share/html/images/p_ms5_1.gif +0 -0
- data/share/html/images/p_ms5_2.gif +0 -0
- data/share/html/images/p_ms5_3.gif +0 -0
- data/share/html/images/p_ms5_4.gif +0 -0
- data/share/html/images/p_ms5_5.gif +0 -0
- data/share/html/images/p_ms5_6.gif +0 -0
- data/share/html/images/p_ms5_7.gif +0 -0
- data/share/html/images/p_ms5r_1.png +0 -0
- data/share/html/images/p_ms5r_3.png +0 -0
- data/share/html/images/p_ms6_0.gif +0 -0
- data/share/html/images/p_ms6_1.gif +0 -0
- data/share/html/images/p_ms6_2.gif +0 -0
- data/share/html/images/p_ms6_3.gif +0 -0
- data/share/html/images/p_ms6_4.gif +0 -0
- data/share/html/images/p_ms6_5.gif +0 -0
- data/share/html/images/p_ms6_6.gif +0 -0
- data/share/html/images/p_ms6_7.gif +0 -0
- data/share/html/images/p_ms7_0.gif +0 -0
- data/share/html/images/p_ms7_1.gif +0 -0
- data/share/html/images/p_ms7_2.gif +0 -0
- data/share/html/images/p_ms7_3.gif +0 -0
- data/share/html/images/p_ms7_4.gif +0 -0
- data/share/html/images/p_ms7_5.gif +0 -0
- data/share/html/images/p_ms7_6.gif +0 -0
- data/share/html/images/p_ms7_7.gif +0 -0
- data/share/html/images/p_ms8_0.gif +0 -0
- data/share/html/images/p_ms8_1.gif +0 -0
- data/share/html/images/p_ms8_2.gif +0 -0
- data/share/html/images/p_ms8_3.gif +0 -0
- data/share/html/images/p_ms8_4.gif +0 -0
- data/share/html/images/p_ms8_5.gif +0 -0
- data/share/html/images/p_ms8_6.gif +0 -0
- data/share/html/images/p_ms8_7.gif +0 -0
- data/share/html/images/p_ms9_0.gif +0 -0
- data/share/html/images/p_ms9_1.gif +0 -0
- data/share/html/images/p_ms9_2.gif +0 -0
- data/share/html/images/p_ms9_3.gif +0 -0
- data/share/html/images/p_ms9_4.gif +0 -0
- data/share/html/images/p_ms9_5.gif +0 -0
- data/share/html/images/p_ms9_6.gif +0 -0
- data/share/html/images/p_ms9_7.gif +0 -0
- data/share/html/images/p_no_0.gif +0 -0
- data/share/html/images/p_no_1.gif +0 -0
- data/share/html/images/p_no_2.gif +0 -0
- data/share/html/images/p_no_3.gif +0 -0
- data/share/html/images/p_no_4.gif +0 -0
- data/share/html/images/p_no_5.gif +0 -0
- data/share/html/images/p_no_6.gif +0 -0
- data/share/html/images/p_no_7.gif +0 -0
- data/share/html/images/p_ps1_0.gif +0 -0
- data/share/html/images/p_ps1_1.gif +0 -0
- data/share/html/images/p_ps1_2.gif +0 -0
- data/share/html/images/p_ps1_3.gif +0 -0
- data/share/html/images/p_ps1_4.gif +0 -0
- data/share/html/images/p_ps1_5.gif +0 -0
- data/share/html/images/p_ps1_6.gif +0 -0
- data/share/html/images/p_ps1_7.gif +0 -0
- data/share/html/images/p_ps2_0.gif +0 -0
- data/share/html/images/p_ps2_1.gif +0 -0
- data/share/html/images/p_ps2_2.gif +0 -0
- data/share/html/images/p_ps2_3.gif +0 -0
- data/share/html/images/p_ps2_4.gif +0 -0
- data/share/html/images/p_ps2_5.gif +0 -0
- data/share/html/images/p_ps2_6.gif +0 -0
- data/share/html/images/p_ps2_7.gif +0 -0
- data/share/html/images/p_ps3_0.gif +0 -0
- data/share/html/images/p_ps3_1.gif +0 -0
- data/share/html/images/p_ps3_2.gif +0 -0
- data/share/html/images/p_ps3_3.gif +0 -0
- data/share/html/images/p_ps3_4.gif +0 -0
- data/share/html/images/p_ps3_5.gif +0 -0
- data/share/html/images/p_ps3_6.gif +0 -0
- data/share/html/images/p_ps3_7.gif +0 -0
- data/share/html/images/p_ps4_0.gif +0 -0
- data/share/html/images/p_ps4_1.gif +0 -0
- data/share/html/images/p_ps4_2.gif +0 -0
- data/share/html/images/p_ps4_3.gif +0 -0
- data/share/html/images/p_ps4_4.gif +0 -0
- data/share/html/images/p_ps4_5.gif +0 -0
- data/share/html/images/p_ps4_6.gif +0 -0
- data/share/html/images/p_ps4_7.gif +0 -0
- data/share/html/images/p_ps5_0.gif +0 -0
- data/share/html/images/p_ps5_1.gif +0 -0
- data/share/html/images/p_ps5_2.gif +0 -0
- data/share/html/images/p_ps5_3.gif +0 -0
- data/share/html/images/p_ps5_4.gif +0 -0
- data/share/html/images/p_ps5_5.gif +0 -0
- data/share/html/images/p_ps5_6.gif +0 -0
- data/share/html/images/p_ps5_7.gif +0 -0
- data/share/html/images/p_ps5r_1.png +0 -0
- data/share/html/images/p_ps5r_3.png +0 -0
- data/share/html/images/p_ps6_0.gif +0 -0
- data/share/html/images/p_ps6_1.gif +0 -0
- data/share/html/images/p_ps6_2.gif +0 -0
- data/share/html/images/p_ps6_3.gif +0 -0
- data/share/html/images/p_ps6_4.gif +0 -0
- data/share/html/images/p_ps6_5.gif +0 -0
- data/share/html/images/p_ps6_6.gif +0 -0
- data/share/html/images/p_ps6_7.gif +0 -0
- data/share/html/images/p_ps7_0.gif +0 -0
- data/share/html/images/p_ps7_1.gif +0 -0
- data/share/html/images/p_ps7_2.gif +0 -0
- data/share/html/images/p_ps7_3.gif +0 -0
- data/share/html/images/p_ps7_4.gif +0 -0
- data/share/html/images/p_ps7_5.gif +0 -0
- data/share/html/images/p_ps7_6.gif +0 -0
- data/share/html/images/p_ps7_7.gif +0 -0
- data/share/html/images/p_ps8_0.gif +0 -0
- data/share/html/images/p_ps8_1.gif +0 -0
- data/share/html/images/p_ps8_2.gif +0 -0
- data/share/html/images/p_ps8_3.gif +0 -0
- data/share/html/images/p_ps8_4.gif +0 -0
- data/share/html/images/p_ps8_5.gif +0 -0
- data/share/html/images/p_ps8_6.gif +0 -0
- data/share/html/images/p_ps8_7.gif +0 -0
- data/share/html/images/p_ps9_0.gif +0 -0
- data/share/html/images/p_ps9_1.gif +0 -0
- data/share/html/images/p_ps9_2.gif +0 -0
- data/share/html/images/p_ps9_3.gif +0 -0
- data/share/html/images/p_ps9_4.gif +0 -0
- data/share/html/images/p_ps9_5.gif +0 -0
- data/share/html/images/p_ps9_6.gif +0 -0
- data/share/html/images/p_ps9_7.gif +0 -0
- data/share/html/images/p_ss1_0.gif +0 -0
- data/share/html/images/p_ss1_1.gif +0 -0
- data/share/html/images/p_ss1_2.gif +0 -0
- data/share/html/images/p_ss1_3.gif +0 -0
- data/share/html/images/p_ss1_4.gif +0 -0
- data/share/html/images/p_ss1_5.gif +0 -0
- data/share/html/images/p_ss1_6.gif +0 -0
- data/share/html/images/p_ss1_7.gif +0 -0
- data/share/html/images/p_ss2_0.gif +0 -0
- data/share/html/images/p_ss2_1.gif +0 -0
- data/share/html/images/p_ss2_2.gif +0 -0
- data/share/html/images/p_ss2_3.gif +0 -0
- data/share/html/images/p_ss2_4.gif +0 -0
- data/share/html/images/p_ss2_5.gif +0 -0
- data/share/html/images/p_ss2_6.gif +0 -0
- data/share/html/images/p_ss2_7.gif +0 -0
- data/share/html/images/p_ss3_0.gif +0 -0
- data/share/html/images/p_ss3_1.gif +0 -0
- data/share/html/images/p_ss3_2.gif +0 -0
- data/share/html/images/p_ss3_3.gif +0 -0
- data/share/html/images/p_ss3_4.gif +0 -0
- data/share/html/images/p_ss3_5.gif +0 -0
- data/share/html/images/p_ss3_6.gif +0 -0
- data/share/html/images/p_ss3_7.gif +0 -0
- data/share/html/images/p_ss4_0.gif +0 -0
- data/share/html/images/p_ss4_1.gif +0 -0
- data/share/html/images/p_ss4_2.gif +0 -0
- data/share/html/images/p_ss4_3.gif +0 -0
- data/share/html/images/p_ss4_4.gif +0 -0
- data/share/html/images/p_ss4_5.gif +0 -0
- data/share/html/images/p_ss4_6.gif +0 -0
- data/share/html/images/p_ss4_7.gif +0 -0
- data/share/html/images/p_ss5_0.gif +0 -0
- data/share/html/images/p_ss5_1.gif +0 -0
- data/share/html/images/p_ss5_2.gif +0 -0
- data/share/html/images/p_ss5_3.gif +0 -0
- data/share/html/images/p_ss5_4.gif +0 -0
- data/share/html/images/p_ss5_5.gif +0 -0
- data/share/html/images/p_ss5_6.gif +0 -0
- data/share/html/images/p_ss5_7.gif +0 -0
- data/share/html/images/p_ss5r_1.png +0 -0
- data/share/html/images/p_ss5r_3.png +0 -0
- data/share/html/images/p_ss6_0.gif +0 -0
- data/share/html/images/p_ss6_1.gif +0 -0
- data/share/html/images/p_ss6_2.gif +0 -0
- data/share/html/images/p_ss6_3.gif +0 -0
- data/share/html/images/p_ss6_4.gif +0 -0
- data/share/html/images/p_ss6_5.gif +0 -0
- data/share/html/images/p_ss6_6.gif +0 -0
- data/share/html/images/p_ss6_7.gif +0 -0
- data/share/html/images/p_ss7_0.gif +0 -0
- data/share/html/images/p_ss7_1.gif +0 -0
- data/share/html/images/p_ss7_2.gif +0 -0
- data/share/html/images/p_ss7_3.gif +0 -0
- data/share/html/images/p_ss7_4.gif +0 -0
- data/share/html/images/p_ss7_5.gif +0 -0
- data/share/html/images/p_ss7_6.gif +0 -0
- data/share/html/images/p_ss7_7.gif +0 -0
- data/share/html/images/p_ss8_0.gif +0 -0
- data/share/html/images/p_ss8_1.gif +0 -0
- data/share/html/images/p_ss8_2.gif +0 -0
- data/share/html/images/p_ss8_3.gif +0 -0
- data/share/html/images/p_ss8_4.gif +0 -0
- data/share/html/images/p_ss8_5.gif +0 -0
- data/share/html/images/p_ss8_6.gif +0 -0
- data/share/html/images/p_ss8_7.gif +0 -0
- data/share/html/images/p_ss9_0.gif +0 -0
- data/share/html/images/p_ss9_1.gif +0 -0
- data/share/html/images/p_ss9_2.gif +0 -0
- data/share/html/images/p_ss9_3.gif +0 -0
- data/share/html/images/p_ss9_4.gif +0 -0
- data/share/html/images/p_ss9_5.gif +0 -0
- data/share/html/images/p_ss9_6.gif +0 -0
- data/share/html/images/p_ss9_7.gif +0 -0
- data/share/html/js/archive_player.coffee +379 -0
- data/share/html/js/archive_player.js +505 -0
- data/share/html/js/dytem.coffee +83 -0
- data/share/html/js/dytem.js +128 -0
- data/share/html/js/jquery-1.7.2.min.js +4 -0
- data/share/html/views/archive_player.erb +61 -0
- metadata +435 -0
data/lib/mjai/player.rb
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
require "mjai/pai"
|
4
|
+
require "mjai/tenpai_analysis"
|
5
|
+
|
6
|
+
|
7
|
+
module Mjai
|
8
|
+
|
9
|
+
class Player
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
@log_text = ""
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader(:id)
|
16
|
+
attr_reader(:tehais) # 手牌
|
17
|
+
attr_reader(:furos) # 副露
|
18
|
+
attr_reader(:ho) # 河 (鳴かれた牌を含まない)
|
19
|
+
attr_reader(:sutehais) # 捨牌 (鳴かれた牌を含む)
|
20
|
+
attr_reader(:extra_anpais) # sutehais以外のこのプレーヤに対する安牌
|
21
|
+
attr_reader(:reach_state)
|
22
|
+
attr_reader(:reach_ho_index)
|
23
|
+
attr_reader(:attributes)
|
24
|
+
attr_reader(:log_text)
|
25
|
+
attr_accessor(:name)
|
26
|
+
attr_accessor(:game)
|
27
|
+
attr_accessor(:score)
|
28
|
+
|
29
|
+
def anpais
|
30
|
+
return @sutehais + @extra_anpais
|
31
|
+
end
|
32
|
+
|
33
|
+
def reach?
|
34
|
+
return @reach_state == :accepted
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_state(action)
|
38
|
+
|
39
|
+
if @game.previous_action &&
|
40
|
+
@game.previous_action.type == :dahai &&
|
41
|
+
@game.previous_action.actor != self &&
|
42
|
+
action.type != :hora
|
43
|
+
@extra_anpais.push(@game.previous_action.pai)
|
44
|
+
end
|
45
|
+
|
46
|
+
case action.type
|
47
|
+
when :start_game
|
48
|
+
@id = action.id
|
49
|
+
@name = action.names[@id] if action.names
|
50
|
+
@score = 25000
|
51
|
+
@attributes = OpenStruct.new()
|
52
|
+
@tehais = nil
|
53
|
+
@furos = nil
|
54
|
+
@ho = nil
|
55
|
+
@sutehais = nil
|
56
|
+
@extra_anpais = nil
|
57
|
+
@reach_state = nil
|
58
|
+
@reach_ho_index = nil
|
59
|
+
when :start_kyoku
|
60
|
+
@tehais = action.tehais[self.id]
|
61
|
+
@furos = []
|
62
|
+
@ho = []
|
63
|
+
@sutehais = []
|
64
|
+
@extra_anpais = []
|
65
|
+
@reach_state = :none
|
66
|
+
@reach_ho_index = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
if action.actor == self
|
70
|
+
case action.type
|
71
|
+
when :tsumo
|
72
|
+
@tehais.push(action.pai)
|
73
|
+
when :dahai
|
74
|
+
delete_tehai(action.pai)
|
75
|
+
@tehais.sort!()
|
76
|
+
@ho.push(action.pai)
|
77
|
+
@sutehais.push(action.pai)
|
78
|
+
@extra_anpais.clear() if !self.reach?
|
79
|
+
when :chi, :pon, :daiminkan, :ankan
|
80
|
+
for pai in action.consumed
|
81
|
+
delete_tehai(pai)
|
82
|
+
end
|
83
|
+
@furos.push(Furo.new({
|
84
|
+
:type => action.type,
|
85
|
+
:taken => action.pai,
|
86
|
+
:consumed => action.consumed,
|
87
|
+
:target => action.target,
|
88
|
+
}))
|
89
|
+
when :kakan
|
90
|
+
delete_tehai(action.pai)
|
91
|
+
pon_index =
|
92
|
+
@furos.index(){ |f| f.type == :pon && f.taken.same_symbol?(action.pai) }
|
93
|
+
raise("should not happen") if !pon_index
|
94
|
+
@furos[pon_index] = Furo.new({
|
95
|
+
:type => :kakan,
|
96
|
+
:taken => @furos[pon_index].taken,
|
97
|
+
:consumed => @furos[pon_index].consumed + [action.pai],
|
98
|
+
:target => @furos[pon_index].target,
|
99
|
+
})
|
100
|
+
when :reach
|
101
|
+
@reach_state = :declared
|
102
|
+
when :reach_accepted
|
103
|
+
@reach_state = :accepted
|
104
|
+
@reach_ho_index = @ho.size - 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if action.target == self
|
109
|
+
case action.type
|
110
|
+
when :chi, :pon, :daiminkan, :ankan
|
111
|
+
pai = @ho.pop()
|
112
|
+
raise("should not happen") if pai != action.pai
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if action.scores
|
117
|
+
@score = action.scores[self.id]
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
def jikaze
|
123
|
+
if @game.oya
|
124
|
+
return Pai.new("t", 1 + (4 + @id - @game.oya.id) % 4)
|
125
|
+
else
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def tenpai?
|
131
|
+
return ShantenAnalysis.new(@tehais, 0).shanten <= 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def furiten?
|
135
|
+
return false if @tehais.size % 3 != 1
|
136
|
+
return false if @tehais.include?(Pai::UNKNOWN)
|
137
|
+
tenpai_info = TenpaiAnalysis.new(@tehais)
|
138
|
+
return false if !tenpai_info.tenpai?
|
139
|
+
anpais = self.anpais
|
140
|
+
return tenpai_info.waited_pais.any?(){ |pai| anpais.include?(pai) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def can_reach?(shanten_analysis = nil)
|
144
|
+
shanten_analysis ||= ShantenAnalysis.new(@tehais, 0)
|
145
|
+
return @game.current_action.type == :tsumo &&
|
146
|
+
@game.current_action.actor == self &&
|
147
|
+
shanten_analysis.shanten <= 0 &&
|
148
|
+
@furos.empty? &&
|
149
|
+
!self.reach? &&
|
150
|
+
self.game.num_pipais >= 4 &&
|
151
|
+
@score >= 1000
|
152
|
+
end
|
153
|
+
|
154
|
+
def can_hora?(shanten_analysis = nil)
|
155
|
+
action = @game.current_action
|
156
|
+
if action.type == :tsumo && action.actor == self
|
157
|
+
hora_type = :tsumo
|
158
|
+
pais = @tehais
|
159
|
+
elsif action.type == :dahai && action.actor != self
|
160
|
+
hora_type = :ron
|
161
|
+
pais = @tehais + [action.pai]
|
162
|
+
else
|
163
|
+
return false
|
164
|
+
end
|
165
|
+
shanten_analysis ||= ShantenAnalysis.new(pais, -1)
|
166
|
+
hora_action =
|
167
|
+
create_action({:type => :hora, :target => action.actor, :pai => pais[-1]})
|
168
|
+
return shanten_analysis.shanten == -1 &&
|
169
|
+
@game.get_hora(hora_action).valid? &&
|
170
|
+
(hora_type == :tsumo || !self.furiten?)
|
171
|
+
end
|
172
|
+
|
173
|
+
def possible_furo_actions
|
174
|
+
|
175
|
+
action = @game.current_action
|
176
|
+
result = []
|
177
|
+
|
178
|
+
if action.type == :dahai &&
|
179
|
+
action.actor != self &&
|
180
|
+
!self.reach? &&
|
181
|
+
@game.num_pipais >= 4
|
182
|
+
|
183
|
+
for consumed in get_pais_combinations([action.pai] * 3, @tehais)
|
184
|
+
result.push(create_action({
|
185
|
+
:type => :daiminkan,
|
186
|
+
:pai => action.pai,
|
187
|
+
:consumed => consumed,
|
188
|
+
:target => action.actor
|
189
|
+
}))
|
190
|
+
end
|
191
|
+
for consumed in get_pais_combinations([action.pai] * 2, @tehais)
|
192
|
+
result.push(create_action({
|
193
|
+
:type => :pon,
|
194
|
+
:pai => action.pai,
|
195
|
+
:consumed => consumed,
|
196
|
+
:target => action.actor
|
197
|
+
}))
|
198
|
+
end
|
199
|
+
if (action.actor.id + 1) % 4 == self.id && action.pai.type != "t"
|
200
|
+
for i in 0...3
|
201
|
+
target_pais = (((-i)...(-i + 3)).to_a() - [0]).map() do |j|
|
202
|
+
Pai.new(action.pai.type, action.pai.number + j)
|
203
|
+
end
|
204
|
+
for consumed in get_pais_combinations(target_pais, @tehais)
|
205
|
+
result.push(create_action({
|
206
|
+
:type => :chi,
|
207
|
+
:pai => action.pai,
|
208
|
+
:consumed => consumed,
|
209
|
+
:target => action.actor,
|
210
|
+
}))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
# Excludes furos which forces kuikae afterwards.
|
215
|
+
result = result.select() do |a|
|
216
|
+
a.type == :daiminkan || !possible_dahais_after_furo(a).empty?
|
217
|
+
end
|
218
|
+
|
219
|
+
elsif action.type == :tsumo &&
|
220
|
+
action.actor == self &&
|
221
|
+
@game.num_pipais > 0
|
222
|
+
|
223
|
+
for pai in self.tehais.uniq
|
224
|
+
same_pais = self.tehais.select(){ |tp| tp.same_symbol?(pai) }
|
225
|
+
if same_pais.size >= 4
|
226
|
+
if self.reach?
|
227
|
+
orig_tenpai = TenpaiAnalysis.new(self.tehais[0...-1])
|
228
|
+
new_tenpai = TenpaiAnalysis.new(
|
229
|
+
self.tehais.select(){ |tp| !tp.same_symbol?(pai) })
|
230
|
+
ok = new_tenpai.tenpai? && new_tenpai.waited_pais == orig_tenpai.waited_pais
|
231
|
+
else
|
232
|
+
ok = true
|
233
|
+
end
|
234
|
+
result.push(create_action({:type => :ankan, :consumed => same_pais})) if ok
|
235
|
+
end
|
236
|
+
pon = self.furos.find(){ |f| f.type == :pon && f.taken.same_symbol?(pai) }
|
237
|
+
if pon
|
238
|
+
result.push(create_action({:type => :kakan, :pai => pai, :consumed => pon.pais}))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
return result
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_pais_combinations(target_pais, source_pais)
|
249
|
+
return Set.new([[]]) if target_pais.empty?
|
250
|
+
result = Set.new()
|
251
|
+
for pai in source_pais.select(){ |pai| target_pais[0].same_symbol?(pai) }.uniq
|
252
|
+
new_source_pais = source_pais.dup()
|
253
|
+
new_source_pais.delete_at(new_source_pais.index(pai))
|
254
|
+
for cdr_pais in get_pais_combinations(target_pais[1..-1], new_source_pais)
|
255
|
+
result.add(([pai] + cdr_pais).sort())
|
256
|
+
end
|
257
|
+
end
|
258
|
+
return result
|
259
|
+
end
|
260
|
+
|
261
|
+
def possible_dahais(action = @game.current_action, tehais = @tehais)
|
262
|
+
# Excludes kuikae.
|
263
|
+
if action.type == :chi && action.actor == self
|
264
|
+
if action.consumed[1].number == action.consumed[0].number + 1
|
265
|
+
forbidden_rnums = [-1, 2]
|
266
|
+
else
|
267
|
+
forbidden_rnums = [1]
|
268
|
+
end
|
269
|
+
elsif action.type == :pon && action.actor == self
|
270
|
+
forbidden_rnums = [0]
|
271
|
+
else
|
272
|
+
forbidden_rnums = []
|
273
|
+
end
|
274
|
+
cands = tehais.uniq()
|
275
|
+
if !forbidden_rnums.empty?
|
276
|
+
key_pai = action.consumed[0]
|
277
|
+
return cands.select() do |pai|
|
278
|
+
!(pai.type == key_pai.type &&
|
279
|
+
forbidden_rnums.any?(){ |rn| key_pai.number + rn == pai.number })
|
280
|
+
end
|
281
|
+
else
|
282
|
+
return cands
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def possible_dahais_after_furo(action)
|
287
|
+
remains = @tehais.dup()
|
288
|
+
for pai in action.consumed
|
289
|
+
remains.delete_at(remains.index(pai))
|
290
|
+
end
|
291
|
+
return possible_dahais(action, remains)
|
292
|
+
end
|
293
|
+
|
294
|
+
def context
|
295
|
+
return Context.new({
|
296
|
+
:oya => self == self.game.oya,
|
297
|
+
:bakaze => self.game.bakaze,
|
298
|
+
:jikaze => self.jikaze,
|
299
|
+
:doras => self.game.doras,
|
300
|
+
:uradoras => [], # TODO
|
301
|
+
:reach => self.reach?,
|
302
|
+
:double_reach => false, # TODO
|
303
|
+
:ippatsu => false, # TODO
|
304
|
+
:rinshan => false, # TODO
|
305
|
+
:haitei => self.game.num_pipais == 0,
|
306
|
+
:first_turn => false, # TODO
|
307
|
+
:chankan => false, # TODO
|
308
|
+
})
|
309
|
+
end
|
310
|
+
|
311
|
+
def delete_tehai(pai)
|
312
|
+
pai_index = @tehais.index(pai) || @tehais.index(Pai::UNKNOWN)
|
313
|
+
raise("trying to delete %p which is not in tehais: %p" % [pai, @tehais]) if !pai_index
|
314
|
+
@tehais.delete_at(pai_index)
|
315
|
+
end
|
316
|
+
|
317
|
+
def create_action(params = {})
|
318
|
+
return Action.new({:actor => self}.merge(params))
|
319
|
+
end
|
320
|
+
|
321
|
+
def rank
|
322
|
+
return @game.ranked_players.index(self) + 1
|
323
|
+
end
|
324
|
+
|
325
|
+
def log(text)
|
326
|
+
@log_text << text << "\n"
|
327
|
+
puts(text)
|
328
|
+
end
|
329
|
+
|
330
|
+
def clear_log()
|
331
|
+
@log_text = ""
|
332
|
+
end
|
333
|
+
|
334
|
+
def inspect
|
335
|
+
return "\#<%p:%d>" % [self.class, self.id]
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require "mjai/pai"
|
2
|
+
require "mjai/mentsu"
|
3
|
+
|
4
|
+
|
5
|
+
module Mjai
|
6
|
+
|
7
|
+
class ShantenAnalysis
|
8
|
+
|
9
|
+
# ryanpen = 両面 or 辺搭
|
10
|
+
MENTSU_TYPES = [:kotsu, :shuntsu, :toitsu, :ryanpen, :kanta, :single]
|
11
|
+
|
12
|
+
MENTSU_CATEGORIES = {
|
13
|
+
:kotsu => :complete,
|
14
|
+
:shuntsu => :complete,
|
15
|
+
:toitsu => :toitsu,
|
16
|
+
:ryanpen => :tatsu,
|
17
|
+
:kanta => :tatsu,
|
18
|
+
:single => :single,
|
19
|
+
}
|
20
|
+
|
21
|
+
MENTSU_SIZES = {
|
22
|
+
:complete => 3,
|
23
|
+
:toitsu => 2,
|
24
|
+
:tatsu => 2,
|
25
|
+
:single => 1,
|
26
|
+
}
|
27
|
+
|
28
|
+
ALL_TYPES = [:normal, :chitoitsu, :kokushimuso]
|
29
|
+
|
30
|
+
def self.benchmark()
|
31
|
+
all_pais = (["m", "p", "s"].map(){ |t| (1..9).map(){ |n| Pai.new(t, n) } }.flatten() +
|
32
|
+
(1..7).map(){ |n| Pai.new("t", n) }) * 4
|
33
|
+
start_time = Time.now.to_f
|
34
|
+
100.times() do
|
35
|
+
pais = all_pais.sample(14).sort()
|
36
|
+
p pais.join(" ")
|
37
|
+
shanten = ShantenAnalysis.count(pais)
|
38
|
+
p shanten
|
39
|
+
=begin
|
40
|
+
for i in 0...pais.size
|
41
|
+
remains_pais = pais.dup()
|
42
|
+
remains_pais.delete_at(i)
|
43
|
+
if ShantenAnalysis.count(remains_pais) == shanten
|
44
|
+
p pais[i]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
=end
|
48
|
+
#gets()
|
49
|
+
end
|
50
|
+
p Time.now.to_f - start_time
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(pais, max_shanten = nil, types = ALL_TYPES,
|
54
|
+
num_used_pais = pais.size, need_all_combinations = true)
|
55
|
+
|
56
|
+
@pais = pais
|
57
|
+
@max_shanten = max_shanten
|
58
|
+
@num_used_pais = num_used_pais
|
59
|
+
@need_all_combinations = need_all_combinations
|
60
|
+
raise(ArgumentError, "invalid number of pais") if @num_used_pais % 3 == 0
|
61
|
+
@pai_set = Hash.new(0)
|
62
|
+
for pai in @pais
|
63
|
+
@pai_set[pai.remove_red()] += 1
|
64
|
+
end
|
65
|
+
|
66
|
+
@cache = {}
|
67
|
+
results = []
|
68
|
+
results.push(count_normal(@pai_set, [])) if types.include?(:normal)
|
69
|
+
results.push(count_chitoi(@pai_set)) if types.include?(:chitoitsu)
|
70
|
+
results.push(count_kokushi(@pai_set)) if types.include?(:kokushimuso)
|
71
|
+
|
72
|
+
@shanten = 1.0/0.0
|
73
|
+
@combinations = []
|
74
|
+
for shanten, combinations in results
|
75
|
+
next if @max_shanten && shanten > @max_shanten
|
76
|
+
if shanten < @shanten
|
77
|
+
@shanten = shanten
|
78
|
+
@combinations = combinations
|
79
|
+
elsif shanten == @shanten
|
80
|
+
@combinations += combinations
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader(:pais, :shanten, :combinations)
|
87
|
+
|
88
|
+
DetailedCombination = Struct.new(:janto, :mentsus)
|
89
|
+
|
90
|
+
def detailed_combinations
|
91
|
+
num_required_mentsus = @pais.size / 3
|
92
|
+
result = []
|
93
|
+
for mentsus in @combinations.map(){ |ms| ms.map(){ |m| convert_mentsu(m) } }
|
94
|
+
for janto_index in [nil] + (0...mentsus.size).to_a()
|
95
|
+
t_mentsus = mentsus.dup()
|
96
|
+
janto = nil
|
97
|
+
if janto_index
|
98
|
+
next if ![:toitsu, :kotsu].include?(mentsus[janto_index].type)
|
99
|
+
janto = t_mentsus.delete_at(janto_index)
|
100
|
+
end
|
101
|
+
current_shanten =
|
102
|
+
-1 +
|
103
|
+
(janto_index ? 0 : 1) +
|
104
|
+
t_mentsus.map(){ |m| 3 - m.pais.size }.
|
105
|
+
sort()[0, num_required_mentsus].
|
106
|
+
inject(0, :+)
|
107
|
+
next if current_shanten != @shanten
|
108
|
+
result.push(DetailedCombination.new(janto, t_mentsus))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return result
|
112
|
+
end
|
113
|
+
|
114
|
+
def convert_mentsu(mentsu)
|
115
|
+
(type, pais) = mentsu
|
116
|
+
if type == :ryanpen
|
117
|
+
if [[1, 2], [8, 9]].include?(pais.map(){ |pai| pai.number })
|
118
|
+
type = :penta
|
119
|
+
else
|
120
|
+
type = :ryanmen
|
121
|
+
end
|
122
|
+
end
|
123
|
+
return Mentsu.new({:type => type, :pais => pais, :visibility => :an})
|
124
|
+
end
|
125
|
+
|
126
|
+
def count_chitoi(pai_set)
|
127
|
+
num_toitsus = pai_set.select(){ |pai, n| n >= 2 }.size
|
128
|
+
num_singles = pai_set.select(){ |pai, n| n == 1 }.size
|
129
|
+
if num_toitsus == 6 && num_singles == 0
|
130
|
+
# toitsu * 5 + kotsu * 1 or toitsu * 5 + kantsu * 1
|
131
|
+
shanten = 1
|
132
|
+
else
|
133
|
+
shanten = -1 + [7 - num_toitsus, 0].max
|
134
|
+
end
|
135
|
+
return [shanten, [:chitoitsu]]
|
136
|
+
end
|
137
|
+
|
138
|
+
def count_kokushi(pai_set)
|
139
|
+
yaochus = pai_set.select(){ |pai, n| pai.yaochu? }
|
140
|
+
has_yaochu_toitsu = yaochus.any?(){ |pai, n| n >= 2 }
|
141
|
+
return [(13 - yaochus.size) - (has_yaochu_toitsu ? 1 : 0), [:kokushimuso]]
|
142
|
+
end
|
143
|
+
|
144
|
+
def count_normal(pai_set, mentsus)
|
145
|
+
# TODO 上がり牌を全部自分が持っているケースを考慮
|
146
|
+
key = get_key(pai_set, mentsus)
|
147
|
+
if !@cache[key]
|
148
|
+
if pai_set.empty?
|
149
|
+
#p mentsus
|
150
|
+
min_shanten = get_min_shanten_for_mentsus(mentsus)
|
151
|
+
min_combinations = [mentsus]
|
152
|
+
else
|
153
|
+
shanten_lowerbound = get_min_shanten_for_mentsus(mentsus) if @max_shanten
|
154
|
+
if @max_shanten && shanten_lowerbound > @max_shanten
|
155
|
+
min_shanten = 1.0/0.0
|
156
|
+
min_combinations = []
|
157
|
+
else
|
158
|
+
min_shanten = 1.0/0.0
|
159
|
+
first_pai = pai_set.keys.sort()[0]
|
160
|
+
for type in MENTSU_TYPES
|
161
|
+
if @max_shanten == -1
|
162
|
+
next if [:ryanpen, :kanta].include?(type)
|
163
|
+
next if mentsus.any?(){ |t, ps| t == :toitsu } && type == :toitsu
|
164
|
+
end
|
165
|
+
(removed_pais, remains_set) = remove(pai_set, type, first_pai)
|
166
|
+
if remains_set
|
167
|
+
(shanten, combinations) =
|
168
|
+
count_normal(remains_set, mentsus + [[type, removed_pais]])
|
169
|
+
if shanten < min_shanten
|
170
|
+
min_shanten = shanten
|
171
|
+
min_combinations = combinations
|
172
|
+
break if !@need_all_combinations && min_shanten == -1
|
173
|
+
elsif shanten == min_shanten && shanten < 1.0/0.0
|
174
|
+
min_combinations += combinations
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
@cache[key] = [min_shanten, min_combinations]
|
181
|
+
end
|
182
|
+
return @cache[key]
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_key(pai_set, mentsus)
|
186
|
+
return [pai_set, Set.new(mentsus)]
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_min_shanten_for_mentsus(mentsus)
|
190
|
+
|
191
|
+
mentsu_categories = mentsus.map(){ |t, ps| MENTSU_CATEGORIES[t] }
|
192
|
+
num_current_pais = mentsu_categories.map(){ |m| MENTSU_SIZES[m] }.inject(0, :+)
|
193
|
+
num_remain_pais = @pais.size - num_current_pais
|
194
|
+
|
195
|
+
min_shantens = []
|
196
|
+
if index = mentsu_categories.index(:toitsu)
|
197
|
+
# Assumes the 対子 is 雀頭.
|
198
|
+
mentsu_categories.delete_at(index)
|
199
|
+
min_shantens.push(get_min_shanten_without_janto(mentsu_categories, num_remain_pais))
|
200
|
+
else
|
201
|
+
# Assumes 雀頭 is missing.
|
202
|
+
min_shantens.push(get_min_shanten_without_janto(mentsu_categories, num_remain_pais) + 1)
|
203
|
+
if num_remain_pais >= 2
|
204
|
+
# Assumes 雀頭 is in remaining pais.
|
205
|
+
min_shantens.push(get_min_shanten_without_janto(mentsu_categories, num_remain_pais - 2))
|
206
|
+
end
|
207
|
+
end
|
208
|
+
return min_shantens.min
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_min_shanten_without_janto(mentsu_categories, num_remain_pais)
|
213
|
+
|
214
|
+
# Assumes remaining pais generates best combinations.
|
215
|
+
mentsu_categories += [:complete] * (num_remain_pais / 3)
|
216
|
+
case num_remain_pais % 3
|
217
|
+
when 1
|
218
|
+
mentsu_categories.push(:single)
|
219
|
+
when 2
|
220
|
+
mentsu_categories.push(:toitsu)
|
221
|
+
end
|
222
|
+
|
223
|
+
sizes = mentsu_categories.map(){ |m| MENTSU_SIZES[m] }.sort_by(){ |n| -n }
|
224
|
+
num_required_mentsus = @num_used_pais / 3
|
225
|
+
return -1 + sizes[0...num_required_mentsus].inject(0){ |r, n| r + (3 - n) }
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
def remove(pai_set, type, first_pai)
|
230
|
+
case type
|
231
|
+
when :kotsu
|
232
|
+
removed_pais = [first_pai] * 3
|
233
|
+
when :shuntsu
|
234
|
+
removed_pais = shuntsu_piece(first_pai, [0, 1, 2])
|
235
|
+
when :toitsu
|
236
|
+
removed_pais = [first_pai] * 2
|
237
|
+
when :ryanpen
|
238
|
+
removed_pais = shuntsu_piece(first_pai, [0, 1])
|
239
|
+
when :kanta
|
240
|
+
removed_pais = shuntsu_piece(first_pai, [0, 2])
|
241
|
+
when :single
|
242
|
+
removed_pais = [first_pai]
|
243
|
+
else
|
244
|
+
raise("should not happen")
|
245
|
+
end
|
246
|
+
return [nil, nil] if !removed_pais
|
247
|
+
result_set = pai_set.dup()
|
248
|
+
for pai in removed_pais
|
249
|
+
if result_set[pai] > 0
|
250
|
+
result_set[pai] -= 1
|
251
|
+
result_set.delete(pai) if result_set[pai] == 0
|
252
|
+
else
|
253
|
+
return [nil, nil]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
return [removed_pais, result_set]
|
257
|
+
end
|
258
|
+
|
259
|
+
def shuntsu_piece(first_pai, relative_numbers)
|
260
|
+
if first_pai.type == "t"
|
261
|
+
return nil
|
262
|
+
else
|
263
|
+
return relative_numbers.map(){ |i| Pai.new(first_pai.type, first_pai.number + i) }
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def inspect
|
268
|
+
return "\#<%p shanten=%d pais=<%s>>" % [self.class, @shanten, @pais.join(" ")]
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|