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,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