Last active
August 22, 2017 15:32
-
-
Save anjensan/3463b76ecb24695d9360a88ed7651330 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cardTypes = {clubs: '\u2663', diamonds: '\u2666', hearts: '\u2665', spades: '\u2660'}; | |
const cardValues = {6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q', 13: 'K', 14: 'A'}; | |
class Card | |
{ | |
constructor(value, type) | |
{ | |
this.value = value; | |
this.type = type; | |
} | |
} | |
class CardCollection | |
{ | |
constructor(cards) | |
{ | |
this.cards = cards; | |
} | |
count() | |
{ | |
return this.cards.length; | |
} | |
add(card) | |
{ | |
this.cards.push(card); | |
} | |
addToStart(card) | |
{ | |
this.cards.unshift(card); | |
} | |
addAll(collection) | |
{ | |
this.cards.push.apply(this.cards, collection.cards); | |
collection.cards.length = 0; | |
} | |
shuffle() | |
{ | |
var cards = this.cards; | |
for (var i = 0; i < cards.length; i++) { | |
var randomIndex = Math.floor(Math.random() * cards.length); | |
var tmp = cards[i]; | |
cards[i] = cards[randomIndex]; | |
cards[randomIndex] = tmp; | |
} | |
} | |
takeLast(n) | |
{ | |
var startIndex = this.cards.length - n; | |
var cards = this.cards.splice((startIndex > 0 ? startIndex : 0), n); | |
return new CardCollection(cards); | |
} | |
takeLastCard() | |
{ | |
var card = this.cards.pop(); | |
return card; | |
} | |
takeAt(index) | |
{ | |
var card; | |
if (index >= 0) { | |
card = this.cards.splice(index, 1)[0]; | |
} | |
return card; | |
} | |
readAt(index) | |
{ | |
var card = this.cards[index]; | |
return card; | |
} | |
} | |
class Player | |
{ | |
constructor(isComputer) | |
{ | |
this.cardsInHands = new CardCollection([]); | |
this.isComputer = isComputer; | |
} | |
takeCard(card) | |
{ | |
this.cardsInHands.add(card); | |
} | |
takeAllCards(cards) | |
{ | |
this.cardsInHands.addAll(cards); | |
} | |
getCardForAttack(gameState) | |
{ | |
var index = this.getCardForComputerAttack(gameState); | |
return this.cardsInHands.takeAt(index); | |
} | |
getCardForDefence(gameState) | |
{ | |
var index = this.getCardForComputerDefence(gameState); | |
return this.cardsInHands.takeAt(index); | |
} | |
getCardForComputerAttack(gameState) | |
{ | |
var index = -1; | |
if (this.cardsInHands.count() > 0) { | |
if (gameState.activeCards.count() == 0) { | |
index = Math.floor(Math.random() * this.cardsInHands.count()); | |
} | |
var i = -1, card; | |
while (card = this.cardsInHands.readAt(++i)) { | |
if (gameState.isAttackStepAllowed(card)) { | |
index = i; | |
break; | |
} | |
} | |
} | |
return index; | |
} | |
getCardForComputerDefence(gameState) | |
{ | |
var index = -1; | |
if (this.cardsInHands.count() > 0) { | |
var i = -1, card; | |
while (card = this.cardsInHands.readAt(++i)) { | |
if (gameState.isDefenceStepAllowed(card)) { | |
index = i; | |
break; | |
} | |
} | |
} | |
return index; | |
} | |
} | |
class GameState | |
{ | |
constructor() | |
{ | |
this.deck = this.initDeck(); | |
this.players = []; | |
this.activeCards = new CardCollection([]); | |
this.beat = new CardCollection([]); | |
this.trump = null; | |
this.attackerIndex = -1; | |
this.defenderIndex = -1; | |
this.attacker = null; | |
this.defender = null; | |
} | |
// for debug | |
setData(data) | |
{ | |
this.deck = new CardCollection(data.deck.cards); | |
var players = []; | |
for (var i in data.players) { | |
var player = new Player(data.players[i].isComputer); | |
player.takeAllCards(new CardCollection(data.players[i].cardsInHands.cards)); | |
players.push(player); | |
} | |
this.players = players; | |
this.activeCards = new CardCollection(data.activeCards.cards); | |
this.beat = new CardCollection(data.beat.cards); | |
this.trump = data.trump; | |
this.attackerIndex = -1; | |
this.defenderIndex = -1; | |
this.attacker = null; | |
this.defender = null; | |
for (var i in data.players) { | |
if (data.attackerIndex >= 0) { | |
this.attackerIndex = data.attackerIndex; | |
this.attacker = this.players[data.attackerIndex]; | |
} | |
if (data.defenderIndex >= 0) { | |
this.defenderIndex = data.defenderIndex; | |
this.defender = this.players[data.defenderIndex]; | |
} | |
} | |
} | |
initDeck() | |
{ | |
var deck = []; | |
for (var type in cardTypes) { | |
for (var value in cardValues) { | |
var card = new Card(parseInt(value), type); | |
deck.push(card); | |
} | |
} | |
return new CardCollection(deck); | |
} | |
// external callback | |
startGame(players) | |
{ | |
this.takeDeck(); | |
this.players = players; | |
this.deck.shuffle(); | |
this.deal(); | |
this.setTrump(); | |
this.setActivePlayers(); | |
this.continueGameLoop(); | |
} | |
takeDeck() | |
{ | |
for (var i in this.players) { | |
this.deck.addAll(this.players[i].cardsInHands); | |
} | |
this.deck.addAll(this.activeCards); | |
this.deck.addAll(this.beat); | |
} | |
deal() | |
{ | |
var maxCards = 6; | |
for (var i in this.players) { | |
var player = this.players[i]; | |
var needCards = (maxCards - player.cardsInHands.count()); | |
if (needCards > 0) { | |
player.takeAllCards(this.deck.takeLast(needCards)); | |
} | |
} | |
} | |
setTrump() | |
{ | |
var trumpCard = this.deck.takeLastCard(); | |
this.deck.addToStart(trumpCard); | |
this.trump = trumpCard.type; | |
} | |
setActivePlayers() | |
{ | |
this.attackerIndex = Math.floor(Math.random() * this.players.length); | |
this.attacker = this.players[this.attackerIndex]; | |
this.defenderIndex = (this.attackerIndex + 1) % this.players.length; | |
this.defender = this.players[this.defenderIndex]; | |
} | |
// rules | |
continueGameLoop() | |
{ | |
while (!this.gameOver()) { | |
while (true) { | |
var card, endStep; | |
if (this.attacker.isComputer) { | |
card = this.attacker.getCardForAttack(this); | |
endStep = this.makeStepByAttacker(card); | |
if (endStep) break; | |
} else { | |
// wait for makeStepByUser() | |
return; | |
} | |
if (this.defender.isComputer) { | |
card = this.defender.getCardForDefence(this); | |
endStep = this.makeStepByDefender(card); | |
if (endStep) break; | |
} else { | |
// wait for makeStepByUser() | |
return; | |
} | |
} | |
} | |
this.updateWinners(); | |
} | |
// external callback | |
makeStepByUser(player, card) | |
{ | |
if (this.gameOver()) { | |
this.returnCardToPlayer(player, card); | |
return; | |
} | |
if (player == this.attacker) { | |
this.continueGameLoop_userAttacker(card); | |
} else if (player == this.defender) { | |
this.continueGameLoop_userDefender(card); | |
} | |
} | |
continueGameLoop_userAttacker(card) | |
{ | |
var endStep = this.makeStepByAttacker(card); | |
if (!endStep) { | |
card = this.defender.getCardForDefence(this); | |
this.makeStepByDefender(card); | |
} | |
this.continueGameLoop(); | |
} | |
continueGameLoop_userDefender(card) | |
{ | |
var endStep = this.makeStepByDefender(card); | |
this.continueGameLoop(); | |
} | |
makeStepByAttacker(card) | |
{ | |
var endStepDecision = (!card); | |
if (endStepDecision) { | |
if (this.activeCards.count() == 0) return false; | |
this.endStep(); | |
return true; | |
} else { | |
if (this.defender.cardsInHands.count() > 0 && this.isAttackStepAllowed(card)) { | |
this.makeStep(card); | |
} else { | |
this.returnCardToPlayer(this.attacker, card); | |
} | |
} | |
return false; | |
} | |
endStep() | |
{ | |
this.moveAciveCardsToBeat(); | |
this.updateWinners(); | |
this.deal(); | |
this.swapPlayers(); | |
} | |
makeStepByDefender(card) | |
{ | |
var takeCardsDecision = (!card); | |
if (takeCardsDecision) { | |
if (this.activeCards.count() == 0) return false; | |
this.defender.takeAllCards(this.activeCards); | |
this.updateWinners(); | |
this.deal(); | |
return true; | |
} else { | |
if (this.isDefenceStepAllowed(card)) { | |
this.makeStep(card); | |
} else { | |
this.returnCardToPlayer(this.defender, card); | |
} | |
} | |
if (this.defender.cardsInHands.count() == 0) { | |
this.endStep(); | |
return true; | |
} | |
if (this.attacker.cardsInHands.count() == 0) { | |
this.endStep(); | |
return true; | |
} | |
return false; | |
} | |
isAttackStepAllowed(card) | |
{ | |
if (!card) return false; | |
if (this.activeCards.count() == 0) return true; | |
var i = -1, currentCard; | |
while (currentCard = this.activeCards.readAt(++i)) { | |
if (card.value == currentCard.value) { | |
return true; | |
} | |
} | |
return false; | |
} | |
isDefenceStepAllowed(card) | |
{ | |
if (!card) return false; | |
if (this.activeCards.count() == 0) return false; | |
if (this.activeCards.count() % 2 == 0) return false; | |
var lastCard = this.activeCards.readAt(this.activeCards.count() - 1); | |
if ( | |
(card.type == lastCard.type && card.value > lastCard.value) || | |
(card.type == this.trump && card.type != lastCard.type) | |
) { | |
return true; | |
} | |
return false; | |
} | |
makeStep(card) | |
{ | |
this.activeCards.add(card); | |
} | |
moveAciveCardsToBeat() | |
{ | |
this.beat.addAll(this.activeCards); | |
} | |
returnCardToPlayer(player, card) | |
{ | |
player.takeCard(card); | |
} | |
updateWinners() | |
{ | |
if (this.deck.count() == 0 && this.activeCards.count() == 0) { | |
var i = 0; | |
while (i < this.players.length) { | |
if (this.players[i].cardsInHands.count() == 0) { | |
this.players.splice(i, 1); | |
} else { | |
i++; | |
} | |
} | |
} | |
} | |
swapPlayers() | |
{ | |
this.attackerIndex = (this.attackerIndex + 1) % this.players.length; | |
this.defenderIndex = (this.defenderIndex + 1) % this.players.length; | |
this.attacker = this.players[this.attackerIndex]; | |
this.defender = this.players[this.defenderIndex]; | |
} | |
gameOver() | |
{ | |
return (this.deck.count() == 0 && this.activeCards.count() == 0 && this.players.length <= 1); | |
} | |
} | |
class GameInterface | |
{ | |
constructor(gameState) | |
{ | |
this.gameState = gameState; | |
this.render(); | |
var self = this; | |
$('#start').click(function() { | |
self.startGame(); | |
}); | |
$('#endStep').click(function() { | |
self.endStep(); | |
}); | |
} | |
render() | |
{ | |
$('#userPlayer').html(''); | |
$('#computerPlayer').html(''); | |
$('#activeCards').html(''); | |
if (this.gameState.players.length > 0) { | |
var player = this.gameState.players[0]; | |
var isUser = !player.isComputer; | |
if (isUser) { | |
this.renderCardsOpen(player.cardsInHands, $('#userPlayer')); | |
} else { | |
this.renderCardsClosed(player.cardsInHands, $('#computerPlayer')); | |
} | |
} | |
this.renderCardsOpen(this.gameState.activeCards, $('#activeCards'), true); | |
if (this.gameState.players.length > 1) { | |
var player = this.gameState.players[1]; | |
var isUser = !player.isComputer; | |
if (isUser || $('#computerPlayer').html() != '') { | |
this.renderCardsOpen(player.cardsInHands, $('#userPlayer')); | |
} else { | |
this.renderCardsClosed(player.cardsInHands, $('#computerPlayer')); | |
} | |
} | |
var html = '' + this.gameState.deck.count(); | |
if (this.gameState.trump) { | |
var cls = this.gameState.trump; | |
html += ' (<span class="'+cls+'">' + cardTypes[this.gameState.trump] + '</span>)'; | |
} | |
if (this.gameState.beat.count()) { | |
html += ' - ' + this.gameState.beat.count(); | |
} | |
if (this.gameState.gameOver()) { | |
if (this.gameState.players.length == 0) { | |
html += ' | Draw'; | |
} else { | |
for (var i in this.gameState.players) { | |
var player = this.gameState.players[i]; | |
if (player.cardsInHands.count() > 0) { | |
if (player.isComputer) { | |
html += ' | Winner - User'; | |
} else { | |
html += ' | Winner - Computer'; | |
} | |
break; | |
} | |
} | |
} | |
} | |
$('#deck').html(html); | |
} | |
renderCardsOpen(cards, $container, useOver) | |
{ | |
if (!cards) return; | |
var i = -1, card; | |
while (card = cards.readAt(++i)) { | |
var $el = $('<div></div>').attr('index', i) | |
.addClass('card active').addClass(card.type) | |
.html(cardValues['' + card.value] + ' ' + cardTypes[card.type]); | |
if (useOver && i % 2 == 1) { | |
$el.addClass('over'); | |
} | |
$container.append($el); | |
} | |
} | |
renderCardsClosed(cards, $container) | |
{ | |
if (!cards) return; | |
var i = -1, card; | |
while (card = cards.readAt(++i)) { | |
var $el = $('<div></div>') | |
.addClass('card closed') | |
.html(''); | |
$container.append($el); | |
} | |
} | |
bindCardHandlers() | |
{ | |
var self = this; | |
$('.card.active').click(function() { | |
var index = $(this).attr('index'); | |
self.makeStep(index); | |
}); | |
} | |
startGame() | |
{ | |
// first - user, second - computer | |
var players = [new Player(true), new Player(false)]; | |
window.oldGameState = JSON.parse(JSON.stringify(this.gameState)); | |
this.gameState.startGame(players); | |
this.render(); | |
this.bindCardHandlers(); | |
} | |
getUserPlayer() | |
{ | |
if (!this.gameState.players[0].isComputer) return this.gameState.players[0]; | |
if (!this.gameState.players[1].isComputer) return this.gameState.players[1]; | |
return null; | |
} | |
makeStep(index) | |
{ | |
if (this.gameState.players.length < 0 || this.gameState.gameOver()) return; | |
var player = this.getUserPlayer(); | |
if (!player) return; | |
window.oldGameState = JSON.parse(JSON.stringify(this.gameState)); | |
var card = player.cardsInHands.readAt(index); | |
if (card) { | |
if (! ( | |
(player == this.gameState.attacker && this.gameState.isAttackStepAllowed(card)) | |
|| | |
(player == this.gameState.defender && this.gameState.isDefenceStepAllowed(card)) | |
)) return; | |
} | |
card = player.cardsInHands.takeAt(index); | |
this.gameState.makeStepByUser(player, card); | |
this.render(); | |
this.bindCardHandlers(); | |
} | |
endStep() | |
{ | |
if (this.gameState.players.length == 0 || this.gameState.gameOver()) return; | |
var player = this.getUserPlayer(); | |
if (!player) return; | |
window.oldGameState = JSON.parse(JSON.stringify(this.gameState)); | |
this.gameState.makeStepByUser(player, null); | |
this.render(); | |
this.bindCardHandlers(); | |
} | |
} | |
var gameState = new GameState(); | |
var gameInterface = new GameInterface(gameState); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment