mjai 0.0.2 → 0.0.3

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.
@@ -1,3 +1,4 @@
1
+ require "set"
1
2
  require "mjai/pai"
2
3
  require "mjai/mentsu"
3
4
 
@@ -56,7 +56,7 @@ module Mjai
56
56
  if sutehai_cands.empty?
57
57
  sutehai_cands = self.possible_dahais
58
58
  end
59
- log("sutehai_cands = %p" % [sutehai_cands])
59
+ #log("sutehai_cands = %p" % [sutehai_cands])
60
60
  sutehai = sutehai_cands[rand(sutehai_cands.size)]
61
61
  tsumogiri = [:tsumo, :reach].include?(action.type) && sutehai == self.tehais[-1]
62
62
  return create_action({:type => :dahai, :pai => sutehai, :tsumogiri => tsumogiri})
@@ -1,6 +1,7 @@
1
1
  require "mjai/active_game"
2
2
  require "mjai/tcp_game_server"
3
3
  require "mjai/confidence_interval"
4
+ require "mjai/file_converter"
4
5
 
5
6
 
6
7
  module Mjai
@@ -26,17 +27,24 @@ module Mjai
26
27
  mjson_path = nil
27
28
  end
28
29
 
30
+ game = nil
31
+ success = false
29
32
  maybe_open(mjson_path, "w") do |mjson_out|
30
33
  mjson_out.sync = true if mjson_out
31
34
  game = ActiveGame.new(players)
32
35
  game.game_type = self.params[:game_type]
33
36
  game.on_action() do |action|
34
- mjson_out.puts(action.to_json()) if mjson_out
35
37
  game.dump_action(action)
36
38
  end
39
+ game.on_responses() do |action, responses|
40
+ # Logs on on_responses to include "logs" field.
41
+ mjson_out.puts(action.to_json()) if mjson_out
42
+ end
37
43
  success = game.play()
38
- return [game, success]
39
44
  end
45
+
46
+ FileConverter.new().convert(mjson_path, "#{mjson_path}.html") if mjson_path
47
+ return [game, success]
40
48
 
41
49
  end
42
50
 
@@ -22,6 +22,7 @@ module Mjai
22
22
  uri = URI.parse(@params[:url])
23
23
  TCPSocket.open(uri.host, uri.port) do |socket|
24
24
  socket.sync = true
25
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
25
26
  socket.each_line() do |line|
26
27
  puts("<-\t%s" % line.chomp())
27
28
  action_json = line.chomp()
@@ -11,6 +11,9 @@ module Mjai
11
11
 
12
12
  class TCPGameServer
13
13
 
14
+ class LocalError < StandardError
15
+ end
16
+
14
17
  def initialize(params)
15
18
  @params = params
16
19
  @server = TCPServer.open(params[:host], params[:port])
@@ -30,43 +33,51 @@ module Mjai
30
33
  start_default_players()
31
34
  while true
32
35
  Thread.new(@server.accept()) do |socket|
33
- socket.sync = true
34
- send(socket, {
35
- "type" => "hello",
36
- "protocol" => "mjsonp",
37
- "protocol_version" => 1,
38
- })
39
36
  error = nil
40
37
  begin
38
+ socket.sync = true
39
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
40
+ send(socket, {
41
+ "type" => "hello",
42
+ "protocol" => "mjsonp",
43
+ "protocol_version" => 2,
44
+ })
41
45
  line = socket.gets()
46
+ if !line
47
+ raise(LocalError, "Connection closed")
48
+ end
42
49
  puts("server <- player ?\t#{line}")
43
50
  message = JSON.parse(line)
44
- if message["type"] == "join" && message["name"] && message["room"]
45
- if message["room"] == @params[:room]
46
- @mutex.synchronize() do
47
- if @players.size < self.num_tcp_players
48
- @players.push(TCPPlayer.new(socket, message["name"]))
49
- puts("Waiting for %s more players..." % (self.num_tcp_players - @players.size))
50
- if @players.size == self.num_tcp_players
51
- Thread.new(){ process_one_game() }
52
- end
53
- else
54
- error = "The room is busy. Retry after a while."
55
- end
56
- end
57
- else
58
- error = "No such room. Available room: %s" % @params[:room]
51
+ if message["type"] != "join" || !message["name"] || !message["room"]
52
+ raise(LocalError, "Expected e.g. %s" %
53
+ JSON.dump({"type" => "join", "name" => "noname", "room" => @params[:room]}))
54
+ end
55
+ if message["room"] != @params[:room]
56
+ raise(LocalError, "No such room. Available room: %s" % @params[:room])
57
+ end
58
+ @mutex.synchronize() do
59
+ if @players.size >= self.num_tcp_players
60
+ raise(LocalError, "The room is busy. Retry after a while.")
61
+ end
62
+ @players.push(TCPPlayer.new(socket, message["name"]))
63
+ puts("Waiting for %s more players..." % (self.num_tcp_players - @players.size))
64
+ if @players.size == self.num_tcp_players
65
+ Thread.new(){ process_one_game() }
59
66
  end
60
- else
61
- error = "Expected e.g. %s" %
62
- JSON.dump({"type" => "join", "name" => "noname", "room" => @params[:room]})
63
67
  end
64
68
  rescue JSON::ParserError => ex
65
69
  error = "JSON syntax error: %s" % ex.message
70
+ rescue SystemCallError => ex
71
+ error = ex.message
72
+ rescue LocalError => ex
73
+ error = ex.message
66
74
  end
67
75
  if error
68
- send(socket, {"type" => "error", "message" => error})
69
- socket.close()
76
+ begin
77
+ send(socket, {"type" => "error", "message" => error})
78
+ socket.close()
79
+ rescue SystemCallError
80
+ end
70
81
  end
71
82
  end
72
83
  end
@@ -21,7 +21,6 @@ module Mjai
21
21
 
22
22
  begin
23
23
 
24
- return nil if action.type == :log
25
24
  puts("server -> player %d\t%s" % [self.id, action.to_json()])
26
25
  @socket.puts(action.to_json())
27
26
  line = nil
@@ -30,11 +29,10 @@ module Mjai
30
29
  end
31
30
  if line
32
31
  puts("server <- player %d\t%s" % [self.id, line])
33
- response = Action.from_json(line.chomp(), self.game)
34
- return response.type == :none ? nil : response
32
+ return Action.from_json(line.chomp(), self.game)
35
33
  else
36
34
  puts("server : Player %d has disconnected." % self.id)
37
- return nil
35
+ return Action.new({:type => :none})
38
36
  end
39
37
 
40
38
  rescue Timeout::Error
@@ -141,11 +141,19 @@ module Mjai
141
141
  end
142
142
  uradora_markers = (elem["doraHaiUra"] || "").
143
143
  split(/,/).map(){ |pid| pid_to_pai(pid) }
144
- yakus = elem["yaku"].
145
- split(/,/).
146
- enum_for(:each_slice, 2).
147
- map(){ |y, f| [YAKU_ID_TO_NAME[y.to_i()], f.to_i()] }.
148
- select(){ |y, f| f != 0 }
144
+
145
+ if elem["yakuman"]
146
+ yakus = elem["yakuman"].
147
+ split(/,/).
148
+ map(){ |y| [YAKU_ID_TO_NAME[y.to_i()], Hora::YAKUMAN_FAN] }
149
+ else
150
+ yakus = elem["yaku"].
151
+ split(/,/).
152
+ enum_for(:each_slice, 2).
153
+ map(){ |y, f| [YAKU_ID_TO_NAME[y.to_i()], f.to_i()] }.
154
+ select(){ |y, f| f != 0 }
155
+ end
156
+
149
157
  do_action({
150
158
  :type => :hora,
151
159
  :actor => self.players[elem["who"].to_i()],
@@ -0,0 +1,105 @@
1
+ require "mjai/pai"
2
+ require "mjai/mentsu"
3
+
4
+
5
+ module Mjai
6
+
7
+ class YmatsuxShantenAnalysis
8
+
9
+ NUM_PIDS = 9 * 3 + 7
10
+ TYPES = ["m", "p", "s", "t"]
11
+ TYPE_TO_TYPE_ID = {"m" => 0, "p" => 1, "s" => 2, "t" => 3}
12
+
13
+ def self.create_mentsus()
14
+ mentsus = []
15
+ for i in 0...NUM_PIDS
16
+ mentsus.push([i] * 3)
17
+ end
18
+ for t in 0...3
19
+ for n in 0...7
20
+ pid = t * 9 + n
21
+ mentsus.push([pid, pid + 1, pid + 2])
22
+ end
23
+ end
24
+ return mentsus
25
+ end
26
+
27
+ MENTSUS = create_mentsus()
28
+
29
+ def initialize(pais)
30
+ @pais = pais
31
+ count_vector = YmatsuxShantenAnalysis.pais_to_count_vector(pais)
32
+ @shanten = YmatsuxShantenAnalysis.calculate_shantensu_internal(count_vector, [0] * NUM_PIDS, 4, 0, 1.0/0.0)
33
+ end
34
+
35
+ attr_reader(:pais, :shanten)
36
+
37
+ def self.pais_to_count_vector(pais)
38
+ count_vector = [0] * NUM_PIDS
39
+ for pai in pais
40
+ count_vector[pai_to_pid(pai)] += 1
41
+ end
42
+ return count_vector
43
+ end
44
+
45
+ def self.pai_to_pid(pai)
46
+ return TYPE_TO_TYPE_ID[pai.type] * 9 + (pai.number - 1)
47
+ end
48
+
49
+ def self.pid_to_pai(pid)
50
+ return Pai.new(TYPES[pid / 9], pid % 9 + 1)
51
+ end
52
+
53
+ def self.calculate_shantensu_internal(
54
+ current_vector, target_vector, left_mentsu, min_mentsu_id, found_min_shantensu)
55
+ min_shantensu = found_min_shantensu
56
+ if left_mentsu == 0
57
+ for pid in 0...NUM_PIDS
58
+ target_vector[pid] += 2
59
+ if valid_target_vector?(target_vector)
60
+ shantensu = calculate_shantensu_lowerbound(current_vector, target_vector)
61
+ min_shantensu = [shantensu, min_shantensu].min
62
+ end
63
+ target_vector[pid] -= 2
64
+ end
65
+ else
66
+ for mentsu_id in min_mentsu_id...MENTSUS.size
67
+ add_mentsu(target_vector, mentsu_id)
68
+ lower_bound = calculate_shantensu_lowerbound(current_vector, target_vector)
69
+ if valid_target_vector?(target_vector) && lower_bound < found_min_shantensu
70
+ shantensu = calculate_shantensu_internal(
71
+ current_vector, target_vector, left_mentsu - 1, mentsu_id, min_shantensu)
72
+ min_shantensu = [shantensu, min_shantensu].min
73
+ end
74
+ remove_mentsu(target_vector, mentsu_id)
75
+ end
76
+ end
77
+ return min_shantensu
78
+ end
79
+
80
+ def self.calculate_shantensu_lowerbound(current_vector, target_vector)
81
+ count = (0...NUM_PIDS).inject(0) do |c, pid|
82
+ c + (target_vector[pid] > current_vector[pid] ? target_vector[pid] - current_vector[pid] : 0)
83
+ end
84
+ return count - 1
85
+ end
86
+
87
+ def self.valid_target_vector?(target_vector)
88
+ return target_vector.all?(){ |c| c <= 4 }
89
+ end
90
+
91
+ def self.add_mentsu(target_vector, mentsu_id)
92
+ for pid in MENTSUS[mentsu_id]
93
+ target_vector[pid] += 1
94
+ end
95
+ end
96
+
97
+ def self.remove_mentsu(target_vector, mentsu_id)
98
+ for pid in MENTSUS[mentsu_id]
99
+ target_vector[pid] -= 1
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -5,74 +5,78 @@
5
5
  display: none; }
6
6
 
7
7
  .pai {
8
- width: 16px;
9
- height: 30px; }
8
+ width: 22px;
9
+ height: 40px; }
10
10
 
11
11
  .laid-pai {
12
- width: 22px;
13
- height: 26px; }
12
+ width: 30px;
13
+ height: 32px; }
14
14
 
15
15
  .board {
16
16
  background-color: green;
17
17
  position: absolute;
18
18
  left: 0px;
19
19
  top: 0px;
20
- width: 400px;
21
- height: 400px; }
20
+ width: 550px;
21
+ height: 550px; }
22
22
 
23
23
  .player {
24
24
  /* border: 1px black solid; */
25
- width: 400px;
25
+ width: 550px;
26
26
  position: absolute; }
27
27
 
28
28
  .player-0 {
29
29
  left: 0px;
30
- top: 250px;
31
- -webkit-transform: rotate(0deg); }
30
+ top: 350px;
31
+ -webkit-transform: rotate(0deg);
32
+ transform: rotate(0deg); }
32
33
 
33
34
  .player-1 {
34
- left: 125px;
35
- top: 125px;
36
- -webkit-transform: rotate(270deg); }
35
+ left: 175px;
36
+ top: 175px;
37
+ -webkit-transform: rotate(270deg);
38
+ transform: rotate(270deg); }
37
39
 
38
40
  .player-2 {
39
41
  left: 0px;
40
42
  top: 0px;
41
- -webkit-transform: rotate(180deg); }
43
+ -webkit-transform: rotate(180deg);
44
+ transform: rotate(180deg); }
42
45
 
43
46
  .player-3 {
44
- left: -125px;
45
- top: 125px;
46
- -webkit-transform: rotate(90deg); }
47
+ left: -175px;
48
+ top: 175px;
49
+ -webkit-transform: rotate(90deg);
50
+ transform: rotate(90deg); }
47
51
 
48
52
  .wanpais-container {
49
53
  position: absolute;
50
- left: 152px;
51
- top: 185px; }
54
+ left: 209px;
55
+ top: 255px; }
52
56
 
53
57
  .ho {
54
- margin-left: 152px;
55
- margin-bottom: 30px; }
58
+ margin-left: 209px;
59
+ margin-bottom: 40px; }
56
60
 
57
61
  .furo-container {
58
62
  float: right; }
59
63
 
60
64
  .tehai-container {
61
- margin-left: 88px;
65
+ margin-left: 121px;
62
66
  float: left; }
63
67
 
64
68
  .tsumo-pai {
65
- margin-left: 4px; }
69
+ margin-left: 5.5px; }
66
70
 
67
71
  .player-footer {
68
72
  clear: both; }
69
73
 
70
74
  .pai-row {
71
- height: 30px; }
75
+ height: 40px; }
72
76
 
73
77
  .controller-container {
74
78
  position: absolute;
75
- left: 400px;
79
+ left: 550px;
76
80
  top: 0px;
77
81
  padding: 8px; }
78
82
 
@@ -1,10 +1,10 @@
1
1
  /* These sizes should be even numbers to get good rendering when they are rotated. */
2
2
  /* Original size: 33x59 */
3
- $pai-width: 16px;
4
- $pai-height: 30px;
3
+ $pai-width: 22px;
4
+ $pai-height: 40px;
5
5
  /* Original size: 44x49 */
6
- $laid-pai-width: 22px;
7
- $laid-pai-height: 26px;
6
+ $laid-pai-width: 30px;
7
+ $laid-pai-height: 32px;
8
8
 
9
9
  $ho-tehai-margin: $pai-height;
10
10
  $board-width: $pai-width * 25;
@@ -187,9 +187,8 @@ loadAction = (action) ->
187
187
  null
188
188
  when "dora"
189
189
  board.doraMarkers = board.doraMarkers.concat([action.dora_marker])
190
- when "log"
191
- if kyoku
192
- kyoku.actions[kyoku.actions.length - 1].log = action.text
190
+ when "error"
191
+ null
193
192
  else
194
193
  throw "unknown action: #{action.type}"
195
194
 
@@ -201,10 +200,9 @@ loadAction = (action) ->
201
200
  for i in [0...4]
202
201
  if action.actor != undefined && i != action.actor
203
202
  ripai(board.players[i])
204
- if action.type != "log"
205
- action.board = board
206
- #dumpBoard(board)
207
- kyoku.actions.push(action)
203
+ action.board = board
204
+ #dumpBoard(board)
205
+ kyoku.actions.push(action)
208
206
 
209
207
  deleteTehai = (player, pai) ->
210
208
  player.tehais = player.tehais.concat([])
@@ -266,10 +264,10 @@ renderAction = (action) ->
266
264
  #console.log(action.type, action)
267
265
  displayAction = {}
268
266
  for k, v of action
269
- if k != "board" && k != "log"
267
+ if k != "board" && k != "logs"
270
268
  displayAction[k] = v
271
269
  $("#action-label").text(JSON.stringify(displayAction))
272
- $("#log-label").text(action.log || "")
270
+ $("#log-label").text((action.logs && action.logs[currentViewpoint]) || "")
273
271
  #dumpBoard(action.board)
274
272
  kyoku = getCurrentKyoku()
275
273
  for i in [0...4]