Skip to content

Instantly share code, notes, and snippets.

@Rednexie
Created June 13, 2025 08:35
Show Gist options
  • Save Rednexie/920e98034058382595a63ba46bdf306b to your computer and use it in GitHub Desktop.
Save Rednexie/920e98034058382595a63ba46bdf306b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import tkinter as tk
from tkinter import simpledialog
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button, TextBox
import argparse
def simulate(rows, balls):
return np.random.binomial(rows, 0.5, balls)
def plot_hist(counts, rows):
balls = len(counts)
bins = np.arange(rows + 2) - 0.5
plt.hist(counts, bins=bins, density=True, color='skyblue', alpha=0.7, label='Simulation')
x = np.linspace(0, rows, 500)
mu, sigma = rows * 0.5, np.sqrt(rows * 0.25)
pdf = 1/(sigma * np.sqrt(2 * np.pi)) * np.exp(-(x - mu)**2 / (2 * sigma**2))
plt.plot(x, pdf, 'r--', label='Gaussian')
plt.xlabel('Slot')
plt.ylabel('Probability')
plt.title(f'{balls} balls over {rows} rows')
plt.legend()
plt.grid()
plt.show()
def interactive(rows):
# flag for stopping drops
cancel_flag = False
counts = np.zeros(rows+1, dtype=int)
fig, (ax_board, ax_hist) = plt.subplots(1, 2, figsize=(12, 6))
plt.subplots_adjust(bottom=0.15, left=0.05, right=0.95, top=0.95)
fig.patch.set_facecolor('#f0f0f0')
# Draw board pegs
peg_x, peg_y = [], []
for i in range(rows):
xs = (np.arange(i+1) - i/2)
y = -i
peg_x.extend(xs); peg_y.extend([y]*len(xs))
ax_board.scatter(peg_x, peg_y, s=25, color='#2c3e50', alpha=0.7)
ax_board.set_xlim(-rows/2-1, rows/2+1)
ax_board.set_ylim(-rows-1, 1)
ax_board.set_aspect('equal')
ax_board.axis('off')
ax_board.set_facecolor('#ecf0f1')
# Ball marker
ball_point, = ax_board.plot([], [], 'o', color='#e74c3c', markersize=10, markeredgecolor='#c0392b', markeredgewidth=1)
# Histogram
bins = np.arange(rows+1)
bars = ax_hist.bar(bins, counts, color='#3498db', edgecolor='#2980b9', alpha=0.8)
ax_hist.set_xlim(-0.5, rows+0.5)
ax_hist.set_ylim(0, 1)
ax_hist.set_xlabel('Yuva')
ax_hist.set_ylabel('Sayı')
ax_hist.set_facecolor('#ecf0f1')
ax_hist.grid(True, alpha=0.3)
# Controls with better spacing and styling
control_y = 0.08
control_height = 0.06
control_width = 0.09
ax_drop1 = fig.add_axes([0.05, control_y, control_width, control_height])
btn1 = Button(ax_drop1, '1 Top', color='#2ecc71', hovercolor='#27ae60')
ax_drop10 = fig.add_axes([0.16, control_y, control_width, control_height])
btn10 = Button(ax_drop10, '10 Top', color='#3498db', hovercolor='#2980b9')
ax_drop100 = fig.add_axes([0.27, control_y, control_width, control_height])
btn100 = Button(ax_drop100, '100 Top', color='#9b59b6', hovercolor='#8e44ad')
ax_drop100fast = fig.add_axes([0.38, control_y, control_width, control_height])
btn100fast = Button(ax_drop100fast, '100 Birden (sim)', color='#f39c12', hovercolor='#e67e22')
ax_text = fig.add_axes([0.49, control_y, control_width, control_height])
textbox = TextBox(ax_text, 'Özel N', initial='5')
ax_dropN = fig.add_axes([0.60, control_y, control_width, control_height])
btnN = Button(ax_dropN, 'N Top', color='#e74c3c', hovercolor='#c0392b')
ax_delay = fig.add_axes([0.71, control_y, control_width, control_height])
delay_box = TextBox(ax_delay, 'Gecikme', initial='1')
ax_cancel = fig.add_axes([0.82, control_y, control_width, control_height])
btn_cancel = Button(ax_cancel, 'İptal', color='#95a5a6', hovercolor='#7f8c8d')
def drop(k):
nonlocal cancel_flag
# reset cancel flag for new batch
cancel_flag = False
try:
delay = float(delay_box.text) / 100.0
except ValueError:
delay = 0.01
skip_anim = delay <= 0
for _ in range(k):
if cancel_flag:
break
choices = np.random.binomial(1, 0.5, rows)
if not skip_anim:
x, y = 0, 0
ball_point.set_data([x], [y])
fig.canvas.draw_idle(); plt.pause(delay)
for ch in choices:
if cancel_flag:
break
dx = (ch*2 - 1)/2
x += dx; y -= 1
ball_point.set_data([x], [y])
fig.canvas.draw_idle(); plt.pause(delay)
slot = int(sum(choices))
counts[slot] += 1
bars[slot].set_height(counts[slot])
if counts[slot] > ax_hist.get_ylim()[1]:
ax_hist.set_ylim(0, counts[slot]*1.1)
fig.canvas.draw_idle()
if not skip_anim and not cancel_flag:
plt.pause(delay)
def drop_fast(k):
# Drop k balls instantly without animation
for _ in range(k):
if cancel_flag:
break
slot = int(np.random.binomial(rows, 0.5))
counts[slot] += 1
bars[slot].set_height(counts[slot])
if counts[slot] > ax_hist.get_ylim()[1]:
ax_hist.set_ylim(0, counts[slot]*1.1)
fig.canvas.draw_idle()
btn1.on_clicked(lambda event: drop(1))
btn10.on_clicked(lambda event: drop(10))
btn100.on_clicked(lambda event: drop(100))
btn100fast.on_clicked(lambda event: drop_fast(100))
def drop_custom(event):
try:
n = int(textbox.text)
except ValueError:
return
drop(n)
btnN.on_clicked(drop_custom)
# cancel handler
def cancel(event):
nonlocal cancel_flag
cancel_flag = True
btn_cancel.on_clicked(cancel)
plt.show()
def main():
# Popup to ask user for number of rows
root = tk.Tk()
root.withdraw()
user_rows = simpledialog.askinteger("Plinko", "Satır sayısı?", minvalue=1)
root.destroy()
p = argparse.ArgumentParser(description="Simplified Plinko simulation")
p.add_argument('--static', action='store_true', help='Statik modda çalıştır')
p.add_argument('-b', '--balls', type=int, default=10000, help='Atılacak top sayısı')
args = p.parse_args()
# Determine rows (popup or default 10)
rows = user_rows if user_rows is not None else 10
if args.static:
counts = simulate(rows, args.balls)
plot_hist(counts, rows)
else:
interactive(rows)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment