(Re) Menerapkan Tetris

43

Dengan semangat menerapkan kembali permainan video klasik , saya ingin mengundang komunitas untuk menciptakan implementasi Tetris terbaik mereka .

masukkan deskripsi gambar di sini
Untuk referensi, tangkapan layar dari versi Tetris resmi NES.

Fitur yang Diperlukan

  • Sistem penilaian yang masuk akal harus ada, yang memberikan penghargaan multi-line lebih dari membersihkan single-line. Skor saat ini harus terlihat setiap saat.
  • Potongan berikutnya yang akan muncul harus ditunjukkan dengan cara tertentu.
  • Distribusi tujuh tetromino harus cukup adil (yaitu pseudo-dipilih secara acak).
  • Pengguna harus memiliki kemampuan untuk memutar bagian saat ini di kedua arah, serta mempercepat penurunannya.
  • Ketika permainan telah berakhir, harus dengan jelas ditunjukkan bahwa permainan telah berakhir.
  • Kode sumber harus terstruktur dan mudah dimengerti.

Fitur pilihan

  • Memajukan kecepatan jatuh setelah sejumlah pembersihan (yaitu meningkatkan tingkat kesulitan), dan memajukan skor per baris jelas, sebanding dengan kecepatan.
  • Gravitasi. Anda dapat memilih untuk menerapkan gravitasi 'klasik', di mana blok dapat tetap melayang di atas celah, atau Anda dapat memilih untuk menerapkan gravitasi 'isi banjir', di mana blok yang telah dipisahkan dari tetromino aslinya melalui jalur garis dapat jatuh menjadi terbuka kesenjangan.
  • Skor tinggi dengan input nama.
  • Animasi setelah baris dihapus, dan / atau setelah mendapatkan skor tinggi baru.

Keterbatasan

  • Setiap perpustakaan yang digunakan ( jQuery , PyGame , dll.) Harus tersedia secara bebas.
  • Ukuran kode sumber tidak boleh melebihi 4096 byte, tidak termasuk spasi dan komentar. Sumber daya eksternal apa pun (file data, gambar, dll.) Akan ditambahkan ke panjang kode, tidak termasuk file apa pun yang dihasilkan, seperti untuk skor tinggi.
    Saya menyadari bahwa ini adalah pembatasan yang agak arbitrer; tujuan utama saya adalah untuk mencegah copy-paste dari implementasi yang ada, dan untuk mendorong singkat dan mandiri.

Kriteria Menang

Tantangan ini akan dinilai sebagai kontes popularitas , yang berarti bahwa pengajuan dengan jumlah suara terbanyak akan dipilih sebagai pemenang. Saat melakukan upvoting, saya mendorong pengguna untuk membatalkan setiap dan semua kiriman yang mereka rasa cukup memenuhi persyaratan yang disebutkan di atas.

Pemenang akan dipilih tidak lebih dari 2 minggu setelah solusi valid pertama. Selain itu, saya akan memberikan hadiah kepada pemenang, kira-kira sebanding dengan jumlah upvotes yang diterima pertanyaan ini ( 10 * #votesdibulatkan ke atas ke 50 terdekat). Jika ada seri setelah periode 2 minggu berakhir, periode kompetisi akan diperpanjang satu minggu. Jika masih ada seri, saya berhak untuk memberikan suara terakhir.

Silakan minta klarifikasi. Semoga implementasi terbaik menang!

primo
sumber
@ Banyak saya maksudkan kode sumber. Pertanyaannya telah diperbarui untuk mencerminkan hal ini.
primo
Hai primo, dapatkah saya menempel dan versi awal dan (mungkin) memperbaruinya nanti
Henrik Mühe
@ HenrikMühe Saya tidak keberatan, tetapi itu tidak akan memulai timer kecuali jika memenuhi set fitur yang diperlukan.
primo
Saya pasti ingin mencoba ini. Mungkin akan melakukannya dengan SDL (meskipun saya belum pernah menggunakannya sebelumnya, sehingga akan membuatnya lebih menyenangkan!).
Ben Richards
Tapi seandainya saya bisa menemukan implementasi Tetris QBASIC saya! :)
Ben Richards

Jawaban:

19

Coba: http://tetris.muehe.org

Pembaruan Ada skor tinggi global. Nikmati pemukulan atau - sebagai alternatif - meretasnya :-)

Tangkapan layar: Tetris beraksi

Versi CoffeeScript dan HTML, harus memenuhi persyaratan sepengetahuan saya (dan saya belum pernah benar-benar memainkan Tetris).

$ make stats
4095

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

fitur

  • Penilaian eksponensial
  • Indikator bagian berikutnya
  • Keacakan yang adil
  • Rotasi, gerakkan dan jatuhkan
  • Game melebihi indikasi
  • Highscore dengan backend server yang tidak boleh yang mudah fakeable.
Henrik Mühe
sumber
Besar! Tapi bug kecil: ketika beberapa baris selesai di bagian bawah lubang, salah satunya tidak menghilang.
manatwork
@manatwork Maksudmu saat salah satu baris untuk menghilang adalah yang paling mengarah ke bawah? Saya akan memeriksanya, tangkapan layar akan luar biasa :)
Henrik Mühe
Itu implementasi yang sangat bagus. Satu permintaan: dapatkah Anda membuat daftar fitur mana yang diterapkan? Semakin banyak solusi diajukan, itu akan membuat perbandingan lebih mudah.
primo
1
@ user2023370, ini byte, meskipun ...
Henrik Mühe
1
Tautan tidak berfungsi.
Erik the Outgolfer
18

Pascal

Dikembangkan di FreePascal 2.6.2, juga harus dikompilasi dengan Turbo Pascal 6.0. Hanya unit Crt yang digunakan, tidak ada sumber daya eksternal.

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.

Tangkapan layar

Tetris dalam Pascal

(Di Linux, di jendela XTerm.)

Kontrol

  • Left - bergerak ke kiri
  • Right - bergerak ke kanan
  • Up - Putar berlawanan arah jarum jam
  • Down - putar searah jarum jam
  • Space - jatuhkan
  • Esc - keluar

Mencetak gol

  • bagian yang dimainkan - 1 poin
  • potongan jatuh - 1 poin
  • baris selesai - 10 poin
  • beberapa baris - pengganda * 2 poin

Level dimulai pada 1 dan meningkat setelah setiap 25 baris selesai. (Beberapa baris diselesaikan sekaligus dihitung sebagai 1.)

Pengukuran

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
manatwork
sumber
17

Java (Swing)

Ini adalah implementasi dari edisi Game Boy historis pertama dari Nintendo (c) dari tahun 1989.

Game Boy Tetris Nintendo

Cara bermain:

Z= memutar kiri
X= memutar kanan
Left= bergerak ke kiri
Right= bergerak ke kanan
Down= bergerak ke bawah (perlahan)
Up= memutar ke kiri (hanya untuk penggunaan yang lebih mudah)
R= mengatur ulang permainan

Saya menghindari menggunakan lebih dari satu kelas (karena aspek golf muncul di pikiran saya). Tapi sekarang ini sudah tidak bisa golf lagi ... Namun, saya zip dan Base64-encoded satu Font-File dan satu Image-File, jadi saya bisa menggunakannya dalam satu-satunya file kelas.

Untuk menjalankannya, salin kode Java ke IDE Anda dan mulai. Anda tidak memerlukan perpustakaan atau sumber daya tambahan apa pun.

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.xml.bind.DatatypeConverter;

public class JTetris extends javax.swing.JFrame {
    static Image IMAGE;
    static Font FONT;

    static {
        ZipInputStream zipImage = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
            "UEsDBBQAAAAIABmiWkRZ0oOouwAAAJYKAAAFAAAAYy5ibXBz8p3GxQAGZUCsAcQ3gFgCiBkZWMDiCkB5GBCA0iJyLAwl+3oYYpJsGK58u8NAKjBGAgyUgVG7QHYxKUGBAjDiBJUUQUhQAMQGA3xsFL3MMBMNRrpdEABhQxQLCiDYmHZBxCF2QdhAu8CyEHLk2cWkBJSH" +
            "xxcoCgyg5hsbAgWhbBgJUg9Wg24XWDGQHJF2QeOCUruQwEi0S0kRe5qHKCY2zQsgkv0ItAsIKC97EQnecGTYRQUwKNsbNAYAUEsBAj8AFAAAAAgAGaJaRFnSg6i7AAAAlgoAAAUAJAAAAAAAAAAgAAAAAAAAAGMuYm1wCgAgAAAAAAABABgAkJCiSyczzwGQkKJLJzPP" +
            "AZCQoksnM88BUEsFBgAAAAABAAEAVwAAAN4AAAAAAA=="
        )));

        ZipInputStream zipFont = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
            "UEsDBBQAAAAIAEq5UEN0MGyWoggAAHgrAAAFAAAAYS50dGbklsvvC1EUx78zU+/3OxHiEuLd1jMlxKNR70cQCxumY9oOnU5Np6osdIGEDYmwZGElkYh/gI2FBUIsvFdILNgh8Rxft4d6RyxEYvq7PZ97es73nHNv8mthAOiJFiyotRtS0xMnum4Gej0HsNXx7erbI6+7" +
            "ks8CxqNiuVk48qTbVKB3BIy8XXLtbVv6x0cBkwuzS3SYrffvuL8MYGzJj3av3BfP4P4p8yeUA8cGxqWBSau4n+Lbu6tYET8AEoMBqIrtuzuPz33D/Uyg28VqUItgxtTrvRGAAoW4LK7BsHTnb5HAE9qB6E+PidGYhCTmYjP24AxuxTGYR98UpLEIdtsXP4zvxXfjO/HN" +
            "+Gp8Kg6p9O8//8ucRns93ZK6vqXfvBccXLuv7b/QH2LjVtyyYLUAdIfJaMmz6Gc83w3yd9OuSLS0WmcxRLj1xYJY/Gz9Wcyfr79XJ6aO3MEKTEAPWsDE988Y4yQM4RGAsEEeIWyiGzLCFsZjgXACY1EQ7oJeOCzclZnHhbthHs4Ld8cQPBPugT6GKdwT841+wr0wzFgl" +
            "3BtpwxHugxHGMeG+mGycEx6EicbdNhusYFqwYCQ4LTJmL2GDnBU20dfcK2xhjXlIOIFl5i3hLhhqjRLuiow1S7gbdlu+cHdMsh4L98DwRD/hntiTmCjcC8nEaeHe2Jq4IdwHmS7ThPtiUxdPeBDWd7nYZgPo33VANqg2Q69YitQ6Owo9Z4daZdd3uGp6etqMDfWqG6rV" +
            "dugF9Fa2qQlL+T9XLQ6aE9V6t1gv22EuqEQborDuROoX0b/87JPUJjeseUFFTUumdfhUHT71Y/hUHT71Y/jULyp6NWWrKLS3ub4d7lBBQeU2LFc6oBRU1fJK5IYVO6KoXVZL/fyyUhRV56ZSBUbUtETSCfyv5h5+6fWveh1+6ZVq2DWVr3vlSDW8qKQ6/fQU+UajkdQl" +
            "2MTHAj+s+sWW/dVSDG6kMpnMjGlzsqHLpne5Khv4Pj9TiyJ2mK9/DBQtR0KcdkQyCIupsue4lZpbS+WbqRnJdCr3USLvFdXOuu3s8CpFtcetlpphTW0P2Lzf5CS7Vd7dli0uWbStXIqQRYAqmgjhoYgSIiisg41Iexzs4H4V93WSS56ONKZhBjbQU6UnpG81bB0dSGwF" +
            "20gTsJTs66zFCFhjImk990XmlnVODgGjI6qxHr0Orfoz7T/P+66rTVqjprMrUJw3ifQX6lM76mRRJ31WJ4s66cczetRXjFbaazPfha9VeeLMKPA9x5zltB2FEqlKz3K9Zy+0NomdaCpDsQcfeSzTdxkxei5SfBVEo9bpglM59Po/v281HJfw+k/PVWe/oqcBW0+bp47H" +
            "HiPt82hLpB+dT89vum/oV7IzhZyETPD7s/7wUzk/elKi3CBl9GsGb38Osox05aR36SmzWs2XPIVFiOQMOSVE8Zu+nG9UnK80krQhiowr65twtdfVXeXR5PsMxqRpc5+7yJOKtDtZ09a356GiPXvgsm6JeaHubzsCOXkfTbmT3VrB5Q1mmbMEi0hl3TNM86VxFF2QMOXL" +
            "XVsuNnEF8mtAHquDQG7DxvVYSOFbbbfVQovG0NwOVV/kFKj3g4eVJZ5/3dALAzGUIl8/MMTDAvEBAC9oe9Hu/WJ/kD9s78f3gfdvpKrPs6H/A+1VkJtADAMnWXFAqEIVqjhw6KEfzQvan/Ud/cQmJdnRjoyzBLUCFAxeZ8aOHSfgk3aU+Kp6oFqX7/mn2s5L6KF+JPoC" +
            "8QOVOqdFAqiOpICIqxbYo82LiTEcccIZF7zjgyiD14JJ9MrUfmsUWAlrx3nybJXJ/b6v1/yIene+vjHV+OQrxNUsQ5XCpSYvtZBCXZeWD85FbA8CGE3TXXlkQwTIJkI1cvQrGmlXFAOiWYslTyXZJ1XX5JWTeT8IXbEKNaBKky2OmVbG17et7AMet8D6Vlh1kZq8sgdj" +
            "RckB7b9Tnx3ELVCmouKUtBHriQZczDu8dlk1i5w+SwGrFYp5mvtc03aE9NdGqP2rSNu3slZt6Ea4zTmuQc/Zcu1q0XEqj52KR/J9YIIwfN4gZOREc/Xdi+O4W6/anb3a9dGGxBoe1NRf8szqSdveBHqRE8SK4PtAbHr2cXFqh9rORFwMcgVACL6qgdi+WY9mzbjJ1agC" +
            "A2Uh8jgnE1wOHu0lvY4L+RGIZCzMHsnS+bwfbljlmyIz/X2Cyaf8t0jUNzue7CR+ZC32wD0kgyL537VkJY/XUtXl4/f3DWJDnotxhmJ67IyMMD2if9qO9ovOCYvrzqWCIf5Z+I4H4iCiODiKdFob37e6M40fO8Cyqtp4cxjXh5iNz2KXn84f5pI65vQ5nKrUWbPJ+fy7" +
            "nMF/ARx6tzdmc0q43dv7n3IcnZlxcHefezcFAJt3de7FX/bLHQWAEAaiuPe/srhiMcEncWDZMkUKwe/oJM+R+Ip1IqfPB1WCakTEbi7q6Jyb9sZ5oAgoYThNGjp24i9uhItx5j6DbyffdZC4XuWaY4gwHjnb1m/m0cxzUENOUK/VVs+f/S1qh79xC6dP/B+YfgEZgCsT" +
            "XRT0T4OPuu6oXXK5r1OxdrBO2xkcikrZz/l3jjozreH9LYpVi1WLVYtVi1VfjbZVGUfbqqNt1dG26mhbFaBB21blBJIgO+HzavBZNXWgKUQCsK8w58oQKQJ19BhR5mHMyUHYuOfWEHOEmL7+B49bDP3Dc/4Q0DW9aSsURmEYfqGx97+Po31eu4MFYdglBEHw47qIMAiD" +
            "YEEQBAuCYF1AGIZBGIRBGARB0MvBA3N8MMccc3wAkN1Zu2umvfeihkba+ueezEstLSxnogE8JMpk9thQDqGsVIWO8JSq0Bmeo/bwEjTUCV57Mn/ragPvUZkO1iWV+8u5rvDRl/NnRyv4CoqawndQ1Bx+0FhL+G0q1xX+oi6QDOAftTWBStAQqh0VUKtBvaTdDVBLAQI/" +
            "ABQAAAAIAEq5UEN0MGyWoggAAHgrAAAFACQAAAAAAAAAIAAAAAAAAABhLnR0ZgoAIAAAAAAAAQAYAABukYC8ys4BJXTvDjUzzwEldO8ONTPPAVBLBQYAAAAAAQABAFcAAADFCAAAAAA=")
        ));

        try {
            zipImage.getNextEntry();
            zipFont.getNextEntry();
            IMAGE = ImageIO.read(zipImage);
            FONT = Font.createFont(0, zipFont);
            FONT = FONT.deriveFont(20.0f);
        } catch (Exception e) {
            System.exit(1);
        }
    }

    int[] speeds = {887,820,753,686,619,552,468,368,284,184,167,150,133,117,100,100,83,83,66,66,50};
    int[] points = {40,100,300,1200};
    int[][][] stones = {
        {{0,1},{1,1},{2,1},{0,2}},      // L 0
        {{0,1},{1,1},{2,1},{3,1}},      // I 1
        {{0,1},{1,1},{2,1},{2,2}},      // J 2
        {{0,1},{1,1},{2,1},{1,2}},      // T 3
        {{0,1},{1,1},{1,2},{2,2}},      // Z 4
        {{1,1},{2,1},{0,2},{1,2}},      // S 5
        {{1,1},{2,1},{1,2},{2,2}}       // O 6
    };
    Color[] colors = {
        new Color(220, 246, 212), // light
        new Color(140, 190, 116), // green
        new Color(60, 98, 92),    // blue
        new Color(4, 30, 20)      // dark
    };
    int FREE = 0;
    int BORDER = 8;
    int DELTA = 20;
    int WIDTH = 10;
    int HEIGHT = 20;
    int SIZE = 24;

    int[][] field = new int[WIDTH][HEIGHT];
    int[][] stone;
    int deltaX;
    int deltaY;
    int curStone;
    int nextStone = new Random().nextInt(7);
    int score;
    int level;
    int lines;
    int steps;
    int speed = speeds[0];

    Timer tickTimer = new Timer();
    Image dbImage;
    Image staticImage;
    Graphics dbg;

    int anim = -1;
    boolean blink = false;
    ArrayList<Integer> removeLinesRows = new ArrayList<Integer>();
    boolean keyDown = true;
    boolean freezeKeys = false;

    private JTetris() {
        setDefaultCloseOperation(2);
        setResizable(false);
        setSize(475, 480);

        addPropertyChangeListener("isDirty", new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
                repaint();
            }
        });

        reset();

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                super.windowClosed(e);
                tickTimer.cancel();
            }
        });

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                keyDown = true;
            }

            @Override
            public void keyPressed(KeyEvent e) {
                if (freezeKeys) {
                    keyDown = false;
                    return;
                }

                switch (e.getKeyCode()) {
                    case 82: reset(); break;
                    case 37: updateField(-1, 0, false); break;
                    case 39: updateField(1, 0, false); break;
                    case 40: if (keyDown) updateField(0, 1, true); break;
                    case 38:
                    case 90: rotate(false); break;
                    case 88: rotate(true);
                }
            }
        });

        setVisible(true);
    }

    private void startTickTimer() {
        if (tickTimer != null) {
            tickTimer.cancel();
        }

        freezeKeys = false;

        tickTimer = new Timer();
        tickTimer.schedule(new TimerTask() {
            public void run() {
                updateField(0, 1, false);
            }
        }, 0, speed);
    }

    private void startRemoveLineTimer() {
        if (!removeLinesRows.isEmpty()) {
            anim = 7;
        }

        if (tickTimer != null) {
            tickTimer.cancel();
        }

        tickTimer = new Timer();
        tickTimer.schedule(new TimerTask() {
            public void run() {
                if (anim > 0) {
                    blink = !blink;
                    firePropertyChange("isDirty", false, true);
                }

                if (anim-- < 0) {
                    blink = false;
                    tickTimer.cancel();
                    if (removeLinesRows.size() > 0) {
                        int delta = 0;
                        for (int row : removeLinesRows) {
                            for (int r = row + delta++; r > 2; r--) {
                                for (int c = 0; c < WIDTH; c++) {
                                    field[c][r] = field[c][r - 1];
                                }
                            }
                        }

                        score += points[removeLinesRows.size() - 1] * (level + 1);
                        lines += removeLinesRows.size();
                        level = lines / 10;
                        speed = speeds[level > 20 ? 20 : level];
                        removeLinesRows.clear();
                    }

                    createNextStone();

                    startTickTimer();
                }
            }
        }, 50, 160);
    }

    public static void main(String[] a) {
        new JTetris();
    }

    @Override
    public void paint(Graphics g) {
        if (dbImage == null) {
            dbImage = createImage(getWidth(), getHeight());
            dbg = dbImage.getGraphics();
            dbg.setFont(FONT);

            staticImage = createImage(getWidth(), getHeight());
            Graphics sg = staticImage.getGraphics();
            sg.setFont(FONT);

            sg.setColor(colors[3]);
            sg.fillRect(0, 0, getWidth(), getHeight());

            sg.setColor(colors[0]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 70, 170, 58);
            sg.setColor(colors[2]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 73, 170, 52);
            sg.setColor(colors[0]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 95, 170, 27);
            sg.setColor(colors[2]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 98, 170, 3);

            sg.setColor(colors[1]);
            sg.fillRoundRect(40 + 13 * SIZE - 15, 40 + 13 * SIZE - 15, SIZE * 4 + 30, SIZE * 4 + 30, 20, 20);
            sg.setColor(colors[0]);
            sg.fillRoundRect(40 + 13 * SIZE - 12, 40 + 13 * SIZE - 12, SIZE * 4 + 24, SIZE * 4 + 24, 15, 15);

            sg.fillRoundRect(330 - 6, 60 - 6, 120 + 12, 25 + 12, 15, 15);
            sg.fillRoundRect(330 - 6, 155 - 6, 120 + 12, 60 + 12, 15, 15);
            sg.fillRoundRect(330 - 6, 235 - 6, 120 + 12, 60 + 12, 15, 15);
            sg.setColor(colors[2]);
            sg.fillRoundRect(40 + 13 * SIZE - 9, 40 + 13 * SIZE - 9, SIZE * 4 + 18, SIZE * 4 + 18, 15, 15);

            sg.fillRoundRect(330 - 3, 60 - 3, 120 + 6, 25 + 6, 15, 15);
            sg.fillRoundRect(330 - 3, 155 - 3, 120 + 6, 60 + 6, 15, 15);
            sg.fillRoundRect(330 - 3, 235 - 3, 120 + 6, 60 + 6, 15, 15);
            sg.setColor(colors[3]);
            sg.fillRoundRect(40 + 13 * SIZE - 6, 40 + 13 * SIZE - 6, SIZE * 4 + 12, SIZE * 4 + 12, 15, 15);

            sg.setColor(colors[0]);
            sg.fillRoundRect(330, 60, 120, 25, 5, 5);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 40, 3, (HEIGHT - 2) * SIZE);
            sg.fillRoundRect(40 + 13 * SIZE - 3, 40 + 13 * SIZE - 3, SIZE * 4 + 6, SIZE * 4 + 6, 5, 5);
            sg.fillRoundRect(330, 155, 120, 60, 15, 15);
            sg.fillRoundRect(330, 235, 120, 60, 15, 15);

            sg.setColor(colors[3]);
            sg.drawString("SCORE", 340, 80);
            sg.drawString("LEVEL", 340, 180);
            sg.drawString("LINES", 340, 260);

            for (int r = 0; r < (HEIGHT - 2) * 24 / 18; r++) {
                sg.drawImage(IMAGE, 16, 40 + r * 18, 16 + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
                sg.drawImage(IMAGE, 40 + WIDTH * SIZE, 40 + r * 18, 40 + WIDTH * SIZE + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
            }
        }

        dbg.drawImage(staticImage, 0, 0, this);

        dbg.setColor(colors[3]);
        dbg.drawString(String.format("%6s", score > 999999 ? 999999 : score), 320, 120);
        dbg.drawString(String.format("%4s", level > 20 ? 20 : level), 340, 205);
        dbg.drawString(String.format("%4s", lines > 9999 ? 9999 : lines), 340, 285);

        for (int x = 0; x < 4; x++) {
            drawField(dbg, stones[nextStone][x][0] + 13, stones[nextStone][x][1] + 13, nextStone + 1);
        }

        for (int r = 2; r < HEIGHT; r++) {
            for (int c = 0; c < WIDTH; c++) {
                drawField(dbg, c, r - 2, field[c][r]);
            }
        }

        if (blink) {
            if (anim == 0)  {
                dbg.setColor(colors[0]);
            } else {
                dbg.setColor(colors[1]);
            }

            for (int row : removeLinesRows) {
                dbg.fillRect(40, 40 + (row - 2) * SIZE, SIZE * WIDTH, SIZE);
            }
        }

        g.drawImage(dbImage, 0, 0, this);
    }

    private void drawField(Graphics g, int x, int y, int idx) {
        if (idx > DELTA) {
            idx -= DELTA;
        }

        g.drawImage(IMAGE, 40 + x * SIZE, 40 + y * SIZE, SIZE + 40 + x * SIZE, SIZE + 40 + y * SIZE, idx * SIZE, 0, (idx + 1) * SIZE, SIZE, this);
    }

    private void createNextStone() {
        curStone = nextStone;
        stone = stones[curStone];
        deltaX = (WIDTH + 1) / 2 - 2;
        deltaY = 1;

        if (!isMoveable(0, 0)) {
            tickTimer.cancel();
            JOptionPane.showMessageDialog(this, "Game over! Press OK for a new game.");
            reset();
            return;
        }

        updateField(0, 0, false);
        nextStone = new Random().nextInt(7);
    }

    private void reset() {
        score = 0;
        steps = 0;
        lines = 0;
        level = 0;
        speed = speeds[0];

        for (int r = 0; r < HEIGHT; r++) {
            for (int c = 0; c < WIDTH; c++) {
                field[c][r] = FREE;
            }
        }

        createNextStone();
        startTickTimer();
    }

    private void updateField(int hor, int ver, boolean player) {
        if (isMoveable(hor, ver)) {
            if (player) {
                steps++;
            }

            for (int x = 0; x < 4; x++) {
                field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
            }

            deltaX += hor;
            deltaY += ver;

            for (int x = 0; x < 4; x++) {
                field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
            }

            firePropertyChange("isDirty", false, true);
        } else if (ver == 1) {
            freezeKeys = true;
            fixStone();
            removeLines();
        }
    }

    private void removeLines() {
        outer:
        for (int r = HEIGHT - 1; r >= 0; r--) {
            for (int c = 0; c < WIDTH; c++) {
                if (field[c][r] == FREE) {
                    continue outer;
                }
            }

            removeLinesRows.add(r);
        }

        startRemoveLineTimer();
    }

    private void fixStone() {
        score += steps;
        steps = 0;

        for (int x = 0; x < 4; x++) {
            field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1 + DELTA;
        }
    }

    private boolean isMoveable(int hor, int ver) {
        for (int x = 0; x < 4; x++) {
            int c = stone[x][0] + deltaX + hor;
            int r = stone[x][1] + deltaY + ver;

            if (c < 0 || c >= WIDTH || r < 0 || r >= HEIGHT) {
                return false;
            }

            if (field[c][r] > DELTA) {
                return false;
            }
        }

        return true;
    }

    private void rotate(boolean r) {
        if (curStone == 6) { // O
            return;
        }

        synchronized (field) {
            int[][] o = new int[4][2];

            for (int x = 0; x < 4; x++) {
                o[x] = new int[] { r ? 3 - stone[x][1] : stone[x][1], r ? stone[x][0] : 3 - stone[x][0] };
            }

            if (canRotate(o)) {
                for (int x = 0; x < 4; x++) {
                    field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
                }

                stone = o;

                for (int x = 0; x < 4; x++) {
                    field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
                }

                firePropertyChange("isDirty", false, true);
            }
        }
    }

    private boolean canRotate(int[][] o) {
        for (int x = 0; x < 4; x++) {
            if (o[x][0] + deltaX < 0 ||
                o[x][0] + deltaX >= WIDTH ||
                o[x][1] + deltaY >= HEIGHT ||
                field[o[x][0] + deltaX][o[x][1] + deltaY] > DELTA) {
                return false;
            }
        }

        return true;
    }
}

TODO:

  • batu yang lebih cepat di level yang lebih tinggi (selesai!)
  • animasi garis hapus asli (selesai!)
  • poin yang benar untuk garis (selesai!)
  • interupsi bergerak turun di atas batu baru (selesai!)
  • layar skor tinggi
  • perilaku rotasi asli
  • menu untuk memilih level
  • layar game-over
  • audio

Komentar dipersilahkan :)

bobbel
sumber
2
Saya sangat, sangat suka yang ini. Satu saran untuk kegunaan: ketika pengguna memegang ↓ untuk menjatuhkan blok, secara sistematis 'lepaskan' kunci ini setelah blok mendarat (yaitu, pengguna perlu melepaskan kunci, dan kemudian tekan lagi untuk membuat blok berikutnya dipercepat) .
primo
Besar! Saya sudah memikirkan, bagaimana cara menanganinya. Ini akan menjadi metode yang bagus! Terima kasih!
bobbel
1
+1 Untuk membuat saya menjerit seperti anak berusia delapan tahun dengan Nintendo GameBoy pertamanya!
WallyWest
Tidak yakin apakah kode Java yang mengerikan itu mengerikan atau retro. +1 lagian. ;)
TwoThe
Untuk kelas apa pun yang digunakan sekali dan hanya sekali, Anda dijamin untuk mempersingkat kode Anda dengan tidak mengimpornya, dan sebaliknya menggunakan nama yang sepenuhnya memenuhi syarat dalam kode. Misalnya, hapus import java.awt.event.KeyAdapter;dan ubah new KeyAdaptermenjadi new java.awt.event.KeyAdapter.
BenGoldberg
8

Lua - 2876

Tetris di terminal, bekerja pada sebagian besar sistem unix, lua murni, tidak ada lib tambahan yang diperlukan.

Kontrol adalah: wasd atau hjkl, w / k untuk drop, s / j untuk memutar, ad / hl untuk bergerak

Kecepatan meningkat dengan skor, setiap kali beberapa baris dihapus, Anda mendapatkan kuadrat jumlah garis yang dihancurkan

Ini bukan solusi golf yang paling mungkin, tetapi saya memutuskan untuk golf sedikit pula. Baris baru hanya untuk menyesuaikan teks dalam 80 cols, saya tidak memasukkannya dalam jumlah karakter.

math.randomseed(os.time())if arg[1]then local function s(e)local e=io.popen(
"sleep "..e)e:read"*a"return e:close()end local l={{0,1,1,0,1,1},{-1,1,-1,0,1,0}
,{-1,0,1,0,1,1},{-1,0,0,1,1,0},{-1,1,0,1,1,0},{-1,0,0,1,1,1},{-1,0,1,0,2,0}}
function T(t,e,o)if o>1 then return T(-e,t,o-1)end return t,e end local t 
function fit(i,n,r,o)if n<1 or n>10 or r<1 or t[r][n]~=0 then return end for e=1
,6,2 do local e,o=T(l[i][e],l[i][e+1],o)e,o=e+n,o+r if e<1 or e>10 or o<1 or((t[
o]or{})[e]or 0)~=0 then return end end return i end function put(e,r,i,n)t[i][r]
=e for o=1,6,2 do local n,o=T(l[e][o],l[e][o+1],n)n,o=n+r,o+i;(t[o]or{})[n]=e 
end end while 1 do os.execute"clear"io.write("\27[0;37m+----------++----+\r\n"..
("|          ||    |\r\n"):rep(2)..
"|          |+----+\r\n|          |Score:\r\n"..("|          |\r\n"):rep(16)..
"+----------+\27[20F\27[C")t={}for e=1,20 do t[e]={}for o=1,10 do t[e][o]=0 end 
end local e,o,f,r,a,c,n=20,5,0,math.random(7),math.random(7),0,1 while 1 do f=f+
1 s(.1)local d=io.open(arg[1],"rb")or os.exit(0,io.write"\27[49;39m",io.stdout:
flush(),os.execute"clear")local i=d:read(1)while i do if i=="a"or i=="h"then if 
fit(r,o-1,e,n)then o=o-1 end elseif i=="d"or i=="l"then if fit(r,o+1,e,n)then o=
o+1 end elseif i=="w"or i=="k"then while fit(r,o,e-1,n)do e=e-1 end elseif i==
"s"or i=="j"then if fit(r,o,e,n+1)then n=n%4+1 end end i=d:read(1)end d:close()d
=io.open(arg[1],"w")d:write()d:close()if f%(math.max(1,10-math.floor(c/20)))==0 
then if fit(r,o,e-1,n)then e=e-1 else put(r,o,e,n)r,e,o,a,n=a,20,5,math.random(7
),1 if not fit(r,o,e,n)then break end local e=1 local o=0 while e<21 do local n=
1 for o=1,10 do n=n*t[e][o]end if n~=0 then table.remove(t,e)local e={}for o=1,
10 do e[o]=0 end table.insert(t,e)o=o+1 else e=e+1 end end c=c+o*o end end put(r
,o,e,n)for e=20,1,-1 do for o=1,10 do io.write("\27[3"..t[e][o].."m"..(t[e][o]
==0 and" "or"#"))end io.write"\27[E\27[C"end io.write("\27[20F\27[13C\27[3",a,
"m")for e=1,0,-1 do for n=-1,2 do local o for t=1,6,2 do if l[a][t]==n and l[a][
t+1]==e then io.write"#"o=e end end if e==0 and n==0 then io.write"#"o=e end if 
not o then io.write" "end end io.write"\27[4D\27[B"end io.write(
"\27[2B\27[D\27[49;37m",c,"\27[4F\27[C")t[e][o]=0 for i=1,6,2 do local r,n=T(l[r
][i],l[r][i+1],n)r,n=r+o,n+e;(t[n]or{})[r]=0 end end io.write"\27[49;39m"io.
stdout:flush()os.execute"clear"io.write
"\aGame over. Will automatically start new game in 5 seconds. Use ^C to exit"s(5
)io.stdout:flush()end else local e=os.tmpname()local o=io.open(e,"w")local n=
newproxy(true)getmetatable(n).__gc=function()os.execute"stty -raw echo"end os.
execute"stty raw -echo"local t=""i=0 while arg[i]do t=arg[i].." "..t i=i-1 end 
os.execute(t..e.." &")while 1 do o:close()local t=io.read(1)o=io.open(e,"a")o:
write(t)if t=="\3"then o:close()os.remove(e)os.execute"stty -raw echo"
getmetatable(n).__gc=nil os.execute"sleep 0.1"os.exit()end end end

tetris di terminal

mniip
sumber
3
Apakah Anda memiliki versi tanpa ungolfed?
nyuszika7h
7
@ nyuszika7h Apa maksudmu? Dia menulisnya seperti itu menggunakan cat.
Camilo Martin
1
@ CamiloMartin apa cat, saya sudah menggunakan butterfiles
mniip
3
@niip Oh ya! Good Ol ' C-x M-c M-butterfly...
Camilo Martin
6

Mathematica

Kode ini ditulis dalam Mathematica oleh Xiangdong Wen dan sebenarnya dapat dimainkan di browser web di sini: Shape Descender (klik pada gambar untuk memulai tombol panah). Di bawah ini adalah cuplikan layar dan kode lengkap - yang cukup menarik untuk aplikasi web lengkap game ini.

masukkan deskripsi gambar di sini

Kode

allBlocks = {{{{1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 
      0}}, {{0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 
      0}}}, {{{0, 2, 0, 0}, {2, 2, 2, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 2, 0, 0}, {0, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {2, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}, {{0, 2, 0, 0}, {2, 2, 0, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}}, {{{0, 0, 3, 0}, {3, 3, 3, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 3, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {3, 3, 3, 0}, {3, 0, 0, 0}, {0, 0, 0, 
      0}}, {{3, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 0}}},
   {{{0, 0, 0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 
      0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 
      0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 
      0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}}, {{{0, 0, 0, 
      0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5, 
      0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}, {{0, 0, 0, 
      0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5, 
      0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}}, {{{6, 6, 0, 
      0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6, 
      0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}, {{6, 6, 0, 
      0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6, 
      0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}}, {{{0, 7, 7, 
      0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0, 
      0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}, {{0, 7, 7, 
      0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0, 
      0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}}};
smallBoard = 
  Table[If[i == 1 || j == 1 || i == 6 || j == 6, 9, 0], {i, 1, 6}, {j,
     1, 6}];

color[v_] := 
  Switch[v, 0, Black, 1, Yellow, 2, Blue, 3, Magenta, 4, Cyan, 5, Red,
    6, Orange, 7, Green, 9, Gray, _, Gray];
showSquare[{i_, j_}, v_] := {color[v], 
   Rectangle[{i + 0.05, j + 0.05}, {i + 0.95, j + 0.95}]};
showHintBlock[n_, p_, x_, y_] := 
  Table[showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], {i, 1, 
    4}, {j, 1, 4}];


initBoard[
   Hold[board_, num_]] := {board = 
    Table[If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 9, 0], {i, 1, 
      16}, {j, 1, 28}], 
   Table[board[[i, 24]] = 0; board[[i, 25]] = 0; 
    board[[i, 26]] = 0, {i, 7, 10}]; 
   Table[board[[i, 25]] = 0, {i, 1, 16}]; board[[6, 25]] = 9, 
   board[[11, 25]] = 9,
   Table[If[Mod[j + i, 2] == 0, board[[j, i + 3]] = 9], {i, 1, 
     num}, {j, 4, 13}]};

bMoveTo[n_, p_, x_, y_, Hold[board_]] := 
  Total[Table[
     allBlocks[[n, p, i, j]]* board[[i + x, j + y]], {i, 1, 4}, {j, 1,
       4}], 2] == 0;
bGameOver[n_, p_, Hold[board_]] := 
  Not[bMoveTo[n, p, 6, 22, Hold[board]]];
bLeft[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x - 1, y, Hold[board]];
bRight[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x + 1, y, Hold[board]];
bDown[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x, y - 1, Hold[board]];
bRotateAnticlock[n_, p_, x_, y_, Hold[board_]] := 
 Module[{tp}, tp = p - 1; If[tp == 0, tp = 4]; 
  bMoveTo[n, tp, x, y, Hold[board]]]; 
bRotateClockwise[n_, p_, x_, y_, Hold[board_]] := 
 Module[{tp}, tp = p + 1; If[tp == 5, tp = 1]; 
  bMoveTo[n, tp, x, y, Hold[board]]];
showBoard[Hold[board_]] := 
  Table[showSquare[{i, j}, board[[i, j]]], {i, 3, 14}, {j, 3, 25}];
showDropBlock[n_, p_, x_, y_] := 
  Table[If[allBlocks[[n, p, i, j]] != 0, 
    showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], Black], {i, 
    1, 4}, {j, 1, 4}];


updateBoard[n_, p_, x_, y_, 
   Hold[board_, score_]] := {Table[
    board[[i + x, j + y]] += allBlocks[[n, p, i, j]], {i, 1, 4}, {j, 
     1, 4}]; score += 
    10 + 100*(2^
         Sum[updateLine[i, Hold[board]], {i, y + 4, y + 1, -1}] - 
        1)};
updateLine[line_, Hold[board_]] := 
  If[line <= 23 && 
    line >= 4 && (Apply[And, 
       Table[board[[i, line]] != 0, {i, 4, 13}]]) == True, 
   Table[board[[i, j]] = board[[i, j + 1]], {i, 4, 13}, {j, line, 
     22}]; Table[board[[i, 23]] = 0, {i, 4, 13}]; 1, 0];
newGame[Hold[n_, p_, x_, y_, board_, nextItem_, gameOver_, pause_, 
    num_]] := {initBoard[
    Hold[board, num]]; {n, p, x, y} = {nextItem, 2, 6, 22}; 
   nextItem = RandomInteger[6] + 1; gameOver = False; pause = False};
newBlock[Hold[n_, p_, x_, y_, board_, score_, gameOver_, nextItem_, 
    pause_]] := {If[! pause && ! gameOver, 
    updateBoard[n, p, x, y, Hold[board, score]]; 
    gameOver = bGameOver[nextItem, 2, Hold[board]]; 
    If[gameOver == False, {n, p, x, y} = {nextItem, 2, 6, 22}; 
     nextItem = RandomInteger[6] + 1]]};


shapeDescend[Hold[level_, state_, num_]] := 
  DynamicModule[{score = 0, gameOver = False, pause = False, 
    board = Table[
      If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 0, 0], {i, 1, 
       16}, {j, 1, 28}], n, p, x, y, nextItem}, 
   SeedRandom[level*10 + num]; nextItem = RandomInteger[6] + 1; 
   newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause, num]];
   EventHandler[
    Graphics[{Text[Style["Level", Blue, Italic, 24], {18, 12}], 
      Text[Style["Score", Blue, Italic, 24], {18, 9}], 
      Table[showSquare[{i + 16, j + 13}, smallBoard[[i, j]]], {i, 1, 
        6}, {j, 1, 6}], 
      Dynamic[Refresh[
        Switch[state, 1, 
         newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause, 
           num]]; state = 3,
         2, pause = True,
         3, pause = False], UpdateInterval -> Infinity, 
        TrackedSymbols -> {state}]; 
       Refresh[If[! pause && ! gameOver && 
          bDown[n, p, x, y, Hold[board]], y--, 
         newBlock[
          Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
         UpdateInterval -> 3./level, TrackedSymbols -> {}];
       Join[{{Text[
           Style[ToString[level], Green, Italic, 24], {20, 11}], 
          Text[Style[ToString[score], Green, Italic, 24], {20, 8}]}}, 
        showBoard[Hold[board]], showDropBlock[n, p, x, y], 
        showHintBlock[nextItem, 2, 17, 14]], 
       TrackedSymbols -> {x, y, n, p, level}]}, Background -> Black, 
     ImageSize -> {400, 400}], {"LeftArrowKeyDown" :> 
      If[! pause && ! gameOver && bLeft[n, p, x, y, Hold[board]], x--],
     "RightArrowKeyDown" :> 
      If[! pause && ! gameOver && bRight[n, p, x, y, Hold[board]], 
       x++], "DownArrowKeyDown" :> (If[! pause && ! gameOver && 
         bRotateClockwise[n, p, x, y, Hold[board]], p++; 
        If[p == 5, p = 1]]), 
     "UpArrowKeyDown" :> (If[! pause && ! gameOver && 
         bRotateAnticlock[n, p, x, y, Hold[board]], p--; 
        If[p == 0, p = 4]]), {"KeyDown", " "} :> 
      If[! pause && ! gameOver && bDown[n, p, x, y, Hold[board]], y--,
        newBlock[
        Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
     "EscapeKeyDown" :> {newGame[
        Hold[n, p, x, y, board, nextItem, gameOver, pause, num]]}}]];

Manipulate[
 shapeDescend[Hold[level, state, initLine]],
 {{level, 5}, 1, 9, 1},
 {{initLine, 3, "height of bottom level"}, 0, 9, 1},
 {{state, 3, ""}, {1 -> "new game", 2 -> "pause", 3 -> "continue"}},
 ContentSize -> {480, 450}, SynchronousUpdating -> False, 
 SaveDefinitions -> True, Alignment -> Center, TrackedSymbols -> {}]
Vitaliy Kaurov
sumber
4

C

Saya menulis ini yaa lalu lalu sambil duduk bosan di kelas di sekolah menengah. Seharusnya, programmer asli menulis versi pertama Tetris menggunakan tanda kurung kotak, dan sepertinya cukup sederhana untuk mencoba dan membuat ulang, bukan? Saya tidak tahu apa-apa tentang GUI, jadi saya membuat program konsol yang bagus dan kuno. Ini kembali sebelum saya belajar C ++ dan praktek-praktek pemrograman sial yang tepat, sehingga kode mungkin agak berantakan. Saya cukup banyak hanya mengayunkannya.

TKlone

Itu memenuhi semua persyaratan tantangan, kecuali potongan hanya berputar dalam satu arah (searah jarum jam). Gunakan WASD untuk bermain, W memutar lagu. Kode sumber dan exe lengkap dapat ditemukan di sini: http://sourceforge.net/projects/tklone/files/tklone/tklone-v1.0/

tetris.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include "defines.h"

int array[FIELD_H][FIELD_W];    //field array
int pieceCoords[2];             //current piece coordinates
int x, y;                       //looping variables
int thisPiece, nextPiece;       //current pieces
double score;                   //the players current score
int newpiece=1;                 //the newpiece flag
int init_h=1;                   /*starting coordinates of piece*/
int init_w=4;                   /*these are obsolete variables*/
int h_offset, w_offset;         //piece position offsets from start
int rtTop, rtBot, rtLeft, rtRight;  //rotation thresholds for collision
int b1h, b1w, b2h, b2w, b3h, b3w;   //block draw offsets
int rotation;                   /*0=normal view
                                  1=90 degrees clockwise
                                  2=upside-down
                                  3=270 degrees clockwise */

clock_t speed;          //time before piece falls (in milliseconds)
clock_t gravity;        //temp variable for speed

//clock_t fps;
int piececount;         //number of pieces that have fallen
int oldpiececount;
int level;


void GameInit(void);
int GameStart(void);
int GameMain(void);
void newPiece(void);

void scrollbar(void);           //scrolling marquee bar
void drawField(void);           //graphics rendering function
void setPiece(int piece);       //this sets the individual piece offsets
void drawPiece(void);           //this sets the blocks in the array
void initArray(void);           //this initializes the array
void getMoves(char move);       //input function
void rotate(int coll);          //loops from 1 to 4
int collisionCheck(int side);   //checks to see if piece will collide
int loseGame(void);             //checks to see if game has ended
void eraseLastMove(void);       //clears the array of the last move
void setLastMove(void);         //sets the piece in place for the next piece
void lineClear(void);           //clears filled lines and tabulates the score
void predict(int undo);         //predicts the next piece rotation for collision
//void delay(int duration);

int main()
{
restart:
    GameInit();
    //some test blocks
    /*for(x=FIELD_H;x>3;x--)        //quick lose pattern
        array[x][4]=1;*/

    nextPiece=rand()%7;     //generate a piece to use
    drawField();            //draw a blank field. should be a title screen
    printf("                             / Welcome to TETRIS! \\"); //29 spaces and title

    getch();            //wait to start the game

    while(1)    //main game loop
    {
        if(newpiece==1)         //check to see if a new piece is required
            newPiece();

        //if(gravity<=clock())
            gravity=speed+clock();  //get the end time
        while(gravity>clock())      //count to the end time
        {
            if(kbhit())
            {
                getMoves(getch());  //get input
                if(newpiece==1)     //if piece reaches bottom...
                    break;          //...skip by redraw to save some cycles
                else
                {
                    eraseLastMove();    //clear the last move from the array
                    drawPiece();        //set the piece in the array
                    drawField();        //refresh the screen
                    scrollbar();        //draw the scrollbar
                }
            }
        }
        if(newpiece==0)     /*minor speed optimization*/
            getMoves('s');  //force piece down a line
        eraseLastMove();    //clear the last move from the array
        drawPiece();        //set the piece in the array
        drawField();        //refresh the screen
        scrollbar();        //draw the scrollbar
    }
    return 0;
}

void GameInit()
{
    srand((unsigned)time(NULL));    //seed the random number genreator...
    initArray();                    //...and initialize the array
    //reset some variables
    score=0;
    piececount=-1;      //set to -1 because of newpiece loop on start
    oldpiececount=0;
    speed=1000;         //starting speed of piece
    level=1;
}

void newPiece()
{
    piececount++;       //another piece has fallen
    setLastMove();      //set the last move in place
    lineClear();        //clear any full lines

    newpiece=0;         //reset the newpiece flag
    rotation=0;         //reset the piece rotation
    h_offset=0;         //reset the...
    w_offset=0;         //...piece coordinates
    gravity=0;          //reset the gravity

    if(piececount-oldpiececount==10)    //if 10 more pieces have fallen
        if(speed!=200)                  //200 is the lowest speed it can go
        {
            oldpiececount=piececount;   //set current number of fallen pieces
            speed-=100;                 //make the speed faster
            level++;                    //increment the level counter
        }

    thisPiece=nextPiece;//get the current piece
    nextPiece=rand()%7; //pick the next piece

    setPiece(thisPiece);//get piece data
    if(loseGame())      //did player lose the game?
    {
        drawField();    //refresh the screen
        printf("You lose! Final score: %.0f. Press any key to begin a new game.",score);
        getch();
        newpiece=1;     //reset the newpiece flag /*add more initialization here*/
        //goto restart; //return to start of the program
        exit(0);        //quit the game for now
    }
    drawPiece();        //set the piece in the array
    drawField();        //refresh the screen
    scrollbar();        //draw the scrollbar
}

void scrollbar()
{
    //printf("================================================================================");

    printf("Score: %.0f\t\t      ",score);  //print the score (6 spaces)
    if(nextPiece==5||nextPiece==6)          //print a space if the piece name is short
        printf(" ");

    printf("Next piece: ");
    if(nextPiece==0)            //print the name of the next piece
        printf("straight");
    else if(nextPiece==1)
        printf("t-shaped");
    else if(nextPiece==2)
        printf("s-shaped");
    else if(nextPiece==3)
        printf("z-shaped");
    else if(nextPiece==4)
        printf("l-shaped");
    else if(nextPiece==5)
        printf("back-l");
    else if(nextPiece==6)
        printf("square");

    printf("\t\t\tLevel %i",level); //print the level
}

void drawField()
{
    system("cls");      //clear the screen

    for(x=0;x<FIELD_H;x++)
    {
        printf("                              |");  //thirty spaces

        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==1||array[x][y]==2)
                printf("[]");
            else
                printf("  ");

        printf("|                              ");  //thirty spaces
    }

    //bottom message bar
    //scrollbar();
}

void drawPiece()
{
    /* This sets the initial location of the piece with
       movement offset. Coordinates are based on the
       center of the piece. */
    pieceCoords[0]=init_h+h_offset; //set height position
    pieceCoords[1]=init_w+w_offset; //set width position

    //mark the coordinates to draw the piece
    array[(pieceCoords[0])][(pieceCoords[1])]=2;        //center piece
    array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]=2;    //other...
    array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]=2;    //...three...
    array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]=2;    //...pieces
}

void initArray()
{
    for(x=0;x<FIELD_H;x++)
        for(y=0;y<FIELD_W;y++)
            array[x][y]=0;
        //array equals: 0 for empty
        //              1 for full
        //              2 for moving piece
}

void getMoves(char move)
{
    switch(move)
    {
        case 'a':
        case 'A':
            if(collisionCheck(1)==0)    //if the piece doesnt hit something...
                w_offset--;             //...move the piece
            break;
        case 'd':
        case 'D':
            if(collisionCheck(2)==0)
                w_offset++;
            break;
        case 'w':
        case 'W':
            if(collisionCheck(0)==0)
            {
                rotate(0);              //rotate the piece and...
                setPiece(thisPiece);    //...get new piece data
            }
            break;
        case 's':
        case 'S':
            if(collisionCheck(3)==0)
                h_offset++;
            else                        //if the piece hits the bottom...
                newpiece=1;             //...set the newpiece flag
            break;
        default:
            break;
    }
}

void rotate(int coll)
{
    if(coll==0)     //rotate clockwise
    {
        if(rotation==3)
            rotation=0;
        else
            rotation++;
    }

    if(coll==1)     //rotate counterclockwise
    {
        if(rotation==0)
            rotation=3;
        else
            rotation--;
    }
}

int collisionCheck(int side)
{
    if(side==0) //check rotation collision
    {
        predict(0);

        /* For the first part, we check to see if the
           rotation will collide with the sides. */
        for(x=0;x<rtTop;x++)    //check top side collision
        {
            if(pieceCoords[0]-x==0) //if piece minus top offset hits the top
            {
                predict(1);
                return 1;   //collision detected!
            }
        }

        for(x=0;x<rtLeft;x++)   //check left side collision
        {
            if(pieceCoords[1]-x==0)
            {
                predict(1);
                return 1;
            }
        }

        for(x=0;x<rtRight;x++)  //check right side collision
        {
            if(pieceCoords[1]+x==(FIELD_W-1))
            {
                predict(1);
                return 1;
            }
        }

        for(x=0;x<rtBot;x++)    //check bottom side collision
        {
            if(pieceCoords[0]+x==(FIELD_H-1))
            {
                predict(1);
                return 1;
            }
        }

        /* Then we check to see if any of the
           blocks will hit other blocks on the field. */
        if(array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]==1
    ||array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]==1
    ||array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]==1)
        {
            predict(1);
            return 1;
        }

        predict(1);
        return 0;
    }

    /* Here, we scan the array and check to see whether
       any of the blocks in the current piece are
       adjacent to the field sides or another block. */
    for(x=0;x<FIELD_H;x++)          //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if array location contains a block
            {
                if(side==1) //check left side
                {
                    if(y==0||array[x][y-1]==1)  //if piece is next to border or block...
                        return 1;   //collision detected!
                }

                else if(side==2)    //check right side
                {
                    if(y==FIELD_W-1||array[x][y+1]==1)
                        return 1;
                }

                else if(side==3)    //check bottom
                {
                    if(x==FIELD_H-1||array[x+1][y]==1)
                        return 1;
                }
            }

    return 0;
}

int loseGame()      /*need to modify the conditions of losing*/
{
    if(array[init_h][init_w]==1
||array[(init_h+b1h)][(init_w+b1w)]==1
||array[(init_h+b2h)][(init_w+b2w)]==1
||array[(init_h+b3h)][(init_w+b3w)]==1)
        return 1;

    else
        return 0;
}

void eraseLastMove()    /*need to optimize this*/
{
    for(x=0;x<FIELD_H;x++)      //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if the spot is filled...
                array[x][y]=0;      //...clear the block
}

void setLastMove()      /*also need to optimize this*/
{
    for(x=0;x<FIELD_H;x++)      //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if the spot is filled...
                array[x][y]=1;      //...set the block
}

void lineClear()
{
    int x2, y2; //new looping variables
    int empty;  //is the row empty?
    int lines=0;//number of lines cleared

    for(x=0;x<FIELD_H;x++)      //scan the rows
    {
        empty=0;                //reset the empty flag on each row

        for(y=0;y<FIELD_W;y++)  //scan the blocks in each row
            if(array[x][y]==0)  //if the row isn't full...
            {
                empty=1;        //...set the empty flag and...
                break;          //...scan the next row
            }

        if(!empty)
        {
            for(y=0;y<FIELD_W;y++)      //rescan the filled row...
                array[x][y]=0;          //...and clear the blocks

            for(x2=x-1;x2>=0;x2--)      //scan rows from the bottom up starting above cleared row
                for(y2=0;y2<FIELD_W;y2++)   //scan each block in the row
                    if(array[x2][y2]==1)
                    {
                        array[x2][y2]=0;    //clear the block
                        array[x2+1][y2]=1;  //fill in the block below it
                    }

            lines++;            //a line has been cleared
        }
    }

    //tabulate the scores
    if(lines==1)
        score+=10;
    else if(lines==2)
        score+=25;
    else if(lines==3)
        score+=50;
    else if(lines==4)   /* TETRIS! */
        score+=100;
}

void predict(int undo)
{
    if(undo==0)
    {
        rotate(0);              //get next rotation...
        setPiece(thisPiece);    //...and next piece data
    }

    if(undo==1)
    {
        rotate(1);              //un-rotate
        setPiece(thisPiece);    //reset the piece
    }
}

/*void delay(int duration)
{
    clock_t done;

    done = duration + clock();
    while(done>clock())
        ;   //sit and wait
}*/

setPiece.c

#include "defines.h"

extern int rtTop, rtBot, rtLeft, rtRight;   //rotation thresholds for collision
extern int b1h, b1w, b2h, b2w, b3h, b3w;    //block draw offsets
extern int rotation;                    /*0=normal view
                                          1=90 degrees clockwise
                                          2=upside-down
                                          3=270 degrees clockwise */

void setPiece(int piece)
{
    if(piece==0)
    {
        switch(rotation)
        {
            case 0:     //piece looks identical rotated upside-down
            case 2:
                /* The rotation thresholds dictate how far away the piece
                   needs to be from the field edge for the next rotation. */
                rtTop=1;
                rtBot=2;
                rtLeft=0;
                rtRight=0;

                /* These are the coordinates of the
                   blocks relative to the center block. */
                b1h=-1;
                b1w=0;
                b2h=1;
                b2w=0;
                b3h=2;
                b3w=0;
                break;
            case 1:     //piece looks identical side to side
            case 3:
                rtTop=0;
                rtBot=0;
                rtLeft=1;
                rtRight=2;

                b1h=0;
                b1w=-1;
                b2h=0;
                b2w=1;
                b3h=0;
                b3w=2;
                break;
        }
    }
        //draw blue straight piece

    //snip other pieces

    else if(piece==6)
    {
        switch(rotation)
        {
            case 0:     //a cube looks identical when rotated 90 degrees
            case 1:
            case 2:
            case 3:
                rtTop=0;    //piece doesn't rotate so no thresholds needed
                rtBot=0;
                rtLeft=0;
                rtRight=0;

                b1h=-1;
                b1w=0;
                b2h=-1;
                b2w=1;
                b3h=0;
                b3w=1;
                break;
        }
    }
        //draw square piece
}
Bigbio2002
sumber
Hai! Selamat datang di PPCG. Kami sangat menganjurkan jawaban untuk benar-benar memasukkan kode sumber mereka di badan jawaban, kecuali jika terlalu besar untuk ukuran posting maksimum. Meskipun demikian, adalah baik untuk memberikan beberapa kutipan ilustrasi.
Jonathan Van Matre
2
Kode ditambahkan. setPiece.c disingkat sedikit, dan mendefinisikan.h tidak terdaftar. Kode asli dapat ditemukan di tautan.
Bigbio2002
1
Luar biasa! Terima kasih telah membagikannya.
Jonathan Van Matre
1

Saya membuat versi javascript https://marchingband.github.io/tetris/

masukkan deskripsi gambar di sini

<!DOCTYPE html>
<html>
  <body>
      <p>q and w rotate</p>
      <p id='score'>0</p>
      <div id='board' style='position:absolute;top:80px' ></div>
  </body>
</html>
  <script>
    var pieceIndex=0
    nextPiece=()=>{
      pieceIndex=pieceIndex<pieces.length-1?pieceIndex+1:0
      return pieces[pieceIndex]
    }
    const pieces=[
    [
        {x:0,y:-1},
        {x:0,y:0},
        {x:1,y:0},
        {x:-1,y:0},
      ],
      [
        {x:0,y:-2},
        {x:0,y:-1},
        {x:0,y:0},
        {x:0,y:1},
      ],
      [
        {x:1,y:0},
        {x:0,y:0},
        {x:2,y:0},
        {x:0,y:-1},
      ],
      [
        {x:0,y:0},
        {x:1,y:0},
        {x:1,y:1},
        {x:0,y:1}
      ],
      [
        {x:0,y:0},
        {x:-1,y:0},
        {x:0,y:1},
        {x:1,y:1}
      ],
    ]
    const speeds =[1000,800,600,400,300,250,200,150,100,90,80,70,60,50,40,30,20]
    const boardWidth = 10
    const boardHeight = 20
    const pixelSize   = 10
    const pixelBorder = 0
    const boardBorder = 5
    
    var score = 0
    var level = 0
    var fallingBits = []

    const b = document.getElementById('board')
     piece = {
      position:{
        x:boardWidth/2,
        y:1,
      },
      bits:[
        {x:0,y:-1},
        {x:0,y:0},
        {x:1,y:0},
        {x:-1,y:0},
      ],
      reset(){
        piece.position={x:boardWidth/2,y:1}
        piece.bits=nextPiece()
        !piece.coordinates().every((b)=>dom[b.x][b.y]?dom[b.x][b.y].style.background=='red':true) && endGame()
      },
      coordinates(){
        return piece.bits.map((b)=>{
          return {x:b.x+piece.position.x,y:b.y+piece.position.y}
        })
      }
    }
    const dom = []
    for(var x=0;x<boardWidth;x++){
      var row = []
      for(var y=0;y<boardHeight;y++){
        var cell = document.createElement('div')
        cell.style.cssText='outline:thin solid;height:'+pixelSize+'px;width:'+pixelSize+'px;background:red;position:absolute'
        cell.style.left= (x*(pixelSize+pixelBorder)+boardBorder) + 'px'
        cell.style.top = (y*(pixelSize+pixelBorder)+boardBorder) + 'px'
        row.push(cell)
        b.appendChild(cell)
      }
      dom.push(row)
    }
    renderPiece=(color)=>{
      piece.coordinates().forEach((b)=>dom[b.x][b.y] && (dom[b.x][b.y].style.background=color))
    }
    drop=()=>{
      piece.coordinates().every((p)=>p.y+1<boardHeight) && piece.coordinates().every((p)=>dom[p.x][p.y+1].style.background!='blue') ?
        movePiece(0,1) : freeze()
    }
    strafe=(d)=>{
      piece.coordinates().every((p)=>p.x+d<boardWidth && p.x+d >=0) && piece.coordinates().every((p)=>dom[p.x+d][p.y].style.background!='blue') ?
        movePiece(d,0) : null
    }
    freeze=()=>{
      piece.coordinates().forEach((p)=>dom[p.x][p.y].style.background='blue')
      lookForRows()
      piece.reset()
    }
    lookForRows=()=>{
      [...dom[0].keys()].filter((i)=>dom.every((c)=>c[i].style.background=='blue')).forEach((r)=>{
        removeLine(r)
        compressAbove(r)
        addPoint()
      })
    }
    compressAbove=(r)=>{
      // dom.reduce((acc,col,x)=>{return acc.concat(col.map((b,y)=>{return {x:x,y:y}}).filter((b,y)=>y<r && dom[b.x][b.y].style.background=='blue'))},[]).forEach((b)=>dom[b.x][b.y].style.background='red').forEach((b)=>dom[b.x][b.y+1].style.background='blue')
      var bits = []
      dom.forEach((x,i)=>{
        x.forEach((y,j)=>{
          if(j<=r && dom[i][j].style.background=='blue'){bits.push({x:i,y:j})}
        })
      })
      bits.forEach((b)=>{
        dom[b.x][b.y].style.background='red'
      })
      bits.forEach((b)=>{
        dom[b.x][b.y+1].style.background='blue'
      })
    }
    removeLine=(r)=>{
      dom.forEach((x)=>x[r].style.background='red')
    }
    changePiece=(x,y)=>{
      piece.position.x += x
      piece.position.y += y
    }
    movePiece=(x,y)=>{
        renderPiece('red')
        changePiece(x,y)
        renderPiece('yellow')
    }
    turn=(d)=>{
      var newBits = []
      for(let i=0;i<piece.bits.length;i++){
        var x = piece.bits[i].x
        var y = piece.bits[i].y
        var bit={}
        if(d==1){
          bit.x=-y
          bit.y=x
        }else{
          bit.x=y
          bit.y=-x
        }
        newBits.push(bit)
      }
      if(newBits.every((p)=>p.x+piece.position.x<boardWidth && p.x+piece.position.x>=0 && p.y+piece.position.y<boardHeight && dom[p.x+piece.position.x][p.y+piece.position.y].style.background!='blue')){
        renderPiece('red')
        piece.bits=newBits
        renderPiece('yellow')
      }
    }
    addPoint=()=>{
      score++
      document.getElementById('score').innerText=score
      score-level*10 > 10 && levelUp()
    }
    levelUp=()=>{
      clearInterval(timer)
      level++
      timer = window.setInterval(drop,speeds[level])
    }
    endGame=()=>{
      clearInterval(timer)
      dom.forEach((row)=>{
        row.forEach((bit)=>{
          bit.style.background=='red' && (bit.style.background='black')
        })
      })
    }
    document.onkeydown = function(e) {
    switch (e.keyCode) {
        case 37:strafe(-1);break
        case 87:turn(1);break
        case 81:turn(-1);break
        case 39:strafe(1);break
        case 40:drop();break
    }
};
  var timer = window.setInterval(drop,1000)
  </script>
</html>

Andrew March
sumber
Bagus Sayangnya memiliki bug kecil di Firefox, di mana semua bagian jatuh sampai ke dasar lubang, karena dom[p.x][p.y+1].style.backgroundkembali blue none repeat scroll 0% 0%, jadi tidak akan pernah sama blue. Solusi Anda valid, tetapi saya sarankan untuk mengubah semua .style.background.style.backgroundColorberfungsi dengan baik di Firefox dan Chrome.
manatwork