use proper line rendering
There are some edge cases to Bresenham's line drawing algorithm with cumul=dx. The fx-92 SC+ clearly checks cumul>dx, but for dx=1 and dy=1 this results in a horizontal line. Apparently dx=dy is the only case where the fx-92 SC+ behaves differently than cumul>dx, as seen with dx=5 dy=4 (causes cumul=dx after 2 iterations but does not trigger the condition). Also allow the program to be interrupted with Escape while paused.
This commit is contained in:
parent
ab057abddd
commit
2c7287338c
2
ast.py
2
ast.py
|
@ -12,7 +12,7 @@ def auto_builder():
|
|||
|
||||
try:
|
||||
enum.auto()
|
||||
except AttributeError:
|
||||
except AttributeError:
|
||||
enum.auto = auto_builder()
|
||||
del auto_builder
|
||||
|
||||
|
|
50
drawing.py
50
drawing.py
|
@ -1,6 +1,10 @@
|
|||
# fx-92 Scientifique Collège+ language interpreter: On-screen drawing
|
||||
|
||||
from sdl2 import *
|
||||
import math
|
||||
|
||||
class InterruptException(Exception):
|
||||
pass
|
||||
|
||||
class Window:
|
||||
"""
|
||||
|
@ -72,13 +76,24 @@ class Window:
|
|||
|
||||
SDL_Quit()
|
||||
|
||||
def _point(self, x, y):
|
||||
# It's important to divide with //, otherwise when the width or height
|
||||
# is odd this transformation loses rounding information and can alter
|
||||
# the position of the final pixel.
|
||||
x = self.width // 2 + x - 1
|
||||
y = self.height // 2 - y
|
||||
|
||||
x = int(round(x))
|
||||
y = int(round(y))
|
||||
SDL_RenderDrawPoint(self.r, x, y)
|
||||
|
||||
def clear(self, color):
|
||||
"""Clear screen in a uniform color."""
|
||||
|
||||
SDL_SetRenderDrawColor(self.r, *color)
|
||||
SDL_RenderClear(self.r)
|
||||
|
||||
def line(self, x1, y1, x2, y2, color):
|
||||
def _line(self, x1, y1, x2, y2, color):
|
||||
"""Draw a straight line."""
|
||||
|
||||
SDL_SetRenderDrawColor(self.r, *color)
|
||||
|
@ -92,9 +107,9 @@ class Window:
|
|||
dx, dy = abs(dx), abs(dy)
|
||||
cumul = 0
|
||||
|
||||
SDL_RenderDrawPoint(self.r, x, y)
|
||||
self._point(x, y)
|
||||
|
||||
if dx >= dy:
|
||||
if dx > dy:
|
||||
cumul = dx // 2
|
||||
for i in range(dx):
|
||||
x += sx
|
||||
|
@ -102,7 +117,12 @@ class Window:
|
|||
if cumul > dx:
|
||||
cumul -= dx
|
||||
y += sy
|
||||
SDL_RenderDrawPoint(self.r, x, y)
|
||||
self._point(x, y)
|
||||
elif dx == dy:
|
||||
for i in range(dx):
|
||||
x += sx
|
||||
y += sy
|
||||
self._point(x, y)
|
||||
else:
|
||||
cumul = dy // 2
|
||||
for i in range(dy):
|
||||
|
@ -111,7 +131,25 @@ class Window:
|
|||
if cumul > dy:
|
||||
cumul -= dy
|
||||
x += sx
|
||||
SDL_RenderDrawPoint(self.r, x, y)
|
||||
self._point(x, y)
|
||||
|
||||
def line(self, x1, y1, x2, y2, color):
|
||||
x1, y1, x2, y2 = round(x1), round(y1), round(x2), round(y2)
|
||||
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
||||
self._line(x1, y1, x2, y2, color)
|
||||
|
||||
def linef(self, x, y, angle, dist, color):
|
||||
"""Draw a line forward."""
|
||||
|
||||
cos = math.cos(angle * math.pi / 180)
|
||||
sin = math.sin(angle * math.pi / 180)
|
||||
|
||||
x2 = x + dist * cos
|
||||
y2 = y + dist * sin
|
||||
|
||||
x1, y1, x2, y2 = round(x), round(y), round(x2), round(y2)
|
||||
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
||||
self._line(x1, y1, x2, y2, color)
|
||||
|
||||
def wait(self, allow_any=False):
|
||||
"""Wait for the window to be closed."""
|
||||
|
@ -125,6 +163,8 @@ class Window:
|
|||
|
||||
if event.type == SDL_QUIT:
|
||||
break
|
||||
if event.type == SDL_KEYDOWN and event.key.keysym.sym==SDLK_ESCAPE:
|
||||
raise InterruptException()
|
||||
if event.type == SDL_KEYDOWN and allow_any:
|
||||
break
|
||||
|
||||
|
|
14
fx92.py
14
fx92.py
|
@ -6,7 +6,7 @@ import getopt
|
|||
from parser import Parser
|
||||
from lexer import UrlLexer, TextLexer
|
||||
from printer import print_ast
|
||||
from drawing import Window
|
||||
from drawing import Window, InterruptException
|
||||
from interpreter import Context
|
||||
|
||||
#---
|
||||
|
@ -97,11 +97,13 @@ def main():
|
|||
|
||||
with Window(width=192, height=47, scale=scale, quiet=quiet) as w:
|
||||
ctx = Context(w)
|
||||
ctx.run(ast)
|
||||
|
||||
if out is not None:
|
||||
w.save(out)
|
||||
w.wait()
|
||||
try:
|
||||
ctx.run(ast)
|
||||
if out is not None:
|
||||
w.save(out)
|
||||
w.wait()
|
||||
except InterruptException:
|
||||
print("[interpreter] Interrupted.", file=sys.stderr)
|
||||
|
||||
return 0
|
||||
|
||||
|
|
|
@ -20,17 +20,17 @@ class Context:
|
|||
|
||||
self.x = float(0)
|
||||
self.y = float(0)
|
||||
self.theta = float(0)
|
||||
self.pen = False
|
||||
|
||||
self.angle = float(0)
|
||||
self.sin = float(0)
|
||||
self.cos = float(1)
|
||||
|
||||
# TODO: Also specify the style!
|
||||
|
||||
self.w.clear(self.w.WHITE)
|
||||
self.w.update()
|
||||
|
||||
def settheta(self, newtheta):
|
||||
self.theta = newtheta % 360
|
||||
|
||||
def run(self, n):
|
||||
"""
|
||||
Execute a full program tree. For expression nodes, returns a value; for
|
||||
|
@ -47,51 +47,27 @@ class Context:
|
|||
|
||||
elif n.type == N.FORWARD:
|
||||
dist, = self.runs(n)
|
||||
dx = self.cos * dist
|
||||
dy = self.sin * dist
|
||||
|
||||
if self.pen:
|
||||
coord = self.x, self.y, self.x + dx, self.y + dy
|
||||
coord = [int(round(x)) for x in coord]
|
||||
x1, y1, x2, y2 = coord
|
||||
|
||||
x1 += self.w.width // 2
|
||||
x2 += self.w.width // 2
|
||||
y1 = self.w.height // 2 - y1
|
||||
y2 = self.w.height // 2 - y2
|
||||
|
||||
self.w.line(x1, y1, x2, y2, self.w.BLACK)
|
||||
self.w.linef(self.x, self.y, self.theta, dist, self.w.BLACK)
|
||||
self.w.update()
|
||||
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
self.x += dist * math.cos(self.theta * math.pi / 180)
|
||||
self.y += dist * math.sin(self.theta * math.pi / 180)
|
||||
|
||||
elif n.type == N.ROTATE:
|
||||
angle, = self.runs(n)
|
||||
self.angle += angle
|
||||
self.sin = math.sin(math.pi * self.angle / 180)
|
||||
self.cos = math.cos(math.pi * self.angle / 180)
|
||||
self.settheta(self.theta + angle)
|
||||
|
||||
elif n.type == N.ORIENT:
|
||||
angle, = self.runs(n)
|
||||
self.angle = angle
|
||||
self.sin = math.sin(math.pi * self.angle / 180)
|
||||
self.cos = math.cos(math.pi * self.angle / 180)
|
||||
self.settheta(angle)
|
||||
|
||||
elif n.type == N.GOTO:
|
||||
x, y = self.runs(n)
|
||||
|
||||
if self.pen:
|
||||
coord = self.x, self.y, x, y
|
||||
coord = [int(round(x)) for x in coord]
|
||||
x1, y1, x2, y2 = coord
|
||||
|
||||
x1 += self.w.width // 2
|
||||
x2 += self.w.width // 2
|
||||
y1 = self.w.height // 2 - y1
|
||||
y2 = self.w.height // 2 - y2
|
||||
|
||||
self.w.line(x1, y1, x2, y2, self.w.BLACK)
|
||||
self.w.line(self.x, self.y, x, y, self.w.BLACK)
|
||||
self.w.update()
|
||||
|
||||
self.x = x
|
||||
|
@ -106,7 +82,14 @@ class Context:
|
|||
val = self.run(n.args[0])
|
||||
var = n.args[1]
|
||||
|
||||
self.vars[var] = val
|
||||
if var == "x":
|
||||
self.x = val
|
||||
elif var == "y":
|
||||
self.y = val
|
||||
elif var == "theta":
|
||||
self.theta = val
|
||||
else:
|
||||
self.vars[var] = val
|
||||
|
||||
elif n.type == N.INPUT:
|
||||
# TODO: Input stmt should call into the lexer+parser (urg)
|
||||
|
@ -122,11 +105,11 @@ class Context:
|
|||
|
||||
elif n.type == N.WAIT:
|
||||
print("---- pause")
|
||||
print(f"x={self.x} y={self.y} theta={self.angle}")
|
||||
print(f"A={self.vars['A']} B={self.vars['B']}")
|
||||
print(f"C={self.vars['C']} D={self.vars['D']}")
|
||||
print(f"E={self.vars['E']} F={self.vars['F']}")
|
||||
print(f"M={self.vars['M']}")
|
||||
print("x={} y={} theta={}".format(self.x, self.y, self.theta))
|
||||
print("A={} B={}".format(self.vars['A'], self.vars['B']))
|
||||
print("C={} D={}".format(self.vars['C'], self.vars['D']))
|
||||
print("E={} F={}".format(self.vars['E'], self.vars['F']))
|
||||
print("M={}".format(self.vars['M']))
|
||||
self.w.pause()
|
||||
|
||||
# Flow control
|
||||
|
@ -181,6 +164,8 @@ class Context:
|
|||
return self.x
|
||||
if var == "y":
|
||||
return self.y
|
||||
if var == "theta":
|
||||
return self.theta
|
||||
return self.vars[var]
|
||||
|
||||
elif n.type == N.CONST:
|
||||
|
@ -217,7 +202,8 @@ class Context:
|
|||
}[n.args[0]]
|
||||
|
||||
if f is None:
|
||||
raise Exception("Function {} not there yet x_x".format(n.args[0]))
|
||||
raise Exception(
|
||||
"Function {} not there yet x_x".format(n.args[0]))
|
||||
return f(arg)
|
||||
|
||||
elif n.type == N.REL:
|
||||
|
|
8
lexer.py
8
lexer.py
|
@ -396,6 +396,14 @@ class TextLexer(LexerBase):
|
|||
err = s[0]
|
||||
self.code = s[1] if len(s) > 1 else ""
|
||||
|
||||
# Comments
|
||||
if c[0] == "#":
|
||||
splits = c.split('\n', maxsplit=1)
|
||||
print(splits)
|
||||
self.code = c[len(splits[0]):]
|
||||
self.position -= 1
|
||||
return self.lex()
|
||||
|
||||
raise Exception("Lexical error near '{}'".format(err))
|
||||
|
||||
def at_end(self):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
goto -60, 12 ; pendown ; goto x+13, y+15 ; penup
|
||||
goto -60, 9 ; pendown ; goto x+13, y+13 ; penup
|
||||
goto -60, 6 ; pendown ; goto x+13, y+8 ; penup
|
||||
goto -60, 3 ; pendown ; goto x+13, y+5 ; penup
|
||||
|
@ -7,12 +6,11 @@ goto -60, 0 ; pendown ; goto x+13, y ; penup
|
|||
goto -47, -8 ; pendown ; goto x-13, y+5 ; penup
|
||||
goto -47, -14 ; pendown ; goto x-13, y+8 ; penup
|
||||
goto -47, -22 ; pendown ; goto x-13, y+13 ; penup
|
||||
goto -47, -27 ; pendown ; goto x-13, y+15 ; penup
|
||||
|
||||
setvar 0, A
|
||||
|
||||
repeat 6
|
||||
goto -30,16-7A
|
||||
goto -35,16-7A
|
||||
pendown
|
||||
goto x+3A+3,y+5
|
||||
penup
|
||||
|
@ -22,3 +20,19 @@ repeat 6
|
|||
penup
|
||||
setvar A+1, A
|
||||
repeat_end
|
||||
|
||||
setvar 0, B
|
||||
|
||||
repeat 5
|
||||
setvar 0, A
|
||||
|
||||
repeat 4
|
||||
goto 3A, 24-30B
|
||||
goto x+B,y+B
|
||||
pendown
|
||||
goto x+A,y+A
|
||||
penup
|
||||
setvar A+1, A
|
||||
repeat_end
|
||||
setvar B+0.2,B
|
||||
repeat_end
|
||||
|
|
Loading…
Reference in New Issue