Skip to content

Instantly share code, notes, and snippets.

@st1vms
Last active June 24, 2025 19:21
Show Gist options
  • Save st1vms/547c57b23e4d9baed118de10ebbf5a51 to your computer and use it in GitHub Desktop.
Save st1vms/547c57b23e4d9baed118de10ebbf5a51 to your computer and use it in GitHub Desktop.
Card Counter
# Requirements: pip install keyboard matplotlib
import keyboard
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from threading import Thread
# Number of decks in use
NUM_DECKS = 8
# Mapping of keyboard keys to card values
KEY_MAP = {
"1": "A", # Subjective mapping
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"0": "10", # Subjective mapping
"/": "J", # Subjective mapping
"*": "Q", # Subjective mapping
"-": "K", # Subjective mapping
}
# Initial count of each card in an 8-deck shoe
INITIAL_CARDS = {
"A": 4 * NUM_DECKS,
"2": 4 * NUM_DECKS,
"3": 4 * NUM_DECKS,
"4": 4 * NUM_DECKS,
"5": 4 * NUM_DECKS,
"6": 4 * NUM_DECKS,
"7": 4 * NUM_DECKS,
"8": 4 * NUM_DECKS,
"9": 4 * NUM_DECKS,
"10": 4 * NUM_DECKS,
"J": 4 * NUM_DECKS,
"Q": 4 * NUM_DECKS,
"K": 4 * NUM_DECKS,
}
# Runtime state
REMAINING_CARDS = INITIAL_CARDS.copy() # Mutable copy for tracking usage
CARD_LOG = [] # Ordered log of input cards
LABELS = list(INITIAL_CARDS.keys()) # For plotting
LAST_CARD_TEXT = None # Used to store the matplotlib text object
def calculate_hi_lo():
"""
Calculate the Hi-Lo running and true count based on the card log.
"""
running = 0
# Hi-Lo count system
for card in CARD_LOG:
if card in ["A", "2", "3", "4", "5", "6"]:
running -= 1
elif card in ["10", "J", "Q", "K"]:
running += 1
total_cards_left = sum(REMAINING_CARDS.values())
decks_remaining = max(total_cards_left / 52, 0.01) # Avoid division by zero
true_count = running / decks_remaining
return running, round(true_count, 2)
def update(_frame):
"""
Update the bar plot and overlayed text each frame.
"""
global LAST_CARD_TEXT
plt.cla() # Clear current axes
# Bar chart values
values = [REMAINING_CARDS[l] for l in LABELS]
bars = plt.bar(LABELS, values, color="dodgerblue", edgecolor="black")
# Title with current count values
running, true = calculate_hi_lo()
plt.title(f"RC: {running} | TC: {true}", pad=30, fontsize=20)
plt.xticks(fontsize=14)
max_y = max(INITIAL_CARDS.values())
plt.yticks(range(0, max_y + 1, 4), fontsize=14)
plt.ylim(0, max_y)
# Display card probability over each bar
total_cards_left = sum(REMAINING_CARDS.values())
for bar, label in zip(bars, LABELS):
prob = (
(REMAINING_CARDS[label] / total_cards_left) * 100
if total_cards_left > 0
else 0
)
plt.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height(),
f"{prob:.1f}%",
ha="center",
va="bottom",
fontsize=12,
)
# Show the last 10 cards played
last_cards = CARD_LOG[-20:]
text_str = "Last 20 Cards: " + " ".join(last_cards)
if LAST_CARD_TEXT is not None:
LAST_CARD_TEXT.set_text(text_str)
else:
LAST_CARD_TEXT = plt.gcf().text(0.5, 0.02, text_str, ha="center", fontsize=18)
def listen_keys():
"""
Listen for keypresses and update game state accordingly.
"""
while True:
event = keyboard.read_event()
if event.event_type == keyboard.KEY_DOWN:
key = event.name.lower()
if key in KEY_MAP:
label = KEY_MAP[key]
if REMAINING_CARDS[label] > 0:
CARD_LOG.append(label)
REMAINING_CARDS[label] -= 1
# Allow undoing the last card entry
elif key == "backspace" and CARD_LOG:
last = CARD_LOG.pop()
REMAINING_CARDS[last] += 1
def _main() -> None:
"""
Initialize plot and start key listener.
"""
fig = plt.figure(figsize=(10, 8))
fig.canvas.manager.set_window_title("Card Counter")
# Disable matplotlib's default key handler
fig.canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)
# Start animation for live updates
_ = FuncAnimation(fig, update, interval=200, cache_frame_data=False)
# Start key listening in a background thread
Thread(target=listen_keys, daemon=True).start()
plt.tight_layout()
plt.show()
if __name__ == "__main__":
_main()
# Requirements: pip install keyboard matplotlib
import keyboard
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from threading import Thread
# Number of decks in use
NUM_DECKS = 8
# Mapping of keyboard keys to card values
KEY_MAP = {
"1": "A", # Subjective mapping
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"0": "10", # Subjective mapping
"/": "J", # Subjective mapping
"*": "Q", # Subjective mapping
"-": "K", # Subjective mapping
}
# Initial count of each card in an 8-deck shoe
INITIAL_CARDS = {
"A": 4 * NUM_DECKS,
"2": 4 * NUM_DECKS,
"3": 4 * NUM_DECKS,
"4": 4 * NUM_DECKS,
"5": 4 * NUM_DECKS,
"6": 4 * NUM_DECKS,
"7": 4 * NUM_DECKS,
"8": 4 * NUM_DECKS,
"9": 4 * NUM_DECKS,
"10": 4 * NUM_DECKS,
"J": 4 * NUM_DECKS,
"Q": 4 * NUM_DECKS,
"K": 4 * NUM_DECKS,
}
# Runtime state
REMAINING_CARDS = INITIAL_CARDS.copy() # Mutable copy for tracking usage
CARD_LOG = [] # Ordered log of input cards
LABELS = list(INITIAL_CARDS.keys()) # For plotting
LAST_CARD_TEXT = None # Used to store the matplotlib text object
def calculate_hi_lo():
"""
Calculate the Hi-Lo running and true count based on the card log.
"""
running = 0
# Hi-Lo count system
for card in CARD_LOG:
if card in ["2", "3", "4", "5", "6"]:
running += 1
elif card in ["10", "J", "Q", "K", "A"]:
running -= 1
total_cards_left = sum(REMAINING_CARDS.values())
decks_remaining = max(total_cards_left / 52, 0.01) # Avoid division by zero
true_count = running / decks_remaining
return running, round(true_count, 2)
def update(_frame):
"""
Update the bar plot and overlayed text each frame.
"""
global LAST_CARD_TEXT
plt.cla() # Clear current axes
# Bar chart values
values = [REMAINING_CARDS[l] for l in LABELS]
bars = plt.bar(LABELS, values, color="dodgerblue", edgecolor="black")
# Title with current count values
running, true = calculate_hi_lo()
plt.title(f"RC: {running} | TC: {true}", pad=30, fontsize=20)
plt.xticks(fontsize=14)
max_y = max(INITIAL_CARDS.values())
plt.yticks(range(0, max_y + 1, 4), fontsize=14)
plt.ylim(0, max_y)
# Display card probability over each bar
total_cards_left = sum(REMAINING_CARDS.values())
for bar, label in zip(bars, LABELS):
prob = (
(REMAINING_CARDS[label] / total_cards_left) * 100
if total_cards_left > 0
else 0
)
plt.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height(),
f"{prob:.1f}%",
ha="center",
va="bottom",
fontsize=12,
)
# Show the last 10 cards played
last_cards = CARD_LOG[-20:]
text_str = "Last 20 Cards: " + " ".join(last_cards)
if LAST_CARD_TEXT is not None:
LAST_CARD_TEXT.set_text(text_str)
else:
LAST_CARD_TEXT = plt.gcf().text(0.5, 0.02, text_str, ha="center", fontsize=18)
def listen_keys():
"""
Listen for keypresses and update game state accordingly.
"""
while True:
event = keyboard.read_event()
if event.event_type == keyboard.KEY_DOWN:
key = event.name.lower()
if key in KEY_MAP:
label = KEY_MAP[key]
if REMAINING_CARDS[label] > 0:
CARD_LOG.append(label)
REMAINING_CARDS[label] -= 1
# Allow undoing the last card entry
elif key == "backspace" and CARD_LOG:
last = CARD_LOG.pop()
REMAINING_CARDS[last] += 1
def _main() -> None:
"""
Initialize plot and start key listener.
"""
fig = plt.figure(figsize=(10, 8))
fig.canvas.manager.set_window_title("Card Counter")
# Disable matplotlib's default key handler
fig.canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)
# Start animation for live updates
_ = FuncAnimation(fig, update, interval=200, cache_frame_data=False)
# Start key listening in a background thread
Thread(target=listen_keys, daemon=True).start()
plt.tight_layout()
plt.show()
if __name__ == "__main__":
_main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment