Tetris/Julia: Difference between revisions

From Rosetta Code
Content added Content deleted
Line 99: Line 99:
therefore global variables have to be declared and used
therefore global variables have to be 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 In16, Int32) would work. When changing:
In most places Int8 (or Int16, Int32) would work. When changing:
take special care as: Int8(1)+1 -> Int64, Int8(1)+0x1 -> Uint8.
take special care as: Int8(1)+1 -> Int64, Int8(1)+0x1 -> UInt8.
Only the variable "score" needs to be UInt32 or larger
Only the variable "score" needs to be UInt32 or larger
Assigning Struct to a variable only creates a reference.
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.
==============================================================================#
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::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>