diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..70eeb76 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +script: + - javac src/com/codecool/klondike/*.java \ No newline at end of file diff --git a/README.md b/README.md index 3141299..77a6f6b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Klondike Solitaire +[![Build Status](https://travis-ci.org/AJuszczakiewicz/javase-klondike-solitaire-kosteczki.svg?branch=master)](https://travis-ci.org/AJuszczakiewicz/javase-klondike-solitaire-kosteczki) + A Klondike Solitaire game written in Java using JavaFX as a GUI. ### Rules of the game diff --git a/src/com/codecool/klondike/Card.java b/src/com/codecool/klondike/Card.java index 896f1fb..74ae5af 100644 --- a/src/com/codecool/klondike/Card.java +++ b/src/com/codecool/klondike/Card.java @@ -7,10 +7,12 @@ import java.util.*; +import static java.util.Collections.*; + public class Card extends ImageView { - private int suit; - private int rank; + private Suit suit; + private Rank rank; private boolean faceDown; private Image backFace; @@ -23,7 +25,7 @@ public class Card extends ImageView { public static final int WIDTH = 150; public static final int HEIGHT = 215; - public Card(int suit, int rank, boolean faceDown) { + public Card(Suit suit, Rank rank, boolean faceDown) { this.suit = suit; this.rank = rank; this.faceDown = faceDown; @@ -34,11 +36,11 @@ public Card(int suit, int rank, boolean faceDown) { setEffect(dropShadow); } - public int getSuit() { + public Suit getSuit() { return suit; } - public int getRank() { + public Rank getRank() { return rank; } @@ -47,7 +49,7 @@ public boolean isFaceDown() { } public String getShortName() { - return "S" + suit + "R" + rank; + return "S" + suit.getValue() + "R" + rank.getValue(); } public DropShadow getDropShadow() { @@ -78,8 +80,10 @@ public String toString() { } public static boolean isOppositeColor(Card card1, Card card2) { - //TODO - return true; + if (card1.suit.getValue() <= 2 && card2.suit.getValue() > 2) + return true; + + return false; } public static boolean isSameSuit(Card card1, Card card2) { @@ -88,39 +92,28 @@ public static boolean isSameSuit(Card card1, Card card2) { public static List createNewDeck() { List result = new ArrayList<>(); - for (int suit = 1; suit < 5; suit++) { - for (int rank = 1; rank < 14; rank++) { - result.add(new Card(suit, rank, true)); + + //Zamienic for na for generyczny + for (Suit suit : Suit.values()) { + for (Rank cardRank: Rank.values()){ + result.add(new Card(suit, cardRank, true)); } } + Collections.shuffle(result); return result; } + public static void loadCardImages() { cardBackImage = new Image("card_images/card_back.png"); - String suitName = ""; - for (int suit = 1; suit < 5; suit++) { - switch (suit) { - case 1: - suitName = "hearts"; - break; - case 2: - suitName = "diamonds"; - break; - case 3: - suitName = "spades"; - break; - case 4: - suitName = "clubs"; - break; - } - for (int rank = 1; rank < 14; rank++) { - String cardName = suitName + rank; - String cardId = "S" + suit + "R" + rank; + for (Suit suit : Suit.values()) { + for (Rank cardRank: Rank.values()){ + String cardName = suit.getTextName() + cardRank.getValue(); + String cardId = "S" + suit.getValue() + "R" + cardRank.getValue(); String imageFileName = "card_images/" + cardName + ".png"; + System.out.println(imageFileName); cardFaceImages.put(cardId, new Image(imageFileName)); } } } - -} +} \ No newline at end of file diff --git a/src/com/codecool/klondike/Game.java b/src/com/codecool/klondike/Game.java index 32963d7..c61c9ac 100644 --- a/src/com/codecool/klondike/Game.java +++ b/src/com/codecool/klondike/Game.java @@ -4,6 +4,7 @@ import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import javafx.scene.control.Alert; +import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Background; @@ -12,10 +13,10 @@ import javafx.scene.layout.BackgroundRepeat; import javafx.scene.layout.BackgroundSize; import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.text.TextAlignment; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; public class Game extends Pane { @@ -37,6 +38,8 @@ public class Game extends Pane { private EventHandler onMouseClickedHandler = e -> { Card card = (Card) e.getSource(); if (card.getContainingPile().getPileType() == Pile.PileType.STOCK) { + saveMove(card); + card.moveToPile(discardPile); card.flip(); card.setMouseTransparent(false); @@ -62,15 +65,20 @@ public class Game extends Pane { double offsetY = e.getSceneY() - dragStartY; draggedCards.clear(); - draggedCards.add(card); + int draggedCardIndex = activePile.getCards().indexOf(card); + + activePile.getCards().listIterator(draggedCardIndex) + .forEachRemaining(draggedCards::add); - card.getDropShadow().setRadius(20); - card.getDropShadow().setOffsetX(10); - card.getDropShadow().setOffsetY(10); + draggedCards.forEach(draggedCard -> { + draggedCard.getDropShadow().setRadius(20); + draggedCard.getDropShadow().setOffsetX(10); + draggedCard.getDropShadow().setOffsetY(10); - card.toFront(); - card.setTranslateX(offsetX); - card.setTranslateY(offsetY); + draggedCard.toFront(); + draggedCard.setTranslateX(offsetX); + draggedCard.setTranslateY(offsetY); + }); }; private EventHandler onMouseReleasedHandler = e -> { @@ -78,12 +86,18 @@ public class Game extends Pane { return; Card card = (Card) e.getSource(); Pile pile = getValidIntersectingPile(card, tableauPiles); + if(pile == null) { + pile = getValidIntersectingPile(card, foundationPiles); + } //TODO if (pile != null) { + saveMove(card); + + //TODO isOpositeColor handleValidMove(card, pile); } else { draggedCards.forEach(MouseUtil::slideBack); - draggedCards = null; + draggedCards.removeAll(draggedCards); } }; @@ -96,6 +110,21 @@ public Game() { deck = Card.createNewDeck(); initPiles(); dealCards(); + + addRestartBtn(); + + // ======= Added dummy for test =============== + Button button = new Button(); + button.setText("Undo"); + button.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + Undoer.getInstance().undoAction(); + } + }); + // ============================================ + + getChildren().add(button); } public void addMouseEventHandlers(Card card) { @@ -106,13 +135,23 @@ public void addMouseEventHandlers(Card card) { } public void refillStockFromDiscard() { - //TODO + Collections.reverse(discardPile.getCards()); + discardPile.getCards().forEach(card -> card.flip()); + + MouseUtil.slideToDest(discardPile.getCards(), stockPile); + System.out.println("Stock refilled from discard pile."); } public boolean isMoveValid(Card card, Pile destPile) { - //TODO - return true; + if (foundationPiles.contains(destPile)) { + System.out.println("asdasd"); + return true; + } + else if (tableauPiles.contains(destPile)) + return true; + + return false; } private Pile getValidIntersectingPile(Card card, List piles) { Pile result = null; @@ -143,10 +182,38 @@ private void handleValidMove(Card card, Pile destPile) { msg = String.format("Placed %s to %s.", card, destPile.getTopCard()); } System.out.println(msg); + MouseUtil.slideToDest(draggedCards, destPile); + draggedCards.clear(); } + private void saveMove(Card card) { + List copyOfDraggedList = FXCollections.observableArrayList(draggedCards); + Pile sourcePile = card.getContainingPile(); + Runnable move; + + if (card.getContainingPile().getPileType() == Pile.PileType.STOCK) { + move = () -> { + card.moveToPile(sourcePile); + card.flip(); + }; + } + else { + Boolean isLastCardFaceDown = sourcePile.getTopCard().isFaceDown(); + Card saveLastCardFromPile = sourcePile.getTopCard(); + move = () -> { + if(isLastCardFaceDown) { + saveLastCardFromPile.flip(); + } + + MouseUtil.slideToDest(copyOfDraggedList, sourcePile); + }; + } + + Undoer.getInstance().addAction(Undoer.ActionOwner.USER, move); + } + private void initPiles() { stockPile = new Pile(Pile.PileType.STOCK, "Stock", STOCK_GAP); @@ -180,9 +247,53 @@ private void initPiles() { } } + private void addRestartBtn(){ + Button restartBtn = new Button("Restart"); + restartBtn.setTextAlignment(TextAlignment.CENTER); + restartBtn.relocate(1300,840); + restartBtn.setStyle("-fx-font: 18 times-new-roman; -fx-base: #c26573;"); + getChildren().add(restartBtn); + + restartBtn.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { + @Override + public void handle(MouseEvent e) { + restart(); + } + }); + } + private void clearPane() { + stockPile.clear(); + discardPile.clear(); + foundationPiles.clear(); + tableauPiles.clear(); + this.getChildren().clear(); + } + + private void restart() { + clearPane(); + deck = Card.createNewDeck(); + initPiles(); + dealCards(); + addRestartBtn(); + } + public void dealCards() { Iterator deckIterator = deck.iterator(); - //TODO + + for (Pile pile: tableauPiles) { + int index = tableauPiles.indexOf(pile); + + for(int i = 0; i < index+1; i++){ + Card card = deckIterator.next(); + pile.addCard(card); + addMouseEventHandlers(card); + getChildren().add(card); + if(i==index){ + card.flip(); + } + } + } + deckIterator.forEachRemaining(card -> { stockPile.addCard(card); addMouseEventHandlers(card); diff --git a/src/com/codecool/klondike/Pile.java b/src/com/codecool/klondike/Pile.java index 46ea20f..9f34243 100644 --- a/src/com/codecool/klondike/Pile.java +++ b/src/com/codecool/klondike/Pile.java @@ -40,8 +40,7 @@ public ObservableList getCards() { } public int numOfCards() { - //TODO - return 1; + return cards.size(); } public boolean isEmpty() { @@ -49,7 +48,7 @@ public boolean isEmpty() { } public void clear() { - //TODO + cards.removeAll(cards); } public void addCard(Card card) { diff --git a/src/com/codecool/klondike/Rank.java b/src/com/codecool/klondike/Rank.java new file mode 100644 index 0000000..8dd928b --- /dev/null +++ b/src/com/codecool/klondike/Rank.java @@ -0,0 +1,34 @@ +package com.codecool.klondike; + +public enum Rank { + Ace(1), + Eight(8), + Five(5), + Four(4), + Jack(11), + King(13), + Nine(9), + Queen(12), + Seven(7), + Six(6), + Ten(10), + Three(3), + Two(2); + + private int cardValue; + + Rank(int rankValue) { + cardValue = rankValue; + } + + public int getValue() { + return cardValue; + } + + public boolean previousEqual(Card card) { + return (cardValue - 1) == card.getRank().getValue(); + } + public boolean nextEqual(Card card) { + return ((cardValue + 1) == card.getRank().getValue()); + } +} diff --git a/src/com/codecool/klondike/Suit.java b/src/com/codecool/klondike/Suit.java new file mode 100644 index 0000000..c5146aa --- /dev/null +++ b/src/com/codecool/klondike/Suit.java @@ -0,0 +1,24 @@ +package com.codecool.klondike; + +public enum Suit { + DIAMONDS(1, "diamonds"), + HEARTS(2, "hearts"), + CLUBS(3, "clubs"), + SPADES(4, "spades"); + + private int number; + private String textName; + + Suit(int number, String textName) { + this.number = number; + this.textName = textName; + } + + public int getValue() { + return number; + } + + public String getTextName() { + return textName; + } +} diff --git a/src/com/codecool/klondike/Undoer.java b/src/com/codecool/klondike/Undoer.java new file mode 100644 index 0000000..b64a50f --- /dev/null +++ b/src/com/codecool/klondike/Undoer.java @@ -0,0 +1,45 @@ +package com.codecool.klondike; + +import java.util.*; +import java.util.Map.Entry; + +public class Undoer { + public enum ActionOwner { + USER, + GAME + } + + private static Undoer instance; + + private final LinkedList> undoSteps; + + private Undoer(){ + undoSteps = new LinkedList<>(); + } + + public void addAction(ActionOwner owner, Runnable undoStep) { + Entry step = new AbstractMap.SimpleEntry<>(owner, undoStep); + + undoSteps.push(step); + } + + public void undoAction() { + if(undoSteps.isEmpty()) return; + + Entry lastStep = undoSteps.pop(); + lastStep.getValue().run(); + + while(!undoSteps.isEmpty() && undoSteps.getLast().getKey() == ActionOwner.GAME) { + lastStep = undoSteps.pop(); + lastStep.getValue().run(); + } + } + + public static Undoer getInstance() { + if(instance == null) { + instance = new Undoer(); + } + + return instance; + } +}