mjai 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,171 @@
1
+ require "rubygems"
2
+ require "json"
3
+
4
+ require "mjai/pai"
5
+
6
+
7
+ module Mjai
8
+
9
+ class JSONizable
10
+
11
+ def self.define_fields(specs)
12
+ @@field_specs = specs
13
+ @@field_specs.each() do |name, type|
14
+ define_method(name) do
15
+ return @fields[name]
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.from_json(json, game)
21
+ hash = JSON.parse(json)
22
+ begin
23
+ validate(hash.is_a?(Hash), "The response must be an object.")
24
+ fields = {}
25
+ for name, type in @@field_specs
26
+ plain = hash[name.to_s()]
27
+ next if plain == nil
28
+ fields[name] = plain_to_obj(plain, type, name.to_s(), game)
29
+ end
30
+ return new(fields)
31
+ rescue ValidationError => ex
32
+ raise(ValidationError, "%s JSON: %s" % [ex.message, json])
33
+ end
34
+ end
35
+
36
+ def self.plain_to_obj(plain, type, name, game)
37
+ case type
38
+ when :number
39
+ validate_class(plain, Integer, name)
40
+ return plain
41
+ when :string
42
+ validate_class(plain, String, name)
43
+ return plain
44
+ when :boolean
45
+ validate(
46
+ plain.is_a?(TrueClass) || plain.is_a?(FalseClass),
47
+ "#{name} must be either true or false.")
48
+ return plain
49
+ when :symbol
50
+ validate_class(plain, String, name)
51
+ validate(!plain.empty?, "#{name} must not be empty.")
52
+ return plain.intern()
53
+ when :player
54
+ validate_class(plain, Integer, name)
55
+ validate((0...4).include?(plain), "#{name} must be either 0, 1, 2 or 3.")
56
+ return game.players[plain]
57
+ when :pai
58
+ validate_class(plain, String, name)
59
+ begin
60
+ return Pai.new(plain)
61
+ rescue ArgumentError => ex
62
+ raise(ValidationError, "Error in %s: %s" % [name, ex.message])
63
+ end
64
+ when :yaku
65
+ validate_class(plain, Array, name)
66
+ validate(
67
+ plain.size == 2 && plain[0].is_a?(String) && plain[1].is_a?(Integer),
68
+ "#{name} must be an array of [String, Integer].")
69
+ validate(!plain[0].empty?, "#{name}[0] must not be empty.")
70
+ return [plain[0].intern(), plain[1]]
71
+ when :numbers
72
+ return plains_to_objs(plain, :number, name, game)
73
+ when :strings
74
+ return plains_to_objs(plain, :string, name, game)
75
+ when :booleans
76
+ return plains_to_objs(plain, :boolean, name, game)
77
+ when :symbols
78
+ return plains_to_objs(plain, :symbol, name, game)
79
+ when :pais
80
+ return plains_to_objs(plain, :pai, name, game)
81
+ when :pais_list
82
+ return plains_to_objs(plain, :pais, name, game)
83
+ when :yakus
84
+ return plains_to_objs(plain, :yaku, name, game)
85
+ else
86
+ raise("unknown type")
87
+ end
88
+ end
89
+
90
+ def self.plains_to_objs(plains, type, name, game)
91
+ validate_class(plains, Array, name)
92
+ return plains.each_with_index().map() do |c, i|
93
+ plain_to_obj(c, type, "#{name}[#{i}]", game)
94
+ end
95
+ end
96
+
97
+ def self.validate(criterion, message)
98
+ raise(ValidationError, message) if !criterion
99
+ end
100
+
101
+ def self.validate_class(plain, klass, name)
102
+ validate(plain.is_a?(klass), "%s must be %p." % [name, klass])
103
+ end
104
+
105
+ def initialize(fields)
106
+ for name, value in fields
107
+ if !@@field_specs.any?(){ |n, t| n == name }
108
+ raise(ArgumentError, "unknown field: %p" % name)
109
+ end
110
+ end
111
+ @fields = fields
112
+ end
113
+
114
+ attr_reader(:fields)
115
+
116
+ def to_json()
117
+ hash = {}
118
+ for name, type in @@field_specs
119
+ obj = @fields[name]
120
+ next if obj == nil
121
+ case type
122
+ when :symbol, :pai
123
+ plain = obj.to_s()
124
+ when :player
125
+ plain = obj.id
126
+ when :symbols, :pais
127
+ plain = obj.map(){ |a| a.to_s() }
128
+ when :pais_list
129
+ plain = obj.map(){ |o| o.map(){ |a| a.to_s() } }
130
+ when :yakus
131
+ plain = obj.map(){ |s, n| [s.to_s(), n] }
132
+ when :number, :numbers, :string, :strings, :boolean, :booleans
133
+ plain = obj
134
+ else
135
+ raise("unknown type")
136
+ end
137
+ hash[name.to_s()] = plain
138
+ end
139
+ return JSON.dump(hash)
140
+ end
141
+
142
+ alias to_s to_json
143
+
144
+ def merge(hash)
145
+ fields = @fields.dup()
146
+ for name, value in hash
147
+ if !@@field_specs.any?(){ |n, t| n == name }
148
+ raise(ArgumentError, "unknown field: %p" % k)
149
+ end
150
+ if value == nil
151
+ fields.delete(name)
152
+ else
153
+ fields[name] = value
154
+ end
155
+ end
156
+ return self.class.new(fields)
157
+ end
158
+
159
+ def ==(other)
160
+ return self.class == other.class && @fields == other.fields
161
+ end
162
+
163
+ alias eql? ==
164
+
165
+ def hash
166
+ return @fields.hash
167
+ end
168
+
169
+ end
170
+
171
+ end
@@ -0,0 +1,46 @@
1
+ require "mjai/with_fields"
2
+
3
+
4
+ module Mjai
5
+
6
+ class Mentsu
7
+
8
+ extend(WithFields)
9
+ include(Comparable)
10
+
11
+ # type: :shuntsu, :kotsu, :toitsu, :ryanmen, :kanchan, :penchan, :single
12
+ # visibility: :an, :min
13
+ define_fields([:pais, :type, :visibility])
14
+
15
+ def initialize(fields)
16
+ @fields = fields
17
+ end
18
+
19
+ attr_reader(:fields)
20
+
21
+ def inspect
22
+ return "\#<%p %p>" % [self.class, @fields]
23
+ end
24
+
25
+ def ==(other)
26
+ return self.class == other.class && @fields == other.fields
27
+ end
28
+
29
+ alias eql? ==
30
+
31
+ def hash()
32
+ return @fields.hash()
33
+ end
34
+
35
+ def <=>(other)
36
+ if self.class == other.class
37
+ return Mentsu.field_names.map(){ |s| @fields[s] } <=>
38
+ Mentsu.field_names.map(){ |s| other.fields[s] }
39
+ else
40
+ raise(ArgumentError, "invalid comparison")
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,93 @@
1
+ require "optparse"
2
+
3
+ require "mjai/tcp_game_server"
4
+ require "mjai/tcp_client_game"
5
+ require "mjai/tsumogiri_player"
6
+ require "mjai/shanten_player"
7
+ require "mjai/file_converter"
8
+
9
+
10
+ module Mjai
11
+
12
+ class MjaiCommand
13
+
14
+ def self.execute(command_name, argv)
15
+
16
+ Thread.abort_on_exception = true
17
+ case command_name
18
+
19
+ when "mjai"
20
+
21
+ action = argv.shift()
22
+ opts = OptionParser.getopts(argv, "",
23
+ "port:11600", "host:127.0.0.1", "room:default", "game_type:one_kyoku",
24
+ "games:1", "repeat", "log_dir:")
25
+ case action
26
+ when "server"
27
+ $stdout.sync = true
28
+ if opts["repeat"]
29
+ num_games = 1.0/0.0
30
+ else
31
+ num_games = opts["games"].to_i()
32
+ end
33
+ server = TCPGameServer.new({
34
+ :host => opts["host"],
35
+ :port => opts["port"].to_i(),
36
+ :room => opts["room"],
37
+ :game_type => opts["game_type"].intern,
38
+ :player_commands => argv,
39
+ :num_games => num_games,
40
+ :log_dir => opts["log_dir"],
41
+ })
42
+ server.run()
43
+ when "convert"
44
+ FileConverter.new().convert(argv.shift(), argv.shift())
45
+ else
46
+ $stderr.puts(
47
+ "Usage:\n" +
48
+ " #{$PROGRAM_NAME} server --port=PORT " +
49
+ "[PLAYER1_COMMAND] [PLAYER2_COMMAND] [...]\n" +
50
+ " #{$PROGRAM_NAME} convert hoge.mjson hoge.html\n" +
51
+ " #{$PROGRAM_NAME} convert hoge.mjlog hoge.mjson\n")
52
+ exit(1)
53
+ end
54
+
55
+ when /^mjai-(.+)$/
56
+
57
+ $stdout.sync = true
58
+ $stderr.sync = true
59
+ player_type = $1
60
+ opts = OptionParser.getopts(argv, "", "t:", "name:")
61
+ url = ARGV.shift()
62
+
63
+ if !url
64
+ $stderr.puts(
65
+ "Usage:\n" +
66
+ " #{$PROGRAM_NAME} mjsonp://localhost:11600/default\n")
67
+ exit(1)
68
+ end
69
+ case player_type
70
+ when "tsumogiri"
71
+ player = TsumogiriPlayer.new()
72
+ when "shanten"
73
+ player = Mjai::ShantenPlayer.new({:use_furo => opts["t"] == "f"})
74
+ else
75
+ raise("should not happen")
76
+ end
77
+ game = TCPClientGame.new({
78
+ :player => player,
79
+ :url => url,
80
+ :name => opts["name"] || player_type,
81
+ })
82
+ game.play()
83
+
84
+ else
85
+ raise("should not happen")
86
+
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,25 @@
1
+ require "mjai/archive"
2
+ require "mjai/puppet_player"
3
+ require "mjai/action"
4
+
5
+
6
+ module Mjai
7
+
8
+ class MjsonArchive < Archive
9
+
10
+ def initialize(path)
11
+ super()
12
+ @path = path
13
+ end
14
+
15
+ attr_reader(:path)
16
+
17
+ def play()
18
+ File.foreach(@path) do |line|
19
+ do_action(Action.from_json(line.chomp(), self))
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,138 @@
1
+ module Mjai
2
+
3
+ class Pai
4
+
5
+ include(Comparable)
6
+
7
+ TSUPAI_STRS = " ESWNPFC".split(//)
8
+
9
+ def self.parse_pais(str)
10
+ type = nil
11
+ pais = []
12
+ red = false
13
+ str.gsub(/\s+/, "").split(//).reverse_each() do |ch|
14
+ next if ch =~ /^\s$/
15
+ if ch =~ /^[mps]$/
16
+ type = ch
17
+ elsif ch =~ /^[1-9]$/
18
+ raise(ArgumentError, "type required after number") if !type
19
+ pais.push(Pai.new(type, ch.to_i(), red))
20
+ red = false
21
+ elsif TSUPAI_STRS.include?(ch)
22
+ pais.push(Pai.new(ch))
23
+ elsif ch == "r"
24
+ red = true
25
+ else
26
+ raise(ArgumentError, "unexpected character: %s", ch)
27
+ end
28
+ end
29
+ return pais.reverse()
30
+ end
31
+
32
+ def self.dump_pais(pais)
33
+ return pais.map(){ |pai| "%-3s" % pai }.join("")
34
+ end
35
+
36
+ def initialize(*args)
37
+ case args.size
38
+ when 1
39
+ str = args[0]
40
+ if str == "?"
41
+ @type = @number = nil
42
+ @red = false
43
+ elsif str =~ /\A([1-9])([mps])(r)?\z/
44
+ @type = $2
45
+ @number = $1.to_i()
46
+ @red = $3 != nil
47
+ elsif number = TSUPAI_STRS.index(str)
48
+ @type = "t"
49
+ @number = number
50
+ @red = false
51
+ else
52
+ raise(ArgumentError, "Unknown pai string: %s" % str)
53
+ end
54
+ when 2, 3
55
+ (@type, @number, @red) = args
56
+ @red = false if @red == nil
57
+ else
58
+ raise(ArgumentError, "Wrong number of args.")
59
+ end
60
+ end
61
+
62
+ def to_s()
63
+ if !@type
64
+ return "?"
65
+ elsif @type == "t"
66
+ return TSUPAI_STRS[@number]
67
+ else
68
+ return "%d%s%s" % [@number, @type, @red ? "r" : ""]
69
+ end
70
+ end
71
+
72
+ def inspect
73
+ return "Pai[%s]" % self.to_s()
74
+ end
75
+
76
+ attr_reader(:type, :number)
77
+
78
+ def red?
79
+ return @red
80
+ end
81
+
82
+ def yaochu?
83
+ return @type == "t" || @number == 1 || @number == 9
84
+ end
85
+
86
+ def fonpai?
87
+ return @type == "t" && (1..4).include?(@number)
88
+ end
89
+
90
+ def sangenpai?
91
+ return @type == "t" && (5..7).include?(@number)
92
+ end
93
+
94
+ def data
95
+ return [@type || "", @number || -1, @red ? 1 : 0]
96
+ end
97
+
98
+ def ==(other)
99
+ return self.class == other.class && self.data == other.data
100
+ end
101
+
102
+ alias eql? ==
103
+
104
+ def hash()
105
+ return self.data.hash()
106
+ end
107
+
108
+ def <=>(other)
109
+ if self.class == other.class
110
+ return self.data <=> other.data
111
+ else
112
+ raise(ArgumentError, "invalid comparison")
113
+ end
114
+ end
115
+
116
+ def remove_red()
117
+ return Pai.new(@type, @number)
118
+ end
119
+
120
+ def same_symbol?(other)
121
+ return @type == other.type && @number == other.number
122
+ end
123
+
124
+ # Next pai in terms of dora derivation.
125
+ def succ
126
+ if (@type == "t" && @number == 7) || (@type != "t" && @number == 9)
127
+ number = 1
128
+ else
129
+ number = @number + 1
130
+ end
131
+ return Pai.new(@type, number)
132
+ end
133
+
134
+ UNKNOWN = Pai.new(nil, nil)
135
+
136
+ end
137
+
138
+ end