From the archive

Create a Classic Solitaire Game with Python and Tkinter: Step-by-Step Guide for Beginners

Review a Python and Tkinter prototype that loads, shuffles, deals, and drags playing-card images, with clear limits on the missing Solitaire rules.

Building a card-table interface is a useful way to practice Python classes, Tkinter canvases, image handling, randomization, and pointer events. The archived version of this tutorial introduced those ideas but did not contain a complete Solitaire implementation.

Scope correction: The reviewed code below is a visual tableau prototype, not a complete or rules-valid game of Solitaire. It can load card images, shuffle a deck, display seven columns, and let you drag individual cards. It does not implement legal moves, face-down cards, a stock or waste pile, foundations, stacked-card movement, game-state updates after dragging, or a win condition.

What You Need

This example uses only Python’s standard library, but Tkinter is an optional module in some Python distributions. Run the following command to confirm that it is installed and to see the Tcl/Tk version in use:

python -m tkinter

If your system uses python3, use that command instead. Installation details vary by distributor; the current official Tkinter documentation explains how to verify the module.

The WordPress export did not contain the card artwork required by the original code. Before this prototype can run, create a cards directory beside the Python file and provide 52 PNG images with names such as:

cards/A_of_hearts.png
cards/2_of_hearts.png
cards/10_of_spades.png
cards/K_of_clubs.png

Every combination of rank (A, 2 through 10, J, Q, K) and suit (hearts, diamonds, clubs, spades) must exist. Use artwork that you have permission to use. Tkinter will raise an error if a referenced file is missing or unreadable.

Import the Required Modules

The prototype needs Tkinter for the window and canvas, random to shuffle the deck, and pathlib to locate the image directory relative to the script:

import random
import tkinter as tk
from pathlib import Path

Reviewed Tableau Prototype

The archived draft split its window, dealing, and dragging logic across separate snippets. The reviewed version combines those original pieces and adds the missing card canvas tag required by the event bindings. It remains intentionally limited to the behavior described above.

import random
import tkinter as tk
from pathlib import Path


SUITS = ("hearts", "diamonds", "clubs", "spades")
RANKS = ("A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K")
CARD_DIRECTORY = Path(__file__).with_name("cards")


class SolitaireTableauPrototype(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Solitaire Tableau Prototype")
        self.geometry("800x600")
        self.configure(bg="green")

        self.canvas = tk.Canvas(self, bg="green", width=800, height=600)
        self.canvas.pack()

        self.card_images = self.load_images()
        self.deck = self.create_deck()
        random.shuffle(self.deck)

        self.tableau = [[] for _ in range(7)]
        self.drag_data = {"item": None, "x": 0, "y": 0}

        self.deal_cards()
        self.setup_interactions()

    def load_images(self):
        images = {}

        for suit in SUITS:
            for rank in RANKS:
                card = f"{rank}_of_{suit}"
                filename = CARD_DIRECTORY / f"{card}.png"
                images[card] = tk.PhotoImage(file=str(filename))

        return images

    def create_deck(self):
        return [f"{rank}_of_{suit}" for suit in SUITS for rank in RANKS]

    def deal_cards(self):
        for column_index in range(7):
            for row_index in range(column_index + 1):
                card = self.deck.pop()
                self.tableau[column_index].append(card)

                x = 50 + column_index * 100
                y = 50 + row_index * 20
                self.canvas.create_image(
                    x,
                    y,
                    image=self.card_images[card],
                    anchor="nw",
                    tags=("card",),
                )

    def setup_interactions(self):
        self.canvas.tag_bind("card", "<ButtonPress-1>", self.on_card_press)
        self.canvas.tag_bind("card", "<B1-Motion>", self.on_card_drag)
        self.canvas.tag_bind("card", "<ButtonRelease-1>", self.on_card_release)

    def on_card_press(self, event):
        current_items = self.canvas.find_withtag("current")
        if not current_items:
            return

        self.drag_data["item"] = current_items[0]
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y
        self.canvas.tag_raise(current_items[0])

    def on_card_drag(self, event):
        item = self.drag_data["item"]
        if item is None:
            return

        delta_x = event.x - self.drag_data["x"]
        delta_y = event.y - self.drag_data["y"]
        self.canvas.move(item, delta_x, delta_y)
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y

    def on_card_release(self, _event):
        self.drag_data["item"] = None
        self.drag_data["x"] = 0
        self.drag_data["y"] = 0


if __name__ == "__main__":
    app = SolitaireTableauPrototype()
    app.mainloop()

Keeping PhotoImage objects in self.card_images is important because Tkinter stops displaying image data after the last Python reference to it is deleted. Each canvas image also receives the card tag so tag_bind() can direct pointer events to it.

What the Prototype Actually Does

When all 52 image files are available, the code:

  1. creates a Tkinter window and canvas;
  2. loads one image for every rank-and-suit combination;
  3. builds and shuffles a 52-card deck;
  4. deals 28 face-up images across seven columns; and
  5. allows one displayed image at a time to be dragged freely.

The remaining 24 cards stay in the in-memory deck and are not displayed.

What Is Still Missing

Turning this prototype into Classic Solitaire would require substantially more work:

  • a card model that tracks face-up and face-down state;
  • stock, waste, tableau, and foundation areas;
  • validation for alternating colors and descending tableau ranks;
  • foundation rules grouped by suit;
  • movement of valid card stacks rather than individual images;
  • synchronized game state after every accepted move;
  • turning newly exposed tableau cards face up;
  • dealing from the stock and recycling rules;
  • a new-game flow and win-condition check; and
  • keyboard and other accessibility considerations beyond pointer dragging.

Those pieces were not present in the archived source, so they have not been invented here. This article preserves the useful Tkinter board prototype while making its technical limits explicit.