Tetris/Julia: Difference between revisions

From Rosetta Code
Content added Content deleted
Line 1: Line 1:
=={{header|Julia}}==
=={{header|Julia}}==
This is a fairly complete, configurable pure Julia implementation of Tetris, played in the standard Julia terminal. No external packages are used.
<pre>#====== Julia-TETRIS: Features/Parameters =====================================
by Laszlo Hars, 10/21/2018
The game is played in a character based Terminal
It must accept ANSI control sequences (e.g. Windows conhost, ConEmu)


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.
Terminal Font (e.g. Fantasque Sans Mono)
Monospace
height:width = 2:1
Include line drawing chars


Tetrominos: ANSI color codes \e[{fg};{bg}m
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 ██████

Playfield (Board) rows*cols: 10x20 active, +2 extra rows on top
Tetrominos start centered (I,O), or 1 column left of center

Keyboard key assignments
Up arrow : rotate 90° counterclockwise
Down arrow : soft drop (one line per press)
Left arrow : shift left
Right arrow: shift right
a: rotate 90° counterclockwise
d: rotate 90° clockwise.
Space: hard drop until hit, then still can shift/rotate/drop
Home: restart
END: exit

Move: shift, rotate and soft/hard drop
Restart timer: pause auto drop
Keeping the move key depressed = Effectively a PAUSE function
Easy navigation in tunnels between/under tetrominos
There is NO move back up

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

Information display, under the board
PREVIEW next tetromino
Game Level
Number of Lines already cleared
Score

Game level: 0,1...= [sqrt(cleared_lines/8)]
Increase at 8,32,72,128,200...

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

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

Time_delay per drop one line, exponential
(0.8-level*0.007)^level (seconds)

Timing values
Initial delay for new tetromino = 0.5s
Cleared line is shown for 0.3s
Hard dropped tetromino is active for the current time_delay
Navigation (move/rotate) must start in this time

Not yet implemented
Esc: pause with hiding the board
Pause:freeze - unfreeze
Music, background graphic, high-score list...

Julia specific issues
Terminal is switched to raw mode to catch keystrokes
Non-blocking read is achieved via asynchronous (maybe blocked) 2nd task
Keystrokes are transfered to the main program through a short Channel
A Timer is set to control the speed of drop of tetrominos
In loops Julia 1.0 assumes local variables, when values get assigned
therefore global variables have to be declared and used
For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
In most places Int8 (or Int16, Int32) would work. When changing:
take special care as: Int8(1)+1 -> Int64, Int8(1)+0x1 -> UInt8.
Only the variable "score" needs to be UInt32 or larger
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 80 chars, to
keep the program under 100 non-blank lines!
#.. Comments note tasks, important points.
==============================================================================#</pre>
{{works with|Julia|1.0}}
{{works with|Julia|1.0}}


Line 120: Line 15:
end end
end end
# print tetromino on playfield
# print tetromino on playfield
function draw(b::B4,r::Integer,c::Integer,clr::Integer=-1)
function draw(b,r,c,clr=-1)
print("\e[$(clr & b.bg)m") # set color. (0 = default colors)
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
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?
# hit other B4s or border?
function hit(B::B4,r::Integer,c::Integer)::Bool
function hit(B,r,c)
for i=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]>0 && return true end
for i=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]>0 && return true end
return false
return false
end
end
# move tetrominos left/right/down, if no hit
# move tetrominos left/right/down, if no hit
function mov(B::B4,r::Integer,c::Integer,x::Integer,y::Integer)
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+x,c+y) || (draw(B,r,c,0);draw(B,r+=x,c+=y))
global tmr = Timer(tm); return (r,c)
TMv || (global tmr = Timer(tm)); return (r,c)
end
end
# rotate tetrominos left/right, if no hit
# rotate tetrominos left/right, if no hit
function rot(B::B4,r::Integer,c::Integer,rigt=0::Integer)
function rot(B,r,c,rt=0)
A = deepcopy(B)
A = deepcopy(B)
for i=1:4 A.d[i,:]=rigt>0 ? [A.d[i,2],3-A.d[i,1]] : [3-A.d[i,2],A.d[i,1]] end
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.d .-= sum.(extrema(A.d,dims=1)).>>1 # centralize
A.d .-= sum.(extrema(A.d,dims=1)).>>1 # centralize
global tmr = Timer(tm)
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) || (draw(B,r,c,0); draw(A,r,j); return (A,j))
Line 146: Line 41:
end
end
# record-place,clear-full-lines,drop-above,score
# record-place,clear-full-lines,drop-above,score
function mark(B::B4,r::Integer,c::Integer)
function mark(B,r,c)
global lines, score, level; ln = 0
global lines, score, level; ln = 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=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]=B.bg end # record stuck B4
Line 158: Line 53:
level=isqrt(lines>>3) # updates lines, score, level
level=isqrt(lines>>3) # updates lines, score, level
end
end
# function for dialog confirmation
function conf(t) # t: prompt, w/o new line!
print("$t - Enter confirms, other keys ignore")
t = take!(chnl); println("\e[2K"); t==[0xd]
end
# show basic usage- and status info
info()=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) # short 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 168: Line 71:
S = B4(102,[0 1; 0 2; 1 0; 1 1]); Z = B4(101,[0 0; 0 1; 1 1; 1 2])
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])
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
begin @label RESTART # @label - @goto: require begin..end
global lines,score,level,s20 = 0,0,0," "^20
global lines,score,level,s20 = 0,0,0," "^20
BD=fill(0,24,15); for j=3:12 BD[23,j]=BD[24,j]=1 end
BD=fill(0,24,15); for j=3:12 BD[23,j]=BD[24,j]=1 end
for i=1:24,j=(1,2,13,14,15) BD[i,j]=1 end # Board border. screen_C = 2j-4
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") # default colors/clear screen/hide cursor
for i=1:22 print("\e[$i;1H▐$(s20)▌") end# BORDER ->
for i=1:22 print("\e[$i;H▐$(s20)▌") end # BORDER ->
print("\e[23;1H▝$("▀"^20)▘") # ROWS=1:22, COLS=2:2:20, Center=10,11
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)]
print("\n\n$s20\n$s20"); 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, timer to drop, act on keystrokes
global lines,score,level,tm,tmr
global lines,score,level,tm,tmr,LDr,TMv,Ifo
draw(X0,24,10,0)
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)
draw(X0,24,10); info()
print("\e[0m\e[27;1H 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) # fixed 0.5s first delay
hit(X,r,c) && (println("\n\n Game Over!...press a key for restart");
hit(X,r,c) && (while true conf("\e[31H Game Over/RESTART")&&@goto RESTART end)
take!(chnl); @goto RESTART )
while isready(chnl) take!(chnl) end # flush queued keystrokes
while isready(chnl) take!(chnl) end # flush keystrokes
while true
while true
global X,r,c,tm,tmr
global X,r,c,tm,tmr
Line 204: Line 107:
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) # SPACE
elseif ch==[0x20] r0=r;draw(X,r,c,0);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; draw(X,r-=1,c);score+=2r-2r0; continue
elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME
elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME
println("\e[0m\e[?25h\e[31;1H RESTARTING...press a key")
conf("\e[0m\e[31H RESTART") && @goto RESTART
take!(chnl); @goto RESTART
elseif ch==[0x1b,0x5b,0x34,0x7e] # END
elseif ch==[0x1b,0x5b,0x34,0x7e] # END
println("\e[0m\e[?25h\e[31;1H EXITING...press a key")
conf("\e[0m\e[31H EXIT") && return
elseif ch == [0x6c] LDr=!LDr; info() # l: Locked drop
take!(chnl); return
elseif ch == [0x74] TMv=!TMv; info() # t: Timed move
end end
elseif ch == [0x69] # i: Info
if (Ifo = !Ifo) io = open(Base.source_path()); readuntil(io,"="^6)
println("\e[0m\e[32H$(readuntil(io,"="^6))"); close(io)
else
print("\e[0m\e[32H\e[J") # erase txt
end end end
sleep(0.01) # not to take all CPU core time
sleep(0.01) # not to take all CPU core time
end end end</lang>
end end end</lang>

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

<pre>#====== Julia-TETRIS by Laszlo Hars, Version 1.1 10/21/2018
- Set Terminal window to 49+ rows, don't scroll!
- Key assignments
↑: rotate 90° counterclockwise
↓: soft drop (one line per press)
←: shift left
→: shift right
a: rotate 90° counterclockwise
d: rotate 90° clockwise
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
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 at 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 specific notes
Terminal is switched to RAW mode to catch keystrokes.
Non-blocking read is achieved via asynchronous (maybe blocked) 2nd TASK.
Keystrokes are transferred to the main program through a CHANNEL.
A TIMER controls the dropping speed of tetrominos.
In loops Julia 1.0 assumes local variables, when values get assigned,
therefore many GLOBAL variables have to be declared and used
For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
In most places Int8 (or Int16, Int32) would work. When changing:
take special care as: Int8(x)+1 -> Int64, Int8(x)+0x1 -> UInt8.
Only the variable "score" needs to be UInt32 or longer.
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,
to keep the program less than 110 non-comment lines!
#.. Comments denote tasks, explain important points.
The program has embedded Unicode characters. They can be replaced with
\u{hex_digits} in strings, and "∪" replaced with "union"
to make the program all ASCII. Otherwise editing must handle Unicode.
======#</pre>

Revision as of 17:15, 29 October 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>using Random struct B4

 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: Z = B4(101,[0 0; 0 1; 1 1; 1 2])
 function B4(bg,A::Array{Int,2}) # - fills in centered offsets for tetromino
   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

                                 # hit other B4s or border?

function hit(B,r,c)

 for i=1:4 BD[r+B.d[i,1],c>>1+B.d[i,2]+2]>0 && return true end
 return false

end

                                 # move tetrominos left/right/down, if no hit

function mov(B,r,c,x,y)

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

end

                                 # rotate tetrominos left/right, if no hit

function rot(B,r,c,rt=0)

 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.d .-= sum.(extrema(A.d,dims=1)).>>1     # centralize
 TMv || (global tmr = Timer(tm))
 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))
 end
 return (B,c)                    # cannot rotate: all 3 positions hit

end

                                 # record-place,clear-full-lines,drop-above,score

function mark(B,r,c)

 global lines, score, level; ln = 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 in r.+sort(unique(B.d[:,1])) # in board: empty full rows; drop part above
   if all(BD[i,3:12].>0) ln += 1
     for j = i-1:-1:1 BD[j+1,3:12] = BD[j,3:12] end
     print("\e[0m\e[$i;2H$s20"); sleep(0.3)
 end end                         # update display from board data ->
 for i=1:22,j=3:12 print("\e[$(BD[i,j])m\e[$i;$(2j-4)H  ") end
 ln > 0 && (lines+=ln; score += (level+1)*(100,300,500,800)[ln])
 level=isqrt(lines>>3)           # updates lines, score, level

end

                                 # function for dialog confirmation

function conf(t) # t: prompt, w/o new line!

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

end

                                 # show basic usage- and status info

info()=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 = 0,0,0," "^20
 BD=fill(0,24,15); for j=3:12 BD[23,j]=BD[24,j]=1 end
 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
 for i=1:22 print("\e[$i;H▐$(s20)▌") end # BORDER -> 
 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)]
 while true                      # random B4, timer to drop, act on keystrokes
   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
   draw(X0,24,10); info()
   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
   hit(X,r,c) && (while true conf("\e[31H Game Over/RESTART")&&@goto RESTART end)
   while isready(chnl) take!(chnl) end   # flush queued keystrokes
   while true
     global X,r,c,tm,tmr
     if !isopen(tmr)             # time to drop tetromino by a line
       hit(X,r+1,c) && (mark(X,r,c); break)
       draw(X,r,c,0); draw(X,r+=1,c); tmr=Timer(tm)
     end
     while isready(chnl)         # are there queued keystrokes?
       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;draw(X,r,c,0);tmr=Timer(tm*!LDr) # SPACE
         while !hit(X,r+=1,c) end; draw(X,r-=1,c);score+=2r-2r0; continue
       elseif ch==[0x1b,0x5b,0x31,0x7e]                        # HOME
         conf("\e[0m\e[31H RESTART") && @goto RESTART
       elseif ch==[0x1b,0x5b,0x34,0x7e]                        # END
         conf("\e[0m\e[31H EXIT") && return
       elseif ch == [0x6c] LDr=!LDr; info()                    # l: Locked drop
       elseif ch == [0x74] TMv=!TMv; info()                    # t: Timed move
       elseif ch == [0x69]                                     # i: Info
         if (Ifo = !Ifo) io = open(Base.source_path()); readuntil(io,"="^6)
           println("\e[0m\e[32H$(readuntil(io,"="^6))"); close(io)
         else
           print("\e[0m\e[32H\e[J")      # erase txt
     end end end
     sleep(0.01)                 # not to take all CPU core time

end end end</lang>

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

#====== Julia-TETRIS by Laszlo Hars, Version 1.1 10/21/2018 
- Set Terminal window to 49+ rows, don't scroll!
- Key assignments
    ↑: rotate 90° counterclockwise
    ↓: soft drop (one line per press)
    ←: shift left
    →: shift right
    a: rotate 90° counterclockwise
    d: rotate 90° clockwise
    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
    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 at 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 specific notes
    Terminal is switched to RAW mode to catch keystrokes.
    Non-blocking read is achieved via asynchronous (maybe blocked) 2nd TASK.
    Keystrokes are transferred to the main program through a CHANNEL.
    A TIMER controls the dropping speed of tetrominos.
    In loops Julia 1.0 assumes local variables, when values get assigned,
      therefore many GLOBAL variables have to be declared and used
    For simplicity Int variables are used; in 64-bit OS versions: 8 Bytes.
      In most places Int8 (or Int16, Int32) would work. When changing:
      take special care as: Int8(x)+1 -> Int64, Int8(x)+0x1 -> UInt8.
      Only the variable "score" needs to be UInt32 or longer.
    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,
      to keep the program less than 110 non-comment lines!
    #.. Comments denote tasks, explain important points.
    The program has embedded Unicode characters. They can be replaced with
      \u{hex_digits} in strings, and "∪" replaced with "union"
      to make the program all ASCII. Otherwise editing must handle Unicode.
======#