# Simple terminal graphics line drawing

The program starts at the center of the screen and starts drawing a line in 50ms steps. When the line hits the edge of the terminal it will be reflected and continue drawing. steps takes the number of drawing steps to perform before finishing while until-key finishes on a key press.

Apart from the lack of comments, what would I need to do to make this "good" Forth and how would I change it to fit in one screen (16 lines of 64 chars)?

variable h
variable w
: height h @ ;
: width  w @ ;

variable point-var
cell allot
: point ( offset -- addr ) cells point-var + ;

variable direction-var
cell allot
: dir ( offset -- addr ) cells direction-var + ;

: init ( -- )
rows h !
cols w !
width  2 / 0 point !
height 2 / 1 point !
1 0 dir !
1 1 dir ! ;

: reflect-top ( -- t/f )
1 point @ 0= dup if 1 1 dir ! then invert ;
: reflect-bot ( -- t/f )
1 point @ height 1 - = if -1 1 dir ! then ;
: reflect-left ( -- t/f )
0 point @ width 1 - = if -1 0 dir ! then ;
: reflect-right ( -- t/f )
0 point @ 0= dup if 1 0 dir ! then invert ;
: reflect ( -- )
reflect-top if reflect-bot then
reflect-right if reflect-left then ;

: draw-point ( -- ) [char] # emit ;

: move-x ( -- n ) 0 point @ 0 dir @ + dup 0 point ! ;
: move-y ( -- n ) 1 point @ 1 dir @ + dup 1 point ! ;
: move-xy ( -- ) move-x move-y at-xy ;

: try ;

: step ( -- )
try reflect
move-xy draw-point ;

: steps ( n -- )
init
page
0 do step 50 ms loop ;

: until-key ( n -- )
init
page
begin step 50 ms key? until ;

• Good material for the review. However some parts seem missing (at-xy, invert). – vnp Apr 5 '16 at 0:10
• invert (used as logical not) and at-xy are provided by gforth in the base dictionary. at-xy ( posX posY -- ) just prints the "\x1b[y;xH" escape sequence to move the cursor. – SANK Apr 5 '16 at 0:21
• I have rolled back Rev 5 → 4. Please see What to do when someone answers. You may wish to post it as a self-answer instead. – 200_success Apr 6 '16 at 20:58

• Magic numbers

    0 constant X
1 constant Y


looks more attractive than naked 0 and 1. I suppose (sorry can't test it right now) it may let you unify move-x with move-y, as well as reflects.

• Height and width

    : height= h @ 1 - = ;
: width=  w @ 1 - = ;


allow more symmetric definition of reflects:

    : reflect-top Y point @ 0=      dup  if  1 1 dir ! then invert ;
: reflect-bot Y point @ height=      if -1 1 dir ! then ;


Now it looks like they have different stack effects, which is strange. Are you sure reflect-bot doesn't miss dup?

• move-*

I'd rearrange pushes, e.g.

    : move-x 0 0 0 point @ dir @ + dup point ! ;
: move-y 1 1 1 point @ dir @ + dup point ! ;


which really prompts for a point @ dir @ + dup point ! word. move-in-dir perhaps?

• One screen

Do modern Forthers still care about screens?

• Ah reflect-bot should have ( -- ) as the diagram. I missed that when fixing a problem I had with it earlier. Also I wanted to see how it would look if it was done in one screen (currently trying) to help avoid variables as they are a lot slower than pure stack operations. – SANK Apr 5 '16 at 4:17
• @SANK Yes variables are generally frowned upon. I am still trying to see a way to avoid them here. – vnp Apr 5 '16 at 4:34
• wouldn't the pushes in move-* will cause undefined behaviour (segfault) if rearranged as dir would be using the value from point @ instead of x/y? – SANK Apr 5 '16 at 4:43
• @SANK I don't think so. My suggestion is a literal rewrite (as far as I understand); it shown;t change the behaviour. – vnp Apr 5 '16 at 4:49
• Since both point and dir consume a stack item, 0 0 point @ will leave the stack with [point value, 0] which dir then consumes causing a segfault when point @ returns greater than 1. 0 0 0 point @ swap dir @ + dup rot point ! works as each must consume a 0 from the stack (currently testing it in gforth). – SANK Apr 5 '16 at 5:01

Almost complete solution to the screen improvement with the bulk taking 15 lines.

• All variables have been removed along with most of the words to define reflect.
• The position, direction and bounds are now stored in 6 cells allocated on the heap.
• "Half" words only used to define others are now replaced by the words they are used to define.

There should also be a slight speedup due to avoiding variables and lower word count per definition.

: bound ( a -- a ) 4 cells + ;
: dir ( a -- a ) 2 cells + ;
: init ( n n a -- a ) rot tuck ! cell + tuck ! cell + ;
: init ( -- a )
6 cells allocate throw dup
rows 2 / cols 2 / init 1 1 init rows 1-  cols 1- init drop ;
: reflect ( a -- )
dup @ 0= if 1 swap dir ! else dup dup bound @ swap @ =
if -1 swap dir ! else drop then then ;
: reflect ( a -- ) dup cell + reflect reflect ;
: move-xy ( a -- )
dup @ over dir @ + over ! cell + dup @ over dir @ + swap ! ;
: step ( a -- )
dup dup dup reflect cell + @ swap @ swap at-xy
move-xy [char] # emit ;

: steps ( n -- )
page init swap 0 do dup step 10 ms loop
dup 6 erase free throw ;

: until-key ( -- )
page init begin dup step key? 10 ms until
dup 6 erase free throw ;