software_challenge_client 19.1.0 → 20.2.0
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.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/.stickler.yml +7 -0
- data/RELEASES.md +4 -0
- data/example/client.rb +12 -1
- data/lib/software_challenge_client.rb +6 -4
- data/lib/software_challenge_client/board.rb +59 -40
- data/lib/software_challenge_client/cube_coordinates.rb +23 -0
- data/lib/software_challenge_client/direction.rb +23 -21
- data/lib/software_challenge_client/drag_move.rb +19 -0
- data/lib/software_challenge_client/field.rb +58 -21
- data/lib/software_challenge_client/game_rule_logic.rb +355 -198
- data/lib/software_challenge_client/game_state.rb +44 -15
- data/lib/software_challenge_client/has_hints.rb +11 -0
- data/lib/software_challenge_client/invalid_move_exception.rb +2 -2
- data/lib/software_challenge_client/network.rb +0 -2
- data/lib/software_challenge_client/piece.rb +31 -0
- data/lib/software_challenge_client/piece_type.rb +18 -0
- data/lib/software_challenge_client/player_color.rb +6 -16
- data/lib/software_challenge_client/protocol.rb +86 -18
- data/lib/software_challenge_client/set_move.rb +15 -0
- data/lib/software_challenge_client/skip_move.rb +8 -0
- data/lib/software_challenge_client/util/constants.rb +4 -2
- data/lib/software_challenge_client/version.rb +1 -1
- metadata +11 -9
- data/lib/software_challenge_client/coordinates.rb +0 -17
- data/lib/software_challenge_client/field_type.rb +0 -30
- data/lib/software_challenge_client/line.rb +0 -126
- data/lib/software_challenge_client/move.rb +0 -84
@@ -4,9 +4,7 @@
|
|
4
4
|
require_relative './util/constants'
|
5
5
|
require_relative 'player'
|
6
6
|
require_relative 'board'
|
7
|
-
require_relative 'move'
|
8
7
|
require_relative 'condition'
|
9
|
-
require_relative 'field_type'
|
10
8
|
|
11
9
|
# Ein Spielzustand. Wird vom Server an die Computerspieler übermittelt und
|
12
10
|
# enthält alles, was der Computerspieler wissen muss, um einen Zug zu machen.
|
@@ -25,6 +23,15 @@ class GameState
|
|
25
23
|
# @return [PlayerColor] Die Farbe des Spielers, der den nächsten Zug machen
|
26
24
|
# darf, der also gerade an der Reihe ist.
|
27
25
|
attr_accessor :current_player_color
|
26
|
+
|
27
|
+
# @!attribute [r] undeployed_red_pieces
|
28
|
+
# @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
|
29
|
+
attr_accessor :undeployed_red_pieces
|
30
|
+
|
31
|
+
# @!attribute [r] undeployed_blue_pieces
|
32
|
+
# @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
|
33
|
+
attr_accessor :undeployed_blue_pieces
|
34
|
+
|
28
35
|
# @!attribute [r] red
|
29
36
|
# @return [Player] Der rote Spieler
|
30
37
|
attr_reader :red
|
@@ -47,13 +54,30 @@ class GameState
|
|
47
54
|
def field(x, y)
|
48
55
|
board.field(x, y)
|
49
56
|
end
|
57
|
+
def self.parse_pieces_string(string, color)
|
58
|
+
string.chars.map do |c|
|
59
|
+
case c
|
60
|
+
when 'Q'
|
61
|
+
Piece.new(color, PieceType::BEE)
|
62
|
+
when 'S'
|
63
|
+
Piece.new(color, PieceType::SPIDER)
|
64
|
+
when 'G'
|
65
|
+
Piece.new(color, PieceType::GRASSHOPPER)
|
66
|
+
when 'B'
|
67
|
+
Piece.new(color, PieceType::BEETLE)
|
68
|
+
when 'A'
|
69
|
+
Piece.new(color, PieceType::ANT)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
50
73
|
|
51
|
-
# Erstellt einen neuen Spielzustand.
|
52
74
|
def initialize
|
53
75
|
@current_player_color = PlayerColor::RED
|
54
76
|
@start_player_color = PlayerColor::RED
|
55
77
|
@board = Board.new
|
56
78
|
@turn = 0
|
79
|
+
@undeployed_red_pieces = GameState.parse_pieces_string(Constants::STARTING_PIECES, PlayerColor::RED)
|
80
|
+
@undeployed_blue_pieces = GameState.parse_pieces_string(Constants::STARTING_PIECES, PlayerColor::BLUE)
|
57
81
|
end
|
58
82
|
|
59
83
|
# Fügt einen Spieler zum Spielzustand hinzu.
|
@@ -89,6 +113,19 @@ class GameState
|
|
89
113
|
turn / 2
|
90
114
|
end
|
91
115
|
|
116
|
+
def undeployed_pieces(color)
|
117
|
+
case color
|
118
|
+
when PlayerColor::RED
|
119
|
+
undeployed_red_pieces
|
120
|
+
when PlayerColor::BLUE
|
121
|
+
undeployed_blue_pieces
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def deployed_pieces(color)
|
126
|
+
board.deployed_pieces(color)
|
127
|
+
end
|
128
|
+
|
92
129
|
# Führt einen Zug auf dem Spielzustand aus. Das Spielbrett wird entsprechend
|
93
130
|
# modifiziert.
|
94
131
|
#
|
@@ -122,7 +159,8 @@ class GameState
|
|
122
159
|
# @return [Integer] Die Punkte des Spielers, entspricht der Anzahl der Fische
|
123
160
|
# im größten Schwarm des Spielers.
|
124
161
|
def points_for_player(player)
|
125
|
-
|
162
|
+
# TODO
|
163
|
+
-1
|
126
164
|
end
|
127
165
|
|
128
166
|
def ==(other)
|
@@ -139,7 +177,7 @@ class GameState
|
|
139
177
|
# Erzeugt eine Kopie des Spielzustandes. Änderungen an dieser Kopie
|
140
178
|
# beeinflussen den originalen Spielzustand nicht. Die Kopie kann also zum
|
141
179
|
# testen von Spielzügen genutzt werden.
|
142
|
-
def
|
180
|
+
def clone
|
143
181
|
Marshal.load(Marshal.dump(self))
|
144
182
|
end
|
145
183
|
|
@@ -150,16 +188,7 @@ class GameState
|
|
150
188
|
|
151
189
|
# @return [Array<Field>] Alle Felder mit Fischen des Spielers, der gerade an der Reihe ist.
|
152
190
|
def own_fields
|
153
|
-
board.
|
154
|
-
PlayerColor.field_type(current_player_color)
|
155
|
-
)
|
156
|
-
end
|
157
|
-
|
158
|
-
# @return [Array<Move>] Alle regelkonformen Züge, die der aktuelle Spieler gerade machen könnte.
|
159
|
-
def possible_moves
|
160
|
-
own_fields.map do |f|
|
161
|
-
GameRuleLogic.possible_moves(board, f, current_player_color)
|
162
|
-
end.flatten
|
191
|
+
board.fields_of_color(current_player_color)
|
163
192
|
end
|
164
193
|
|
165
194
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
# Exception, die geworfen wird, wenn ein ungültiger Zug ausgeführt wird.
|
4
|
-
# @see
|
4
|
+
# @see GameRuleLogic#perform_move
|
5
5
|
class InvalidMoveException < StandardError
|
6
6
|
def initialize(msg, move)
|
7
7
|
@move = move
|
8
8
|
super(msg)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def to_s
|
12
12
|
"#{super}: #{@move}"
|
13
13
|
end
|
14
14
|
end
|
@@ -68,14 +68,12 @@ class Network
|
|
68
68
|
sock_msg = ''
|
69
69
|
|
70
70
|
line = ''
|
71
|
-
logger.debug 'reading'
|
72
71
|
@socket.each_char do |char|
|
73
72
|
line += char
|
74
73
|
sock_msg += char
|
75
74
|
line = '' if ['\n', ' '].include? char
|
76
75
|
break if ['</room>', '</protocol>'].include? line
|
77
76
|
end
|
78
|
-
logger.debug 'ended reading'
|
79
77
|
if sock_msg != ''
|
80
78
|
@receive_buffer.concat(sock_msg)
|
81
79
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Piece
|
2
|
+
|
3
|
+
# @!attribute [r] type
|
4
|
+
# @return [PieceType]
|
5
|
+
attr_reader :type
|
6
|
+
|
7
|
+
# @!attribute [r] color
|
8
|
+
# @return [PlayerColor]
|
9
|
+
attr_reader :color
|
10
|
+
|
11
|
+
def initialize(color, type)
|
12
|
+
@type = type
|
13
|
+
@color = color
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
type == other.type && color == other.color
|
18
|
+
end
|
19
|
+
|
20
|
+
def owner
|
21
|
+
color
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
color.value + type.value
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
to_s
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'typesafe_enum'
|
4
|
+
# Der Typ eines Spielsteins. Es gibt folgende Typen:
|
5
|
+
# - BEE
|
6
|
+
# - BEETLE
|
7
|
+
# - GRASSHOPPER
|
8
|
+
# - SPIDER
|
9
|
+
# - ANT
|
10
|
+
#
|
11
|
+
# Zugriff z.B. mit PieceType::BEE
|
12
|
+
class PieceType < TypesafeEnum::Base
|
13
|
+
new :BEE, 'Q'
|
14
|
+
new :BEETLE, 'B'
|
15
|
+
new :GRASSHOPPER, 'G'
|
16
|
+
new :SPIDER, 'S'
|
17
|
+
new :ANT, 'A'
|
18
|
+
end
|
@@ -2,11 +2,10 @@
|
|
2
2
|
# player color constants
|
3
3
|
require 'typesafe_enum'
|
4
4
|
|
5
|
-
# Die Spielerfarben. RED
|
5
|
+
# Die Spielerfarben. RED oder BLUE
|
6
6
|
class PlayerColor < TypesafeEnum::Base
|
7
|
-
new :
|
8
|
-
new :
|
9
|
-
new :BLUE
|
7
|
+
new :RED, 'R'
|
8
|
+
new :BLUE, 'B'
|
10
9
|
|
11
10
|
# @param color [PlayerColor]
|
12
11
|
# @return [PlayerColor] Farbe des Gegenspielers
|
@@ -16,20 +15,11 @@ class PlayerColor < TypesafeEnum::Base
|
|
16
15
|
PlayerColor::BLUE
|
17
16
|
when PlayerColor::BLUE
|
18
17
|
PlayerColor::RED
|
19
|
-
when PlayerColor::NONE
|
20
|
-
PlayerColor::NONE
|
21
18
|
end
|
22
19
|
end
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
# @see FieldType#player_color
|
27
|
-
def self.field_type(color)
|
28
|
-
case color
|
29
|
-
when PlayerColor::RED
|
30
|
-
FieldType::RED
|
31
|
-
when PlayerColor::BLUE
|
32
|
-
FieldType::BLUE
|
33
|
-
end
|
21
|
+
def opponent
|
22
|
+
PlayerColor.opponent_color(self)
|
34
23
|
end
|
24
|
+
|
35
25
|
end
|
@@ -2,7 +2,9 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
require 'socket'
|
4
4
|
require_relative 'board'
|
5
|
-
require_relative '
|
5
|
+
require_relative 'set_move'
|
6
|
+
require_relative 'drag_move'
|
7
|
+
require_relative 'skip_move'
|
6
8
|
require_relative 'player'
|
7
9
|
require_relative 'network'
|
8
10
|
require_relative 'client_interface'
|
@@ -40,7 +42,12 @@ class Protocol
|
|
40
42
|
# @param text [String] the xml-string that will be parsed
|
41
43
|
def process_string(text)
|
42
44
|
logger.debug "Parse XML:\n#{text}\n----END XML"
|
43
|
-
|
45
|
+
begin
|
46
|
+
REXML::Document.parse_stream(text, self)
|
47
|
+
rescue REXML::ParseException => e
|
48
|
+
# to parse incomplete xml, ignore missing end tag exceptions
|
49
|
+
raise e unless e.message =~ /Missing end tag/
|
50
|
+
end
|
44
51
|
end
|
45
52
|
|
46
53
|
|
@@ -113,29 +120,68 @@ class Protocol
|
|
113
120
|
when 'board'
|
114
121
|
logger.debug 'new board'
|
115
122
|
@gamestate.board = Board.new
|
116
|
-
@context[:current_tile_index] = nil
|
117
|
-
@context[:current_tile_direction] = nil
|
118
123
|
when 'field'
|
119
|
-
type = FieldType.find_by_key(attrs['state'].to_sym)
|
120
124
|
x = attrs['x'].to_i
|
121
125
|
y = attrs['y'].to_i
|
122
|
-
|
123
|
-
|
126
|
+
obstructed = attrs['isObstructed'] == 'true'
|
127
|
+
field = Field.new(x, y, [], obstructed)
|
128
|
+
@gamestate.board.add_field(field)
|
129
|
+
@context[:piece_target] = :field
|
130
|
+
@context[:field] = field
|
131
|
+
when 'piece'
|
132
|
+
owner = PlayerColor.find_by_key(attrs['owner'].to_sym)
|
133
|
+
type = PieceType.find_by_key(attrs['type'].to_sym)
|
134
|
+
piece = Piece.new(owner, type)
|
135
|
+
case @context[:piece_target]
|
136
|
+
when :field
|
137
|
+
@context[:field].add_piece(piece)
|
138
|
+
when :undeployed_red_pieces
|
139
|
+
@gamestate.undeployed_red_pieces << piece
|
140
|
+
when :undeployed_blue_pieces
|
141
|
+
@gamestate.undeployed_blue_pieces << piece
|
142
|
+
when :last_move
|
143
|
+
@context[:last_move_piece] = piece
|
144
|
+
else
|
145
|
+
raise "unknown piece target #{@context[:piece_target]}"
|
146
|
+
end
|
147
|
+
when 'undeployedRedPieces'
|
148
|
+
@context[:piece_target] = :undeployed_red_pieces
|
149
|
+
@gamestate.undeployed_red_pieces = []
|
150
|
+
when 'undeployedBluePieces'
|
151
|
+
@context[:piece_target] = :undeployed_blue_pieces
|
152
|
+
@gamestate.undeployed_blue_pieces = []
|
124
153
|
when 'lastMove'
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
154
|
+
type = attrs['class']
|
155
|
+
if type == 'skipmove'
|
156
|
+
@gamestate.last_move = SkipMove.new
|
157
|
+
else
|
158
|
+
@context[:last_move_type] = type
|
159
|
+
@context[:piece_target] = :last_move
|
160
|
+
end
|
161
|
+
when 'start'
|
162
|
+
@context[:last_move_start] = CubeCoordinates.new(attrs['x'].to_i, attrs['y'].to_i, attrs['z'].to_i)
|
163
|
+
when 'destination'
|
164
|
+
destination = CubeCoordinates.new(attrs['x'].to_i, attrs['y'].to_i, attrs['z'].to_i)
|
165
|
+
case @context[:last_move_type]
|
166
|
+
when 'setmove'
|
167
|
+
@gamestate.last_move = SetMove.new(@context[:last_move_piece], destination)
|
168
|
+
when 'dragmove'
|
169
|
+
@gamestate.last_move = SetMove.new(@context[:last_move_start], destination)
|
170
|
+
end
|
130
171
|
when 'winner'
|
131
|
-
|
132
|
-
|
172
|
+
# TODO
|
173
|
+
#winning_player = parsePlayer(attrs)
|
174
|
+
#@gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
|
133
175
|
when 'score'
|
176
|
+
# TODO
|
134
177
|
# there are two score tags in the result, but reason attribute should be equal on both
|
135
|
-
|
178
|
+
#@gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
|
136
179
|
when 'left'
|
137
180
|
logger.debug 'got left event, terminating'
|
138
181
|
@network.disconnect
|
182
|
+
when 'sc.protocol.responses.CloseConnection'
|
183
|
+
logger.debug 'got left close connection event, terminating'
|
184
|
+
@network.disconnect
|
139
185
|
end
|
140
186
|
end
|
141
187
|
|
@@ -180,9 +226,31 @@ class Protocol
|
|
180
226
|
# class interface to supply a method which returns the XML
|
181
227
|
# because XML-generation should be decoupled from internal data
|
182
228
|
# structures.
|
183
|
-
|
184
|
-
|
185
|
-
|
229
|
+
case move
|
230
|
+
when SetMove
|
231
|
+
builder.data(class: 'setmove') do |data|
|
232
|
+
data.piece(owner: move.piece.owner.key, type: move.piece.type.key)
|
233
|
+
d = move.destination
|
234
|
+
data.destination(x: d.x, y: d.y, z: d.z)
|
235
|
+
move.hints.each do |hint|
|
236
|
+
data.hint(content: hint.content)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
when DragMove
|
240
|
+
builder.data(class: 'dragmove') do |data|
|
241
|
+
s = move.start
|
242
|
+
data.start(x: s.x, y: s.y, z: s.z)
|
243
|
+
d = move.destination
|
244
|
+
data.destination(x: d.x, y: d.y, z: d.z)
|
245
|
+
move.hints.each do |hint|
|
246
|
+
data.hint(content: hint.content)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
when SkipMove
|
250
|
+
builder.data(class: 'skipmove') do |data|
|
251
|
+
move.hints.each do |hint|
|
252
|
+
data.hint(content: hint.content)
|
253
|
+
end
|
186
254
|
end
|
187
255
|
end
|
188
256
|
builder.target!
|
@@ -4,6 +4,8 @@
|
|
4
4
|
# Konstanten zum aktuellen Spiel.
|
5
5
|
module Constants
|
6
6
|
ROUND_LIMIT = 30 # Rundenbegrenzung. Nach Ende der angegebenen Runde endet auch das Spiel.
|
7
|
-
|
8
|
-
|
7
|
+
GAME_IDENTIFIER = 'swc_2020_hive' # Der Identifikator des Spiels. Für die Kommunikation mit dem Spielserver.
|
8
|
+
STARTING_PIECES = 'QSSSGGBBAAA'
|
9
|
+
BOARD_SIZE = 11
|
10
|
+
SHIFT = ((BOARD_SIZE - 1) / 2)
|
9
11
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: software_challenge_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 20.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 'kwollw '
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2019-11-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: typesafe_enum
|
@@ -249,6 +249,7 @@ files:
|
|
249
249
|
- ".rspec"
|
250
250
|
- ".rubocop.yml"
|
251
251
|
- ".ruby-version"
|
252
|
+
- ".stickler.yml"
|
252
253
|
- AUTHORS
|
253
254
|
- CODE_OF_CONDUCT.md
|
254
255
|
- Dockerfile
|
@@ -268,23 +269,26 @@ files:
|
|
268
269
|
- lib/software_challenge_client/board.rb
|
269
270
|
- lib/software_challenge_client/client_interface.rb
|
270
271
|
- lib/software_challenge_client/condition.rb
|
271
|
-
- lib/software_challenge_client/
|
272
|
+
- lib/software_challenge_client/cube_coordinates.rb
|
272
273
|
- lib/software_challenge_client/debug_hint.rb
|
273
274
|
- lib/software_challenge_client/direction.rb
|
275
|
+
- lib/software_challenge_client/drag_move.rb
|
274
276
|
- lib/software_challenge_client/field.rb
|
275
|
-
- lib/software_challenge_client/field_type.rb
|
276
277
|
- lib/software_challenge_client/game_rule_logic.rb
|
277
278
|
- lib/software_challenge_client/game_state.rb
|
279
|
+
- lib/software_challenge_client/has_hints.rb
|
278
280
|
- lib/software_challenge_client/invalid_move_exception.rb
|
279
|
-
- lib/software_challenge_client/line.rb
|
280
281
|
- lib/software_challenge_client/line_direction.rb
|
281
282
|
- lib/software_challenge_client/logging.rb
|
282
|
-
- lib/software_challenge_client/move.rb
|
283
283
|
- lib/software_challenge_client/network.rb
|
284
|
+
- lib/software_challenge_client/piece.rb
|
285
|
+
- lib/software_challenge_client/piece_type.rb
|
284
286
|
- lib/software_challenge_client/player.rb
|
285
287
|
- lib/software_challenge_client/player_color.rb
|
286
288
|
- lib/software_challenge_client/protocol.rb
|
287
289
|
- lib/software_challenge_client/runner.rb
|
290
|
+
- lib/software_challenge_client/set_move.rb
|
291
|
+
- lib/software_challenge_client/skip_move.rb
|
288
292
|
- lib/software_challenge_client/util/constants.rb
|
289
293
|
- lib/software_challenge_client/version.rb
|
290
294
|
- release.sh
|
@@ -307,11 +311,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
307
311
|
- !ruby/object:Gem::Version
|
308
312
|
version: '0'
|
309
313
|
requirements: []
|
310
|
-
|
311
|
-
rubygems_version: 2.6.11
|
314
|
+
rubygems_version: 3.0.3
|
312
315
|
signing_key:
|
313
316
|
specification_version: 4
|
314
317
|
summary: This gem provides functions to build a client for the coding competition
|
315
318
|
Software-Challenge 2017.
|
316
319
|
test_files: []
|
317
|
-
has_rdoc:
|