software_challenge_client 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/README.md +45 -0
- data/RELEASES.md +3 -0
- data/Rakefile +7 -0
- data/example/client.rb +30 -0
- data/example/main.rb +36 -0
- data/lib/software_challenge_client.rb +17 -0
- data/lib/software_challenge_client/board.rb +315 -0
- data/lib/software_challenge_client/client_interface.rb +7 -0
- data/lib/software_challenge_client/condition.rb +21 -0
- data/lib/software_challenge_client/connection.rb +49 -0
- data/lib/software_challenge_client/debug_hint.rb +33 -0
- data/lib/software_challenge_client/field.rb +42 -0
- data/lib/software_challenge_client/field_type.rb +8 -0
- data/lib/software_challenge_client/game_state.rb +343 -0
- data/lib/software_challenge_client/move.rb +58 -0
- data/lib/software_challenge_client/network.rb +146 -0
- data/lib/software_challenge_client/player.rb +24 -0
- data/lib/software_challenge_client/player_color.rb +23 -0
- data/lib/software_challenge_client/protocol.rb +149 -0
- data/lib/software_challenge_client/runner.rb +33 -0
- data/lib/software_challenge_client/util/constants.rb +5 -0
- data/lib/software_challenge_client/version.rb +3 -0
- data/software_challenge_client.gemspec +24 -0
- metadata +116 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'debug_hint'
|
2
|
+
|
3
|
+
# @author Ralf-Tobias Diekert
|
4
|
+
# A move, that can be performed in twixt
|
5
|
+
class Move
|
6
|
+
# @!attribute [r] x
|
7
|
+
# @return [Integer] x-coordinate
|
8
|
+
attr_reader :x
|
9
|
+
# @!attribute [r] y
|
10
|
+
# @return [Integer] y-coordinate
|
11
|
+
attr_reader :y
|
12
|
+
# @!attribute [r] hints
|
13
|
+
# @return [Array<DebugHint>] the move's hints
|
14
|
+
attr_reader :hints
|
15
|
+
|
16
|
+
# Initializer
|
17
|
+
#
|
18
|
+
# @param x [Integer] x-coordinate
|
19
|
+
# @param y [Integer] y-coordinate
|
20
|
+
def initialize(x, y)
|
21
|
+
@x = x
|
22
|
+
@y = y
|
23
|
+
@hints = Array.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# @overload addHint(hint)
|
27
|
+
# adds a hint to the move
|
28
|
+
# @param hint [DebugHint] the added hint
|
29
|
+
# @overload addHint(key, value)
|
30
|
+
# adds a hint to the move
|
31
|
+
# @param key the added hint's key
|
32
|
+
# @param value the added hint's value
|
33
|
+
# @overload addHint(string)
|
34
|
+
# adds a hint to the move
|
35
|
+
# @param hint [String] the added hint's content
|
36
|
+
def addHint(hint)
|
37
|
+
@hints.push(hint);
|
38
|
+
end
|
39
|
+
|
40
|
+
# adds a hint to the move
|
41
|
+
def addHint(key, value)
|
42
|
+
self.addHint(DebugHint.new(key, value))
|
43
|
+
end
|
44
|
+
|
45
|
+
# adds a hint to the move
|
46
|
+
def addHint(string)
|
47
|
+
self.addHint(DebugHint.new(string))
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(another_move)
|
51
|
+
return self.x == another_move.x && self.y == another_move.y
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
return "Move:(#{self.x},#{self.y})"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'protocol'
|
3
|
+
require_relative 'board'
|
4
|
+
require_relative 'client_interface'
|
5
|
+
require 'rexml/document'
|
6
|
+
require 'rexml/element'
|
7
|
+
|
8
|
+
# @author Ralf-Tobias Diekert
|
9
|
+
# This class handles the socket connection to the server
|
10
|
+
class Network
|
11
|
+
@socket
|
12
|
+
@host
|
13
|
+
@port
|
14
|
+
|
15
|
+
@board
|
16
|
+
@client
|
17
|
+
@protocol
|
18
|
+
|
19
|
+
@receiveBuffer
|
20
|
+
@reservationID
|
21
|
+
|
22
|
+
# @!attribute [r] connected
|
23
|
+
# @return [Boolean] true, if the client is connected to a server
|
24
|
+
attr_reader :connected
|
25
|
+
|
26
|
+
def initialize(host, port, board, client)
|
27
|
+
@host, @port, @connected, @board, @client =
|
28
|
+
host, port, false, board, client
|
29
|
+
|
30
|
+
@protocol = Protocol.new(self, @client)
|
31
|
+
@reservationID = ''
|
32
|
+
@receiveBuffer = ''
|
33
|
+
|
34
|
+
puts '> Network/Socket created.'
|
35
|
+
end
|
36
|
+
|
37
|
+
# connects the client with a given server
|
38
|
+
#
|
39
|
+
# @return [Boolean] true, if successfully connected to the server
|
40
|
+
def connect
|
41
|
+
@socket = TCPSocket.open(@host, @port)
|
42
|
+
@connected = true
|
43
|
+
|
44
|
+
self.sendString('<protocol>')
|
45
|
+
if @reservationID != ''
|
46
|
+
document = REXML::Docuent.new
|
47
|
+
element = REXML::Element.new('joinPrepared')
|
48
|
+
element.add_attribute('reservationCode', @reservationID)
|
49
|
+
document.add(element)
|
50
|
+
self.sendXML(document)
|
51
|
+
else
|
52
|
+
document = REXML::Document.new
|
53
|
+
element = REXML::Element.new('join')
|
54
|
+
element.add_attribute('gameType', 'swc_2016_twixt')
|
55
|
+
document.add(element)
|
56
|
+
self.sendXML(document)
|
57
|
+
end
|
58
|
+
return @connected
|
59
|
+
end
|
60
|
+
|
61
|
+
# disconnects the client from a server
|
62
|
+
def disconnect
|
63
|
+
|
64
|
+
if @connected
|
65
|
+
sendString("</protocol>")
|
66
|
+
@connected = false
|
67
|
+
@socket.close
|
68
|
+
end
|
69
|
+
puts '> Disconnected.'
|
70
|
+
end
|
71
|
+
|
72
|
+
# reads from the socket until "</room>" is read
|
73
|
+
def readString
|
74
|
+
puts 'reading'
|
75
|
+
sockMsg = ''
|
76
|
+
if(!@connected)
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
line =''
|
81
|
+
char = ''
|
82
|
+
while line!="</room>"
|
83
|
+
char = @socket.getc
|
84
|
+
line+=char
|
85
|
+
if char=='\n' || char==' '
|
86
|
+
|
87
|
+
line = ''
|
88
|
+
end
|
89
|
+
sockMsg += char
|
90
|
+
end
|
91
|
+
puts 'ended reading'
|
92
|
+
if sockMsg != ''
|
93
|
+
|
94
|
+
@receiveBuffer.concat(sockMsg)
|
95
|
+
|
96
|
+
# Remove <protocol> tag
|
97
|
+
@receiveBuffer = @receiveBuffer.gsub('<protocol>', '')
|
98
|
+
|
99
|
+
puts 'Receive:'
|
100
|
+
puts ''
|
101
|
+
#puts @receiveBuffer
|
102
|
+
|
103
|
+
# Process text
|
104
|
+
@protocol.processString('<msg>'+@receiveBuffer+'</msg>');
|
105
|
+
self.emptyReceiveBuffer
|
106
|
+
end
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
|
110
|
+
# empties the receive buffer
|
111
|
+
def emptyReceiveBuffer
|
112
|
+
@receiveBuffer = ''
|
113
|
+
end
|
114
|
+
|
115
|
+
# processes an incomming message
|
116
|
+
#
|
117
|
+
# @return [Boolean] true, if the processing of a incomming message was successfull
|
118
|
+
def processMessages
|
119
|
+
if !@connected
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
return self.readString
|
123
|
+
end
|
124
|
+
|
125
|
+
# sends a string to the socket
|
126
|
+
#
|
127
|
+
# @param s [String] the message, to be sent
|
128
|
+
def sendString(s)
|
129
|
+
if(@connected)
|
130
|
+
@socket.print(s);
|
131
|
+
puts 'Send:'
|
132
|
+
puts ''
|
133
|
+
puts(s);
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# sends a xml Document to the buffer
|
138
|
+
#
|
139
|
+
# @param xml [REXML::Docuent] the Document, that will be sent
|
140
|
+
def sendXML(xml)
|
141
|
+
text = ''
|
142
|
+
xml.write(text)
|
143
|
+
self.sendString(text);
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'player_color'
|
2
|
+
|
3
|
+
# @author Ralf-Tobias Diekert
|
4
|
+
# A player, participating at a game
|
5
|
+
class Player
|
6
|
+
# @!attribute [r] color
|
7
|
+
# @return [PlayerColor] the player's color
|
8
|
+
attr_reader :color
|
9
|
+
# @!attribute [rw] points
|
10
|
+
# @return [Integer] the player's points
|
11
|
+
attr_accessor :points
|
12
|
+
|
13
|
+
# Initializer
|
14
|
+
# @param the new player's color
|
15
|
+
def initialize(color)
|
16
|
+
@color = color
|
17
|
+
self.points = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(another_player)
|
21
|
+
return self.color == another_player.color
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# @author Ralf-Tobias Diekert
|
2
|
+
#player color constants
|
3
|
+
module PlayerColor
|
4
|
+
NONE = 1
|
5
|
+
RED = 2
|
6
|
+
BLUE = 4
|
7
|
+
|
8
|
+
# Returns the opponents Color
|
9
|
+
#
|
10
|
+
# @param color [PlayerColor] The player's color, whose opponent needs to be found
|
11
|
+
# @return [PlayerColor] the opponent's color
|
12
|
+
def self.opponentColor(color)
|
13
|
+
if color == PlayerColor::RED
|
14
|
+
return PlayerColor::BLUE
|
15
|
+
end
|
16
|
+
if color == PlayerColor::BLUE
|
17
|
+
return PlayerColor::RED
|
18
|
+
end
|
19
|
+
if color == PlayerColor::NONE
|
20
|
+
return PlayerColor::NONE
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'board'
|
3
|
+
require_relative 'move'
|
4
|
+
require_relative 'player'
|
5
|
+
require_relative 'network'
|
6
|
+
require_relative 'connection'
|
7
|
+
require_relative 'client_interface'
|
8
|
+
require 'rexml/document'
|
9
|
+
require 'rexml/streamlistener'
|
10
|
+
|
11
|
+
# @author Ralf-Tobias Diekert
|
12
|
+
# This class handles the parsing of xml strings according to the network protocol of twixt
|
13
|
+
class Protocol
|
14
|
+
include REXML::StreamListener
|
15
|
+
|
16
|
+
# @!attribute [r] gamestate
|
17
|
+
# @return [Gamestate] current gamestate
|
18
|
+
attr_reader :gamestate
|
19
|
+
# @!attribute [rw] roomID
|
20
|
+
# @return [String] current room id
|
21
|
+
attr_accessor :roomID
|
22
|
+
# @!attribute [r] client
|
23
|
+
# @return [ClientInterface] current client
|
24
|
+
attr_reader :client
|
25
|
+
@network
|
26
|
+
|
27
|
+
def initialize(network, client)
|
28
|
+
@gamestate = GameState.new
|
29
|
+
@network, @client = network, client
|
30
|
+
self.client.gamestate = self.gamestate
|
31
|
+
end
|
32
|
+
|
33
|
+
# starts xml-string parsing
|
34
|
+
#
|
35
|
+
# @param text [String] the xml-string that will be parsed
|
36
|
+
def processString(text)
|
37
|
+
list = self
|
38
|
+
#puts "Parse XML:\n#{text}\n----END XML"
|
39
|
+
REXML::Document.parse_stream(text, list)
|
40
|
+
end
|
41
|
+
|
42
|
+
# called if an end-tag is read
|
43
|
+
#
|
44
|
+
# @param name [String] the end-tag name, that was read
|
45
|
+
def tag_end(name)
|
46
|
+
case name
|
47
|
+
when "board"
|
48
|
+
puts @gamestate.board.to_s
|
49
|
+
when "condition"
|
50
|
+
puts "Game ended"
|
51
|
+
@network.disconnect
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# called if a start tag is read
|
56
|
+
# Depending on the tag the gamestate is updated
|
57
|
+
# or the client will be asked for a move
|
58
|
+
#
|
59
|
+
# @param name [String] the start-tag, that was read
|
60
|
+
# @param attrs [Dictionary<String, String>] Attributes attached to the tag
|
61
|
+
def tag_start(name, attrs)
|
62
|
+
case name
|
63
|
+
when "room"
|
64
|
+
@roomID = attrs['roomId']
|
65
|
+
puts "roomId : "+@roomID
|
66
|
+
when "data"
|
67
|
+
puts "data(class) : "+attrs['class']
|
68
|
+
if attrs['class'] == "sc.framework.plugins.protocol.MoveRequest"
|
69
|
+
@client.gamestate = self.gamestate
|
70
|
+
move = @client.getMove
|
71
|
+
document = REXML::Document.new
|
72
|
+
document.add_element('room',{'roomId' => @roomID})
|
73
|
+
data = REXML::Element.new('data')
|
74
|
+
data.add_attribute('class', 'move')
|
75
|
+
data.add_attribute('x', move.x)
|
76
|
+
data.add_attribute('y', move.y)
|
77
|
+
document.root.add_element(data)
|
78
|
+
for h in move.hints
|
79
|
+
hint = REXML::Element.new('hint')
|
80
|
+
hint.add_attribute('content', h.content)
|
81
|
+
document.root.elements['data'].elements << hint
|
82
|
+
end
|
83
|
+
self.sendXml(document)
|
84
|
+
end
|
85
|
+
if attrs['class'] == "error"
|
86
|
+
puts "Game ended - ERROR"
|
87
|
+
puts attrs['message']
|
88
|
+
@network.disconnect
|
89
|
+
end
|
90
|
+
when "state"
|
91
|
+
puts 'new gamestate'
|
92
|
+
@gamestate.turn = attrs['turn'].to_i
|
93
|
+
@gamestate.startPlayerColor = attrs['startPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
|
94
|
+
@gamestate.currentPlayerColor = attrs['currentPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
|
95
|
+
puts "Turn: #{@gamestate.turn}"
|
96
|
+
when "red"
|
97
|
+
puts 'new red player'
|
98
|
+
@gamestate.addPlayer(Player.new(attrs['color'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE))
|
99
|
+
@gamestate.red.points = attrs['points'].to_i
|
100
|
+
when "blue"
|
101
|
+
puts 'new blue player'
|
102
|
+
@gamestate.addPlayer(Player.new(attrs['color'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE))
|
103
|
+
@gamestate.blue.points = attrs['points'].to_i
|
104
|
+
when "board"
|
105
|
+
puts 'new board'
|
106
|
+
@gamestate.board = Board.new(true)
|
107
|
+
when "field"
|
108
|
+
type = FieldType::NORMAL
|
109
|
+
ownerColor = PlayerColor::NONE
|
110
|
+
case attrs['type']
|
111
|
+
when 'SWAMP'
|
112
|
+
type = FieldType::SWAMP
|
113
|
+
when 'RED'
|
114
|
+
type = FieldType::RED
|
115
|
+
when 'BLUE'
|
116
|
+
type = FieldType::BLUE
|
117
|
+
when "winner"
|
118
|
+
puts "Game ended"
|
119
|
+
@network.disconnect
|
120
|
+
end
|
121
|
+
|
122
|
+
case attrs['owner']
|
123
|
+
when 'RED'
|
124
|
+
ownerColor = PlayerColor::RED
|
125
|
+
when 'BLUE'
|
126
|
+
ownerColor = PlayerColor::BLUE
|
127
|
+
end
|
128
|
+
x = attrs['x'].to_i
|
129
|
+
y = attrs['y'].to_i
|
130
|
+
|
131
|
+
@gamestate.board.fields[x][y] = Field.new(type, x, y)
|
132
|
+
@gamestate.board.fields[x][y].ownerColor = ownerColor
|
133
|
+
when "connection"
|
134
|
+
@gamestate.board.connections.push(Connection.new(attrs['x1'].to_i, attrs['y1'].to_i, attrs['x2'].to_i, attrs['y2'].to_i, attrs['owner']))
|
135
|
+
when "lastMove"
|
136
|
+
@gamestate.lastMove = Move.new(attrs['x'], attrs['y'])
|
137
|
+
when "condition"
|
138
|
+
@gamestate.condition = Condition.new(attrs['winner'], attrs['reason'])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# send a xml document
|
143
|
+
#
|
144
|
+
# @param document [REXML::Document] the document, that will be send to the connected server
|
145
|
+
def sendXml(document)
|
146
|
+
@network.sendXML(document)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'board'
|
2
|
+
require_relative 'client_interface'
|
3
|
+
require_relative 'network'
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
attr_reader :network
|
7
|
+
|
8
|
+
def initialize(host, port, client)
|
9
|
+
puts 'Software Challenge 2015'
|
10
|
+
puts 'Ruby Client'
|
11
|
+
puts "Host: #{host}"
|
12
|
+
puts "Port: #{port}"
|
13
|
+
|
14
|
+
board = Board.new(true)
|
15
|
+
@network = Network.new(host, port, board, client)
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
self.network.connect
|
20
|
+
if self.network.connected == false
|
21
|
+
puts 'Not connected'
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
while self.network.connected
|
26
|
+
self.network.processMessages
|
27
|
+
sleep(0.01)
|
28
|
+
end
|
29
|
+
|
30
|
+
puts 'Program end...'
|
31
|
+
self.network.disconnect
|
32
|
+
end
|
33
|
+
end
|