software_challenge_client 1.2.1 → 19.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|