2

I am very new to javascript and I have a little problem.

As my first "project", I want my program to give me a random poker card. Nothing to great, but I'm trying to figure out an elegant way to do it. By now my only idea is to give a random number between 1 and 52 a specific card, but there got to be a better way.

Here is my current code :

function newCard() {
var card_id = document.getElementById("card_id");
var c1 = Math.floor(Math.random() * 52) + 1;
switch(c1) {
    case 1:
        c1 = "ace of spades";
        break;
    case 2:
        c1 = "2 of spades";
        break;
    case 3:
        c1 = "3 of spades";
        break;  


    // ...I think you get the idea here


         } 

card_id.innerHTML = c1;
}

Do you have a hint for me, how to make this quicker/better ?

Robin
  • 111
  • 1
  • 7
  • You should use a few arrays, one for suits, one for 1-10jqk, and one for the deck... See https://stackoverflow.com/questions/26248001/creating-playing- – Stuart Sep 16 '17 at 22:31
  • Check whether, below link can help you. https://stackoverflow.com/questions/32769010/generate-a-deck-of-cards-in-javascript – msg Sep 16 '17 at 22:34

4 Answers4

7

function newCard() {
  // Pick from 0 to 51, not 1 to 52
  var cardId = Math.floor(Math.random() * 52);
  
  var ranks = ["ace", "2", "3", "4", "5", "6", "7", "8", "9", "ten", "jack", "queen", "king"];
  var suits = ["spades", "hearts", "diamonds", "clubs"];
 
  // % is the modulo operator, so 0 => 0, 1 => 1, ... 12 => 12, 13 => 0, 14 => 1, ...
  // Math.floor(cardId / 13) gets the suit (0, 1, 2, 3)
  return ranks[cardId % 13] + " of " + suits[Math.floor(cardId / 13)];
}

for (var i = 0; i < 10; i++) {
  console.log(newCard());
}

UPDATE

Because this has led to a bit of discussion around dealing from a non-infinite deck (not repeating cards), here's a typical solution that produces a full deck, shuffles it, and then deals out the cards. cardIdToEnglish is basically the above solution. shuffle is a Fisher-Yates shuffle. Note that using pop deals from the end of the deck, which may seem odd, but also note that this is irrelevant. :-)

function cardIdToEnglish(id) {
    var ranks = ["ace", "2", "3", "4", "5", "6", "7", "8", "9", "ten", "jack", "queen", "king"];
    var suits = ["clubs", "diamonds", "hearts", "spades"];

    return ranks[id % 13] + " of " + suits[Math.floor(id / 13)];
}

function shuffle(arr) {
    // Fisher-Yates shuffle
    var n = arr.length;

    for (var i = 0; i < arr.length - 2; i++) {
        var j = Math.floor(Math.random() * (n - i)) + i;

        // Swap a[i] and a[j]
        var temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

function newDeck() {
    var deck = [];
    for (var i = 0; i < 52; i++) {
        deck.push(i);
    }

    shuffle(deck);
    return deck;
}

var deck = newDeck();
for (var i = 0; i < 52; i++) {
    var card = deck.pop();
    console.log(cardIdToEnglish(card));
}
user94559
  • 59,196
  • 6
  • 103
  • 103
  • Thank you very much! That is really a nice way :D – Robin Sep 16 '17 at 22:37
  • whilst I like this - I wonder if there should be a mechanism to prevent the same selected card being chosen - it is not impossible that two subsequent draws select the same card due to the same random number. So selecting the 2 of diamonds should remove that option from subsequent draws in the one hand - it would be as simple as creating an array of selected card objects (number and suit) and checking against that after selection but before displaying the selection. Then if the card exists in the array - redraw the random number etc. – gavgrif Sep 16 '17 at 22:43
  • For games that use a non-infinite deck of cards, the typical solution is to shuffle the deck up front and then draw cards from the shuffled deck. – user94559 Sep 16 '17 at 22:44
  • @gavgrif Check the answer below. – jack Sep 16 '17 at 22:49
  • Thanks. So if I get it right, with this code the deck is set in a pre shuffled position, before the first card is "drawn", and then gives you the second, third, fourth, etc. card wich is following in the order. Is that correct like this? – Robin Sep 16 '17 at 23:14
1

I would create an array of all the cards as objects of the suits and ranks (using nested forEach loops for ease - iterating over the suits and ranks arrays) and then use the random number to generate a card id. This would then give the rank and suit of that card. Then i remove the card from the deck - to prevent it from being dealt twice and decrement the count of available cards). Doing it this way means that the selected card is random and not duplicated. I also put in a button that resets it all and allows further dealing of the 10 card hand.

var deck = [];

function setCards(){
  var suits = ["Spades", "Hearts", "Diamonds", "Clubs"];
  var ranks = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "Ten", "Jack", "Queen", "King"];
  
  
  //creates an array of card objects that have the rank and suit as properties
 //eg: deck = [ {suit: "Spades", rank: "Ace"}, {suit: "Spades", rank: "2"},...etc] 
  suits.forEach(function(suit){
     ranks.forEach(function(rank){
      var card = {};
      card.suit = suit;
      card.rank = rank;
      deck.push(card);
     });
      
    });
}

function newCard(count) {
  //decreases the count of the deck since some cards have already been dealt
  var remainingCards = 52 - count;
  var index = Math.floor(Math.random() * remainingCards);
  var card= deck[index];
  deck.splice(index,1); //removes the selected card from the deck
  return card;
 }

function dealCards() {
  document.getElementById('hand').innerHTML = '<dt>My Hand </dt>';
  setCards();
  
  for (var i = 0; i < 10; i++) {
  var card = newCard(i);
  document.getElementById('hand').innerHTML += '<dd>'+ card.rank +  ' of '  + card.suit + '</dd>';
  }
}
<button type="button" onclick="dealCards()">Deal New Hand</button>
<hr/>


<dt id = "hand"></dt>
gavgrif
  • 15,194
  • 2
  • 25
  • 27
  • Hi, thank you too for your code :). Could you explain how exactly the code remembers the specific card which is taken out? I can't really see through this – Robin Sep 16 '17 at 23:38
  • `deck.splice(index,1);` removes the card from the `deck`. So rather than remembering which cards have been removed, the code is remembering which cards remain. – user94559 Sep 16 '17 at 23:41
  • @gavgrif Why don't you use `deck.length` rather than passing in a `count`? – user94559 Sep 16 '17 at 23:42
  • Hi Robin - the selected card is removed from the deck (using .splice() so that it is no longer available) and because the index of the selection is passed to the newCard() function, the number of cards (the length of the deck array) is reduced by the index number - so for example - the newCard(3) will allow the random number to be the initial length - 3 os the length of hte array always keeps pace with the size of the deck array. – gavgrif Sep 16 '17 at 23:43
  • @smarx - this is because deck.length on each iteration would force the browser to calculate the length of the array on each iteration - this is not as performant as giving it an explicit length using the above method. In all cases - it is preferable to not force the dynamic determination of the length of an array - each item of the array has to be inspected to see if its the last one - far better to say here is the length. In normal cases - you would calculate it once and cache the results - but in this case the reducing length prohibits that approach. – gavgrif Sep 16 '17 at 23:45
  • @gavgrif I don't know much about the popular JavaScript implementations, but I suspect getting the length of an array is constant time. Similarly, I believe the `splice` is fairly expensive, since it has to "move" all the elements after the one that was removed. – user94559 Sep 16 '17 at 23:49
  • @gavgrif See https://stackoverflow.com/questions/6501160/why-is-pop-faster-than-shift for why the `splice` is slow. (It's the same reason why I used `pop` rather than `shift`.) **Not** that I think we need to be optimizing for performance here. I just think that if you *were* optimizing for performance, `deck.length` is fine, but you should avoid the `splice`. – user94559 Sep 16 '17 at 23:52
  • I can't really contribute to your conversation here, but I got to thank both of you very much. Although I don't get the code to 100%, I will sit down tomorrow and try to get every part of your codes. You really helped me :) – Robin Sep 16 '17 at 23:55
0

Here's an idea:

var deck = ['ace of spades', 'two of spades', 'three of spades', '...']; 
// and so on, all 52 cards
var rand = Math.floor(Math.random() * 52);

console.log(deck[rand]); // will print a random card from the deck

Optionally, you could also remove the drawn card from the deck, if you'd like to continue "the game".

deck.splice(rand, 1);

After this, you would need to regenerate the random for a smaller deck:

rand = Math.floor(Math.random() * deck.length); 
jack
  • 1,391
  • 6
  • 21
0

That's an interesting problem. Here is what I'm thinking... Number cards go from 2 to 9. If we continue to use integers as card values, jack, queen, king, and ace could be represented as 10, 11, 12, and 13, respectively.

But of course, we have four of each of those values in the form of the four suits. A quick google search reveals that in English speaking countries the relative value of the suits could be ranked from lowest to highest as clubs, diamonds, hearts, and spades.Therefore, we could say that 2 - 13 are the clubs, 14 - 25 the diamonds, 26 - 37 the hearts, and 28 - 49 the spades. Following this formula, your random number should be between 2 and 49.

So with this in mind, you could write a function that does some math to determine the exact card based on the generated number. Obviously, you cannot deal the same card twice, so once a number is generated, it should be stored in an array of dealt cards. Each time you generate a number you will want to check to see if that number is in the dealt cards array. If it is, you will want to generate another number. I have an idea for an approach for setting up and checking this array. If you think my idea is worth pursuing, let me know and I'll add it to this answer.

Neil Girardi
  • 4,533
  • 1
  • 28
  • 45
  • Hey, if it wouldn't be to much effort, I'd love to see this! – Robin Sep 16 '17 at 23:04
  • Take a look at the edit I made to my solution which shows a more typical way of dealing with a deck of cards. (Shuffle it up front.) "Draw a card, and if it's a duplicate, draw another one" is inefficient... drawing a new card takes longer and longer as the deck is depleted, and theoretically, it isn't guaranteed that you'll ever succeed! It's better to keep track of the remaining available cards and always draw from that pool. A Fisher-Yates shuffle essentially does this. – user94559 Sep 16 '17 at 23:07
  • Nice! @Robin, I would go with smarx's answer. I'm going to up-vote it. – Neil Girardi Sep 16 '17 at 23:10