Tetris/Julia: Difference between revisions

m
Fixed syntax highlighting.
m (Fixed syntax highlighting.)
 
(6 intermediate revisions by 2 users not shown)
Line 1:
<span style='font-family: "Linux Libertine",Georgia,Times,serif;font-size:150%;'>[[Julia]]</span><hr>
=={{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}}
 
<syntaxhighlight lang="julia">struct B4 # Tetromino made of 4 Blocks
<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: Zset =centered B4(101,[0offsets 0;of 0 1; 1 1; 1 2])blocks
function B4(bg,A::Array{Int,2}) #= new(bg,A.- fills in centered offsets for tetrominosum.(extrema(A,dims=1)).>>1)
new(bg, A .- sum.(extrema(A,dims=1)).>>1)
end end
# print tetromino on playfield
function draw(b::B4,r::Integer,c::Integer,clr::Integer=-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
# hitin otherloops B4sITERATE orover border?the blocks in B4
Base.iterate(B::B4,i=1) = i<5 ? (B.d[i,:],i+1) : nothing
function hit(B::B4,r::Integer,c::Integer)::Bool
# 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
drw(B,r,c)=(for(i,j)=B print("\e[$(B.bg)m\e[$(r+i);$(c+2j)H ") end)
return false
clr(B,r,c)=(for(i,j)=B print("\e[0m\e[$(r+i);$(c+2j)H ") end)
end
# moveHIT tetrominosother left/right/down,B4s ifor no hitborder?
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::B4,r::Integer,c::Integer,x::Integer,y::Integer)
hit(B,r+x,c+y) || (draw(B,r,c,0);draw(B,r+=x,c+=y))
function mov(B,r,c,x,y) # MOVE tetrominos left/right/down, if no hit
global tmr = Timer(tm); return (r,c)
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
 
# rotate tetrominos left/right, if no hit
function rot(B::B4,r::Integer,c::Integer,rigtrt=0::Integer) # ROTATE tetrominos left/right, if no hit
TMv || (global tmr=Timer(tm))
A = deepcopy(B)
forA i=1:4 AB4(B.d[ibg,:]=rigt rt>0 ? [AB.d[i:,2], (3 .-AB.d[i:,1])] : [(3 .-AB.d[i:,2],A) B.d[i:,1]] end)
A.d .-= sum.(extrema(A.d,dims=1)).>>1 # centralize
global tmr = Timer(tm)
for j = c.+(0,+2,-2) # try shifted positions
hit(A,r,j) || (drawclr(B,r,c,0); drawdrw(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
 
# record-place,clear-full-lines,drop-above,score
function mrk(B,r,c) # RECORD place,CLEAR full-lines,DROP-above,score
function mark(B::B4,r::Integer,c::Integer)
global lines, score, level; ln n,l = 0,0
for (i,j)=1:4B BD[r+B.d[i,1],c>>1+B.d[i,2]+2j]=B.bg end # record stuck B4
for i in r.+sort(unique(B.d[:,1])) # in board: empty full rows in board; drop partall above
if all(BD[i,3:12].>0) lnn += 1; l = i # l = lowest line changed
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 changed display from board data ->
for i=1:22l,j=31:1210 print("\e[$(BD[i,j])m\e[$i;$(2j-4)H ") end
ln > 0 && (lines+=ln; score += (level+1)*(0,100,300,500,800)[lnn+1])
lines+=n; level=isqrt(lines>>3) # updatesupdate lines, score, level
end
 
# Setup nonblocking, non-echoed keyboard input
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) # shortunbuffered 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
Line 168 ⟶ 63:
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,2422,1510); for j=3:12 BD[23,j]=BD[24,j] # empty BOARD. Screen_Col =1 end2*Board_Col
for i=1:24,j=(1,2,13,14,15) BD[i,j]=1 end # Board border. screen_C = 2j-4
 
print("\e[0m\e[2J\e[?25l\e[1H") # #Set default colors/clear screen/hide cursor
for i=1:22 print("\e[$i;1H▐$(s20)▌\n"^22*"▝$("▀"^20)▘\n\n$s20\n$s20") end# screen BORDER, -> Cols=2:2:20
print("\e[23;1H▝$("▀"^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
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
clr(X,24,10); drw(X0,24,10); inf() # show next piece, information
draw(X0,24,10)
print("\e[0m\e[27;1Hn Level =\t$level\n Lines filled =\t$lines\n Score =\t$score")
tm=(.8-level*.007)^level; tmr=Timer(0.5) # fixed 0.5s firstinitial delay
hit(X,r,c) && (printlnwhile true cnf("\n\ne[31H Game Over!...press: aRESTART")&&@goto keyRESTART for restart"end);
while isready(chnl) take!(chnl); @gotoend RESTART ) # flush queued keystrokes, max 1
while isready(chnl) take!(chnl) end # flush keystrokes
while true
global X,r,c,tm,tmr
if !isopen(tmr) # time to drop tetromino by a line
hit(X,r+1,c) && (markmrk(X,r,c); break)
drawclr(X,r,c,0); drawdrw(X,r+=1,c); tmr=Timer(tm)
end
whileif isready(chnl) # areif there is a queued keystrokes?keystroke
global X,r,c,score
ch = take!(chnl) # take keys
Line 204 ⟶ 96:
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; drawclr(X,r,c,0); tmr=Timer(tm*!LDr) # SPACE
while !hit(X,r+=1,c) end; drawdrw(X,r-=1,c); score+=2r-2r0; continue
elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME
printlncnf("\e[0m\e[?25h\e[31;1H31H RESTARTING...pressRESTART") a&& key")@goto RESTART
take!(chnl); @goto RESTART
elseif ch==[0x1b,0x5b,0x34,0x7e] # END
printlncnf("\e[0m\e[?25h\e[31;1H31H EXITING...pressEXIT") a&& key")return
elseif ch==[0x6c] LDr=!LDr; inf() # l: Locked drop
take!(chnl); return
elseif ch==[0x74] TMv=!TMv; inf() # t: Timed move
end end
elseif ch==[0x69] if (Ifo=!Ifo) open(@__FILE__) do f # i: long-Info
sleep(0.01) # not to take all CPU core time
readuntil(f,eq); println("\e[0m\e[32H$(readuntil(f,eq))") end
end end end</lang>
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</syntaxhighlight>
 
The following long comment block has to be appended to program code:
 
<pre>#====== 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).
======#</pre>
9,476

edits