Tetris/Julia: Difference between revisions

From Rosetta Code
Content added Content deleted
Line 6: Line 6:
{{works with|Julia|1.0}}
{{works with|Julia|1.0}}


<lang julia>struct B4 # Tetromino made of 4 Blocks
<lang julia>using Random
struct B4
bg::Int # ANSI code of tetromino color (bground of " ")
bg::Int # ANSI code of tetromino color (bground of " ")
d::Array{Int,2} # d[b,1:2] = row,col/2 offset of block b
d::Array{Int,2} # d[b,1:2] = row,col/2 offset of block b
# constructor: Z = B4(101,[0 0; 0 1; 1 1; 1 2])
# constructor: set centered offsets of blocks
function B4(bg,A::Array{Int,2}) # - fills in centered offsets for tetromino
B4(bg,A::Array{Int,2}) = new(bg,A.-sum.(extrema(A,dims=1)).>>1)
new(bg,A.-sum.(extrema(A,dims=1)).>>1)
end end
# print tetromino on playfield
function draw(b,r,c,clr=-1)
print("\e[$(clr & b.bg)m") # set color. (0 = default colors)
for i=1:4 print("\e[$(r+b.d[i,1]);$(c+2b.d[i,2])H ") end
end
end
# hit other B4s or border?
# in loops ITERATE over the blocks in B4
Base.iterate(B::B4,i=1) = i<5 ? (B.d[i,:],i+1) : nothing
function hit(B,r,c)
# PRINT/CLEAR tetromino on terminal
for i=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]>0 && return true end; false
drw(B,r,c)=(for(i,j)=B print("\e[$(B.bg)m\e[$(r+i);$(c+2j)H ") end)
end
clr(B,r,c)=(for(i,j)=B print("\e[0m\e[$(r+i);$(c+2j)H ") end)
# move tetrominos left/right/down, if no hit
# HIT other B4s or border?
function mov(B,r,c,x,y)
hit(B,r+x,c+y) || (draw(B,r,c,0);draw(B,r+=x,c+=y))
hit(B,r,c)=(for(i,j)=B try BD[r+i,c>>1+j]>0 catch;true end&& return true end;false)
TMv || (global tmr = Timer(tm)); return (r,c)
function mov(B,r,c,x,y) # MOVE tetrominos left/right/down, if no hit
hit(B,r+x,c+y) || (clr(B,r,c); drw(B,r+=x,c+=y))
TMv || (global tmr=Timer(tm)); return (r,c)
end
end

# rotate tetrominos left/right, if no hit
function rot(B,r,c,rt=0)
function rot(B,r,c,rt=0) # ROTATE tetrominos left/right, if no hit
TMv || (global tmr=Timer(tm))
A = deepcopy(B)
for i=1:4 A.d[i,:]=rt>0 ? [A.d[i,2],3-A.d[i,1]] : [3-A.d[i,2],A.d[i,1]] end
A = B4(B.bg, rt>0 ? [B.d[:,2] (3 .-B.d[:,1])] : [(3 .-B.d[:,2]) B.d[:,1]])
A.d .-= sum.(extrema(A.d,dims=1)).>>1 # centralize
TMv || (global tmr = Timer(tm))
for j = c.+(0,+2,-2) # try shifted positions
for j = c.+(0,+2,-2) # try shifted positions
hit(A,r,j) || (draw(B,r,c,0); draw(A,r,j); return (A,j))
hit(A,r,j) || (clr(B,r,c); drw(A,r,j); return (A,j))
end; return (B,c) # cannot rotate: all 3 positions hit
end; return (B,c) # cannot rotate: all 3 positions hit
end
end

# record-place,clear-full-lines,drop-above,score
function mrk(B,r,c) # RECORD place,CLEAR full-lines,DROP-above,score
function mark(B,r,c)
global lines, score, level; ln = 0
global lines,score,level; n,l = 0,0
for i=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]=B.bg end # record stuck B4
for(i,j)=B BD[r+i,c>>1+j]=B.bg end # record stuck B4
for i in r.+sort(unique(B.d[:,1])) # in board: empty full rows; drop part above
for i in r.+sort(unique(B.d[:,1])) # empty full rows in board; drop all above
if all(BD[i,3:12].>0) ln += 1
if all(BD[i,:].>0) n += 1; l = i # l = lowest line changed
for j = i-1:-1:1 BD[j+1,3:12] = BD[j,3:12] end
for j=i-1:-1:1 BD[j+1,:]=BD[j,:] end
print("\e[0m\e[$i;2H$s20"); sleep(0.3)
print("\e[0m\e[$i;2H$s20"); sleep(0.3)
end end # update display from board data ->
end end # update changed display from board data ->
for i=1:22,j=3:12 print("\e[$(BD[i,j])m\e[$i;$(2j-4)H ") end
for i=1:l,j=1:10 print("\e[$(BD[i,j])m\e[$i;$(2j)H ") end
ln > 0 && (lines+=ln; score += (level+1)*(100,300,500,800)[ln])
score+=(level+1)*(0,100,300,500,800)[n+1]
level=isqrt(lines>>3) # updates lines, score, level
lines+=n; level=isqrt(lines>>3) # update lines, score, level
end
end

# function for dialog confirmation
function conf(t) # t: prompt, w/o new line!
function cnf(p) # CONFIRMATION dialog. p = 1-line prompt
print("$t - Enter confirms, other keys ignore")
print("$p - Enter confirms, other keys ignore")
t = take!(chnl); println("\e[2K"); t==[0xd]
t = take!(chnl); println("\e[2K"); t==[0xd]
end
end
# show basic usage- and status info
# SHOW basic usage- and status info
info()=print("\e[0m\e[26H\e[2K i: Info, l: Locked drop ($LDr), t: Timed move ($TMv)")
inf()=print("\e[0m\e[26H\e[2K i: Info, l: Locked drop=$LDr, t: Timed move=$TMv")


# Setup nonblocking, non-echoed keyboard input
# SETUP nonblocking, non-echoed keyboard INPUT
ccall(:jl_tty_set_mode,Cint,(Ptr{Cvoid},Cint),stdin.handle,1)==0 ||
ccall(:jl_tty_set_mode,Cint,(Ptr{Cvoid},Cint),stdin.handle,1)==0 ||
throw("Terminal cannot enter raw mode.") # raw terminal mode to catch keystrokes
throw("Terminal cannot enter raw mode.") # raw terminal mode to catch keystrokes
const chnl = Channel{Array{UInt8,1}}(0) # unbuffered channel for key codes
const chnl = Channel{Array{UInt8,1}}(0) # unbuffered channel for key codes
@async while true put!(chnl,readavailable(stdin)) end # task catching keystrokes
@async while true put!(chnl,readavailable(stdin)) end # task, catching keystrokes


I = B4(106,[0 0; 0 1; 0 2; 0 3]) # define the 7 tetrominos
I = B4(106,[0 0; 0 1; 0 2; 0 3]) # define the 7 tetrominos
Line 72: Line 66:


begin @label RESTART # @label - @goto: require begin..end
begin @label RESTART # @label - @goto: require begin..end
global lines,score,level,s20 = 0,0,0," "^20
global lines,score,level,s20,eq = 0,0,0," "^20,"="^6
BD=fill(0,24,15); for j=3:12 BD[23,j]=BD[24,j]=1 end
BD = fill(0,22,10) # empty BOARD. Screen_Col = 2*Board_Col
for i=1:24,j=∪(1,2,13:15) BD[i,j]=1 end # Board border. screen_C = 2j-4


print("\e[0m\e[2J\e[?25l") # default colors/clear screen/hide cursor
print("\e[0m\e[2J\e[?25l\e[1H") # Set default colors/clear screen/hide cursor
for i=1:22 print("\e[$i;H▐$(s20)▌") end # BORDER ->
print("$(s20)▌\n"^22*"▝$("▀"^20)▘\n\n$s20\n$s20") # screen BORDER, Cols=2:2:20
print("\e[23H▝$("▀"^20)▘") # ROWS=1:22, COLS=2:2:20, Center=10,11
print("\n\n$s20\n$s20"); X0 = (I,T,O,S,Z,J,L)[rand(1:7)]


X0 = (I,T,O,S,Z,J,L)[rand(1:7)]
while true # random B4, timer to drop, act on keystrokes
while true # random B4, timed drop, act on keystrokes
global lines,score,level,tm,tmr,LDr,TMv,Ifo
global lines,score,level,tm,tmr,LDr,TMv,Ifo
draw(X0,24,10,0)
global X0,X,r,c = (I,T,O,S,Z,J,L)[rand(1:7)],X0,2,10
global X0,X,r,c = (I,T,O,S,Z,J,L)[rand(1:7)],X0,2,10
draw(X0,24,10); info()
clr(X,24,10); drw(X0,24,10); inf() # show next piece, information
print("\n Level =\t$level\n Lines filled =\t$lines\n Score =\t$score")
print("\n Level =\t$level\n Lines filled =\t$lines\n Score =\t$score")
tm=(.8-level*.007)^level; tmr=Timer(0.5) # fixed 0.5s first delay
tm=(.8-level*.007)^level; tmr=Timer(0.5) # 0.5s initial delay
hit(X,r,c) && (while true conf("\e[31H Game Over/RESTART")&&@goto RESTART end)
hit(X,r,c) && (while true cnf("\e[31H Game Over: RESTART")&&@goto RESTART end)
while isready(chnl) take!(chnl) end # flush queued keystrokes
while isready(chnl) take!(chnl) end # flush queued keystrokes, max 1
while true
while true
global X,r,c,tm,tmr
global X,r,c,tm,tmr
if !isopen(tmr) # time to drop tetromino by a line
if !isopen(tmr) # time to drop tetromino by a line
hit(X,r+1,c) && (mark(X,r,c); break)
hit(X,r+1,c) && (mrk(X,r,c); break)
draw(X,r,c,0); draw(X,r+=1,c); tmr=Timer(tm)
clr(X,r,c); drw(X,r+=1,c); tmr=Timer(tm)
end
end
while isready(chnl) # are there queued keystrokes?
if isready(chnl) # if there is a queued keystroke
global X,r,c,score
global X,r,c,score
ch = take!(chnl) # take keys
ch = take!(chnl) # take keys
Line 105: Line 96:
elseif ch==[0x61] X,c=rot(X,r,c) # a
elseif ch==[0x61] X,c=rot(X,r,c) # a
elseif ch==[0x64] X,c=rot(X,r,c,1) # d
elseif ch==[0x64] X,c=rot(X,r,c,1) # d
elseif ch==[0x20] r0=r;draw(X,r,c,0);tmr=Timer(tm*!LDr) # SPACE
elseif ch==[0x20] r0=r; clr(X,r,c); tmr=Timer(tm*!LDr) # SPACE
while !hit(X,r+=1,c) end; draw(X,r-=1,c);score+=2r-2r0; continue
while !hit(X,r+=1,c) end; drw(X,r-=1,c); score+=2r-2r0; continue
elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME
elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME
conf("\e[0m\e[31H RESTART") && @goto RESTART
cnf("\e[0m\e[31H RESTART") && @goto RESTART
elseif ch==[0x1b,0x5b,0x34,0x7e] # END
elseif ch==[0x1b,0x5b,0x34,0x7e] # END
conf("\e[0m\e[31H EXIT") && return
cnf("\e[0m\e[31H EXIT") && return
elseif ch == [0x6c] LDr=!LDr; info() # l: Locked drop
elseif ch==[0x6c] LDr=!LDr; inf() # l: Locked drop
elseif ch == [0x74] TMv=!TMv; info() # t: Timed move
elseif ch==[0x74] TMv=!TMv; inf() # t: Timed move
elseif ch == [0x69] # i: Info
elseif ch==[0x69] if (Ifo=!Ifo) open(@__FILE__) do f # i: long-Info
if (Ifo = !Ifo) io = open(Base.source_path()); readuntil(io,"="^6)
readuntil(f,eq); println("\e[0m\e[32H$(readuntil(f,eq))") end
println("\e[0m\e[32H$(readuntil(io,"="^6))"); close(io)
else print("\e[0m\e[32H\e[J") # erase txt
else print("\e[0m\e[32H\e[J") # erase txt
end end end
end end end
sleep(0.01) # not to take all CPU core time
sleep(0.01) # not to take all CPU time
end end end</lang>
end end end</lang>


The following long comment block has to be appended to program code:
The following long comment block has to be appended to program code:


<pre>#====== Julia-TETRIS by Laszlo Hars, Version 1.1 10/21/2018
<pre>#====== Julia-TETRIS by Laszlo Hars, Version 1.2 10/31/2018
- Set Terminal window to 49+ rows, don't scroll!
(Set Terminal window to 43+ rows, don't scroll)
- Key assignments
-- Key assignments --
↑: rotate 90° counterclockwise
↑: rotate +90° counterclockwise i: Info about the program on/off
↓: soft drop (one line per press)
↓: soft drop (one line) l: Locked hard drop on/off
←: shift left
←: shift left t: restart Timer after move on/off
→: shift right
→: shift right SPACE: hard drop until hit
a: rotate 90° counterclockwise
a: rotate +90° counterclockwise HOME: restart game
d: rotate 90° clockwise
d: rotate -90° clockwise END: exit
i: Info about the program on/off
l: Locked-drop on/off (move after hard drop)
t: Timed move on/off (restart drop timer after move)
SPACE: hard drop until hit
HOME: restart game
END: exit
======
======
The game is played in the Julia standard Unicode character based Terminal
The game is played in the Julia standard Unicode character based Terminal
Line 160: Line 144:
Number of LINES cleared
Number of LINES cleared
SCORE
SCORE
Keys for short Info, Locked-drop-, and Timed move status
Keys for short Info, Locked-drop-, and Timed-move status


Game LEVEL: 0,1...= [sqrt(LINES/8)]
Game LEVEL: 0,1...= [sqrt(LINES/8)]
Increase at 8,32,72,128,200... lines cleared
Increase after 8,32,72,128,200... lines cleared


SCORE: Cleared_Line values: *(LEVEL+1)
SCORE: Cleared_Line values: *(LEVEL+1)
Line 201: Line 185:
White 107 L ██████
White 107 L ██████


Julia specific notes
Julia specifics
Terminal is switched to RAW mode to catch keystrokes.
Terminal is switched to RAW mode to catch keystrokes.
Non-blocking read is achieved via asynchronous (maybe blocked) 2nd TASK.
Non-blocking keyboard input is by asynchronous (maybe blocked) 2nd TASK.
Keystrokes are transferred to the main program through a CHANNEL.
Keystrokes are transferred to the main program through a CHANNEL.
STRUCTs (B4) describe Tetrominos, with color code and coordinates of blocks.
A TIMER controls the dropping speed of tetrominos.
At construction the coordinates get centralized.
In loops Julia 1.0 assumes local variables, when values get assigned,
Assigning Struct to a variable (X) only creates a REFERENCE, not copied.
therefore many GLOBAL variables have to be declared and used
Memory used by orphan instance is cleared by internal GARBAGE COLLECTION.
Out of range indices are caught with TRY..CATCH, to keep tetrominos in board.
A TIMER controls the dropping delay of tetrominos.
ITERATE is defined for tetrominos, looping over the coordinates of blocks.
In loops when variables get assigned before other use, Julia 1.0 assumes
local variables, therefore many GLOBAL variables are declared and used
For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
In most places Int8 (or Int16, Int32) would work. When changing:
In most places 1 Byte Int8 (or Int16, Int32) would work. Take care
take special care as: Int8(x)+1 -> Int64, Int8(x)+0x1 -> UInt8.
when changing: Int8(x)+1->Int64, Int8(x)+0x1->UInt8. Use +Int8(1).
Only the variable "score" needs to be UInt32 or longer.
Only "lines" and "score" need to be UInt32 or longer.
LABEL - GOTO (for restart) require the main loop enclosed in Begin..End.
Assigning Struct to a variable only creates a reference.
For a new instance we need "deepcopy", and rely on garbage collection.
Label - GoTo (for restart) require the main loop enclosed in Begin..End.
Several instructions are written in single lines of at most 84 chars,
Several instructions are written in single lines of at most 84 chars,
to keep the program around 100 non-comment lines long!
to keep the program well under 100 non-comment/non-blank lines!
#.. Comments denote tasks, explain important points.
#.. Comments denote tasks, explain important points.
The program has embedded Unicode characters. They can be replaced with
In strings some UNICODE characters appear. They can be replaced with
\u{hex_digits} in strings, and "∪" replaced with "union"
\u{hex_digits} to make the program all ASCII (for old editors).
to make the program all ASCII. Otherwise editing must handle Unicode.
======#</pre>
======#</pre>

Revision as of 02:56, 8 November 2018

Julia

This is a fairly complete, configurable pure Julia implementation of Tetris, played in the standard Julia terminal. No external packages are used.

Detailed notes are in the long comment below the Julia script, to be attached to the code. At least its first few lines are needed, as they are shown as info, upon the request of the user.

Works with: Julia version 1.0

<lang julia>struct B4 # Tetromino made of 4 Blocks

 bg::Int                         # ANSI code of tetromino color (bground of " ")
 d::Array{Int,2}                 # d[b,1:2] = row,col/2 offset of block b
                                 # constructor: set centered offsets of blocks
 B4(bg,A::Array{Int,2}) = new(bg,A.-sum.(extrema(A,dims=1)).>>1)

end

                                 # in loops ITERATE over the blocks in B4

Base.iterate(B::B4,i=1) = i<5 ? (B.d[i,:],i+1) : nothing

                                 # PRINT/CLEAR tetromino on terminal

drw(B,r,c)=(for(i,j)=B print("\e[$(B.bg)m\e[$(r+i);$(c+2j)H ") end) clr(B,r,c)=(for(i,j)=B print("\e[0m\e[$(r+i);$(c+2j)H ") end)

                                 # HIT other B4s or border?

hit(B,r,c)=(for(i,j)=B try BD[r+i,c>>1+j]>0 catch;true end&& return true end;false)

function mov(B,r,c,x,y) # MOVE tetrominos left/right/down, if no hit

 hit(B,r+x,c+y) || (clr(B,r,c); drw(B,r+=x,c+=y))
 TMv || (global tmr=Timer(tm)); return (r,c)

end

function rot(B,r,c,rt=0) # ROTATE tetrominos left/right, if no hit

 TMv || (global tmr=Timer(tm))
 A = B4(B.bg, rt>0 ? [B.d[:,2] (3 .-B.d[:,1])] : [(3 .-B.d[:,2]) B.d[:,1]])
 for j = c.+(0,+2,-2)            # try shifted positions
   hit(A,r,j) || (clr(B,r,c); drw(A,r,j); return (A,j))
 end; return (B,c)               # cannot rotate: all 3 positions hit

end

function mrk(B,r,c) # RECORD place,CLEAR full-lines,DROP-above,score

 global lines,score,level;  n,l = 0,0
 for(i,j)=B BD[r+i,c>>1+j]=B.bg end  # record stuck B4
 for i in r.+sort(unique(B.d[:,1]))  # empty full rows in board; drop all above
   if all(BD[i,:].>0) n += 1; l = i  # l = lowest line changed
     for j=i-1:-1:1 BD[j+1,:]=BD[j,:] end
     print("\e[0m\e[$i;2H$s20"); sleep(0.3)
 end end                         # update changed display from board data ->
 for i=1:l,j=1:10 print("\e[$(BD[i,j])m\e[$i;$(2j)H  ") end
 score+=(level+1)*(0,100,300,500,800)[n+1]
 lines+=n; level=isqrt(lines>>3) # update lines, score, level

end

function cnf(p) # CONFIRMATION dialog. p = 1-line prompt

 print("$p - Enter confirms, other keys ignore")
 t = take!(chnl); println("\e[2K"); t==[0xd]

end

                                 # SHOW basic usage- and status info

inf()=print("\e[0m\e[26H\e[2K i: Info, l: Locked drop=$LDr, t: Timed move=$TMv")

                                 # SETUP nonblocking, non-echoed keyboard INPUT

ccall(:jl_tty_set_mode,Cint,(Ptr{Cvoid},Cint),stdin.handle,1)==0 ||

 throw("Terminal cannot enter raw mode.") # raw terminal mode to catch keystrokes

const chnl = Channel{Array{UInt8,1}}(0) # unbuffered channel for key codes @async while true put!(chnl,readavailable(stdin)) end # task, catching keystrokes

I = B4(106,[0 0; 0 1; 0 2; 0 3]) # define the 7 tetrominos T = B4(105,[0 1; 1 0; 1 1; 1 2]); O = B4(103,[0 0; 0 1; 1 0; 1 1]) S = B4(102,[0 1; 0 2; 1 0; 1 1]); Z = B4(101,[0 0; 0 1; 1 1; 1 2]) J = B4(104,[0 0; 1 0; 1 1; 1 2]); L = B4(107,[0 2; 1 0; 1 1; 1 2]) LDr,TMv,Ifo = falses(3)

begin @label RESTART # @label - @goto: require begin..end

 global lines,score,level,s20,eq = 0,0,0," "^20,"="^6
 BD = fill(0,22,10)              # empty BOARD. Screen_Col = 2*Board_Col
 print("\e[0m\e[2J\e[?25l\e[1H") # Set default colors/clear screen/hide cursor
 print("▐$(s20)▌\n"^22*"▝$("▀"^20)▘\n\n$s20\n$s20") # screen BORDER, Cols=2:2:20
 X0 = (I,T,O,S,Z,J,L)[rand(1:7)]
 while true                      # random B4, timed drop, act on keystrokes
   global lines,score,level,tm,tmr,LDr,TMv,Ifo
   global X0,X,r,c = (I,T,O,S,Z,J,L)[rand(1:7)],X0,2,10
   clr(X,24,10); drw(X0,24,10); inf()       # show next piece, information
   print("\n Level =\t$level\n Lines filled =\t$lines\n Score =\t$score")
   tm=(.8-level*.007)^level; tmr=Timer(0.5) # 0.5s initial delay
   hit(X,r,c) && (while true cnf("\e[31H Game Over: RESTART")&&@goto RESTART end)
   while isready(chnl) take!(chnl) end      # flush queued keystrokes, max 1
   while true
     global X,r,c,tm,tmr
     if !isopen(tmr)             # time to drop tetromino by a line
       hit(X,r+1,c) && (mrk(X,r,c); break)
       clr(X,r,c); drw(X,r+=1,c); tmr=Timer(tm)
     end
     if isready(chnl)            # if there is a queued keystroke
       global X,r,c,score
       ch = take!(chnl)          # take keys
       if     ch==[0x1b,0x5b,0x41] X,c=rot(X,r,c)              # UP
       elseif ch==[0x1b,0x5b,0x42] r,c=mov(X,r,c,1, 0);score+=1# DOWN
       elseif ch==[0x1b,0x5b,0x43] r,c=mov(X,r,c,0, 2)         # RIGHT
       elseif ch==[0x1b,0x5b,0x44] r,c=mov(X,r,c,0,-2)         # LEFT
       elseif ch==[0x61]           X,c=rot(X,r,c)              # a
       elseif ch==[0x64]           X,c=rot(X,r,c,1)            # d
       elseif ch==[0x20] r0=r; clr(X,r,c); tmr=Timer(tm*!LDr)  # SPACE
         while !hit(X,r+=1,c) end; drw(X,r-=1,c); score+=2r-2r0; continue
       elseif ch==[0x1b,0x5b,0x31,0x7e]                        # HOME
         cnf("\e[0m\e[31H RESTART") && @goto RESTART
       elseif ch==[0x1b,0x5b,0x34,0x7e]                        # END
         cnf("\e[0m\e[31H EXIT") && return
       elseif ch==[0x6c] LDr=!LDr; inf()                       # l: Locked drop
       elseif ch==[0x74] TMv=!TMv; inf()                       # t: Timed move
       elseif ch==[0x69] if (Ifo=!Ifo) open(@__FILE__) do f    # i: long-Info
           readuntil(f,eq); println("\e[0m\e[32H$(readuntil(f,eq))") end
         else print("\e[0m\e[32H\e[J") # erase txt
     end end end
     sleep(0.01)                 # not to take all CPU time

end end end</lang>

The following long comment block has to be appended to program code:

#======       Julia-TETRIS by Laszlo Hars, Version 1.2 10/31/2018 
         (Set Terminal window to 43+ rows, don't scroll)
                          -- Key assignments --
  ↑: rotate +90° counterclockwise   i: Info about the program on/off
  ↓: soft drop (one line)           l: Locked hard drop on/off
  ←: shift left                     t: restart Timer after move on/off
  →: shift right                    SPACE: hard drop until hit
  a: rotate +90° counterclockwise   HOME: restart game
  d: rotate -90° clockwise          END:  exit
======
The game is played in the Julia standard Unicode character based Terminal
    Controlled by ANSI escape sequences (Windows conhost, ConEmu...)
    Set font to e.g. Fantasque Sans Mono
      Monospace, height:width = 2:1, line drawing chars

Playfield rows*cols: 10x20 active, +2 extra rows on top
Tetrominos appear centered in row 2 (and 1)

Rotate is around the center of the tetromino. It is not unique
    thus the rotated tetromino is also shifted left, then right.
    The first of the 3 positions, which does not hit anything is taken.
    
Naive gravity is used
    Full rows (lines) get cleared
    Rows above it move down, but floating blocks can remain

Information is displayed under the board
    PREVIEW next tetromino
    Game LEVEL
    Number of LINES cleared
    SCORE
    Keys for short Info, Locked-drop-, and Timed-move status

Game LEVEL: 0,1...= [sqrt(LINES/8)]
    Increase after 8,32,72,128,200... lines cleared

SCORE: Cleared_Line values: *(LEVEL+1)
    Single = 100
    Double = 300
    Triple = 500
    Tetris = 800

Bonus points: (not increasing with level)
    Soft drop = 1 point per line
    Hard drop = 2 points per line

Time_delay per drop one line, decreasing with LEVEL
    (0.8-LEVEL*0.007)^LEVEL (seconds)

Timing values
    Initial delay for new tetromino = 0.5s
    Cleared line is shown for 0.3s

Tetrominos are drawn by blocks of 2 space characters with color background
    Background color is set by ANSI codes, e.g print("\e[101m  ")
   bg-color_code  shape
Black   100 
                     ████
Red     101   Z        ████
                  ████
Green   102   S ████
                       ████
Yellow  103   O        ████
                ██
Blue    104   J ██████
                       ██
Magenta 105   T      ██████

Cyan    106   I ████████
                         ██
White   107   L      ██████

Julia specifics
    Terminal is switched to RAW mode to catch keystrokes.
    Non-blocking keyboard input is by asynchronous (maybe blocked) 2nd TASK.
    Keystrokes are transferred to the main program through a CHANNEL.
    STRUCTs (B4) describe Tetrominos, with color code and coordinates of blocks.
      At construction the coordinates get centralized.
    Assigning Struct to a variable (X) only creates a REFERENCE, not copied.
    Memory used by orphan instance is cleared by internal GARBAGE COLLECTION.
    Out of range indices are caught with TRY..CATCH, to keep tetrominos in board.
    A TIMER controls the dropping delay of tetrominos.
    ITERATE is defined for tetrominos, looping over the coordinates of blocks.
    In loops when variables get assigned before other use, Julia 1.0 assumes
      local variables, therefore many GLOBAL variables are declared and used
    For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
      In most places 1 Byte Int8 (or Int16, Int32) would work. Take care
        when changing: Int8(x)+1->Int64, Int8(x)+0x1->UInt8. Use +Int8(1).
      Only "lines" and "score" need to be UInt32 or longer.
    LABEL - GOTO (for restart) require the main loop enclosed in Begin..End.
    Several instructions are written in single lines of at most 84 chars,
      to keep the program well under 100 non-comment/non-blank lines!
    #.. Comments denote tasks, explain important points.
    In strings some UNICODE characters appear. They can be replaced with
      \u{hex_digits} to make the program all ASCII (for old editors).
======#