I know there are many tutorials on how to calculate pi using Buffon's needles, but I didn't find any in tkinter.
I am a beginner, so sorry about the dirty code, but I will shortly walk you through it.
After drawing the setup it generates random x1 and y1 coordinates for the starting point of the line. Note that I want the lines to be 50 units long. So I generate x_rel
which can be in a range -50 to 50. After that using Pythagorean's theorem, I calculate y_rel
. Randomly it can be made negative or it can stay positive. To get the ending coordinates of the line, you just add up x_rel
and x1
, same for y.
To detect a collision with one of the starting lines on the board, I compare the first number of the y1 and y2. If they are the same, then there is no collision (this works because the gap between each starting line on the board is 100). After the counted collisions, I just calculate the pi and print it. But it usually returns ~2,6. But other programs like this which I have found online can calculate pi in this way fairly accurate. Where is my error?
import tkinter as tk
import random
import math
count = 0
class App:
def __init__(self, root):
self.root = root
root.title("PI calculator")
c = tk.Canvas(root, height=700, width=700)
c.pack()
c.create_line(100, 100, 600, 100)
c.create_line(100, 200, 600, 200)
c.create_line(100, 300, 600, 300)
c.create_line(100, 400, 600, 400)
c.create_line(100, 500, 600, 500)
c.create_line(100, 600, 600, 600)
for x in range(100000):
c.create_line(self.generate_coords(), fill="red", width=1.5)
def generate_coords(self):
choices = [1, 0]
x1 = random.randint(100, 600)
y1 = random.randint(100, 600)
x_rel = random.randint(-50, 50)
y_rel = math.sqrt(50**2 - x_rel**2)
if random.choice(choices) == 1:
y_rel = -abs(y_rel)
if random.choice(choices) == 0:
y_rel = abs(y_rel)
x2 = x1 + x_rel
y2 = y1 + y_rel
col = self.detect_collision(y1, y2)
if col == True:
global count
count += 1
return x1, y1, x2, y2
def detect_collision(self, y1, y2):
first_num_1 = math.floor(y1/100)
first_num_2 = math.floor(y2 / 100)
if first_num_1 != first_num_2:
return True
else:
return False
root = tk.Tk()
window = App(root)
pi = (2*50*100000)/(100*count)
print(count)
print(pi)
root.mainloop()
38243
2.6148576210025363
I played around with your code and compared it with similar programs and the conclusion I came to is that your pin drops weren't as random as they should be and this was skewing your results. Below I've reworked the pin dropping logic and the results seem to have improved:
import tkinter as tk
import random
import math
NUMBER_STICKS = 250
LINE_DISTANCE = 100
STICK_LENGTH = 83
class App:
def __init__(self, root):
self.root = root
root.title("PI calculator")
self.collisions = 0
c = tk.Canvas(root, height=700, width=700)
c.pack()
for y in range(1, 7):
c.create_line(100, y * LINE_DISTANCE, 600, y * LINE_DISTANCE)
for _ in range(NUMBER_STICKS):
collision, *coords = self.generate_coords()
c.create_line(coords, fill="red" if collision else "green", width=1.5)
def generate_coords(self):
x1 = random.randint(100, 600)
y1 = random.randint(100, 600)
angle = random.randrange(360)
x2 = x1 + STICK_LENGTH * math.cos(math.radians(angle))
y2 = y1 + STICK_LENGTH * math.sin(math.radians(angle))
collision = self.detect_collision(y1, y2)
if collision:
self.collisions += 1
return collision, x1, y1, x2, y2
def detect_collision(self, y1, y2):
num_1 = y1 // LINE_DISTANCE
num_2 = y2 // LINE_DISTANCE
return num_1 != num_2
def get_collisions(self):
return self.collisions
root = tk.Tk()
application = App(root)
collisions = application.get_collisions()
print((2 * STICK_LENGTH * NUMBER_STICKS) / (LINE_DISTANCE * collisions))
root.mainloop()
OUTPUT
% python3 test.py
3.516949152542373
%
I also modified the program to color code the pins as crossing or not crossing the lines: