Tetris/Julia: Difference between revisions
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 |
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 |
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 |
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 |
function rot(B,r,c,rt=0) |
||
A = deepcopy(B) |
A = deepcopy(B) |
||
for i=1:4 A.d[i,:]= |
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 |
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) # |
const chnl = Channel{Array{UInt8,1}}(0) # unbuffered channel for key codes |
||
@async while true put!(chnl,readavailable(stdin)) end |
@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 |
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; |
for i=1:22 print("\e[$i;H▐$(s20)▌") end # BORDER -> |
||
print("\e[ |
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("\ |
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) && ( |
hit(X,r,c) && (while true conf("\e[31H Game Over/RESTART")&&@goto RESTART end) |
||
take!(chnl) |
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; |
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 |
||
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 |
||
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.
<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. ======#