最近,我正在尝试使用 Python 和 Pygame 库创建一个全功能的绘图计算器。我创建了一个可以在大多数函数上正常工作的绘图计算器。它可以将用户输入的前缀表达式转换为后缀表达式以便进行更简单的计算。然后,我循环遍历后缀表达式并传入 x 值以获取要用 Pygame 绘图的 Y 值。
然而,我遇到了一个问题,即当计算不可能的事情时会出现异常情况(例如除以零、平方根为负数、0 的非正数次方)。如果发生这种情况,我会输出 None,并且该像素不会添加到要绘制的点的列表中。
我尝试了几种方法来解决这个问题:
- 方法 1:使用 Pygame 的 aalines 函数绘制线条
这种方法在没有缺失点的情况下可以正常工作,但它不会处理缺失点的情况。这导致绘制的线条不连续。
- 方法 1.1:将线条拆分成两部分
当一个 None 被打印出来时,我将线条拆分成两部分。这种方法对 1/x 有效,但它只适用于传入的 X 值之一恰好落在 Y 值为 None 的点上。
- 方法 2:将像素 x 值转换为相应的 x 点值
我将每个像素 x 值转换为窗口中的相应 x 点值。然后,我使用这些 x 值计算每个 y 值。这种方法适用于斜率大于 1 或小于 -1 的任何线条。
- 当前方法:改进版方法 2
我改进了一下方法 2。我计算每个像素左、右侧的 x 值,并将这两个值代入表达式以获取两个 Y 值。然后,我循环遍历该列上的每个 y 值,并检查当前值是否介于先前计算的两个 Y 值之间。
这种方法解决了方法 2 中像素未连接成连续线条的问题。但是,它没有解决方法 1 的问题。当绘制 1/x 时,它看起来与 aalines 方法完全相同。
2、解决方案
最终,我找到了一种解决方案,就是使用大量 x 值。我将显示窗口的宽度和高度除以 2,得到 x 值的数量。然后,我循环遍历这些 x 值,并使用它们来计算 y 值。这种方法可以解决所有问题,并且速度足够快。
以下是使用这种方法的代码示例:
import pygame
import math
# 初始化 Pygame
pygame.init()
# 设置窗口大小
size = (500, 500)
screen = pygame.display.set_mode(size)
# 设置绘图窗口的 x 和 y 轴最小值和最大值
xWin = [-10, 10]
yWin = [-10, 10]
# 将像素值转换为绘图窗口中的值
def pixelToPoint(pixel, size, win, isY):
if isY:
return win[1] - (pixel / size) * (win[1] - win[0])
else:
return win[0] + (pixel / size) * (win[1] - win[0])
# 计算后缀表达式的值
def calcPostfix(postfix, x):
stack = []
for token in postfix:
if token not in ["+", "-", "*", "/", "^"]:
stack.append(float(token))
else:
op2 = stack.pop()
op1 = stack.pop()
if token == "+":
result = op1 + op2
elif token == "-":
result = op1 - op2
elif token == "*":
result = op1 * op2
elif token == "/":
if op2 == 0:
return None
result = op1 / op2
elif token == "^":
result = math.pow(op1, op2)
stack.append(result)
return stack[0]
# 获取要绘制的像素列表
pixels = []
for x in range(size[0]):
leftX = pixelToPoint(x, size[0] + 1, xWin, False)
rightX = pixelToPoint(x + 1, size[0] + 1, xWin, False)
leftY = calcPostfix(postfix, leftX)
rightY = calcPostfix(postfix, rightX)
for y in range(size[1]):
if leftY != None and rightY != None:
yPoint = pixelToPoint(y, size[1], yWin, True)
if (rightY <= yPoint <= leftY) or (rightY >= yPoint >= leftY):
pixels.append((x, y))
# 绘制像素
for p in pixels:
screen.fill((0, 0, 0), (p, (1, 1)))
# 更新屏幕
pygame.display.update()
# 等待用户退出
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()