Class: Protocol

Inherits:
Object
  • Object
show all
Includes:
Logging, REXML::StreamListener
Defined in:
lib/software_challenge_client/protocol.rb

Overview

This class handles communication to the server over the XML communication protocol. Messages from the server are parsed and moves are serialized and send back.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger, logger

Constructor Details

#initialize(network, client) ⇒ Protocol

Returns a new instance of Protocol


30
31
32
33
34
35
36
# File 'lib/software_challenge_client/protocol.rb', line 30

def initialize(network, client)
  @gamestate = GameState.new
  @network = network
  @client = client
  @context = {} # for saving context when stream-parsing the XML
  @client.gamestate = @gamestate
end

Instance Attribute Details

#clientClientInterface (readonly)

Returns current client

Returns:


28
29
30
# File 'lib/software_challenge_client/protocol.rb', line 28

def client
  @client
end

#gamestateGamestate (readonly)

Returns current gamestate

Returns:

  • (Gamestate)

    current gamestate


22
23
24
# File 'lib/software_challenge_client/protocol.rb', line 22

def gamestate
  @gamestate
end

#roomIdString

Returns current room id

Returns:

  • (String)

    current room id


25
26
27
# File 'lib/software_challenge_client/protocol.rb', line 25

def roomId
  @roomId
end

Instance Method Details

#move_to_xml(move) ⇒ Object

Converts a move to XML for sending to the server.

Parameters:

  • move (Move)

    The move to convert to XML.


174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/software_challenge_client/protocol.rb', line 174

def move_to_xml(move)
  builder = Builder::XmlMarkup.new(indent: 2)
  # Converting every the move here instead of requiring the Move
  # class interface to supply a method which returns the XML
  # because XML-generation should be decoupled from internal data
  # structures.
  builder.data(class: 'move', x: move.x, y: move.y, direction: move.direction.key) do |data|
    move.hints.each do |hint|
      data.hint(content: hint.content)
    end
  end
  builder.target!
end

#parsePlayer(attributes) ⇒ Player

Converts XML attributes for a Player to a new Player object

Parameters:

  • attributes (Hash)

    Attributes for the new Player.

Returns:

  • (Player)

    The created Player object.


143
144
145
146
147
148
# File 'lib/software_challenge_client/protocol.rb', line 143

def parsePlayer(attributes)
  Player.new(
    PlayerColor.find_by_key(attributes['color'].to_sym),
    attributes['displayName']
  )
end

#process_string(text) ⇒ Object

starts xml-string parsing

Parameters:

  • text (String)

    the xml-string that will be parsed


41
42
43
44
# File 'lib/software_challenge_client/protocol.rb', line 41

def process_string(text)
  logger.debug "Parse XML:\n#{text}\n----END XML"
  REXML::Document.parse_stream(text, self)
end

#sendString(string) ⇒ Object

send a string

Parameters:

  • document (String)

    The string that will be send to the connected server.


160
161
162
# File 'lib/software_challenge_client/protocol.rb', line 160

def sendString(string)
  @network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
end

#sendXml(document) ⇒ Object

send a xml document

Parameters:

  • document (REXML::Document)

    the document, that will be send to the connected server


153
154
155
# File 'lib/software_challenge_client/protocol.rb', line 153

def sendXml(document)
  @network.sendXML(document)
end

#snake_case_to_lower_camel_case(string) ⇒ Object

converts “this_snake_case” to “thisSnakeCase”


165
166
167
168
169
# File 'lib/software_challenge_client/protocol.rb', line 165

def snake_case_to_lower_camel_case(string)
  string.split('_').inject([]) do |result, e|
    result + [result.empty? ? e : e.capitalize]
  end.join
end

#tag_end(name) ⇒ Object

called if an end-tag is read

Parameters:

  • name (String)

    the end-tag name, that was read


55
56
57
58
59
60
# File 'lib/software_challenge_client/protocol.rb', line 55

def tag_end(name)
  case name
  when 'board'
    logger.debug @gamestate.board.to_s
  end
end

#tag_start(name, attrs) ⇒ Object

called if a start tag is read Depending on the tag the gamestate is updated or the client will be asked for a move

Parameters:

  • name (String)

    the start-tag, that was read

  • attrs (Dictionary<String, String>)

    Attributes attached to the tag


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/software_challenge_client/protocol.rb', line 68

def tag_start(name, attrs)
  case name
  when 'room'
    @roomId = attrs['roomId']
    logger.info 'roomId : ' + @roomId
  when 'data'
    logger.debug "data(class) : #{attrs['class']}"
    @context[:data_class] = attrs['class']
    if attrs['class'] == 'sc.framework.plugins.protocol.MoveRequest'
      @client.gamestate = gamestate
      move = @client.move_requested
      sendString(move_to_xml(move))
    end
    if attrs['class'] == 'error'
      logger.info "Game ended - ERROR: #{attrs['message']}"
      @network.disconnect
    end
    if attrs['class'] == 'result'
      logger.info 'Got game result'
      @network.disconnect
    end
  when 'state'
    logger.debug 'new gamestate'
    @gamestate = GameState.new
    @gamestate.turn = attrs['turn'].to_i
    @gamestate.start_player_color = attrs['startPlayerColor'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
    @gamestate.current_player_color = attrs['currentPlayerColor'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
    logger.debug "Turn: #{@gamestate.turn}"
  when 'red'
    logger.debug 'new red player'
    player = parsePlayer(attrs)
    if player.color != PlayerColor::RED
      throw new IllegalArgumentException("expected #{PlayerColor::RED} Player but got #{player.color}")
    end
    @gamestate.add_player(player)
    @context[:player] = player
  when 'blue'
    logger.debug 'new blue player'
    player = parsePlayer(attrs)
    if player.color != PlayerColor::BLUE
      throw new IllegalArgumentException("expected #{PlayerColor::BLUE} Player but got #{player.color}")
    end
    @gamestate.add_player(player)
    @context[:player] = player
  when 'board'
    logger.debug 'new board'
    @gamestate.board = Board.new
    @context[:current_tile_index] = nil
    @context[:current_tile_direction] = nil
  when 'field'
    type = FieldType.find_by_key(attrs['state'].to_sym)
    x = attrs['x'].to_i
    y = attrs['y'].to_i
    raise "unexpected field type: #{attrs['type']}. Known types are #{FieldType.map { |t| t.key.to_s }}" if type.nil?
    @gamestate.board.add_field(Field.new(x, y, type))
  when 'lastMove'
    direction = Direction.find_by_key(attrs['direction'].to_sym)
    x = attrs['x'].to_i
    y = attrs['y'].to_i
    raise "unexpected direction: #{attrs['direction']}. Known directions are #{Direction.map { |d| d.key.to_s }}" if direction.nil?
    @gamestate.last_move = Move.new(x, y, direction)
  when 'winner'
    winning_player = parsePlayer(attrs)
    @gamestate.condition = Condition.new(winning_player)
    @context[:player] = winning_player
  when 'left'
    logger.debug 'got left event, terminating'
    @network.disconnect
  end
end

#text(text) ⇒ Object

called when text is encountered


48
49
50
# File 'lib/software_challenge_client/protocol.rb', line 48

def text(text)
  @context[:last_text] = text
end