mjai 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]