Tetris/Phix
Appearance
< Tetris
You can run this online here. For instructions, see key_cb()
-- -- demo\rosetta\Tetrominoes.exw -- ============================ -- -- Author Pete Lomax, Jan 2020 -- -- 1. A robot may not injure a human being, or, through inaction, -- allow a human being to come to harm. -- -- 2. A robot must obey the orders given it by human beings -- except where such orders would conflict with the First Law. -- -- 3. A robot must protect its own existence as long as such -- protection does not conflict with the First or Second Law. -- -- 4. A robot must never place the long skinny ones horizontally, -- unless it leads to a long skinny vertical hole so 4 rows can -- be cleared at once the next time a long skinny one comes -- around. -- with javascript_semantics include pGUI.e Ihandle canvas, dialog, timer cdCanvas cddbuffer, cdcanvas constant TITLE = "Tetrominoes", lct = {{'I',CD_CYAN, {{-2, 0},{-1, 0},{ 0, 0},{ 1, 0}}}, {'T',CD_PURPLE, {{-1, 0},{ 0, 0},{ 1, 0},{ 0, 1}}}, {'O',CD_YELLOW, {{-1, 0},{ 0, 0},{-1, 1},{ 0, 1}}}, {'S',CD_GREEN, {{-1, 1},{ 0, 1},{ 0, 0},{ 1, 0}}}, {'Z',CD_RED, {{-1, 0},{ 0, 0},{ 0, 1},{ 1, 1}}}, {'J',CD_BLUE, {{-1, 0},{ 0, 0},{ 1, 0},{ 1, 1}}}, {'L',CD_ORANGE, {{-1, 1},{-1, 0},{ 0, 0},{ 1, 0}}}}, {letters,colours,tiles} = columnize(lct), topbot = {repeat('*',12)}, clear_line = '*'&repeat(' ',10)&'*', full_line = '*'&repeat('Z',10)&'*', init_board = topbot&repeat(clear_line,20)&topbot, directions = {{0,+1}, -- DOWN {-1,0}, -- LEFT {+1,0}} -- RIGHT --enum type direction DOWN, LEFT, RIGHT end type enum DOWN, LEFT, RIGHT type direction(integer d) return d>=DOWN and d<=RIGHT end type sequence board, -- (nb 2-based indexing!) tile integer px, py, preview, level, completed, -- (the first complete line found, cleared soon) completen, -- (number of "" "" s "" "" "" ) completot -- (the running total, used to update the level) function check(sequence curr, next, integer nx, ny) -- (common code for move and rotate) integer tx, ty, what for i=1 to length(next) do -- check tgt free sequence ti = next[i] if not find(ti,curr) then {tx,ty} = ti if tx>11 or board[ty,tx]!=' ' then return false end if end if end for for i=1 to 4 do -- clear current {tx,ty} = curr[i] what = board[ty,tx] board[ty,tx]=' ' end for for i=1 to 4 do -- set new position {tx,ty} = next[i] board[ty,tx] = what end for {px,py} = {nx,ny} IupUpdate(canvas) return true end function function move(direction d, bool place=false, what=' ') -- returns true/false to allow detection of "game over", drop new, etc integer {dx,dy} = directions[d], tx, ty, x, y sequence curr = repeat({},4), next = repeat({},4) for i=1 to length(tile) do {tx,ty} = tile[i] {x,y} = {px+tx+1,py+ty+1} curr[i] = {x,y} next[i] = {x+dx,y+dy} if place then board[y,x] = what end if end for return check(curr,next,px+dx,py+dy) end function procedure rotate() -- 90 degrees anti-clockwise sequence rile = deep_copy(tile), curr = repeat({},4), next = repeat({},4) for i=1 to length(tile) do integer {tx,ty} = tile[i] rile[i] = {ty,-tx} curr[i] = {px+tx+1,py+ty+1} next[i] = {px+ty+1,py-tx+1} end for if check(curr,next,px,py) then tile = rile end if end procedure procedure set_level(integer nl) level = nl IupSetInt(timer,"RUN",false) IupSetInt(timer,"TIME",50*(11-level)) IupSetInt(timer,"RUN",true) IupSetStrAttribute(dialog,"TITLE","%s - level %d",{TITLE,level}) end procedure procedure drop(bool bStart=false) if bStart then board = deep_copy(init_board) preview = rand(7) completed = 0 completot = 0 set_level(1) else while move(DOWN) do end while for i=2 to 21 do if not find(' ',board[i]) then completed = i for j=i+1 to 22 do board[j-1] = full_line if j=22 or find(' ',board[j]) then completen = j-i IupUpdate(canvas) return -- (show it with a brief pause) end if end for end if end for end if integer t = preview preview = rand(7) {px,py,tile} = {6,1,tiles[t]} if not move(DOWN,true,letters[t]) then IupSetStrAttribute(dialog,"TITLE","%s - GAME OVER",{TITLE}) IupSetInt(timer,"RUN",false) end if end procedure procedure new_game() drop(bStart:=true) end procedure procedure clear() completot += completen integer nl = floor(min(completot-1,90)/10)+1 if nl!=level then set_level(nl) end if for i=completed+completen-1 to 3 by -1 do completed -= (completed>2) board[i] = board[completed] end for board[2] = clear_line completed = 0 end procedure function redraw_cb(Ihandle /*canvas*/, integer /*posx*/, /*posy*/) integer {cw,ch} = IupGetIntInt(canvas, "DRAWSIZE"), dx = min(floor(cw/10), floor(ch/20))-1, mx = floor((cw-10*dx-11)/2), my = floor((ch-20*dx-21)/2), gx, gy, sq cdCanvasActivate(cddbuffer) cdCanvasSetBackground(cddbuffer, CD_DARK_GREY) cdCanvasClear(cddbuffer) for y=1 to 20 do gy = my+(20-y)*(dx+1)+1 for x=1 to 10 do sq = board[y+1,x+1] if sq!=' ' then cdCanvasSetForeground(cddbuffer,colours[find(sq,letters)]) gx = mx+(x-1)*(dx+1)+1 cdCanvasBox(cddbuffer, gx, gx+dx-1, gy, gy+dx-1) end if end for end for cdCanvasSetForeground(cddbuffer,colours[preview]) gx = mx+9*(dx+1)+1 gy = my+19*(dx+1)+1 dx = floor(dx/4) sequence ptile = sq_add(repeat({gx+dx*2,gy+dx*2},4),sq_mul(tiles[preview],dx)) for i=1 to 4 do {gx,gy} = ptile[i] cdCanvasBox(cddbuffer, gx, gx+dx-1, gy, gy+dx-1) end for cdCanvasFlush(cddbuffer) return IUP_DEFAULT end function function map_cb(Ihandle ih) cdcanvas = cdCreateCanvas(CD_IUP, ih) cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas) return IUP_DEFAULT end function function key_cb(Ihandle /*ih*/, atom c) if c=K_CR or c=K_SP then new_game() elsif c=K_LEFT then {} = move(LEFT) elsif c=K_RIGHT then {} = move(RIGHT) elsif c=K_UP then rotate() elsif c=K_DOWN then drop() elsif c=K_ESC then return IUP_CLOSE end if return IUP_CONTINUE end function function timer_cb(Ihandle /*timer*/) if completed then clear() elsif not move(DOWN) then drop() end if return IUP_DEFAULT end function procedure main() IupOpen() canvas = IupCanvas("RASTERSIZE=350x675") IupSetCallback(canvas, "MAP_CB", Icallback("map_cb")) IupSetCallback(canvas, "ACTION", Icallback("redraw_cb")) dialog = IupDialog(canvas) IupSetAttribute(dialog,"TITLE",TITLE) IupSetCallback(dialog, "K_ANY", Icallback("key_cb")); timer = IupTimer(Icallback("timer_cb"), 500) new_game() IupShow(dialog) IupSetAttribute(canvas, "RASTERSIZE", NULL) if platform()!=JS then IupMainLoop() IupClose() end if end procedure main()