You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Tkinter台球模拟中按Spinbox选定角度击球?

Tkinter台球模拟程序:让白球沿选定角度运动的修复方案

问题描述

我编写了一个基于Tkinter的台球模拟程序,期望点击“Kresli”按钮后,白球能够沿Spinbox选定的角度(箭头已对应瞄准该角度)运动。尝试使用Spinbox的get()方法但无效,附上代码寻求解决。

核心问题分析

原代码存在几个关键问题导致功能失效:

  • __init__方法中self.mainloop()之后的self.posun_lopty()和self.rotate()永远不会执行,因为mainloop是阻塞的,程序会停在这里直到窗口关闭。
  • 白球的移动增量dx和dy被固定为(5, 0),从未根据Spinbox选择的角度更新,所以无论选什么角度,白球只会水平向右移动。
  • 多次点击“Kresli”按钮会重复启动posun_lopty的循环,导致白球加速甚至异常。
  • 碰撞检测使用==判断位置,存在精度问题,可能导致碰撞逻辑失效。

修改后的完整代码

import tkinter
import math
from tkinter import ttk

class Simulacia(tkinter.Tk):
    def __init__(self):
        '''Nastavenie počiatočných vlastnosti a spúšťanie ďalších metód triedy'''
        super().__init__()
        self.title('Rovnomerný priamočiary pohyb')
        self.is_moving = False  # 控制运动状态,防止重复启动
        self.vytvorGUI()
        self.rotate()  # 初始化箭头角度
        self.mainloop()

    def vytvorGUI(self):
        '''Vytvorenie grafického rozhrania programu (GUI)'''
        self.uhol = tkinter.IntVar()
        tkinter.Button(self, text='Kresli', command=self.start_movement).grid(row=1, column=0)
        tkinter.Button(self, text='Ukonči', command=self.destroy).grid(row=1, column=3)
        tkinter.Button(self, text='Vymaž pozadie', command=self.reset_canvas).grid(row=2, column=3)
        tkinter.Label(self, text="Uhol=", width=10).grid(row=2, column=0)
        
        self.platno = tkinter.Canvas(self, width=800, height=600, background='green')
        self.x = 150
        self.y = 300
        self.lopta = self.platno.create_oval(self.x, self.y, self.x+20, self.y+20, fill="white")
        self.sipka = self.platno.create_line(160, 310, 280, 310, tags=("minute",), arrow="last")
        self.radius = 30
        
        # 绘制台球桌边框和球洞
        self.platno.create_rectangle(3, 3, 800, 600, outline="brown", width=10)
        self.platno.create_oval(-self.radius, -self.radius, +self.radius, +self.radius, fill="black")  # VLAVO HORe
        self.platno.create_oval(-self.radius, 600 - self.radius, +self.radius, 600 + self.radius, fill="black")  # VLAVO DOLE
        self.platno.create_oval(800 - self.radius, 0 - self.radius, 800 + self.radius, 0 + self.radius, fill="black")  # Vpravo HORE
        self.platno.create_oval(800 - self.radius, 600 - self.radius, 800 + self.radius, 600 + self.radius, fill="black")  # Vpravo Dole
        self.platno.create_oval(400 - self.radius, -self.radius, 400 + self.radius, self.radius, fill="black")  # Hore
        self.platno.create_oval(400 - self.radius, 600 - self.radius, 400 + self.radius, 600 + self.radius, fill="black")  # Dole
        
        self.sb_minute = ttk.Spinbox(self, from_=0, to=60, increment=1, width=4, command=self.rotate)
        self.sb_minute.grid(row=2, column=1)
        self.sb_minute.set(15)
        self.platno.grid(row=3, column=0, columnspan=4)
        
        # 初始化速度参数
        self.speed = 5  # 白球移动速度
        self.dx = self.speed
        self.dy = 0

    def rotate(self):
        self.size = 300
        self.m = int(self.sb_minute.get())
        self.degrees = self.m * 6  # 每分钟对应6度(360/60)
        self.angle = math.radians(self.degrees)  # 转换为弧度
        self.cx = 160
        self.cy = 310
        # 计算箭头终点坐标
        self.x0 = self.cx + self.size * math.sin(self.angle) * 0.40
        self.y1 = self.cy - self.size * math.cos(self.angle) * 0.40
        self.platno.coords('minute', (self.cx, self.cy, self.x0, self.y1))

    def start_movement(self):
        # 如果已经在运动则不重复启动
        if self.is_moving:
            return
        # 根据当前角度更新dx和dy
        current_angle = math.radians(int(self.sb_minute.get()) * 6)
        self.dx = self.speed * math.sin(current_angle)
        self.dy = -self.speed * math.cos(current_angle)  # Tkinter y轴向下为正,取负对应数学坐标系向上
        self.is_moving = True
        self.posun_lopty()

    def posun_lopty(self):
        if not self.is_moving:
            return
        
        self.x += self.dx
        self.y += self.dy
        self.platno.move(self.lopta, self.dx, self.dy)
        
        # 边界碰撞检测(优化为范围判断,避免==的精度问题)
        # 上下边界(白球直径20,所以y范围限制在0到580)
        if self.y <= 0 or self.y >= 580:
            self.dy = -self.dy
        # 左右边界(x范围限制在0到780)
        if self.x <= 0 or self.x >= 780:
            self.dx = -self.dx
        
        # 球洞检测
        text_lopticka = "Loptička spadla do diery. Hra skončila"
        hole_detected = False
        # 左上洞
        if self.x <= self.radius and self.y <= self.radius:
            hole_detected = True
        # 左下洞
        elif self.x <= self.radius and self.y >= 600 - self.radius:
            hole_detected = True
        # 右上洞
        elif self.x >= 800 - self.radius and self.y <= self.radius:
            hole_detected = True
        # 右下洞
        elif self.x >= 800 - self.radius and self.y >= 600 - self.radius:
            hole_detected = True
        # 上中洞
        elif abs(self.x - 400) <= self.radius and self.y <= self.radius:
            hole_detected = True
        # 下中洞
        elif abs(self.x - 400) <= self.radius and self.y >= 600 - self.radius:
            hole_detected = True
        
        if hole_detected:
            self.is_moving = False
            self.platno.create_text(400, 300, text=text_lopticka, fill="black", font=('Helvetica 15 bold'))
            self.platno.delete(self.lopta)
            self.platno.create_oval(400 - self.radius + 10, 600 - self.radius + 10, 
                                   400 + self.radius - 10, 600 + self.radius - 10, fill="white")
            return
        
        self.platno.after(10, self.posun_lopty)

    def reset_canvas(self):
        # 重置画布和白球位置
        self.is_moving = False
        self.platno.delete("all")
        self.x = 150
        self.y = 300
        self.lopta = self.platno.create_oval(self.x, self.y, self.x+20, self.y+20, fill="white")
        self.sipka = self.platno.create_line(160, 310, 280, 310, tags=("minute",), arrow="last")
        # 重新绘制边框和球洞
        self.platno.create_rectangle(3, 3, 800, 600, outline="brown", width=10)
        self.platno.create_oval(-self.radius, -self.radius, +self.radius, +self.radius, fill="black")
        self.platno.create_oval(-self.radius, 600 - self.radius, +self.radius, 600 + self.radius, fill="black")
        self.platno.create_oval(800 - self.radius, 0 - self.radius, 800 + self.radius, 0 + self.radius, fill="black")
        self.platno.create_oval(800 - self.radius, 600 - self.radius, 800 + self.radius, 600 + self.radius, fill="black")
        self.platno.create_oval(400 - self.radius, -self.radius, 400 + self.radius, self.radius, fill="black")
        self.platno.create_oval(400 - self.radius, 600 - self.radius, 400 + self.radius, 600 + self.radius, fill="black")
        self.rotate()  # 重置箭头角度

Simulacia()

关键修改说明

  1. 修复代码执行顺序:将__init__中self.mainloop()之后的无效代码移到mainloop之前,确保初始化逻辑正常执行。
  2. 新增运动状态控制:添加self.is_moving标志,防止多次点击“Kresli”导致重复启动移动循环,避免白球加速异常。
  3. 角度驱动速度计算:新增start_movement方法,点击按钮时根据Spinbox的角度实时计算dx和dy,确保白球沿箭头方向运动。
  4. 优化碰撞与检测逻辑:将边界碰撞的==判断改为范围判断,解决精度问题;合并球洞检测逻辑,避免重复创建提示文本。
  5. 修复画布重置功能:替换“Vymaž pozadie”按钮的绑定方法,正确重置画布元素,而不是重复创建GUI控件。

内容的提问来源于stack exchange,提问作者Dominik Spišák

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 23:41:32