mjai 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (355) hide show
  1. data/bin/mjai +9 -0
  2. data/bin/mjai-shanten +9 -0
  3. data/bin/mjai-tsumogiri +9 -0
  4. data/lib/mjai/action.rb +41 -0
  5. data/lib/mjai/active_game.rb +230 -0
  6. data/lib/mjai/archive.rb +46 -0
  7. data/lib/mjai/archive_player.rb +47 -0
  8. data/lib/mjai/context.rb +34 -0
  9. data/lib/mjai/file_converter.rb +86 -0
  10. data/lib/mjai/furo.rb +57 -0
  11. data/lib/mjai/game.rb +357 -0
  12. data/lib/mjai/hora.rb +528 -0
  13. data/lib/mjai/jsonizable.rb +171 -0
  14. data/lib/mjai/mentsu.rb +46 -0
  15. data/lib/mjai/mjai_command.rb +93 -0
  16. data/lib/mjai/mjson_archive.rb +25 -0
  17. data/lib/mjai/pai.rb +138 -0
  18. data/lib/mjai/player.rb +340 -0
  19. data/lib/mjai/puppet_player.rb +14 -0
  20. data/lib/mjai/shanten_analysis.rb +273 -0
  21. data/lib/mjai/shanten_player.rb +102 -0
  22. data/lib/mjai/tcp_client_game.rb +63 -0
  23. data/lib/mjai/tcp_game_server.rb +205 -0
  24. data/lib/mjai/tcp_player.rb +66 -0
  25. data/lib/mjai/tenhou_archive.rb +412 -0
  26. data/lib/mjai/tenpai_analysis.rb +62 -0
  27. data/lib/mjai/tsumogiri_player.rb +20 -0
  28. data/lib/mjai/validation_error.rb +7 -0
  29. data/lib/mjai/with_fields.rb +18 -0
  30. data/share/html/css/style.css +77 -0
  31. data/share/html/css/style.scss +106 -0
  32. data/share/html/images/README.txt +1 -0
  33. data/share/html/images/b_1_1.gif +0 -0
  34. data/share/html/images/b_1_2.gif +0 -0
  35. data/share/html/images/b_5_1.gif +0 -0
  36. data/share/html/images/b_5_2.gif +0 -0
  37. data/share/html/images/b_8_1.gif +0 -0
  38. data/share/html/images/b_8_2.gif +0 -0
  39. data/share/html/images/b_9_1.gif +0 -0
  40. data/share/html/images/b_9_2.gif +0 -0
  41. data/share/html/images/blank.png +0 -0
  42. data/share/html/images/c_c_1.gif +0 -0
  43. data/share/html/images/c_c_2.gif +0 -0
  44. data/share/html/images/c_c_3.gif +0 -0
  45. data/share/html/images/c_c_4.gif +0 -0
  46. data/share/html/images/c_e_1.gif +0 -0
  47. data/share/html/images/c_e_2.gif +0 -0
  48. data/share/html/images/c_e_3.gif +0 -0
  49. data/share/html/images/c_e_4.gif +0 -0
  50. data/share/html/images/c_n_1.gif +0 -0
  51. data/share/html/images/c_n_2.gif +0 -0
  52. data/share/html/images/c_n_3.gif +0 -0
  53. data/share/html/images/c_n_4.gif +0 -0
  54. data/share/html/images/c_s_1.gif +0 -0
  55. data/share/html/images/c_s_2.gif +0 -0
  56. data/share/html/images/c_s_3.gif +0 -0
  57. data/share/html/images/c_s_4.gif +0 -0
  58. data/share/html/images/c_w_1.gif +0 -0
  59. data/share/html/images/c_w_2.gif +0 -0
  60. data/share/html/images/c_w_3.gif +0 -0
  61. data/share/html/images/c_w_4.gif +0 -0
  62. data/share/html/images/dice.gif +0 -0
  63. data/share/html/images/p_bk_0.gif +0 -0
  64. data/share/html/images/p_bk_1.gif +0 -0
  65. data/share/html/images/p_bk_2.gif +0 -0
  66. data/share/html/images/p_bk_3.gif +0 -0
  67. data/share/html/images/p_bk_4.gif +0 -0
  68. data/share/html/images/p_bk_5.gif +0 -0
  69. data/share/html/images/p_bk_6.gif +0 -0
  70. data/share/html/images/p_bk_7.gif +0 -0
  71. data/share/html/images/p_ji_c_0.gif +0 -0
  72. data/share/html/images/p_ji_c_1.gif +0 -0
  73. data/share/html/images/p_ji_c_2.gif +0 -0
  74. data/share/html/images/p_ji_c_3.gif +0 -0
  75. data/share/html/images/p_ji_c_4.gif +0 -0
  76. data/share/html/images/p_ji_c_5.gif +0 -0
  77. data/share/html/images/p_ji_c_6.gif +0 -0
  78. data/share/html/images/p_ji_c_7.gif +0 -0
  79. data/share/html/images/p_ji_e_0.gif +0 -0
  80. data/share/html/images/p_ji_e_1.gif +0 -0
  81. data/share/html/images/p_ji_e_2.gif +0 -0
  82. data/share/html/images/p_ji_e_3.gif +0 -0
  83. data/share/html/images/p_ji_e_4.gif +0 -0
  84. data/share/html/images/p_ji_e_5.gif +0 -0
  85. data/share/html/images/p_ji_e_6.gif +0 -0
  86. data/share/html/images/p_ji_e_7.gif +0 -0
  87. data/share/html/images/p_ji_h_0.gif +0 -0
  88. data/share/html/images/p_ji_h_1.gif +0 -0
  89. data/share/html/images/p_ji_h_2.gif +0 -0
  90. data/share/html/images/p_ji_h_3.gif +0 -0
  91. data/share/html/images/p_ji_h_4.gif +0 -0
  92. data/share/html/images/p_ji_h_5.gif +0 -0
  93. data/share/html/images/p_ji_h_6.gif +0 -0
  94. data/share/html/images/p_ji_h_7.gif +0 -0
  95. data/share/html/images/p_ji_n_0.gif +0 -0
  96. data/share/html/images/p_ji_n_1.gif +0 -0
  97. data/share/html/images/p_ji_n_2.gif +0 -0
  98. data/share/html/images/p_ji_n_3.gif +0 -0
  99. data/share/html/images/p_ji_n_4.gif +0 -0
  100. data/share/html/images/p_ji_n_5.gif +0 -0
  101. data/share/html/images/p_ji_n_6.gif +0 -0
  102. data/share/html/images/p_ji_n_7.gif +0 -0
  103. data/share/html/images/p_ji_s_0.gif +0 -0
  104. data/share/html/images/p_ji_s_1.gif +0 -0
  105. data/share/html/images/p_ji_s_2.gif +0 -0
  106. data/share/html/images/p_ji_s_3.gif +0 -0
  107. data/share/html/images/p_ji_s_4.gif +0 -0
  108. data/share/html/images/p_ji_s_5.gif +0 -0
  109. data/share/html/images/p_ji_s_6.gif +0 -0
  110. data/share/html/images/p_ji_s_7.gif +0 -0
  111. data/share/html/images/p_ji_w_0.gif +0 -0
  112. data/share/html/images/p_ji_w_1.gif +0 -0
  113. data/share/html/images/p_ji_w_2.gif +0 -0
  114. data/share/html/images/p_ji_w_3.gif +0 -0
  115. data/share/html/images/p_ji_w_4.gif +0 -0
  116. data/share/html/images/p_ji_w_5.gif +0 -0
  117. data/share/html/images/p_ji_w_6.gif +0 -0
  118. data/share/html/images/p_ji_w_7.gif +0 -0
  119. data/share/html/images/p_ms1_0.gif +0 -0
  120. data/share/html/images/p_ms1_1.gif +0 -0
  121. data/share/html/images/p_ms1_2.gif +0 -0
  122. data/share/html/images/p_ms1_3.gif +0 -0
  123. data/share/html/images/p_ms1_4.gif +0 -0
  124. data/share/html/images/p_ms1_5.gif +0 -0
  125. data/share/html/images/p_ms1_6.gif +0 -0
  126. data/share/html/images/p_ms1_7.gif +0 -0
  127. data/share/html/images/p_ms2_0.gif +0 -0
  128. data/share/html/images/p_ms2_1.gif +0 -0
  129. data/share/html/images/p_ms2_2.gif +0 -0
  130. data/share/html/images/p_ms2_3.gif +0 -0
  131. data/share/html/images/p_ms2_4.gif +0 -0
  132. data/share/html/images/p_ms2_5.gif +0 -0
  133. data/share/html/images/p_ms2_6.gif +0 -0
  134. data/share/html/images/p_ms2_7.gif +0 -0
  135. data/share/html/images/p_ms3_0.gif +0 -0
  136. data/share/html/images/p_ms3_1.gif +0 -0
  137. data/share/html/images/p_ms3_2.gif +0 -0
  138. data/share/html/images/p_ms3_3.gif +0 -0
  139. data/share/html/images/p_ms3_4.gif +0 -0
  140. data/share/html/images/p_ms3_5.gif +0 -0
  141. data/share/html/images/p_ms3_6.gif +0 -0
  142. data/share/html/images/p_ms3_7.gif +0 -0
  143. data/share/html/images/p_ms4_0.gif +0 -0
  144. data/share/html/images/p_ms4_1.gif +0 -0
  145. data/share/html/images/p_ms4_2.gif +0 -0
  146. data/share/html/images/p_ms4_3.gif +0 -0
  147. data/share/html/images/p_ms4_4.gif +0 -0
  148. data/share/html/images/p_ms4_5.gif +0 -0
  149. data/share/html/images/p_ms4_6.gif +0 -0
  150. data/share/html/images/p_ms4_7.gif +0 -0
  151. data/share/html/images/p_ms5_0.gif +0 -0
  152. data/share/html/images/p_ms5_1.gif +0 -0
  153. data/share/html/images/p_ms5_2.gif +0 -0
  154. data/share/html/images/p_ms5_3.gif +0 -0
  155. data/share/html/images/p_ms5_4.gif +0 -0
  156. data/share/html/images/p_ms5_5.gif +0 -0
  157. data/share/html/images/p_ms5_6.gif +0 -0
  158. data/share/html/images/p_ms5_7.gif +0 -0
  159. data/share/html/images/p_ms5r_1.png +0 -0
  160. data/share/html/images/p_ms5r_3.png +0 -0
  161. data/share/html/images/p_ms6_0.gif +0 -0
  162. data/share/html/images/p_ms6_1.gif +0 -0
  163. data/share/html/images/p_ms6_2.gif +0 -0
  164. data/share/html/images/p_ms6_3.gif +0 -0
  165. data/share/html/images/p_ms6_4.gif +0 -0
  166. data/share/html/images/p_ms6_5.gif +0 -0
  167. data/share/html/images/p_ms6_6.gif +0 -0
  168. data/share/html/images/p_ms6_7.gif +0 -0
  169. data/share/html/images/p_ms7_0.gif +0 -0
  170. data/share/html/images/p_ms7_1.gif +0 -0
  171. data/share/html/images/p_ms7_2.gif +0 -0
  172. data/share/html/images/p_ms7_3.gif +0 -0
  173. data/share/html/images/p_ms7_4.gif +0 -0
  174. data/share/html/images/p_ms7_5.gif +0 -0
  175. data/share/html/images/p_ms7_6.gif +0 -0
  176. data/share/html/images/p_ms7_7.gif +0 -0
  177. data/share/html/images/p_ms8_0.gif +0 -0
  178. data/share/html/images/p_ms8_1.gif +0 -0
  179. data/share/html/images/p_ms8_2.gif +0 -0
  180. data/share/html/images/p_ms8_3.gif +0 -0
  181. data/share/html/images/p_ms8_4.gif +0 -0
  182. data/share/html/images/p_ms8_5.gif +0 -0
  183. data/share/html/images/p_ms8_6.gif +0 -0
  184. data/share/html/images/p_ms8_7.gif +0 -0
  185. data/share/html/images/p_ms9_0.gif +0 -0
  186. data/share/html/images/p_ms9_1.gif +0 -0
  187. data/share/html/images/p_ms9_2.gif +0 -0
  188. data/share/html/images/p_ms9_3.gif +0 -0
  189. data/share/html/images/p_ms9_4.gif +0 -0
  190. data/share/html/images/p_ms9_5.gif +0 -0
  191. data/share/html/images/p_ms9_6.gif +0 -0
  192. data/share/html/images/p_ms9_7.gif +0 -0
  193. data/share/html/images/p_no_0.gif +0 -0
  194. data/share/html/images/p_no_1.gif +0 -0
  195. data/share/html/images/p_no_2.gif +0 -0
  196. data/share/html/images/p_no_3.gif +0 -0
  197. data/share/html/images/p_no_4.gif +0 -0
  198. data/share/html/images/p_no_5.gif +0 -0
  199. data/share/html/images/p_no_6.gif +0 -0
  200. data/share/html/images/p_no_7.gif +0 -0
  201. data/share/html/images/p_ps1_0.gif +0 -0
  202. data/share/html/images/p_ps1_1.gif +0 -0
  203. data/share/html/images/p_ps1_2.gif +0 -0
  204. data/share/html/images/p_ps1_3.gif +0 -0
  205. data/share/html/images/p_ps1_4.gif +0 -0
  206. data/share/html/images/p_ps1_5.gif +0 -0
  207. data/share/html/images/p_ps1_6.gif +0 -0
  208. data/share/html/images/p_ps1_7.gif +0 -0
  209. data/share/html/images/p_ps2_0.gif +0 -0
  210. data/share/html/images/p_ps2_1.gif +0 -0
  211. data/share/html/images/p_ps2_2.gif +0 -0
  212. data/share/html/images/p_ps2_3.gif +0 -0
  213. data/share/html/images/p_ps2_4.gif +0 -0
  214. data/share/html/images/p_ps2_5.gif +0 -0
  215. data/share/html/images/p_ps2_6.gif +0 -0
  216. data/share/html/images/p_ps2_7.gif +0 -0
  217. data/share/html/images/p_ps3_0.gif +0 -0
  218. data/share/html/images/p_ps3_1.gif +0 -0
  219. data/share/html/images/p_ps3_2.gif +0 -0
  220. data/share/html/images/p_ps3_3.gif +0 -0
  221. data/share/html/images/p_ps3_4.gif +0 -0
  222. data/share/html/images/p_ps3_5.gif +0 -0
  223. data/share/html/images/p_ps3_6.gif +0 -0
  224. data/share/html/images/p_ps3_7.gif +0 -0
  225. data/share/html/images/p_ps4_0.gif +0 -0
  226. data/share/html/images/p_ps4_1.gif +0 -0
  227. data/share/html/images/p_ps4_2.gif +0 -0
  228. data/share/html/images/p_ps4_3.gif +0 -0
  229. data/share/html/images/p_ps4_4.gif +0 -0
  230. data/share/html/images/p_ps4_5.gif +0 -0
  231. data/share/html/images/p_ps4_6.gif +0 -0
  232. data/share/html/images/p_ps4_7.gif +0 -0
  233. data/share/html/images/p_ps5_0.gif +0 -0
  234. data/share/html/images/p_ps5_1.gif +0 -0
  235. data/share/html/images/p_ps5_2.gif +0 -0
  236. data/share/html/images/p_ps5_3.gif +0 -0
  237. data/share/html/images/p_ps5_4.gif +0 -0
  238. data/share/html/images/p_ps5_5.gif +0 -0
  239. data/share/html/images/p_ps5_6.gif +0 -0
  240. data/share/html/images/p_ps5_7.gif +0 -0
  241. data/share/html/images/p_ps5r_1.png +0 -0
  242. data/share/html/images/p_ps5r_3.png +0 -0
  243. data/share/html/images/p_ps6_0.gif +0 -0
  244. data/share/html/images/p_ps6_1.gif +0 -0
  245. data/share/html/images/p_ps6_2.gif +0 -0
  246. data/share/html/images/p_ps6_3.gif +0 -0
  247. data/share/html/images/p_ps6_4.gif +0 -0
  248. data/share/html/images/p_ps6_5.gif +0 -0
  249. data/share/html/images/p_ps6_6.gif +0 -0
  250. data/share/html/images/p_ps6_7.gif +0 -0
  251. data/share/html/images/p_ps7_0.gif +0 -0
  252. data/share/html/images/p_ps7_1.gif +0 -0
  253. data/share/html/images/p_ps7_2.gif +0 -0
  254. data/share/html/images/p_ps7_3.gif +0 -0
  255. data/share/html/images/p_ps7_4.gif +0 -0
  256. data/share/html/images/p_ps7_5.gif +0 -0
  257. data/share/html/images/p_ps7_6.gif +0 -0
  258. data/share/html/images/p_ps7_7.gif +0 -0
  259. data/share/html/images/p_ps8_0.gif +0 -0
  260. data/share/html/images/p_ps8_1.gif +0 -0
  261. data/share/html/images/p_ps8_2.gif +0 -0
  262. data/share/html/images/p_ps8_3.gif +0 -0
  263. data/share/html/images/p_ps8_4.gif +0 -0
  264. data/share/html/images/p_ps8_5.gif +0 -0
  265. data/share/html/images/p_ps8_6.gif +0 -0
  266. data/share/html/images/p_ps8_7.gif +0 -0
  267. data/share/html/images/p_ps9_0.gif +0 -0
  268. data/share/html/images/p_ps9_1.gif +0 -0
  269. data/share/html/images/p_ps9_2.gif +0 -0
  270. data/share/html/images/p_ps9_3.gif +0 -0
  271. data/share/html/images/p_ps9_4.gif +0 -0
  272. data/share/html/images/p_ps9_5.gif +0 -0
  273. data/share/html/images/p_ps9_6.gif +0 -0
  274. data/share/html/images/p_ps9_7.gif +0 -0
  275. data/share/html/images/p_ss1_0.gif +0 -0
  276. data/share/html/images/p_ss1_1.gif +0 -0
  277. data/share/html/images/p_ss1_2.gif +0 -0
  278. data/share/html/images/p_ss1_3.gif +0 -0
  279. data/share/html/images/p_ss1_4.gif +0 -0
  280. data/share/html/images/p_ss1_5.gif +0 -0
  281. data/share/html/images/p_ss1_6.gif +0 -0
  282. data/share/html/images/p_ss1_7.gif +0 -0
  283. data/share/html/images/p_ss2_0.gif +0 -0
  284. data/share/html/images/p_ss2_1.gif +0 -0
  285. data/share/html/images/p_ss2_2.gif +0 -0
  286. data/share/html/images/p_ss2_3.gif +0 -0
  287. data/share/html/images/p_ss2_4.gif +0 -0
  288. data/share/html/images/p_ss2_5.gif +0 -0
  289. data/share/html/images/p_ss2_6.gif +0 -0
  290. data/share/html/images/p_ss2_7.gif +0 -0
  291. data/share/html/images/p_ss3_0.gif +0 -0
  292. data/share/html/images/p_ss3_1.gif +0 -0
  293. data/share/html/images/p_ss3_2.gif +0 -0
  294. data/share/html/images/p_ss3_3.gif +0 -0
  295. data/share/html/images/p_ss3_4.gif +0 -0
  296. data/share/html/images/p_ss3_5.gif +0 -0
  297. data/share/html/images/p_ss3_6.gif +0 -0
  298. data/share/html/images/p_ss3_7.gif +0 -0
  299. data/share/html/images/p_ss4_0.gif +0 -0
  300. data/share/html/images/p_ss4_1.gif +0 -0
  301. data/share/html/images/p_ss4_2.gif +0 -0
  302. data/share/html/images/p_ss4_3.gif +0 -0
  303. data/share/html/images/p_ss4_4.gif +0 -0
  304. data/share/html/images/p_ss4_5.gif +0 -0
  305. data/share/html/images/p_ss4_6.gif +0 -0
  306. data/share/html/images/p_ss4_7.gif +0 -0
  307. data/share/html/images/p_ss5_0.gif +0 -0
  308. data/share/html/images/p_ss5_1.gif +0 -0
  309. data/share/html/images/p_ss5_2.gif +0 -0
  310. data/share/html/images/p_ss5_3.gif +0 -0
  311. data/share/html/images/p_ss5_4.gif +0 -0
  312. data/share/html/images/p_ss5_5.gif +0 -0
  313. data/share/html/images/p_ss5_6.gif +0 -0
  314. data/share/html/images/p_ss5_7.gif +0 -0
  315. data/share/html/images/p_ss5r_1.png +0 -0
  316. data/share/html/images/p_ss5r_3.png +0 -0
  317. data/share/html/images/p_ss6_0.gif +0 -0
  318. data/share/html/images/p_ss6_1.gif +0 -0
  319. data/share/html/images/p_ss6_2.gif +0 -0
  320. data/share/html/images/p_ss6_3.gif +0 -0
  321. data/share/html/images/p_ss6_4.gif +0 -0
  322. data/share/html/images/p_ss6_5.gif +0 -0
  323. data/share/html/images/p_ss6_6.gif +0 -0
  324. data/share/html/images/p_ss6_7.gif +0 -0
  325. data/share/html/images/p_ss7_0.gif +0 -0
  326. data/share/html/images/p_ss7_1.gif +0 -0
  327. data/share/html/images/p_ss7_2.gif +0 -0
  328. data/share/html/images/p_ss7_3.gif +0 -0
  329. data/share/html/images/p_ss7_4.gif +0 -0
  330. data/share/html/images/p_ss7_5.gif +0 -0
  331. data/share/html/images/p_ss7_6.gif +0 -0
  332. data/share/html/images/p_ss7_7.gif +0 -0
  333. data/share/html/images/p_ss8_0.gif +0 -0
  334. data/share/html/images/p_ss8_1.gif +0 -0
  335. data/share/html/images/p_ss8_2.gif +0 -0
  336. data/share/html/images/p_ss8_3.gif +0 -0
  337. data/share/html/images/p_ss8_4.gif +0 -0
  338. data/share/html/images/p_ss8_5.gif +0 -0
  339. data/share/html/images/p_ss8_6.gif +0 -0
  340. data/share/html/images/p_ss8_7.gif +0 -0
  341. data/share/html/images/p_ss9_0.gif +0 -0
  342. data/share/html/images/p_ss9_1.gif +0 -0
  343. data/share/html/images/p_ss9_2.gif +0 -0
  344. data/share/html/images/p_ss9_3.gif +0 -0
  345. data/share/html/images/p_ss9_4.gif +0 -0
  346. data/share/html/images/p_ss9_5.gif +0 -0
  347. data/share/html/images/p_ss9_6.gif +0 -0
  348. data/share/html/images/p_ss9_7.gif +0 -0
  349. data/share/html/js/archive_player.coffee +379 -0
  350. data/share/html/js/archive_player.js +505 -0
  351. data/share/html/js/dytem.coffee +83 -0
  352. data/share/html/js/dytem.js +128 -0
  353. data/share/html/js/jquery-1.7.2.min.js +4 -0
  354. data/share/html/views/archive_player.erb +61 -0
  355. metadata +435 -0
@@ -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,14 @@
1
+ require "mjai/player"
2
+
3
+
4
+ module Mjai
5
+
6
+ class PuppetPlayer < Player
7
+
8
+ def respond_to_action(action)
9
+ return nil
10
+ end
11
+
12
+ end
13
+
14
+ 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