software_challenge_client 1.2.1 → 19.0.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 +4 -4
- data/Dockerfile +3 -0
- data/README.md +64 -26
- data/example/client.rb +1 -71
- data/example/main.rb +1 -1
- data/lib/software_challenge_client.rb +4 -2
- data/lib/software_challenge_client/board.rb +66 -19
- data/lib/software_challenge_client/client_interface.rb +10 -5
- data/lib/software_challenge_client/condition.rb +2 -2
- data/lib/software_challenge_client/coordinates.rb +17 -0
- data/lib/software_challenge_client/debug_hint.rb +8 -4
- data/lib/software_challenge_client/direction.rb +53 -0
- data/lib/software_challenge_client/field.rb +26 -12
- data/lib/software_challenge_client/field_type.rb +25 -19
- data/lib/software_challenge_client/game_rule_logic.rb +230 -0
- data/lib/software_challenge_client/game_state.rb +45 -191
- data/lib/software_challenge_client/invalid_move_exception.rb +6 -8
- data/lib/software_challenge_client/line.rb +126 -0
- data/lib/software_challenge_client/line_direction.rb +15 -0
- data/lib/software_challenge_client/logging.rb +3 -2
- data/lib/software_challenge_client/move.rb +51 -38
- data/lib/software_challenge_client/network.rb +3 -1
- data/lib/software_challenge_client/player.rb +0 -39
- data/lib/software_challenge_client/player_color.rb +23 -13
- data/lib/software_challenge_client/protocol.rb +20 -83
- data/lib/software_challenge_client/runner.rb +2 -1
- data/lib/software_challenge_client/util/constants.rb +8 -5
- data/lib/software_challenge_client/version.rb +1 -1
- data/software_challenge_client.gemspec +2 -0
- metadata +24 -8
- data/lib/software_challenge_client/action.rb +0 -217
- data/lib/software_challenge_client/card_type.rb +0 -13
- data/lib/software_challenge_client/field_unavailable_exception.rb +0 -17
- data/lib/software_challenge_client/game_rules.rb +0 -376
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42f624c3d6632a5451ccd927967699ce2c663340
|
4
|
+
data.tar.gz: 5e8540a594b7a896535c64a617c80e50d801c9db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efbae299e27a8a7db3aa5cf706c2e0c4a987647822103d59394ebee3bbb6367d7f0308cf8173c519a81a228277ab0f1ca0e78cd8b914e6afef681eb5d7a8a309
|
7
|
+
data.tar.gz: 2b35430ade3b3cb6edfda0c8f29b5b6dc131f5f278da1f88a4cbec9a1f06b336d7fa2fb457eefd8ac758f6086021b12e10767eb6364494823477ea560e7afb93
|
data/Dockerfile
ADDED
data/README.md
CHANGED
@@ -1,47 +1,89 @@
|
|
1
|
-
# Software
|
1
|
+
# Software-Challenge Client
|
2
2
|
|
3
3
|
This gem includes everything to build a client for the coding
|
4
4
|
competition [Software-Challenge](http://www.software-challenge.de).
|
5
5
|
|
6
|
+
------------------------------------------------------------------------
|
7
|
+
|
8
|
+
_Language:_ Most documentation will be in german language, because it is intended
|
9
|
+
to be used by pupils taking part in the Software-Challenge programming
|
10
|
+
competition. Only internal documentation is in english.
|
11
|
+
|
12
|
+
------------------------------------------------------------------------
|
13
|
+
|
14
|
+
_Sprache:_ Ein Grossteil der Dokumentation ist in deutscher Sprache verfasst, da
|
15
|
+
sie dazu gedacht ist, von am Programmierwettbewerb Software-Challenge
|
16
|
+
teilnehmenden Schülerinnen und Schülern gelesen zu werden. Interne Dokumentation
|
17
|
+
ist weiterhin in englisch.
|
18
|
+
|
19
|
+
------------------------------------------------------------------------
|
20
|
+
|
6
21
|
## Installation
|
7
22
|
|
8
|
-
|
23
|
+
Um die Software-Challenge Bibliothek in deinem Computerspieler zu verwenden, füge folgende Zeile in das Gemfile deines Projektes ein:
|
9
24
|
|
10
|
-
|
11
|
-
gem 'software_challenge_client'
|
12
|
-
```
|
25
|
+
gem 'software_challenge_client'
|
13
26
|
|
14
|
-
|
27
|
+
Installiere das Gem dann mit dem Befehl:
|
15
28
|
|
16
29
|
$ bundle
|
17
30
|
|
18
|
-
|
31
|
+
Oder installiere das Gem ohne ein Gemfile mit:
|
19
32
|
|
20
33
|
$ gem install software_challenge_client
|
21
34
|
|
22
|
-
##
|
35
|
+
## Verwendung
|
36
|
+
|
37
|
+
Ein Beispielprojekt zur Verwendung der Bibliothek findet man im Verzeichnis `example` ([Beipspielprojekt auf GitHub](https://github.com/CAU-Kiel-Tech-Inf/socha_ruby_client/tree/master/example)).
|
38
|
+
|
39
|
+
Du kannst den Beispielclient mittels
|
40
|
+
|
41
|
+
ruby main.rb
|
42
|
+
|
43
|
+
in einer Konsole ausführen (dazu musst du dich im Verzeichnis `example` befinden).
|
44
|
+
|
45
|
+
Damit dies funktioniert, muss das Gem bereits wie oben beschrieben installiert
|
46
|
+
sein und es muss ein Spielserver auf eine Verbindung warten (also zum Beispiel
|
47
|
+
ein Spiel mit einem manuell gestarteten Spieler in der grafischen Oberfläche
|
48
|
+
angelegt worden sein).
|
49
|
+
|
50
|
+
Neben Beiwerk wie dem Initialisieren der Verbindung zum Spielserver und
|
51
|
+
Verarbeiten der Startparameter (was beides in `main.rb` des Beispielprojektes
|
52
|
+
passiert), musst du nur eine Klasse implementieren, um einen lauffähigen
|
53
|
+
Computerspieler zu haben (`client.rb` im Beispielprojekt):
|
54
|
+
|
55
|
+
require 'software_challenge_client'
|
56
|
+
|
57
|
+
class Client < ClientInterface
|
58
|
+
include Logging
|
23
59
|
|
24
|
-
|
60
|
+
attr_accessor :gamestate
|
25
61
|
|
26
|
-
|
62
|
+
def initialize(log_level)
|
63
|
+
logger.level = log_level
|
64
|
+
logger.info 'Einfacher Spieler wurde erstellt.'
|
65
|
+
end
|
27
66
|
|
28
|
-
|
29
|
-
|
30
|
-
|
67
|
+
# gets called, when it's your turn
|
68
|
+
def move_requested
|
69
|
+
logger.info "Spielstand: #{gamestate.points_for_player(gamestate.current_player)} - #{gamestate.points_for_player(gamestate.other_player)}"
|
70
|
+
move = best_move
|
71
|
+
logger.debug "Zug gefunden: #{move}" unless move.nil?
|
72
|
+
move
|
73
|
+
end
|
31
74
|
|
32
|
-
|
33
|
-
|
34
|
-
|
75
|
+
def best_move
|
76
|
+
gamestate.possible_moves.sample
|
77
|
+
end
|
78
|
+
end
|
35
79
|
|
36
|
-
## Documentation
|
80
|
+
## Generating the Documentation
|
37
81
|
|
38
82
|
Code documentation can be generated using YARD in the project root (source code
|
39
83
|
needs to be checked out and `bundle` has to be executed,
|
40
84
|
see [Installation](#installation)):
|
41
85
|
|
42
|
-
|
43
|
-
yard
|
44
|
-
```
|
86
|
+
yard
|
45
87
|
|
46
88
|
After generation, the docs can be found in the `doc` directory. Start at
|
47
89
|
`index.html`.
|
@@ -52,15 +94,11 @@ on
|
|
52
94
|
|
53
95
|
When updating the docs, you may use
|
54
96
|
|
55
|
-
|
56
|
-
yard server --reload
|
57
|
-
```
|
97
|
+
yard server --reload
|
58
98
|
|
59
99
|
or inside a docker container
|
60
100
|
|
61
|
-
|
62
|
-
yard server --reload --bind 0.0.0.0
|
63
|
-
```
|
101
|
+
yard server --reload --bind 0.0.0.0
|
64
102
|
|
65
103
|
to get a live preview of them at [http://localhost:8808](http://localhost:8808).
|
66
104
|
|
data/example/client.rb
CHANGED
@@ -8,9 +8,6 @@ class Client < ClientInterface
|
|
8
8
|
|
9
9
|
attr_accessor :gamestate
|
10
10
|
|
11
|
-
# Anzahl der Spielfelder
|
12
|
-
NUM_FIELDS = 65
|
13
|
-
|
14
11
|
def initialize(log_level)
|
15
12
|
logger.level = log_level
|
16
13
|
logger.info 'Einfacher Spieler wurde erstellt.'
|
@@ -25,73 +22,6 @@ class Client < ClientInterface
|
|
25
22
|
end
|
26
23
|
|
27
24
|
def best_move
|
28
|
-
|
29
|
-
salad_moves = []
|
30
|
-
winning_moves = []
|
31
|
-
selected_moves = []
|
32
|
-
|
33
|
-
index = gamestate.current_player.index
|
34
|
-
possible_moves.each do |move|
|
35
|
-
move.actions.each do |action|
|
36
|
-
case action.type
|
37
|
-
when :advance
|
38
|
-
target_field_index = action.distance + index
|
39
|
-
if target_field_index == NUM_FIELDS - 1
|
40
|
-
winning_moves << move
|
41
|
-
elsif gamestate.field(target_field_index).type == FieldType::SALAD
|
42
|
-
salad_moves << move
|
43
|
-
else
|
44
|
-
selected_moves << move
|
45
|
-
end
|
46
|
-
when :card
|
47
|
-
if action.card_type == CardType::EAT_SALAD
|
48
|
-
# Zug auf Hasenfeld und danach Salatkarte
|
49
|
-
salad_moves << move
|
50
|
-
# Muss nicht zusätzlich ausgewählt werden, wurde schon durch Advance ausgewählt
|
51
|
-
end
|
52
|
-
when :exchange_carrots
|
53
|
-
if action.value == 10 &&
|
54
|
-
gamestate.current_player.carrots < 30 &&
|
55
|
-
index < 40 &&
|
56
|
-
!gamestate.current_player.last_non_skip_action.instance_of?(ExchangeCarrots)
|
57
|
-
# Nehme nur Karotten auf, wenn weniger als 30 und nur am Anfang und nicht zwei
|
58
|
-
# mal hintereinander
|
59
|
-
selected_moves << move
|
60
|
-
elsif action.value == -10 &&
|
61
|
-
gamestate.current_player.carrots > 30 &&
|
62
|
-
index >= 40
|
63
|
-
# Abgeben von Karotten ist nur am Ende sinnvoll
|
64
|
-
selected_moves << move
|
65
|
-
end
|
66
|
-
when :fall_back
|
67
|
-
if index > 56 && # letztes Salatfeld
|
68
|
-
gamestate.current_player.salads > 0
|
69
|
-
# Falle nur am Ende (index > 56) zurück, außer du musst noch einen Salat loswerden
|
70
|
-
selected_moves << move
|
71
|
-
elsif index <= 56 &&
|
72
|
-
gamestate.previous_field_of_type(FieldType::HEDGEHOG, index) &&
|
73
|
-
index - gamestate.previous_field_of_type(FieldType::HEDGEHOG, index).index < 5
|
74
|
-
# Falle zurück, falls sich Rückzug lohnt (nicht zu viele Karotten aufnehmen)
|
75
|
-
selected_moves << move
|
76
|
-
end
|
77
|
-
else
|
78
|
-
# Füge Salatessen oder Skip hinzu
|
79
|
-
selected_moves << move
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
if !winning_moves.empty?
|
85
|
-
logger.info("Waehle Gewinnzug")
|
86
|
-
winning_moves.sample
|
87
|
-
elsif !salad_moves.empty?
|
88
|
-
# es gibt die Möglichkeit einen Salat zu essen
|
89
|
-
logger.info("Waehle Zug zum Salatessen")
|
90
|
-
salad_moves.sample
|
91
|
-
elsif !selected_moves.empty?
|
92
|
-
selected_moves.sample
|
93
|
-
else
|
94
|
-
possible_moves.sample
|
95
|
-
end
|
25
|
+
gamestate.possible_moves.sample
|
96
26
|
end
|
97
27
|
end
|
data/example/main.rb
CHANGED
@@ -3,7 +3,6 @@ module SoftwareChallengeClient
|
|
3
3
|
require 'software_challenge_client/version'
|
4
4
|
require 'software_challenge_client/logging'
|
5
5
|
require 'software_challenge_client/invalid_move_exception'
|
6
|
-
require 'software_challenge_client/field_unavailable_exception'
|
7
6
|
require 'software_challenge_client/board'
|
8
7
|
require 'software_challenge_client/client_interface'
|
9
8
|
require 'software_challenge_client/condition'
|
@@ -17,5 +16,8 @@ module SoftwareChallengeClient
|
|
17
16
|
require 'software_challenge_client/player_color'
|
18
17
|
require 'software_challenge_client/protocol'
|
19
18
|
require 'software_challenge_client/runner'
|
20
|
-
require 'software_challenge_client/
|
19
|
+
require 'software_challenge_client/game_rule_logic'
|
20
|
+
require 'software_challenge_client/direction'
|
21
|
+
require 'software_challenge_client/line_direction'
|
22
|
+
require 'software_challenge_client/coordinates'
|
21
23
|
end
|
@@ -1,46 +1,93 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require_relative './util/constants'
|
3
5
|
require_relative 'game_state'
|
4
|
-
require_relative 'player'
|
5
6
|
require_relative 'field_type'
|
6
7
|
require_relative 'field'
|
7
8
|
|
8
|
-
# Ein Spielbrett bestehend aus
|
9
|
+
# Ein Spielbrett bestehend aus 10x10 Feldern.
|
9
10
|
class Board
|
10
11
|
# @!attribute [r] fields
|
11
12
|
# @note Besser über die {#field} Methode auf Felder zugreifen.
|
12
|
-
# @return [Array<Field
|
13
|
-
#
|
13
|
+
# @return [Array<Array<Field>>] Ein Feld wird an der Position entsprechend
|
14
|
+
# seiner Koordinaten im Array gespeichert.
|
14
15
|
attr_reader :fields
|
15
16
|
|
16
|
-
#
|
17
|
+
# Erstellt ein neues leeres Spielbrett.
|
17
18
|
def initialize
|
18
19
|
@fields = []
|
20
|
+
(0..9).to_a.each do |y|
|
21
|
+
@fields[y] = []
|
22
|
+
(0..9).to_a.each do |x|
|
23
|
+
@fields[y][x] = Field.new(x, y, FieldType::EMPTY)
|
24
|
+
end
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
28
|
+
# Vergleicht zwei Spielbretter. Gleichheit besteht, wenn zwei Spielbretter die
|
29
|
+
# gleichen Felder enthalten.
|
25
30
|
def ==(other)
|
26
|
-
fields.each_with_index do |
|
27
|
-
|
31
|
+
fields.each_with_index do |row, y|
|
32
|
+
row.each_with_index do |field, x|
|
33
|
+
return false if field != other.field(x, y)
|
34
|
+
end
|
28
35
|
end
|
29
36
|
true
|
30
37
|
end
|
31
38
|
|
39
|
+
# Fügt ein Feld dem Spielbrett hinzu. Das übergebene Feld ersetzt das an den Koordinaten bestehende Feld.
|
40
|
+
#
|
41
|
+
# @param field [Field] Das einzufügende Feld.
|
32
42
|
def add_field(field)
|
33
|
-
@fields[field.
|
43
|
+
@fields[field.y][field.x] = field
|
44
|
+
end
|
45
|
+
|
46
|
+
# Ändert den Typ eines bestimmten Feldes des Spielbrettes.
|
47
|
+
#
|
48
|
+
# @param x [Integer] Die X-Koordinate des zu ändernden Feldes. 0..9, wobei Spalte 0 ganz links und Spalte 9 ganz rechts liegt.
|
49
|
+
# @param y [Integer] Die Y-Koordinate des zu ändernden Feldes. 0..9, wobei Zeile 0 ganz unten und Zeile 9 ganz oben liegt.
|
50
|
+
# @param type [FieldType] Der neue Typ des Feldes.
|
51
|
+
def change_field(x, y, type)
|
52
|
+
@fields[y][x].type = type
|
34
53
|
end
|
35
54
|
|
36
55
|
# Zugriff auf die Felder des Spielfeldes
|
37
56
|
#
|
38
|
-
# @param
|
39
|
-
# @
|
40
|
-
#
|
41
|
-
#
|
42
|
-
def field(
|
43
|
-
return
|
44
|
-
fields.
|
57
|
+
# @param x [Integer] Die X-Koordinate des Feldes. 0..9, wobei Spalte 0 ganz links und Spalte 9 ganz rechts liegt.
|
58
|
+
# @param y [Integer] Die Y-Koordinate des Feldes. 0..9, wobei Zeile 0 ganz unten und Zeile 9 ganz oben liegt.
|
59
|
+
# @return [Field] Das Feld mit den gegebenen Koordinaten. Falls das Feld nicht
|
60
|
+
# exisitert (weil die Koordinaten ausserhalb von (0,0)..(9,9) liegen), wird nil zurückgegeben.
|
61
|
+
def field(x, y)
|
62
|
+
return nil if x.negative? || y.negative?
|
63
|
+
fields.dig(y, x) # NOTE that #dig requires ruby 2.3+
|
64
|
+
end
|
65
|
+
|
66
|
+
# Zugriff auf die Felder des Spielfeldes über ein Koordinaten-Paar.
|
67
|
+
#
|
68
|
+
# @param coordinates [Coordinates] X- und Y-Koordinate als Paar, sonst wie bei {Board#field}.
|
69
|
+
# @return [Field] Wie bei {Board#field}.
|
70
|
+
#
|
71
|
+
# @see #field
|
72
|
+
def field_at(coordinates)
|
73
|
+
field(coordinates.x, coordinates.y)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Liefert alle Felder eines angegebenen Typs des Spielbrettes.
|
77
|
+
#
|
78
|
+
# @param field_type [FieldType] Der Typ, dessen Felder zurückgegeben werden sollen.
|
79
|
+
# @return [Array<Field>] Alle Felder des angegebenen Typs die das Spielbrett enthält.
|
80
|
+
def fields_of_type(field_type)
|
81
|
+
fields.flatten.select{ |f| f.type == field_type }
|
45
82
|
end
|
83
|
+
|
84
|
+
# Gibt eine textuelle Repräsentation des Spielbrettes aus. Hier steht R für
|
85
|
+
# einen roten Fisch, B für einen blauen, ~ für ein leeres Feld und O für ein
|
86
|
+
# Kraken-Feld.
|
87
|
+
def to_s
|
88
|
+
fields.reverse.map do |row|
|
89
|
+
row.map { |f| f.type.value }.join(' ')
|
90
|
+
end.join("\n")
|
91
|
+
end
|
92
|
+
|
46
93
|
end
|
@@ -1,12 +1,17 @@
|
|
1
|
-
# encoding:
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
|
-
#
|
3
|
+
# Das Interface sollte von einem Client implementiert werden, damit er über das
|
4
|
+
# Gem an einem Spiel teilnehmen kann.
|
4
5
|
class ClientInterface
|
5
|
-
#
|
6
|
+
# Wird automatisch aktualisiert und ist immer der Spielzustand des aktuellen Zuges.
|
6
7
|
attr_accessor :gamestate
|
7
8
|
|
8
|
-
#
|
9
|
-
#
|
9
|
+
# Wird aufgerufen, wenn der Client einen Zug machen soll. Dies ist der
|
10
|
+
# Einstiegspunkt für die eigentliche Logik des Computerspielers. Er muss auf
|
11
|
+
# Basis des Spielzustandes entscheiden, welchen Zug er machen möchte und diese
|
12
|
+
# zurückgeben.
|
13
|
+
#
|
14
|
+
# @return [Move] Ein für den aktuellen Spielzustand gültiger Spielzug.
|
10
15
|
def move_requested
|
11
16
|
raise 'Not yet implemented'
|
12
17
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require_relative 'player'
|
3
3
|
|
4
|
-
#
|
4
|
+
# Das Ergebnis eines Spieles. Ist im `GameState#condition` zu finden, wenn das Spiel beendet wurde.
|
5
5
|
class Condition
|
6
6
|
# @!attribute [r] winner
|
7
|
-
# @return [Player]
|
7
|
+
# @return [Player] Spieler, der das Spiel gewonnen hat.
|
8
8
|
attr_reader :winner
|
9
9
|
|
10
10
|
# Initializes the winning Condition with a player
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Ein Koordinatenpaar für ein zweidimensionales Koordinatensystem.
|
5
|
+
class Coordinates
|
6
|
+
|
7
|
+
# X-Koordinate
|
8
|
+
attr_reader :x
|
9
|
+
# Y-Koordinate
|
10
|
+
attr_reader :y
|
11
|
+
|
12
|
+
# Erstellt ein neues Koordinatenpaar aus X- und Y-Koordinate.
|
13
|
+
def initialize(x, y)
|
14
|
+
@x = x
|
15
|
+
@y = y
|
16
|
+
end
|
17
|
+
end
|
@@ -1,11 +1,15 @@
|
|
1
|
-
# encoding:
|
2
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Ein Hinweis, der zu einem Zug hinzugefügt werden kann. Z.B. zu
|
4
|
+
# Diagnosezwecken. Der Hinweis wird in der grafischen Oberfläche angezeigt und
|
5
|
+
# in Replay-Dateien gespeichert.
|
3
6
|
class DebugHint
|
4
7
|
# @!attribute [r] content
|
5
|
-
# @return [String]
|
8
|
+
# @return [String] Der Text des Hinweises.
|
6
9
|
attr_reader :content
|
7
10
|
|
8
|
-
#
|
11
|
+
# Erstellt einen neuen Hinweis.
|
12
|
+
# @param content [Object] Inhalt des Hinweises. Wird zu String konvertiert.
|
9
13
|
def initialize(content)
|
10
14
|
@content = content.to_s
|
11
15
|
end
|