Created
June 13, 2025 08:35
-
-
Save Rednexie/920e98034058382595a63ba46bdf306b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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