Initial commit

This commit is contained in:
Charles Gould 2015-04-18 01:26:08 -04:00
commit c2bcbe9f32
75 changed files with 1120 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Maven
target/
# Eclipse
.settings/
*.classpath
*.project
# IntelliJ IDEA
*.idea
*.iml

39
pom.xml Normal file
View 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>

View File

@ -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();
}
}

View 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;
}
}
}

View 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();
}
}

View 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);
}
}
}

View File

@ -0,0 +1,7 @@
package com.charego.freecellfx.model.action;
public interface Action {
void redo();
void undo();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View 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;
}
}

View File

@ -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;
}
}

View 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();
}

View 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();
}
}

View File

@ -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);
}
}

View 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;
}
}
}
}

View File

@ -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);
}
}
}
}

View 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;
}
}

View File

@ -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);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB