Tetris/Julia: Difference between revisions
Line 99:
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
take special care as: Int8(1)+1 -> Int64, Int8(1)+0x1 ->
Only the variable "score" needs to be UInt32 or larger
Assigning Struct to a variable only creates a reference.
|
Revision as of 23:40, 23 October 2018
Julia
#====== 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) 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. ==============================================================================#
<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::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
# hit other B4s or border?
function hit(B::B4,r::Integer,c::Integer)::Bool
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::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)) global tmr = Timer(tm); return (r,c)
end
# rotate tetrominos left/right, if no hit
function rot(B::B4,r::Integer,c::Integer,rigt=0::Integer)
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 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) || (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::B4,r::Integer,c::Integer)
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
# 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) # short 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])
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,14,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;1H▐$(s20)▌") end# BORDER -> 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)]
while true # random B4, timer to drop, act on keystrokes global lines,score,level,tm,tmr 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) print("\e[0m\e[27;1H 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) && (println("\n\n Game Over!...press a key for restart"); take!(chnl); @goto RESTART ) 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) && (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) # SPACE while !hit(X,r+=1,c) end; draw(X,r-=1,c);score+=2r-2r0; continue elseif ch==[0x1b,0x5b,0x31,0x7e] # HOME println("\e[0m\e[?25h\e[31;1H RESTARTING...press a key") take!(chnl); @goto RESTART elseif ch==[0x1b,0x5b,0x34,0x7e] # END println("\e[0m\e[?25h\e[31;1H EXITING...press a key") take!(chnl); return end end sleep(0.01) # not to take all CPU core time
end end end</lang>