Skip to content

Commit

Permalink
More development (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmizv authored Jan 23, 2023
1 parent 83ba5cf commit 2c0783b
Show file tree
Hide file tree
Showing 19 changed files with 733 additions and 142 deletions.
111 changes: 78 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
# Skat-Game-Id

This library should give a handy way to identify any Skat game by a simple number.
It also comes with a way to read and write games, i.e. the card distributions.
This library should give a handy way to identify any [Skat](https://en.wikipedia.org/wiki/Skat_(card_game)) game or game run by a
simple id. It also comes with a way to read and write games, i.e. the card distributions and a certain game run.

Moreover, it defines two ways to store and display either a distribution of 32 Skat cards
and a complete Skat game which includes the card distribution but also all other information
to uniquely identify a single Skat game play. The two ways are: human-readable and by giving an
integer (either coded decimal or by a Base64 string)

## Definitions
Skat is a three-player trick-taking card game. There are 32 cards.
Each has a value and a suit.

The eight values are:
Skat is a three-player trick-taking card game. There are 32 cards. Each has a value and a suit.

The eight values are:

| 7 | 8 | 9 | 10 | Jack | Queen | King | Ace |
|---|---|---|----|---|---|---|---|
| 7 | 8 | 9 | X | U | O | K | A |

The lower row denote the single character used to denote it within this
library. It's coming from the German names: Unter / Ober / König.)
The lower row denote the single character used to denote it within this library. It's coming from the German names:
Unter / Ober / König.)

The four suits are:

| | |
|---|---|
| Diamonds ♦️ | S |
| Hearts ♥️ | H |
| Spades ♠️ | G |
| Clubs ♣️ | K |
| Diamonds ♦ | Hearts ♥ | Spades ♠ | Clubs ♣ |
|---|---|---|----------|
| ️ S | R | G | E |

Again the latter column denotes the character used in this library. It comes from their
German names: Schellen, Herz, Grün, Kreuz.
Again the latter column denotes the character used in this library.
It comes from their German names: **S**chellen, **R**ot,
**G**rün, **E**icheln.

## Classes


### Game
Should only indicate which player had which card. And what two cards were
in the Skat before starting the game.

`de.jmizv.skatgameid.Game`

Should only indicate which player had which card. And what two cards were in the Skat before starting the game. This will
be the situation right before the bidding starts.

We introduce the following human-readable notation:

Expand All @@ -44,28 +49,68 @@ GU GO GK GA E7 E8 E9 EX EU EO
EK EA
```

The first line shows the cards that the player in front had, the second line
the cards of the player in the middle and the third line the cards for the
player in rear. The last line shows the two cards of the Skat.
The first line shows the cards that the player in front had, the second line the cards of the player in the middle and
the third line the cards for the player in rear. The last line shows the two cards of the Skat.

Cards are denoted by two characters, e.g. `S7` is a 7 of Diamonds
while `GU` is the Jack of Clubs.
Cards are denoted by two characters, e.g. `S7` is a 7 of Diamonds (=_Schell Sieben_ German)
while `GU` is the Jack of Clubs (= _Grün Unter_ in German).

It should be possible to add additional white space but not between the two characters of
one card. One could even put all in one line without problems. Capitalization should
also be neglected but upper-case is preferred.
It should be possible to add additional white space but not between the two characters of one card. One could even put
all in one line without problems. Capitalization should also be neglected but upper-case is preferred.

Note that we don't demand that the cards are ordered from low to high for each stack. Although a normalization is offered.

The above schema converts to the following ID via the method `computeId`: `AABQVVWqqvo=`. Note that it is simply all cards
ordered from lowest to highest. To convert this to a decimal number, the method `computeIdAsBigInteger` helps.
So the real decimal ID would be then: `88327439690490`.

If we reverse this order, the ID becomes `r6qqVVUF` or `-88327439690491`.

Note also that the cards are ordered from low to high for each stack. This is some kind of
normalization. While it is not illegal to have it unsorted this library will always perform
a sorting when printing a game out.

### Game Run
Should indicate when which player had played which card. And what game for what value
were played.

We extend the previous human-readable notation:
`de.jmizv.skatgameid.GameRun`

Should indicate when which player had played which card.
And what game for what value were played and how the bidding went.

We extend the previous human-readable notation (note that `--` are comments and get
discarded while reading):

```
S7S8S9SXSUSOSKSAR7R8 -- front
R9RXRURORKRAG7G8G9GX -- middle
GUGOGKGAE7E8E9EXEUEO -- rear
EKEA -- Skat before game
R7R8 -- Skat on announced game
07 -- bid of front
05 -- bid of middle
04 -- bid of rear
41111 -- spades
012 -- Denotes the index of the players cards from above, e.g.
123 -- forehand played their first card etc.
234
345
456
567
678
789
890
901
```

There first four lines are equal to the `Game` notation. That means they could be collapsed into one single line, too.
Also, the played cards are not validated, e.g. it is not checked whether a player has correctly *bedient*.

If the three values for bidding are all `0`, the game is considered to be *eingepasst*.

The above game definition would result in the following Base64 encoded ID:

```
TODO
AABQVVWqqvpwKETIPCAhMbGwqKipuXl4ZCQkIA==
```

The same id as `BigInteger` would be:
```
129090697728898356188161029675937060363232303187716560461767712
```
10 changes: 8 additions & 2 deletions src/main/java/de/jmizv/skatgameid/Card.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public int number() {
* @return true if this card can be played on the given card
*/
public boolean canBePlayedUpon(Card otherCard, GameTypeKind gameType) {
if (this.equals(otherCard)) {
throw new IllegalArgumentException("Cannot check for played upon with the same two cards.");
}
switch (gameType) {
case NULL:
// with NULL game the colour must match
Expand All @@ -73,7 +76,7 @@ public boolean canBePlayedUpon(Card otherCard, GameTypeKind gameType) {
}
return getSuit() == otherCard.getSuit();
default:
throw new UnsupportedOperationException("xxx");
throw new UnsupportedOperationException("Unsupported value: " + gameType);
}
}

Expand Down Expand Up @@ -140,7 +143,10 @@ public boolean beats(GameTypeKind gameTypeKind, Card card) {
}
switch (gameTypeKind) {
case NULL:
throw new UnsupportedOperationException("Not yet implemented for NULL.");
if (getSuit() == card.getSuit()) {
return getValue().ordinal() - card.getValue().ordinal() > 0;
}
return false;
case GRAND: {
if (getValue() == Value.JACK && card.getValue() == Value.JACK) {
return getSuit().ordinal() - card.getSuit().ordinal() > 0;
Expand Down
50 changes: 38 additions & 12 deletions src/main/java/de/jmizv/skatgameid/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.ImmutableList;

import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -40,8 +41,7 @@ private Game(List<Card> player1Front, List<Card> player2Middle, List<Card> playe
}

/**
*
* @return the same card distribution for this game but with all cards sorted by their values
* @return the same card distribution for this game but with all cards sorted by their values for every player and the skat.
*/
public Game normalized() {
return new Game(_player1Front.stream().sorted().collect(Collectors.toList()),
Expand All @@ -50,39 +50,39 @@ public Game normalized() {
_skat.stream().sorted().collect(Collectors.toList()));
}

public List<Card> getPlayerFront() {
public List<Card> frontCards() {
return _player1Front;
}

public List<Card> getPlayerMiddle() {
public List<Card> middleCards() {
return _player2Middle;
}

public List<Card> getPlayerRear() {
public List<Card> rearCards() {
return _player3Rear;
}

public List<Card> getSkat() {
public List<Card> skatCards() {
return _skat;
}

/**
* Generates the game id in a bitset object.
* <p>
* As we have 32 cards and 3 player and the skat we set for every card to which of the four stacks it belongs. 0x0 is
* As we have 32 cards and 3 player and the skat, we set for every card to which of the four stacks it belongs. 0x0 is
* forehand, 0x1 is middle hand, 0x10 is rear hand and finally 0x11 is the skat.
*
* @return the game id as a bitset
*/
BitSet computeIdAsBitSet() {
BitSet bitSet = new BitSet(32 * 2);
for (Card card : getPlayerMiddle()) {
for (Card card : middleCards()) {
bitSet.set(card.number() * 2);
}
for (Card card : getPlayerRear()) {
for (Card card : rearCards()) {
bitSet.set(card.number() * 2 + 1);
}
for (Card card : getSkat()) {
for (Card card : skatCards()) {
bitSet.set(card.number() * 2);
bitSet.set(card.number() * 2 + 1);
}
Expand All @@ -98,13 +98,28 @@ public String computeId() {
return Base64.getEncoder().encodeToString(computeIdAsBitSet().toByteArray());
}

public BigInteger computeIdAsBigInteger() {
return new BigInteger(computeIdAsBitSet().toByteArray());
}

/**
* Builds a Game object from the given id. Note that the object will not be normalized,
* i.e. the order of the cards per player stays unchanged.
*
* @param id the game identification
* @return a Game object
*/

public static Game ofId(String id) {
if (id.length() < 2) {
throw new IllegalArgumentException("Game id \"" + id + "\" is too short.");
}
BitSet bitSet = BitSet.valueOf(Base64.getDecoder().decode(id));
return ofBitSet(BitSet.valueOf(Base64.getDecoder().decode(id)));
}

public static Game ofBitSet(BitSet bitSet) {
if (bitSet.size() != 64) {
throw new IllegalArgumentException("Game id \"" + id + "\" is not compatible for decoding as it results in " + bitSet.size() / 2 + " cards.");
throw new IllegalArgumentException("BitSet is not compatible for decoding as it results in " + bitSet.size() / 2 + " cards.");
}
Game.Builder builder = Game.builder();
for (int i = 0; i < 64; i += 2) {
Expand Down Expand Up @@ -139,10 +154,21 @@ public int hashCode() {
return Objects.hash(_player1Front, _player2Middle, _player3Rear, _skat);
}

/**
* Generates a random Game object using the current time in millis as a seed for the Random object.
*
* @return a random Game object
*/
public static Game random() {
return random(System.currentTimeMillis());
}

/**
* Generates a random Game object from the given seed. Using the same seed will result in the same Game object.
*
* @param seed a seed for the used Random initialization
* @return a Game object
*/
public static Game random(long seed) {
List<Card> cards = new ArrayList<>();
for (Suit suit : Suit.values()) {
Expand Down
Loading

0 comments on commit 2c0783b

Please sign in to comment.