Skip to content

Commit ba5beee

Browse files
committed
rework: simplify plugin & start migration to season 2022
1 parent b4bb6d5 commit ba5beee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+393
-2305
lines changed

player/src/main/sc/player2021/logic/Logic.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
import sc.api.plugins.TwoPlayerGameState;
66
import sc.framework.plugins.Player;
77
import sc.player.IGameHandler;
8-
import sc.plugin2021.GameState;
9-
import sc.plugin2021.Move;
10-
import sc.plugin2021.util.GameRuleLogic;
8+
import sc.plugin2022.GameState;
9+
import sc.plugin2022.Move;
1110
import sc.shared.GameResult;
1211

1312
import java.util.ArrayList;
@@ -33,20 +32,19 @@ public void onGameOver(GameResult data, String errorMessage) {
3332
@Override
3433
public Move calculateMove() {
3534
long startTime = System.currentTimeMillis();
36-
Player player = gameState.getCurrentPlayer();
37-
log.info("Es wurde ein Zug von {} angefordert.", player);
35+
log.info("Es wurde ein Zug von {} angefordert.", gameState.getCurrentTeam());
3836

39-
List<Move> possibleMoves = new ArrayList<>(GameRuleLogic.getPossibleMoves(gameState));
37+
List<Move> possibleMoves = gameState.getPossibleMoves();
4038
Move move = possibleMoves.get((int) (Math.random() * possibleMoves.size()));
4139

4240
log.info("Sende {} nach {}ms.", move, System.currentTimeMillis() - startTime);
4341
return move;
4442
}
4543

4644
@Override
47-
public void onUpdate(TwoPlayerGameState<?> gameState) {
45+
public void onUpdate(TwoPlayerGameState gameState) {
4846
this.gameState = (GameState) gameState;
49-
log.info("Zug: {} Dran: {}", gameState.getTurn(), gameState.getCurrentPlayer().getColor());
47+
log.info("Zug: {} Dran: {}", gameState.getTurn(), gameState.getCurrentTeam());
5048
}
5149

5250
}

plugin/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ val game: String by project
22

33
sourceSets {
44
main {
5-
java.setSrcDirs(listOf("src/shared", "src/client", "src/server"))
5+
java.setSrcDirs(listOf("src/main"))
66
resources.setSrcDirs(listOf("src/resources"))
77
}
88
test {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package sc.plugin2022
2+
3+
import com.thoughtworks.xstream.annotations.XStreamAlias
4+
import sc.api.plugins.IBoard
5+
import sc.api.plugins.ITeam
6+
import sc.api.plugins.Team
7+
import sc.plugin2022.util.Constants.boardrange
8+
import sc.plugin2022.util.MoveMistake
9+
import sc.shared.InvalidMoveException
10+
11+
/** Das Spielbrett besteht aus 8x8 Feldern. */
12+
@XStreamAlias(value = "board")
13+
data class Board(
14+
private val board: MutableMap<Coordinates, Piece>,
15+
): IBoard, Map<Coordinates, Piece> by board {
16+
17+
constructor(): this(generateBoard())
18+
19+
/** Gibt das Feld an den gegebenen Koordinaten zurück. */
20+
operator fun get(x: Int, y: Int) =
21+
get(Coordinates(x, y))
22+
23+
/** Moves a piece according to [Move].
24+
* @throws InvalidMoveException if something is wrong with the Move.
25+
* @return the moved [Piece], null if it turned into an amber. */
26+
@Throws(InvalidMoveException::class)
27+
fun movePiece(move: Move): Piece? =
28+
board[move.start]?.let { piece ->
29+
if (move.delta !in piece.possibleMoves)
30+
throw InvalidMoveException(MoveMistake.INVALID_MOVEMENT, move)
31+
board[move.destination]?.let { piece.capture(it) }
32+
board.remove(move.start)
33+
if (piece.isAmber || (piece.type.isLight && move.destination.y == piece.team.opponent().startLine)) {
34+
board.remove(move.destination)
35+
null
36+
} else {
37+
board[move.destination] = piece
38+
piece
39+
}
40+
} ?: throw InvalidMoveException(MoveMistake.START_EMPTY, move)
41+
42+
override fun toString() =
43+
boardrange.joinToString("\n") { y ->
44+
boardrange.joinToString("") { x ->
45+
get(x, y)?.shortString() ?: "--"
46+
}
47+
}
48+
49+
override fun clone() = Board(HashMap(board))
50+
51+
companion object {
52+
@JvmStatic
53+
fun generateBoard() =
54+
(PieceType.values() + PieceType.values()).let { pieces ->
55+
pieces.shuffle()
56+
pieces.withIndex().flatMap { (index, type) ->
57+
Team.values().map { team ->
58+
createField(team, index, type)
59+
}
60+
}.toMap(HashMap())
61+
}
62+
63+
@JvmStatic
64+
fun createField(team: ITeam, x: Int, type: PieceType) =
65+
Coordinates(if (team.index == 0) x else boardrange.last - x, team.startLine) to Piece(type, team)
66+
}
67+
}
68+
69+
val ITeam.startLine
70+
get() = index * boardrange.last

plugin/src/shared/sc/plugin2021/Color.kt renamed to plugin/src/main/sc/plugin2022/Color.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package sc.plugin2021
1+
package sc.plugin2022
22

33
import com.thoughtworks.xstream.annotations.XStreamAlias
4+
import sc.api.plugins.Team
45

56
/** Die vier verschiedenen Farben im Spiel. */
67
@XStreamAlias(value = "color")
@@ -26,14 +27,6 @@ enum class Color {
2627
YELLOW, GREEN -> Team.TWO
2728
}
2829

29-
/** @return [FieldContent], der dieser Farbe entspricht. */
30-
operator fun unaryPlus(): FieldContent = when (this) {
31-
BLUE -> FieldContent.BLUE
32-
YELLOW -> FieldContent.YELLOW
33-
RED -> FieldContent.RED
34-
GREEN -> FieldContent.GREEN
35-
}
36-
3730
override fun toString() = "Farbe $german"
3831

3932
val german: String

plugin/src/shared/sc/plugin2021/Coordinates.kt renamed to plugin/src/main/sc/plugin2022/Coordinates.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
package sc.plugin2021
1+
package sc.plugin2022
22

33
import com.thoughtworks.xstream.annotations.XStreamAlias
44
import com.thoughtworks.xstream.annotations.XStreamAsAttribute
5+
import sc.plugin2022.util.Constants
56
import kotlin.math.min
67

78
/** Eine 2D Koordinate der Form (x, y). */
@@ -27,14 +28,15 @@ data class Coordinates(
2728
/** Wandelt die [Coordinates] in einen entsprechenden [Vector]. */
2829
operator fun unaryPlus(): Vector = Vector(x, y)
2930

30-
/** Gibt ein Set der vier Ecken dieser Koordinaten zurück. */
31-
val corners: Set<Coordinates>
32-
get() = Vector.diagonals.mapTo(HashSet()) { this + it }
33-
3431
/** Gibt ein Set der vier benachbarten Felder dieser Koordinaten zurück. */
3532
val neighbors: Set<Coordinates>
3633
get() = Vector.cardinals.mapTo(HashSet()) { this + it }
37-
34+
35+
/** Whether these coordinates mark a field on the board. */
36+
val isValid =
37+
x >= 0 && x < Constants.BOARD_SIZE &&
38+
y >= 0 && y < Constants.BOARD_SIZE
39+
3840
companion object {
3941
/** Der Ursprung des Koordinatensystems (0, 0). */
4042
val origin = Coordinates(0, 0)
@@ -64,19 +66,19 @@ data class Vector(val dx: Int, val dy: Int) {
6466
operator fun compareTo(other: Vector): Int =
6567
min(other.dx - dx, other.dy - dy)
6668

67-
/** Konvertiert den Vektor zu entsprechendn [Coordinates]. */
69+
/** Konvertiert den Vektor zu entsprechenden [Coordinates]. */
6870
operator fun unaryPlus(): Coordinates = Coordinates(dx, dy)
6971

7072
companion object {
7173
/** Die vier Vektoren in diagonaler Richtung. */
72-
val diagonals: Set<Vector> = setOf(
74+
val diagonals: Array<Vector> = arrayOf(
7375
Vector(-1, -1),
7476
Vector(-1, 1),
7577
Vector(1, -1),
7678
Vector(1, 1)
7779
)
7880
/** Die vier Vektoren in kardinaler Richtung. */
79-
val cardinals: Set<Vector> = setOf(
81+
val cardinals: Array<Vector> = arrayOf(
8082
Vector(-1, 0),
8183
Vector(0, -1),
8284
Vector(1, 0),

plugin/src/server/sc/plugin2021/Game.kt renamed to plugin/src/main/sc/plugin2022/Game.kt

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
package sc.plugin2021
1+
package sc.plugin2022
22

33
import org.slf4j.LoggerFactory
4+
import sc.api.plugins.ITeam
5+
import sc.api.plugins.Team
46
import sc.api.plugins.exceptions.TooManyPlayersException
57
import sc.framework.plugins.AbstractGame
68
import sc.framework.plugins.ActionTimeout
79
import sc.framework.plugins.Player
8-
import sc.plugin2021.util.Constants
9-
import sc.plugin2021.util.GameRuleLogic
10-
import sc.plugin2021.util.MoveMistake
11-
import sc.plugin2021.util.WinReason
10+
import sc.plugin2022.util.Constants
11+
import sc.plugin2022.util.MoveMistake
12+
import sc.plugin2022.util.WinReason
1213
import sc.protocol.room.RoomMessage
1314
import sc.shared.InvalidMoveException
1415
import sc.shared.PlayerScore
@@ -20,14 +21,13 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
2021
val logger = LoggerFactory.getLogger(Game::class.java)
2122
}
2223

23-
private val availableTeams = mutableListOf(Team.ONE, Team.TWO)
24+
private val availableTeams = ArrayDeque<ITeam>().also { it.addAll(Team.values()) }
2425
override fun onPlayerJoined(): Player {
2526
if (availableTeams.isEmpty())
2627
throw TooManyPlayersException()
27-
val player = currentState.getPlayer(availableTeams.removeAt(0))
2828

29+
val player = Player(availableTeams.removeFirst())
2930
players.add(player)
30-
currentState.addPlayer(player)
3131
return player
3232
}
3333

@@ -41,8 +41,8 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
4141
if (players.last().hasViolated())
4242
return players.subList(0, 1)
4343

44-
val first = currentState.getPointsForPlayer(players.first().color)
45-
val second = currentState.getPointsForPlayer(players.last().color)
44+
val first = currentState.getPointsForTeam(players.first().team)
45+
val second = currentState.getPointsForTeam(players.last().team)
4646

4747
if (first > second)
4848
return players.subList(0, 1)
@@ -55,7 +55,7 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
5555
get() = players.mapTo(ArrayList(players.size)) { getScoreFor(it) }
5656

5757
val isGameOver: Boolean
58-
get() = !currentState.hasValidColors() || currentState.round > Constants.ROUND_LIMIT
58+
get() = currentState.isOver
5959

6060
/**
6161
* Checks whether and why the game is over.
@@ -65,10 +65,10 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
6565
override fun checkWinCondition(): WinCondition? {
6666
if (!isGameOver) return null
6767

68-
val scores: Map<Team, Int> = Team.values().map {
69-
it to currentState.getPointsForPlayer(it)
70-
}.toMap()
71-
68+
val scores: Map<Team, Int> = Team.values().associate {
69+
it to currentState.getPointsForTeam(it)
70+
}
71+
7272
return when {
7373
scores.getValue(Team.ONE) > scores.getValue(Team.TWO) -> WinCondition(Team.ONE, WinReason.DIFFERING_SCORES)
7474
scores.getValue(Team.ONE) < scores.getValue(Team.TWO) -> WinCondition(Team.TWO, WinReason.DIFFERING_SCORES)
@@ -77,15 +77,15 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
7777
}
7878

7979
override fun getScoreFor(player: Player): PlayerScore {
80-
val team = player.color as Team
80+
val team = player.team as Team
8181
logger.debug("Get score for player $team (violated: ${if (player.hasViolated()) "yes" else "no"})")
82-
val opponent = currentState.getOpponent(player)
82+
val opponent = players[team.opponent().index]
8383
val winCondition = checkWinCondition()
8484

8585
var cause: ScoreCause = ScoreCause.REGULAR
8686
var reason = ""
8787
var score: Int = Constants.LOSE_SCORE
88-
val points = currentState.getPointsForPlayer(team)
88+
val points = currentState.getPointsForTeam(team)
8989

9090
// Is the game already finished?
9191
if (winCondition?.reason == WinReason.EQUAL_SCORE)
@@ -126,16 +126,14 @@ class Game(override val currentState: GameState = GameState()): AbstractGame<Pla
126126
ActionTimeout(true, Constants.HARD_TIMEOUT, Constants.SOFT_TIMEOUT)
127127

128128
@Throws(InvalidMoveException::class)
129-
override fun onRoundBasedAction(fromPlayer: Player, data: RoomMessage) {
129+
override fun onRoundBasedAction(data: RoomMessage) {
130130
if (data !is Move)
131131
throw InvalidMoveException(MoveMistake.INVALID_FORMAT)
132132

133-
logger.debug("Current State: $currentState")
134-
logger.debug("Performing Move $data")
135-
GameRuleLogic.performMove(currentState, data)
136-
GameRuleLogic.removeInvalidColors(currentState)
137-
next(if (isGameOver) null else currentState.currentPlayer)
138-
logger.debug("Current Board:\n${currentState.board}")
133+
logger.debug("Performing $data")
134+
currentState.performMove(data)
135+
next()
136+
logger.debug("Current State: ${currentState.longString()}")
139137
}
140138

141139
override fun toString(): String =

plugin/src/server/sc/plugin2021/GamePlugin.kt renamed to plugin/src/main/sc/plugin2022/GamePlugin.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package sc.plugin2021
1+
package sc.plugin2022
22

33
import sc.api.plugins.IGameInstance
44
import sc.api.plugins.IGamePlugin
55
import sc.api.plugins.IGameState
6-
import sc.plugin2021.util.Constants
6+
import sc.plugin2022.util.Constants
77
import sc.plugins.PluginDescriptor
88
import sc.shared.ScoreAggregation
99
import sc.shared.ScoreDefinition
@@ -16,7 +16,8 @@ class GamePlugin: IGamePlugin {
1616
val scoreDefinition: ScoreDefinition =
1717
ScoreDefinition(arrayOf(
1818
ScoreFragment("Gewinner"),
19-
ScoreFragment("\u2205 Punkte", ScoreAggregation.AVERAGE)
19+
ScoreFragment("Bernsteine", ScoreAggregation.AVERAGE),
20+
ScoreFragment("Figur vorne", ScoreAggregation.AVERAGE)
2021
))
2122
}
2223

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package sc.plugin2022
2+
3+
import com.thoughtworks.xstream.annotations.XStreamAlias
4+
import com.thoughtworks.xstream.annotations.XStreamAsAttribute
5+
import sc.api.plugins.ITeam
6+
import sc.api.plugins.Team
7+
import sc.api.plugins.TwoPlayerGameState
8+
import sc.plugin2022.util.Constants
9+
import java.util.EnumMap
10+
11+
/**
12+
* Der aktuelle Spielstand.
13+
*
14+
* Er hält alle Informationen zur momentanen Runde,
15+
* mit deren Hilfe der nächste Zug berechnet werden kann.
16+
*/
17+
@XStreamAlias(value = "state")
18+
class GameState @JvmOverloads constructor(
19+
/** Das aktuelle Spielfeld. */
20+
override val board: Board = Board(),
21+
/** Die Anzahl an bereits getätigten Zügen. */
22+
@XStreamAsAttribute override var turn: Int = 0,
23+
/** Der zuletzt gespielte Zug. */
24+
override var lastMove: Move? = null,
25+
private val ambers: EnumMap<Team, Int> = EnumMap(Team::class.java),
26+
): TwoPlayerGameState(Team.ONE) {
27+
28+
constructor(other: GameState): this(other.board.clone(), other.turn, other.lastMove, other.ambers.clone())
29+
30+
fun performMove(move: Move) {
31+
if (board.movePiece(move) == null)
32+
ambers[currentTeam as Team] = (ambers[currentTeam] ?: 0) + 1
33+
turn++
34+
}
35+
36+
val currentPieces
37+
get() = board.filterValues { it.team == currentTeam }
38+
39+
val possibleMoves
40+
get() = currentPieces.flatMap { (pos, piece) ->
41+
piece.possibleMoves.mapNotNull {
42+
Move.create(pos, it)
43+
}
44+
}
45+
46+
val isOver
47+
get() = round > Constants.ROUND_LIMIT || ambers.any { it.value >= 2 }
48+
49+
/** Berechne die Punkteanzahl für das gegebene Team. */
50+
override fun getPointsForTeam(team: ITeam): Int =
51+
ambers[team]!!
52+
53+
override fun clone() = GameState(this)
54+
55+
override fun toString(): String =
56+
"GameState $round/$turn -> ${currentTeam.color} (ambers: $ambers)"
57+
58+
}
59+
60+
val ITeam.color
61+
get() = if (index == 0) "Rot" else "Blau"

0 commit comments

Comments
 (0)