Anda dapat melihat sistem Anda seolah-olah terdiri dari serangkaian status dan fungsi, di mana fungsi f[j]
dengan input x[j]
mengubah status sistem s[j]
menjadi status s[j+1]
, seperti:
s[j+1] = f[j](s[j], x[j])
Keadaan adalah penjelasan seluruh dunia Anda. Lokasi pemain, lokasi musuh, skor, amunisi yang tersisa, dll. Semua yang Anda butuhkan untuk menggambar bingkai permainan Anda.
Fungsi adalah segala sesuatu yang dapat mempengaruhi dunia. Perubahan bingkai, penekanan tombol, paket jaringan.
Input adalah data fungsi yang diambil. Perubahan bingkai mungkin memerlukan waktu sejak frame terakhir berlalu, penekanan tombol mungkin menyertakan tombol yang sebenarnya ditekan, serta apakah tombol shift ditekan atau tidak.
Demi penjelasan ini, saya akan membuat asumsi berikut:
Asumsi 1:
Jumlah status untuk menjalankan game tertentu jauh lebih besar dari jumlah fungsi. Anda mungkin memiliki ratusan ribu status, tetapi hanya beberapa fungsi (perubahan frame, penekanan tombol, paket jaringan, dll). Tentu saja, jumlah input harus sama dengan jumlah status minus satu.
Asumsi 2:
Biaya spasial (memori, disk) menyimpan satu negara jauh lebih besar daripada menyimpan fungsi dan inputnya.
Asumsi 3:
Biaya temporal (waktu) untuk mempresentasikan suatu negara adalah serupa, atau hanya satu atau dua kali lipat yang lebih panjang daripada menghitung suatu fungsi daripada suatu negara.
Bergantung pada persyaratan sistem replay Anda, ada beberapa cara untuk mengimplementasikan sistem replay, sehingga kami dapat mulai dengan yang paling sederhana. Saya juga akan membuat contoh kecil menggunakan permainan catur, yang direkam pada selembar kertas.
Metode 1:
Toko s[0]...s[n]
. Ini sangat sederhana, sangat mudah. Karena asumsi 2, biaya spasial ini cukup tinggi.
Untuk catur, ini akan dicapai dengan menggambar seluruh papan untuk setiap gerakan.
Metode 2:
Jika Anda hanya perlu meneruskan replay, Anda cukup menyimpan s[0]
, lalu menyimpan f[0]...f[n-1]
(ingat, ini hanya nama id dari fungsi) dan x[0]...x[n-1]
(apa input untuk masing-masing fungsi ini). Untuk memutar ulang, Anda cukup memulai s[0]
, dan menghitung
s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])
dan seterusnya...
Saya ingin membuat anotasi kecil di sini. Beberapa komentator lain mengatakan bahwa permainan "harus deterministik". Siapa pun yang mengatakan bahwa perlu mengambil Ilmu Komputer 101 lagi, karena kecuali permainan Anda dimaksudkan untuk dijalankan pada komputer kuantum, SEMUA PROGRAM KOMPUTER ADALAH DETERMINISTIK¹. Itulah yang membuat komputer sangat mengagumkan.
Namun, karena program Anda kemungkinan besar tergantung pada program eksternal, mulai dari pustaka hingga implementasi CPU yang sebenarnya, memastikan bahwa fungsi Anda berperilaku sama di antara platform mungkin cukup sulit.
Jika Anda menggunakan angka pseudo-acak, Anda bisa menyimpan angka yang dihasilkan sebagai bagian dari input Anda x
, atau menyimpan status fungsi prng sebagai bagian dari negara Anda s
, dan implementasinya sebagai bagian dari fungsi f
.
Untuk catur, ini akan dicapai dengan menggambar papan awal (yang diketahui) dan kemudian menggambarkan setiap gerakan mengatakan bagian mana yang pergi ke mana. Begitulah cara mereka melakukannya.
Metode 3:
Sekarang, Anda kemungkinan besar ingin mencari replay Anda. Artinya, hitung s[n]
untuk sewenang-wenang n
. Dengan menggunakan metode 2, Anda perlu menghitung s[0]...s[n-1]
sebelum dapat menghitung s[n]
, yang, menurut asumsi 2, mungkin cukup lambat.
Untuk mengimplementasikan ini, metode 3 adalah generalisasi dari metode 1 dan 2: store f[0]...f[n-1]
dan x[0]...x[n-1]
sama seperti metode 2, tetapi juga store s[j]
, untuk semua j % Q == 0
untuk konstanta yang diberikan Q
. Dalam istilah yang lebih mudah, ini berarti Anda menyimpan bookmark di satu dari setiap Q
negara. Misalnya, untuk Q == 100
, Anda menyimpans[0], s[100], s[200]...
Untuk menghitung s[n]
sewenang-wenang n
, Anda pertama-tama memuat yang disimpan sebelumnya s[floor(n/Q)]
, dan kemudian menghitung semua fungsi dari floor(n/Q)
hingga n
. Paling banyak, Anda akan menghitung Q
fungsi. Nilai yang lebih kecil Q
lebih cepat untuk dihitung tetapi mengkonsumsi lebih banyak ruang, sementara nilai yang lebih besar Q
mengkonsumsi lebih sedikit ruang, tetapi membutuhkan waktu lebih lama untuk menghitung.
Metode 3 dengan Q==1
sama dengan metode 1, sedangkan metode 3 dengan Q==inf
sama dengan metode 2.
Untuk catur, ini akan dicapai dengan menggambar setiap gerakan, serta satu dari setiap 10 papan (untuk Q==10
).
Metode 4:
Jika Anda ingin membalikkan replay, Anda dapat membuat variasi kecil metode 3. Misalkan Q==100
, dan Anda ingin menghitung s[150]
melalui s[90]
secara terbalik. Dengan metode 3 yang tidak dimodifikasi, Anda perlu membuat 50 perhitungan untuk mendapatkan s[150]
dan kemudian 49 perhitungan lagi untuk mendapatkan s[149]
dan seterusnya. Tetapi karena Anda sudah menghitung s[149]
untuk mendapatkannya s[150]
, Anda bisa membuat cache dengan s[100]...s[150]
ketika Anda menghitung s[150]
untuk pertama kalinya, dan kemudian Anda sudah ada s[149]
dalam cache saat Anda perlu menampilkannya.
Anda hanya perlu membuat ulang cache setiap kali Anda perlu menghitung s[j]
, j==(k*Q)-1
untuk apa pun yang diberikan k
. Kali ini, peningkatan Q
akan menghasilkan ukuran yang lebih kecil (hanya untuk cache), tetapi kali lebih lama (hanya untuk membuat ulang cache). Nilai optimal untuk Q
dapat dihitung jika Anda mengetahui ukuran dan waktu yang diperlukan untuk menghitung status dan fungsi.
Untuk catur, ini akan dicapai dengan menggambar setiap gerakan, serta satu dari setiap 10 papan (untuk Q==10
), tetapi juga, itu perlu menggambar dalam selembar kertas terpisah, 10 papan terakhir yang telah Anda hitung.
Metode 5:
Jika status hanya menghabiskan terlalu banyak ruang, atau fungsi menghabiskan terlalu banyak waktu, Anda dapat membuat solusi yang benar-benar menerapkan (bukan palsu) membalikkan memutar ulang. Untuk melakukan ini, Anda harus membuat fungsi terbalik untuk masing-masing fungsi yang Anda miliki. Namun, ini mensyaratkan bahwa masing-masing fungsi Anda adalah injeksi. Jika ini bisa dilakukan, maka untuk f'
menunjukkan fungsi terbalik f
, menghitung s[j-1]
sesederhana
s[j-1] = f'[j-1](s[j], x[j-1])
Perhatikan bahwa di sini, fungsi dan input keduanya j-1
, tidak j
. Fungsi dan input yang sama ini akan menjadi yang akan Anda gunakan jika Anda menghitung
s[j] = f[j-1](s[j-1], x[j-1])
Membuat kebalikan dari fungsi-fungsi ini adalah bagian yang sulit. Namun, Anda biasanya tidak bisa, karena beberapa data keadaan biasanya hilang setelah setiap fungsi dalam permainan.
Metode ini, seperti apa adanya, dapat membalikkan perhitungan s[j-1]
, tetapi hanya jika Anda memilikinya s[j]
. Ini berarti Anda hanya dapat menonton tayangan mundur, mulai dari titik di mana Anda memutuskan untuk memutar ulang. Jika Anda ingin memutar ulang mundur dari titik arbitrer, Anda harus mencampur ini dengan metode 4.
Untuk catur, ini tidak dapat diterapkan, karena dengan papan yang diberikan dan gerakan sebelumnya, Anda bisa tahu bagian mana yang dipindahkan, tetapi bukan dari mana ia dipindahkan.
Metode 6:
Akhirnya, jika Anda tidak dapat menjamin semua fungsi Anda adalah suntikan, Anda dapat membuat trik kecil untuk melakukannya. Alih-alih setiap fungsi hanya mengembalikan yang baru, status, Anda juga dapat mengembalikannya ke data yang dibuang, seperti:
s[j+1], r[j] = f[j](s[j], x[j])
Di mana r[j]
data yang dibuang. Dan kemudian buat fungsi terbalik Anda sehingga mereka mengambil data yang dibuang, seperti:
s[j] = f'[j](s[j+1], x[j], r[j])
Selain f[j]
dan x[j]
, Anda juga harus menyimpan r[j]
untuk setiap fungsi. Sekali lagi, jika Anda ingin dapat mencari, Anda harus menyimpan bookmark, seperti dengan metode 4.
Untuk catur, ini akan sama dengan metode 2, tetapi tidak seperti metode 2, yang hanya mengatakan bagian mana yang pergi, Anda juga perlu menyimpan dari mana masing-masing bagian berasal.
Penerapan:
Karena ini berfungsi untuk semua jenis negara, dengan semua jenis fungsi, untuk game tertentu, Anda dapat membuat beberapa asumsi, yang akan membuatnya lebih mudah untuk diterapkan. Sebenarnya, jika Anda menerapkan metode 6 dengan seluruh kondisi permainan, tidak hanya Anda akan dapat memutar ulang data, tetapi juga kembali ke waktu dan melanjutkan bermain dari setiap saat. Itu akan sangat luar biasa.
Alih-alih menyimpan semua kondisi gim, Anda cukup menyimpan minimum yang diperlukan untuk menggambar kondisi tertentu, dan membuat serialisasi data ini setiap jumlah waktu yang tetap. Status Anda akan menjadi serialisasi ini, dan input Anda sekarang akan menjadi perbedaan antara dua serialisasi. Mereka kunci untuk ini bekerja adalah bahwa serialisasi harus sedikit berubah jika negara dunia berubah sedikit juga. Perbedaan ini sepenuhnya tidak dapat dibalik, sehingga penerapan metode 5 dengan bookmark sangat dimungkinkan.
Saya telah melihat ini diimplementasikan dalam beberapa permainan utama, sebagian besar untuk memutar ulang instan data terbaru ketika sebuah peristiwa (frag di fps, atau skor dalam permainan olahraga) terjadi.
Saya harap penjelasan ini tidak terlalu membosankan.
¹ Ini tidak berarti beberapa program bertindak seperti non-deterministik (seperti MS Windows ^^). Sekarang serius, jika Anda dapat membuat program non-deterministik pada komputer deterministik, Anda dapat yakin Anda akan secara bersamaan memenangkan medali Fields, penghargaan Turing dan mungkin bahkan Oscar dan Grammy untuk semua yang bernilai.