Message Boards Message Boards

Mathematica Tetris

In this post I'd like to present how we can implement Tetris using Mathematica's Dynamic[] interactivity. I am aware of two existing implementations of Tetris so far. The first one utilizes GUIKit and dates back to 2005. The second one is a demonstration on Demonstrations Project.

Our version presented below is fully independent of the aforementioned projects and is aimed at maximum playability and code minimalization. At the same time we tried to implement as much of standard tetris rules as possible.


WARNING: the gameplay is implemented using a combination of CreateScheduledTask[] and EventHandler[] and is not bulletproof. Sporadic errors can be encoutered which are difficult to debug since they are hard to reproduce. Most likely the reason lies in thread synchronization issues. You can also try to enable simple sounds by setting sounds = True, but this can lead to frontend freeze.


Here is a screen capture:

Mathematica Tetris demonstration

The main code goes below.

(* ::Package:: *)

BeginPackage["Tetris`"]

Tetris::usage = "Tetris[] starts game";

Begin["Private`"]

{w, h} = {14, 26};
bg = {0, 0, 0}; (* background color *)
br = {.3, .3, .3}; (* border color *)
speed0 =.6; (* initial speed *)
fallspeed = 0.001; 
acc = 0.8; (* speed acceleration factor *)
sounds = False; 
lpl = 2; (* lines per level *)
sndbrick = Play[Sin[1000 t] + Cos[1100 t], {t, 0, .1}];
gomsg = "GAME OVER";
bcaption = "  New game  ";

figs = { (* figure: {coords, color} *)
   {{{0, -1}, {0, 0}, {0, 1}, {1, -1}}, {0.1, 0.1, 1.0}}(*J*)
   , {{{0, -1}, {0, 0}, {0, 1}, {1, 1}}, {1.0, 0.5, 0.0}}(*L*)
   , {{{1, 0}, {0, 0}, {1, -1}, {0, 1}}, {1.0, 0.0, 0.0}}(*Z*)
   , {{{1, 0}, {0, 0}, {0, -1}, {1, 1}}, {0.1, 1.0, 0.1}}(*S*)
   , {{{0, 1}, {0, 0}, {0, 2}, {0, -1}}, {0.1, 0.9, 1.0}}(*I*)
   , {{{0, 0}, {1, 0}, {1, 1}, {0, 1}}, {1.0, 1.0, 0.1}}(*O*)
   , {{{0, -1}, {0, 0}, {0, 1}, {1, 0}}, {0.9, 0.1, 1.0}}(*T*)
   };

init[] := (
   oldspeed = 0;
   speed = speed0;
   lines = score = level = 0;
   glass = Table[
     If[1 < j < w && i > 1, bg, br]
     , {i, h}, {j, w}
     ];
   nextglass = Table[bg, {4}, {6}];
   fig7 = RandomSample@figs;
   nfig = First@fig7; fig7 = Rest@fig7;
   newmask[];
   playing = False;
   benabled = True;
   msg = "";
   RemoveScheduledTask/@ScheduledTasks[]; 
   );

newmask[] := (mask = Map[# == bg &, glass, {2}]);

newfig[] := (    
  put[nextglass, nfig[[1]], {2, 3}, bg];
  {fig, fc} = nfig;
  nfig = First@fig7; fig7 = Rest@fig7;
  If[fig7 === {}, fig7 = RandomSample@figs];
  put[nextglass, nfig[[1]], {2, 3}, nfig[[2]]];
  {y, x} = {h - 3, Floor[w/2]};
  If[check[fig, {y, x}]
  , put[glass, fig, {y, x}, fc]
  , stop[]
  ; playing = False
  ; benabled = True
  ; msg = gomsg     
  ; Return[]     
  ];    
  If[oldspeed != 0     
  , stop[]
  ; speed = oldspeed
  ; oldspeed = 0
  ; start[]     
  ];    
);

rotate[f_] := If[f[[1]] == {0, 0}, f, {{0, -1}, {1, 0}}.# & /@ f];

SetAttributes[do, HoldAll];
do[act_] := If[playing, act];

stop[] := RemoveScheduledTask[t];
start[] := StartScheduledTask[t = CreateScheduledTask[move[], speed]];

move[] := (
   If[check[fig, {y - 1, x}]
    , put[glass, fig, {y, x}, bg]
    ; put[glass, fig, {--y, x}, fc]
    , es@sndbrick
    ; newmask[]; del[]; newfig[]
    ]
   );

turn[] := Block[{newf},
   newf = rotate@fig;
   If[check[newf, {y, x}]
    , put[glass, fig, {y, x}, bg]
    ; fig = newf
    ; put[glass, fig, {y, x}, fc]]
   ];

shift[dx_] := If[check[fig, {y, x + dx}]
    , put[glass, fig, {y, x}, bg]
    ; x += dx;
    ; put[glass, fig, {y, x}, fc]
    ];

SetAttributes[es, HoldFirst];
es[s_] := If[sounds, EmitSound@s];

price[n_] := Switch[n, 1, 40, 2, 100, 3, 300, 4, 1200];

del[] := Module[{sel, g, ln},
   sel = Not[Or @@ #] & /@ mask;
   sel[[1]] = False;
   g = Pick[glass, Not /@ sel];
   If[(ln = h - Length@g) > 0
    , glass = g~Join~Table[If[1 < i < w, bg, br], {ln}, {i, w}]
    ; es@Play[UnitStep@Sin[2000 t] Sin[5000 t t], {t, 0, .2 (ln)}]
    ; lines += ln
    ; score += price[ln]*(level + 1)
    ; cl = Quotient[lines, lpl];
    ; If[cl > level, level = cl; stop[]; speed *= acc; 
     oldspeed *= acc; start[]]
    ; newmask[]
    ];
   ];

SetAttributes[#, HoldFirst] & /@ {set, put};

set[g_, p_, c_] := (g[[Sequence @@ p]] = c);

put[g_, f_, p_, c_] := set[g, #, c] & /@ (# + p & /@ f);

get[p_] := mask[[Sequence @@ p]];

check[f_, p_] := And @@ (get /@ (# + p & /@ f));

drop[] := If[speed != fallspeed
   , stop[]; playing = False;
   ; oldspeed = speed
   ; speed = fallspeed
   ; start[]; playing=True;
   ];

  menu = Button[bcaption
    , init[]
    ; newfig[]
    ; playing = True
    ; start[]
    ; benabled = False;
    , Enabled -> Dynamic@benabled
  ];


Tetris[] := DynamicModule[{},   
  init[];
  EventHandler[
   Graphics[{
     Raster@Dynamic@glass[[;; -4]]
     , Raster[Dynamic@nextglass, {{w, h - 7}, {w + 6, h - 3}}]
     , Text[Style["Score", 24, White, Bold], {w, 17}, {-1, 0}]
     , Text[Style[Dynamic@score, 24, White, Bold], {w + 6, 17}, {1, 0}]
     , Text[Style["Lines", 24, Green, Bold], {w, 14}, {-1, 0}]
     , Text[Style[Dynamic@lines, 24, Green, Bold], {w + 6, 14}, {1, 0}]
     , Text[Style["Level", 24, Cyan, Bold], {w, 11}, {-1, 0}]
     , Text[Style[Dynamic@level, 24, Cyan, Bold], {w + 6, 11}, {1, 0}]
     , Text[Style[Dynamic@msg, 24, White, Bold], {w + 3, 7}, {0, 0}]
     , Inset[menu, {w + 3, 2}]
     }
    , PlotRange -> {{0, w + 7}, {0, h - 2}}
    , Background -> RGBColor@br
    , ImageSize -> 600]
   , {"RightArrowKeyDown" :> do@shift@1
    , "LeftArrowKeyDown" :> do@shift@-1
    , "UpArrowKeyDown" :> do@turn[]
    , "DownArrowKeyDown" :> do@drop[]
    }
   ]
];

End[]

EndPackage[]

You can start the game by executing

<< "Tetris`"
Tetris[]

CREDITS: this is a joint work with my student Nikita Seredinski.

POSTED BY: Boris Faleichik
6 Replies
Posted 9 years ago

Tetris, so classical game! Yeasterday I saw a kid was playing Tetris 3D. It might be also be developed by Wolfram Language one day.

https://en.wikipedia.org/wiki/3D_Tetris enter image description here

POSTED BY: Frederick Wu

Nice, let me just point out something,

"Private`" vs "`Private`"

unless you really intend to do so, Begin["Private`"] should have an accent before Private - Begin["`Private`"].

Without it all those symbols are going to be created in Private` context instead of Tetris`Private` context. And you don't probably want this since it will happen for all packages which will then share symbols in Private`.

Take a look

BeginPackage["Tetris`"];

Begin["Private`"];
testSymbol`
End[];

Begin["`Private`"]; (* "correctly" *)
testSymbol2
End[]; 

EndPackage[];

Names["*`testSymbol*"]
 {"Private`testSymbol1", "Tetris`Private`testSymbol2", ...}

Why is this a problem?

Let's create two packages:

BeginPackage["MyPackage`"];    
showString;    
Begin["Private`"];

temp = "MyPackage";
showString[] := temp;

End[];    
EndPackage[];


BeginPackage["YourPackage`"];    
whatever;    
Begin["Private`"];

temp = "YourPackage";
whatever[] := "doesNotMatter";

End[];
EndPackage[];

showString[]

YourPackage (while expected is MyPackage, isn't it?)

POSTED BY: Kuba Podkalicki

Kuba, thank you for pointing out this mistake. And I really appreciate your nice explanation :)

POSTED BY: Boris Faleichik

enter image description here - another post of yours has been selected for the Staff Picks group, congratulations !

We are happy to see you at the top of the "Featured Contributor" board. Thank you for your wonderful contributions, and please keep them coming!

POSTED BY: EDITORIAL BOARD

WoW! great job! excellent!

First time I see someone that has ; at the start of a line! It looks a bit strange!

POSTED BY: Sander Huisman

Thank you, Sander. I agree this code formatting looks unusual, but quite recently I've found that it is convenient and more readable, especially for If[] statements with "Else" section.

POSTED BY: Boris Faleichik
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract