1

Ive been having an issue with getting a random int from a function after going through a while loop. The purpose of the function is to shuffle a deck:

def shuffling(maindeck, shuffle_steps):
random.seed()
# sets a number of steps and creates a new array to be returned
steps = 0
shuffler = maindeck

while steps < shuffle_steps:
    firstR = random.randrange(len(maindeck) - 1)
    secondR = random.randrange(len(maindeck) - 1)

    shuffler[firstR], shuffler[secondR] = shuffler[secondR], shuffler[firstR]

    steps +=1

return shuffler

and this is the code that uses the function:

from deck import *
from shuffle import shuffling
gameState = True


while gameState:
    input("Welcome to War! Press any key to continue... \n")

    game_deck = shuffling(total_deck, 500)

    while gameState and len(game_deck) > 1:
        print("Both players draw a card...")

        playerCard = game_deck.pop(0)
        opponentCard = game_deck.pop(0)

        # some code

        keep_playing = input("Play again? (y/n) \n")
        if keep_playing is not ('y' or 'Y'):
           gameState = False

gameState = False

if len(game_deck) < 2:
    print("No cards left!")
    keepPlaying = input("Play again? (y/n) \n")
    if keepPlaying is ('y' or 'Y'):
        gameState = True

where total_deck is an array from a file deck.py

This code works fine over the first iteration of the while loop, but when the loop iterates I get the error:

 ValueError: empty range for randrange()

And that the error occurs when

random.randrange(len(Maindeck) - 1) 

is called, since

len(Maindeck) - 1 

now evaluates to a number equal to or lower than 0? Why is this?

frank
  • 113
  • 2
  • 1
    Your shuffle algorithm isn't a uniform shuffle. Anyways, that aside, you can use `random.shuffle(maindeck)` instead of your own shuffling function. – Silver Nov 04 '18 at 08:42
  • Have you noticed that the last card of the deck never gets shuffled? That's because `random.randrange(n)` never returns `n` (even says so in the docs). So you should drop the `-1` in `random.randrange(len(maindeck)-1)`. – digitalarbeiter Nov 04 '18 at 09:24
  • @SilverSlash is right about the [non-uniform shuffle](https://cs.stackexchange.com/a/47342) ([maths details](https://stats.stackexchange.com/a/3087)). Also +1 for pointing to the standard library. – digitalarbeiter Nov 04 '18 at 09:29

2 Answers2

1

Options:

  • If you have a set of cards in a list and want to rearrage the whole sequence, simply use random.shuffle(sequence) for an inplace shuffling.

  • If you want to get a shuffled copy of it, use random.sample() and set the length to the length of the sequence.


This line:

shuffler = maindeck

presuming maindeck is somekind of mutable list data structure, shuffler references the same data that maindeck does - you are shuffling your maindeck (in disguise) and return it - and further down you are maniputaling it. Youcould use random.sample() instead wich returns a shuffled copy of your maindeck.

random.seed() sets the starting-internal state of the Mersenne_Twister that generates your randomness - set it once if you need but not every time you shuffle through your deck. Seeding your random with a fixed value will lead to the same random numbers given u use the same random operations as each random operation changes the internal state - using it without any seed is not needed, its done by default in the sources: see Python's random: What happens if I don't use seed(someValue)?

Your code should use random.sample(maindeck,k=len(maindeck)).


Code:

import random
random.seed(42) # generate same states if same operations are used 

# https://en.wikipedia.org/wiki/Standard_52-card_deck
ranks = ["ace"] + [str(c) for c in range(2,11)] + ["jack", "queen", "king"]
colors = ["clubs","diamonds","hearts","spades"]

def generate_52_card_deck():
    """Returns a new deck of cards. Each card is a tuple of (rank,color)."""
    return [ (r,c) for c in colors for r in ranks  ]

deck = generate_52_card_deck()    
print(deck)

# inplace
random.shuffle(deck)
print(deck)

new_deck = random.sample(deck,k=52)
print("sampled deck:  ", new_deck[:10])
print("original deck: ", deck[::5])

Output:

# after generation (shortened)
[('ace', 'clubs'), ('2', 'clubs'), ('3', 'clubs'), ('4', 'clubs'), 
 ('5', 'clubs'), ('6', 'clubs'), ('7', 'clubs'), ('8', 'clubs'), 
 ('9', 'clubs'), ('10', 'clubs'), ('jack', 'clubs'), ('queen', 'clubs'),
 ('king', 'clubs'), 
 ('ace', 'diamonds'), ('2', 'diamonds'), ('3', 'diamonds'), ('4', 'diamonds'), 
 ('5', 'diamonds'), ('6', 'diamonds'), ('7', 'diamonds'),  ... , 
 ('jack', 'spades'), ('queen', 'spades'), ('king', 'spades')]


# after shuffling once (shortened)
[('10', 'clubs'), ('jack', 'diamonds'), ('king', 'diamonds'), ('4', 'clubs'),
 ('9', 'diamonds'), ('king', 'hearts'), ('4', 'diamonds'), ('ace', 'spades'), 
 ('7', 'diamonds'), ('queen', 'clubs'), ('8', 'spades'), 
 ('queen', 'diamonds'), ('8', 'hearts'), ('4', 'hearts'), ..., 
 ('9', 'spades'), ('2', 'clubs'), ('8', 'clubs'), ('2', 'spades')]

# first 10 cards ...
sampled deck:   [('4', 'spades'), ('king', 'hearts'), ('ace', 'diamonds'), 
                 ('jack', 'clubs'), ('queen', 'hearts'), ('2', 'hearts'),
                 ('6', 'diamonds'), ('3', 'spades'), ('8', 'hearts'), 
                 ('9', 'diamonds')]

# first 10 cards
original deck:  [('10', 'clubs'), ('jack', 'diamonds'), ('king', 'diamonds'), 
                 ('4', 'clubs'), ('9', 'diamonds'), ('king', 'hearts'), 
                 ('4', 'diamonds'), ('ace', 'spades'), ('7', 'diamonds'), 
                 ('queen', 'clubs')]

If you need the cards value, use:

def get_base_card_value(c):
    # ace == 11 not done here
    v = {"ace":1 ,"jack":10, "queen":10, "king":10}
    return v.get(c[0]) or int(c[0])
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • `random.seed()` without an argument uses the system time as a seed, which should be sufficient for this task (unless @frank resets the system clock before each game :-) ). – digitalarbeiter Nov 04 '18 at 09:32
  • @digitalarbeiter and it serves no purpose whatsoever because using any random-funtion without seeding it, also takes the system time to give it a random internal state - so you can skip it ... – Patrick Artner Nov 04 '18 at 09:34
  • @digitalarbeiter see [pythons-random-what-happens-if-i-dont-use-seedsomevalue](https://stackoverflow.com/questions/817705/pythons-random-what-happens-if-i-dont-use-seedsomevalue) – Patrick Artner Nov 04 '18 at 09:37
  • 1
    Indeed. I brought it up because your initial answer seemed to imply the way frank seeds his shuffle was problematic (rather than just redundant). – digitalarbeiter Nov 04 '18 at 09:46
1

Ah! I see the problem! Your Game "uses up" the main deck because the shuffling() function permutes and returns the original deck:

shuffler = maindeck

does not create a copy of the main deck. Thus

game_deck.pop(0)

takes its cards from the main deck, too.

Fix: make a deep copy of the main deck and use that (in shuffling()):

import copy
...
shuffler = copy.deepcopy(maindeck)

Et voila! a fresh game deck for every game.

digitalarbeiter
  • 2,295
  • 14
  • 16