Search code examples
pythontkinterpi

How to calculate pi in tkinter using Buffon’s needles?


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

Solution

  • 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:

    enter image description here