Initial commit
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.settings/
|
||||||
|
*.classpath
|
||||||
|
*.project
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
*.idea
|
||||||
|
*.iml
|
39
pom.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.charego</groupId>
|
||||||
|
<artifactId>freecellfx</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
|
||||||
|
<organization>
|
||||||
|
<name>charego.com</name>
|
||||||
|
</organization>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.3</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.zenjava</groupId>
|
||||||
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
|
<version>8.1.2</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>com.charego.freecellfx.FreeCellApplication</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.charego.freecellfx;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Game;
|
||||||
|
import com.charego.freecellfx.view.GameCanvas;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
public class FreeCellApplication extends Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(FreeCellApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws Exception {
|
||||||
|
AnchorPane root = new AnchorPane();
|
||||||
|
root.setBackground(new Background(new BackgroundImage(new Image(
|
||||||
|
"/deck/FELT.jpg"), BackgroundRepeat.NO_REPEAT,
|
||||||
|
BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER,
|
||||||
|
BackgroundSize.DEFAULT)));
|
||||||
|
|
||||||
|
GameCanvas canvas = new GameCanvas(new Game(), 731, 600);
|
||||||
|
root.getChildren().add(canvas);
|
||||||
|
Scene scene = new Scene(root);
|
||||||
|
|
||||||
|
stage.setTitle("FreeCell");
|
||||||
|
stage.getIcons().add(new Image("/icons/DIAMOND.jpg"));
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.setResizable(false);
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
104
src/main/java/com/charego/freecellfx/model/Card.java
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package com.charego.freecellfx.model;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.util.DoubleKeyedMap;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a playing card with the ability to turn between its face and
|
||||||
|
* backside images.
|
||||||
|
*/
|
||||||
|
public class Card {
|
||||||
|
|
||||||
|
private static DoubleKeyedMap<Rank, Suit, Image> faceImages;
|
||||||
|
public static final Image backImage = new Image("/deck/CARDBACK.png");
|
||||||
|
public static final double width = backImage.getWidth();
|
||||||
|
public static final double height = backImage.getHeight();
|
||||||
|
|
||||||
|
public final Rank rank;
|
||||||
|
public final Suit suit;
|
||||||
|
public final Color color;
|
||||||
|
private boolean faceUp;
|
||||||
|
|
||||||
|
public Card(Rank rank, Suit suit) {
|
||||||
|
this.rank = rank;
|
||||||
|
this.suit = suit;
|
||||||
|
this.color = suit.color();
|
||||||
|
this.faceUp = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the backside image of a card.
|
||||||
|
*/
|
||||||
|
public static Image backImage() {
|
||||||
|
return backImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the face image of a card.
|
||||||
|
*/
|
||||||
|
public static Image faceImage(Rank rank, Suit suit) {
|
||||||
|
if (faceImages == null) {
|
||||||
|
faceImages = new DoubleKeyedMap<>();
|
||||||
|
}
|
||||||
|
if (!faceImages.contains(rank, suit)) {
|
||||||
|
faceImages.put(rank, suit, new Image("/deck/" + rank.value
|
||||||
|
+ suit.firstLetter + ".png"));
|
||||||
|
}
|
||||||
|
return faceImages.get(rank, suit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the card's face image if its face is up or its backside image
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
public Image image() {
|
||||||
|
return faceUp ? faceImage(rank, suit) : backImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns the card over, negating its face up status.
|
||||||
|
*/
|
||||||
|
public void turn() {
|
||||||
|
faceUp = !faceUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null || !(other instanceof Card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Card card = (Card) other;
|
||||||
|
return rank == card.rank && suit == card.suit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return rank + " of " + suit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Rank {
|
||||||
|
ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1 for Ace up to 13 for King.
|
||||||
|
*/
|
||||||
|
public final int value = ordinal() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Suit {
|
||||||
|
SPADES, HEARTS, DIAMONDS, CLUBS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S for SPADES, H for HEARTS, D for DIAMONDS, or C for CLUBS.
|
||||||
|
*/
|
||||||
|
public final Character firstLetter = name().charAt(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Black or red.
|
||||||
|
*/
|
||||||
|
public Color color() {
|
||||||
|
return (this == SPADES || this == CLUBS) ? Color.BLACK : Color.RED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/main/java/com/charego/freecellfx/model/Deck.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package com.charego.freecellfx.model;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Card.Rank;
|
||||||
|
import com.charego.freecellfx.model.Card.Suit;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a deck with the ability to deal and shuffle.
|
||||||
|
*/
|
||||||
|
public class Deck {
|
||||||
|
|
||||||
|
private Deque<Card> cards = new LinkedList<>();
|
||||||
|
|
||||||
|
private Deck() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a 52-card, unshuffled deck.
|
||||||
|
*/
|
||||||
|
public static Deck newDeck() {
|
||||||
|
Deck deck = new Deck();
|
||||||
|
for (Rank rank : Rank.values()) {
|
||||||
|
for (Suit suit : Suit.values()) {
|
||||||
|
deck.cards.push(new Card(rank, suit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deck;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deals a card from the top of the deck.
|
||||||
|
*/
|
||||||
|
public Card deal() {
|
||||||
|
return cards.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuffles the deck.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shuffle() {
|
||||||
|
Collections.shuffle((List<Card>) cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return cards.toString();
|
||||||
|
}
|
||||||
|
}
|
195
src/main/java/com/charego/freecellfx/model/Game.java
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package com.charego.freecellfx.model;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.pile.Cell;
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
import com.charego.freecellfx.model.action.MoveAction;
|
||||||
|
import com.charego.freecellfx.model.pile.Foundation;
|
||||||
|
import com.charego.freecellfx.model.pile.Tableau;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Game {
|
||||||
|
private List<Cell> cells = new ArrayList<>(4);
|
||||||
|
private List<Foundation> foundations = new ArrayList<>(4);
|
||||||
|
private List<Tableau> tableaux = new ArrayList<>(8);
|
||||||
|
private MoveTracker moveTracker = new MoveTracker();
|
||||||
|
|
||||||
|
public Game() {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
cells.add(new Cell());
|
||||||
|
foundations.add(new Foundation());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
tableaux.add(new Tableau());
|
||||||
|
newGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Cell> getCells() {
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Foundation> getFoundations() {
|
||||||
|
return foundations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tableau> getTableaux() {
|
||||||
|
return tableaux;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves cards from one pile to another, if the move is valid.
|
||||||
|
*/
|
||||||
|
public boolean tryMove(Pile fromPile, Pile toPile) {
|
||||||
|
int cardsMoved = toPile.moveFrom(fromPile);
|
||||||
|
if (cardsMoved > 0) {
|
||||||
|
moveTracker.addMove(new MoveAction(fromPile, toPile, cardsMoved));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redo a move that was previously undone.
|
||||||
|
*
|
||||||
|
* @return true if another redo can be performed
|
||||||
|
*/
|
||||||
|
public boolean redo() {
|
||||||
|
if (moveTracker.hasNextMove()) {
|
||||||
|
moveTracker.getNextMove().redo();
|
||||||
|
}
|
||||||
|
return moveTracker.hasNextMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo a previous move.
|
||||||
|
*
|
||||||
|
* @return true if another undo can be performed
|
||||||
|
*/
|
||||||
|
public boolean undo() {
|
||||||
|
if (moveTracker.hasLastMove()) {
|
||||||
|
moveTracker.getLastMove().undo();
|
||||||
|
}
|
||||||
|
return moveTracker.hasLastMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the game cannot be lost.
|
||||||
|
*/
|
||||||
|
public boolean isWon() {
|
||||||
|
for (Pile pile : tableaux) {
|
||||||
|
if (!pile.isInOrder()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the game cannot be won.
|
||||||
|
*/
|
||||||
|
public boolean isLost() {
|
||||||
|
// Are free cells full?
|
||||||
|
for (Pile pile : cells) {
|
||||||
|
if (pile.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Can you not move to any tableau?
|
||||||
|
for (Pile pile : tableaux) {
|
||||||
|
for (Pile tableau : tableaux) {
|
||||||
|
if (pile.canMoveFrom(tableau)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Pile cell : cells) {
|
||||||
|
if (pile.canMoveFrom(cell)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Can you not move to any home cell?
|
||||||
|
for (Pile pile : foundations) {
|
||||||
|
for (Pile tableau : tableaux) {
|
||||||
|
if (pile.canMoveFrom(tableau)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Pile cell : cells) {
|
||||||
|
if (pile.canMoveFrom(cell)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newGame() {
|
||||||
|
Deck deck = Deck.newDeck();
|
||||||
|
deck.shuffle();
|
||||||
|
moveTracker.clearMoves();
|
||||||
|
tableaux.forEach(Pile::clear);
|
||||||
|
cells.forEach(Pile::clear);
|
||||||
|
foundations.forEach(Pile::clear);
|
||||||
|
// Deal 6 cards to each tableau.
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
Card card = deck.deal();
|
||||||
|
card.turn();
|
||||||
|
tableaux.get(j).addCard(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Deal an additional card to first 4.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Card card = deck.deal();
|
||||||
|
card.turn();
|
||||||
|
tableaux.get(i).addCard(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MoveTracker {
|
||||||
|
private final Deque<MoveAction> nextMoves = new LinkedList<>();
|
||||||
|
private final Deque<MoveAction> previousMoves = new LinkedList<>();
|
||||||
|
|
||||||
|
public void clearMoves() {
|
||||||
|
nextMoves.clear();
|
||||||
|
previousMoves.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLastMove() {
|
||||||
|
return !previousMoves.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoveAction getLastMove() {
|
||||||
|
MoveAction lastMove = previousMoves.pop();
|
||||||
|
nextMoves.push(lastMove);
|
||||||
|
return lastMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNextMove() {
|
||||||
|
return !nextMoves.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoveAction getNextMove() {
|
||||||
|
MoveAction nextMove = nextMoves.pop();
|
||||||
|
previousMoves.push(nextMove);
|
||||||
|
return nextMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMove(MoveAction move) {
|
||||||
|
MoveAction nextMove = nextMoves.peek();
|
||||||
|
/*
|
||||||
|
* if new move differs from saved next move, clear the remaining
|
||||||
|
* next moves
|
||||||
|
*/
|
||||||
|
if (move.equals(nextMove)) {
|
||||||
|
nextMoves.pop();
|
||||||
|
} else {
|
||||||
|
nextMoves.clear();
|
||||||
|
}
|
||||||
|
previousMoves.push(move);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.charego.freecellfx.model.action;
|
||||||
|
|
||||||
|
public interface Action {
|
||||||
|
void redo();
|
||||||
|
|
||||||
|
void undo();
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.charego.freecellfx.model.action;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
|
||||||
|
public class MoveAction implements Action {
|
||||||
|
private final Pile fromPile;
|
||||||
|
private final Pile toPile;
|
||||||
|
private final int numCards;
|
||||||
|
|
||||||
|
public MoveAction(Pile fromPile, Pile toPile, int numCards) {
|
||||||
|
this.fromPile = fromPile;
|
||||||
|
this.toPile = toPile;
|
||||||
|
this.numCards = numCards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void redo() {
|
||||||
|
toPile.moveFromBlindly(fromPile, numCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undo() {
|
||||||
|
fromPile.moveFromBlindly(toPile, numCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == null || !(other instanceof MoveAction)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MoveAction action = (MoveAction) other;
|
||||||
|
return fromPile == action.fromPile
|
||||||
|
&& toPile == action.toPile
|
||||||
|
&& numCards == action.numCards;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.charego.freecellfx.model.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
abstract class AbstractPile implements Pile {
|
||||||
|
|
||||||
|
private Deque<Card> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCard(Card card) {
|
||||||
|
stack.push(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card removeCard() {
|
||||||
|
return stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card topCard() {
|
||||||
|
return stack.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
stack.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return stack.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return stack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Card> iterator() {
|
||||||
|
return stack.descendingIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/main/java/com/charego/freecellfx/model/pile/Cell.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package com.charego.freecellfx.model.pile;
|
||||||
|
|
||||||
|
public class Cell extends AbstractPile {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canMoveFrom(Pile other) {
|
||||||
|
return isEmpty() && !other.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int moveFrom(Pile other) {
|
||||||
|
if (canMoveFrom(other)) {
|
||||||
|
addCard(other.removeCard());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
|
if (numCards != 1) {
|
||||||
|
throw new IllegalArgumentException("numCards must be 1");
|
||||||
|
}
|
||||||
|
addCard(other.removeCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInOrder() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.charego.freecellfx.model.pile;
|
||||||
|
|
||||||
|
import static com.charego.freecellfx.model.Card.Rank;
|
||||||
|
import static com.charego.freecellfx.model.Card.Suit;
|
||||||
|
|
||||||
|
public class Foundation extends AbstractPile {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canMoveFrom(Pile other) {
|
||||||
|
if (other.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Rank otherRank = other.topCard().rank;
|
||||||
|
Suit otherSuit = other.topCard().suit;
|
||||||
|
if (isEmpty()) {
|
||||||
|
return otherRank == Rank.ACE;
|
||||||
|
}
|
||||||
|
Rank thisRank = topCard().rank;
|
||||||
|
Suit thisSuit = topCard().suit;
|
||||||
|
return otherSuit == thisSuit
|
||||||
|
&& otherRank.value == thisRank.value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int moveFrom(Pile other) {
|
||||||
|
if (canMoveFrom(other)) {
|
||||||
|
addCard(other.removeCard());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
|
if (numCards != 1) {
|
||||||
|
throw new IllegalArgumentException("numCards must be 1");
|
||||||
|
}
|
||||||
|
addCard(other.removeCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInOrder() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
src/main/java/com/charego/freecellfx/model/pile/Pile.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package com.charego.freecellfx.model.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
|
||||||
|
public interface Pile extends Iterable<Card> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a card to the top of the pile.
|
||||||
|
*/
|
||||||
|
void addCard(Card card);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the top card from the pile.
|
||||||
|
*/
|
||||||
|
Card removeCard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the contents of another pile can be moved to this pile.
|
||||||
|
*/
|
||||||
|
boolean canMoveFrom(Pile other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves cards from another pile to this pile, if the tryMove is valid.
|
||||||
|
*
|
||||||
|
* @return the number of cards moved (i.e., possibly 0)
|
||||||
|
*/
|
||||||
|
int moveFrom(Pile other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves cards from another pile to this pile, whether or not the tryMove is valid.
|
||||||
|
*/
|
||||||
|
void moveFromBlindly(Pile other, int numCards);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the top card in the pile, if a card is present.
|
||||||
|
*/
|
||||||
|
Card topCard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the contents of the pile are in order.
|
||||||
|
*/
|
||||||
|
boolean isInOrder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the contents of the pile.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the pile is empty.
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the pile.
|
||||||
|
*/
|
||||||
|
int size();
|
||||||
|
|
||||||
|
}
|
140
src/main/java/com/charego/freecellfx/model/pile/Tableau.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.charego.freecellfx.model.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
public class Tableau extends AbstractPile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains the ordering of the cards within the pile.
|
||||||
|
*/
|
||||||
|
private Deque<Integer> orderStack = new LinkedList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCard(Card card) {
|
||||||
|
countAddedCard(card);
|
||||||
|
super.addCard(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countAddedCard(Card card) {
|
||||||
|
if (isEmpty() || orderStack.isEmpty()) {
|
||||||
|
orderStack.push(1);
|
||||||
|
} else if (card.color != topCard().color &&
|
||||||
|
card.rank.value == topCard().rank.value - 1) {
|
||||||
|
orderStack.push(orderStack.pop() + 1);
|
||||||
|
} else {
|
||||||
|
orderStack.push(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card removeCard() {
|
||||||
|
countRemovedCard();
|
||||||
|
return super.removeCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countRemovedCard() {
|
||||||
|
int topInOrder = orderStack.pop();
|
||||||
|
if (topInOrder > 1) {
|
||||||
|
orderStack.push(topInOrder - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canMoveFrom(Pile other) {
|
||||||
|
if (other.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
||||||
|
if (rankDiff < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other instanceof Foundation || other instanceof Cell) {
|
||||||
|
return rankDiff == 1 && topCard().color != other.topCard().color;
|
||||||
|
}
|
||||||
|
if (other instanceof Tableau) {
|
||||||
|
int otherTopInOrder = ((Tableau) other).topInOrder();
|
||||||
|
if (otherTopInOrder >= rankDiff) {
|
||||||
|
switch (rankDiff % 2) {
|
||||||
|
case 0:
|
||||||
|
return topCard().color == other.topCard().color;
|
||||||
|
case 1:
|
||||||
|
return topCard().color != other.topCard().color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int moveFrom(Pile other) {
|
||||||
|
if (!canMoveFrom(other)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (other instanceof Foundation || other instanceof Cell) {
|
||||||
|
addCard(other.removeCard());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (other instanceof Tableau) {
|
||||||
|
if (isEmpty()) {
|
||||||
|
// Move all ordered cards to this pile
|
||||||
|
int otherTopInOrder = ((Tableau) other).topInOrder();
|
||||||
|
moveFrom(other, otherTopInOrder);
|
||||||
|
return otherTopInOrder;
|
||||||
|
} else {
|
||||||
|
// Move ordered cards until they reach the current top card
|
||||||
|
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
||||||
|
moveFrom(other, rankDiff);
|
||||||
|
return rankDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves {@code n} cards from the other pile to this pile.
|
||||||
|
*/
|
||||||
|
private void moveFrom(Pile other, int n) {
|
||||||
|
Deque<Card> topCards = new LinkedList<>();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
topCards.addLast(other.removeCard());
|
||||||
|
}
|
||||||
|
while (!topCards.isEmpty()) {
|
||||||
|
addCard(topCards.removeLast());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
|
moveFrom(other, numCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
super.clear();
|
||||||
|
orderStack.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInOrder() {
|
||||||
|
return (orderStack.size() < 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of cards in order at the top of the pile.
|
||||||
|
*/
|
||||||
|
public int topInOrder() {
|
||||||
|
return orderStack.size() == 0 ? 0 : orderStack.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return orderStack.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.charego.freecellfx.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a pair of coordinates to an object.
|
||||||
|
*
|
||||||
|
* @param <K1> the first coordinate
|
||||||
|
* @param <K2> the second coordinate
|
||||||
|
* @param <V> the treasure
|
||||||
|
*/
|
||||||
|
public class DoubleKeyedMap<K1, K2, V> {
|
||||||
|
|
||||||
|
private Map<K1, Map<K2, V>> map = new HashMap<>();
|
||||||
|
|
||||||
|
public boolean contains(K1 firstKey, K2 secondKey) {
|
||||||
|
return map.containsKey(firstKey) && map.get(firstKey).containsKey(secondKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V get(K1 firstKey, K2 secondKey) {
|
||||||
|
return map.get(firstKey).get(secondKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the old value if it exists, or null.
|
||||||
|
*/
|
||||||
|
public V put(K1 firstKey, K2 secondKey, V newValue) {
|
||||||
|
if (!map.containsKey(firstKey)) {
|
||||||
|
map.put(firstKey, new HashMap<>());
|
||||||
|
}
|
||||||
|
return map.get(firstKey).put(secondKey, newValue);
|
||||||
|
}
|
||||||
|
}
|
169
src/main/java/com/charego/freecellfx/view/GameCanvas.java
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package com.charego.freecellfx.view;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
import com.charego.freecellfx.model.Game;
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
import com.charego.freecellfx.view.pile.CascadingPileView;
|
||||||
|
import com.charego.freecellfx.view.pile.PileView;
|
||||||
|
import com.charego.freecellfx.view.pile.StackedPileView;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GameCanvas extends Canvas {
|
||||||
|
|
||||||
|
private final Game game;
|
||||||
|
private final GraphicsContext gc;
|
||||||
|
private final PileView[] pileViews;
|
||||||
|
|
||||||
|
private PileView fromPile;
|
||||||
|
private boolean winMessageShown;
|
||||||
|
|
||||||
|
public GameCanvas(Game game, double width, double height) {
|
||||||
|
super(width, height);
|
||||||
|
this.game = game;
|
||||||
|
this.gc = getGraphicsContext2D();
|
||||||
|
this.pileViews = constructPileViews(game);
|
||||||
|
setOnKeyReleased(new KeyReleaseHandler());
|
||||||
|
setOnMouseClicked(new MouseClickHandler());
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PileView[] constructPileViews(Game game) {
|
||||||
|
List<PileView> list = new ArrayList<>();
|
||||||
|
for (Pile pile : game.getCells()) {
|
||||||
|
list.add(new StackedPileView(pile));
|
||||||
|
}
|
||||||
|
for (Pile pile : game.getFoundations()) {
|
||||||
|
list.add(new StackedPileView(pile));
|
||||||
|
}
|
||||||
|
for (Pile pile : game.getTableaux()) {
|
||||||
|
list.add(new CascadingPileView(pile));
|
||||||
|
}
|
||||||
|
return list.toArray(new PileView[16]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEndingConditions() {
|
||||||
|
if (winMessageShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (game.isWon()) {
|
||||||
|
winMessageShown = true;
|
||||||
|
new Alert(Alert.AlertType.CONFIRMATION, "You won!").showAndWait();
|
||||||
|
} else if (game.isLost()) {
|
||||||
|
new Alert(Alert.AlertType.CONFIRMATION, "You lost!").showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PileView findPile(double x, double y) {
|
||||||
|
int columnIndex = 0;
|
||||||
|
x -= 15;
|
||||||
|
while (x > 90) {
|
||||||
|
x -= 90;
|
||||||
|
columnIndex++;
|
||||||
|
}
|
||||||
|
boolean columnPressed = x < Card.width;
|
||||||
|
if (!columnPressed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (y > 15 && y < 15 + Card.height) {
|
||||||
|
return pileViews[columnIndex];
|
||||||
|
}
|
||||||
|
if (y > 120) {
|
||||||
|
PileView pileView = pileViews[columnIndex + 8];
|
||||||
|
Pile pile = pileView.getPile();
|
||||||
|
double pileHeight = 120 + (pile.size() - 1) * CascadingPileView.CARD_MARGIN + Card.height;
|
||||||
|
if (y < pileHeight) {
|
||||||
|
return pileView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateView() {
|
||||||
|
gc.clearRect(0, 0, super.getWidth(), super.getHeight());
|
||||||
|
double x = 15;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
pileViews[i].paint(gc, x, 15);
|
||||||
|
x += 90;
|
||||||
|
}
|
||||||
|
x = 15;
|
||||||
|
for (int i = 8; i < 16; i++) {
|
||||||
|
pileViews[i].paint(gc, x, 120);
|
||||||
|
x += 90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyReleaseHandler implements EventHandler<KeyEvent> {
|
||||||
|
@Override
|
||||||
|
public void handle(KeyEvent e) {
|
||||||
|
if (e.isControlDown()) {
|
||||||
|
if (e.getCode() == KeyCode.Y) {
|
||||||
|
game.redo();
|
||||||
|
updateView();
|
||||||
|
} else if (e.getCode() == KeyCode.Z) {
|
||||||
|
game.undo();
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.getCode() == KeyCode.F2) {
|
||||||
|
game.newGame();
|
||||||
|
winMessageShown = false;
|
||||||
|
if (fromPile != null && fromPile.isSelected()) {
|
||||||
|
fromPile.toggleSelected();
|
||||||
|
}
|
||||||
|
fromPile = null;
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MouseClickHandler implements EventHandler<MouseEvent> {
|
||||||
|
@Override
|
||||||
|
public void handle(MouseEvent e) {
|
||||||
|
PileView clickedPile = findPile(e.getX(), e.getY());
|
||||||
|
if (clickedPile == null) {
|
||||||
|
if (fromPile != null) {
|
||||||
|
fromPile.toggleSelected();
|
||||||
|
}
|
||||||
|
fromPile = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fromPile == null) {
|
||||||
|
fromPile = clickedPile;
|
||||||
|
fromPile.toggleSelected();
|
||||||
|
updateView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fromPile == clickedPile) {
|
||||||
|
// Double click: try moving to a foundation.
|
||||||
|
fromPile.toggleSelected();
|
||||||
|
for (int i = 4; i < 8; i++) {
|
||||||
|
if (game.tryMove(fromPile.getPile(), pileViews[i].getPile())) {
|
||||||
|
updateView();
|
||||||
|
checkEndingConditions();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fromPile = null;
|
||||||
|
} else {
|
||||||
|
// Try moving to other cell.
|
||||||
|
fromPile.toggleSelected();
|
||||||
|
if (game.tryMove(fromPile.getPile(), clickedPile.getPile())) {
|
||||||
|
updateView();
|
||||||
|
checkEndingConditions();
|
||||||
|
}
|
||||||
|
fromPile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.charego.freecellfx.view.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
import com.charego.freecellfx.model.pile.Tableau;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public class CascadingPileView extends PileView {
|
||||||
|
|
||||||
|
public static final int CARD_MARGIN = 22;
|
||||||
|
public static final int TOP_MARGIN = 0;
|
||||||
|
|
||||||
|
public CascadingPileView(Pile pile) {
|
||||||
|
super(pile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(GraphicsContext gc, double x, double y) {
|
||||||
|
if (pile.isEmpty()) {
|
||||||
|
// draw an outline
|
||||||
|
gc.setStroke(Color.YELLOW);
|
||||||
|
gc.strokeRect(x, y, Card.width, Card.height);
|
||||||
|
if (isSelected()) {
|
||||||
|
// highlight empty cell
|
||||||
|
gc.setFill(highlight);
|
||||||
|
gc.fillRect(x, y, Card.width, Card.height);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
double yCurrent = y;
|
||||||
|
// draw the cards in a cascade from top to bottom
|
||||||
|
for (Card card : pile) {
|
||||||
|
gc.drawImage(card.image(), x, yCurrent);
|
||||||
|
yCurrent += CARD_MARGIN;
|
||||||
|
}
|
||||||
|
if (isSelected()) {
|
||||||
|
// highlight ordered cards
|
||||||
|
if (yCurrent > TOP_MARGIN) {
|
||||||
|
yCurrent -= CARD_MARGIN;
|
||||||
|
}
|
||||||
|
int multiplier = ((Tableau) pile).topInOrder();
|
||||||
|
int heightOfRect = (multiplier - 1) * CARD_MARGIN;
|
||||||
|
gc.setFill(highlight);
|
||||||
|
gc.fillRect(x, yCurrent - heightOfRect, Card.width, heightOfRect
|
||||||
|
+ Card.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/main/java/com/charego/freecellfx/view/pile/PileView.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package com.charego.freecellfx.view.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public abstract class PileView {
|
||||||
|
|
||||||
|
protected static final Color highlight = new Color(1, 1, 0, 0.6);
|
||||||
|
protected final Pile pile;
|
||||||
|
private boolean isSelected = false;
|
||||||
|
|
||||||
|
public PileView(Pile pile) {
|
||||||
|
this.pile = pile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pile getPile() {
|
||||||
|
return pile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void paint(GraphicsContext gc, double x, double y);
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return isSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSelected() {
|
||||||
|
isSelected = !isSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.charego.freecellfx.view.pile;
|
||||||
|
|
||||||
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
import com.charego.freecellfx.model.Card;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public class StackedPileView extends PileView {
|
||||||
|
|
||||||
|
public StackedPileView(Pile pile) {
|
||||||
|
super(pile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(GraphicsContext gc, double x, double y) {
|
||||||
|
if (pile.isEmpty()) {
|
||||||
|
// draw an outline
|
||||||
|
gc.setStroke(Color.YELLOW);
|
||||||
|
gc.strokeRect(x, y, Card.width, Card.height);
|
||||||
|
} else {
|
||||||
|
// draw the top card
|
||||||
|
gc.drawImage(pile.topCard().image(), x, y);
|
||||||
|
}
|
||||||
|
if (isSelected()) {
|
||||||
|
gc.setFill(highlight);
|
||||||
|
gc.fillRect(x, y, Card.width, Card.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
src/main/resources/deck/10C.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
src/main/resources/deck/10D.png
Normal file
After Width: | Height: | Size: 554 B |
BIN
src/main/resources/deck/10H.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
src/main/resources/deck/10S.png
Normal file
After Width: | Height: | Size: 596 B |
BIN
src/main/resources/deck/11C.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/main/resources/deck/11D.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/11H.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/11S.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/12C.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/12D.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/12H.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/main/resources/deck/12S.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/main/resources/deck/13C.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/13D.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/13H.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/main/resources/deck/13S.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/deck/1C.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
src/main/resources/deck/1D.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
src/main/resources/deck/1H.png
Normal file
After Width: | Height: | Size: 453 B |
BIN
src/main/resources/deck/1S.png
Normal file
After Width: | Height: | Size: 583 B |
BIN
src/main/resources/deck/2C.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
src/main/resources/deck/2D.png
Normal file
After Width: | Height: | Size: 411 B |
BIN
src/main/resources/deck/2H.png
Normal file
After Width: | Height: | Size: 474 B |
BIN
src/main/resources/deck/2S.png
Normal file
After Width: | Height: | Size: 463 B |
BIN
src/main/resources/deck/3C.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
src/main/resources/deck/3D.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
src/main/resources/deck/3H.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
src/main/resources/deck/3S.png
Normal file
After Width: | Height: | Size: 511 B |
BIN
src/main/resources/deck/4C.png
Normal file
After Width: | Height: | Size: 474 B |
BIN
src/main/resources/deck/4D.png
Normal file
After Width: | Height: | Size: 435 B |
BIN
src/main/resources/deck/4H.png
Normal file
After Width: | Height: | Size: 494 B |
BIN
src/main/resources/deck/4S.png
Normal file
After Width: | Height: | Size: 486 B |
BIN
src/main/resources/deck/5C.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
src/main/resources/deck/5D.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
src/main/resources/deck/5H.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
src/main/resources/deck/5S.png
Normal file
After Width: | Height: | Size: 558 B |
BIN
src/main/resources/deck/6C.png
Normal file
After Width: | Height: | Size: 510 B |
BIN
src/main/resources/deck/6D.png
Normal file
After Width: | Height: | Size: 463 B |
BIN
src/main/resources/deck/6H.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
src/main/resources/deck/6S.png
Normal file
After Width: | Height: | Size: 520 B |
BIN
src/main/resources/deck/7C.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
src/main/resources/deck/7D.png
Normal file
After Width: | Height: | Size: 513 B |
BIN
src/main/resources/deck/7H.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
src/main/resources/deck/7S.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
src/main/resources/deck/8C.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
src/main/resources/deck/8D.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
src/main/resources/deck/8H.png
Normal file
After Width: | Height: | Size: 625 B |
BIN
src/main/resources/deck/8S.png
Normal file
After Width: | Height: | Size: 618 B |
BIN
src/main/resources/deck/9C.png
Normal file
After Width: | Height: | Size: 679 B |
BIN
src/main/resources/deck/9D.png
Normal file
After Width: | Height: | Size: 533 B |
BIN
src/main/resources/deck/9H.png
Normal file
After Width: | Height: | Size: 634 B |
BIN
src/main/resources/deck/9S.png
Normal file
After Width: | Height: | Size: 608 B |
BIN
src/main/resources/deck/CARDBACK.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
src/main/resources/deck/FELT.jpg
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
src/main/resources/icons/DIAMOND.jpg
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
src/main/resources/icons/LOST.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/main/resources/icons/WON.png
Normal file
After Width: | Height: | Size: 1.8 KiB |