Pada artikel ini, saya akan mengeksplorasi mekanisme menipu sederhana dari Nintendo Tetris, dan di bagian kedua saya akan menjelaskan bagaimana saya membuat AI yang mengeksploitasi mekanik ini.
Coba sendiri
Tentang proyek
Bagi mereka yang tidak memiliki ketekunan, kesabaran, dan waktu yang dibutuhkan untuk menguasai Nintendo Tetris, saya menciptakan AI yang bisa bermain sendiri. Anda akhirnya bisa mencapai level 30 dan bahkan lebih jauh. Anda akan melihat cara mendapatkan jumlah poin maksimum dan mengamati perubahan tak henti dari penghitung baris, level, dan statistik. Anda akan belajar warna apa yang muncul pada level di atas yang tidak bisa didaki seseorang. Lihat seberapa jauh Anda bisa melangkah.
Persyaratan
Untuk menjalankan AI, Anda memerlukan
emulator NES / Famicom
FCEUX universal. Kecerdasan buatan dikembangkan untuk
FCEUX 2.2.2 , versi terbaru dari emulator pada saat penulisan.
Anda juga memerlukan file ROM Nintendo Tetris (versi AS). Coba cari di
Google .
Unduh
Unzip
lua/NintendoTetrisAI.lua
dari
file zip sumber ini.
Luncurkan
Luncurkan FCEUX. Dari menu, pilih File | Buka ROM ... Di kotak dialog Buka File, pilih file Nintendo Tetris ROM dan klik Buka. Game akan dimulai.
Dari menu, pilih File | Lua | Jendela Lua Script Baru ... Di jendela Lua Script, masukkan path ke
NintendoTetrisAI.lua
atau klik tombol Browse untuk menemukannya. Setelah itu, klik Run.
Skrip pada Lua akan mengarahkan Anda ke layar pertama dari menu. Tinggalkan jenis permainan A-Type, dan Anda dapat memilih musik apa pun. Pada komputer yang lambat, musik dapat diputar dengan sangat menyentak, maka Anda harus mematikannya. Tekan Mulai (Enter) untuk pergi ke layar menu berikutnya. Di menu kedua, Anda dapat menggunakan tombol panah untuk mengubah level awal. Klik Mulai untuk memulai permainan. Dan di sini AI akan mengambil kendali.
Jika setelah memilih level pada layar menu kedua, tahan tombol gamepad A (Anda dapat mengubah tata letak keyboard di menu Config | Input ...) dan tekan Start, maka level awal akan 10 lebih dari nilai yang dipilih. Level entri maksimum adalah sembilan belas.
Konfigurasi
Untuk membuat game berjalan lebih cepat, buka skrip Lua dalam editor teks. Di awal file, temukan baris berikut.
PLAY_FAST = false
Ganti
false
dengan
true
seperti yang ditunjukkan di bawah ini.
PLAY_FAST = true
Simpan file. Kemudian klik tombol Restart di jendela Lua Script.
Mekanisme Nintendo Tetris
Deskripsi Tetrimino
Setiap angka tetrimino sesuai dengan nama huruf tunggal yang menyerupai bentuknya.
Desainer Nintendo Tetris secara sewenang-wenang mengatur urutan tetrimino yang ditunjukkan di atas. Angka-angka ditunjukkan dalam orientasi di mana mereka muncul di layar, dan sirkuit menciptakan gambar yang hampir simetris (mungkin inilah mengapa urutan ini dipilih). Indeks urutan memberi setiap tetrimino ID numerik unik. Pengidentifikasi urutan dan tipe penting di tingkat pemrograman; selain itu, mereka memanifestasikan diri dalam urutan angka yang ditampilkan di bidang statistik (lihat di bawah).
19 orientasi yang digunakan dalam Nintendo Tetris tetrimino dikodekan dalam tabel yang terletak pada
$8A9C
dari memori konsol NES. Setiap gambar direpresentasikan sebagai urutan 12 byte yang dapat dibagi menjadi tiga kali lipat
(Y, tile, X)
yang menggambarkan setiap kotak pada gambar. Nilai heks koordinat di atas
$7F
menunjukkan bilangan bulat negatif (
$FF= โ1
, dan
$FE = โ2
).
; Y0 T0 X0 Y1 T1 X1 Y2 T2 X2 Y3 T3 X3
8A9C: 00 7B FF 00 7B 00 00 7B 01 FF 7B 00 ; 00: T up
8AA8: FF 7B 00 00 7B 00 00 7B 01 01 7B 00 ; 01: T right
8AB4: 00 7B FF 00 7B 00 00 7B 01 01 7B 00 ; 02: T down (spawn)
8AC0: FF 7B 00 00 7B FF 00 7B 00 01 7B 00 ; 03: T left
8ACC: FF 7D 00 00 7D 00 01 7D FF 01 7D 00 ; 04: J left
8AD8: FF 7D FF 00 7D FF 00 7D 00 00 7D 01 ; 05: J up
8AE4: FF 7D 00 FF 7D 01 00 7D 00 01 7D 00 ; 06: J right
8AF0: 00 7D FF 00 7D 00 00 7D 01 01 7D 01 ; 07: J down (spawn)
8AFC: 00 7C FF 00 7C 00 01 7C 00 01 7C 01 ; 08: Z horizontal (spawn)
8B08: FF 7C 01 00 7C 00 00 7C 01 01 7C 00 ; 09: Z vertical
8B14: 00 7B FF 00 7B 00 01 7B FF 01 7B 00 ; 0A: O (spawn)
8B20: 00 7D 00 00 7D 01 01 7D FF 01 7D 00 ; 0B: S horizontal (spawn)
8B2C: FF 7D 00 00 7D 00 00 7D 01 01 7D 01 ; 0C: S vertical
8B38: FF 7C 00 00 7C 00 01 7C 00 01 7C 01 ; 0D: L right
8B44: 00 7C FF 00 7C 00 00 7C 01 01 7C FF ; 0E: L down (spawn)
8B50: FF 7C FF FF 7C 00 00 7C 00 01 7C 00 ; 0F: L left
8B5C: FF 7C 01 00 7C FF 00 7C 00 00 7C 01 ; 10: L up
8B68: FE 7B 00 FF 7B 00 00 7B 00 01 7B 00 ; 11: I vertical
8B74: 00 7B FE 00 7B FF 00 7B 00 00 7B 01 ; 12: I horizontal (spawn)
8B80: 00 FF 00 00 FF 00 00 FF 00 00 FF 00 ; 13: Unused
Di bagian bawah tabel ada satu catatan yang tidak digunakan, berpotensi memberikan kesempatan untuk menambahkan orientasi lain. Namun, di berbagai bagian kode,
$13
menunjukkan bahwa pengenal orientasi tetrimino aktif tidak diberi nilai.
Untuk kemudahan membaca, koordinat kotak dalam desimal ditunjukkan di bawah ini.
-- { { X0, Y0 }, { X1, Y1 }, { X2, Y2 }, { X3, Y3 }, },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, -1 }, }, -- 00: T up
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 01: T right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 02: T down (spawn)
{ { 0, -1 }, { -1, 0 }, { 0, 0 }, { 0, 1 }, }, -- 03: T left
{ { 0, -1 }, { 0, 0 }, { -1, 1 }, { 0, 1 }, }, -- 04: J left
{ { -1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 05: J up
{ { 0, -1 }, { 1, -1 }, { 0, 0 }, { 0, 1 }, }, -- 06: J right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 1, 1 }, }, -- 07: J down (spawn)
{ { -1, 0 }, { 0, 0 }, { 0, 1 }, { 1, 1 }, }, -- 08: Z horizontal (spawn)
{ { 1, -1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 09: Z vertical
{ { -1, 0 }, { 0, 0 }, { -1, 1 }, { 0, 1 }, }, -- 0A: O (spawn)
{ { 0, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, }, -- 0B: S horizontal (spawn)
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 }, }, -- 0C: S vertical
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, 1 }, }, -- 0D: L right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { -1, 1 }, }, -- 0E: L down (spawn)
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, }, -- 0F: L left
{ { 1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 10: L up
{ { 0, -2 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, }, -- 11: I vertical
{ { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 12: I horizontal (spawn)
Semua orientasi ditempatkan dalam matriks 5 ร 5.
Pada gambar di atas, kotak putih menunjukkan pusat matriks, titik referensi untuk rotasi gambar.
Tabel orientasi disajikan secara grafis di bawah ini.
Pengidentifikasi orientasi (tabel indeks) ditampilkan dalam heksadesimal di sudut kanan atas setiap matriks. Dan mnemonik yang ditemukan untuk proyek ini ditampilkan di sudut kiri atas.
u
,
r
,
d
,
l
,
h
dan
v
adalah singkatan dari "atas, kanan, bawah, kiri, horisontal dan vertikal". Misalnya, lebih mudah untuk menunjukkan orientasi
Jd
daripada
$07
.
Matriks yang berisi orientasi gambar selama penciptaan ditandai dengan bingkai putih.
Tetrimino I, S dan Z dapat diberikan 4 orientasi terpisah, tetapi pencipta Nintendo Tetris memutuskan untuk membatasi diri menjadi dua. Selain itu,
Zv
dan
Sv
bukan gambar cermin ideal satu sama lain. Keduanya dibuat dengan memutar berlawanan arah jarum jam, menghasilkan ketidakseimbangan.
Tabel orientasi juga berisi nilai ubin untuk setiap kotak di setiap gambar yang berorientasi. Namun, dengan penelitian yang cermat, menjadi jelas bahwa nilai-nilai untuk satu jenis tetrimino selalu sama.
Nilai petak adalah indeks dari tabel (warna semu) dari pola yang ditunjukkan di bawah ini.
Ubin
$7B
,
$7C
dan
$7D
terletak tepat di bawah "ATIS" dari kata "STATISTIK". Ini adalah tiga jenis kotak dari mana tetrimino dibuat.
Bagi yang penasaran, saya akan mengatakan bahwa burung unta dan penguin digunakan di akhir mode Tipe-B. Topik ini dibahas secara rinci di bagian "Berakhir".
Di bawah ini adalah hasil dari memodifikasi ROM setelah mengganti
$7B
dengan
$29
. Jantung adalah ubin di bawah simbol P dalam tabel pola untuk semua orientasi T.
Ubin hati tetap berada di lapangan bermain bahkan setelah Ts yang dimodifikasi dikunci di tempatnya. Seperti yang dinyatakan di bawah ini di bagian "Menciptakan Tetrimino", ini berarti bahwa lapangan bermain menyimpan nilai sebenarnya dari indeks petak Tetrimino yang dimainkan.
Pemrogram game memungkinkan untuk menggunakan 4 ubin terpisah untuk setiap gambar, dan bukan hanya satu jenis kotak yang tidak berubah-ubah. Ini adalah fitur yang berguna yang dapat digunakan untuk mengubah tampilan permainan. Tabel pola memiliki banyak ruang kosong untuk ubin baru yang dapat memberikan tampilan unik pada setiap tetrimino.
Koordinat kotak sangat mudah untuk dimanipulasi. Sebagai contoh, versi modifikasi dari tiga kali lipat pertama dalam tabel orientasi ditunjukkan di bawah ini.
8A9C: FE 7B FE FE 7B 02 02 7B FE 02 7B 02 ; 00: T up
Perubahan ini mirip dengan yang berikut:
{ { -2, -2 }, { 2, -2 }, { -2, 2 }, { 2, 2 }, }, -- 00: T up
Hasilnya adalah split tetrimino.
Saat memindahkan tetrimino yang terbagi, kuadratnya tidak bisa melampaui batas-batas lapangan bermain dan tidak bisa melewati angka tempat yang sebelumnya terkunci. Selain itu, permainan melarang rotasi dalam orientasi ini jika mengarah ke kotak jatuh di luar batas lapangan bermain atau fakta bahwa kotak tumpang tindih dengan kotak yang sudah berbohong.
Split tetrimino terkunci pada tempatnya ketika ada dukungan untuk kotak mana pun. Jika angka itu diblokir, maka kotak yang menggantung di udara terus menggantung.
Permainan ini menangani tetrimino yang terpecah seperti sosok normal lainnya. Ini membuat kita mengerti bahwa tidak ada tabel tambahan yang menyimpan metadata dari gambar. Misalnya, mungkin ada meja yang menyimpan ukuran kotak pembatas dari masing-masing orientasi untuk memeriksa tabrakan dengan batas lapangan permainan. Tapi meja seperti itu tidak digunakan. Sebagai gantinya, game hanya memeriksa keempat kotak tepat sebelum memanipulasi bentuk.
Selain itu, koordinat kotak dapat berupa nilai apa pun; mereka tidak terbatas pada interval
[โ2, 2]
. Tentu saja, nilai-nilai yang jauh melebihi interval ini akan memberi kita angka yang tidak dapat diterapkan yang tidak dapat ditampung di lapangan permainan. Lebih penting lagi, seperti yang dinyatakan dalam bagian "Status permainan dan mode render", ketika sebuah gambar terkunci di tempatnya, mekanisme untuk membersihkan garis yang diisi hanya memindai perpindahan baris dari โ2 ke 1 dari kuadrat tengah gambar; kuadrat dengan koordinat
y
luar interval ini tidak akan dikenali.
Rotasi tetrimino
Dalam ilustrasi grafis dari tabel orientasi, rotasi terdiri dari bergerak dari matriks ke salah satu matriks di sebelah kiri atau di sebelah kanan dengan transfer seri jika perlu. Konsep ini dikodekan dalam tabel di
$88EE
.
; CCW CW
88EE: 03 01 ; Tl Tr
88F0: 00 02 ; Tu Td
88F2: 01 03 ; Tr Tl
88F4: 02 00 ; Td Tu
88F6: 07 05 ; Jd Ju
88F8: 04 06 ; Jl Jr
88FA: 05 07 ; Ju Jd
88FC: 06 04 ; Jr Jl
88FE: 09 09 ; Zv Zv
8900: 08 08 ; Zh Zh
8902: 0A 0A ; OO
8904: 0C 0C ; Sv Sv
8906: 0B 0B ; Sh Sh
8908: 10 0E ; Lu Ld
890A: 0D 0F ; Lr Ll
890C: 0E 10 ; Ld Lu
890E: 0F 0D ; Ll Lr
8910: 12 12 ; Ih Ih
8912: 11 11 ; Iv Iv
Untuk membuatnya lebih jelas, kami akan memindahkan setiap kolom dari tabel ini ke baris tabel di bawah ini.
Mnemonik dalam pos di atas dapat diartikan sebagai indeks urutan atau kunci distribusi. Sebagai contoh, memutar berlawanan arah jarum jam memberi kita
Tl
, dan memutar searah jarum jam memberi kita
Tr
.
Tabel rotasi menyandikan urutan ID orientasi yang terhubung rantai; oleh karena itu, kita dapat memodifikasi rekaman sehingga rotasi mengubah satu jenis tetrimino menjadi yang lain. Teknik ini berpotensi digunakan untuk mengambil keuntungan dari baris yang tidak digunakan dalam tabel orientasi.
Di depan tabel rotasi adalah kode untuk mengaksesnya.
88AB: LDA $0042
88AD: STA $00AE ; originalOrientationID = orientationID;
88AF: CLC
88B0: LDA $0042
88B2: ASL
88B3: TAX ; index = 2 * orientationID;
88B4: LDA $00B5
88B6: AND #$80 ; if (not just pressed button A) {
88B8: CMP #$80 ; goto aNotPressed;
88BA: BNE $88CF ; }
88BC: INX
88BD: LDA $88EE,X
88C0: STA $0042 ; orientationID = rotationTable[index + 1];
88C2: JSR $948B ; if (new orientation not valid) {
88C5: BNE $88E9 ; goto restoreOrientationID;
; }
88C7: LDA #$05
88C9: STA $06F1 ; play rotation sound effect;
88CC: JMP $88ED ; return;
aNotPressed:
88CF: LDA $00B5
88D1: AND #$40 ; if (not just pressed button B) {
88D3: CMP #$40 ; return;
88D5: BNE $88ED ; }
88D7: LDA $88EE,X
88DA: STA $0042 ; orientationID = rotationTable[index];
88DC: JSR $948B ; if (new orientation not valid) {
88DF: BNE $88E9 ; goto restoreOrientationID;
; }
88E1: LDA #$05
88E3: STA $06F1 ; play rotation sound effect;
88E6: JMP $88ED ; return;
restoreOrientationID:
88E9: LDA $00AE
88EB: STA $0042 ; orientationID = originalOrientationID;
88ED: RTS ; return;
Untuk rotasi berlawanan arah jarum jam, indeks tabel rotasi dikurangi dengan menggandakan ID orientasi. Dengan menambahkan 1 padanya, kita mendapatkan indeks rotasi searah jarum jam.
Koordinat
x
,
y
dan orientasi ID tetrimino saat ini masing-masing disimpan di alamat
$0040
,
$0041
dan
$0042
.
Kode menggunakan variabel sementara untuk membuat cadangan ID orientasi. Kemudian, setelah mengubah orientasi, kode memverifikasi bahwa keempat kotak berada di dalam batas-batas lapangan bermain dan bahwa tidak ada yang tumpang tindih dengan kotak yang sudah berbohong (kode verifikasi terletak di
$948B
, di bawah fragmen kode yang ditunjukkan di atas). Jika orientasi baru salah, maka yang asli dikembalikan, tidak memungkinkan pemain untuk memutar gambar.
Menghitung dengan tanda silang, pengontrol NES memiliki delapan tombol, statusnya diwakili oleh bit alamat
$00B6
.
Misalnya,
$00B6
akan berisi nilai
$81
sementara pemain memegang A dan Kiri.
Di sisi lain,
$00B5
melaporkan ketika tombol-tombol ditekan; bit
$00B5
hanya berlaku selama satu iterasi dari loop game (1 frame yang diberikan). Kode ini menggunakan
$00B5
untuk merespons penekanan A dan B. Masing-masing harus dilepaskan sebelum digunakan lagi.
$00B5
dan
$00B6
adalah mirror dari
$00F5
dan
$00F6
. Kode di bagian berikut menggunakan alamat ini secara bergantian.
Buat Tetrimino
Bidang permainan Nintendo Tetris terdiri dari matriks dengan 22 baris dan 10 kolom sehingga dua baris teratas disembunyikan dari pemain.
Seperti yang ditunjukkan dalam kode di bawah ini, saat membuat angka Tetrimino, itu selalu terletak di koordinat
(5, 0)
lapangan bermain.
98BA: LDA #$00
98BC: STA $00A4
98BE: STA $0045
98C0: STA $0041 ; Tetrimino Y = 0
98C2: LDA #$01
98C4: STA $0048
98C6: LDA #$05
98C8: STA $0040 ; Tetrimino X = 5
Di bawah ini adalah matriks 5 ร 5 yang ditumpangkan pada titik ini.
Tidak satu pun dari matriks penciptaan memiliki kuadrat di atas titik awal. Artinya, saat membuat tetrimino, keempat kotaknya segera menjadi terlihat oleh pemain. Namun, jika pemain dengan cepat memutar bagian sebelum sempat jatuh, bagian dari potongan akan sementara disembunyikan di dua baris pertama dari lapangan bermain.
Biasanya kami berpikir bahwa permainan berakhir ketika tumpukan mencapai puncak. Tetapi pada kenyataannya, ini tidak sepenuhnya benar. Permainan berakhir ketika tidak mungkin lagi membuat karya selanjutnya. Artinya, sebelum penampilan gambar, keempat sel dari lapangan bermain sesuai dengan posisi kotak dari tetrimino yang dibuat harus bebas. Angka tersebut dapat dikunci di tempat sedemikian rupa sehingga bagian dari kuadratnya muncul dalam garis bernomor negatif, dan permainan tidak berakhir; namun, di Nintendo Tetris, garis negatif adalah abstraksi yang hanya terkait dengan tetrimino aktif. Setelah gambar diblokir (menjadi bohong), hanya kotak di garis dari nol dan lebih banyak yang ditulis ke lapangan. Secara konseptual, ternyata garis bernomor negatif secara otomatis dihapus setelah diblokir. Namun dalam kenyataannya, permainan tidak menyimpan data ini, memotong bagian atas angka.
Area yang terlihat dari lapangan bermain 20 ร 10 disimpan pada
$0400
baris demi baris, setiap byte berisi nilai ubin latar belakang. Sel kosong dilambangkan oleh ubin
$EF
, kotak hitam solid.
Saat membuat bentuk, tiga tabel pencarian digunakan. Jika ada ID orientasi sewenang-wenang, tabel di
$9956
memberi kita ID orientasi saat membuat jenis tetrimino yang sesuai.
9956: 02 02 02 02 ; Td
995A: 07 07 07 07 ; Jd
995E: 08 08 ; Zh
9960: 0A ; O
9961: 0B 0B ; Sh
9963: 0E 0E 0E 0E ; Ld
9967: 12 12 ; Ih
Lebih mudah untuk menunjukkan ini di tabel.
Misalnya, semua orientasi J dilampirkan ke
Jd
.
Tabel di
$993B
berisi tipe Tetrimino untuk ID orientasi yang diberikan.
993B: 00 00 00 00 ; T
993F: 01 01 01 01 ; J
9943: 02 02 ; Z
9945: 03 ; O
9946: 04 04 ; S
9948: 05 05 05 05 ; L
994C: 06 06 ; I
Untuk lebih jelasnya, saya akan menunjukkan semuanya dalam bentuk tabel.
Kami akan melihat tabel pencarian ketiga di bagian selanjutnya.
Seleksi tetrimino
Nintendo Tetris menggunakan register geser umpan balik linier (LFSR) 16-bit sebagai pembangkit angka pseudo-acak (PRNG) dalam konfigurasi Fibonacci. Nilai 16-bit disimpan sebagai big-endian di alamat
$0017
-
$0018
. Angka sewenang-wenang
$8988
digunakan sebagai Benih.
80BC: LDX #$89
80BE: STX $0017
80C0: DEX
80C1: STX $0018
Setiap angka pseudo-acak berikutnya dihasilkan sebagai berikut: nilainya dianggap sebagai angka 17-bit, dan bit yang paling signifikan diperoleh dengan melakukan XOR untuk bit 1 dan 9. Kemudian nilainya digeser ke kanan, membuang bit yang paling tidak signifikan.
Proses ini terjadi pada
$AB47
.
AB47: LDA $00,X
AB49: AND #$02
AB4B: STA $0000 ; extract bit 1
AB4D: LDA $01,X
AB4F: AND #$02 ; extract bit 9
AB51: EOR $0000
AB53: CLC
AB54: BEQ $AB57
AB56: SEC ; XOR bits 1 and 9 together
AB57: ROR $00,X
AB59: INX
AB5A: DEY ; right shift
AB5B: BNE $AB57 ; shifting in the XORed value
AB5D: RTS ; return
Menariknya, parameter subrutin di atas dapat diatur sehingga fungsi panggilan dapat menentukan lebar register geser dan alamat di mana ia dapat ditemukan dalam memori. Namun, parameter yang sama digunakan di mana-mana, sehingga kita dapat mengasumsikan bahwa pengembang meminjam kode ini di suatu tempat.
Bagi mereka yang ingin memodifikasi algoritma lebih lanjut, saya menulisnya di Java.
int generateNextPseudorandomNumber(int value) { int bit1 = (value >> 1) & 1; int bit9 = (value >> 9) & 1; int leftmostBit = bit1 ^ bit9; return (leftmostBit << 15) | (value >> 1); }
Dan semua kode ini dapat diperas menjadi satu baris.
int generateNextPseudorandomNumber(int value) { return ((((value >> 9) & 1) ^ ((value >> 1) & 1)) << 15) | (value >> 1); }
PRNG ini secara terus menerus dan deterministik menghasilkan 32.767 nilai unik, memulai setiap siklus dari seed asli. Ini adalah kurang dari setengah dari angka yang mungkin bisa masuk dalam register, dan nilai apa pun dalam set ini dapat digunakan sebagai seed. Banyak nilai di luar himpunan membuat rantai yang akhirnya mengarah ke nomor dari himpunan. Namun, beberapa angka awal menghasilkan urutan nol tanpa batas.
Untuk mengevaluasi kinerja PRNG ini secara kasar, saya membuat representasi grafis dari nilai-nilai yang dibuatnya berdasarkan kalimat dengan
RANDOM.ORG .
Saat membuat gambar, PRNG digunakan sebagai generator angka pseudo-acak, bukan bilangan bulat 16-bit. Setiap piksel diwarnai berdasarkan nilai bit 0. Gambar tersebut memiliki ukuran 128 ร 256, artinya mencakup seluruh urutan.
Terlepas dari garis-garis yang hampir tidak terlihat di sisi atas dan kiri, terlihat acak. Tidak ada pola yang jelas muncul.
Setelah memulai, PRNG secara konstan menggeser register, bekerja setidaknya sekali dalam satu frame. Ini tidak hanya terjadi pada layar splash dan layar menu, tetapi juga ketika tetrimino jatuh di antara operasi pembuatan bentuk. Artinya, angka yang muncul berikut ini tergantung pada jumlah frame yang dibutuhkan pemain untuk menempatkan angka tersebut. Faktanya, permainan bergantung pada keacakan dari tindakan orang yang berinteraksi dengannya.
Selama pembuatan gambar, kode dieksekusi di alamat
$9907
, yang memilih jenis angka baru.
9907: INC $001A ; spawnCount++;
9909: LDA $0017 ; index = high byte of randomValue;
990B: CLC
990C: ADC $001A ; index += spawnCount;
990E: AND #$07 ; index &= 7;
9910: CMP #$07 ; if (index == 7) {
9912: BEQ $991C ; goto invalidIndex;
; }
9914: TAX
9915: LDA $994E,X ; newSpawnID = spawnTable[index];
9918: CMP $0019 ; if (newSpawnID != spawnID) {
991A: BNE $9938 ; goto useNewSpawnID;
; }
invalidIndex:
991C: LDX #$17
991E: LDY #$02
9920: JSR $AB47 ; randomValue = generateNextPseudorandomNumber(randomValue);
9923: LDA $0017 ; index = high byte of randomValue;
9925: AND #$07 ; index &= 7;
9927: CLC
9928: ADC $0019 ; index += spawnID;
992A: CMP #$07
992C: BCC $9934
992E: SEC
992F: SBC #$07
9931: JMP $992A ; index %= 7;
9934: TAX
9935: LDA $994E,X ; newSpawnID = spawnTable[index];
useNewSpawnID:
9938: STA $0019 ; spawnID = newSpawnID;
993A: RTS ; return;
Di alamat
$001A
toko
$001A
menyimpan penghitung jumlah angka yang dibuat dengan power-up. Penambahan penghitung dilakukan oleh baris pertama dari subrutin, dan karena itu adalah penghitung byte tunggal, setelah setiap 256 keping kembali ke nol lagi. Karena penghitung tidak diatur ulang di antara game, riwayat game sebelumnya memengaruhi proses pemilihan gambar. Ini adalah cara lain permainan menggunakan pemain sebagai sumber keacakan.
Rutin mengubah byte paling signifikan dari nomor pseudo-acak (
$0017
) ke jenis tetrimino dan menggunakannya sebagai indeks tabel yang terletak di
$994E
untuk mengonversi jenis ke ID orientasi penciptaan bentuk.
994E: 02 ; Td
994F: 07 ; Jd
9950: 08 ; Zh
9951: 0A ; O
9952: 0B ; Sh
9953: 0E ; Ld
9954: 12 ; Ih
Pada tahap pertama konversi, penghitung angka yang dibuat ditambahkan ke byte atas. Kemudian topeng diterapkan untuk menyimpan hanya 3 bit yang lebih rendah. Jika hasilnya bukan 7, maka ini adalah jenis tetrimino yang benar, dan jika tidak sama dengan angka yang dipilih sebelumnya, maka angka tersebut digunakan sebagai indeks dalam tabel untuk membuat angka. Jika tidak, nomor pseudo-acak berikutnya dihasilkan dan mask diterapkan untuk mendapatkan 3 bit lebih rendah dari byte atas, dan kemudian ID orientasi penciptaan bentuk sebelumnya ditambahkan. Akhirnya, operasi modulo dilakukan untuk mendapatkan jenis tetrimino yang benar, yang digunakan sebagai indeks dalam tabel pembuatan bentuk.
Karena prosesor tidak mendukung pembagian dengan sisa, operator ini ditiru dengan berulang kali mengurangi 7 sampai hasilnya menjadi kurang dari 7. Divisi dengan sisa diterapkan ke jumlah byte atas dengan penutup yang diterapkan dan ke ID orientasi penciptaan bentuk sebelumnya. Nilai maksimum dari jumlah ini adalah 25. Artinya, untuk menguranginya menjadi sisa 4, hanya 3 iterasi yang diperlukan.
Di awal setiap permainan, ID orientasi pembuatan bentuk (
$0019
) diinisialisasi dengan nilai
Tu
(
$00
). Nilai ini berpotensi digunakan pada
$9928
selama pembuatan bentuk pertama.
Saat menggunakan ID orientasi sebelumnya untuk membuat gambar, daripada tipe sebelumnya, Tetrimino menambahkan distorsi, karena nilai-nilai ID orientasi tidak terdistribusi secara merata. Ini ditunjukkan pada tabel:
Setiap sel berisi jenis tetrimino, dihitung dengan menambahkan ID orientasi dari gambar yang dibuat (kolom) ke nilai 3-bit (baris), dan kemudian menerapkan sisa pembagian dengan 7. Jumlah, setiap baris berisi duplikat, karena
$07
dan
$0E
dibagi secara merata oleh 7, sedangkan
$0B
dan
$12
memiliki saldo bersama. Garis 0 dan 7 sama karena jarak 7.
Ada 56 kemungkinan kombinasi input, dan jika tipe tetrimino yang dihasilkan terdistribusi secara merata, maka kita dapat berharap bahwa pada tabel di atas, setiap tipe akan muncul tepat 8 kali. Tetapi seperti yang ditunjukkan di bawah ini, ini tidak terjadi.
T dan S muncul lebih sering, dan L dan I - lebih jarang. Tetapi kode miring menggunakan ID orientasi tidak mengeksekusi setiap kali subrutin dipanggil.
Misalkan PRNG memang membuat urutan nilai independen statistik terdistribusi secara seragam. Ini sebenarnya adalah asumsi yang adil, mengingat bagaimana permainan mencoba untuk mendapatkan keacakan yang tepat dari tindakan pemain. Menambahkan jumlah angka yang dibuat ke alamat
$990C
tidak akan mempengaruhi distribusi, karena jumlahnya meningkat secara merata antar panggilan. Menggunakan bitmask pada
$990E
mirip dengan menerapkan pembagian dengan 8 dengan sisanya, yang juga tidak mempengaruhi distribusi. Oleh karena itu, memeriksa
$9910
masuk ke
invalidIndex
di 1/8 dari semua kasus. Dan probabilitas memukul ketika memeriksa di alamat
$9918
, di mana angka yang baru dipilih dibandingkan dengan angka sebelumnya, adalah 7/8, dengan kemungkinan kebetulan 1/7.
Ini berarti bahwa ada peluang tambahan untuk 7/8 ร 1/7 = 1/8
masuk invalidIndex
. Secara umum, ada kemungkinan 25% menggunakan kode miring dan 75% kemungkinan menggunakan kode yang memilih Tetrimino secara merata.Dalam satu set 224 tetriminos yang dibuat, ekspektasi matematis adalah 32 instance untuk setiap tipe. Tetapi sebenarnya kode menciptakan distribusi berikut:Yaitu, membersihkan 90 garis dan mencapai level 9, pemain akan menerima satu tambahan T dan S dan satu kurang L dan I daripada yang diharapkan secara statistik.Tetrimino dipilih dengan probabilitas berikut:Tampaknya dalam pernyataan bahwa "tongkat panjang" saya tidak pernah muncul ketika dibutuhkan, ada bagian dari kebenaran (setidaknya untuk Nintendo Tetris).Pergeseran Tetrimino
Nintendo Tetris menggunakan Delayed Auto Shift (DAS). Mengklik "Kiri" atau "Kanan" langsung menggerakkan tetrimino satu sel secara horizontal. Sambil memegang salah satu tombol arah ini menyebabkan game secara otomatis menggeser gambar setiap 6 frame dengan penundaan awal 16 frame.Jenis gerakan horisontal ini dikendalikan oleh kode di alamat $89AE
. Seperti dalam kode rotasi, variabel sementara digunakan di sini untuk membuat cadangan koordinat jika posisi baru salah. Perhatikan bahwa cek mencegah Anda dari memindahkan potongan sementara pemain menekan Bawah.89AE: LDA $0040
89B0: STA $00AE ; originalX = tetriminoX;
89B2: LDA $00B6 ; if (pressing down) {
89B4: AND #$04 ; return;
89B6: BNE $8A09 ; }
89B8: LDA $00B5 ; if (just pressed left/right) {
89BA: AND #$03 ; goto resetAutorepeatX;
89BC: BNE $89D3 ; }
89BE: LDA $00B6 ; if (not pressing left/right) {
89C0: AND #$03 ; return;
89C2: BEQ $8A09 ; }
89C4: INC $0046 ; autorepeatX++;
89C6: LDA $0046 ; if (autorepeatX < 16) {
89C8: CMP #$10 ; return;
89CA: BMI $8A09 ; }
89CC: LDA #$0A
89CE: STA $0046 ; autorepeatX = 10;
89D0: JMP $89D7 ; goto buttonHeldDown;
resetAutorepeatX:
89D3: LDA #$00
89D5: STA $0046 ; autorepeatX = 0;
buttonHeldDown:
89D7: LDA $00B6 ; if (not pressing right) {
89D9: AND #$01 ; goto notPressingRight;
89DB: BEQ $89EC ; }
89DD: INC $0040 ; tetriminoX++;
89DF: JSR $948B ; if (new position not valid) {
89E2: BNE $8A01 ; goto restoreX;
; }
89E4: LDA #$03
89E6: STA $06F1 ; play shift sound effect;
89E9: JMP $8A09 ; return;
notPressingRight:
89EC: LDA $00B6 ; if (not pressing left) {
89EE: AND #$02 ; return;
89F0: BEQ $8A09 ; }
89F2: DEC $0040 ; tetriminoX--;
89F4: JSR $948B ; if (new position not valid) {
89F7: BNE $8A01 ; goto restoreX;
; }
89F9: LDA #$03
89FB: STA $06F1 ; play shift sound effect;
89FE: JMP $8A09 ; return;
restoreX:
8A01: LDA $00AE
8A03: STA $0040 ; tetriminoX = originalX;
8A05: LDA #$10
8A07: STA $0046 ; autorepeatX = 16;
8A09: RTS ; return;
x
Melontar Tetrimino
Kecepatan keturunan otomatis Tetrimino adalah fungsi dari angka level. Kecepatan dikodekan sebagai jumlah frame yang diberikan untuk keturunan dalam tabel yang terletak di $898E
. Karena NES beroperasi pada 60,0988 frame / s, Anda dapat menghitung periode antara turun dan kecepatan.Tabel ini memiliki total 30 entri. Setelah level 29, nilai frame untuk descent selalu 1.Jumlah integer dari frame untuk descent bukanlah cara yang sangat rinci untuk menggambarkan kecepatan. Seperti yang ditunjukkan pada grafik di bawah, kecepatan meningkat secara eksponensial dengan setiap level. Faktanya, level 29 dua kali lebih cepat dari level 28.Dengan 1 frame / keturunan, pemain tidak memiliki lebih dari 1/3 detik untuk memposisikan gambar, setelah itu ia akan mulai bergerak. Pada kecepatan turun ini, DAS tidak memungkinkan gambar untuk mencapai tepi lapangan bermain sampai terkunci di tempatnya, yang bagi kebanyakan orang berarti mengakhiri permainan dengan cepat. Namun, beberapa pemain, terutama Thor Akerlund , berhasil mengalahkan DAS dengan getaran cepat dari tombol silang ( D-pad
). Dalam kode shift yang ditunjukkan di atas, dapat dilihat bahwa sementara tombol arah horisontal dilepaskan melalui bingkai, dimungkinkan untuk menggeser tetrimino pada level 29 dan di atas dengan setengah frekuensi. Ini adalah maksimum teoretis, tetapi setiap getaran jempol di atas 3,75 ketukan / s dapat mengalahkan keterlambatan asli 16 frame.Jika penurunan otomatis dan pemain-dikendalikan (dengan menekan "Turun") bertepatan dan terjadi dalam satu bingkai, efeknya tidak bertambah. Setiap atau kedua peristiwa ini menyebabkan bentuk turun tepat satu sel dalam bingkai ini.Logika kendali pemicu terletak di $8914
. Tabel frame keturunan di bawah label . Seperti disebutkan di atas, pada level 29 dan di atas, kecepatannya secara konstan sama dengan 1 shutter / frame. (alamat ) memulai keturunan saat mencapai ( ). Peningkatan dilakukan pada alamat di luar fragmen kode ini. Selama penurunan otomatis atau terkontrol, itu diatur ulang ke 0. Variabel ( ) diinisialisasi dengan nilai (di alamat8914: LDA $004E ; if (autorepeatY > 0) {
8916: BPL $8922 ; goto autorepeating;
; } else if (autorepeatY == 0) {
; goto playing;
; }
; game just started
; initial Tetrimino hanging at spawn point
8918: LDA $00B5 ; if (not just pressed down) {
891A: AND #$04 ; goto incrementAutorepeatY;
891C: BEQ $8989 ; }
; player just pressed down ending startup delay
891E: LDA #$00
8920: STA $004E ; autorepeatY = 0;
8922: BNE $8939
playing:
8924: LDA $00B6 ; if (left or right pressed) {
8926: AND #$03 ; goto lookupDropSpeed;
8928: BNE $8973 ; }
; left/right not pressed
892A: LDA $00B5
892C: AND #$0F ; if (not just pressed only down) {
892E: CMP #$04 ; goto lookupDropSpeed;
8930: BNE $8973 ; }
; player exclusively just presssed down
8932: LDA #$01
8934: STA $004E ; autorepeatY = 1;
8936: JMP $8973 ; goto lookupDropSpeed;
autorepeating:
8939: LDA $00B6
893B: AND #$0F ; if (down pressed and not left/right) {
893D: CMP #$04 ; goto downPressed;
893F: BEQ $894A ; }
; down released
8941: LDA #$00
8943: STA $004E ; autorepeatY = 0
8945: STA $004F ; holdDownPoints = 0
8947: JMP $8973 ; goto lookupDropSpeed;
downPressed:
894A: INC $004E ; autorepeatY++;
894C: LDA $004E
894E: CMP #$03 ; if (autorepeatY < 3) {
8950: BCC $8973 ; goto lookupDropSpeed;
; }
8952: LDA #$01
8954: STA $004E ; autorepeatY = 1;
8956: INC $004F ; holdDownPoints++;
drop:
8958: LDA #$00
895A: STA $0045 ; fallTimer = 0;
895C: LDA $0041
895E: STA $00AE ; originalY = tetriminoY;
8960: INC $0041 ; tetriminoY++;
8962: JSR $948B ; if (new position valid) {
8965: BEQ $8972 ; return;
; }
; the piece is locked
8967: LDA $00AE
8969: STA $0041 ; tetriminoY = originalY;
896B: LDA #$02
896D: STA $0048 ; playState = UPDATE_PLAYFIELD;
896F: JSR $9CAF ; updatePlayfield();
8972: RTS ; return;
lookupDropSpeed:
8973: LDA #$01 ; tempSpeed = 1;
8975: LDX $0044 ; if (level >= 29) {
8977: CPX #$1D ; goto noTableLookup;
8979: BCS $897E ; }
897B: LDA $898E,X ; tempSpeed = framesPerDropTable[level];
noTableLookup:
897E: STA $00AF ; dropSpeed = tempSpeed;
8980: LDA $0045 ; if (fallTimer >= dropSpeed) {
8982: CMP $00AF ; goto drop;
8984: BPL $8958 ; }
8986: JMP $8972 ; return;
incrementAutorepeatY:
8989: INC $004E ; autorepeatY++;
898B: JMP $8972 ; return;
lookupDropSpeed
fallTimer
$0045
dropSpeed
$00AF
fallTimer
$8892
autorepeatY
$004E
$0A
$8739
), yang ditafsirkan sebagai โ96. Suatu kondisi di awal menyebabkan penundaan awal. Tetrimino pertama tetap berada di udara pada titik penciptaan hingga autorepeatY
meningkat menjadi 0, yang membutuhkan 1,6 detik. Namun, ketika Anda menekan Turun dalam fase ini, itu autorepeatY
langsung ditetapkan 0. Sangat menarik bahwa Anda dapat memindahkan dan memutar angka dalam fase ini dari penundaan awal tanpa membatalkannya.Peningkatan autorepeatY
dilakukan sambil menahan. Ketika mencapai 3, penurunan yang dikontrol pria (keturunan "lembut") terjadi dan autorepeatY
ditetapkan 1. Oleh karena itu, penurunan lembut awal memerlukan 3 frame, tetapi kemudian diulang di setiap frame.Selain itu, autorepeatY
peningkatan dari 0 menjadi 1 hanya ketika permainan mengakui bahwa pemain baru saja mengklik Bawah (at$00B5
), tetapi tidak mengakui menahan. Ini penting karena autorepeatY
diatur ulang ke 0 saat membuat Tetrimino (di alamat $98E8
), yang menciptakan fitur penting: jika pemain sendiri menurunkan angka dan diblokir, dan ia terus menekan "Turun" ketika membuat angka berikutnya, yang sering terjadi pada level tinggi, lalu ini tidak akan menyebabkan keturunan lembut dari sosok baru. Agar ini terjadi, pemain harus melepaskan "Bawah," dan kemudian tekan tombol lagi.Keturunan yang berpotensi lembut dapat meningkatkan poin. holdDownPoints
($004F
) meningkat dengan setiap keturunan, tetapi ketika dirilis, "Down" diatur ulang ke 0. Oleh karena itu, untuk mencetak poin, perlu untuk menurunkan tetrimino ke dalam kunci dengan keturunan lembut. Keturunan lunak jangka pendek, yang mungkin terjadi di jalan gambar, tidak mempengaruhi poin. Akun diperbarui di alamat$9BFE
, tetapi holdDownPoints
diatur ulang ke 0 segera setelah itu, di alamat $9C2F
.Cek, yang mencegah pemain melakukan penurunan lembut dengan pergeseran horisontal angka, mempersulit set poin. Ini berarti bahwa langkah terakhir sebelum mengunci bagian di tempat harus "Turun."Ketika keturunan terjadi, tetriminoY
( $0041
) disalin ke originalY
( $00AE
). Jika posisi baru yang dibuat oleh kenaikan tetriminoY
ternyata tidak benar (yaitu, angka itu mendorong lantai lapangan bermain, atau ditumpangkan pada kotak yang sudah berbohong), maka tetrimino tetap di posisi sebelumnya. Dalam hal ini, ini dikembalikantetriminoY
dan angka tersebut dianggap diblokir. Ini berarti bahwa penundaan sebelum penguncian (jumlah maksimum frame yang diharapkan tetrimino, tahan di udara sebelum mengunci) sama dengan penundaan dalam penurunan.Keturunan kaku (langsung jatuh) tidak didukung di Nintendo Tetris.Geser dan Gulir
Buklet manual Nintendo Tetris memiliki contoh slip bergambar:Geser terdiri dari pemindahan di sepanjang permukaan figur-figur lain atau di sepanjang lantai lapangan bermain. Biasanya digunakan untuk mendorong sosok di bawah kotak menggantung. Geser dapat dilakukan sampai waktu jatuh mencapai kecepatan turun, setelah itu angka akan terkunci di tempatnya. Contoh animasi ditunjukkan di bawah ini.Di sisi lain, menggulir memungkinkan Anda untuk mendorong angka ke dalam ruang yang tidak dapat dicapai dengan cara lain (lihat di bawah).Seperti halnya meluncur, menggulir tidak dimungkinkan tanpa penundaan kunci. Tapi di luar itu, gulir mengeksploitasi cara permainan memanipulasi bentuk. Sebelum memindahkan atau memutar angka, permainan memeriksa bahwa setelah mengubah posisi semua kotak tetrimino akan berada di sel kosong dalam batas-batas lapangan bermain. Pemeriksaan seperti itu, seperti yang ditunjukkan di bawah ini, tidak mencegah rotasi melalui blok yang diisi di dekatnya. Sebagaimana dinyatakan dalam bagian Deskripsi Tetrimino, setiap baris tabel orientasi berisi 12 byte; oleh karena itu, indeks dalam tabel ini dihitung dengan mengalikan ID orientasi tetrimino aktif dengan 12. Seperti yang ditunjukkan di bawah ini, semua perkalian dalam rutin dilakukan dengan menggunakan shift dan penambahan.948B: LDA $0041
948D: ASL
948E: STA $00A8
9490: ASL
9491: ASL
9492: CLC
9493: ADC $00A8
9495: ADC $0040
9497: STA $00A8
9499: LDA $0042
949B: ASL
949C: ASL
949D: STA $00A9
949F: ASL
94A0: CLC
94A1: ADC $00A9
94A3: TAX ; index = 12 * orientationID;
94A4: LDY #$00
94A6: LDA #$04
94A8: STA $00AA ; for(i = 0; i < 4; i++) {
94AA: LDA $8A9C,X ; squareY = orientationTable[index];
94AD: CLC
94AE: ADC $0041 ; cellY = squareY + tetriminoY;
94B0: ADC #$02 ; if (cellY < -2 || cellY >= 20) {
94B2: CMP #$16 ; return false;
94B4: BCS $94E9 ; }
94B6: LDA $8A9C,X
94B9: ASL
94BA: STA $00AB
94BC: ASL
94BD: ASL
94BE: CLC
94BF: ADC $00AB
94C1: CLC
94C2: ADC $00A8
94C4: STA $00AD
94C6: INX
94C7: INX ; index += 2;
94C8: LDA $8A9C,X ; squareX = orientationTable[index];
94CB: CLC
94CC: ADC $00AD
94CE: TAY ; cellX = squareX + tetriminoX;
94CF: LDA ($B8),Y ; if (playfield[10 * cellY + cellX] != EMPTY_TILE) {
94D1: CMP #$EF ; return false;
94D3: BCC $94E9 ; }
94D5: LDA $8A9C,X
94D8: CLC
94D9: ADC $0040 ; if (cellX < 0 || cellX >= 10) {
94DB: CMP #$0A ; return false;
94DD: BCS $94E9 ; }
94DF: INX ; index++;
94E0: DEC $00AA
94E2: BNE $94AA ; }
94E4: LDA #$00
94E6: STA $00A8
94E8: RTS ; return true;
94E9: LDA #$FF
94EB: STA $00A8
94ED: RTS
index = (orientationID << 3) + (orientationID << 2); // index = 8 * orientationID + 4 * orientationID;
(cellY << 3) + (cellY << 1) // 8 * cellY + 2 * cellY
Setiap iterasi siklus menggeser posisi tetrimino oleh koordinat relatif dari salah satu kotak dari tabel orientasi untuk mendapatkan lokasi sel yang sesuai di lapangan bermain. Dia kemudian memeriksa bahwa koordinat sel berada dalam batas-batas lapangan bermain, dan bahwa sel itu sendiri kosong.Komentar lebih jelas menggambarkan cara memeriksa spasi baris. Selain sel-sel dalam garis yang terlihat, kode menganggap dua garis tersembunyi di atas lapangan bermain sebagai posisi hukum kotak tanpa menggunakan kondisi gabungan. Ini berfungsi karena dalam kode tambahan angka negatif yang diwakili oleh variabel byte tunggal setara dengan nilai yang lebih besar dari 127. Dalam kasus ini, nilai minimum adalah โ2, yang disimpan sebagai94AA: LDA $8A9C,X ; squareY = orientationTable[index];
94AD: CLC
94AE: ADC $0041 ; cellY = squareY + tetriminoY;
94B0: ADC #$02 ; if (cellY + 2 >= 22) {
94B2: CMP #$16 ; return false;
94B4: BCS $94E9 ; }
cellY
$FE
(254 dalam notasi desimal). Aneh cek interval ituIndeks dari lapangan bermain adalah jumlah cellY
dikalikan dengan 10 dan cellX
. Namun, ketika cellY
โ1 ( $FF
= 255) atau โ2 ( $FE
= 254), produk menghasilkan โ10 ( $F6
= 246) dan โ20 ( $EC
= 236). Berada dalam interval, cellX
bisa tidak lebih dari 9, yang memberikan indeks maksimum 246 + 9 = 255, dan ini jauh lebih jauh dari akhir lapangan bermain. Namun, game ini menginisialisasi $0400
- $04FF
dengan nilai $EF
(ubin kosong), menciptakan 56 byte tambahan ruang kosong. dilakukan setelah memeriksa sel lapangan bermain. Tapi itu berfungsi dengan benar dalam urutan apa pun. Selain itu, memeriksa interval menghindari kondisi komposit, seperti yang ditunjukkan dalam komentar di bawah ini.cellX
94D5: LDA $8A9C,X
94D8: CLC
94D9: ADC $0040 ; if (cellX >= 10) {
94DB: CMP #$0A ; return false;
94DD: BCS $94E9 ; }
Contoh bergulir yang ditunjukkan di bawah ini dimungkinkan karena cara kode ini memeriksa posisi.Seperti yang ditunjukkan di bawah ini, Anda bahkan dapat melakukan geser dengan menggulir.AI mengambil keuntungan penuh dari kemampuan bergerak Nintendo Tetris, termasuk sliding dan scrolling.Level 30 ke atas
Setelah mencapai level 30, tampaknya level tersebut diatur ulang ke nol.Tetapi level 31 menunjukkan bahwa sesuatu yang lain sedang terjadi:Nilai level yang ditampilkan terletak di tabel di alamat $96B8
.96B8: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Seperti ditunjukkan di bawah, tabel pola memerintahkan agar ubin dengan $00
di $0F
adalah simbol untuk mesin terbang 0
pada F
. Ini berarti bahwa ketika menampilkan angka desimal atau heksadesimal, nilai digit itu sendiri digunakan sebagai indeks dari tabel pola. Dalam kasus kami, nilai level disimpan sebagai binary-coded decimal (BCD); setiap gigitan dari setiap byte dalam urutan adalah nilai ubin.Sayangnya, tampaknya para desainer game berasumsi bahwa tidak ada yang akan lulus level 29, dan karena itu memutuskan untuk memasukkan hanya 30 entri ke dalam tabel. Nilai yang ditampilkan aneh adalah byte yang berbeda setelah tabel. Hanya satu byte (di alamat $0044
) yang digunakan untuk menunjukkan nomor level , itulah sebabnya permainan perlahan-lahan memutar sekitar 256 nilai yang ditunjukkan di bawah ini.20 nilai ordinal pertama sebenarnya adalah tabel lain yang menyimpan offset pada lapangan bermain untuk masing-masing dari 20 baris. Karena bidang permainan dimulai dengan dan setiap baris berisi 10 sel, alamat sel sewenang-wenang adalah: Mengingat bahwa prosesor tidak mendukung perkalian secara langsung, tabel pencarian ini menyediakan cara yang sangat cepat untuk mendapatkan produk. Tabel yang sesuai menempati 40 byte berikutnya. Ini berisi 20 alamat dalam format endian kecil untuk nametable 0 (area memori VRAM yang berisi nilai ubin latar belakang). Mereka adalah pointer ke garis offset lapangan bermain . Bytes yang tersisa dari mana nilai level yang ditampilkan disusun adalah instruksi.96D6: 00 ; 0
96D7: 0A ; 10
96D8: 14 ; 20
96D9: 1E ; 30
96DA: 28 ; 40
96DB: 32 ; 50
96DC: 3C ; 60
96DD: 46 ; 70
96DE: 50 ; 80
96DF: 5A ; 90
96E0: 64 ; 100
96E1: 6E ; 110
96E2: 78 ; 120
96E3: 82 ; 130
96E4: 8C ; 140
96E5: 96 ; 150
96E6: A0 ; 160
96E7: AA ; 170
96E8: B4 ; 180
96E9: BE ; 190
$0400
$0400 + 10 * y + x
$0400 + [$96D6 + y] + x
$06
Baris dan statistik
Jumlah baris dan statistik tetrimino yang lengkap masing-masing menempati 2 byte di alamat berikut.Bahkan, nilai-nilai ini disimpan sebagai BCD endian kecil 16-bit yang dikemas. Misalnya, jumlah baris ditampilkan di bawah ini, yaitu 123. Byte dihitung dari kanan ke kiri sehingga angka desimal berjalan berurutan.Namun, para desainer game mengasumsikan bahwa tidak ada nilai yang lebih besar dari 999. Oleh karena itu, logika tampilan memproses byte pertama dengan benar sebagai BCD yang dikemas, di mana setiap nibble digunakan sebagai nilai ubin. Tetapi keseluruhan byte kedua sebenarnya digunakan sebagai angka desimal teratas. Ketika digit bawah beralih dari 99
ke 00
, kenaikan normal dari byte kedua terjadi. Akibatnya, byte kedua siklus melalui semua 256 ubin. Contohnya ditunjukkan di bawah ini.Setelah menghapus baris, kode berikut dijalankan untuk menambah jumlah baris. Pemeriksaan dilakukan untuk digit tengah dan bawah sehingga mereka tetap antara 0 dan 9. Tetapi digit atas dapat ditingkatkan tanpa batas. Jika setelah penambahan jumlah baris, angka yang lebih rendah adalah 0, maka ini berarti bahwa pemain baru saja menyelesaikan serangkaian 10 baris dan Anda perlu menambah nomor level. Seperti yang dapat Anda lihat dari kode di bawah ini, pemeriksaan tambahan dilakukan sebelum kenaikan level. Pemeriksaan kedua terkait dengan level entri yang dipilih. Untuk menuju ke level tertentu , terlepas dari level awal, pemain harus membersihkan9BA8: INC $0050 ; increment middle-lowest digit pair
9BAA: LDA $0050
9BAC: AND #$0F
9BAE: CMP #$0A ; if (lowest digit > 9) {
9BB0: BMI $9BC7
9BB2: LDA $0050
9BB4: CLC
9BB5: ADC #$06 ; set lowest digit to 0, increment middle digit
9BB7: STA $0050
9BB9: AND #$F0
9BBB: CMP #$A0 ; if (middle digit > 9) {
9BBD: BCC $9BC7
9BBF: LDA $0050
9BC1: AND #$0F
9BC3: STA $0050 ; set middle digit to 0
9BC5: INC $0051 ; increment highest digit
; }
; }
9BC7: LDA $0050
9BC9: AND #$0F
9BCB: BNE $9BFB ; if (lowest digit == 0) {
9BCD: JMP $9BD0
9BD0: LDA $0051
9BD2: STA $00A9
9BD4: LDA $0050
9BD6: STA $00A8 ; copy digits from $0050-$0051 to $00A8-$00A9
9BD8: LSR $00A9
9BDA: ROR $00A8
9BDC: LSR $00A9
9BDE: ROR $00A8
9BE0: LSR $00A9
9BE2: ROR $00A8 ; treat $00A8-$00A9 as a 16-bit packed BCD value
9BE4: LSR $00A9 ; and right-shift it 4 times
9BE6: ROR $00A8 ; this leaves the highest and middle digits in $00A8
9BE8: LDA $0044
9BEA: CMP $00A8 ; if (level < [$00A8]) {
9BEC: BPL $9BFB
9BEE: INC $0044 ; increment level
; }
; }
X
10X
garis. Misalnya, jika seorang pemain mulai di level 5, ia akan tetap di sana sampai ia telah membersihkan 60 baris, setelah itu ia akan pergi ke level 6. Setelah itu, setiap 10 baris tambahan akan mengarah pada peningkatan jumlah level.Untuk melakukan pemeriksaan ini, nilai baris yang diisi disalin dari $0050
- $0051
ke $00A8
- $00A9
. Kemudian salinan digeser ke kanan 4 kali, yang untuk BCD yang dikemas mirip dengan membagi dengan 10. Digit desimal terkecil dibuang, dan digit tertinggi dan tengah digeser oleh satu posisi, menghasilkan camilan $00A8
.Namun, pada alamat tersebut, $9BEA
jumlah level secara langsung dibandingkan dengan nilai paket BCD $00A8
. Tidak ada pencarian di tabel untuk mengonversi nilai BCD ke desimal, dan ini merupakan kesalahan yang jelas. Sebagai contoh, pada gambar di atas, angka level harus dibandingkan dengan $12
(18 dalam desimal), dan bukan 12. Oleh karena itu, jika seorang pemain memutuskan untuk memulai di level 17, maka level sebenarnya akan pergi ke 120 baris, karena 18 lebih dari 17.Tabel menunjukkan jumlah baris yang diharapkan diperlukan untuk transisi di setiap level awal. Ini dibandingkan dengan apa yang sebenarnya terjadi karena bug.Jumlah yang diharapkan sama dengan benar untuk level awal 0โ9. Faktanya, kebetulan untuk entry level 9 adalah acak; 10-15 juga naik ke level berikutnya dengan 100 baris, karena $10
- ini 16 dalam bentuk desimal. Perbedaan terbesar antara yang diharapkan dan yang sebenarnya adalah 60 baris.Saya menduga bug ini disebabkan oleh perubahan desain pada tahap pengembangan selanjutnya. Lihatlah layar menu, yang memungkinkan pemain untuk memilih level entri.Tidak ada penjelasan tentang bagaimana memulai dari level di atas 9. Namun dalam buklet Nintendo Tetris, rahasia ini terungkap:Tampaknya fitur tersembunyi ini ditemukan pada saat terakhir. Mungkin itu ditambahkan sangat dekat dengan tanggal rilis, yang tidak memungkinkan untuk sepenuhnya mengujinya.Bahkan, memeriksa seri awal berisi kesalahan kedua terkait dengan output nilai untuk interval. Di bawah ini adalah komentar dalam kode yang menjelaskan lebih baik apa yang terjadi pada level rendah. Perbandingan dilakukan dengan mengurangi dan memeriksa tanda hasil. Tetapi bilangan bertanda single-byte terbatas pada โ128 hingga 127. Jika perbedaannya kurang dari โ128, jumlahnya terbawa dan hasilnya menjadi angka positif. Prinsip ini dijelaskan dalam komentar pada kode. Ketika memeriksa bahwa perbedaannya ada dalam interval ini, harus diperhitungkan bahwa nomor level, ketika menambah nilai lebih dari 255, melakukan transfer ke 0, dan9BE8: LDA $0044
9BEA: CMP $00A8 ; if (level - [$00A8] < 0) {
9BEC: BPL $9BFB
9BEE: INC $0044 ; increment level
; }
9BE8: LDA $0044 ; difference = level - [$00A8];
9BEA: CMP $00A8 ; if (difference < 0 && difference >= -128) {
9BEC: BPL $9BFB
9BEE: INC $0044 ; increment level
; }
$00A8
berpotensi mengandung nilai apa pun, karena gigitan atasnya diambil dari $0051
, yang kenaikannya dapat terjadi tanpa batas.Efek ini tumpang tindih, menciptakan periode di mana angka level secara keliru tetap tidak berubah. Periode terjadi pada interval reguler 2.900 baris, mulai dari 2.190 baris, dan terakhir untuk 800 baris. Misalnya, dari 2190 ( L90
) hingga 2990 ( T90
), levelnya tetap sama dengan $DB
( 96
), seperti yang ditunjukkan di bawah ini.Periode berikutnya terjadi dari 5090 hingga 5890, levelnya selalu sama dengan $AD
( 06
). Selain itu, selama periode ini, palet warna juga tidak berubah.Halaman mewarnai Tetrimino
Di setiap tingkat, ubin tetrimino diberi 4 warna unik. Warna diambil dari tabel yang terletak di $984C
. Catatannya digunakan kembali setiap 10 level. Dari kiri ke kanan: kolom tabel yang sesuai dengan area hitam, putih, biru dan merah pada gambar di bawah ini.984C: 0F 30 21 12 ; level 0
9850: 0F 30 29 1A ; level 1
9854: 0F 30 24 14 ; level 2
9858: 0F 30 2A 12 ; level 3
985C: 0F 30 2B 15 ; level 4
9860: 0F 30 22 2B ; level 5
9864: 0F 30 00 16 ; level 6
9868: 0F 30 05 13 ; level 7
986C: 0F 30 16 12 ; level 8
9870: 0F 30 27 16 ; level 9
Nilai-nilai sesuai dengan palet warna NES.2 warna pertama dari setiap entri selalu hitam dan putih. Namun, warna pertama sebenarnya diabaikan; terlepas dari nilainya, itu dianggap sebagai warna transparan yang melaluinya latar belakang hitam pekat mengintip.Akses ke tabel warna dilakukan dalam rutinitas di $9808
. Indeks tabel warna didasarkan pada jumlah level dibagi dengan sisa 10. Siklus menyalin entri ke tabel palet di VRAM. Pembagian dengan sisanya ditiru dengan pengurangan konstan 10 hingga hasilnya kurang dari 10. Awal dari subrutin dengan komentar ditunjukkan di bawah ini.9808: LDA $0064
980A: CMP #$0A
980C: BMI $9814
980E: SEC
980F: SBC #$0A
9811: JMP $980A ; index = levelNumber % 10;
9814: ASL
9815: ASL
9816: TAX ; index *= 4;
9817: LDA #$00
9819: STA $00A8 ; for(i = 0; i < 32; i += 16) {
981B: LDA #$3F
981D: STA $2006
9820: LDA #$08
9822: CLC
9823: ADC $00A8
9825: STA $2006 ; palette = $3F00 + i + 8;
9828: LDA $984C,X
982B: STA $2007 ; palette[0] = colorTable[index + 0];
982E: LDA $984D,X
9831: STA $2007 ; palette[1] = colorTable[index + 1];
9834: LDA $984E,X
9837: STA $2007 ; palette[2] = colorTable[index + 2];
983A: LDA $984F,X
983D: STA $2007 ; palette[3] = colorTable[index + 3];
9840: LDA $00A8
9842: CLC
9843: ADC #$10
9845: STA $00A8
9847: CMP #$20
9849: BNE $981B ; }
984B: RTS ; return;
9808: LDA $0064 ; index = levelNumber;
980A: CMP #$0A ; while(index >= 10) {
980C: BMI $9814
980E: SEC
980F: SBC #$0A ; index -= 10;
9811: JMP $980A ; }
Namun, seperti yang dinyatakan pada bagian sebelumnya, pengurangan dan percabangan berdasarkan tanda perbedaan digunakan sebagai perbandingan. Nomor bertanda byte tunggal terbatas pada โ128 hingga 127. Komentar yang diperbarui di bawah ini mencerminkan prinsip ini. Komentar di bawah ini semakin disederhanakan. Kata-kata ini mengungkapkan kesalahan dalam kode. Operasi pembagian sisanya sepenuhnya dilewati untuk level dari 138 dan lebih tinggi. Sebagai gantinya, indeks ditugaskan langsung ke nomor level, yang menyediakan akses ke byte jauh melampaui akhir tabel warna. Seperti yang ditunjukkan di bawah ini, ini bahkan dapat menyebabkan tetrimino yang hampir tidak terlihat.9808: LDA $0064 ; index = levelNumber;
; difference = index - 10;
980A: CMP #$0A ; while(difference >= 0 && difference <= 127) {
980C: BMI $9814
980E: SEC ; index -= 10;
980F: SBC #$0A ; difference = index - 10;
9811: JMP $980A ; }
9808: LDA $0064 ; index = levelNumber;
980A: CMP #$0A ; while(index >= 10 && index <= 137) {
980C: BMI $9814
980E: SEC
980F: SBC #$0A ; index -= 10;
9811: JMP $980A ; }
Di bawah ini adalah warna dari semua 256 level. Ubin disusun dalam 10 kolom untuk menekankan penggunaan siklus tabel warna, dilanggar di level 138. Baris dan kolom di header ditunjukkan dalam desimal.Setelah 255, angka level kembali ke 0.Selain itu, seperti yang disebutkan di bagian sebelumnya, beberapa level tidak berubah hingga 800 baris dihapus. Selama level panjang ini, warna tetap tidak berubah.Mode permainan
Mode permainan yang disimpan di alamat $00C0
menentukan dari berbagai layar dan menu mana yang saat ini ditampilkan kepada pengguna.Seperti yang ditunjukkan di atas, gim ini memiliki rutin yang ditulis dengan cerdas yang bertindak sebagai pernyataan pergantian menggunakan tabel navigasi endian kecil yang terletak tepat setelah panggilan. Daftar di atas menunjukkan alamat semua mode game. Perhatikan bahwa mode "Game" dan "Demo" menggunakan kode yang sama. Rutinitas ini tidak pernah kembali. Sebaliknya, kode tersebut menggunakan alamat pengirim; biasanya itu menunjuk ke instruksi segera setelah panggilan ke lompat ke subrutin (minus 1 byte), tetapi dalam kasus ini menunjuk ke tabel lompat. Alamat pengirim muncul dari tumpukan dan disimpan di - . Setelah menyimpan alamat dari tabel lompat, kode menggunakan nilai dalam register A sebagai indeks dan melakukan transisi yang sesuai.8161: LDA $00C0
8163: JSR $AC82 ; switch(gameMode) {
8166: 00 82 ; case 0: goto 8200; //
8168: 4F 82 ; case 1: goto 824F; //
816A: D1 82 ; case 2: goto 82D1; //
816C: D7 83 ; case 3: goto 83D7; //
816E: 5D 81 ; case 4: goto 815D; // / / /
8170: 5D 81 ; case 5: goto 815D; //
; }
$0000
$0001
AC82: ASL
AC83: TAY
AC84: INY
AC85: PLA
AC86: STA $0000
AC88: PLA ; pop return address off of stack
AC89: STA $0001 ; and store it at $0000-$0001
AC8B: LDA ($00),Y
AC8D: TAX
AC8E: INY
AC8F: LDA ($00),Y
AC91: STA $0001
AC93: STX $0000
AC95: JMP ($0000) ; goto Ath 16-bit address
; in table at [$0000-$0001]
Kode dapat menggunakan saklar rutin ini selama indeks mendekati 0 dan tidak ada spasi atau beberapa di antara kemungkinan kasus.Layar Informasi Hukum
Permainan dimulai dengan layar yang menunjukkan pemberitahuan hukum.Di bagian bawah layar, Aleksey Pazhitnov disebut - sebut sebagai penemu, perancang dan pemrogram Tetris pertama. Pada tahun 1984, bekerja sebagai pengembang komputer di Dorodnitsyn Computing Center (lembaga penelitian terkemuka dari Akademi Ilmu Pengetahuan Rusia di Moskow), ia mengembangkan prototipe permainan pada Electronics-60 (klon Soviet DEC LSI-11 ). Sebuah prototipe dikembangkan untuk mode teks monokrom hijau di mana kotak ditunjukkan oleh pasangan tanda kurung []
. Dengan bantuan anak sekolah berusia 16 tahun, Vadim Gerasimov dan insinyur komputer Dmitry Pavlovsky beberapa hari setelah penemuan game, prototipe diangkut ke PC IBM dengan MS DOS dan Turbo Pascal. Selama dua tahun, mereka menyempurnakan permainan bersama, menambahkan fitur seperti warna tetrimino, statistik, dan, yang lebih penting, waktu dan kode grafis yang memungkinkan game untuk bekerja pada berbagai model PC dan klon.Sayangnya, karena kekhasan Uni Soviet pada waktu itu, upaya mereka untuk mendapatkan uang dari permainan tidak berhasil dan pada akhirnya mereka memutuskan untuk membagikan versi PC dengan teman-teman mereka secara gratis. Sejak saat itu, "Tetris" mulai menyebar secara virus di seluruh negeri dan di luar, disalin dari disk ke disk. Tetapi karena permainan dikembangkan oleh karyawan dari lembaga negara, negara adalah pemiliknya, dan pada tahun 1987, organisasi yang bertanggung jawab atas perdagangan internasional dalam teknologi elektronik mengambil alih lisensi permainan ( Electronorgtekhnika (ELORG)) Singkatan V / O pada layar informasi hukum mungkin merupakan kependekan dari Version Originale.Perusahaan perangkat lunak Inggris Andromeda mencoba untuk mendapatkan hak atas Tetris dan, sebelum penyelesaian transaksi, mensublisensikan permainan tersebut kepada pemasok lain, misalnya, penerbit Inggris untuk permainan komputer Mirrorsoft . Mirrorsoft, pada gilirannya, mensublisensikannya ke Tengen , anak perusahaan Atari Games. Tengen memberikan Bullet-Proof Software hak untuk mengembangkan game untuk komputer dan konsol di Jepang, yang menghasilkan Tetris untuk Nintendo Famicom . Di bawah ini adalah layar informasi hukumnya.Menariknya, dalam versi ini, anak sekolah Vadim Gerasimov disebut desainer dan programmer asli.Mencoba untuk mengamankan versi portabel dari konsol Game Boy yang akan datang, Nintendo menggunakan Bullet-Proof Software untuk menyimpulkan kesepakatan yang sukses secara langsung dengan ELORG. Dalam proses menyimpulkan kesepakatan, ELORG merevisi kontraknya dengan Andromeda, menambahkan bahwa Andromeda hanya mendapatkan hak untuk game untuk komputer dan mesin arcade. Karena itu, Bullet-Proof Software harus membayar royalti ELORG untuk semua kartrid yang dijual untuk Famicom, karena hak yang diterima dari Tengen ternyata palsu. Tetapi melalui rekonsiliasi dengan ELORG, Bullet-Proof Software akhirnya berhasil mendapatkan hak game konsol di seluruh dunia untuk Nintendo.Bullet-Proof Software mensublisensikan hak game portabel Nintendo dan bersama-sama mereka mengembangkan Game Boy Tetris, yang tercermin dalam layar informasi hukum di bawah ini.Dengan hak game konsol global, Nintendo telah mengembangkan versi Tetris untuk NES yang kami eksplorasi dalam artikel ini. Kemudian Bullet-Proof Software mensublisensikan hak Nintendo, yang memungkinkannya untuk terus menjual kartrid untuk Famicom di Jepang.Ini diikuti oleh pertempuran hukum yang kompleks. Baik Nintendo dan Tengen menuntut agar pihak lawan berhenti memproduksi dan menjual versi permainan mereka. Akibatnya, Nintendo menang, dan ratusan ribu kartrid Tengen Tetris hancur. Putusan pengadilan juga melarang beberapa perusahaan lain seperti Mirrorsoft untuk membuat versi konsol.Pajitnov tidak pernah menerima potongan dari ELORG atau negara Soviet. Namun, pada tahun 1991 ia pindah ke Amerika Serikat dan pada tahun 1996 dengan dukungan dari pemilik Bullet-Proof SoftwareHenka Rogers ikut mendirikan The Tetris Company , yang memungkinkannya mengambil untung dari versi perangkat seluler dan konsol modern.Sangat menarik untuk melihat layar informasi hukum sebagai jendela yang memberikan gagasan tentang asal usul permainan yang sederhana dan pertarungan berikutnya untuk hak kekayaan intelektual, karena bagi sebagian besar pemain layar ini hanya merupakan penghalang yang menjengkelkan, hilangnya yang sepertinya harus menunggu selamanya. Penundaan diatur oleh dua penghitung, penghitungan berurutan dari 255 ke 0. Fase pertama tidak dapat dilewati, dan yang kedua dilewati dengan menekan tombol Start. Oleh karena itu, layar informasi hukum ditampilkan setidaknya 4,25 detik dan tidak lebih dari 8,5 detik. Namun, saya pikir sebagian besar pemain menyerah, berhenti menekan Start selama interval pertama, dan karena ini mereka menunggu penyelesaian lengkap.Waktu fase, serta sisa permainan, diatur oleh penangan interrupt terbuka yang disebut pada awal setiap interval pengosongan vertikal, periode waktu singkat antara rendering frame televisi. Artinya, setiap 16.6393 milidetik, eksekusi program normal terganggu oleh kode berikut. Pawang mulai dengan meneruskan nilai register utama ke stack dan mengambilnya setelah selesai agar tidak mengganggu tugas yang terputus. Panggilan memperbarui VRAM, mengubah deskripsi model memori menjadi apa yang ditampilkan di layar. Lebih lanjut, pawang mengurangi nilai penghitung layar informasi hukum jika lebih besar dari nol. Tantangan8005: PHA
8006: TXA
8007: PHA
8008: TYA
8009: PHA ; save A, X, Y
800A: LDA #$00
800C: STA $00B3
800E: JSR $804B ; render();
8011: DEC $00C3 ; legalScreenCounter1--;
8013: LDA $00C3
8015: CMP #$FF ; if (legalScreenCounter1 < 0) {
8017: BNE $801B ; legalScreenCounter1 = 0;
8019: INC $00C3 ; }
801B: JSR $AB5E ; initializeOAM();
801E: LDA $00B1
8020: CLC
8021: ADC #$01
8023: STA $00B1
8025: LDA #$00
8027: ADC $00B2
8029: STA $00B2 ; frameCounter++;
802B: LDX #$17
802D: LDY #$02
802F: JSR $AB47 ; randomValue = generateNextPseudorandomNumber(randomValue);
8032: LDA #$00
8034: STA $00FD
8036: STA $2005 ; scrollX = 0;
8039: STA $00FC
803B: STA $2005 ; scrollY = 0;
803E: LDA #$01
8040: STA $0033 ; verticalBlankingInterval = true;
8042: JSR $9D51 ; pollControllerButtons();
8045: PLA
8046: TAY
8047: PLA
8048: TAX
8049: PLA ; restore A, X, Y
804A: RTI ; resume interrupted task
render()
initializeOAM()
melakukan langkah yang diperlukan oleh peralatan pembuatan bingkai. Pawang terus bekerja dengan menambah penghitung bingkai - nilai endian 16-bit kecil yang disimpan di alamat $00B1
- $00B2
yang digunakannya di tempat yang berbeda untuk pengaturan waktu yang terkontrol. Setelah itu, nomor pseudo-acak berikut dihasilkan; seperti yang disebutkan di atas, ini terjadi terlepas dari mode setidaknya sekali per frame. $8040
Bendera interval pengosongan vertikal ditetapkan pada alamat , yang berarti bahwa pawang baru saja dieksekusi. Akhirnya, tombol-tombol pengontrol disurvei; perilaku rutin ini dijelaskan di bawah di bagian Demo.Bendera verticalBlankingInterval
digunakan oleh rutin yang dibahas di atas. Ini berlanjut sampai pelaksanaan interrupt handler dimulai.AA2F: JSR $E000 ; updateAudio();
AA32: LDA #$00
AA34: STA $0033 ; verticalBlankingInterval = false;
AA36: NOP
AA37: LDA $0033
AA39: BEQ $AA37 ; while(!verticalBlankingInterval) { }
AA3B: LDA #$FF
AA3D: LDX #$02
AA3F: LDY #$02
AA41: JSR $AC6A ; fill memory page 2 with all $FF's
AA44: RTS ; return;
Rutin pemblokiran ini digunakan oleh dua tahap waktu layar informasi hukum, yang dieksekusi satu demi satu. Skrip Lua AI memotong keterlambatan ini dengan mengatur kedua penghitung ke 0.8236: LDA #$FF
8238: JSR $A459
...
A459: STA $00C3 ; legalScreenCounter1 = 255;
A45B: JSR $AA2F ; do {
A45E: LDA $00C3 ; waitForVerticalBlankingInterval();
A460: BNE $A45B ; } while(legalScreenCounter1 > 0);
A462: RTS ; return;
823B: LDA #$FF
823D: STA $00A8 ; legalScreenCounter2 = 255;
; do {
823F: LDA $00F5 ; if (just pressed Start) {
8241: CMP #$10 ; break;
8243: BEQ $824C ; }
8245: JSR $AA2F ; waitForVerticalBlankingInterval();
8248: DEC $00A8 ; legalScreenCounter2--;
824A: BNE $823F ; } while(legalScreenCounter2 > 0);
824C: INC $00C0 ; gameMode = TITLE_SCREEN;
Demo
Demo ini menampilkan sekitar 80 detik gameplay pra-rekam. Itu tidak hanya menampilkan file video, tetapi menggunakan mesin yang sama seperti di dalam game. Selama pemutaran, dua tabel digunakan. Yang pertama, terletak di alamat $DF00
, berisi urutan pembuatan tetrimino berikut:TJTSZJTSZJSZLZJTTSITO JSZLZLIOLZLIOJTSITOJ
Saat membuat gambar, itu dipilih secara acak, atau dibaca dari tabel, tergantung pada mode. Perpindahan terjadi di alamat $98EB
. Jenis tetrimino diekstraksi dari bit 6, 5 dan 4 dari setiap byte. Dari waktu ke waktu, operasi ini memberi kita nilai - tipe yang salah. Namun, tabel menciptakan bentuk ( ) digunakan untuk konversi ketik ID tetrimino orientasi sebenarnya terletak antara dua tabel terkait: Arti98EB: LDA $00C0
98ED: CMP #$05
98EF: BNE $9903 ; if (gameMode == DEMO) {
98F1: LDX $00D3
98F3: INC $00D3
98F5: LDA $DF00,X ; value = demoTetriminoTypeTable[++demoIndex];
98F8: LSR
98F9: LSR
98FA: LSR
98FB: LSR
98FC: AND #$07
98FE: TAX ; tetriminoType = bits 6,5,4 of value;
98FF: LDA $994E,X
9902: RTS ; return spawnTable[tetriminoType];
; } else {
; pickRandomTetrimino();
; }
$07
$994E
993B: 00 00 00 00 ; T
993F: 01 01 01 01 ; J
9943: 02 02 ; Z
9945: 03 ; O
9946: 04 04 ; S
9948: 05 05 05 05 ; L
994C: 06 06 ; I
994E: 02 ; Td
994F: 07 ; Jd
9950: 08 ; Zh
9951: 0A ; O
9952: 0B ; Sh
9953: 0E ; Ld
9954: 12 ; Ih
9956: 02 02 02 02 ; Td
995A: 07 07 07 07 ; Jd
995E: 08 08 ; Zh
9960: 0A ; O
9961: 0B 0B ; Sh
9963: 0E 0E 0E 0E ; Ld
9967: 12 12 ; Ih
$07
memaksanya untuk membaca di luar akhir tabel, di berikutnya, yang memberi Td
( $02
).Karena efek ini, skema ini dapat memberi kita urutan ID pseudo-acak yang tak terbatas tetapi dapat direproduksi dari orientasi gambar yang dibuat. Kode akan berfungsi karena setiap alamat yang berubah-ubah dalam urutan byte yang berubah tidak memungkinkan kami untuk menentukan di mana tabel berakhir. Bahkan, urutan di alamat $DF00
dapat menjadi bagian dari sesuatu yang sama sekali tidak terkait dengan ini, terutama mengingat bahwa penugasan 5 bit bukan nol yang tersisa tidak jelas, dan urutan yang dihasilkan menunjukkan pengulangan.Selama inisialisasi mode demo, indeks tabel ( $00D3
) diatur ulang ke alamat $872B
.Tabel kedua dari demo berisi catatan tombol gamepad yang disandikan dalam pasangan byte. Bit dari byte pertama berhubungan dengan tombol.Byte kedua menyimpan jumlah bingkai di mana kombinasi tombol ditekan.Tabel ini memuat alamat $DD00
- $DEFF
dan terdiri dari 256 pasangan. Akses ke sana dilakukan oleh subrutin di alamat $9D5B
. Karena tabel tombol demo panjangnya 512 byte, indeks dua byte diperlukan untuk mengaksesnya. Indeks disimpan sebagai endian kecil di - . Ini diinisialisasi dengan nilai alamat tabel , dan kenaikannya dilakukan oleh kode berikut. Programmer meninggalkan pemrosesan input pemain dalam kode, yang memungkinkan kita untuk melihat proses pengembangan dan mengganti demo dengan catatan lain. Mode perekaman demo diaktifkan ketika nilai diberikan.9D5B: LDA $00D0 ; if (recording mode) {
9D5D: CMP #$FF ; goto recording;
9D5F: BEQ $9DB0 ; }
9D61: JSR $AB9D ; pollController();
9D64: LDA $00F5 ; if (start button pressed) {
9D66: CMP #$10 ; goto startButtonPressed;
9D68: BEQ $9DA3 ; }
9D6A: LDA $00CF ; if (repeats == 0) {
9D6C: BEQ $9D73 ; goto finishedMove;
; } else {
9D6E: DEC $00CF ; repeats--;
9D70: JMP $9D9A ; goto moveInProgress;
; }
finishedMove:
9D73: LDX #$00
9D75: LDA ($D1,X)
9D77: STA $00A8 ; buttons = demoButtonsTable[index];
9D79: JSR $9DE8 ; index++;
9D7C: LDA $00CE
9D7E: EOR $00A8
9D80: AND $00A8
9D82: STA $00F5 ; setNewlyPressedButtons(difference between heldButtons and buttons);
9D84: LDA $00A8
9D86: STA $00CE ; heldButtons = buttons;
9D88: LDX #$00
9D8A: LDA ($D1,X)
9D8C: STA $00CF ; repeats = demoButtonsTable[index];
9D8E: JSR $9DE8 ; index++;
9D91: LDA $00D2 ; if (reached end of demo table) {
9D93: CMP #$DF ; return;
9D95: BEQ $9DA2 ; }
9D97: JMP $9D9E ; goto holdButtons;
moveInProgress:
9D9A: LDA #$00
9D9C: STA $00F5 ; clearNewlyPressedButtons();
holdButtons:
9D9E: LDA $00CE
9DA0: STA $00F7 ; setHeldButtons(heldButtons);
9DA2: RTS ; return;
startButtonPressed:
9DA3: LDA #$DD
9DA5: STA $00D2 ; reset index;
9DA7: LDA #$00
9DA9: STA $00B2 ; counter = 0;
9DAB: LDA #$01
9DAD: STA $00C0 ; gameMode = TITLE_SCREEN;
9DAF: RTS ; return;
$00D1
$00D2
$872D
9DE8: LDA $00D1
9DEA: CLC ; increment [$00D1]
9DEB: ADC #$01 ; possibly causing wrap around to 0
9DED: STA $00D1 ; which produces a carry
9DEF: LDA #$00
9DF1: ADC $00D2
9DF3: STA $00D2 ; add carry to [$00D2]
9DF5: RTS ; return
$00D0
$FF
. Dalam hal ini, kode berikut diluncurkan, dimaksudkan untuk menulis ke tabel tombol untuk demo. Namun, tabel disimpan dalam PRG-ROM. Mencoba menulis tidak akan memengaruhi data yang disimpan. Sebaliknya, setiap operasi penulisan memicu switch bank, menghasilkan efek kesalahan yang ditunjukkan di bawah ini.recording:
9DB0: JSR $AB9D ; pollController();
9DB3: LDA $00C0 ; if (gameMode != DEMO) {
9DB5: CMP #$05 ; return;
9DB7: BNE $9DE7 ; }
9DB9: LDA $00D0 ; if (not recording mode) {
9DBB: CMP #$FF ; return;
9DBD: BNE $9DE7 ; }
9DBF: LDA $00F7 ; if (getHeldButtons() == heldButtons) {
9DC1: CMP $00CE ; goto buttonsNotChanged;
9DC3: BEQ $9DE4 ; }
9DC5: LDX #$00
9DC7: LDA $00CE
9DC9: STA ($D1,X) ; demoButtonsTable[index] = heldButtons;
9DCB: JSR $9DE8 ; index++;
9DCE: LDA $00CF
9DD0: STA ($D1,X) ; demoButtonsTable[index] = repeats;
9DD2: JSR $9DE8 ; index++;
9DD5: LDA $00D2 ; if (reached end of demo table) {
9DD7: CMP #$DF ; return;
9DD9: BEQ $9DE7 ; }
9DDB: LDA $00F7
9DDD: STA $00CE ; heldButtons = getHeldButtons();
9DDF: LDA #$00
9DE1: STA $00CF ; repeats = 0;
9DE3: RTS ; return;
buttonsNotChanged:
9DE4: INC $00CF ; repeats++;
9DE6: RTS
9DE7: RTS ; return;
Ini menunjukkan bahwa pengembang dapat menjalankan program sebagian atau sepenuhnya dalam RAM.Untuk mengatasi kendala ini, saya membuat lua/RecordDemo.lua
satu yang terletak di zip dengan kode sumber . Setelah beralih ke mode perekaman demo, itu mengalihkan operasi tulis ke tabel di konsol Lua. Dari itu, byte dapat disalin dan ditempelkan ke ROM.Untuk merekam demo Anda sendiri, jalankan FCEUX dan unduh file Nintendo Tetris ROM (File | Open ROM ...). Kemudian buka jendela Lua Script (File | Lua | New Lua Script Window ...), telusuri file tersebut atau masukkan path. Tekan tombol Jalankan untuk memulai mode perekaman demo, dan kemudian klik pada jendela FCEUX untuk beralih fokus ke sana. Anda dapat mengontrol bentuk sampai tabel tombol penuh. Setelah itu, game akan secara otomatis kembali ke screen saver. Klik Stop di jendela Lua Script untuk menghentikan skrip. Data yang direkam akan muncul di Konsol Output, seperti yang ditunjukkan pada gambar di bawah ini.Pilih semua konten dan salin ke clipboard (Ctrl + C). Kemudian jalankan Hex Editor (Debug | Hex Editor ...). Dari menu Hex Editor, pilih View | File ROM lalu File | Alamat Goto. Di kotak dialog Goto, masukkan 5D10 (alamat tabel tombol demo di file ROM) dan klik Ok. Kemudian rekatkan isi clipboard (Ctrl + V).Terakhir, di menu FCEUX, pilih NES | Setel ulang Jika Anda berhasil mengulangi semua langkah ini, maka demo harus diganti oleh versi Anda sendiri.Jika Anda ingin menyimpan perubahan, pilih File | Simpan Rom As ... dan masukkan nama file ROM yang dimodifikasi, lalu klik Simpan.Dengan cara yang sama, Anda dapat menyesuaikan urutan tetriminos yang dibuat.Layar kematian
Seperti disebutkan di atas, sebagian besar pemain tidak dapat mengatasi kecepatan penurunan angka di level 29, yang dengan cepat mengarah ke akhir pertandingan. Karena itu, para pemain ia menjadi terkait dengan nama "layar kematian." Tetapi dari sudut pandang teknis, layar kematian tidak memungkinkan pemain untuk melangkah lebih jauh karena bug di mana keturunan cepat sebenarnya bukan bug, tetapi fitur. Para desainer sangat baik bahwa mereka membiarkan permainan untuk melanjutkan sementara pemain mampu menahan kecepatan manusia super.Layar kematian sebenarnya muncul pada sekitar 1550 baris yang ditarik. Ia memanifestasikan dirinya dalam berbagai cara. Terkadang gim ini dimulai kembali. Dalam kasus lain, layar menjadi hitam. Biasanya gim membeku ("membeku") segera setelah menghapus satu baris, seperti yang ditunjukkan di bawah ini. Efek seperti itu sering didahului oleh artefak grafis acak.Layar kematian adalah hasil dari bug dalam kode yang menambahkan poin saat menghapus baris. Akun enam karakter disimpan sebagai BCD endian kecil 24-bit dan terletak di $0053
- $0055
. Untuk melakukan konversi antara jumlah baris yang dihapus dan poin yang diperoleh, sebuah tabel digunakan; setiap entri di dalamnya adalah nilai endian BCD kecil 16 bit yang dikemas. Setelah menambah jumlah baris, dan mungkin level, nilai dalam daftar ini dikalikan dengan jumlah level ditambah satu, dan hasilnya ditambahkan ke poin. Ini jelas ditunjukkan dalam tabel dari buklet manual Nintendo Tetris:9CA5: 00 00 ; 0: 0
9CA7: 40 00 ; 1: 40
9CA9: 00 01 ; 2: 100
9CAB: 00 03 ; 3: 300
9CAD: 00 12 ; 4: 1200
Seperti yang ditunjukkan di bawah ini, perkalian disimulasikan oleh siklus yang menambahkan poin ke skor. Ini dieksekusi setelah bentuk terkunci, bahkan jika tidak ada baris yang dihapus. Sayangnya, Ricoh 2A03 tidak memiliki mode desimal biner 6502; dia bisa sangat menyederhanakan tubuh siklus. Sebagai gantinya, penambahan dilakukan dalam langkah-langkah menggunakan mode biner. Digit apa pun yang melebihi 9 setelah penambahan pada dasarnya diperoleh dengan mengurangi 10 dan menambah digit di sebelah kiri. Misalnya, yang dikonversi menjadi . Tetapi skema semacam itu tidak sepenuhnya dilindungi. Ambil : cek tidak dapat mengubah hasilnya menjadi9C31: LDA $0044
9C33: STA $00A8
9C35: INC $00A8 ; for(i = 0; i <= level; i++) {
9C37: LDA $0056
9C39: ASL
9C3A: TAX
9C3B: LDA $9CA5,X ; points[0] = pointsTable[2 * completedLines];
9C3E: CLC
9C3F: ADC $0053
9C41: STA $0053 ; score[0] += points[0];
9C43: CMP #$A0
9C45: BCC $9C4E ; if (upper digit of score[0] > 9) {
9C47: CLC
9C48: ADC #$60
9C4A: STA $0053 ; upper digit of score[0] -= 10;
9C4C: INC $0054 ; score[1]++;
; }
9C4E: INX
9C4F: LDA $9CA5,X ; points[1] = pointsTable[2 * completedLines + 1];
9C52: CLC
9C53: ADC $0054
9C55: STA $0054 ; score[1] += points[1];
9C57: AND #$0F
9C59: CMP #$0A
9C5B: BCC $9C64 ; if (lower digit of score[1] > 9) {
9C5D: LDA $0054
9C5F: CLC ; lower digit of score[1] -= 10;
9C60: ADC #$06 ; increment upper digit of score[1];
9C62: STA $0054 ; }
9C64: LDA $0054
9C66: AND #$F0
9C68: CMP #$A0
9C6A: BCC $9C75 ; if (upper digit of score[1] > 9) {
9C6C: LDA $0054
9C6E: CLC
9C6F: ADC #$60
9C71: STA $0054 ; upper digit of score[1] -= 10;
9C73: INC $0055 ; score[2]++;
; }
9C75: LDA $0055
9C77: AND #$0F
9C79: CMP #$0A
9C7B: BCC $9C84 ; if (lower digit of score[2] > 9) {
9C7D: LDA $0055
9C7F: CLC ; lower digit of score[2] -= 10;
9C80: ADC #$06 ; increment upper digit of score[2];
9C82: STA $0055 ; }
9C84: LDA $0055
9C86: AND #$F0
9C88: CMP #$A0
9C8A: BCC $9C94 ; if (upper digit of score[2] > 9) {
9C8C: LDA #$99
9C8E: STA $0053
9C90: STA $0054
9C92: STA $0055 ; max out score to 999999;
; }
9C94: DEC $00A8
9C96: BNE $9C37 ; }
$07 + $07 = $0E
$14
$09 + $09 = $12
$18
. Untuk mengkompensasi hal ini, tidak satu pun angka desimal dalam entri dalam kartu skor melebihi 6. Selain itu, agar dapat menggunakannya, digit terakhir dari semua entri selalu 0.Perlu waktu untuk menyelesaikan siklus yang panjang dan rumit ini. Pada level tinggi, sejumlah besar iterasi memengaruhi waktu permainan, karena dibutuhkan lebih dari 1/60 detik untuk menghasilkan setiap frame. Semua ini sebagai akibatnya mengarah pada berbagai manifestasi "layar kematian".Skrip Lua AI membatasi jumlah iterasi dalam satu lingkaran hingga 30 - nilai maksimum yang bisa dicapai oleh desainer seperti yang dirancang oleh desainer, yang menghilangkan layar kematian.Akhir
Dalam buklet Nintendo Tetris, game A-Type dijelaskan sebagai berikut:Permainan ini memberi penghargaan kepada para pemain yang mencetak jumlah poin yang cukup besar di salah satu dari lima animasi akhir. Pilihan akhir seluruhnya didasarkan pada dua digit paling kiri dari skor enam digit. Seperti yang ditunjukkan di bawah ini, untuk mendapatkan salah satu ujungnya, pemain harus mencetak setidaknya 30.000 poin. Perlu dicatat bahwa - adalah cermin alamat - . Akun tersebut digandakan di alamat - . Setelah melewati tes pertama, animasi akhir dipilih oleh pernyataan switch berikut.9A4D: LDA $0075
9A4F: CMP #$03
9A51: BCC $9A5E ; if (score[2] >= $03) {
9A53: LDA #$80
9A55: JSR $A459
9A58: JSR $9E3A
9A5B: JMP $9A64 ; select ending;
; }
$0060
$007F
$0040
$005F
$0073
$0075
A96E: LDA #$00
A970: STA $00C4
A972: LDA $0075 ; if (score[2] < $05) {
A974: CMP #$05 ; ending = 0;
A976: BCC $A9A5 ; }
A978: LDA #$01
A97A: STA $00C4
A97C: LDA $0075 ; else if (score[2] < $07) {
A97E: CMP #$07 ; ending = 1;
A980: BCC $A9A5 ; }
A982: LDA #$02
A984: STA $00C4
A986: LDA $0075 ; else if (score[2] < $10) {
A988: CMP #$10 ; ending = 2;
A98A: BCC $A9A5 ; }
A98C: LDA #$03
A98E: STA $00C4
A990: LDA $0075 ; else if (score[2] < $12) {
A992: CMP #$12 ; ending = 3;
A994: BCC $A9A5 ; }
A996: LDA #$04 ; else {
A998: STA $00C4 ; ending = 4;
; }
Pada akhirnya, roket dengan ukuran yang semakin besar diluncurkan dari landasan peluncuran di sebelah Katedral St. Basil. Di akhir keempat, pesawat ruang angkasa Buran ditampilkan - versi Soviet dari Space Shuttle Amerika. Pada akhir terbaik, katedral itu sendiri naik ke udara, dan sebuah UFO menggantung di atas landasan peluncuran. Di bawah ini adalah gambar dari setiap akhir dan skor yang terkait dengannya.Dalam mode permainan B-Type, tes lain diterapkan, yang dijelaskan dalam buklet Nintendo Tetris sebagai berikut:Jika pemain berhasil mengosongkan 25 baris, maka permainan menunjukkan akhir, tergantung pada level awal. Ujung untuk level 0โ8 terdiri dari hewan dan benda yang terbang atau berlari dalam bingkai, secara misterius lewat di belakang Katedral St. Basil. UFO dari akhir terbaik dari mode A-Type muncul di akhir 3. Pada akhirnya 4, pterosaurus terbang yang punah muncul, dan pada akhirnya 7 naga terbang mitos ditampilkan. Di akhir 2 dan 6, burung tanpa sayap ditampilkan: penguin dan burung unta berlari. Pada akhir 5, langit diisi dengan airships BAIK (jangan dikacaukan dengan Goodyear airships). Dan di akhir 8, banyak "Buranas" menyapu layar, meskipun pada kenyataannya hanya ada satu.Ketinggian awal (plus 1) digunakan sebagai pengganda, yang memberi penghargaan kepada pemain dengan sejumlah besar binatang / benda untuk peningkatan kompleksitas.Di akhir terbaik dari B-Type, sebuah kastil yang penuh dengan karakter dari alam semesta Nintendo ditampilkan: Princess Peach bertepuk tangan, Kid Icarus memainkan biola, Donkey Kong mengetuk drum besar, tarian Mario dan Luigi, Bowser memainkan akordeon, Samus memainkan cello, Link - dengan seruling, sementara kubah Katedral St. Basil melambung ke udara. Jumlah elemen-elemen ini yang ditunjukkan pada akhir tergantung pada ketinggian awal. Di bawah ini adalah gambar dari 10 ujung.AI dapat dengan cepat menghapus semua 25 baris yang diperlukan dalam mode B-Type pada level dan ketinggian awal apa pun, yang memungkinkan Anda untuk melihat salah satu dari ujungnya. Penting juga mengevaluasi seberapa keren dia menangani tumpukan besar blok acak.Pada akhir 0โ8, hingga 6 objek dapat bergerak dalam bingkai. Koordinat y dari objek disimpan dalam tabel yang terletak di at $A7B7
. Jarak horizontal antara objek disimpan dalam tabel di alamat . Urutan nilai dengan tanda di alamat menentukan kecepatan dan arah objek. Indeks sprite disimpan di . Bahkan, setiap objek terdiri dari dua sprite dengan indeks yang berdekatan. Untuk mendapatkan indeks kedua, Anda perlu menambahkan 1. Misalnya, naga terdiri dariA7B7: 98 A8 C0 A8 90 B0 ; 0
A7BD: B0 B8 A0 B8 A8 A0 ; 1
A7C3: C8 C8 C8 C8 C8 C8 ; 2
A7C9: 30 20 40 28 A0 80 ; 3
A7CF: A8 88 68 A8 48 78 ; 4
A7D5: 58 68 18 48 78 38 ; 5
A7DB: C8 C8 C8 C8 C8 C8 ; 6
A7E1: 90 58 70 A8 40 38 ; 7
A7E7: 68 88 78 18 48 A8 ; 8
$A77B
A77B: 3A 24 0A 4A 3A FF ; 0
A781: 22 44 12 32 4A FF ; 1
A787: AE 6E 8E 6E 1E 02 ; 2
A78D: 42 42 42 42 42 02 ; 3
A793: 22 0A 1A 04 0A FF ; 4
A799: EE DE FC FC F6 02 ; 5
A79F: 80 80 80 80 80 FF ; 6
A7A5: E8 E8 E8 E8 48 FF ; 7
A7AB: 80 AE 9E 90 80 02 ; 8
$A771
A771: 01 ; 0: 1
A772: 01 ; 1: 1
A773: FF ; 2: -1
A774: FC ; 3: -4
A775: 01 ; 4: 1
A776: FF ; 5: -1
A777: 02 ; 6: 2
A778: 02 ; 7: 2
A779: FE ; 8: -1
$A7F3
A7F3: 2C ; 0: dragonfly
A7F4: 2E ; 1: dove
A7F5: 54 ; 2: penguin
A7F6: 32 ; 3: UFO
A7F7: 34 ; 4: pterosaur
A7F8: 36 ; 5: blimp
A7F9: 4B ; 6: ostrich
A7FA: 38 ; 7: dragon
A7FB: 3A ; 8: Buran
$38
dan $39
. Ubin untuk sprite ini terdapat dalam tabel pola di bawah ini.Kami memeriksa tabel pusat dari pola di atas, digunakan untuk menampilkan tetrimino dan lapangan bermain. Menariknya, ini berisi seluruh alfabet, sementara yang lain hanya berisi sebagian saja untuk menghemat ruang. Tetapi yang lebih menarik adalah sprite pesawat dan helikopter di tabel pola di sebelah kiri; mereka tidak muncul di akhir atau di bagian lain dari permainan. Ternyata pesawat dan helikopter memiliki indeks sprite $30
dan $16
dan Anda dapat mengubah tabel yang ditunjukkan di atas, untuk melihat mereka beraksi.Sayangnya, tunggangan helikopter tidak ditampilkan, tetapi rotor utama dan rotornya beranimasi indah.2 Pemain Versus
Nintendo Tetris berisi mode dua pemain yang tidak lengkap yang dapat Anda aktifkan dengan mengubah jumlah pemain ( $00BE
) menjadi 2. Seperti yang ditunjukkan di bawah ini, dua bidang game muncul di latar mode single-player.Tidak ada batas antara bidang karena wilayah tengah latar belakang hitam pekat. Nilai yang 003
ditunjukkan di atas bidang bermain menunjukkan jumlah baris yang dihapus oleh masing-masing pemain. Satu-satunya figur umum untuk dua pemain muncul di tempat yang sama dengan mode pemain tunggal. Sayangnya, ini terletak di lapangan bermain yang tepat. Kotak dan ubin lainnya berwarna salah. Dan saat pemain kalah, permainan dimulai kembali.Tetapi jika Anda mengabaikan masalah ini, maka mode ini cukup bisa dimainkan. Setiap pemain dapat secara independen mengontrol potongan-potongan di lapangan bermain yang sesuai. Dan ketika pemain mengetik Double, Triple atau Tetris (yaitu, membersihkan dua, tiga atau empat baris), baris sampah dengan satu kotak yang hilang muncul di bagian bawah bidang permainan lawan.Bidang tambahan terletak di $0500
. A $0060
- $007F
, biasanya menjadi cermin $0040
- $005F
, digunakan untuk pemain kedua.Mungkin, mode menarik ini ditinggalkan karena jadwal pengembangan yang sibuk. Atau mungkin dia dibiarkan belum selesai dengan sengaja. Salah satu alasan Tetris dipilih sebagai game yang dibundel dengan Nintendo Game Boy adalah karena mendorong pembelian Game Link Cable- aksesori yang menghubungkan dua Game Boys bersama-sama untuk meluncurkan 2 pemain versus mode. Kabel ini menambahkan elemen "sosialitas" ke sistem - ini mendorong teman untuk membeli Game Boy untuk bergabung dalam kegembiraan. Mungkin Nintendo takut bahwa jika versi konsol permainan memiliki 2 pemain versus mode, maka kekuatan "iklan" Tetris, yang merangsang pembelian Game Boy, mungkin akan melemah.Efek musik dan suara
Musik latar dihidupkan ketika $06F5
salah satu nilai yang tercantum dalam tabel diberikan.Anda dapat mendengarkan musik yang tidak digunakan dari screen saver di sini . Dalam game itu sendiri, tidak ada yang terdengar selama layar screen saver.Music-1 adalah versi " Dance of the Dragee Fairy ", musik untuk balerina dari babak ketiga pas de deux waltz "The Nutcracker" oleh Tchaikovsky. Musik penutup adalah variasi dari " Bullfighter Verses ", sebuah aria dari opera Carmen Georges Bizet. Komposisi ini diatur oleh komposer dari sisa musik Hirokazu Tanaka .Musik-2 terinspirasi oleh lagu-lagu tradisional Rusia. Musik-3 misterius, futuristik dan lembut; Untuk sementara, itu nada dering telepon dukungan pelanggan Nintendo of America.Untuk membantu pemain jatuh ke dalam kepanikan ketika ketinggian tumpukan mendekati langit-langit lapangan bermain, versi musik latar mulai diputar dengan kecepatan cepat ( $06
- $08
).Menariknya, di antara komposisi musik tidak ada " Chapman ", tema terkenal yang terdengar di Game Boy Tetris.Efek suara dipicu dengan merekam di $06F0
dan $06F1
, sesuai dengan tabel berikut.Status gim dan mode render
Selama bermain game, kondisi permainan saat ini diwakili oleh bilangan bulat di alamat $0048
. Sebagian besar waktu memiliki makna $01
yang menunjukkan bahwa pemain mengontrol tetrimino aktif. Namun, ketika potongan terkunci di tempatnya, permainan secara bertahap beralih dari negara $02
ke negara $08
, seperti yang ditunjukkan dalam tabel.Percabangan kode, tergantung pada kondisi gim, terjadi di alamat berikut $81B2
: Pada kondisi sakelar, lompatan ke kode yang memberikan nilai yang menunjukkan bahwa orientasi tidak diatur. Pawang tidak pernah dipanggil; Namun, kondisi permainan berfungsi sebagai sinyal ke bagian lain dari kode. Negara memungkinkan pemain untuk menggeser, memutar, dan menurunkan tetrimino aktif: Seperti yang dinyatakan dalam bagian sebelumnya, pergeseran, rotasi, dan penurunan rutinitas gambar sebelum menjalankan kode memeriksa posisi baru tetrimino. Satu-satunya cara untuk memblokir bentuk di posisi yang salah adalah membuatnya di atas bentuk yang sudah ada. Dalam hal ini, permainan berakhir. Seperti yang ditunjukkan di bawah ini, kode status melakukan pemeriksaan ini.81B2: LDA $0048
81B4: JSR $AC82 ; switch(playState) {
81B7: 2F 9E ; case 00: goto 9E2F; // Unassign orientationID
81B9: CF 81 ; case 01: goto 81CF; // Player controls active Tetrimino
81BB: A2 99 ; case 02: goto 99A2; // Lock Tetrimino into playfield
81BD: 6B 9A ; case 03: goto 9A6B; // Check for completed rows
81BF: 39 9E ; case 04: goto 9E39; // Display line clearing animation
81C1: 58 9B ; case 05: goto 9B58; // Update lines and statistics
81C3: F2 A3 ; case 06: goto A3F2; // B-Type goal check; Unused frame for A-Type
81C5: 03 9B ; case 07: goto 9B03; // Unused frame; Execute unfinished 2 player mode logic
81C7: 8E 98 ; case 08: goto 988E; // Spawn next Tetrimino
81C9: 39 9E ; case 09: goto 9E39; // Unused
81CB: 11 9A ; case 0A: goto 9A11; // Update game over curtain
81CD: 37 9E ; case 0B: goto 9E37; // Increment play state
; }
$00
orientationID
$13
9E2F: LDA #$13
9E31: STA $0042 ; orientationID = UNASSIGNED;
9E33: RTS ; return;
$00
$01
81CF: JSR $89AE ; shift Tetrimino;
81D2: JSR $88AB ; rotate Tetrimino;
81D5: JSR $8914 ; drop Tetrimino;
81D8: RTS ; return;
$02
. Jika posisi terkunci benar, ini menandai 4 sel terkait dari lapangan bermain sebagai ditempati. Jika tidak, ia membuat transisi ke keadaan - tirai yang tidak menyenangkan di akhir permainan.99A2: JSR $948B ; if (new position valid) {
99A5: BEQ $99B8 ; goto updatePlayfield;
; }
99A7: LDA #$02
99A9: STA $06F0 ; play curtain sound effect;
99AC: LDA #$0A
99AE: STA $0048 ; playState = UPDATE_GAME_OVER_CURTAIN;
99B0: LDA #$F0
99B2: STA $0058 ; curtainRow = -16;
99B4: JSR $E003 ; updateAudio();
99B7: RTS ; return;
$0A
Tirai ditarik dari bagian atas lapangan bermain ke bawah, turun satu baris setiap 4 frame. curtainRow
( $0058
) menginisialisasi dengan nilai โ16, menciptakan penundaan tambahan 0,27 detik antara kunci terakhir dan awal animasi. Di alamat $9A21
dalam keadaan $0A
kode yang ditunjukkan di bawah ini, tabel perkalian diakses, yang keliru ditampilkan sebagai angka level. Ini dilakukan untuk menskalakan curtainRow
10. Selain itu, seperti yang ditunjukkan di atas, kode di alamat $9A51
memulai animasi akhir jika skor pemain tidak kurang dari 30.000 poin; jika tidak, ia akan mengklik Mulai. Kode selesai dengan memberikan nilai ke status permainan , tetapi penangan yang sesuai tidak dipanggil karena permainan selesai.9A11: LDA $0058 ; if (curtainRow == 20) {
9A13: CMP #$14 ; goto endGame;
9A15: BEQ $9A47 ; }
9A17: LDA $00B1 ; if (frameCounter not divisible by 4) {
9A19: AND #$03 ; return;
9A1B: BNE $9A46 ; }
9A1D: LDX $0058 ; if (curtainRow < 0) {
9A1F: BMI $9A3E ; goto incrementCurtainRow;
; }
9A21: LDA $96D6,X
9A24: TAY ; rowIndex = 10 * curtainRow;
9A25: LDA #$00
9A27: STA $00AA ; i = 0;
9A29: LDA #$13
9A2B: STA $0042 ; orientationID = NONE;
drawCurtainRow:
9A2D: LDA #$4F
9A2F: STA ($B8),Y ; playfield[rowIndex + i] = CURTAIN_TILE;
9A31: INY
9A32: INC $00AA ; i++;
9A34: LDA $00AA
9A36: CMP #$0A ; if (i != 10) {
9A38: BNE $9A2D ; goto drawCurtainRow;
; }
9A3A: LDA $0058
9A3C: STA $0049 ; vramRow = curtainRow;
incrementCurtainRow:
9A3E: INC $0058 ; curtainRow++;
9A40: LDA $0058 ; if (curtainRow != 20) {
9A42: CMP #$14 ; return;
9A44: BNE $9A46 ; }
9A46: RTS ; return;
endGame:
9A47: LDA $00BE
9A49: CMP #$02
9A4B: BEQ $9A64 ; if (numberOfPlayers == 1) {
9A4D: LDA $0075
9A4F: CMP #$03
9A51: BCC $9A5E ; if (score[2] >= $03) {
9A53: LDA #$80
9A55: JSR $A459
9A58: JSR $9E3A
9A5B: JMP $9A64 ; select ending;
; }
9A5E: LDA $00F5 ; if (not just pressed Start) {
9A60: CMP #$10 ; return;
9A62: BNE $9A6A ; }
; }
9A64: LDA #$00
9A66: STA $0048 ; playState = INITIALIZE_ORIENTATION_ID;
9A68: STA $00F5 ; clear newly pressed buttons;
9A6A: RTS ; return;
$00
Baris dari lapangan bermain secara bertahap disalin ke VRAM untuk menampilkannya. Indeks baris saat ini untuk disalin terkandung dalam vramRow
( $0049
). $9A3C
vramRow
Nilai ditugaskan di alamat curtainRow
, yang akhirnya membuat garis ini terlihat saat rendering.Manipulasi dengan VRAM terjadi selama interval pengosongan vertikal, yang diakui oleh pengendali interupsi yang dijelaskan dalam bagian "Layar Informasi Hukum". Ia menyebut subrutin yang ditunjukkan di bawah ini (ditandai dalam komentar interrupt handler as render()
). Mode rendering mirip dengan mode game. Itu disimpan di alamat dan dapat memiliki salah satu dari nilai berikut:804B: LDA $00BD
804D: JSR $AC82 ; switch(renderMode) {
8050: B1 82 ; case 0: goto 82B1; // Legal and title screens
8052: DA 85 ; case 1: goto 85DA; // Menu screens
8054: 44 A3 ; case 2: goto A344; // Congratulations screen
8056: EE 94 ; case 3: goto 94EE; // Play and demo
8058: 95 9F ; case 4: goto 9F95; // Ending animation
; }
$00BD
Bagian dari mode rendering $03
ditunjukkan di bawah ini. Seperti yang dapat Anda lihat di bawah, ini melewati VRAM garis lapangan bermain dengan indeks . Jika lebih besar dari 20, rutin tidak melakukan apa pun. Tabel ( ) berisi alamat-alamat VRAM dalam format endian kecil yang sesuai dengan garis-garis yang ditampilkan dari lapangan bermain bergeser 6 dalam mode normal dan by2 dan 12 untuk lapangan bermain dalam mode yang belum selesai 2 Player Versus. Byte dari tabel ini adalah bagian dari daftar nilai yang keliru ditampilkan sebagai angka level setelah level 29. Bytes yang lebih rendah dan lebih tinggi dari masing-masing alamat diperoleh secara terpisah dan pada dasarnya digabungkan menjadi alamat 16-bit, yang digunakan dalam siklus penyalinan. Selisih dieksekusi pada akhir subrutin.952A: JSR $9725 ; copyPlayfieldRowToVRAM();
952D: JSR $9725 ; copyPlayfieldRowToVRAM();
9530: JSR $9725 ; copyPlayfieldRowToVRAM();
9533: JSR $9725 ; copyPlayfieldRowToVRAM();
copyPlayfieldRowToVRAM()
vramRow
vramRow
9725: LDX $0049 ; if (vramRow > 20) {
9727: CPX #$15 ; return;
9729: BPL $977E ; }
972B: LDA $96D6,X
972E: TAY ; playfieldAddress = 10 * vramRow;
972F: TXA
9730: ASL
9731: TAX
9732: INX ; high = vramPlayfieldRows[vramRow * 2 + 1];
9733: LDA $96EA,X
9736: STA $2006
9739: DEX
973A: LDA $00BE
973C: CMP #$01
973E: BEQ $975E ; if (numberOfPlayers == 2) {
9740: LDA $00B9
9742: CMP #$05
9744: BEQ $9752 ; if (leftPlayfield) {
9746: LDA $96EA,X
9749: SEC
974A: SBC #$02
974C: STA $2006 ; low = vramPlayfieldRows[vramRow * 2] - 2;
974F: JMP $9767 ; } else {
9752: LDA $96EA,X
9755: CLC
9756: ADC #$0C
9758: STA $2006 ; low = vramPlayfieldRows[vramRow * 2] + 12;
975B: JMP $9767 ; } else {
975E: LDA $96EA,X
9761: CLC
9762: ADC #$06 ; low = vramPlayfieldRows[vramRow * 2] + 6;
9764: STA $2006 ; }
; vramAddress = (high << 8) | low;
9767: LDX #$0A
9769: LDA ($B8),Y
976B: STA $2007
976E: INY ; for(i = 0; i < 10; i++) {
976F: DEX ; vram[vramAddress + i] = playfield[playfieldAddress + i];
9770: BNE $9769 ; }
9772: INC $0049 ; vramRow++;
9774: LDA $0049 ; if (vramRow < 20) {
9776: CMP #$14 ; return;
9778: BMI $977E ; }
977A: LDA #$20
977C: STA $0049 ; vramRow = 32;
977E: RTS ; return;
vramPlayfieldRows
$96EA
vramRow
. Jika nilainya mencapai 20, maka diberikan nilai 32, yang berarti bahwa salinannya telah selesai sepenuhnya. Seperti yang ditunjukkan di atas, hanya 4 baris yang disalin per bingkai.Penangan negara $03
bertanggung jawab untuk mengenali garis yang telah diselesaikan dan menghapusnya dari lapangan permainan. Selama 4 panggilan terpisah, ia memindai garis offset di [โ2, 1]
dekat pusat tetrimino (kedua koordinat dari semua kotak tetrimino berada dalam interval ini). Indeks baris selesai disimpan di $004A
- $004D
; indeks yang direkam 0 digunakan untuk menunjukkan bahwa tidak ada baris lengkap yang ditemukan dalam pass ini. Pawang ditunjukkan di bawah ini. Pemeriksaan di awal tidak memungkinkan pawang untuk mengeksekusi ketika mentransfer garis lapangan bermain ke VRAM (pengendali keadaan)9A6B: LDA $0049
9A6D: CMP #$20 ; if (vramRow < 32) {
9A6F: BPL $9A74 ; return;
9A71: JMP $9B02 ; }
9A74: LDA $0041 ; rowY = tetriminoY - 2;
9A76: SEC
9A77: SBC #$02 ; if (rowY < 0) {
9A79: BPL $9A7D ; rowY = 0;
9A7B: LDA #$00 ; }
9A7D: CLC
9A7E: ADC $0057
9A80: STA $00A9 ; rowY += lineIndex;
9A82: ASL
9A83: STA $00A8
9A85: ASL
9A86: ASL
9A87: CLC
9A88: ADC $00A8
9A8A: STA $00A8 ; rowIndex = 10 * rowY;
9A8C: TAY
9A8D: LDX #$0A
9A8F: LDA ($B8),Y
9A91: CMP #$EF ; for(i = 0; i < 10; i++) {
9A93: BEQ $9ACC ; if (playfield[rowIndex + i] == EMPTY_TILE) {
9A95: INY ; goto rowNotComplete;
9A96: DEX ; }
9A97: BNE $9A8F ; }
9A99: LDA #$0A
9A9B: STA $06F1 ; play row completed sound effect;
9A9E: INC $0056 ; completedLines++;
9AA0: LDX $0057
9AA2: LDA $00A9
9AA4: STA $4A,X ; lines[lineIndex] = rowY;
9AA6: LDY $00A8
9AA8: DEY
9AA9: LDA ($B8),Y
9AAB: LDX #$0A
9AAD: STX $00B8
9AAF: STA ($B8),Y
9AB1: LDA #$00
9AB3: STA $00B8
9AB5: DEY ; for(i = rowIndex - 1; i >= 0; i--) {
9AB6: CPY #$FF ; playfield[i + 10] = playfield[i];
9AB8: BNE $9AA9 ; }
9ABA: LDA #$EF
9ABC: LDY #$00
9ABE: STA ($B8),Y
9AC0: INY ; for(i = 0; i < 10; i++) {
9AC1: CPY #$0A ; playfield[i] = EMPTY_TILE;
9AC3: BNE $9ABE ; }
9AC5: LDA #$13
9AC7: STA $0042 ; orientationID = UNASSIGNED;
9AC9: JMP $9AD2 ; goto incrementLineIndex;
rowNotComplete:
9ACC: LDX $0057
9ACE: LDA #$00
9AD0: STA $4A,X ; lines[lineIndex] = 0;
incrementLineIndex:
9AD2: INC $0057 ; lineIndex++;
9AD4: LDA $0057 ; if (lineIndex < 4) {
9AD6: CMP #$04 ; return;
9AD8: BMI $9B02 ; }
9ADA: LDY $0056
9ADC: LDA $9B53,Y
9ADF: CLC
9AE0: ADC $00BC
9AE2: STA $00BC ; totalGarbage += garbageLines[completedLines];
9AE4: LDA #$00
9AE6: STA $0049 ; vramRow = 0;
9AE8: STA $0052 ; clearColumnIndex = 0;
9AEA: LDA $0056
9AEC: CMP #$04
9AEE: BNE $9AF5 ; if (completedLines == 4) {
9AF0: LDA #$04 ; play Tetris sound effect;
9AF2: STA $06F1 ; }
9AF5: INC $0048 ; if (completedLines > 0) {
9AF7: LDA $0056 ; playState = DISPLAY_LINE_CLEARING_ANIMATION;
9AF9: BNE $9B02 ; return;
; }
9AFB: INC $0048 ; playState = UPDATE_LINES_AND_STATISTICS;
9AFD: LDA #$07
9AFF: STA $06F1 ; play piece locked sound effect;
9B02: RTS ; return;
vramRow
$03
disebut dalam setiap frame). Jika baris yang diisi terdeteksi, maka vramRow
reset ke 0, yang memaksa pemindahan total.lineIndex
( $00A9
) diinisialisasi dengan nilai 0 dan kenaikannya dilakukan di setiap pass.Tidak seperti keadaan gim $0A
dan rutinitas salin bidang gim, yang menggunakan tabel perkalian alamat $96D6
, blok yang dimulai dengan $9A82
mengalikan rowY
dengan 10 menggunakan shift dan penambahan:rowIndex = (rowY << 1) + (rowY << 3); // rowIndex = 2 * rowY + 8 * rowY;
Ini dilakukan hanya karena rowY
dibatasi oleh interval [0, 20]
, dan tabel perkalian hanya mencakup [0, 19]
. Pemindaian baris dapat melampaui batas lapangan permainan. Namun, seperti yang dikatakan sebelumnya, permainan menginisialisasi $0400
- $04FF
dengan nilai$EF
(ubin kosong), membuat lebih dari 5 garis tersembunyi kosong tambahan di bawah lantai lapangan bermain.Blok yang dimulai dengan $9ADA
adalah bagian dari mode 2 Player Versus yang tidak lengkap. Seperti disebutkan di atas, membersihkan baris menambah puing ke lapangan permainan lawan. Jumlah baris sampah ditentukan oleh tabel di alamat $9B53
: Siklus di alamat menggeser materi di atas baris yang diisi satu baris ke bawah. Dia mengambil keuntungan dari kenyataan bahwa setiap baris dalam urutan kontinyu dipisahkan dari yang lain dengan 10 byte. Loop berikutnya membersihkan baris atas. Animasi baris-jelas dilakukan selama keadaan permainan , tetapi seperti yang ditunjukkan di bawah ini, itu tidak terjadi di pengendali keadaan permainan, yang benar-benar kosong.9B53: 00 ; no cleared lines
9B54: 00 ; Single
9B55: 01 ; Double
9B56: 02 ; Triple
9B57: 04 ; Tetris
$9AA6
$04
9E39: RTS ; return;
Sebagai gantinya, selama kondisi permainan $04
, percabangan berikutnya dari mode rendering dilakukan $03
. dan nilai cermin diperlukan untuk mode yang belum selesai 2 Player Versus. Subrutin ditunjukkan di bawah ini . Itu disebut di setiap frame, tetapi kondisi di awal memungkinkan untuk dieksekusi hanya di setiap frame keempat. Di setiap lintasan, ia menelusuri daftar indeks dari baris yang telah selesai dan membersihkan 2 kolom di baris ini, bergerak dari kolom tengah ke arah luar. Alamat VRAM 16-bit dibangun dengan cara yang sama seperti yang ditunjukkan dalam rutinitas bidang salin. Namun, dalam hal ini, ia melakukan offset dengan indeks kolom yang diperoleh dari tabel di bawah ini.94EE: LDA $0068
94F0: CMP #$04
94F2: BNE $9522 ; if (playState == DISPLAY_LINE_CLEARING_ANIMATION) {
94F4: LDA #$04
94F6: STA $00B9 ; leftPlayfield = true;
94F8: LDA $0072
94FA: STA $0052
94FC: LDA $006A
94FE: STA $004A
9500: LDA $006B
9502: STA $004B
9504: LDA $006C
9506: STA $004C
9508: LDA $006D
950A: STA $004D
950C: LDA $0068
950E: STA $0048 ; mirror values;
9510: JSR $977F ; updateLineClearingAnimation();
; ...
; }
leftPlayfield
updateLineClearingAnimation()
977F: LDA $00B1 ; if (frameCounter not divisible by 4) {
9781: AND #$03 ; return;
9783: BNE $97FD ; }
9785: LDA #$00 ; for(i = 0; i < 4; i++) {
9787: STA $00AA ; rowY = lines[i];
9789: LDX $00AA ; if (rowY == 0) {
978B: LDA $4A,X ; continue;
978D: BEQ $97EB ; }
978F: ASL
9790: TAY
9791: LDA $96EA,Y
9794: STA $00A8 ; low = vramPlayfieldRows[2 * rowY];
9796: LDA $00BE ; if (numberOfPlayers == 2) {
9798: CMP #$01 ; goto twoPlayers;
979A: BNE $97A6 ; }
979C: LDA $00A8
979E: CLC
979F: ADC #$06
97A1: STA $00A8 ; low += 6;
97A3: JMP $97BD ; goto updateVRAM;
twoPlayers:
97A6: LDA $00B9
97A8: CMP #$04
97AA: BNE $97B6 ; if (leftPlayfield) {
97AC: LDA $00A8
97AE: SEC
97AF: SBC #$02
97B1: STA $00A8 ; low -= 2;
97B3: JMP $97BD ; } else {
97B6: LDA $00A8
97B8: CLC
97B9: ADC #$0C ; low += 12;
97BB: STA $00A8 ; }
updateVRAM:
97BD: INY
97BE: LDA $96EA,Y
97C1: STA $00A9
97C3: STA $2006
97C6: LDX $0052 ; high = vramPlayfieldRows[2 * rowY + 1];
97C8: LDA $97FE,X
97CB: CLC ; rowAddress = (high << 8) | low;
97CC: ADC $00A8
97CE: STA $2006 ; vramAddress = rowAddress + leftColumns[clearColumnIndex];
97D1: LDA #$FF
97D3: STA $2007 ; vram[vramAddress] = 255;
97D6: LDA $00A9
97D8: STA $2006
97DB: LDX $0052 ; high = vramPlayfieldRows[2 * rowY + 1];
97DD: LDA $9803,X
97E0: CLC ; rowAddress = (high << 8) | low;
97E1: ADC $00A8
97E3: STA $2006 ; vramAddress = rowAddress + rightColumns[clearColumnIndex];
97E6: LDA #$FF
97E8: STA $2007 ; vram[vramAddress] = 255;
97EB: INC $00AA
97ED: LDA $00AA
97EF: CMP #$04
97F1: BNE $9789 ; }
97F3: INC $0052 ; clearColumnIndex++;
97F5: LDA $0052 ; if (clearColumnIndex < 5) {
97F7: CMP #$05 ; return;
97F9: BMI $97FD ; }
97FB: INC $0048 ; playState = UPDATE_LINES_AND_STATISTICS;
97FD: RTS ; return;
97FE: 04 03 02 01 00 ; left columns
9803: 05 06 07 08 09 ; right columns
Untuk membersihkan animasi, diperlukan 5 pass. Kemudian kode berlanjut ke status permainan berikutnya.Penangan status gim $05
berisi kode yang dijelaskan di bagian "Baris dan Statistik". Pawang berakhir dengan kode ini: Variabel tidak direset hingga akhir kondisi permainan , setelah itu digunakan untuk memperbarui jumlah total baris dan skor. Urutan ini memungkinkan bug menarik untuk dieksekusi. Dalam mode demo, Anda harus menunggu sampai permainan mengumpulkan baris penuh, dan kemudian dengan cepat tekan Mulai sampai animasi untuk menghapus seri telah berakhir. Gim akan kembali ke screen saver, tetapi jika Anda memilih waktu yang tepat, nilainya akan disimpan. Sekarang Anda dapat memulai permainan dalam mode A-Type. Ketika dikunci di tempat angka pertama, pengendali status permainan9C9E: LDA #$00
9CA0: STA $0056 ; completedLines = 0;
9CA2: INC $0048 ; playState = B_TYPE_GOAL_CHECK;
9CA4: RTS ; return;
completedLines
$05
completedLines
$03
mulai memindai baris yang sudah selesai. Dia tidak akan menemukan mereka, tetapi membiarkannya completedLines
tidak berubah. Akhirnya, ketika kondisi permainan terpenuhi, $05
jumlah total baris dan skor akan meningkat, seolah-olah Anda telah mencetaknya.Cara termudah untuk melakukan ini adalah untuk mendapatkan jumlah terbesar, menunggu demo untuk mengumpulkan Tetris (akan ada 2 dari mereka dalam demo). Segera setelah Anda melihat layar berkedip, klik Mulai.
Setelah memulai gim baru, layar akan terus berkedip. Semua ini berkat kode berikut yang disebut oleh interrupt handler. Bahkan, jika Anda membiarkan angka pertama secara otomatis turun ke lantai lapangan bermain, skor akan meningkat dengan nilai yang lebih besar, karena ( ) juga akan menyimpan nilainya dari demo. Ini berlaku bahkan untuk kasus-kasus ketika demo tidak mengisi satu baris pun. Ini tidak diatur ulang sampai tombol "Turun" ditekan. Selain itu, jika Anda mengklik Mulai selama animasi membersihkan baris kombinasi Tetris dalam mode demo, dan kemudian menunggu demo untuk memulai lagi, tidak hanya poin untuk Tetris akan dihitung dalam demo, tetapi seluruh waktu akan tercampur. Akibatnya, demo akan kalah. Setelah membatasi akhir permainan, Anda dapat kembali ke screen saver dengan mengklik Mulai.9673: LDA #$3F
9675: STA $2006
9678: LDA #$0E
967A: STA $2006 ; prepare to modify background tile color;
967D: LDX #$00 ; color = DARK_GRAY;
967F: LDA $0056
9681: CMP #$04
9683: BNE $9698 ; if (completedLines == 4) {
9685: LDA $00B1
9687: AND #$03
9689: BNE $9698 ; if (frameCounter divisible by 4) {
968B: LDX #$30 ; color = WHITE;
968D: LDA $00B1
968F: AND #$07
9691: BNE $9698 ; if (frameCounter divisible by 8) {
9693: LDA #$09
9695: STA $06F1 ; play clear sound effect;
; }
; }
; }
9698: STX $2007 ; update background tile color;
holdDownPoints
$004F
holdDownPoints
Status permainan $06
melakukan pemeriksaan target untuk game Tipe-B. Dalam mode A-Type, ini pada dasarnya adalah bingkai yang tidak digunakan.Status permainan $07
berisi secara eksklusif 2 Player vs. logika tidak lengkap. Dalam mode pemain tunggal, ia berperilaku seperti bingkai yang tidak digunakan.Status permainan $08
dibahas di bagian โMenciptakan Tetriminoโ dan โMemilih Tetriminoโ.Status permainan $09
tidak digunakan. $0B
meningkatkan status permainan, tetapi juga terlihat tidak digunakan.Dan akhirnya, siklus utama permainan:; while(true) {
8138: JSR $8161 ; branchOnGameMode();
813B: CMP $00A7 ; if (vertical blanking interval wait requested) {
813D: BNE $8142 ; waitForVerticalBlankingInterval();
813F: JSR $AA2F ; }
8142: LDA $00C0
8144: CMP #$05
8146: BNE $815A ; if (gameMode == DEMO) {
8148: LDA $00D2
814A: CMP #$DF
814C: BNE $815A ; if (reached end of demo table) {
814E: LDA #$DD
8150: STA $00D2 ; reset demo table index;
8152: LDA #$00
8154: STA $00B2 ; clear upper byte of frame counter;
8156: LDA #$01
8158: STA $00C0 ; gameMode = TITLE_SCREEN;
; }
; }
815A: JMP $8138 ; }