forked from exercism/java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCamicia.java
More file actions
156 lines (127 loc) · 4.96 KB
/
Copy pathCamicia.java
File metadata and controls
156 lines (127 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Camicia {
private Status status;
private int cards;
private int tricks;
private enum Player {
PLAYER_A, PLAYER_B
}
private enum Status {
FINISHED, LOOP
}
private static int penaltyOf(String card) {
return switch (card) {
case "J" -> 1;
case "Q" -> 2;
case "K" -> 3;
case "A" -> 4;
default -> 0;
};
}
private static boolean isPaymentCard(String card) {
return penaltyOf(card) > 0;
}
/**
* Return a snapshot of the current state
*/
private static String stateKey(Deque<String> deckA, Deque<String> deckB) {
StringBuilder sb = new StringBuilder();
for (String card : deckA) {
sb.append(isPaymentCard(card) ? card : "N");
}
sb.append('|');
for (String card : deckB) {
sb.append(isPaymentCard(card) ? card : "N");
}
return sb.toString();
}
private static Player otherPlayer(Player player) {
return player == Player.PLAYER_A ? Player.PLAYER_B : Player.PLAYER_A;
}
private static Deque<String> deckOf(Player player, Deque<String> deckA, Deque<String> deckB) {
return player == Player.PLAYER_A ? deckA : deckB;
}
public static CamiciaResult simulateGame(List<String> playerA, List<String> playerB) {
Deque<String> deckA = new ArrayDeque<>(playerA);
Deque<String> deckB = new ArrayDeque<>(playerB);
int cardsPlayed = 0;
int tricksCount = 0;
Player current = Player.PLAYER_A;
Set<String> seenStates = new HashSet<>();
while (true) {
String key = stateKey(deckA, deckB);
// Key already exists, which means this is a loop
if (!seenStates.add(key)) {
return finishGame(Status.LOOP, cardsPlayed, tricksCount);
}
// Otherwise, play next round
RoundResult result = playRound(deckA, deckB, current);
cardsPlayed += result.pileSize();
tricksCount++;
// Check if someone wins and finish the game
if (hasWinner(deckA, deckB)) {
return finishGame(Status.FINISHED, cardsPlayed, tricksCount);
}
// Otherwise, play next round
current = result.nextStarter();
}
}
private static RoundResult playRound(Deque<String> deckA, Deque<String> deckB, Player startingPlayer) {
Deque<String> pile = new ArrayDeque<>(); // cards played in this round
Player currentPlayer = startingPlayer;
int pendingPenalty = 0;
while (true) {
Deque<String> currentPlayerDeck = deckOf(currentPlayer, deckA, deckB);
Player opponent = otherPlayer(currentPlayer);
Deque<String> opponentDeck = deckOf(opponent, deckA, deckB);
// Current player deck is empty, opponent collects all pile, end this round
if (currentPlayerDeck.isEmpty()) {
opponentDeck.addAll(pile);
return new RoundResult(opponent, pile.size());
}
// Otherwise, current player plays 1 card, add to pile
String card = currentPlayerDeck.poll();
pile.addLast(card);
// Current player must pay off pending penalty
if (pendingPenalty > 0) {
// And player reveals a payment card
if (isPaymentCard(card)) {
// reset penalty based on new card, switch turn
pendingPenalty = penaltyOf(card);
currentPlayer = opponent;
} else {
// Otherwise, deduct penalty
pendingPenalty--;
// No pending penalty
if (pendingPenalty == 0) {
// Opponent collects all pile and win the round
deckOf(opponent, deckA, deckB).addAll(pile);
return new RoundResult(opponent, pile.size());
}
}
} else {
// Normal gameplay, without pending penalty
// If player reveals a payment card, update penalty
if (isPaymentCard(card)) {
pendingPenalty = penaltyOf(card);
}
currentPlayer = opponent;
}
}
}
private static CamiciaResult finishGame(Status status, int cardsPlayed, int tricksCount) {
return new CamiciaResult(status == null ? null : status.toString().toLowerCase(), cardsPlayed, tricksCount);
}
private static boolean hasWinner(Deque<String> deckA, Deque<String> deckB) {
return deckA.isEmpty() || deckB.isEmpty();
}
/**
* Immutable round result
*/
private record RoundResult(Player nextStarter, int pileSize) {
}
}