Untitled

 avatar
unknown
plain_text
5 months ago
32 kB
20
No Index
"""
EuroMillions QTRNG Correlator
Copyright (c) Jags Uprising

This script provides a GUI-based tool to correlate EuroMillions draw numbers
with a quantum random number generator (QTRNG). It includes features to:
- Input and check drawn numbers.
- Fetch numbers and validate their ranks and scores.
- Import and export data as CSV files.

Requires:
- PyFTDI for USB communication.
- Tkinter for GUI.
"""

import io
import threading
import contextlib
import tkinter as tk
from tkinter import ttk, Canvas, Frame
from datetime import datetime, timedelta
import time
import requests
from pyftdi.ftdi import Ftdi
import numpy as np
from bs4 import BeautifulSoup
from tkinter.filedialog import askopenfilename  # Moved import to top-level

# Constants
DATES_ALLOWED = {"Tuesday", "Friday"}
TEST_TIME = timedelta(minutes=10)  # Test duration
BITS_PER_SECOND = 620
TOTAL_NUMBERS = 50 + 12  # 50 normal + 12 lucky numbers
BITS_PER_NUMBER = 10

def initialize_ftdi():
    """Initializes the FTDI device for reading random bits."""
    def list_devices():
        """Lists connected FTDI devices."""
        buffer = io.StringIO()
        try:
            with contextlib.redirect_stdout(buffer):
                Ftdi().show_devices()
            output = buffer.getvalue()
            for line in output.split("\n"):
                if "ftdi://" in line:
                    return line.split()[0]
            return None
        except ValueError as e:
            print("No FTDI USB device available:", e)
            return None

    device_url = list_devices()
    if not device_url:
        raise RuntimeError('Defaulting to "Algorithmic" bit source.')

    ftdi = Ftdi()
    ftdi.open_from_url(device_url)
    ftdi.set_latency_timer(2)
    return ftdi

def read_bits_from_ftdi(ftdi, num_bits=8):
    """
    Reads bits from the FTDI device.
    Args:
        ftdi (Ftdi): The FTDI device instance.
        num_bits (int): The number of bits to read.
    Returns:
        list: A list of bits read from the device.
    """
    data = ftdi.read_data(num_bits)
    bits = []
    for byte in data:
        bits.extend([(byte >> i) & 1 for i in range(7, -1, -1)])
    return bits

class EuroMillionsTracker:
    """
    A GUI application for tracking EuroMillions lottery data and analyzing random bit correlations.
    """

    def __init__(self, master):
        """
        Initializes the EuroMillionsTracker application.
        Args:
            master (tk.Tk): The Tkinter root window.
        """
        self.master = master
        self.master.title("EuroMillions QTRNG Correlator")

        # Initialize attributes
        self.bit_source = "FTDI USB Device"  # Default bit source
        self.device_available = True  # USB device availability flag
        try:
            self.ftdi = initialize_ftdi()
        except RuntimeError as e:
            print(f"{e}")
            self.device_available = False
            self.bit_source = "Algorithmic"

        self.sums = np.zeros(TOTAL_NUMBERS)
        self.highest = np.full(TOTAL_NUMBERS, -np.inf)
        self.lowest = np.full(TOTAL_NUMBERS, np.inf)
        self.last = np.zeros(TOTAL_NUMBERS)
        self.round_robin_index = 0
        self.running = False
        self.start_time = None
        self.timestamps = []
        self.sum_history = []

        self.create_widgets()

    def get_valid_dates(self):
        """
        Calculates valid dates for EuroMillions draws.
        Returns:
            list: A list of valid dates as strings in YYYY-MM-DD format.
        """
        base_date = datetime.now() - timedelta(days=60)
        valid_dates = []
        for i in range(120):
            future_date = base_date + timedelta(days=i)
            if future_date.strftime("%A") in DATES_ALLOWED:
                valid_dates.append(future_date.strftime("%Y-%m-%d"))
        return valid_dates

    def import_csv(self, filename):
        """
        Imports data from a CSV file.
        Args:
            filename (str): The path to the CSV file.
        """
        try:
            with open(filename, "r", encoding="utf-8") as file:  # Added encoding
                lines = file.readlines()

            correlated_date = None
            self.start_time = None
            self.bit_source = None
            self.timestamps = []
            self.sum_history = []

            # Reset statistics
            self.sums = np.zeros(TOTAL_NUMBERS)
            self.lowest = np.full(TOTAL_NUMBERS, np.inf)
            self.highest = np.full(TOTAL_NUMBERS, -np.inf)
            self.last = np.zeros(TOTAL_NUMBERS)

            line_index = 0
            while line_index < len(lines):
                line = lines[line_index].strip()
                if line.startswith("Correlated Date:"):
                    correlated_date = line.split(": ", 1)[1]
                elif line.startswith("Test Start Time:"):
                    test_start_time = line.split(": ", 1)[1]
                    self.start_time = datetime.strptime(test_start_time, "%Y-%m-%d %H:%M:%S.%f")
                elif line.startswith("Bit Source:"):
                    self.bit_source = line.split(": ", 1)[1]
                elif line.startswith("Time (s),"):
                    line_index += 1
                    break
                line_index += 1

            if hasattr(self, "date_picker") and correlated_date:
                self.date_picker.set(correlated_date)

            while line_index < len(lines):
                line = lines[line_index].strip()
                if line == "":
                    break
                parts = line.split(", ")
                timestamp = float(parts[0])
                sums = list(map(lambda x: int(float(x)), parts[1:TOTAL_NUMBERS + 1]))
                self.timestamps.append(timestamp)
                self.sum_history.append(sums)

                for i in range(TOTAL_NUMBERS):
                    value = sums[i]
                    self.sums[i] = value
                    self.lowest[i] = min(self.lowest[i], value)
                    self.highest[i] = max(self.highest[i], value)
                    self.last[i] = value
                line_index += 1

            self.update_table()
            print("CSV file import successful.")

        except Exception as e:
            print(f"Error importing CSV file: {e}")

    def create_widgets(self):
        """Creates the widget interface for the application."""
        # Control Frame
        self.control_frame = ttk.Frame(self.master)
        self.control_frame.pack(side="top", fill="x", padx=5, pady=5)
        ttk.Label(self.control_frame, text="Draw Date:").pack(side="left", padx=5)
        self.date_picker = ttk.Combobox(
            self.control_frame, values=self.get_valid_dates(), state="readonly"
        )
        self.date_picker.pack(side="left", padx=5)
        self.date_picker.set(self.get_valid_dates()[0])
        self.run_button = ttk.Button(
            self.control_frame, text="Run", command=self.run_test
        )
        self.run_button.pack(side="left", padx=5)
        self.stop_button = ttk.Button(
            self.control_frame, text="Stop", command=self.stop_test
        )
        self.stop_button.pack(side="left", padx=5)
        self.log_file_button = ttk.Button(
            self.control_frame, text="Export CSV", command=self.save_log
        )
        self.log_file_button.pack(side="left", padx=5)
        self.bit_source_button = ttk.Button(
            self.control_frame,
            text=f"Source: {self.bit_source}",
            command=self.toggle_bit_source,
        )
        self.bit_source_button.pack(side="left", padx=5)

        # Disable toggle button if no USB device is available
        if not self.device_available:
            self.bit_source_button.config(state="disabled")

        # Subtitle
        subtitle = "Time: 21:00-21:10 CET, Paris, France"
        self.subtitle_label = ttk.Label(
            self.master,
            text=f"EuroMillions RNG Correlation\n{subtitle}",
            font=("Arial", 10),
        )
        self.subtitle_label.pack(side="top", pady=5)

        # Input Frame for Drawn Numbers
        self.input_frame = ttk.Frame(self.master)
        self.input_frame.pack(side="top", fill="x", padx=5, pady=5)

        ttk.Label(self.input_frame, text="Drawn Numbers:").grid(row=0, column=0, padx=5)
        self.drawn_normal_numbers = [
            ttk.Entry(self.input_frame, width=5) for _ in range(5)
        ]
        for i, entry in enumerate(self.drawn_normal_numbers):
            entry.grid(row=0, column=i + 1, padx=5)

        ttk.Label(self.input_frame, text="Lucky Numbers:").grid(row=0, column=6, padx=5)
        self.drawn_lucky_numbers = [
            ttk.Entry(self.input_frame, width=5) for _ in range(2)
        ]
        for i, entry in enumerate(self.drawn_lucky_numbers):
            entry.grid(row=0, column=i + 7, padx=5)

        # "Fetch Numbers" Button
        self.fetch_button = ttk.Button(
            self.input_frame,
            text="Fetch Numbers",
            command=self.fetch_and_fill_drawn_numbers,
        )
        self.fetch_button.grid(row=0, column=9, padx=5)

        # Button to check the ranks
        self.check_button = ttk.Button(
            self.input_frame, text="Check Ranks", command=self.check_drawn_numbers
        )
        self.check_button.grid(row=0, column=10, padx=5)

        # Add "Predicted:" labels
        ttk.Label(self.input_frame, text="Predicted:").grid(row=1, column=0, padx=5)
        self.predicted_normal_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(5)
        ]
        for i in range(5):
            self.predicted_normal_labels[i].grid(row=1, column=i + 1, padx=5)

        ttk.Label(self.input_frame, text="Predicted:").grid(row=1, column=6, padx=5)
        self.predicted_lucky_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(2)
        ]
        for i in range(2):
            self.predicted_lucky_labels[i].grid(row=1, column=i + 7, padx=5)

        # Create labels for normal numbers ranks and scores (displaying results below the input)
        ttk.Label(self.input_frame, text="Rank:").grid(row=2, column=0, padx=5)
        self.normal_ranks_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(5)
        ]
        self.normal_scores_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(5)
        ]
        for i in range(5):
            self.normal_ranks_labels[i].grid(row=2, column=i + 1, padx=5)
            self.normal_scores_labels[i].grid(row=3, column=i + 1, padx=5)

        # Create labels for lucky numbers ranks and scores
        ttk.Label(self.input_frame, text="Rank:").grid(row=2, column=6, padx=5)
        self.lucky_ranks_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(2)
        ]
        self.lucky_scores_labels = [
            ttk.Label(self.input_frame, text="") for _ in range(2)
        ]
        for i in range(2):
            self.lucky_ranks_labels[i].grid(row=2, column=i + 7, padx=5)
            self.lucky_scores_labels[i].grid(row=3, column=i + 7, padx=5)

        # Create "Score:" labels below the "Rank:" labels for lucky numbers
        ttk.Label(self.input_frame, text="Score:").grid(row=3, column=6, padx=5)
        ttk.Label(self.input_frame, text="Score:").grid(row=3, column=0, padx=5)

        # Tables
        self.scroll_frame = Frame(self.master)
        self.scroll_frame.pack(side="top", fill="x", padx=5, pady=5)
        self.create_tables()

        # Add a "Import CSV" button to load a CSV file
        self.import_button = ttk.Button(
            self.control_frame, text="Import CSV", command=self.import_csv_dialog
        )
        self.import_button.pack(side="left", padx=5)

    def check_drawn_numbers(self):
        """Check the ranks of the entered drawn numbers and predicted numbers."""
        try:
            # Get the entered numbers
            normal_numbers = [
                int(entry.get())
                for entry in self.drawn_normal_numbers
                if entry.get().isdigit()
            ]
            lucky_numbers = [
                int(entry.get())
                for entry in self.drawn_lucky_numbers
                if entry.get().isdigit()
            ]

            # Check if all numbers are valid
            if len(normal_numbers) != 5 or len(lucky_numbers) != 2:
                print("Please enter exactly 5 normal numbers and 2 lucky numbers.")
                return

            # Ensure self.highest contains no invalid values
            self.highest = np.nan_to_num(self.highest)

            # Create sorted lists of indices based on the highest scores
            normal_ranks = np.argsort(
                -self.highest[:50]
            )  # Normal numbers
            lucky_ranks = np.argsort(
                -self.highest[50:]
            )  # Lucky numbers

            # Prepare results for normal and lucky numbers
            normal_scores = []
            normal_ranks_list = []
            for num in normal_numbers:
                if 1 <= num <= 50:
                    index = num - 1  # Convert to zero-based index
                    rank_indices = np.where(normal_ranks == index)[0]
                    if rank_indices.size > 0:  # Check if the index exists in normal_ranks
                        rank = rank_indices[0] + 1  # Find rank (1-based)
                        score = int(self.highest[index])
                        normal_scores.append(score)
                        normal_ranks_list.append(rank)
                    else:
                        normal_scores.append("Rank not found")
                        normal_ranks_list.append("N/A")
                else:
                    normal_scores.append("Invalid")
                    normal_ranks_list.append("Invalid")

            lucky_scores = []
            lucky_ranks_list = []
            for num in lucky_numbers:
                if 1 <= num <= 12:
                    index = 50 + num - 1  # Convert to zero-based index in lucky numbers
                    rank_indices = np.where(lucky_ranks == (num - 1))[0]
                    if rank_indices.size > 0:  # Check if the index exists in lucky_ranks
                        rank = rank_indices[0] + 1  # Find rank (1-based)
                        score = int(self.highest[index])
                        lucky_scores.append(score)
                        lucky_ranks_list.append(rank)
                    else:
                        lucky_scores.append("Rank not found")
                        lucky_ranks_list.append("N/A")
                else:
                    lucky_scores.append("Invalid")
                    lucky_ranks_list.append("Invalid")

            # Calculate the predicted numbers
            predicted_normal_indices = normal_ranks[:5]
            predicted_lucky_indices = lucky_ranks[:2]

            predicted_normal_numbers = [idx + 1 for idx in predicted_normal_indices]
            predicted_normal_scores = [int(self.highest[idx]) for idx in predicted_normal_indices]

            predicted_lucky_numbers = [idx + 1 for idx in predicted_lucky_indices]
            predicted_lucky_scores = [int(self.highest[50 + idx]) for idx in predicted_lucky_indices]

            # Display the predicted numbers and their scores
            for i, label in enumerate(self.predicted_normal_labels):
                label.config(text=f"{predicted_normal_numbers[i]} ({predicted_normal_scores[i]})")

            for i, label in enumerate(self.predicted_lucky_labels):
                label.config(text=f"{predicted_lucky_numbers[i]} ({predicted_lucky_scores[i]})")

            # Display the ranks and scores for drawn numbers
            for i in range(5):
                self.normal_ranks_labels[i].config(text=normal_ranks_list[i])
                self.normal_scores_labels[i].config(text=normal_scores[i])

            for i in range(2):
                self.lucky_ranks_labels[i].config(text=lucky_ranks_list[i])
                self.lucky_scores_labels[i].config(text=lucky_scores[i])

        except Exception as e:
            print(f"Error checking numbers: {e}")

            # Update Normal Numbers Ranks and Scores in respective labels
            for i, (num, rank, score) in enumerate(
                zip(normal_numbers, normal_ranks_list, normal_scores)
            ):
                # Just show the rank and score without color or bold formatting
                self.normal_ranks_labels[i].config(text=str(rank))
                self.normal_scores_labels[i].config(text=str(score))

            # Update Lucky Numbers Ranks and Scores in respective labels
            for i, (num, rank, score) in enumerate(
                zip(lucky_numbers, lucky_ranks_list, lucky_scores)
            ):
                # Just show the rank and score without color or bold formatting
                self.lucky_ranks_labels[i].config(text=str(rank))
                self.lucky_scores_labels[i].config(text=str(score))

        except Exception as e:
            print(f"Error checking ranks: {e}")

    def fetch_and_fill_drawn_numbers(self):
        """Fetch drawn numbers from the website and fill them in the input fields."""
        try:
            # Get the selected date from the combobox
            selected_date = self.date_picker.get()
            if not selected_date:
                print("No date selected.")
                return

            # Fetch the drawn numbers from the website
            drawn_numbers = self.fetch_drawn_numbers_from_web(selected_date)

            # Update the fields with fetched numbers
            if drawn_numbers:  # Ensure we got numbers before updating fields
                normal_numbers, lucky_numbers = drawn_numbers
                for i in range(5):
                    self.drawn_normal_numbers[i].delete(0, "end")
                    self.drawn_normal_numbers[i].insert(0, str(normal_numbers[i]))
                for i in range(2):
                    self.drawn_lucky_numbers[i].delete(0, "end")
                    self.drawn_lucky_numbers[i].insert(0, str(lucky_numbers[i]))
                print("Fetched and updated drawn numbers successfully.")
            else:
                print("Failed to fetch numbers. Please try again or enter manually.")

        except Exception as e:
            print(f"Error fetching drawn numbers: {e}")

    def fetch_drawn_numbers_from_web(self, selected_date):
        """
        Scrape drawn numbers from the website based on the selected date.
        Args:
            selected_date (str): The date to fetch the drawn numbers for.
        Returns:
            tuple: Normal numbers and lucky numbers scraped from the website.
        """
        try:
            # Convert the string date to a datetime object
            date_obj = datetime.strptime(selected_date, "%Y-%m-%d")
            formatted_date = date_obj.strftime("%d-%m-%Y")

            url = f"https://www.lottery.co.uk/euromillions/results-{formatted_date}"
            response = requests.get(url, timeout=5)  # Added timeout
            if response.status_code != 200:
                raise Exception(
                    f"Failed to fetch webpage. Status code: {response.status_code}"
                )

            soup = BeautifulSoup(response.content, "html.parser")
            balls = soup.select("#ballsAscending span")

            if len(balls) < 7:
                raise ValueError("Unable to find all drawn numbers on the webpage.")

            # Extract numbers
            normal_numbers = [int(balls[i].text) for i in range(5)]
            lucky_numbers = [int(balls[i].text) for i in range(5, 7)]

            return normal_numbers, lucky_numbers

        except ValueError as ve:
            print(f"ValueError: {ve}")
            return None
        except requests.RequestException as re:
            print(f"RequestException: {re}")
            return None
        except Exception as e:
            print(f"Error scraping website for drawn numbers: {e}")
            return None

    def import_csv_dialog(self):
        """Open a dialog to select a CSV file for import."""
        filename = askopenfilename(filetypes=[("CSV Files", "*.csv")])
        if filename:
            self.import_csv(filename)

    def toggle_bit_source(self):
        """Toggle between USB Device and Algorithmic Random Bit sources."""
        if not self.device_available:
            print("FTDI USB device is not available. Cannot toggle to 'FTDI USB Device'.")
            return
        self.bit_source = "Algorithmic" if self.bit_source == "FTDI USB Device" else "FTDI USB Device"
        self.bit_source_button.config(text=f"Source: {self.bit_source}")

    def create_tables(self):
        """Create 1 scrollable table for all normal numbers and 1 for lucky numbers."""
        self.labels = []  # Store all labels for updating

        table_frame = Frame(self.scroll_frame)
        table_frame.pack(side="top", fill="x", pady=10)

        # Create 1 scrollable table for all normal numbers (50 numbers)
        frame = Frame(table_frame, relief="ridge", borderwidth=2)
        frame.pack(side="top", padx=5)

        ttk.Label(frame, text="Normal Numbers", font=("Arial", 10, "bold")).grid(
            row=0, column=0, columnspan=4, pady=5
        )

        # Column Headers for Normal Numbers
        headers = ["Number", "Lowest", "Highest", "Last"]
        for col_idx, header in enumerate(headers):
            header_label = ttk.Label(frame, text=header, font=("Arial", 10, "bold"))
            header_label.grid(row=1, column=col_idx, padx=5, pady=5)

        # Create a canvas with a scrollbar for scrolling the table rows
        canvas = Canvas(frame, height=115)
        canvas.grid(row=2, column=0, columnspan=4, padx=5, pady=5)

        scrollbar = ttk.Scrollbar(frame, orient="vertical", command=canvas.yview)
        scrollbar.grid(row=2, column=4, sticky="ns")

        canvas.configure(yscrollcommand=scrollbar.set)
        canvas_frame = Frame(canvas)
        canvas.create_window((0, 0), window=canvas_frame, anchor="nw")

        # Store normal number rows as labels
        self.normal_number_rows = []

        # Add rows for normal numbers (50 total)
        for num_idx in range(50):
            row = Frame(canvas_frame)
            row.grid(row=num_idx, column=0, sticky="w", padx=5, pady=2)

            # Align the row content labels properly under the column headers
            ttk.Label(row, text=f"Num {num_idx + 1}", width=16, anchor="w").grid(
                row=0, column=0, padx=5
            )
            lowest_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            lowest_label.grid(row=0, column=1, padx=5)
            highest_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            highest_label.grid(row=0, column=2, padx=5)
            last_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            last_label.grid(row=0, column=3, padx=5)

            self.normal_number_rows.append((lowest_label, highest_label, last_label))

        # Update canvas scrolling region for normal numbers
        canvas_frame.update_idletasks()
        canvas.config(scrollregion=canvas.bbox("all"))

        # Lucky Numbers Table (below the normal numbers table)
        frame = Frame(table_frame, relief="ridge", borderwidth=2)
        frame.pack(side="top", padx=5, pady=10)

        ttk.Label(frame, text="Lucky Numbers", font=("Arial", 10, "bold")).grid(
            row=0, column=0, columnspan=4, pady=5
        )

        # Column Headers for Lucky Numbers
        for col_idx, header in enumerate(headers):
            ttk.Label(frame, text=header, font=("Arial", 10, "bold")).grid(
                row=1, column=col_idx, padx=5, pady=5
            )

        # Create a canvas with a scrollbar for lucky numbers
        canvas = Canvas(frame, height=115)
        canvas.grid(row=2, column=0, columnspan=4, padx=5, pady=5)

        scrollbar = ttk.Scrollbar(frame, orient="vertical", command=canvas.yview)
        scrollbar.grid(row=2, column=4, sticky="ns")

        canvas.configure(yscrollcommand=scrollbar.set)
        canvas_frame = Frame(canvas)
        canvas.create_window((0, 0), window=canvas_frame, anchor="nw")

        # Store lucky number rows as labels
        self.lucky_number_rows = []

        # Add rows for lucky numbers (12 total)
        for num_idx in range(12):
            row = Frame(canvas_frame)
            row.grid(row=num_idx, column=0, sticky="w", padx=5, pady=2)

            # Align the row content labels properly under the column headers
            ttk.Label(row, text=f"Lucky {num_idx + 1}", width=16, anchor="w").grid(
                row=0, column=0, padx=5
            )
            lowest_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            lowest_label.grid(row=0, column=1, padx=5)
            highest_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            highest_label.grid(row=0, column=2, padx=5)
            last_label = ttk.Label(row, text="N/A", width=16, anchor="w")
            last_label.grid(row=0, column=3, padx=5)

            self.lucky_number_rows.append((lowest_label, highest_label, last_label))

        # Update canvas scrolling region for lucky numbers
        canvas_frame.update_idletasks()
        canvas.config(scrollregion=canvas.bbox("all"))

    def update_table(self):
        """Update the statistics table in the GUI."""
        current_time = time.time()  # Get the current time in seconds
        if not hasattr(self, "last_ui_update_time"):
            self.last_ui_update_time = current_time

        if current_time - self.last_ui_update_time >= 1:
            self.last_ui_update_time = current_time

        # Update the rows
        for i, (lowest_label, highest_label, last_label) in enumerate(
            self.normal_number_rows
        ):
            lowest_label.config(text=str(int(self.lowest[i])))  # Convert to integer
            highest_label.config(text=str(int(self.highest[i])))  # Convert to integer
            last_label.config(text=str(int(self.last[i])))  # Convert to integer

        # Do the same for lucky numbers (if needed)
        for i, (lowest_label, highest_label, last_label) in enumerate(
            self.lucky_number_rows
        ):
            lowest_label.config(
                text=str(int(self.lowest[50 + i]))
            )  # Convert to integer
            highest_label.config(
                text=str(int(self.highest[50 + i]))
            )  # Convert to integer
            last_label.config(text=str(int(self.last[50 + i])))  # Convert to integer

    def run_test(self):
        """Start the data collection process."""
        if self.running:
            return
        self.running = True
        threading.Thread(target=self.collect_data, daemon=True).start()

    def stop_test(self):
        """Stop the data collection process."""
        self.running = False

    def collect_data(self):
        """Collect data from the random bit source."""
        try:
            if self.bit_source == "USB Device":
                self.ftdi = initialize_ftdi()
        except RuntimeError as e:
            print("No FTDI device available:", e)
            self.running = False
            return

        self.start_time = datetime.now()
        end_time = self.start_time + TEST_TIME
        last_saved_second = -1  # Track the last saved second

        while self.running and datetime.now() < end_time:
            if self.bit_source == "USB Device":
                bits = read_bits_from_ftdi(self.ftdi, num_bits=BITS_PER_SECOND)
            else:
                bits = np.random.choice([0, 1], size=BITS_PER_SECOND).tolist()

            for bit in bits:
                self.update_statistics(bit)
                self.round_robin_index = (self.round_robin_index + 1) % TOTAL_NUMBERS

            # Check if we have moved to the next full second
            current_second = int((datetime.now() - self.start_time).total_seconds())
            if current_second > last_saved_second:
                last_saved_second = current_second
                # Record the sums only for the full second
                self.timestamps.append(current_second)
                self.sum_history.append(self.last.copy())

            self.update_table()

    def update_statistics(self, bit):
        """Update statistics based on the received bit."""
        value = 1 if bit == 1 else -1
        index = self.round_robin_index

        self.sums[index] = int(self.sums[index] + value)  # Ensure integer sum
        self.last[index] = int(self.sums[index])  # Ensure last is an integer
        self.highest[index] = int(
            max(self.highest[index], self.sums[index])
        )  # Ensure highest is an integer
        self.lowest[index] = int(
            min(self.lowest[index], self.sums[index])
        )  # Ensure lowest is an integer

    def save_log(self):
        """Save the collected data to a CSV file."""
        if not self.start_time or not self.timestamps:
            print("No data to save.")
            return
        selected_date = self.date_picker.get()
        formatted_date = datetime.strptime(selected_date, "%Y-%m-%d").strftime(
            "%Y-%m-%d"
        )
        log_filename = f"EuroMillions_Log_{formatted_date}_{self.start_time.strftime('%Y%m%d_%H%M%S')}.csv"
        try:
            with open(log_filename, "w", encoding="utf-8") as log_file:  # Added encoding
                # Write the header
                log_file.write(f"Correlated Date: {formatted_date}\n")
                log_file.write("Correlated Time: 21:00-21:10 CET\n")
                log_file.write("Correlated Place: Paris, France\n")
                log_file.write(f"Test Start Time: {self.start_time}\n")
                log_file.write(f"Bit Source: {self.bit_source}\n\n")

                # Write column headers: First 50 are normal numbers, the last 12 are lucky numbers
                headers = (
                    ["Time (s)"]
                    + [f"Num {i+1}" for i in range(50)]
                    + [f"Lucky {i+1}" for i in range(12)]
                )
                log_file.write(", ".join(headers) + "\n")

                # Write the recorded data
                for i, timestamp in enumerate(self.timestamps):
                    row = (
                        [str(timestamp)]
                        + [str(self.sum_history[i][j]) for j in range(50)]
                        + [str(self.sum_history[i][50 + j]) for j in range(12)]
                    )
                    log_file.write(", ".join(row) + "\n")

                # Write Lowest, Highest, and Last for each number at the end of the log file
                log_file.write("\nLowest, Highest, Last Values:\n")
                for i in range(50):  # For normal numbers (1-50)
                    log_file.write(
                        f"Num {i+1}: {int(self.lowest[i])}, {int(self.highest[i])}, {int(self.last[i])}\n"
                    )
                for i in range(12):  # For lucky numbers (51-62)
                    log_file.write(
                        f"Lucky {i+1}: {int(self.lowest[50 + i])}, {int(self.highest[50 + i])}, {int(self.last[50 + i])}\n"
                    )

            print(f"Data exported saved successfully: {log_filename}")
        except IOError as e:
            print(f"Error exporting data: {e}")


# Main
if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("725x610")
    app = EuroMillionsTracker(root)
    root.mainloop()
Editor is loading...
Leave a Comment