Hi all,

I know there have been several threads about getting card images into a GUI, but I'm flustered trying to understand why some things work and some things don't. Here's the code:

import random
from Tkinter import *
import Image, ImageTk

class Card(object):
    suits = ["C","D","H","S"]
    values = ['A','2','3','4','5','6','7','8','9','T','J','Q','K']
    down_img = PhotoImage(file="Deck1.gif")

    cards = [x+y for x in suits for y in values]

    pics = {}
    for x in cards:
        pics[x] = PhotoImage(file=x+'.gif')
        

    def __init__(self, suit=None, value=None, up=True):

        if suit in Card.suits:
            self.suit = suit
        else:
            self.suit = random.choice(Card.suits)

        if value in Card.values:
            self.value = value
        else:
            self.value = random.choice(Card.values)
        self.up = up

    def __str__(self):
        return self.value+self.suit

    def picURL(self):
        return Card.pics[self.suit+self.value]

    def display(self, x,y,angle,canvas):
        if self.up:
            canvas.create_image(x,y,Card.pics[self.suit+self.value])
        else:
            canvas.create_image(x,y,Card.down_image)


class Deck(Card):

    deck = Card.cards[:]

    def __init__(self, suit=None, value=None, up=True):
        card = str(suit)+str(value)
        if card not in Deck.deck:
            if Deck.deck == []:
                raise ValueError, "Deck empty!"
            card = random.choice(Deck.deck)
            suit=card[0]
            value=card[1:]
        Deck.deck.remove(card)
        self.suit = suit
        self.value = value
        self.up = up

    @staticmethod
    def shuffle():
        Deck.deck = Card.cards

    
    

class BridgeRound(Frame):

    # display_data contains display info for each hand:
    #(startx, starty, dx, dy, angle of rotation)
    display_data = [(30,0,10,0,0), \
                    (200, 30, 0, 10, 270), \
                    (170, 200, -10, 0, 180), \
                    (0, 170, 0, -10, 90)]

    def __init__(self, master):
        Frame.__init__(self,master)
        self.canvas = Canvas(self)
        self.canvas.grid()
        self.grid()
        self.deal()
        self.display()

    def deal(self):
        Deck.shuffle()
        self.hands = [[Deck() for i in range(13)] for j in range(4)]

    def display(self):
        for i in range(4):
            hand = self.hands[i]
            x, y, dx, dy, angle = BridgeRound.display_data[i]
            for j in hand:
                print j,
                j.display(self.canvas,x,y,angle)
                x += dx
                y += dy

mainw = Tk()
mainw.f = BridgeRound(mainw)
mainw.mainloop()

You may ask, why does he create the images in the Card class? Isn't that wasteful of memory? Well, yes. BUT...for some reason, if I don't maintain a static reference to the card images, they don't display. Thus, a line like

canvas.create_image(PhotoImage(file="H6.gif"))

results in a blank image. So if I want to display all the cards at once, then I have to maintain them all in a data structure.

Anyways, when I run the code, I get

>>>

Traceback (most recent call last):
File "C:\Python24\src\cards\cards.py", line 5, in -toplevel-
class Card(object):
File "C:\Python24\src\cards\cards.py", line 14, in Card
pics[x] = PhotoImage(file=x+'.gif')
File "C:\Python24\lib\lib-tk\Tkinter.py", line 3203, in __init__
Image.__init__(self, 'photo', name, cnf, master, **kw)
File "C:\Python24\lib\lib-tk\Tkinter.py", line 3144, in __init__
raise RuntimeError, 'Too early to create image'
RuntimeError: Too early to create image

to which I say ?!@#*^$#^?!

So when's the best time to create an image?! I'm sure I can tweak the code to make it run ... but what's the underlying theory about Tkinter images here?

Thanks,
Jeff

Recommended Answers

All 4 Replies

Fixed. Uff. Here's the problem: the Card class code runs first, and the images were attempting to be created before Tk() is ever initialized. Apparently, one can't create images until a Tk is created. OK.

Next problem: how does one rotate the card images so that they all display inwards?

Here's the code, with some bugs fixed:

import random
from Tkinter import *
import Image, ImageTk

class Card(object):
    suits = ["C","D","H","S"]
    values = ['A','2','3','4','5','6','7','8','9','T','J','Q','K']
    down_img = None

    cards = [x+y for x in suits for y in values]

    pics = {}
    
    def __init__(self, suit=None, value=None, up=True):

        if suit in Card.suits:
            self.suit = suit
        else:
            self.suit = random.choice(Card.suits)

        if value in Card.values:
            self.value = value
        else:
            self.value = random.choice(Card.values)
        self.up = up

    def __str__(self):
        return self.value+self.suit

    def picURL(self):
        return Card.pics[self.suit+self.value]

    def display(self, canvas, x,y,angle):
        if self.up:
            image=Card.pics[self.suit+self.value]
        else:
            image=Card.down_image
        #image.rotate(angle)
        canvas.create_image(x,y,image=image)

    @staticmethod
    def create_images():
        for x in Card.cards:
            Card.pics[x] = PhotoImage(file=x+'.gif')
        Card.down_img = PhotoImage(file="Deck1.gif")
        

class Deck(Card):

    deck = Card.cards[:]

    def __init__(self, suit=None, value=None, up=True):
        card = str(suit)+str(value)
        if card not in Deck.deck:
            if Deck.deck == []:
                raise ValueError, "Deck empty!"
            card = random.choice(Deck.deck)
            suit=card[0]
            value=card[1:]
        Deck.deck.remove(card)
        self.suit = suit
        self.value = value
        self.up = up

    @staticmethod
    def shuffle():
        Deck.deck = Card.cards[:]


        

    
    

class BridgeRound(Frame):

    # display_data contains display info for each hand:
    #(startx, starty, dx, dy, angle of rotation)
    display_data = [(120, 50,12,0,0), \
                    (340, 30, 0, 15, 270), \
                    (260, 220, -12, 0, 180), \
                    (40, 220, 0, -15, 90)]

    def __init__(self, master):
        Frame.__init__(self,master)
        self.canvas = Canvas(self)
        self.canvas.grid()
        self.grid()
        self.deal()
        self.display()

    def deal(self):
        Deck.shuffle()
        self.hands = [[Deck() for i in range(13)] for j in range(4)]

    def display(self):
        Card.create_images()
        for i in range(4):
            hand = self.hands[i]
            x, y, dx, dy, angle = BridgeRound.display_data[i]
            for j in hand:
                #print j,
                j.display(self.canvas,x,y,angle)
                x += dx
                y += dy

mainw = Tk()
mainw.f = BridgeRound(mainw)
mainw.mainloop()

Jeff

Jeff, I trust you have taken a look at:
http://www.daniweb.com/techtalkforums/post257347-78.html
I had a similar problem there and solved it. Other articles mention that sometimes you have to protect Tkinter stored images from garbage collection. That may be with earlier versions of Python.

To rotate an image you best use the Python Image Library (PIL), it works very well with the Tkinter GUI.

Interesting project you have there! Good luck!

OK, this is progressing in increments. Why doesn't the canvas resize? As it is, the cards go off the right edge, regardless of the order in which I grid the canvas and its containing frame. Tkinter is sooo weird.

import random
from Tkinter import *
import Image, ImageTk

class Card(object):
    suits = ["C","D","H","S"]
    values = ['A','K','Q','J','T','9','8','7','6','5','4','3','2']
    down_img = None

    cards = [x+y for x in suits for y in values]

    pics = {}
    
    def __init__(self, suit=None, value=None, up=True):

        if suit in Card.suits:
            self.suit = suit
        else:
            self.suit = random.choice(Card.suits)

        if value in Card.values:
            self.value = value
        else:
            self.value = random.choice(Card.values)
        self.up = up

    def __str__(self):
        return self.value+self.suit

    def picURL(self):
        return Card.pics[self.suit+self.value]

    def display(self, canvas, x,y,angle):
        if self.up:
            image=Card.pics[self.suit+self.value]
        else:
            image=Card.down_image
        image=image.rotate(angle)
        self.image = ImageTk.PhotoImage(image)
        canvas.create_image(x,y,image=self.image)

    def key(self):
        # returns hash value of 20*suit + value -- guaranteed unique; sorts
        # by color.
        return 20*('SHCD'.find(self.suit))+Card.values.index(self.value)

    @staticmethod
    def create_images():
        for x in Card.cards:
            Card.pics[x] = Image.open(x+'.gif')
        Card.down_img = Image.open('Deck1.gif')
        

class Deck(Card):

    deck = Card.cards[:]

    def __init__(self, suit=None, value=None, up=True):
        card = str(suit)+str(value)
        if card not in Deck.deck:
            if Deck.deck == []:
                raise ValueError, "Deck empty!"
            card = random.choice(Deck.deck)
            suit=card[0]
            value=card[1:]
        Deck.deck.remove(card)
        self.suit = suit
        self.value = value
        self.up = up

    @staticmethod
    def shuffle():
        Deck.deck = Card.cards[:]

class BridgeRound(Frame):

    # display_data contains display info for each hand:
    #(startx, starty, dx, dy, angle of rotation)
    display_data = [(150, 50,12,0,0), \
                    (390, 40, 0, 15, 270), \
                    (290, 220, -12, 0, 180), \
                    (50, 220, 0, -15, 90)]

    def __init__(self, master):
        Frame.__init__(self,master)
        self.canvas = Canvas(self)

        #self.grid()
        self.deal()
        self.display()
        self.canvas.grid()
        self.grid()

    def deal(self):
        Deck.shuffle()
        self.hands = [[Deck() for i in range(13)] for j in range(4)]

    def display(self):
        Card.create_images()
        for i in range(4):
            hand = self.hands[i]
            hand.sort(key=lambda x:x.key())
            x, y, dx, dy, angle = BridgeRound.display_data[i]
            for j in hand:
                #print j,
                j.display(self.canvas,x,y,angle)
                x += dx
                y += dy

mainw = Tk()
mainw.f = BridgeRound(mainw)
mainw.mainloop()

Jeff,
the display looks pretty good! On my Sony multimedia machine using Windows XP the display looks very normal, nothing hanging off the edge. All I can think off is an update() thrown in.

Well, merry Christmas and a jolly Ho, Ho , Ho ...

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.