Commit 2a0dd62e authored by Muhammad Tehami's avatar Muhammad Tehami 💡

Solitaire game added

parent 4450df98
{
"directory": "app/bower_components"
}
\ No newline at end of file
{
"globals": {
"angular": false,
"_": false
},
"rules": {
"no-use-before-define": [
2,
"nofunc"
]
}
}
\ No newline at end of file
logs/*
!.gitkeep
node_modules/
bower_components/
tmp
.DS_Store
## Directory-based project format:
.idea/
Note: card images - see license in app/cards/images/0_LICENSE.txt or http://www.jfitz.com/cards/
The MIT License
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
{
"env": {
"browser": true,
"node": false
}
}
\ No newline at end of file
(function () {
"use strict";
angular.module("solitaire", ["klondike", "ngDraggable"]);
})();
<div ng-if="card">
<img ng-if="card.turnedUp"
ng-src="cards/images/{{card.image}}"
/>
<div ng-if="!card.turnedUp"
class="turned-down-card"
></div>
</div>
\ No newline at end of file
function Card(card) {
this.suit = card.suit;
this.rank = card.rank;
this.image = (Card.ranksInImagesOrder.indexOf(this.rank) * 4 + Card.suitsInImagesOrder.indexOf(this.suit) + 1) + ".png";
this.color = card.suit === "Spades" || card.suit === "Clubs" ? "black" : "red";
this.turnedUp = false;
}
Card.ranksInImagesOrder = ["Ace", "King", "Queen", "Jack", "10", "9", "8", "7", "6", "5", "4", "3", "2"];
Card.suitsInImagesOrder = ["Clubs", "Spades", "Hearts", "Diamonds"];
Card.prototype.turnUp = function () {
this.turnedUp = true;
};
Card.prototype.turnDown = function () {
this.turnedUp = false;
};
function Deck() {
this.unShuffled = function unShuffled() {
return _.chain(Card.ranksInImagesOrder)
.map(function (rank) {
return Card.suitsInImagesOrder.map(function (suit) {
return {
suit: suit,
rank: rank
};
});
})
.flatten()
.map(function (card) {
return new Card(card);
})
.value();
};
this.shuffled = function shuffled() {
return _.shuffle(this.unShuffled());
};
}
\ No newline at end of file
http://www.jfitz.com/cards/
These images were created using GIFCon, XnView and Paint Shop Pro.
Feel free to use for personal or professional purposes, subject to the following:
Additional Copyright information:
*Larry Ewing <lewing@isc.tamu.edu> created Tux using GIMP.
*Marshall Kirk McKusick <mckusick@mckusick.com> is the copyright holder and creator of the BSD Daemon image.
*The "Windows" cards were originally designed by Susan Kare for Microsoft.
To the best of my knowledge, the images used in any "standard" French/British 52 card deck are public domain.
*The top Jokers are derived from an image I found of a John Waddington design.
*The bottom Jokers were created by me as quick placeholders for a design I never got around to implementing.
*I guess it's possible that Microsoft could claim copyright on the Windows Ace of Spades.
*If they did, I'd suggest filling in or changing the white pattern on the Spade.
*The game of solitaire did not require the cards mentioned in these additional copyright notices
\ No newline at end of file
<!DOCTYPE html>
<html lang="en" ng-app="solitaire" class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Solitaire</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="klondike/game.css">
</head>
<body>
<div ng-view></div>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/ngDraggable/ngDraggable.js"></script>
<script src="app.js"></script>
<script src="klondike/piles/pile.js"></script>
<script src="klondike/piles/remainderPile.js"></script>
<script src="klondike/piles/tableauPile.js"></script>
<script src="klondike/piles/foundationPile.js"></script>
<script src="klondike/scoring.js"></script>
<script src="klondike/klondike.js"></script>
<script src="klondike/board.js"></script>
<script src="klondike/game.js"></script>
<script src="cards/cards.js"></script>
</body>
</html>
<p>
<button ng-click="game.newGame()">New Game</button>
Score: {{ scoring.score }}
</p>
<div class="top-row">
<s-foundation ng-repeat="foundation in game.foundations"></s-foundation>
<s-no-pile></s-no-pile>
<s-no-pile></s-no-pile>
<s-remainder></s-remainder>
<s-waste></s-waste>
</div>
<div class="tableau-row">
<s-no-pile></s-no-pile>
<s-tableau ng-repeat="tableau in game.tableaus"></s-tableau>
</div>
\ No newline at end of file
(function () {
"use strict";
angular.module("klondike.board", ["ngRoute", "klondike.game"])
.config(["$routeProvider", function ($routeProvider) {
$routeProvider
.when("/board", {
templateUrl: "klondike/board.html",
controller: "KlondikeController"
})
.otherwise({redirectTo: "/board"});
}])
.controller("KlondikeController", ["$scope", "klondikeGame", "scoring", function KlondikeController($scope, klondikeGame, scoring) {
klondikeGame.newGame();
$scope.game = klondikeGame;
$scope.scoring = scoring;
}])
.directive("sNoPile", function () {
return {
restrict: "E",
template: "<div class=\"no-pile\"></div>"
};
})
.directive("sTableau", function () {
return {
restrict: "E",
templateUrl: "klondike/piles/tableau.html"
};
})
.directive("sFoundation", function () {
return {
restrict: "E",
templateUrl: "klondike/piles/foundation.html"
};
})
.directive("sCard", function () {
return {
restrict: "A",
templateUrl: "cards/card.html",
scope: {
card: "="
}
};
})
.directive("sRemainder", function () {
return {
restrict: "E",
templateUrl: "klondike/piles/remainder.html"
};
})
.directive("sWaste", function () {
return {
restrict: "E",
templateUrl: "klondike/piles/waste.html"
};
});
})();
body {
padding-left: 50px;
padding-right: 50px;
background: #B1CB9B;
}
div.pile,
div.no-pile {
width: 72px;
height: 96px;
margin-right: 10px;
float: left;
}
div.pile {
background-color: #CCE1A9;
}
.turned-down-card {
background: url('../cards/images/b1fv.png');
width: 72px;
height: 96px;
float: left;
}
.only-show-top-of-card {
width: 72px;
height: 16px;
float: left;
}
.tableau-row {
padding-top: 50px;
clear: both;
}
\ No newline at end of file
(function () {
"use strict";
angular.module("klondike.game", [])
.service("klondikeGame", ["scoring", KlondikeGame]);
function KlondikeGame(scoring) {
this.newGame = function newGame() {
var cards = new Deck().shuffled();
this.newGameFromDeck(cards);
};
this.newGameFromDeck = function (cards) {
scoring.newGame();
turnAllCardsDown(cards);
this.tableaus = dealTableaus(cards);
this.foundations = buildFoundations();
this.remainder = dealRemainder(cards);
};
function turnAllCardsDown(cards) {
cards.forEach(function (card) {
card.turnDown();
});
}
function dealTableaus(cards) {
var tableaus = [
new TableauPile(cards.slice(0, 1), scoring),
new TableauPile(cards.slice(1, 3), scoring),
new TableauPile(cards.slice(3, 6), scoring),
new TableauPile(cards.slice(6, 10), scoring),
new TableauPile(cards.slice(10, 15), scoring),
new TableauPile(cards.slice(15, 21), scoring),
new TableauPile(cards.slice(21, 28), scoring)
];
tableaus.forEach(function (tableau) {
tableau.turnTopCardUp();
});
return tableaus;
}
function buildFoundations() {
return _.range(1, 5)
.map(function () {
return new FoundationPile([], scoring);
});
}
function dealRemainder(cards) {
return new RemainderPile(cards.slice(28), scoring);
}
}
KlondikeGame.prototype.tryMoveTopCardToAnyFoundation = function (sourcePile) {
if (sourcePile.isEmpty()) {
return;
}
var foundationThatWillAccept = _.find(this.foundations, function (foundation) {
return foundation.willAcceptCard(sourcePile.topCard());
});
if (foundationThatWillAccept) {
foundationThatWillAccept.moveCardsFrom(sourcePile);
}
};
})();
angular.module("klondike", [
"klondike.game",
"klondike.board",
"klondike.scoring"
]);
<div class="pile"
ng-drop="true" ng-drop-success="foundation.moveCardsFrom($data)">
<div s-card card="foundation.topCard()"
ng-drag="true" ng-drag-data="foundation">
</div>
</div>
\ No newline at end of file
function FoundationPile(cards, scoring) {
Pile.call(this, cards, scoring);
}
FoundationPile.prototype = Object.create(Pile.prototype);
FoundationPile.prototype.constructor = FoundationPile;
FoundationPile.prototype.willAcceptCard = function (card) {
if (this.isEmpty()) {
return card.rank === "Ace";
}
var topCard = this.topCard();
var nextRank = Pile.increasingRanks[Pile.increasingRanks.indexOf(topCard.rank) + 1];
return topCard.suit === card.suit
&& card.rank === nextRank;
};
FoundationPile.prototype.drop = function (card) {
if (this.willAcceptCard(card)) {
this.addTopCard(card);
return true;
}
return false;
};
function Pile(cards, scoring) {
this.cards = cards || [];
this.scoring = scoring;
}
Pile.prototype.isEmpty = function () {
return this.cards.length === 0;
};
Pile.prototype.topCard = function () {
return this.cards[this.cards.length - 1];
};
Pile.prototype.removeCard = function (card) {
var index = this.cards.indexOf(card);
this.cards.splice(index, 1);
};
Pile.prototype.addTopCard = function (card) {
this.cards.push(card);
};
Pile.prototype.moveTopCardFrom = function (source) {
var topOfSource = source.topCard();
var accepted = this.drop(topOfSource);
if (accepted) {
this.scoring.dropped(source.constructor, this.constructor);
source.removeCard(topOfSource);
}
};
Pile.prototype.moveCardsFrom = Pile.prototype.moveTopCardFrom;
Pile.increasingRanks = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"];
\ No newline at end of file
<div class="pile"
ng-click="game.remainder.flipTopCardToWaste()">
<div s-card card="game.remainder.topCard()">
</div>
</div>
\ No newline at end of file
function WastePile(cards, scoring) {
Pile.call(this, cards, scoring);
}
WastePile.prototype = Object.create(Pile.prototype);
WastePile.prototype.constructor = WastePile;
function RemainderPile(cards, scoring) {
Pile.call(this, cards, scoring);
this.waste = new WastePile([], scoring);
}
RemainderPile.prototype = Object.create(Pile.prototype);
RemainderPile.prototype.constructor = RemainderPile;
RemainderPile.prototype.flipTopCardToWaste = function () {
var card = this.topCard();
if (card === undefined) {
recycleWaste.call(this);
this.scoring.wasteRecycled();
return;
}
flipToWaste.call(this, card);
};
function flipToWaste(card) {
this.removeCard(card);
this.waste.addTopCard(card);
card.turnUp();
}
function recycleWaste() {
this.cards = this.waste.cards.reverse();
this.waste = new WastePile([], this.scoring);
this.cards.forEach(function (card) {
card.turnDown();
});
}
\ No newline at end of file
<div class="pile"
ng-dblclick="game.tryMoveTopCardToAnyFoundation(tableau)"
ng-drop="true" ng-drop-success="tableau.moveCardsFrom($data)"
ng-style="{height: tableau.heightForDrop()}">
<div ng-repeat="card in tableau.cards"
s-card card="card"
ng-class="{ 'only-show-top-of-card': card != tableau.topCard() }"
ng-drag="card == tableau.topCard()" ng-drag-data="tableau">
</div>
</div>
\ No newline at end of file
function TableauPile(cards, scoring) {
Pile.call(this, cards, scoring);
}
TableauPile.prototype = Object.create(Pile.prototype);
TableauPile.prototype.constructor = TableauPile;
TableauPile.prototype.turnTopCardUp = function () {
this.topCard().turnUp();
};
TableauPile.prototype.willAcceptCard = function (card) {
if (this.isEmpty()) {
return card.rank === "King";
}
var topCard = this.topCard();
var nextLowerRank = Pile.increasingRanks[Pile.increasingRanks.indexOf(topCard.rank) - 1];
return card.rank === nextLowerRank &&
card.color !== topCard.color;
};
TableauPile.prototype.drop = function (card) {
if (this.willAcceptCard(card)) {
this.addTopCard(card);
return true;
}
return false;
};
TableauPile.prototype.removeCard = function (card) {
Pile.prototype.removeCard.call(this, card);
var topCard = this.topCard();
if (!topCard) {
return;
}
if (!topCard.turnedUp) {
this.scoring.tableauCardTurnedUp();
}
topCard.turnUp();
};
TableauPile.prototype.moveFromTableau = function (source) {
var destination = this;
var acceptIndex = _.findIndex(source.cards, function (card) {
return card.turnedUp && destination.willAcceptCard(card);
});
if (acceptIndex < 0) {
return;
}
var rest = _.rest(source.cards, acceptIndex);
rest.forEach(function (card) {
source.removeCard(card);
destination.addTopCard(card);
});
};
TableauPile.prototype.moveCardsFrom = function (source) {
if(source == null) {
return;
}
if (source instanceof TableauPile) {
this.moveFromTableau(source);
return;
}
this.moveTopCardFrom(source);
};
TableauPile.prototype.heightForDrop = function () {
return (96 + Math.max(0, this.cards.length - 1) * 16) + 'px';
};
\ No newline at end of file
<div class="pile"
ng-dblclick="game.tryMoveTopCardToAnyFoundation(game.remainder.waste)">
<div s-card card="game.remainder.waste.topCard()"
ng-drag="true" ng-drag-data="game.remainder.waste">
</div>
</div>
\ No newline at end of file
angular.module("klondike.scoring", [])
.service("scoring", [function Scoring() {
"use strict";
this.score = 0;
this.newGame = function () {
this.score = 0;
};
this.tableauCardTurnedUp = function () {
this.score += 5;
};
this.dropped = function (source, destionation) {
this.score += scoreForMoving(source, destionation) || 0;
};
this.wasteRecycled = function () {
this.score = Math.max(this.score - 100, 0);
};
function scoreForMoving(source, destionation) {
if (destionation.name === "TableauPile") {
if (source.name === "FoundationPile") {
return -15;
}
return 5;
}
if (destionation.name === "FoundationPile") {
if (source.name === "TableauPile" || source.name === "WastePile") {
return 10;
}
}
}
}]);
{
"name": "solitaire",
"private": true,
"dependencies": {
"angular": "1.2.28",
"angular-route": "1.2.28",
"angular-loader": "1.2.28",
"angular-mocks": "1.2.28",
"underscore": "1.8.2",
"ngDraggable": "0.1.8"
}
}
/* eslint-env node*/
module.exports = function (config) {
"use strict";
config.set({
basePath: "./",
files: [
"app/bower_components/angular/angular.js",
"app/bower_components/angular-route/angular-route.js",
"app/bower_components/angular-mocks/angular-mocks.js",
"app/bower_components/underscore/underscore.js",
"app/cards/**/*.js",
"app/klondike/piles/pile.js",
"app/klondike/piles/foundationPile.js",
"app/klondike/piles/remainderPile.js",
"app/klondike/piles/tableauPile.js",
"app/klondike/*.js",
"tests/**/*.js"
],
autoWatch: true,
frameworks: ["jasmine"],
browsers: ["Chrome", "Firefox"],
plugins: [
"karma-chrome-launcher",
"karma-firefox-launcher",
"karma-jasmine",
"karma-junit-reporter"
],
junitReporter: {
outputFile: "test_out/unit.xml",
suite: "unit"
}
});
};
{
"name": "solitaire",
"private": true,
"version": "0.0.0",
"description": "A game of solitaire",
"repository": "https://github.com/g0t4/angular-solitaire",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"bower": "^1.3.1",
"eslint": "^1.5.1",
"http-server": "^0.8.4",
"jasmine-core": "^2.3.4",
"karma": "~0.13",
"karma-chrome-launcher": "^0.2.0",
"karma-firefox-launcher": "^0.1.6",
"karma-jasmine": "^0.3.6",
"karma-junit-reporter": "^0.3.4"
},
"scripts": {
"postinstall": "node node_modules/bower/bin/bower install",
"prestart": "npm install",
"start": "http-server -c-1 -o",
"pretest": "npm install",
"test": "karma start karma.conf.js",
"test-single-run": "karma start karma.conf.js --single-run"
}
}
{
"env": {
"node": true,
"jasmine": true
},
"globals": {
"module": false,
"inject": false
}
}
\ No newline at end of file
describe("A card", function () {
"use strict";
it("should link to the image of the suit and rank", function () {
expect(new Card({rank: "King", suit: "Spades"}).image).toBe("6.png");
expect(new Card({rank: "8", suit: "Hearts"}).image).toBe("27.png");
expect(new Card({rank: "Ace", suit: "Diamonds"}).image).toBe("4.png");
expect(new Card({rank: "2", suit: "Clubs"}).image).toBe("49.png");
});
it("should map its color to black or red based on suit", function () {
expect(new Card({suit: "Spades"}).color).toBe("black");
expect(new Card({suit: "Hearts"}).color).toBe("red");
expect(new Card({suit: "Diamonds"}).color).toBe("red");
expect(new Card({suit: "Clubs"}).color).toBe("black");
});
});
describe("A deck of cards", function () {
"use strict";
it("should have 52 cards", inject(function () {
expect(new Deck().unShuffled().length).toBe(52);
}));
it("should start with aces", inject(function () {
var cards = new Deck().unShuffled();
expect(cards[0].rank).toEqual("Ace");
expect(cards[0].suit).toEqual("Clubs");
expect(cards[1].rank).toEqual("Ace");
expect(cards[1].suit).toEqual("Spades");
expect(cards[2].rank).toEqual("Ace");
expect(cards[2].suit).toEqual("Hearts");
expect(cards[3].rank).toEqual("Ace");
expect(cards[3].suit).toEqual("Diamonds");
}));
it("should end with twos", inject(function () {
var cards = new Deck().unShuffled();
expect(cards[48].rank).toEqual("2");
expect(cards[48].suit).toEqual("Clubs");
expect(cards[49].rank).toEqual("2");
expect(cards[49].suit).toEqual("Spades");
expect(cards[50].rank).toEqual("2");
expect(cards[50].suit).toEqual("Hearts");
expect(cards[51].rank).toEqual("2");
expect(cards[51].suit).toEqual("Diamonds");
}));
it("should shuffle", inject(function () {
var unShuffled = new Deck().unShuffled();
var shuffled = new Deck().shuffled();
expect(shuffled).not.toEqual(unShuffled);
}));
});
describe("A klondike board controller", function () {
"use strict";
beforeEach(module("klondike"));
var scope;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
$controller("KlondikeController", {$scope: scope});
expect(scope.game).toBeDefined();
expect(scope.game.tableaus).toBeDefined();
}));
it("should load a new game", function () {
expect(scope.game).toBeDefined();
expect(scope.game.tableaus).toBeDefined();
});
it("should load the score", function () {
expect(scope.scoring).toBeDefined();
expect(scope.scoring.score).toBe(0);
});
});
describe("A game of klondike", function () {
"use strict";
beforeEach(module("klondike"));
it("should deal seven tableau piles of increasing piles.", inject(function (klondikeGame) {
klondikeGame.newGame();
expect(klondikeGame.tableaus.length).toBe(7);
expect(klondikeGame.tableaus[0].cards.length).toBe(1);
expect(klondikeGame.tableaus[1].cards.length).toBe(2);
expect(klondikeGame.tableaus[2].cards.length).toBe(3);
expect(klondikeGame.tableaus[3].cards.length).toBe(4);
expect(klondikeGame.tableaus[4].cards.length).toBe(5);
expect(klondikeGame.tableaus[5].cards.length).toBe(6);
expect(klondikeGame.tableaus[6].cards.length).toBe(7);
}));
function anyTurnedUp(Klondike) {
return Klondike.remainder.cards.filter(function (card) {
return card.turnedUp === true;
}).length > 0;
}
it("should deal remainder to deck turned down.", inject(function (klondikeGame) {
klondikeGame.newGame();
expect(klondikeGame.remainder.cards.length).toBe(24);
expect(anyTurnedUp(klondikeGame)).toBe(false);
}));
it("should not deal any duplicate cards.", inject(function (klondikeGame) {
klondikeGame.newGame();
var uniqueDealtCards = _.chain(klondikeGame.tableaus)
.map("cards")
.flatten()
.union(klondikeGame.remainder.cards)
.uniq()
.value();
expect(uniqueDealtCards.length).toBe(52);
}));
it("should deal tableau piles with last card turned up.", inject(function (klondikeGame) {
klondikeGame.newGame();
var tableaus = klondikeGame.tableaus;
var uniqueTableauCards = _.chain(tableaus).map("cards").flatten().uniq().value();
var shouldBeTurnedUp = tableaus.map(function (tableau) {
return tableau.topCard();
});
var actuallyTurnedUp = _.filter(uniqueTableauCards, "turnedUp");
expect(actuallyTurnedUp).toEqual(shouldBeTurnedUp);
}));
describe("with an unshuffled deck", function () {
var klondikeGame;
beforeEach(inject(function (_klondikeGame_) {
// note: board dealt with cards in order has the following in tableaus:
// ace spades, ace hearts, king spades, queen spades, jack hearts, 9 clubs, 8 diamonds
// first remainder card is 2 diamonds
var unShuffled = new Deck().unShuffled();
_klondikeGame_.newGameFromDeck(unShuffled);
klondikeGame = _klondikeGame_;
}));
it("should allow moving first tableau's ace to first foundation", function () {
var firstTableau = klondikeGame.tableaus[0];
var firstFoundation = klondikeGame.foundations[0];
var ace = firstTableau.topCard();
expect(ace.rank).toBe("Ace");
firstFoundation.moveCardsFrom(firstTableau);
expect(firstFoundation.cards).toContain(ace);
expect(firstTableau.isEmpty()).toBe(true);
});
it("should allow moving second tableau's ace to first foundation and turns over next card in second tableau", function () {
var secondTableau = klondikeGame.tableaus[1];
var firstFoundation = klondikeGame.foundations[0];
var ace = secondTableau.topCard();
expect(ace.rank).toBe("Ace");
firstFoundation.moveCardsFrom(secondTableau);
expect(secondTableau.cards.length).toBe(1);
expect(secondTableau.topCard().turnedUp).toBe(true);
});
it("should not allow moving third tableau's king of spades to first foundation", function () {
var thirdTableau = klondikeGame.tableaus[2];
var firstFoundation = klondikeGame.foundations[0];
var king = thirdTableau.topCard();
expect(king.rank).toBe("King");
firstFoundation.moveCardsFrom(thirdTableau);
expect(firstFoundation.isEmpty()).toBe(true);
expect(thirdTableau.topCard()).toBe(king);
});
it("should not allow moving remainder's two of diamonds to first tableau's ace of clubs", function () {
var firstTableau = klondikeGame.tableaus[0];
klondikeGame.remainder.flipTopCardToWaste();
var waste = klondikeGame.remainder.waste;
var two = waste.topCard();
expect(two.rank).toBe("2");
firstTableau.moveCardsFrom(waste);
expect(firstTableau.topCard().rank).toBe("Ace");
expect(waste.topCard()).toBe(two);
});
it(", if we reverse remainder (hack the scenario) so 7 of clubs is on top, should allow moving foundation's 7 of clubs to last tableau's 8 of diamonds", function () {
var lastTableau = klondikeGame.tableaus[6];
klondikeGame.remainder.cards.reverse();
klondikeGame.remainder.flipTopCardToWaste();
var waste = klondikeGame.remainder.waste;
var sevenOfClubs = waste.topCard();
expect(sevenOfClubs.rank).toBe("7");
expect(sevenOfClubs.suit).toBe("Clubs");
lastTableau.moveCardsFrom(waste);
expect(lastTableau.topCard()).toBe(sevenOfClubs);
expect(waste.isEmpty()).toBe(true);
});
it("should allow moving remainder to foundation", function () {
var secondTableau = klondikeGame.tableaus[1];
var firstFoundation = klondikeGame.foundations[0];
var aceOfHearts = secondTableau.topCard();
expect(aceOfHearts.rank).toBe("Ace");
expect(aceOfHearts.suit).toBe("Hearts");
firstFoundation.moveCardsFrom(secondTableau);
var remainder = klondikeGame.remainder;
remainder.flipTopCardToWaste();
remainder.flipTopCardToWaste();
var twoOfHearts = remainder.waste.topCard();
expect(twoOfHearts.rank).toBe("2");
expect(twoOfHearts.suit).toBe("Hearts");
firstFoundation.moveCardsFrom(remainder.waste);
expect(remainder.waste.topCard()).not.toBe(twoOfHearts);
expect(firstFoundation.topCard()).toBe(twoOfHearts);
});
it("should not allow moving remainder to foundation if not next rank in suit", function () {
var firstFoundation = klondikeGame.foundations[0];
var remainder = klondikeGame.remainder;
remainder.flipTopCardToWaste();
var two = remainder.waste.topCard();
expect(two.rank).toBe("2");
firstFoundation.moveCardsFrom(remainder.waste);
expect(firstFoundation.isEmpty()).toBe(true);
expect(remainder.waste.topCard()).toBe(two);
});
it(", tryMoveTopCardToAnyFoundation should moveCardsFrom first tableau's ace to first foundation", function () {
var firstTableau = klondikeGame.tableaus[0];
var firstFoundation = klondikeGame.foundations[0];
var ace = firstTableau.topCard();
expect(ace.rank).toBe("Ace");
klondikeGame.tryMoveTopCardToAnyFoundation(firstTableau);
expect(firstFoundation.cards).toContain(ace);
expect(firstTableau.isEmpty()).toBe(true);
});
it(", tryMoveTopCardToAnyFoundation should not error on empty pile", function () {
var firstTableau = klondikeGame.tableaus[0];
var ace = firstTableau.topCard();
expect(ace.rank).toBe("Ace");
klondikeGame.tryMoveTopCardToAnyFoundation(firstTableau);
klondikeGame.tryMoveTopCardToAnyFoundation(firstTableau);
});
});
});
describe("A foundation pile", function () {
"use strict";
it("that is empty will accept an ace", function () {
var ace = new Card({rank: "Ace", suit: "Clubs"});
var foundation = new FoundationPile();
var accepted = foundation.drop(ace);
expect(accepted).toBe(true);
expect(foundation.topCard()).toBe(ace);
});
it("that is empty will not accept a rank other than ace", function () {
var king = new Card({rank: "King", suit: "Clubs"});
var foundation = new FoundationPile();
var accepted = foundation.drop(king);
expect(accepted).toBe(false);
expect(foundation.isEmpty()).toBe(true);
});
it("will accept next rank in same suit", function() {
var aceOfClubs = new Card({rank: "Ace", suit: "Clubs"});
var foundation = new FoundationPile();
foundation.drop(aceOfClubs);
var twoOfClubs = new Card({rank: "2", suit: "Clubs"});
var accepted = foundation.drop(twoOfClubs);
expect(accepted).toBe(true);
expect(foundation.topCard()).toBe(twoOfClubs);
});
it("will not accept same suit but not next rank", function() {
var aceOfClubs = new Card({rank: "Ace", suit: "Clubs"});
var foundation = new FoundationPile();
foundation.drop(aceOfClubs);
var threeOfClubs = new Card({rank: "3", suit: "Clubs"});
var accepted = foundation.drop(threeOfClubs);
expect(accepted).toBe(false);
expect(foundation.topCard()).toBe(aceOfClubs);
});
it("will not accept different suit", function() {
var aceOfClubs = new Card({rank: "Ace", suit: "Clubs"});
var foundation = new FoundationPile();
foundation.drop(aceOfClubs);
var twoOfHearts = new Card({rank: "2", suit: "Hearts"});
var accepted = foundation.drop(twoOfHearts);
expect(accepted).toBe(false);
expect(foundation.topCard()).toBe(aceOfClubs);
});
});
describe("A pile", function () {
"use strict";
describe("without cards ", function () {
var emptyPile;
beforeEach(function() {
emptyPile = new Pile();
});
it("should be empty", function () {
expect(emptyPile.isEmpty()).toBe(true);
});
it("should return undefined as the top card", function () {
expect(emptyPile.topCard()).toBeUndefined();
});
it(", removeCard should not error", function () {
emptyPile.removeCard({});
});
});
describe("with cards", function () {
var cards;
var pileWithCards;
beforeEach(function() {
cards = new Deck().unShuffled();
pileWithCards = new Pile(cards);
});
it("should not be empty", function () {
expect(pileWithCards.isEmpty()).toBe(false);
});
it("should return the last card as the top card", function () {
var lastCard = cards[cards.length - 1];
expect(pileWithCards.topCard()).toBe(lastCard);
});
it(", removeCard should not error if card not in pile", function () {
pileWithCards.removeCard({});
});
it(", removeCard should remove card", function () {
var bottomCard = pileWithCards.cards[0];
pileWithCards.removeCard(bottomCard);
expect(pileWithCards.cards).not.toContain(bottomCard);
});
it(", addTopCard should put card on top", function () {
var newCard = {};
pileWithCards.addTopCard(newCard);
expect(pileWithCards.topCard()).toBe(newCard);
});
});
});
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment