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 class FreeCellApplication extends Application {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch(FreeCellApplication.class, args);
|
launch(FreeCellApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws Exception {
|
public void start(Stage stage) throws Exception {
|
||||||
VBox root = new VBox();
|
VBox root = new VBox();
|
||||||
root.setBackground(new Background(new BackgroundImage(new Image(
|
root.setBackground(new Background(new BackgroundImage(new Image("/deck/FELT.jpg"), BackgroundRepeat.NO_REPEAT,
|
||||||
"/deck/FELT.jpg"), BackgroundRepeat.NO_REPEAT,
|
BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, BackgroundSize.DEFAULT)));
|
||||||
BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER,
|
|
||||||
BackgroundSize.DEFAULT)));
|
|
||||||
|
|
||||||
Game game = new Game();
|
Game game = new Game();
|
||||||
GameMenuBar menuBar = new GameMenuBar();
|
GameMenuBar menuBar = new GameMenuBar();
|
||||||
GameCanvas canvas = new GameCanvas(game, 731, 600);
|
GameCanvas canvas = new GameCanvas(game, 731, 600);
|
||||||
menuBar.setNewGameAction(canvas.getNewGameAction());
|
menuBar.setNewGameAction(canvas.getNewGameAction());
|
||||||
menuBar.setUndoAction(canvas.getUndoAction());
|
menuBar.setUndoAction(canvas.getUndoAction());
|
||||||
menuBar.setRedoAction(canvas.getRedoAction());
|
menuBar.setRedoAction(canvas.getRedoAction());
|
||||||
menuBar.setExitAction(e -> Platform.exit());
|
menuBar.setExitAction(e -> Platform.exit());
|
||||||
root.getChildren().addAll(menuBar, canvas);
|
root.getChildren().addAll(menuBar, canvas);
|
||||||
Scene scene = new Scene(root);
|
Scene scene = new Scene(root);
|
||||||
|
|
||||||
stage.setTitle("FreeCell");
|
stage.setTitle("FreeCell");
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setResizable(false);
|
stage.setResizable(false);
|
||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,95 +10,94 @@ import javafx.scene.paint.Color;
|
|||||||
*/
|
*/
|
||||||
public class Card {
|
public class Card {
|
||||||
|
|
||||||
private static DoubleKeyedMap<Rank, Suit, Image> faceImages;
|
private static DoubleKeyedMap<Rank, Suit, Image> faceImages;
|
||||||
public static final Image backImage = new Image("/deck/CARDBACK.png");
|
public static final Image backImage = new Image("/deck/CARDBACK.png");
|
||||||
public static final double width = backImage.getWidth();
|
public static final double width = backImage.getWidth();
|
||||||
public static final double height = backImage.getHeight();
|
public static final double height = backImage.getHeight();
|
||||||
|
|
||||||
public final Rank rank;
|
public final Rank rank;
|
||||||
public final Suit suit;
|
public final Suit suit;
|
||||||
public final Color color;
|
public final Color color;
|
||||||
private boolean faceUp;
|
private boolean faceUp;
|
||||||
|
|
||||||
public Card(Rank rank, Suit suit) {
|
public Card(Rank rank, Suit suit) {
|
||||||
this.rank = rank;
|
this.rank = rank;
|
||||||
this.suit = suit;
|
this.suit = suit;
|
||||||
this.color = suit.color();
|
this.color = suit.color();
|
||||||
this.faceUp = false;
|
this.faceUp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the backside image of a card.
|
* Returns the backside image of a card.
|
||||||
*/
|
*/
|
||||||
public static Image backImage() {
|
public static Image backImage() {
|
||||||
return backImage;
|
return backImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the face image of a card.
|
* Returns the face image of a card.
|
||||||
*/
|
*/
|
||||||
public static Image faceImage(Rank rank, Suit suit) {
|
public static Image faceImage(Rank rank, Suit suit) {
|
||||||
if (faceImages == null) {
|
if (faceImages == null) {
|
||||||
faceImages = new DoubleKeyedMap<>();
|
faceImages = new DoubleKeyedMap<>();
|
||||||
}
|
}
|
||||||
if (!faceImages.contains(rank, suit)) {
|
if (!faceImages.contains(rank, suit)) {
|
||||||
faceImages.put(rank, suit, new Image("/deck/" + rank.value
|
faceImages.put(rank, suit, new Image("/deck/" + rank.value + suit.firstLetter + ".png"));
|
||||||
+ suit.firstLetter + ".png"));
|
}
|
||||||
}
|
return faceImages.get(rank, suit);
|
||||||
return faceImages.get(rank, suit);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the card's face image if its face is up or its backside image
|
* Returns the card's face image if its face is up or its backside image
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public Image image() {
|
public Image image() {
|
||||||
return faceUp ? faceImage(rank, suit) : backImage();
|
return faceUp ? faceImage(rank, suit) : backImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns the card over, negating its face up status.
|
* Turns the card over, negating its face up status.
|
||||||
*/
|
*/
|
||||||
public void turn() {
|
public void turn() {
|
||||||
faceUp = !faceUp;
|
faceUp = !faceUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if (other == null || !(other instanceof Card)) {
|
if (other == null || !(other instanceof Card)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Card card = (Card) other;
|
Card card = (Card) other;
|
||||||
return rank == card.rank && suit == card.suit;
|
return rank == card.rank && suit == card.suit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return rank + " of " + suit;
|
return rank + " of " + suit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Rank {
|
public enum Rank {
|
||||||
ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING;
|
ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1 for Ace up to 13 for King.
|
* 1 for Ace up to 13 for King.
|
||||||
*/
|
*/
|
||||||
public final int value = ordinal() + 1;
|
public final int value = ordinal() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Suit {
|
public enum Suit {
|
||||||
SPADES, HEARTS, DIAMONDS, CLUBS;
|
SPADES, HEARTS, DIAMONDS, CLUBS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* S for SPADES, H for HEARTS, D for DIAMONDS, or C for CLUBS.
|
* S for SPADES, H for HEARTS, D for DIAMONDS, or C for CLUBS.
|
||||||
*/
|
*/
|
||||||
public final Character firstLetter = name().charAt(0);
|
public final Character firstLetter = name().charAt(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Black or red.
|
* Black or red.
|
||||||
*/
|
*/
|
||||||
public Color color() {
|
public Color color() {
|
||||||
return (this == SPADES || this == CLUBS) ? Color.BLACK : Color.RED;
|
return (this == SPADES || this == CLUBS) ? Color.BLACK : Color.RED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,41 +13,41 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class Deck {
|
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.
|
* Makes a 52-card, unshuffled deck.
|
||||||
*/
|
*/
|
||||||
public static Deck newDeck() {
|
public static Deck newDeck() {
|
||||||
Deck deck = new Deck();
|
Deck deck = new Deck();
|
||||||
for (Rank rank : Rank.values()) {
|
for (Rank rank : Rank.values()) {
|
||||||
for (Suit suit : Suit.values()) {
|
for (Suit suit : Suit.values()) {
|
||||||
deck.cards.push(new Card(rank, suit));
|
deck.cards.push(new Card(rank, suit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return deck;
|
return deck;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deals a card from the top of the deck.
|
* Deals a card from the top of the deck.
|
||||||
*/
|
*/
|
||||||
public Card deal() {
|
public Card deal() {
|
||||||
return cards.pop();
|
return cards.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffles the deck.
|
* Shuffles the deck.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void shuffle() {
|
public void shuffle() {
|
||||||
Collections.shuffle((List<Card>) cards);
|
Collections.shuffle((List<Card>) cards);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return cards.toString();
|
return cards.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,184 +12,184 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Game {
|
public class Game {
|
||||||
private List<Cell> cells = new ArrayList<>(4);
|
private List<Cell> cells = new ArrayList<>(4);
|
||||||
private List<Foundation> foundations = new ArrayList<>(4);
|
private List<Foundation> foundations = new ArrayList<>(4);
|
||||||
private List<Tableau> tableaux = new ArrayList<>(8);
|
private List<Tableau> tableaux = new ArrayList<>(8);
|
||||||
private MoveTracker moveTracker = new MoveTracker();
|
private MoveTracker moveTracker = new MoveTracker();
|
||||||
|
|
||||||
public Game() {
|
public Game() {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
cells.add(new Cell());
|
cells.add(new Cell());
|
||||||
foundations.add(new Foundation());
|
foundations.add(new Foundation());
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
tableaux.add(new Tableau());
|
tableaux.add(new Tableau());
|
||||||
newGame();
|
newGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Cell> getCells() {
|
public List<Cell> getCells() {
|
||||||
return cells;
|
return cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Foundation> getFoundations() {
|
public List<Foundation> getFoundations() {
|
||||||
return foundations;
|
return foundations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Tableau> getTableaux() {
|
public List<Tableau> getTableaux() {
|
||||||
return tableaux;
|
return tableaux;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves cards from one pile to another, if the move is valid.
|
* Moves cards from one pile to another, if the move is valid.
|
||||||
*/
|
*/
|
||||||
public boolean tryMove(Pile fromPile, Pile toPile) {
|
public boolean tryMove(Pile fromPile, Pile toPile) {
|
||||||
int cardsMoved = toPile.moveFrom(fromPile);
|
int cardsMoved = toPile.moveFrom(fromPile);
|
||||||
if (cardsMoved > 0) {
|
if (cardsMoved > 0) {
|
||||||
moveTracker.addMove(new MoveAction(fromPile, toPile, cardsMoved));
|
moveTracker.addMove(new MoveAction(fromPile, toPile, cardsMoved));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redo a move that was previously undone.
|
* Redo a move that was previously undone.
|
||||||
*
|
*
|
||||||
* @return true if another redo can be performed
|
* @return true if another redo can be performed
|
||||||
*/
|
*/
|
||||||
public boolean redo() {
|
public boolean redo() {
|
||||||
if (moveTracker.hasNextMove()) {
|
if (moveTracker.hasNextMove()) {
|
||||||
moveTracker.getNextMove().redo();
|
moveTracker.getNextMove().redo();
|
||||||
}
|
}
|
||||||
return moveTracker.hasNextMove();
|
return moveTracker.hasNextMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undo a previous move.
|
* Undo a previous move.
|
||||||
*
|
*
|
||||||
* @return true if another undo can be performed
|
* @return true if another undo can be performed
|
||||||
*/
|
*/
|
||||||
public boolean undo() {
|
public boolean undo() {
|
||||||
if (moveTracker.hasLastMove()) {
|
if (moveTracker.hasLastMove()) {
|
||||||
moveTracker.getLastMove().undo();
|
moveTracker.getLastMove().undo();
|
||||||
}
|
}
|
||||||
return moveTracker.hasLastMove();
|
return moveTracker.hasLastMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the game cannot be lost.
|
* Returns true if the game cannot be lost.
|
||||||
*/
|
*/
|
||||||
public boolean isWon() {
|
public boolean isWon() {
|
||||||
for (Pile pile : tableaux) {
|
for (Pile pile : tableaux) {
|
||||||
if (!pile.isInOrder()) {
|
if (!pile.isInOrder()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the game cannot be won.
|
* Returns true if the game cannot be won.
|
||||||
*/
|
*/
|
||||||
public boolean isLost() {
|
public boolean isLost() {
|
||||||
// Are free cells full?
|
// Are free cells full?
|
||||||
for (Pile pile : cells) {
|
for (Pile pile : cells) {
|
||||||
if (pile.isEmpty()) {
|
if (pile.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Can you not move to any tableau?
|
// Can you not move to any tableau?
|
||||||
for (Pile pile : tableaux) {
|
for (Pile pile : tableaux) {
|
||||||
for (Pile tableau : tableaux) {
|
for (Pile tableau : tableaux) {
|
||||||
if (pile.canMoveFrom(tableau)) {
|
if (pile.canMoveFrom(tableau)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Pile cell : cells) {
|
for (Pile cell : cells) {
|
||||||
if (pile.canMoveFrom(cell)) {
|
if (pile.canMoveFrom(cell)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Can you not move to any home cell?
|
// Can you not move to any home cell?
|
||||||
for (Pile pile : foundations) {
|
for (Pile pile : foundations) {
|
||||||
for (Pile tableau : tableaux) {
|
for (Pile tableau : tableaux) {
|
||||||
if (pile.canMoveFrom(tableau)) {
|
if (pile.canMoveFrom(tableau)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Pile cell : cells) {
|
for (Pile cell : cells) {
|
||||||
if (pile.canMoveFrom(cell)) {
|
if (pile.canMoveFrom(cell)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newGame() {
|
public void newGame() {
|
||||||
Deck deck = Deck.newDeck();
|
Deck deck = Deck.newDeck();
|
||||||
deck.shuffle();
|
deck.shuffle();
|
||||||
moveTracker.clearMoves();
|
moveTracker.clearMoves();
|
||||||
tableaux.forEach(Pile::clear);
|
tableaux.forEach(Pile::clear);
|
||||||
cells.forEach(Pile::clear);
|
cells.forEach(Pile::clear);
|
||||||
foundations.forEach(Pile::clear);
|
foundations.forEach(Pile::clear);
|
||||||
// Deal 6 cards to each tableau.
|
// Deal 6 cards to each tableau.
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
for (int j = 0; j < 8; j++) {
|
for (int j = 0; j < 8; j++) {
|
||||||
Card card = deck.deal();
|
Card card = deck.deal();
|
||||||
card.turn();
|
card.turn();
|
||||||
tableaux.get(j).addCard(card);
|
tableaux.get(j).addCard(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Deal an additional card to first 4.
|
// Deal an additional card to first 4.
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
Card card = deck.deal();
|
Card card = deck.deal();
|
||||||
card.turn();
|
card.turn();
|
||||||
tableaux.get(i).addCard(card);
|
tableaux.get(i).addCard(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MoveTracker {
|
private static class MoveTracker {
|
||||||
private final Deque<MoveAction> nextMoves = new LinkedList<>();
|
private final Deque<MoveAction> nextMoves = new LinkedList<>();
|
||||||
private final Deque<MoveAction> previousMoves = new LinkedList<>();
|
private final Deque<MoveAction> previousMoves = new LinkedList<>();
|
||||||
|
|
||||||
public void clearMoves() {
|
public void clearMoves() {
|
||||||
nextMoves.clear();
|
nextMoves.clear();
|
||||||
previousMoves.clear();
|
previousMoves.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasLastMove() {
|
public boolean hasLastMove() {
|
||||||
return !previousMoves.isEmpty();
|
return !previousMoves.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveAction getLastMove() {
|
public MoveAction getLastMove() {
|
||||||
MoveAction lastMove = previousMoves.pop();
|
MoveAction lastMove = previousMoves.pop();
|
||||||
nextMoves.push(lastMove);
|
nextMoves.push(lastMove);
|
||||||
return lastMove;
|
return lastMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasNextMove() {
|
public boolean hasNextMove() {
|
||||||
return !nextMoves.isEmpty();
|
return !nextMoves.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveAction getNextMove() {
|
public MoveAction getNextMove() {
|
||||||
MoveAction nextMove = nextMoves.pop();
|
MoveAction nextMove = nextMoves.pop();
|
||||||
previousMoves.push(nextMove);
|
previousMoves.push(nextMove);
|
||||||
return nextMove;
|
return nextMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMove(MoveAction move) {
|
public void addMove(MoveAction move) {
|
||||||
MoveAction nextMove = nextMoves.peek();
|
MoveAction nextMove = nextMoves.peek();
|
||||||
/*
|
/*
|
||||||
* if new move differs from saved next move, clear the remaining
|
* if new move differs from saved next move, clear the remaining
|
||||||
* next moves
|
* next moves
|
||||||
*/
|
*/
|
||||||
if (move.equals(nextMove)) {
|
if (move.equals(nextMove)) {
|
||||||
nextMoves.pop();
|
nextMoves.pop();
|
||||||
} else {
|
} else {
|
||||||
nextMoves.clear();
|
nextMoves.clear();
|
||||||
}
|
}
|
||||||
previousMoves.push(move);
|
previousMoves.push(move);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.charego.freecellfx.model.action;
|
package com.charego.freecellfx.model.action;
|
||||||
|
|
||||||
public interface 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;
|
import com.charego.freecellfx.model.pile.Pile;
|
||||||
|
|
||||||
public class MoveAction implements Action {
|
public class MoveAction implements Action {
|
||||||
private final Pile fromPile;
|
private final Pile fromPile;
|
||||||
private final Pile toPile;
|
private final Pile toPile;
|
||||||
private final int numCards;
|
private final int numCards;
|
||||||
|
|
||||||
public MoveAction(Pile fromPile, Pile toPile, int numCards) {
|
public MoveAction(Pile fromPile, Pile toPile, int numCards) {
|
||||||
this.fromPile = fromPile;
|
this.fromPile = fromPile;
|
||||||
this.toPile = toPile;
|
this.toPile = toPile;
|
||||||
this.numCards = numCards;
|
this.numCards = numCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void redo() {
|
public void redo() {
|
||||||
toPile.moveFromBlindly(fromPile, numCards);
|
toPile.moveFromBlindly(fromPile, numCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo() {
|
public void undo() {
|
||||||
fromPile.moveFromBlindly(toPile, numCards);
|
fromPile.moveFromBlindly(toPile, numCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if (other == null || !(other instanceof MoveAction)) {
|
if (other == null || !(other instanceof MoveAction)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MoveAction action = (MoveAction) other;
|
MoveAction action = (MoveAction) other;
|
||||||
return fromPile == action.fromPile
|
return fromPile == action.fromPile && toPile == action.toPile && numCards == action.numCards;
|
||||||
&& toPile == action.toPile
|
}
|
||||||
&& numCards == action.numCards;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,41 +8,41 @@ import java.util.Iterator;
|
|||||||
|
|
||||||
abstract class AbstractPile implements Pile {
|
abstract class AbstractPile implements Pile {
|
||||||
|
|
||||||
private Deque<Card> stack = new ArrayDeque<>();
|
private Deque<Card> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCard(Card card) {
|
public void addCard(Card card) {
|
||||||
stack.push(card);
|
stack.push(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card removeCard() {
|
public Card removeCard() {
|
||||||
return stack.pop();
|
return stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card topCard() {
|
public Card topCard() {
|
||||||
return stack.peek();
|
return stack.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
stack.clear();
|
stack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return stack.isEmpty();
|
return stack.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return stack.size();
|
return stack.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Card> iterator() {
|
public Iterator<Card> iterator() {
|
||||||
return stack.descendingIterator();
|
return stack.descendingIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,31 @@ package com.charego.freecellfx.model.pile;
|
|||||||
|
|
||||||
public class Cell extends AbstractPile {
|
public class Cell extends AbstractPile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canMoveFrom(Pile other) {
|
public boolean canMoveFrom(Pile other) {
|
||||||
return isEmpty() && !other.isEmpty();
|
return isEmpty() && !other.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int moveFrom(Pile other) {
|
public int moveFrom(Pile other) {
|
||||||
if (canMoveFrom(other)) {
|
if (canMoveFrom(other)) {
|
||||||
addCard(other.removeCard());
|
addCard(other.removeCard());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveFromBlindly(Pile other, int numCards) {
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
if (numCards != 1) {
|
if (numCards != 1) {
|
||||||
throw new IllegalArgumentException("numCards must be 1");
|
throw new IllegalArgumentException("numCards must be 1");
|
||||||
}
|
}
|
||||||
addCard(other.removeCard());
|
addCard(other.removeCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInOrder() {
|
public boolean isInOrder() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,42 +5,41 @@ import static com.charego.freecellfx.model.Card.Suit;
|
|||||||
|
|
||||||
public class Foundation extends AbstractPile {
|
public class Foundation extends AbstractPile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canMoveFrom(Pile other) {
|
public boolean canMoveFrom(Pile other) {
|
||||||
if (other.isEmpty()) {
|
if (other.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Rank otherRank = other.topCard().rank;
|
Rank otherRank = other.topCard().rank;
|
||||||
Suit otherSuit = other.topCard().suit;
|
Suit otherSuit = other.topCard().suit;
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
return otherRank == Rank.ACE;
|
return otherRank == Rank.ACE;
|
||||||
}
|
}
|
||||||
Rank thisRank = topCard().rank;
|
Rank thisRank = topCard().rank;
|
||||||
Suit thisSuit = topCard().suit;
|
Suit thisSuit = topCard().suit;
|
||||||
return otherSuit == thisSuit
|
return otherSuit == thisSuit && otherRank.value == thisRank.value + 1;
|
||||||
&& otherRank.value == thisRank.value + 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int moveFrom(Pile other) {
|
public int moveFrom(Pile other) {
|
||||||
if (canMoveFrom(other)) {
|
if (canMoveFrom(other)) {
|
||||||
addCard(other.removeCard());
|
addCard(other.removeCard());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveFromBlindly(Pile other, int numCards) {
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
if (numCards != 1) {
|
if (numCards != 1) {
|
||||||
throw new IllegalArgumentException("numCards must be 1");
|
throw new IllegalArgumentException("numCards must be 1");
|
||||||
}
|
}
|
||||||
addCard(other.removeCard());
|
addCard(other.removeCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInOrder() {
|
public boolean isInOrder() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,56 +4,57 @@ import com.charego.freecellfx.model.Card;
|
|||||||
|
|
||||||
public interface Pile extends Iterable<Card> {
|
public interface Pile extends Iterable<Card> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a card to the top of the pile.
|
* Adds a card to the top of the pile.
|
||||||
*/
|
*/
|
||||||
void addCard(Card card);
|
void addCard(Card card);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the top card from the pile.
|
* Removes the top card from the pile.
|
||||||
*/
|
*/
|
||||||
Card removeCard();
|
Card removeCard();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the contents of another pile can be moved to this pile.
|
* Returns true if the contents of another pile can be moved to this pile.
|
||||||
*/
|
*/
|
||||||
boolean canMoveFrom(Pile other);
|
boolean canMoveFrom(Pile other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves cards from another pile to this pile, if the tryMove is valid.
|
* Moves cards from another pile to this pile, if the tryMove is valid.
|
||||||
*
|
*
|
||||||
* @return the number of cards moved (i.e., possibly 0)
|
* @return the number of cards moved (i.e., possibly 0)
|
||||||
*/
|
*/
|
||||||
int moveFrom(Pile other);
|
int moveFrom(Pile other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves cards from another pile to this pile, whether or not the tryMove is valid.
|
* Moves cards from another pile to this pile, whether or not the tryMove is
|
||||||
*/
|
* valid.
|
||||||
void moveFromBlindly(Pile other, int numCards);
|
*/
|
||||||
|
void moveFromBlindly(Pile other, int numCards);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the top card in the pile, if a card is present.
|
* Returns the top card in the pile, if a card is present.
|
||||||
*/
|
*/
|
||||||
Card topCard();
|
Card topCard();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the contents of the pile are in order.
|
* Returns true if the contents of the pile are in order.
|
||||||
*/
|
*/
|
||||||
boolean isInOrder();
|
boolean isInOrder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the contents of the pile.
|
* Clears the contents of the pile.
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the pile is empty.
|
* Returns true if the pile is empty.
|
||||||
*/
|
*/
|
||||||
boolean isEmpty();
|
boolean isEmpty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the pile.
|
* Returns the size of the pile.
|
||||||
*/
|
*/
|
||||||
int size();
|
int size();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,134 +7,133 @@ import java.util.LinkedList;
|
|||||||
|
|
||||||
public class Tableau extends AbstractPile {
|
public class Tableau extends AbstractPile {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintains the ordering of the cards within the pile.
|
* Maintains the ordering of the cards within the pile.
|
||||||
*/
|
*/
|
||||||
private Deque<Integer> orderStack = new LinkedList<>();
|
private Deque<Integer> orderStack = new LinkedList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCard(Card card) {
|
public void addCard(Card card) {
|
||||||
countAddedCard(card);
|
countAddedCard(card);
|
||||||
super.addCard(card);
|
super.addCard(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void countAddedCard(Card card) {
|
private void countAddedCard(Card card) {
|
||||||
if (isEmpty() || orderStack.isEmpty()) {
|
if (isEmpty() || orderStack.isEmpty()) {
|
||||||
orderStack.push(1);
|
orderStack.push(1);
|
||||||
} else if (card.color != topCard().color &&
|
} else if (card.color != topCard().color && card.rank.value == topCard().rank.value - 1) {
|
||||||
card.rank.value == topCard().rank.value - 1) {
|
orderStack.push(orderStack.pop() + 1);
|
||||||
orderStack.push(orderStack.pop() + 1);
|
} else {
|
||||||
} else {
|
orderStack.push(1);
|
||||||
orderStack.push(1);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card removeCard() {
|
public Card removeCard() {
|
||||||
countRemovedCard();
|
countRemovedCard();
|
||||||
return super.removeCard();
|
return super.removeCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void countRemovedCard() {
|
private void countRemovedCard() {
|
||||||
int topInOrder = orderStack.pop();
|
int topInOrder = orderStack.pop();
|
||||||
if (topInOrder > 1) {
|
if (topInOrder > 1) {
|
||||||
orderStack.push(topInOrder - 1);
|
orderStack.push(topInOrder - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canMoveFrom(Pile other) {
|
public boolean canMoveFrom(Pile other) {
|
||||||
if (other.isEmpty()) {
|
if (other.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
||||||
if (rankDiff < 1) {
|
if (rankDiff < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (other instanceof Foundation || other instanceof Cell) {
|
if (other instanceof Foundation || other instanceof Cell) {
|
||||||
return rankDiff == 1 && topCard().color != other.topCard().color;
|
return rankDiff == 1 && topCard().color != other.topCard().color;
|
||||||
}
|
}
|
||||||
if (other instanceof Tableau) {
|
if (other instanceof Tableau) {
|
||||||
int otherTopInOrder = ((Tableau) other).topInOrder();
|
int otherTopInOrder = ((Tableau) other).topInOrder();
|
||||||
if (otherTopInOrder >= rankDiff) {
|
if (otherTopInOrder >= rankDiff) {
|
||||||
switch (rankDiff % 2) {
|
switch (rankDiff % 2) {
|
||||||
case 0:
|
case 0:
|
||||||
return topCard().color == other.topCard().color;
|
return topCard().color == other.topCard().color;
|
||||||
case 1:
|
case 1:
|
||||||
return topCard().color != other.topCard().color;
|
return topCard().color != other.topCard().color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int moveFrom(Pile other) {
|
public int moveFrom(Pile other) {
|
||||||
if (!canMoveFrom(other)) {
|
if (!canMoveFrom(other)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (other instanceof Foundation || other instanceof Cell) {
|
if (other instanceof Foundation || other instanceof Cell) {
|
||||||
addCard(other.removeCard());
|
addCard(other.removeCard());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (other instanceof Tableau) {
|
if (other instanceof Tableau) {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
// Move all ordered cards to this pile
|
// Move all ordered cards to this pile
|
||||||
int otherTopInOrder = ((Tableau) other).topInOrder();
|
int otherTopInOrder = ((Tableau) other).topInOrder();
|
||||||
moveFrom(other, otherTopInOrder);
|
moveFrom(other, otherTopInOrder);
|
||||||
return otherTopInOrder;
|
return otherTopInOrder;
|
||||||
} else {
|
} else {
|
||||||
// Move ordered cards until they reach the current top card
|
// Move ordered cards until they reach the current top card
|
||||||
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
int rankDiff = topCard().rank.value - other.topCard().rank.value;
|
||||||
moveFrom(other, rankDiff);
|
moveFrom(other, rankDiff);
|
||||||
return rankDiff;
|
return rankDiff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves {@code n} cards from the other pile to this pile.
|
* Moves {@code n} cards from the other pile to this pile.
|
||||||
*/
|
*/
|
||||||
private void moveFrom(Pile other, int n) {
|
private void moveFrom(Pile other, int n) {
|
||||||
Deque<Card> topCards = new LinkedList<>();
|
Deque<Card> topCards = new LinkedList<>();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
topCards.addLast(other.removeCard());
|
topCards.addLast(other.removeCard());
|
||||||
}
|
}
|
||||||
while (!topCards.isEmpty()) {
|
while (!topCards.isEmpty()) {
|
||||||
addCard(topCards.removeLast());
|
addCard(topCards.removeLast());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveFromBlindly(Pile other, int numCards) {
|
public void moveFromBlindly(Pile other, int numCards) {
|
||||||
moveFrom(other, numCards);
|
moveFrom(other, numCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
orderStack.clear();
|
orderStack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInOrder() {
|
public boolean isInOrder() {
|
||||||
return (orderStack.size() < 2);
|
return (orderStack.size() < 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of cards in order at the top of the pile.
|
* Returns the number of cards in order at the top of the pile.
|
||||||
*/
|
*/
|
||||||
public int topInOrder() {
|
public int topInOrder() {
|
||||||
return orderStack.size() == 0 ? 0 : orderStack.peek();
|
return orderStack.size() == 0 ? 0 : orderStack.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return orderStack.toString();
|
return orderStack.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,29 +6,32 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Maps a pair of coordinates to an object.
|
* Maps a pair of coordinates to an object.
|
||||||
*
|
*
|
||||||
* @param <K1> the first coordinate
|
* @param <K1>
|
||||||
* @param <K2> the second coordinate
|
* the first coordinate
|
||||||
* @param <V> the treasure
|
* @param <K2>
|
||||||
|
* the second coordinate
|
||||||
|
* @param <V>
|
||||||
|
* the treasure
|
||||||
*/
|
*/
|
||||||
public class DoubleKeyedMap<K1, K2, V> {
|
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) {
|
public boolean contains(K1 firstKey, K2 secondKey) {
|
||||||
return map.containsKey(firstKey) && map.get(firstKey).containsKey(secondKey);
|
return map.containsKey(firstKey) && map.get(firstKey).containsKey(secondKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public V get(K1 firstKey, K2 secondKey) {
|
public V get(K1 firstKey, K2 secondKey) {
|
||||||
return map.get(firstKey).get(secondKey);
|
return map.get(firstKey).get(secondKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the old value if it exists, or null.
|
* Returns the old value if it exists, or null.
|
||||||
*/
|
*/
|
||||||
public V put(K1 firstKey, K2 secondKey, V newValue) {
|
public V put(K1 firstKey, K2 secondKey, V newValue) {
|
||||||
if (!map.containsKey(firstKey)) {
|
if (!map.containsKey(firstKey)) {
|
||||||
map.put(firstKey, new HashMap<>());
|
map.put(firstKey, new HashMap<>());
|
||||||
}
|
}
|
||||||
return map.get(firstKey).put(secondKey, newValue);
|
return map.get(firstKey).put(secondKey, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,152 +18,152 @@ import java.util.List;
|
|||||||
|
|
||||||
public class GameCanvas extends Canvas {
|
public class GameCanvas extends Canvas {
|
||||||
|
|
||||||
private final Game game;
|
private final Game game;
|
||||||
private final GraphicsContext gc;
|
private final GraphicsContext gc;
|
||||||
private final PileView[] pileViews;
|
private final PileView[] pileViews;
|
||||||
|
|
||||||
private PileView fromPile;
|
private PileView fromPile;
|
||||||
private boolean winMessageShown;
|
private boolean winMessageShown;
|
||||||
|
|
||||||
public GameCanvas(Game game, double width, double height) {
|
public GameCanvas(Game game, double width, double height) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.gc = getGraphicsContext2D();
|
this.gc = getGraphicsContext2D();
|
||||||
this.pileViews = constructPileViews(game);
|
this.pileViews = constructPileViews(game);
|
||||||
setOnMouseClicked(new MouseClickHandler());
|
setOnMouseClicked(new MouseClickHandler());
|
||||||
updateView();
|
updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PileView[] constructPileViews(Game game) {
|
private static PileView[] constructPileViews(Game game) {
|
||||||
List<PileView> list = new ArrayList<>();
|
List<PileView> list = new ArrayList<>();
|
||||||
for (Pile pile : game.getCells()) {
|
for (Pile pile : game.getCells()) {
|
||||||
list.add(new StackedPileView(pile));
|
list.add(new StackedPileView(pile));
|
||||||
}
|
}
|
||||||
for (Pile pile : game.getFoundations()) {
|
for (Pile pile : game.getFoundations()) {
|
||||||
list.add(new StackedPileView(pile));
|
list.add(new StackedPileView(pile));
|
||||||
}
|
}
|
||||||
for (Pile pile : game.getTableaux()) {
|
for (Pile pile : game.getTableaux()) {
|
||||||
list.add(new CascadingPileView(pile));
|
list.add(new CascadingPileView(pile));
|
||||||
}
|
}
|
||||||
return list.toArray(new PileView[16]);
|
return list.toArray(new PileView[16]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkEndingConditions() {
|
private void checkEndingConditions() {
|
||||||
if (winMessageShown) {
|
if (winMessageShown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (game.isWon()) {
|
if (game.isWon()) {
|
||||||
winMessageShown = true;
|
winMessageShown = true;
|
||||||
new Alert(Alert.AlertType.INFORMATION, "You won!").showAndWait();
|
new Alert(Alert.AlertType.INFORMATION, "You won!").showAndWait();
|
||||||
} else if (game.isLost()) {
|
} else if (game.isLost()) {
|
||||||
new Alert(Alert.AlertType.INFORMATION, "You lost!").showAndWait();
|
new Alert(Alert.AlertType.INFORMATION, "You lost!").showAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PileView findPile(double x, double y) {
|
private PileView findPile(double x, double y) {
|
||||||
int columnIndex = 0;
|
int columnIndex = 0;
|
||||||
x -= 15;
|
x -= 15;
|
||||||
while (x > 90) {
|
while (x > 90) {
|
||||||
x -= 90;
|
x -= 90;
|
||||||
columnIndex++;
|
columnIndex++;
|
||||||
}
|
}
|
||||||
boolean columnPressed = x < Card.width;
|
boolean columnPressed = x < Card.width;
|
||||||
if (!columnPressed) {
|
if (!columnPressed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (y > 15 && y < 15 + Card.height) {
|
if (y > 15 && y < 15 + Card.height) {
|
||||||
return pileViews[columnIndex];
|
return pileViews[columnIndex];
|
||||||
}
|
}
|
||||||
if (y > 120) {
|
if (y > 120) {
|
||||||
PileView pileView = pileViews[columnIndex + 8];
|
PileView pileView = pileViews[columnIndex + 8];
|
||||||
Pile pile = pileView.getPile();
|
Pile pile = pileView.getPile();
|
||||||
double pileHeight = 120 + (pile.size() - 1) * CascadingPileView.CARD_MARGIN + Card.height;
|
double pileHeight = 120 + (pile.size() - 1) * CascadingPileView.CARD_MARGIN + Card.height;
|
||||||
if (y < pileHeight) {
|
if (y < pileHeight) {
|
||||||
return pileView;
|
return pileView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateView() {
|
public void updateView() {
|
||||||
gc.clearRect(0, 0, super.getWidth(), super.getHeight());
|
gc.clearRect(0, 0, super.getWidth(), super.getHeight());
|
||||||
double x = 15;
|
double x = 15;
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
pileViews[i].paint(gc, x, 15);
|
pileViews[i].paint(gc, x, 15);
|
||||||
x += 90;
|
x += 90;
|
||||||
}
|
}
|
||||||
x = 15;
|
x = 15;
|
||||||
for (int i = 8; i < 16; i++) {
|
for (int i = 8; i < 16; i++) {
|
||||||
pileViews[i].paint(gc, x, 120);
|
pileViews[i].paint(gc, x, 120);
|
||||||
x += 90;
|
x += 90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventHandler<ActionEvent> getNewGameAction() {
|
public EventHandler<ActionEvent> getNewGameAction() {
|
||||||
return e -> {
|
return e -> {
|
||||||
game.newGame();
|
game.newGame();
|
||||||
winMessageShown = false;
|
winMessageShown = false;
|
||||||
if (fromPile != null && fromPile.isSelected()) {
|
if (fromPile != null && fromPile.isSelected()) {
|
||||||
fromPile.toggleSelected();
|
fromPile.toggleSelected();
|
||||||
}
|
}
|
||||||
fromPile = null;
|
fromPile = null;
|
||||||
updateView();
|
updateView();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventHandler<ActionEvent> getUndoAction() {
|
public EventHandler<ActionEvent> getUndoAction() {
|
||||||
return e -> {
|
return e -> {
|
||||||
game.undo();
|
game.undo();
|
||||||
updateView();
|
updateView();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventHandler<ActionEvent> getRedoAction() {
|
public EventHandler<ActionEvent> getRedoAction() {
|
||||||
return e -> {
|
return e -> {
|
||||||
game.redo();
|
game.redo();
|
||||||
updateView();
|
updateView();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MouseClickHandler implements EventHandler<MouseEvent> {
|
private class MouseClickHandler implements EventHandler<MouseEvent> {
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent e) {
|
public void handle(MouseEvent e) {
|
||||||
PileView clickedPile = findPile(e.getX(), e.getY());
|
PileView clickedPile = findPile(e.getX(), e.getY());
|
||||||
if (clickedPile == null) {
|
if (clickedPile == null) {
|
||||||
if (fromPile != null) {
|
if (fromPile != null) {
|
||||||
fromPile.toggleSelected();
|
fromPile.toggleSelected();
|
||||||
}
|
}
|
||||||
fromPile = null;
|
fromPile = null;
|
||||||
updateView();
|
updateView();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fromPile == null) {
|
if (fromPile == null) {
|
||||||
fromPile = clickedPile;
|
fromPile = clickedPile;
|
||||||
fromPile.toggleSelected();
|
fromPile.toggleSelected();
|
||||||
updateView();
|
updateView();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fromPile == clickedPile) {
|
if (fromPile == clickedPile) {
|
||||||
// Double click: try moving to a foundation.
|
// Double click: try moving to a foundation.
|
||||||
fromPile.toggleSelected();
|
fromPile.toggleSelected();
|
||||||
for (int i = 4; i < 8; i++) {
|
for (int i = 4; i < 8; i++) {
|
||||||
if (game.tryMove(fromPile.getPile(), pileViews[i].getPile())) {
|
if (game.tryMove(fromPile.getPile(), pileViews[i].getPile())) {
|
||||||
checkEndingConditions();
|
checkEndingConditions();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fromPile = null;
|
fromPile = null;
|
||||||
updateView();
|
updateView();
|
||||||
} else {
|
} else {
|
||||||
// Try moving to other cell.
|
// Try moving to other cell.
|
||||||
fromPile.toggleSelected();
|
fromPile.toggleSelected();
|
||||||
if (game.tryMove(fromPile.getPile(), clickedPile.getPile())) {
|
if (game.tryMove(fromPile.getPile(), clickedPile.getPile())) {
|
||||||
checkEndingConditions();
|
checkEndingConditions();
|
||||||
}
|
}
|
||||||
fromPile = null;
|
fromPile = null;
|
||||||
updateView();
|
updateView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,44 +11,44 @@ import javafx.scene.input.KeyCombination;
|
|||||||
|
|
||||||
public class GameMenuBar extends MenuBar {
|
public class GameMenuBar extends MenuBar {
|
||||||
|
|
||||||
private final MenuItem newGame;
|
private final MenuItem newGame;
|
||||||
private final MenuItem undoMove;
|
private final MenuItem undoMove;
|
||||||
private final MenuItem redoMove;
|
private final MenuItem redoMove;
|
||||||
private final MenuItem exitGame;
|
private final MenuItem exitGame;
|
||||||
|
|
||||||
public GameMenuBar() {
|
public GameMenuBar() {
|
||||||
super();
|
super();
|
||||||
newGame = new MenuItem("New");
|
newGame = new MenuItem("New");
|
||||||
undoMove = new MenuItem("Undo");
|
undoMove = new MenuItem("Undo");
|
||||||
redoMove = new MenuItem("Redo");
|
redoMove = new MenuItem("Redo");
|
||||||
exitGame = new MenuItem("Exit");
|
exitGame = new MenuItem("Exit");
|
||||||
setAccelerators();
|
setAccelerators();
|
||||||
Menu gameMenu = new Menu("Game");
|
Menu gameMenu = new Menu("Game");
|
||||||
gameMenu.getItems().addAll(newGame, undoMove, redoMove, exitGame);
|
gameMenu.getItems().addAll(newGame, undoMove, redoMove, exitGame);
|
||||||
super.getMenus().add(gameMenu);
|
super.getMenus().add(gameMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAccelerators() {
|
private void setAccelerators() {
|
||||||
newGame.setAccelerator(new KeyCodeCombination(KeyCode.F2));
|
newGame.setAccelerator(new KeyCodeCombination(KeyCode.F2));
|
||||||
undoMove.setAccelerator(new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN));
|
undoMove.setAccelerator(new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN));
|
||||||
redoMove.setAccelerator(new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN));
|
redoMove.setAccelerator(new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN));
|
||||||
exitGame.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE));
|
exitGame.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNewGameAction(EventHandler<ActionEvent> handler) {
|
public void setNewGameAction(EventHandler<ActionEvent> handler) {
|
||||||
newGame.setOnAction(handler);
|
newGame.setOnAction(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUndoAction(EventHandler<ActionEvent> handler) {
|
public void setUndoAction(EventHandler<ActionEvent> handler) {
|
||||||
undoMove.setOnAction(handler);
|
undoMove.setOnAction(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRedoAction(EventHandler<ActionEvent> handler) {
|
public void setRedoAction(EventHandler<ActionEvent> handler) {
|
||||||
redoMove.setOnAction(handler);
|
redoMove.setOnAction(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExitAction(EventHandler<ActionEvent> handler) {
|
public void setExitAction(EventHandler<ActionEvent> handler) {
|
||||||
exitGame.setOnAction(handler);
|
exitGame.setOnAction(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,43 +8,42 @@ import javafx.scene.paint.Color;
|
|||||||
|
|
||||||
public class CascadingPileView extends PileView {
|
public class CascadingPileView extends PileView {
|
||||||
|
|
||||||
public static final int CARD_MARGIN = 22;
|
public static final int CARD_MARGIN = 22;
|
||||||
public static final int TOP_MARGIN = 0;
|
public static final int TOP_MARGIN = 0;
|
||||||
|
|
||||||
public CascadingPileView(Pile pile) {
|
public CascadingPileView(Pile pile) {
|
||||||
super(pile);
|
super(pile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paint(GraphicsContext gc, double x, double y) {
|
public void paint(GraphicsContext gc, double x, double y) {
|
||||||
if (pile.isEmpty()) {
|
if (pile.isEmpty()) {
|
||||||
// draw an outline
|
// draw an outline
|
||||||
gc.setStroke(Color.YELLOW);
|
gc.setStroke(Color.YELLOW);
|
||||||
gc.strokeRect(x, y, Card.width, Card.height);
|
gc.strokeRect(x, y, Card.width, Card.height);
|
||||||
if (isSelected()) {
|
if (isSelected()) {
|
||||||
// highlight empty cell
|
// highlight empty cell
|
||||||
gc.setFill(highlight);
|
gc.setFill(highlight);
|
||||||
gc.fillRect(x, y, Card.width, Card.height);
|
gc.fillRect(x, y, Card.width, Card.height);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
double yCurrent = y;
|
double yCurrent = y;
|
||||||
// draw the cards in a cascade from top to bottom
|
// draw the cards in a cascade from top to bottom
|
||||||
for (Card card : pile) {
|
for (Card card : pile) {
|
||||||
gc.drawImage(card.image(), x, yCurrent);
|
gc.drawImage(card.image(), x, yCurrent);
|
||||||
yCurrent += CARD_MARGIN;
|
yCurrent += CARD_MARGIN;
|
||||||
}
|
}
|
||||||
if (isSelected()) {
|
if (isSelected()) {
|
||||||
// highlight ordered cards
|
// highlight ordered cards
|
||||||
if (yCurrent > TOP_MARGIN) {
|
if (yCurrent > TOP_MARGIN) {
|
||||||
yCurrent -= CARD_MARGIN;
|
yCurrent -= CARD_MARGIN;
|
||||||
}
|
}
|
||||||
int multiplier = ((Tableau) pile).topInOrder();
|
int multiplier = ((Tableau) pile).topInOrder();
|
||||||
int heightOfRect = (multiplier - 1) * CARD_MARGIN;
|
int heightOfRect = (multiplier - 1) * CARD_MARGIN;
|
||||||
gc.setFill(highlight);
|
gc.setFill(highlight);
|
||||||
gc.fillRect(x, yCurrent - heightOfRect, Card.width, heightOfRect
|
gc.fillRect(x, yCurrent - heightOfRect, Card.width, heightOfRect + Card.height);
|
||||||
+ Card.height);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,26 +6,26 @@ import javafx.scene.paint.Color;
|
|||||||
|
|
||||||
public abstract class PileView {
|
public abstract class PileView {
|
||||||
|
|
||||||
protected static final Color highlight = new Color(1, 1, 0, 0.6);
|
protected static final Color highlight = new Color(1, 1, 0, 0.6);
|
||||||
protected final Pile pile;
|
protected final Pile pile;
|
||||||
private boolean isSelected = false;
|
private boolean isSelected = false;
|
||||||
|
|
||||||
public PileView(Pile pile) {
|
public PileView(Pile pile) {
|
||||||
this.pile = pile;
|
this.pile = pile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pile getPile() {
|
public Pile getPile() {
|
||||||
return pile;
|
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() {
|
public boolean isSelected() {
|
||||||
return isSelected;
|
return isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleSelected() {
|
public void toggleSelected() {
|
||||||
isSelected = !isSelected;
|
isSelected = !isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,24 @@ import javafx.scene.paint.Color;
|
|||||||
|
|
||||||
public class StackedPileView extends PileView {
|
public class StackedPileView extends PileView {
|
||||||
|
|
||||||
public StackedPileView(Pile pile) {
|
public StackedPileView(Pile pile) {
|
||||||
super(pile);
|
super(pile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paint(GraphicsContext gc, double x, double y) {
|
public void paint(GraphicsContext gc, double x, double y) {
|
||||||
if (pile.isEmpty()) {
|
if (pile.isEmpty()) {
|
||||||
// draw an outline
|
// draw an outline
|
||||||
gc.setStroke(Color.YELLOW);
|
gc.setStroke(Color.YELLOW);
|
||||||
gc.strokeRect(x, y, Card.width, Card.height);
|
gc.strokeRect(x, y, Card.width, Card.height);
|
||||||
} else {
|
} else {
|
||||||
// draw the top card
|
// draw the top card
|
||||||
gc.drawImage(pile.topCard().image(), x, y);
|
gc.drawImage(pile.topCard().image(), x, y);
|
||||||
}
|
}
|
||||||
if (isSelected()) {
|
if (isSelected()) {
|
||||||
gc.setFill(highlight);
|
gc.setFill(highlight);
|
||||||
gc.fillRect(x, y, Card.width, Card.height);
|
gc.fillRect(x, y, Card.width, Card.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user