Tell me more ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

In the spirit of re-implementing classic video games, I would like to invite the community to create their best implementation of Tetris.

enter image description here
For reference, a screenshot of the official NES version of Tetris.

Required Features

  • A reasonable scoring system must be in place, which rewards multi-line clears more than single-line clears. The current score must be visible at all times.
  • The next piece that will appear must be indicated in some manner.
  • The distribution of the seven tetrominoes should be fairly even (i.e. pseudo-randomly chosen).
  • The user must have the ability to rotate the current piece in both directions, as well as accelerate its descent.
  • When the game has ended, it should be clearly indicated that the game is over.
  • The source code must be structured and easily understandable.

Optional Features

  • Advancing falling speed after a certain number of clears (i.e. increasing difficulty level), and advancing score per line clear, proportional to speed.
  • Gravity. You may choose to implement the 'classic' gravity, in which blocks can remain floating over gaps, or you may choose to implement 'flood fill' gravity, in which blocks which have been separated from their original tetromino via line clears may fall into open gaps.
  • High scores with name input.
  • Animation after line clears, and/or after obtaining a new high score.

Limitations

  • Any libraries used (jQuery, PyGame, etc.) should be freely available.
  • Source code size must not exceed 4096 bytes, excluding whitespace and comments. Any external resources (data files, images, etc.) will be added to the code length, excluding any files which are generated, such as for high scores.
    I realize that this is a rather arbitrary restriction; my primary goal is to discourage copy-pasting of existing implementations, and to encourage brevity and self-containment.

Winning Criteria

This challenge will be judged as a popularity contest, meaning that the submission with the most upvotes will be selected as the winner. When upvoting, I encourage users to upvote any and all submissions which they feel adequately meet the above stated requirements.

The winner will be chosen no sooner than 2 weeks after the first valid solution. Additionally, I will be granting a bounty to the winner, roughly proportional to the number of upvotes this question receives (10 * #votes rounded upwards to the nearest 50). Should there be a tie after the 2 week period has expired, the competition period will be extended by one week. Should there still be a tie, I reserve the right to place the final vote.

Please ask for any clarifications. May the best implementation win!

share|improve this question
@moose I do mean source code. The question has been updated to reflect this. – primo Apr 17 at 5:54
Hey primo, can I paste and early version and (maybe) update that later? – Henrik Mühe Apr 18 at 13:25
@HenrikMühe I don't mind, but it won't start the timer unless it meets the required feature set. – primo Apr 18 at 13:41
I definitely will want to try this out. Probably will do it with SDL (though I haven't used it before, so it will make it even more fun!). – Ben Richards Apr 22 at 16:33
If only I could find my QBASIC implementation of Tetris, though! :) – Ben Richards Apr 22 at 16:46
show 2 more comments

2 Answers

up vote 8 down vote accepted

Try: http://henrikm85-tetris.nodejitsu.com/tetris.html

Update There's a global high score. Enjoy beating it or - alternatively - hacking it :-)

Screenshot: Tetris in action

CoffeeScript and HTML version, should fulfill the requirements to the best of my knowledge (and I have never really played Tetris).

$ make stats
4095

Github https://github.com/henrik-muehe/tetris

Features

  • Exponential scoring
  • Next piece indicator
  • Fair randomness
  • Rotation, move and drop down
  • Game over indication
  • Highscore with server backend that should not be that easily fakeable.
share|improve this answer
Great! But a small bug: when multiple rows are completed at the bottom of the pit, one of them not disappears. – manatwork Apr 19 at 7:55
@manatwork You mean when one of the rows to disappear is the one the most towards the bottom? I'll look into it, a screenshot would be awesome :) – Henrik Mühe Apr 19 at 7:59
That is a very nice implementation. One request: could you list which of the features are implemented? As more solutions are submitted, it will make comparison easier. – primo Apr 20 at 2:43
@primo Thanks. I'll add the list of features later. Would it be ok with you if a shortened-identifier version is <=4096 bytes of code and I'd still also show the original? I don't like mangling the code too much as it makes modification harder and I could apply shortened identifiers and such automatically :) – Henrik Mühe Apr 20 at 10:13
Feel free to show the orginal as well. And a screenshot would probably be nice too ;) – primo Apr 21 at 3:49

Pascal

Developed in FreePascal 2.6.2, should compile with Turbo Pascal 6.0 too. Only the Crt unit is used, no external resources.

program tetris;

  uses
    Crt;

  const
    width = 10;                                  { playing field width }
    height = 20;                                 { playing field height }

  const
    piece: array [1..7, 1..3, 0..1] of ShortInt = ( { piece shapes : piece, element, coordinate }
      (( 1, 0), ( 1, 1), ( 0, 1)),               { O }
      ((-1, 0), ( 1, 0), ( 2, 0)),               { I }
      (( 0, 1), ( 1, 0), ( 2, 0)),               { L }
      (( 0, 1), (-1, 0), (-2, 0)),               { J }
      ((-1, 0), ( 1, 0), ( 0, 1)),               { T }
      (( 1, 0), ( 0, 1), (-1, 1)),               { S }
      ((-1, 0), ( 0, 1), ( 1, 1))                { Z }
    );
    color: array [1..7, 0..1] of Byte = (        { piece colors : foreground, background }
      (Yellow,       Brown),                     { O }
      (LightCyan,    Cyan),                      { I }
      (LightBlue,    Blue),                      { L }
      (White,        LightGray),                 { J }
      (LightMagenta, Magenta),                   { T }
      (LightGreen,   Green),                     { S }
      (LightRed,     Red)                        { Z }
    );

  var
    area: array [1..width + 2, 1..height + 1] of Byte; { playing field }
    coord: array [0..3, 0..1] of Byte;           { precalculated element coordinates }
    played,                                      { played pieces count }
    removed,                                     { completed lines count }
    level,                                       { current level }
    score: LongInt;                              { accumulated score }
    time,                                        { current level delay }
    wait,                                        { current piece delay }
    made,                                        { completed lines in current level }
    multi,                                       { multiline count }
    screen,                                      { original screen size }
    i, j, j2: Word;                              { counters }
    current,                                     { current piece }
    next,                                        { next piece }
    x,                                           { horizontal coordinate }
    y,                                           { vertical coordinate }
    position,                                    { rotation position }
    k: Byte;                                     { pressed key }
    ok: Boolean;                                 { aggregated condition }

{ precalculates the give piece's elements coordinates }
  procedure coordinate(current, x, y, position: Byte);
  begin
    coord[0, 0] := x;
    coord[0, 1] := y;
    for i := 1 to 3 do begin
      coord[i, 0] := x + piece[current, i, position mod 2] * (Ord(position in [0, 1]) * 2 - 1);
      coord[i, 1] := y + piece[current, i, 1 - position mod 2] * (Ord(position in [0, 3]) * 2 - 1);
    end;
  end;

{ draws a piece }
  procedure draw(current, x, y, position: Byte; visible: Boolean);
  begin
    coordinate(current, x, y, position);

    for i := 0 to 3 do begin
      GotoXY(coord[i, 0] * 2 + 1, coord[i, 1]);
      if visible then begin
        TextColor(color[current, 0]);
        TextBackground(color[current, 1]);
        Write('[]');
      end else begin
        TextBackground(Black);
        Write('  ');
      end;
    end;
  end;

{ check whether a piece can be placed in given position }
  function check(x2, y2, position2: Byte): Boolean;
  begin
    coordinate(current, x2, y2, position2);

    ok := True;
    for i := 0 to 3 do if area[coord[i, 0], coord[i, 1]] <> 0 then ok := False;

    if ok then begin
      x := x2;
      y := y2;
      position := position2;
    end;

    check := ok;
  end;

begin

  Randomize;
  TextColor(LightGray);
  TextBackground(Black);
  ClrScr;
  screen := WindMax;

  for i := 0 to width + 1 do for j := 1 to height + 1 do area[i, j] := 9 * Ord(not ((i in [1..width]) and (j in [1..height])));

  Window(1, 1, width * 2 + 4, height + 2);
  for i := 1 to (width + 2) * (height + 1) do Write('##');
  Window(3, 1, width * 2 + 2, height);
  ClrScr;
  Window(1, 1, Lo(screen), Hi(screen));

  Window(width * 2 + 7, 1, width * 2 + 25, 10);
  Writeln('Next');
  Writeln;
  Writeln;
  Writeln('Piece');
  Writeln('Line');
  Writeln('Level');
  Writeln('Score');
  Window(1, 1, Lo(screen), Hi(screen));

  played := 0;
  removed := 0;
  level := 1;
  score := 0;

  current := 9;
  next := 9;
  made := 0;

  repeat

    if current = 9 then begin
      if next = 9 then next := Random(7) + 1 else draw(next, width + 9, 1, 0, False);

      current := next;
      next := Random(7) + 1;
      x := width div 2;
      y := 1;
      position := 0;
      time := 1100 - level * 100;
      wait := time;

      Inc(played);
      Inc(score);
      if made = 25 then begin
        Inc(level);
        made := 0;
      end;

      draw(next, width + 9, 1, 0, True);

      Window(width * 2 + 15, 4, width * 2 + 25, 10);
      TextColor(LightGray);
      TextBackground(Black);
      Writeln(played:5);
      Writeln(removed:5);
      Writeln(level:5);
      Writeln(score:5);
      Window(1, 1, Lo(screen), Hi(screen));

      if not check(x, y, position) then begin
        GotoXY(width * 2 + 7, 10);
        Write('Game Over');
        Break;
      end;
    end;

    draw(current, x, y, position, True);
    GotoXY(1, 1);
    repeat Delay(1);
      Dec(wait);
    until KeyPressed or (wait = 0);
    draw(current, x, y, position, False);

    if KeyPressed then begin
      k := Ord(ReadKey);
      case k of
        75, 77: check(x + Ord(k = 77) * 2 - 1, y, position);
        72, 80: check(x, y, (position + Ord(k = 80) * 2 + 1) mod 4);
        32: begin
          time := 1;
          Inc(score);
        end;
      end;
    end;

    if wait = 0 then begin
      if not check(x, y + 1, position) then begin
        draw(current, x, y, position, True);
        for i := 0 to 3 do area[coord[i, 0], coord[i, 1]] := current;

        multi := 0;
        for j := 1 to height do begin
          ok := True;
          for i := 1 to width do if area[i, j] = 0 then ok := False;

          if ok then begin
            for j2 := j downto 2 do for i := 1 to width do area[i, j2] := area[i, j2 - 1];
            for i := 1 to width do area[i, 1] := 0;

            Inc(score, 10 + multi * 2);
            Inc(removed);
            Inc(multi);

            Window(3, 1, width * 2 + 2, height);
            TextBackground(Black);
            GotoXY(1, j);
            DelLine;
            GotoXY(1, 1);
            InsLine;
            Window(1, 1, Lo(screen), Hi(screen));
          end;

        end;
        if multi <> 0 then Inc(made);

        current := 9;
      end;
      wait := time;
    end;

  until k = 27;

  ReadKey;
  TextColor(LightGray);
  TextBackground(Black);
  ClrScr;

end.

Screenshot

Tetris in Pascal

(On Linux, in XTerm window.)

Control

  • Left – move left
  • Right – move right
  • Up – rotate counterclockwise
  • Down – rotate clockwise
  • Space – drop down
  • Esc – exit

Scoring

  • played piece – 1 point
  • dropped piece – 1 point
  • completed line – 10 point
  • multiple lines – multiplier * 2 point

Level starts at 1 and increases after each 25 line completing. (Multiple lines completed at once count as 1.)

Measurement

bash-4.2$ sed '
s/{[^{}]*}//g                  # remove comments
s/^ *\| *$//g                  # trim leading and trailing spaces
/^$/d                          # remove empty lines
s/ *\([:=<>,;+*-]\+\) */\1/g   # no space around operators
' tetris.pas | wc -c
3697
share|improve this answer
+1 for tolerating pascal – jamylak May 2 at 12:41

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.