frank
frank

Reputation: 113

Empty range for randrange() over a while loop in python

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?

Upvotes: 1

Views: 525

Answers (2)

Patrick Artner
Patrick Artner

Reputation: 51643

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])

Upvotes: 1

digitalarbeiter
digitalarbeiter

Reputation: 2335

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.

Upvotes: 1

Related Questions