Format using tabs
This commit is contained in:
parent
1b0d06f20c
commit
43bf9f9adf
@ -12,32 +12,30 @@ import javafx.stage.Stage;
|
||||
|
||||
public class FreeCellApplication extends Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(FreeCellApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
launch(FreeCellApplication.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
VBox root = new VBox();
|
||||
root.setBackground(new Background(new BackgroundImage(new Image(
|
||||
"/deck/FELT.jpg"), BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER,
|
||||
BackgroundSize.DEFAULT)));
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
VBox root = new VBox();
|
||||
root.setBackground(new Background(new BackgroundImage(new Image("/deck/FELT.jpg"), BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, BackgroundSize.DEFAULT)));
|
||||
|
||||
Game game = new Game();
|
||||
GameMenuBar menuBar = new GameMenuBar();
|
||||
GameCanvas canvas = new GameCanvas(game, 731, 600);
|
||||
menuBar.setNewGameAction(canvas.getNewGameAction());
|
||||
menuBar.setUndoAction(canvas.getUndoAction());
|
||||
menuBar.setRedoAction(canvas.getRedoAction());
|
||||
menuBar.setExitAction(e -> Platform.exit());
|
||||
root.getChildren().addAll(menuBar, canvas);
|
||||
Scene scene = new Scene(root);
|
||||
Game game = new Game();
|
||||
GameMenuBar menuBar = new GameMenuBar();
|
||||
GameCanvas canvas = new GameCanvas(game, 731, 600);
|
||||
menuBar.setNewGameAction(canvas.getNewGameAction());
|
||||
menuBar.setUndoAction(canvas.getUndoAction());
|
||||
menuBar.setRedoAction(canvas.getRedoAction());
|
||||
menuBar.setExitAction(e -> Platform.exit());
|
||||
root.getChildren().addAll(menuBar, canvas);
|
||||
Scene scene = new Scene(root);
|
||||
|
||||
stage.setTitle("FreeCell");
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.show();
|
||||
}
|
||||
stage.setTitle("FreeCell");
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,95 +10,94 @@ import javafx.scene.paint.Color;
|
||||
*/
|
||||
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();
|
||||
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 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;
|
||||
}
|
||||
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 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 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();
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return rank + " of " + suit;
|
||||
}
|
||||
|
||||
public enum Rank {
|
||||
ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 1 for Ace up to 13 for King.
|
||||
*/
|
||||
public final int value = ordinal() + 1;
|
||||
}
|
||||
|
||||
public enum Suit {
|
||||
SPADES, HEARTS, DIAMONDS, CLUBS;
|
||||
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);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Black or red.
|
||||
*/
|
||||
public Color color() {
|
||||
return (this == SPADES || this == CLUBS) ? Color.BLACK : Color.RED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,41 +13,41 @@ import java.util.List;
|
||||
*/
|
||||
public class Deck {
|
||||
|
||||
private Deque<Card> cards = new LinkedList<>();
|
||||
private Deque<Card> cards = new LinkedList<>();
|
||||
|
||||
private Deck() {
|
||||
}
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* Shuffles the deck.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shuffle() {
|
||||
Collections.shuffle((List<Card>) cards);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cards.toString();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return cards.toString();
|
||||
}
|
||||
}
|
@ -12,184 +12,184 @@ 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();
|
||||
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 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<Cell> getCells() {
|
||||
return cells;
|
||||
}
|
||||
|
||||
public List<Foundation> getFoundations() {
|
||||
return foundations;
|
||||
}
|
||||
public List<Foundation> getFoundations() {
|
||||
return foundations;
|
||||
}
|
||||
|
||||
public List<Tableau> getTableaux() {
|
||||
return tableaux;
|
||||
}
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
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<>();
|
||||
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 void clearMoves() {
|
||||
nextMoves.clear();
|
||||
previousMoves.clear();
|
||||
}
|
||||
|
||||
public boolean hasLastMove() {
|
||||
return !previousMoves.isEmpty();
|
||||
}
|
||||
public boolean hasLastMove() {
|
||||
return !previousMoves.isEmpty();
|
||||
}
|
||||
|
||||
public MoveAction getLastMove() {
|
||||
MoveAction lastMove = previousMoves.pop();
|
||||
nextMoves.push(lastMove);
|
||||
return lastMove;
|
||||
}
|
||||
public MoveAction getLastMove() {
|
||||
MoveAction lastMove = previousMoves.pop();
|
||||
nextMoves.push(lastMove);
|
||||
return lastMove;
|
||||
}
|
||||
|
||||
public boolean hasNextMove() {
|
||||
return !nextMoves.isEmpty();
|
||||
}
|
||||
public boolean hasNextMove() {
|
||||
return !nextMoves.isEmpty();
|
||||
}
|
||||
|
||||
public MoveAction getNextMove() {
|
||||
MoveAction nextMove = nextMoves.pop();
|
||||
previousMoves.push(nextMove);
|
||||
return nextMove;
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (move.equals(nextMove)) {
|
||||
nextMoves.pop();
|
||||
} else {
|
||||
nextMoves.clear();
|
||||
}
|
||||
previousMoves.push(move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.charego.freecellfx.model.action;
|
||||
|
||||
public interface Action {
|
||||
void redo();
|
||||
void redo();
|
||||
|
||||
void undo();
|
||||
void undo();
|
||||
}
|
||||
|
@ -3,34 +3,32 @@ 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;
|
||||
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;
|
||||
}
|
||||
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 redo() {
|
||||
toPile.moveFromBlindly(fromPile, numCards);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
fromPile.moveFromBlindly(toPile, 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;
|
||||
}
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
@ -8,41 +8,41 @@ import java.util.Iterator;
|
||||
|
||||
abstract class AbstractPile implements Pile {
|
||||
|
||||
private Deque<Card> stack = new ArrayDeque<>();
|
||||
private Deque<Card> stack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void addCard(Card card) {
|
||||
stack.push(card);
|
||||
}
|
||||
@Override
|
||||
public void addCard(Card card) {
|
||||
stack.push(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card removeCard() {
|
||||
return stack.pop();
|
||||
}
|
||||
@Override
|
||||
public Card removeCard() {
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card topCard() {
|
||||
return stack.peek();
|
||||
}
|
||||
@Override
|
||||
public Card topCard() {
|
||||
return stack.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
stack.clear();
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return stack.isEmpty();
|
||||
}
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return stack.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Card> iterator() {
|
||||
return stack.descendingIterator();
|
||||
}
|
||||
@Override
|
||||
public Iterator<Card> iterator() {
|
||||
return stack.descendingIterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,31 +2,31 @@ package com.charego.freecellfx.model.pile;
|
||||
|
||||
public class Cell extends AbstractPile {
|
||||
|
||||
@Override
|
||||
public boolean canMoveFrom(Pile other) {
|
||||
return isEmpty() && !other.isEmpty();
|
||||
}
|
||||
@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 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 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;
|
||||
}
|
||||
@Override
|
||||
public boolean isInOrder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,42 +5,41 @@ 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 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 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 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;
|
||||
}
|
||||
@Override
|
||||
public boolean isInOrder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,56 +4,57 @@ 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);
|
||||
/**
|
||||
* Adds a card to the top of the pile.
|
||||
*/
|
||||
void addCard(Card card);
|
||||
|
||||
/**
|
||||
* Removes the top card from the pile.
|
||||
*/
|
||||
Card removeCard();
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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, 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);
|
||||
/**
|
||||
* 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 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();
|
||||
/**
|
||||
* Returns true if the contents of the pile are in order.
|
||||
*/
|
||||
boolean isInOrder();
|
||||
|
||||
/**
|
||||
* Clears the contents of the pile.
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* Clears the contents of the pile.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Returns true if the pile is empty.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
/**
|
||||
* Returns true if the pile is empty.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Returns the size of the pile.
|
||||
*/
|
||||
int size();
|
||||
/**
|
||||
* Returns the size of the pile.
|
||||
*/
|
||||
int size();
|
||||
|
||||
}
|
||||
|
@ -7,134 +7,133 @@ import java.util.LinkedList;
|
||||
|
||||
public class Tableau extends AbstractPile {
|
||||
|
||||
/**
|
||||
* Maintains the ordering of the cards within the pile.
|
||||
*/
|
||||
private Deque<Integer> orderStack = new LinkedList<>();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
@Override
|
||||
public Card removeCard() {
|
||||
countRemovedCard();
|
||||
return super.removeCard();
|
||||
}
|
||||
|
||||
private void countRemovedCard() {
|
||||
int topInOrder = orderStack.pop();
|
||||
if (topInOrder > 1) {
|
||||
orderStack.push(topInOrder - 1);
|
||||
}
|
||||
}
|
||||
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 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();
|
||||
}
|
||||
@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());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 moveFromBlindly(Pile other, int numCards) {
|
||||
moveFrom(other, numCards);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
orderStack.clear();
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
orderStack.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInOrder() {
|
||||
return (orderStack.size() < 2);
|
||||
}
|
||||
@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();
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return orderStack.toString();
|
||||
}
|
||||
|
||||
}
|
@ -6,29 +6,32 @@ 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
|
||||
* @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<>();
|
||||
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 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -18,152 +18,152 @@ import java.util.List;
|
||||
|
||||
public class GameCanvas extends Canvas {
|
||||
|
||||
private final Game game;
|
||||
private final GraphicsContext gc;
|
||||
private final PileView[] pileViews;
|
||||
private final Game game;
|
||||
private final GraphicsContext gc;
|
||||
private final PileView[] pileViews;
|
||||
|
||||
private PileView fromPile;
|
||||
private boolean winMessageShown;
|
||||
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);
|
||||
setOnMouseClicked(new MouseClickHandler());
|
||||
updateView();
|
||||
}
|
||||
public GameCanvas(Game game, double width, double height) {
|
||||
super(width, height);
|
||||
this.game = game;
|
||||
this.gc = getGraphicsContext2D();
|
||||
this.pileViews = constructPileViews(game);
|
||||
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 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.INFORMATION, "You won!").showAndWait();
|
||||
} else if (game.isLost()) {
|
||||
new Alert(Alert.AlertType.INFORMATION, "You lost!").showAndWait();
|
||||
}
|
||||
}
|
||||
private void checkEndingConditions() {
|
||||
if (winMessageShown) {
|
||||
return;
|
||||
}
|
||||
if (game.isWon()) {
|
||||
winMessageShown = true;
|
||||
new Alert(Alert.AlertType.INFORMATION, "You won!").showAndWait();
|
||||
} else if (game.isLost()) {
|
||||
new Alert(Alert.AlertType.INFORMATION, "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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public EventHandler<ActionEvent> getNewGameAction() {
|
||||
return e -> {
|
||||
game.newGame();
|
||||
winMessageShown = false;
|
||||
if (fromPile != null && fromPile.isSelected()) {
|
||||
fromPile.toggleSelected();
|
||||
}
|
||||
fromPile = null;
|
||||
updateView();
|
||||
};
|
||||
}
|
||||
public EventHandler<ActionEvent> getNewGameAction() {
|
||||
return e -> {
|
||||
game.newGame();
|
||||
winMessageShown = false;
|
||||
if (fromPile != null && fromPile.isSelected()) {
|
||||
fromPile.toggleSelected();
|
||||
}
|
||||
fromPile = null;
|
||||
updateView();
|
||||
};
|
||||
}
|
||||
|
||||
public EventHandler<ActionEvent> getUndoAction() {
|
||||
return e -> {
|
||||
game.undo();
|
||||
updateView();
|
||||
};
|
||||
}
|
||||
public EventHandler<ActionEvent> getUndoAction() {
|
||||
return e -> {
|
||||
game.undo();
|
||||
updateView();
|
||||
};
|
||||
}
|
||||
|
||||
public EventHandler<ActionEvent> getRedoAction() {
|
||||
return e -> {
|
||||
game.redo();
|
||||
updateView();
|
||||
};
|
||||
}
|
||||
public EventHandler<ActionEvent> getRedoAction() {
|
||||
return e -> {
|
||||
game.redo();
|
||||
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;
|
||||
updateView();
|
||||
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())) {
|
||||
checkEndingConditions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
fromPile = null;
|
||||
updateView();
|
||||
} else {
|
||||
// Try moving to other cell.
|
||||
fromPile.toggleSelected();
|
||||
if (game.tryMove(fromPile.getPile(), clickedPile.getPile())) {
|
||||
checkEndingConditions();
|
||||
}
|
||||
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;
|
||||
updateView();
|
||||
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())) {
|
||||
checkEndingConditions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
fromPile = null;
|
||||
updateView();
|
||||
} else {
|
||||
// Try moving to other cell.
|
||||
fromPile.toggleSelected();
|
||||
if (game.tryMove(fromPile.getPile(), clickedPile.getPile())) {
|
||||
checkEndingConditions();
|
||||
}
|
||||
fromPile = null;
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,44 +11,44 @@ import javafx.scene.input.KeyCombination;
|
||||
|
||||
public class GameMenuBar extends MenuBar {
|
||||
|
||||
private final MenuItem newGame;
|
||||
private final MenuItem undoMove;
|
||||
private final MenuItem redoMove;
|
||||
private final MenuItem exitGame;
|
||||
private final MenuItem newGame;
|
||||
private final MenuItem undoMove;
|
||||
private final MenuItem redoMove;
|
||||
private final MenuItem exitGame;
|
||||
|
||||
public GameMenuBar() {
|
||||
super();
|
||||
newGame = new MenuItem("New");
|
||||
undoMove = new MenuItem("Undo");
|
||||
redoMove = new MenuItem("Redo");
|
||||
exitGame = new MenuItem("Exit");
|
||||
setAccelerators();
|
||||
Menu gameMenu = new Menu("Game");
|
||||
gameMenu.getItems().addAll(newGame, undoMove, redoMove, exitGame);
|
||||
super.getMenus().add(gameMenu);
|
||||
}
|
||||
public GameMenuBar() {
|
||||
super();
|
||||
newGame = new MenuItem("New");
|
||||
undoMove = new MenuItem("Undo");
|
||||
redoMove = new MenuItem("Redo");
|
||||
exitGame = new MenuItem("Exit");
|
||||
setAccelerators();
|
||||
Menu gameMenu = new Menu("Game");
|
||||
gameMenu.getItems().addAll(newGame, undoMove, redoMove, exitGame);
|
||||
super.getMenus().add(gameMenu);
|
||||
}
|
||||
|
||||
private void setAccelerators() {
|
||||
newGame.setAccelerator(new KeyCodeCombination(KeyCode.F2));
|
||||
undoMove.setAccelerator(new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN));
|
||||
redoMove.setAccelerator(new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN));
|
||||
exitGame.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE));
|
||||
}
|
||||
private void setAccelerators() {
|
||||
newGame.setAccelerator(new KeyCodeCombination(KeyCode.F2));
|
||||
undoMove.setAccelerator(new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN));
|
||||
redoMove.setAccelerator(new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN));
|
||||
exitGame.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE));
|
||||
}
|
||||
|
||||
public void setNewGameAction(EventHandler<ActionEvent> handler) {
|
||||
newGame.setOnAction(handler);
|
||||
}
|
||||
public void setNewGameAction(EventHandler<ActionEvent> handler) {
|
||||
newGame.setOnAction(handler);
|
||||
}
|
||||
|
||||
public void setUndoAction(EventHandler<ActionEvent> handler) {
|
||||
undoMove.setOnAction(handler);
|
||||
}
|
||||
public void setUndoAction(EventHandler<ActionEvent> handler) {
|
||||
undoMove.setOnAction(handler);
|
||||
}
|
||||
|
||||
public void setRedoAction(EventHandler<ActionEvent> handler) {
|
||||
redoMove.setOnAction(handler);
|
||||
}
|
||||
public void setRedoAction(EventHandler<ActionEvent> handler) {
|
||||
redoMove.setOnAction(handler);
|
||||
}
|
||||
|
||||
public void setExitAction(EventHandler<ActionEvent> handler) {
|
||||
exitGame.setOnAction(handler);
|
||||
}
|
||||
public void setExitAction(EventHandler<ActionEvent> handler) {
|
||||
exitGame.setOnAction(handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,43 +8,42 @@ 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 static final int CARD_MARGIN = 22;
|
||||
public static final int TOP_MARGIN = 0;
|
||||
|
||||
public CascadingPileView(Pile pile) {
|
||||
super(pile);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,26 +6,26 @@ 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;
|
||||
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 PileView(Pile pile) {
|
||||
this.pile = pile;
|
||||
}
|
||||
|
||||
public Pile getPile() {
|
||||
return pile;
|
||||
}
|
||||
public Pile getPile() {
|
||||
return pile;
|
||||
}
|
||||
|
||||
public abstract void paint(GraphicsContext gc, double x, double y);
|
||||
public abstract void paint(GraphicsContext gc, double x, double y);
|
||||
|
||||
public boolean isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
public boolean isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void toggleSelected() {
|
||||
isSelected = !isSelected;
|
||||
}
|
||||
public void toggleSelected() {
|
||||
isSelected = !isSelected;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,24 +7,24 @@ import javafx.scene.paint.Color;
|
||||
|
||||
public class StackedPileView extends PileView {
|
||||
|
||||
public StackedPileView(Pile pile) {
|
||||
super(pile);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user