Building a card-table prototype is a useful way to practice Python, Pygame drawing, image loading, rectangle collision checks, and mouse events. The archived draft introduced those ideas, but it did not contain a complete or rules-valid game of Solitaire.
Scope correction: The reviewed program below is a board prototype. It displays a seven-column tableau, shows a stock and waste area, draws cards from the stock, and lets you place a top tableau card into another column. It does not enforce legal moves, track face-up and face-down state, build foundations, move card stacks, recycle the stock, or detect a win.
Install Pygame Safely
Pygame is a third-party package, not part of Python’s standard library. A virtual environment keeps project packages separate from the rest of your system:
python -m venv .venv
Activate the environment using the command appropriate for your operating system, then install Pygame through that interpreter:
python -m pip install pygame
Refer to the current Python virtual-environment documentation and official Pygame installation guidance if your platform uses a different Python command or requires platform-specific setup.
Required Card Assets
The WordPress export did not contain the artwork required to run the example. Create a cards directory beside the Python file and supply 52 card images plus one card-back image:
cards/A_of_hearts.png
cards/2_of_hearts.png
cards/10_of_spades.png
cards/K_of_clubs.png
cards/back.png
Every combination of rank (A, 2 through 10, J, Q, K) and suit (hearts, diamonds, clubs, spades) must exist. Use only artwork you have permission to use. The program cannot run without these files.
Reviewed Board Prototype
The archived draft contained two separate game loops, loaded the card-back image during every frame, duplicated dealt cards in the stock, and tested mouse coordinates against rectangles that were never positioned. The reviewed version below makes the intended corrections while keeping the original limited behavior.
import random
from pathlib import Path
import pygame
SCREEN_WIDTH = 1024
SCREEN_HEIGHT = 768
CARD_WIDTH = 72
CARD_HEIGHT = 96
GREEN = (0, 128, 0)
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 Card:
def __init__(self, rank, suit, image):
self.rank = rank
self.suit = suit
self.image = image
self.rect = self.image.get_rect()
def draw(self, surface, x, y):
self.rect.topleft = (x, y)
surface.blit(self.image, self.rect)
def load_card_images():
images = {}
for suit in SUITS:
for rank in RANKS:
card_name = f"{rank}_of_{suit}"
path = CARD_DIRECTORY / f"{card_name}.png"
image = pygame.image.load(str(path)).convert_alpha()
images[card_name] = pygame.transform.scale(
image,
(CARD_WIDTH, CARD_HEIGHT),
)
return images
def create_deck():
return [f"{rank}_of_{suit}" for suit in SUITS for rank in RANKS]
def make_card(card_name, card_images):
rank, suit = card_name.split("_of_")
return Card(rank, suit, card_images[card_name])
def deal_tableau(deck, card_images):
tableau = [[] for _ in range(7)]
for column_index in range(7):
for _ in range(column_index + 1):
card_name = deck.pop()
tableau[column_index].append(make_card(card_name, card_images))
return tableau
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Solitaire Board Prototype")
clock = pygame.time.Clock()
card_images = load_card_images()
card_back_path = CARD_DIRECTORY / "back.png"
card_back = pygame.image.load(str(card_back_path)).convert_alpha()
card_back = pygame.transform.scale(card_back, (CARD_WIDTH, CARD_HEIGHT))
deck = create_deck()
random.shuffle(deck)
tableau = deal_tableau(deck, card_images)
stock = deck.copy()
waste = []
stock_rect = pygame.Rect(50, 50, CARD_WIDTH, CARD_HEIGHT)
selected_column = None
running = True
while running:
screen.fill(GREEN)
for column_index, column in enumerate(tableau):
for row_index, card in enumerate(column):
x = 100 + column_index * 100
y = 150 + row_index * 20
card.draw(screen, x, y)
if stock:
screen.blit(card_back, stock_rect)
if waste:
waste[-1].draw(screen, 150, 50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if stock and stock_rect.collidepoint(event.pos):
waste.append(make_card(stock.pop(), card_images))
continue
for column_index, column in enumerate(tableau):
if column and column[-1].rect.collidepoint(event.pos):
selected_column = column_index
break
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
if selected_column is None:
continue
for target_index, column in enumerate(tableau):
target_y = 150 + len(column) * 20
drop_area = pygame.Rect(
100 + target_index * 100,
target_y,
CARD_WIDTH,
CARD_HEIGHT,
)
if drop_area.collidepoint(event.pos):
card = tableau[selected_column].pop()
tableau[target_index].append(card)
break
selected_column = None
pygame.display.flip()
clock.tick(60)
pygame.quit()
The program handles Pygame’s event queue every frame, uses positioned Rect objects for hit testing, and limits the loop to 60 frames per second. The card-back image is loaded once rather than repeatedly inside the loop.
What the Prototype Does
With all required image files available, the code:
- creates a Pygame window;
- loads and scales the card images;
- shuffles a 52-card deck;
- deals 28 cards across seven tableau columns;
- keeps the remaining 24 cards in the stock;
- draws a stock card into the waste area when the stock is clicked; and
- allows the top card in a tableau column to be placed into a drop area in another column.
The placement behavior does not check whether a move is legal. It also does not animate a card under the pointer while the mouse moves, despite the archived draft describing the interaction as dragging.
What Is Still Missing
A complete Classic Solitaire implementation would still need:
- face-up and face-down card state;
- tableau move validation by rank and color;
- movement of valid multi-card sequences;
- four working foundation piles;
- stock and waste rules, including any chosen recycle behavior;
- correct handling of empty tableau columns;
- synchronized visual and game state for every move;
- a new-game flow and win-condition check; and
- keyboard and other accessibility support beyond mouse input.
Those features and the required artwork were not present in the exported article, so they have not been invented here. Python syntax can be validated without the assets, but that does not prove functional correctness or rules-valid gameplay.