This version of Project Penney is a 3-bit simulation where two players compete by selecting sequences of three binary playing card values:
- RED = 0
- BLACK = 1
The goal of the project is to simulate n Penney games and to visualize player combination probabilities. Each simulation shuffles a deck of 52 cards (26 red, 26 black), and the game is played by searching for each player's sequence within this shuffled deck. The player whose sequence appears first in the deck will win a trick.
Points are awarded in two ways:
- Tricks: When a player's sequence appears in the deck, a trick is earned. Each trick is always work +1 point.
- Cards: The number of cards between the last match and the current match (inclusive) is added to the player's card score. Cards will always be awarded when a trick is won, and the minimum number of won cards will always be three (equivalent to the length of the combination).
Since Player 1 (P1) selectes their sequence, Player 2 (P2) has a strategic advantage in selecting a counter-sequence to maximize their probability of winning or at least forcing a draw. This advantage is represented in the selection formula:
- If P1 chooses
[1, 2, 1], the optimal response for P2 is[-2, 1, 2]where P2 inverts the second position of P1's combo in their first position. Then appends P1's original first and second positions in the combination.
In the context of Project Penney:
- If P1 chooses
[R, R, B], then P2's counter combination might be[B, R, R]. For more informmation regarding Penney probabilities on scoring and countering, see this explanation.
- Efficient generation of large numbers of shuffled decks using seed generation to allow for reproducible simulations.
- Decks are saved in compressed
.npzfiles and stored in the/deckssubfolder within the/datafolder. - All used seeds are saved for referencing in
.jsonfiles and located in the/seedssubfolder within the/datafolder. - Any large batched datasets are automatically chunked into separate files, with a maximum of 10000 decks per file. Per-file prefixes on each deck file name indicated the total number of contained decks within the file.
- All 56 valid P1 vs P2 matchups (which omits self-matches) are simulated on every deck.
- The scoring approach uses a sliding window algorithm to locate sequence matches. More information on this approach may be found here.
- Win and draw probabilities are calculated from scoring on both tricks and cards.
- This game version includes framework for future analysis of P1 strategies.
- Employs
tqdmto track the length of simulation runs.
- Matchup probabilities have been calculated by normalizing results across the number of decks simulated
- Results are stored as 8x8 matrices for visualization.
- Generates two separate annotated heatmaps (one for tricks and another for cards) with P2 Win(Draw) percentages, rounded to the nearest whole number.
- Invalid P1 vs P2 matchups have been handled by greying out diagonal cells.
- Annotation font color will dynamically adjust in heatmap cells based on probability percentage/cell color.
- Heatmaps are saved at 300dpi to
/plotsCard_Heatmap.pngTrick_Heatmap.png
Disclaimer: this project is not a game rather it is a simulation and visualization tool to aid in optimizing 3-bit Penney's games.
project-root/
│
├── main.py # entry point to run project penney simulation and visualization
├── pyproject.toml # dependencies and configuration (managed by uv)
├── uv.lock # lock file for uv environment
├── .python-version # specifies Python version
├── .gitignore # git ignore rules
├── .gitattributes # git attributes
├── README.md # project documentation
│
├── src/ # source code modules
│ ├── datagen.py # generates and stores shuffled decks
│ ├── helpers.py # constants, utility functions, and path configs
│ ├── processing.py # game simulation, scoring logic, probability calculation
│ └── visualization.py # matrix formatting and heatmap generation
│
├── data/ # simulation data
│ ├── decks/ # stored shuffled decks (.npz)
│ └── seeds/ # JSON files of seeds used for each deck set
│
└── plots/ # Win/Draw percentage heatmaps for tricks and cards
To begin using this repository, clone it and install dependencies listed in pyproject.toml. This project is managed with uv; additional setup instructions may be found in the uv installation guide. However, to simply see the project's results, continue and clone the repository, and run the script out of main.py. An alternative set up is below:
from src.datagen import store_decks
# generates and stores 50000 decks across 5 files of 10k decks each
# note: using the same seed will generate the same shuffled decks
store_decks(n_decks=50000, seed=15)
from src.processing import load_decks, aggregate_results
decks = load_decks()
trick_results, card_results = aggregate_results(decks)
from src.visualization import plot_results
# automatically reads decks and generates heatmaps
plot_results(decks)