Skip to content

Instantly share code, notes, and snippets.

@anjensan
Last active August 22, 2017 15:32
Show Gist options
  • Save anjensan/3463b76ecb24695d9360a88ed7651330 to your computer and use it in GitHub Desktop.
Save anjensan/3463b76ecb24695d9360a88ed7651330 to your computer and use it in GitHub Desktop.
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