Class: GameRuleLogic

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/software_challenge_client/game_rule_logic.rb

Overview

Methoden, welche die Spielregeln von Blokus abbilden.

Es gibt hier viele Helfermethoden, die von den beiden Hauptmethoden GameRuleLogic.valid_move? und GameRuleLogic.possible_moves benutzt werden.

Constant Summary collapse

SUM_MAX_SQUARES =
89

Constants included from Constants

Constants::BOARD_SIZE, Constants::GAME_IDENTIFIER, Constants::ROUND_LIMIT, Constants::TOTAL_PIECE_SHAPES

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.all_possible_setmoves(gamestate) ⇒ Object

Gib eine Liste aller möglichen Legezüge zurück, auch wenn es die erste Runde ist.



88
89
90
91
92
93
94
95
# File 'lib/software_challenge_client/game_rule_logic.rb', line 88

def self.all_possible_setmoves(gamestate)
  moves = []
  fields = valid_fields(gamestate)
  gamestate.undeployed_pieces(gamestate.current_color).each do |p|
    (moves << possible_moves_for_shape(gamestate, p, fields)).flatten
  end
  moves
end

.borders_on_color?(board, position, color) ⇒ Boolean

Überprüft, ob das gegebene Feld ein Nachbarfeld mit der Farbe [color] hat

Parameters:

  • board (Board)

    Das aktuelle Spielbrett

  • field (Field)

    Das zu überprüfende Feld

  • color (Color)

    Nach der zu suchenden Farbe

Returns:

  • (Boolean)


228
229
230
231
232
233
234
235
236
# File 'lib/software_challenge_client/game_rule_logic.rb', line 228

def self.borders_on_color?(board, position, color)
  [Coordinates.new(1, 0), Coordinates.new(0, 1), Coordinates.new(-1, 0), Coordinates.new(0, -1)].any? do |it|
    if board.in_bounds?(position + it)
      board[position + it].color == color
    else
      false
    end
  end
end

.corner?(position) ⇒ Boolean

Überprüft, ob die gegebene [position] an einer Ecke des Boards liegt.

Parameters:

  • position (Coordinates)

    Die zu überprüfenden Koordinaten

Returns:

  • (Boolean)


250
251
252
253
254
255
256
257
258
# File 'lib/software_challenge_client/game_rule_logic.rb', line 250

def self.corner?(position)
  corner = [
    Coordinates.new(0, 0),
    Coordinates.new(BOARD_SIZE - 1, 0),
    Coordinates.new(0, BOARD_SIZE - 1),
    Coordinates.new(BOARD_SIZE - 1, BOARD_SIZE - 1)
  ]
  corner.include? position
end

.corners_on_color?(board, position, color) ⇒ Boolean

Überprüft, ob das gegebene Feld ein diagonales Nachbarfeld mit der Farbe [color] hat

Parameters:

  • board (Board)

    Das aktuelle Spielbrett

  • field

    Das zu überprüfende [Field]

  • color

    Nach der zu suchenden [Color]

Returns:

  • (Boolean)


242
243
244
245
246
# File 'lib/software_challenge_client/game_rule_logic.rb', line 242

def self.corners_on_color?(board, position, color)
  [Coordinates.new(1, 1), Coordinates.new(1, -1), Coordinates.new(-1, -1), Coordinates.new(-1, 1)].any? do |it|
    board.in_bounds?(position + it) && board[position + it].color == color
  end
end

.get_points_from_undeployed(undeployed, mono_last = false) ⇒ Object

Berechne den Punktestand anhand der gegebenen [PieceShape]s.

Parameters:

  • undeployed

    eine Sammlung aller nicht gelegten [PieceShape]s

  • monoLast

    ob der letzte gelegte Stein das Monomino war

Returns:

  • die erreichte Punktezahl



302
303
304
305
306
307
308
309
310
311
312
# File 'lib/software_challenge_client/game_rule_logic.rb', line 302

def self.get_points_from_undeployed(undeployed, mono_last = false)
  # If all pieces were placed:
  if undeployed.empty?
    # Return sum of all squares plus 15 bonus points
    return SUM_MAX_SQUARES + 15 +
           # If the Monomino was the last placed piece, add another 5 points
           mono_last ? 5 : 0
  end
  # One point per block per piece placed
  SUM_MAX_SQUARES - undeployed.map(&:size).sum
end

.get_random_pentominoObject

Gibt einen zufälligen Pentomino zurück, welcher nicht ‘x` ist.



315
316
317
# File 'lib/software_challenge_client/game_rule_logic.rb', line 315

def self.get_random_pentomino
  PieceShape.map(&:value).select { |it| it.size == 5 && it != PieceShape::PENTO_X }
end

.moves_for_shape_on(color, shape, position) ⇒ Object

Helper method to calculate all transformations of one shape on one spot



77
78
79
80
81
82
83
84
85
# File 'lib/software_challenge_client/game_rule_logic.rb', line 77

def self.moves_for_shape_on(color, shape, position)
  moves = Set[]
  Rotation.each do |r|
    [true, false].each do |f|
      moves << SetMove.new(Piece.new(color, shape, r, f, position))
    end
  end
  moves
end

.neighbor_of_color?(board, field, color) ⇒ Boolean

Überprüft, ob das gegebene Feld ein Nachbarfeld mit der Farbe [color] hat

Parameters:

  • board

    Das aktuelle Board

  • field

    Das zu überprüfende Feld

  • color

    Nach der zu suchenden Farbe

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
# File 'lib/software_challenge_client/game_rule_logic.rb', line 144

def self.neighbor_of_color?(board, field, color)
  [Coordinates.new(field.x - 1, field.y),
   Coordinates.new(field.x, field.y - 1),
   Coordinates.new(field.x + 1, field.y),
   Coordinates.new(field.x, field.y + 1)].any? do |neighbor|
    Board.contains(neighbor) && board[neighbor].color == color
  end
end

.obstructed?(board, position) ⇒ Boolean

Überprüft, ob die gegebene [position] schon mit einer Farbe belegt wurde.

Parameters:

  • board (Board)

    Das aktuelle Spielbrett

  • position (Coordinates)

    Die zu überprüfenden Koordinaten

Returns:

  • (Boolean)


263
264
265
# File 'lib/software_challenge_client/game_rule_logic.rb', line 263

def self.obstructed?(board, position)
  !board[position].color.nil?
end

.perform_move(gamestate, move) ⇒ Object

Führe den gegebenen [Move] im gebenenen [GameState] aus.

Parameters:

  • gamestate (GameState)

    der aktuelle Spielstand

  • move

    der auszuführende Zug



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/software_challenge_client/game_rule_logic.rb', line 272

def self.perform_move(gamestate, move)
  raise 'Invalid move!' unless valid_move?(gamestate, move)

  if move.instance_of? SetMove
    gamestate.undeployed_pieces(move.piece.color).delete(move.piece)
    # gamestate.deployed_pieces(move.piece.color).add(move.piece)

    # Apply piece to board
    move.piece.coords.each do |coord|
      gamestate.board[coord].color = move.piece.color
    end

    # If it was the last piece for this color, remove it from the turn queue
    if gamestate.undeployed_pieces(move.piece.color).empty?
      gamestate.lastMoveMono += move.color to(move.piece.kind == PieceShape.MONO)
      gamestate.remove_active_color
    end
  end
  gamestate.turn += 1
  gamestate.round += 1
  gamestate.last_move = move
end

.possible_move(gamestate) ⇒ Object

Gibt einen zufälligen möglichen Zug zurück

Parameters:

  • gamestate (GameState)

    Der zu untersuchende Spielstand.



34
35
36
# File 'lib/software_challenge_client/game_rule_logic.rb', line 34

def self.possible_move(gamestate)
  possible_moves(gamestate).sample
end

.possible_moves(gamestate) ⇒ Object

Gibt alle möglichen Züge für den Spieler zurück, der in der gamestate dran ist. Diese ist die wichtigste Methode dieser Klasse für Schüler.

Parameters:

  • gamestate (GameState)

    Der zu untersuchende Spielstand.



24
25
26
27
28
29
30
# File 'lib/software_challenge_client/game_rule_logic.rb', line 24

def self.possible_moves(gamestate)
  re = possible_setmoves(gamestate)

  re << SkipMove.new unless gamestate.is_first_move?

  re
end

.possible_moves_for_shape(gamestate, shape, fields = valid_fields(gamestate)) ⇒ Object

Gibt eine Liste aller möglichen SetMoves für diese Form zurück.

Parameters:

  • gamestate

    Der aktuelle Spielstand

  • shape

    Die [PieceShape], die die Züge nutzen sollen

Returns:

  • Alle möglichen Züge mit der Form



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/software_challenge_client/game_rule_logic.rb', line 102

def self.possible_moves_for_shape(gamestate, shape, fields = valid_fields(gamestate))
  color = gamestate.current_color

  moves = Set[]
  fields.each do |field|
    Rotation.each do |r|
      [true, false].each do |f|
        piece = Piece.new(color, shape, r, f, Coordinates.new(0, 0))
        piece.coords.each do |pos|
          moves << SetMove.new(Piece.new(color, shape, r, f, Coordinates.new(field.x - pos.x, field.y - pos.y)))
        end
      end
    end
  end
  moves.filter { |m| valid_set_move?(gamestate, m) }.to_a
end

.possible_setmoves(gamestate) ⇒ Object

Gibt alle möglichen Legezüge zurück

Parameters:

  • gamestate (GameState)

    Der zu untersuchende Spielstand.



40
41
42
43
44
45
46
# File 'lib/software_challenge_client/game_rule_logic.rb', line 40

def self.possible_setmoves(gamestate)
  if gamestate.is_first_move?
    possible_start_moves(gamestate)
  else
    all_possible_setmoves(gamestate).flatten
  end
end

.possible_start_moves(gamestate) ⇒ Object

Gibt alle möglichen Legezüge in der ersten Runde zurück

Parameters:

  • gamestate (GameState)

    Der zu untersuchende Spielstand.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/software_challenge_client/game_rule_logic.rb', line 50

def self.possible_start_moves(gamestate)
  color = gamestate.current_color
  shape = gamestate.start_piece
  area1 = shape.dimension
  area2 = Coordinates.new(area1.y, area1.x)
  moves = Set[]

  # Hard code corners for most efficiency (and because a proper algorithm would be pretty illegible here)
  # Upper Left
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(0, 0)))

  # Upper Right
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(BOARD_SIZE - area1.x, 0)))
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(BOARD_SIZE - area2.x, 0)))

  # Lower Left
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(0, BOARD_SIZE - area1.y)))
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(0, BOARD_SIZE - area2.y)))

  # Lower Right
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(BOARD_SIZE - area1.x, BOARD_SIZE - area1.y)))
  moves.merge(moves_for_shape_on(color, shape, Coordinates.new(BOARD_SIZE - area2.x, BOARD_SIZE - area2.y)))

  moves.filter { |m| valid_set_move?(gamestate, m) }.to_a
end

.valid_fields(gamestate) ⇒ Object

Gibt eine Liste aller Felder zurück, an denen möglicherweise Züge gemacht werden kann.

Parameters:

  • gamestate

    Der aktuelle Spielstand



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/software_challenge_client/game_rule_logic.rb', line 121

def self.valid_fields(gamestate)
  color = gamestate.current_color
  board = gamestate.board
  fields = Set[]
  board.fields_of_color(color).each do |field|
    [Coordinates.new(field.x - 1, field.y - 1),
     Coordinates.new(field.x - 1, field.y + 1),
     Coordinates.new(field.x + 1, field.y - 1),
     Coordinates.new(field.x + 1, field.y + 1)].each do |corner|
      next unless Board.contains(corner)
      next unless board[corner].empty?
      next if neighbor_of_color?(board, Field.new(corner.x, corner.y), color)

      fields << corner
    end
  end
  fields
end

.valid_move?(gamestate, move) ⇒ Boolean

Prüft, ob der gegebene [Move] zulässig ist.

Parameters:

  • gamestate

    der aktuelle Spielstand

  • move

    der zu überprüfende Zug

Returns:

  • (Boolean)

    ob der Zug zulässig ist



182
183
184
185
186
187
188
# File 'lib/software_challenge_client/game_rule_logic.rb', line 182

def self.valid_move?(gamestate, move)
  if move.instance_of? SkipMove
    !gamestate.is_first_move?
  else
    valid_set_move?(gamestate, move)
  end
end

.valid_set_move?(gamestate, move) ⇒ Boolean

Prüft, ob der gegebene [SetMove] zulässig ist.

Parameters:

  • gamestate (GameState)

    der aktuelle Spielstand

  • move (SetMove)

    der zu überprüfende Zug

Returns:

  • (Boolean)

    ob der Zug zulässig ist



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/software_challenge_client/game_rule_logic.rb', line 195

def self.valid_set_move?(gamestate, move)
  # Check whether the color's move is currently active
  return false if move.piece.color != gamestate.current_color

  # Check whether the shape is valid
  if gamestate.is_first_move?
    return false if move.piece.kind != gamestate.start_piece
  elsif !gamestate.undeployed_pieces(move.piece.color).include?(move.piece.kind)
    return false
  end

  # Check whether the piece can be placed
  move.piece.coords.each do |it|
    return false unless gamestate.board.in_bounds?(it)
    return false if obstructed?(gamestate.board, it)
    return false if borders_on_color?(gamestate.board, it, move.piece.color)
  end

  if gamestate.is_first_move?
    # Check if it is placed correctly in a corner
    return false if move.piece.coords.none? { |it| corner?(it) }
  else
    # Check if the piece is connected to at least one tile of same color by corner
    return false if move.piece.coords.none? { |it| corners_on_color?(gamestate.board, it, move.piece.color) }
  end

  true
end

.winning_condition(_gamestate) ⇒ Condition

Prueft, ob ein Spieler im gegebenen GameState gewonnen hat.

Parameters:

  • gamestate (GameState)

    Der zu untersuchende GameState.

Returns:

  • (Condition)

    nil, if the game is not won or a Condition indicating the winning player



331
332
333
# File 'lib/software_challenge_client/game_rule_logic.rb', line 331

def self.winning_condition(_gamestate)
  raise 'Not implemented yet!'
end

Instance Method Details

#remove_invalid_colors(gamestate) ⇒ Object

Entferne alle Farben, die keine Steine mehr auf dem Feld platzieren können.



320
321
322
323
324
325
326
# File 'lib/software_challenge_client/game_rule_logic.rb', line 320

def remove_invalid_colors(gamestate)
  return if gamestate.ordered_colors.empty?
  return unless get_possible_moves(gamestate).empty?

  gamestate.remove_active_color
  remove_invalid_colors(gamestate)
end