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,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+
6
+ require "mjai/mjai_command"
7
+
8
+
9
+ Mjai::MjaiCommand.execute("mjai", ARGV)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+
6
+ require "mjai/mjai_command"
7
+
8
+
9
+ Mjai::MjaiCommand.execute("mjai-shanten", ARGV)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+
6
+ require "mjai/mjai_command"
7
+
8
+
9
+ Mjai::MjaiCommand.execute("mjai-tsumogiri", ARGV)
@@ -0,0 +1,41 @@
1
+ require "mjai/jsonizable"
2
+
3
+
4
+ module Mjai
5
+
6
+ class Action < JSONizable
7
+
8
+ define_fields([
9
+ [:type, :symbol],
10
+ [:reason, :symbol],
11
+ [:actor, :player],
12
+ [:target, :player],
13
+ [:pai, :pai],
14
+ [:consumed, :pais],
15
+ [:pais, :pais],
16
+ [:tsumogiri, :boolean],
17
+ [:id, :number],
18
+ [:bakaze, :pai],
19
+ [:kyoku, :number],
20
+ [:honba, :number],
21
+ [:oya, :player],
22
+ [:dora_marker, :pai],
23
+ [:uradora_markers, :pais],
24
+ [:tehais, :pais_list],
25
+ [:uri, :string],
26
+ [:names, :strings],
27
+ [:hora_tehais, :pais],
28
+ [:yakus, :yakus],
29
+ [:fu, :number],
30
+ [:fan, :number],
31
+ [:hora_points, :number],
32
+ [:tenpais, :booleans],
33
+ [:deltas, :numbers],
34
+ [:scores, :numbers],
35
+ [:text, :string],
36
+ [:message, :string],
37
+ ])
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,230 @@
1
+ require "mjai/game"
2
+ require "mjai/action"
3
+ require "mjai/hora"
4
+ require "mjai/validation_error"
5
+
6
+
7
+ module Mjai
8
+
9
+ class ActiveGame < Game
10
+
11
+ ACTION_PREFERENCES = {
12
+ :hora => 3,
13
+ :pon => 2,
14
+ :daiminkan => 2,
15
+ :chi => 1,
16
+ }
17
+
18
+ def initialize(players)
19
+ super(players.shuffle())
20
+ @game_type = :one_kyoku
21
+ end
22
+
23
+ attr_accessor(:game_type)
24
+
25
+ def play()
26
+ begin
27
+ do_action({:type => :start_game, :names => self.players.map(){ |pl| pl.name }})
28
+ @ag_oya = @ag_chicha = @players[0]
29
+ @ag_bakaze = Pai.new("E")
30
+ @ag_honba = 0
31
+ while !self.game_finished?
32
+ play_kyoku()
33
+ end
34
+ do_action({:type => :end_game})
35
+ return true
36
+ rescue ValidationError => ex
37
+ do_action({:type => :error, :message => ex.message})
38
+ return false
39
+ end
40
+ end
41
+
42
+ def play_kyoku()
43
+ catch(:end_kyoku) do
44
+ @pipais = @all_pais.shuffle()
45
+ @pipais.shuffle!()
46
+ @wanpais = @pipais.pop(14)
47
+ dora_marker = @wanpais.pop()
48
+ tehais = Array.new(4){ @pipais.pop(13).sort() }
49
+ do_action({
50
+ :type => :start_kyoku,
51
+ :bakaze => @ag_bakaze,
52
+ :kyoku => (4 + @ag_oya.id - @ag_chicha.id) % 4 + 1,
53
+ :honba => @ag_honba,
54
+ :oya => @ag_oya,
55
+ :dora_marker => dora_marker,
56
+ :tehais => tehais,
57
+ })
58
+ @actor = self.oya
59
+ while !@pipais.empty?
60
+ mota()
61
+ @actor = @players[(@actor.id + 1) % 4]
62
+ end
63
+ process_ryukyoku()
64
+ end
65
+ do_action({:type => :end_kyoku})
66
+ end
67
+
68
+ # 摸打
69
+ def mota()
70
+ reach = false
71
+ tsumo_actor = @actor
72
+ actions = [Action.new({:type => :tsumo, :actor => @actor, :pai => @pipais.pop()})]
73
+ while !actions.empty?
74
+ if actions[0].type == :hora
75
+ # actions.size >= 2 in case of double/triple ron
76
+ process_hora(actions)
77
+ throw(:end_kyoku)
78
+ else
79
+ raise("should not happen") if actions.size != 1
80
+ action = actions[0]
81
+ responses = do_action(action)
82
+ case action.type
83
+ when :daiminkan, :kakan, :ankan
84
+ # Actually takes one from wanpai and moves one pai from pipai to wanpai,
85
+ # but it's equivalent to taking from pipai.
86
+ actions =
87
+ [Action.new({:type => :tsumo, :actor => action.actor, :pai => @pipais.pop()})]
88
+ # TODO Add dora.
89
+ next
90
+ when :reach
91
+ reach = true
92
+ end
93
+ actions = choose_actions(responses)
94
+ if reach && (actions.empty? || ![:dahai, :hora].include?(actions[0].type))
95
+ deltas = [0, 0, 0, 0]
96
+ deltas[tsumo_actor.id] = -1000
97
+ do_action({
98
+ :type => :reach_accepted,:actor => tsumo_actor,
99
+ :deltas => deltas,
100
+ :scores => get_scores(deltas),
101
+ })
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def update_state(action)
108
+ super(action)
109
+ if action.type == :tsumo && @pipais.size != self.num_pipais
110
+ raise("num pipais mismatch: %p != %p" % [@pipais.size, self.num_pipais])
111
+ end
112
+ end
113
+
114
+ def choose_actions(actions)
115
+ actions = actions.select(){ |a| a }
116
+ max_pref = actions.map(){ |a| ACTION_PREFERENCES[a.type] || 0 }.max
117
+ max_actions = actions.select(){ |a| (ACTION_PREFERENCES[a.type] || 0) == max_pref }
118
+ return max_actions
119
+ end
120
+
121
+ def process_hora(actions)
122
+ # TODO ダブロンの上家取り
123
+ for action in actions
124
+ hora = get_hora(action)
125
+ raise("no yaku") if !hora.valid?
126
+ deltas = [0, 0, 0, 0]
127
+ # TODO 積み棒
128
+ deltas[action.actor.id] += hora.points + self.honba * 300
129
+ deltas[action.actor.id] += self.players.select(){ |pl| pl.reach? }.size * 1000
130
+ if hora.hora_type == :tsumo
131
+ for player in self.players
132
+ next if player == action.actor
133
+ deltas[player.id] -=
134
+ ((player == self.oya ? hora.oya_payment : hora.ko_payment) + self.honba * 100)
135
+ end
136
+ else
137
+ deltas[action.target.id] -= (hora.points + self.honba * 300)
138
+ end
139
+ do_action({
140
+ :type => action.type,
141
+ :actor => action.actor,
142
+ :target => action.target,
143
+ :pai => action.pai,
144
+ :hora_tehais => action.actor.tehais,
145
+ :yakus => hora.yakus,
146
+ :fu => hora.fu,
147
+ :fan => hora.fan,
148
+ :hora_points => hora.points,
149
+ :deltas => deltas,
150
+ :scores => get_scores(deltas),
151
+ })
152
+ end
153
+ update_oya(actions.any?(){ |a| a.actor == self.oya }, false)
154
+ end
155
+
156
+ def process_ryukyoku()
157
+ tenpais = []
158
+ tehais = []
159
+ for player in players
160
+ if player.tenpai?
161
+ tenpais.push(true)
162
+ tehais.push(player.tehais)
163
+ else
164
+ tenpais.push(false)
165
+ tehais.push([Pai::UNKNOWN] * player.tehais.size)
166
+ end
167
+ end
168
+ tenpai_ids = (0...4).select(){ |i| tenpais[i] }
169
+ noten_ids = (0...4).select(){ |i| !tenpais[i] }
170
+ deltas = [0, 0, 0, 0]
171
+ if (1..3).include?(tenpai_ids.size)
172
+ for id in tenpai_ids
173
+ deltas[id] += 3000 / tenpai_ids.size
174
+ end
175
+ for id in noten_ids
176
+ deltas[id] -= 3000 / noten_ids.size
177
+ end
178
+ end
179
+ do_action({
180
+ :type => :ryukyoku,
181
+ :reason => :fanpai,
182
+ :tenpais => tenpais,
183
+ :tehais => tehais,
184
+ :deltas => deltas,
185
+ :scores => get_scores(deltas),
186
+ })
187
+ update_oya(tenpais[self.oya.id], true)
188
+ end
189
+
190
+ def update_oya(renchan, ryukyoku)
191
+ if renchan
192
+ @ag_oya = self.oya
193
+ else
194
+ if self.oya == @players[3]
195
+ @ag_bakaze = @ag_bakaze.succ
196
+ if (@game_type == :tonpu && @ag_bakaze == Pai.new("S")) ||
197
+ (@game_type == :tonnan && @ag_bakaze == Pai.new("W"))
198
+ # TODO Consider 南入, etc.
199
+ @last = true
200
+ end
201
+ end
202
+ @ag_oya = @players[(self.oya.id + 1) % 4]
203
+ end
204
+ if renchan || ryukyoku
205
+ @ag_honba += 1
206
+ else
207
+ @ag_honba = 0
208
+ end
209
+ end
210
+
211
+ def game_finished?
212
+ if @last
213
+ return true
214
+ else
215
+ @last = true if @game_type == :one_kyoku
216
+ return false
217
+ end
218
+ end
219
+
220
+ def expect_response_from?(player)
221
+ return true
222
+ end
223
+
224
+ def get_scores(deltas)
225
+ return (0...4).map(){ |i| self.players[i].score + deltas[i] }
226
+ end
227
+
228
+ end
229
+
230
+ end
@@ -0,0 +1,46 @@
1
+ require "mjai/game"
2
+
3
+
4
+ module Mjai
5
+
6
+ autoload(:TenhouArchive, "mjai/tenhou_archive")
7
+ autoload(:MjsonArchive, "mjai/mjson_archive")
8
+
9
+ class Archive < Game
10
+
11
+ def self.load(path)
12
+ case File.extname(path)
13
+ when ".mjlog"
14
+ return TenhouArchive.new(path)
15
+ when ".mjson"
16
+ return MjsonArchive.new(path)
17
+ else
18
+ raise("unknown format")
19
+ end
20
+ end
21
+
22
+ def initialize()
23
+ super((0...4).map(){ PuppetPlayer.new() })
24
+ @actions = nil
25
+ end
26
+
27
+ def each_action(&block)
28
+ if block
29
+ on_action(&block)
30
+ play()
31
+ else
32
+ return enum_for(:each_action)
33
+ end
34
+ end
35
+
36
+ def actions
37
+ return @actions ||= self.each_action.to_a()
38
+ end
39
+
40
+ def expect_response_from?(player)
41
+ return false
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,47 @@
1
+ require "mjai/player"
2
+ require "mjai/archive"
3
+
4
+
5
+ module Mjai
6
+
7
+ class ArchivePlayer < Player
8
+
9
+ def initialize(archive_path)
10
+ super()
11
+ @archive = Archive.load(archive_path)
12
+ @action_index = 0
13
+ end
14
+
15
+ def update_state(action)
16
+ super(action)
17
+ expected_action = @archive.actions[@action_index]
18
+ if action.type == :start_game
19
+ action = action.merge({:id => nil})
20
+ expected_action = expected_action.merge({:id => nil})
21
+ end
22
+ if action.to_json() != expected_action.to_json()
23
+ raise((
24
+ "live action doesn't match one in archive\n" +
25
+ "actual: %s\n" +
26
+ "expected: %s\n") %
27
+ [action, expected_action])
28
+ end
29
+ @action_index += 1
30
+ end
31
+
32
+ def respond_to_action(action)
33
+ next_action = @archive.actions[@action_index]
34
+ if next_action &&
35
+ next_action.actor &&
36
+ next_action.actor.id == 0 &&
37
+ [:dahai, :chi, :pon, :daiminkan, :kakan, :ankan, :reach, :hora].include?(
38
+ next_action.type)
39
+ return Action.from_json(next_action.to_json(), self.game)
40
+ else
41
+ return nil
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,34 @@
1
+ require "mjai/with_fields"
2
+
3
+
4
+ module Mjai
5
+
6
+ # Context of the game which affects hora yaku and points.
7
+ class Context
8
+
9
+ extend(WithFields)
10
+
11
+ define_fields([
12
+ :oya, :bakaze, :jikaze, :doras, :uradoras,
13
+ :reach, :double_reach, :ippatsu,
14
+ :rinshan, :haitei, :first_turn, :chankan,
15
+ ])
16
+
17
+ def initialize(fields)
18
+ @fields = fields
19
+ end
20
+
21
+ def fanpai_fan(pai)
22
+ if pai.sangenpai?
23
+ return 1
24
+ else
25
+ fan = 0
26
+ fan += 1 if pai == self.bakaze
27
+ fan += 1 if pai == self.jikaze
28
+ return fan
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,86 @@
1
+ require "erb"
2
+ require "fileutils"
3
+
4
+ require "mjai/archive"
5
+
6
+
7
+ module Mjai
8
+
9
+ class FileConverter
10
+
11
+ include(ERB::Util)
12
+
13
+ def convert(src_path, dest_path)
14
+ src_ext = File.extname(src_path)
15
+ dest_ext = File.extname(dest_path)
16
+ case [src_ext, dest_ext]
17
+ when [".mjson", ".html"]
18
+ mjson_to_html(src_path, dest_path)
19
+ when [".mjlog", ".xml"]
20
+ archive = Archive.load(src_path)
21
+ open(dest_path, "w"){ |f| f.write(archive.xml) }
22
+ when [".mjson", ".human"], [".mjlog", ".human"]
23
+ dump_archive(src_path, dest_path, :human)
24
+ when [".mjlog", ".mjson"]
25
+ dump_archive(src_path, dest_path, :mjson)
26
+ else
27
+ raise("unsupported ext pair: #{src_ext}, #{dest_ext}")
28
+ end
29
+ end
30
+
31
+ def dump_archive(archive_path, output_path, output_format)
32
+ archive = Archive.load(archive_path)
33
+ open(output_path, "w") do |f|
34
+ archive.on_action() do |action|
35
+ if output_format == :human
36
+ archive.dump_action(action, f)
37
+ else
38
+ f.puts(action.to_json())
39
+ end
40
+ end
41
+ archive.play()
42
+ end
43
+ end
44
+
45
+ def mjson_to_html(mjson_path, html_path)
46
+
47
+ res_dir = File.dirname(__FILE__) + "/../../share/html"
48
+ make("#{res_dir}/js/archive_player.coffee",
49
+ "#{res_dir}/js/archive_player.js",
50
+ "coffee -cb #{res_dir}/js/archive_player.coffee")
51
+ make("#{res_dir}/js/dytem.coffee",
52
+ "#{res_dir}/js/dytem.js",
53
+ "coffee -cb #{res_dir}/js/dytem.coffee")
54
+ make("#{res_dir}/css/style.scss",
55
+ "#{res_dir}/css/style.css",
56
+ "sass #{res_dir}/css/style.scss #{res_dir}/css/style.css")
57
+
58
+ # Variants used in template.
59
+ action_jsons = File.readlines(mjson_path).map(){ |s| s.chomp().gsub(/\//){ "\\/" } }
60
+ actions_json = "[%s]" % action_jsons.join(",\n")
61
+ base_name = File.basename(html_path)
62
+
63
+ html = ERB.new(File.read("#{res_dir}/views/archive_player.erb"), nil, "<>").
64
+ result(binding)
65
+ open(html_path, "w"){ |f| f.write(html) }
66
+ for src_path in Dir["#{res_dir}/css/*.css"] + Dir["#{res_dir}/js/*.js"]
67
+ exp = Regexp.new("^%s\\/" % Regexp.escape(res_dir))
68
+ dest_path = src_path.gsub(exp){ "#{html_path}.files/" }
69
+ FileUtils.mkdir_p(File.dirname(dest_path))
70
+ FileUtils.cp(src_path, dest_path)
71
+ end
72
+
73
+ end
74
+
75
+ def make(src_path, dest_path, command)
76
+ if File.mtime(src_path) > File.mtime(dest_path)
77
+ puts(command)
78
+ if !system(command)
79
+ exit(1)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end