Bạn có thể xem hệ thống của mình như thể nó bao gồm một loạt các trạng thái và hàm, trong đó một hàm f[j]
có đầu vào x[j]
thay đổi trạng thái hệ thống s[j]
thành trạng thái s[j+1]
, như vậy:
s[j+1] = f[j](s[j], x[j])
Một trạng thái là lời giải thích của toàn bộ thế giới của bạn. Vị trí của người chơi, vị trí của kẻ thù, điểm số, số đạn còn lại, v.v ... Tất cả mọi thứ bạn cần để vẽ một khung của trò chơi của bạn.
Một chức năng là bất cứ điều gì có thể ảnh hưởng đến thế giới. Thay đổi khung hình, nhấn phím, gói mạng.
Đầu vào là dữ liệu mà hàm lấy. Thay đổi khung có thể mất thời gian kể từ khi khung cuối cùng được thông qua, nhấn phím có thể bao gồm phím thực tế được nhấn, cũng như liệu phím shift có được nhấn hay không.
Vì lợi ích của lời giải thích này, tôi sẽ đưa ra các giả định sau:
Giả định 1:
Số lượng trạng thái cho một lần chạy nhất định của trò chơi lớn hơn nhiều so với số lượng chức năng. Bạn có thể có hàng trăm ngàn trạng thái, nhưng chỉ có vài chục chức năng (thay đổi khung, nhấn phím, gói mạng, v.v.). Tất nhiên, lượng đầu vào phải bằng lượng trạng thái trừ đi một trạng thái.
Giả định 2:
Chi phí không gian (bộ nhớ, đĩa) của việc lưu trữ một trạng thái duy nhất lớn hơn nhiều so với việc lưu trữ một chức năng và đầu vào của nó.
Giả định 3:
Chi phí tạm thời (thời gian) của việc trình bày một trạng thái là tương tự nhau, hoặc chỉ một hoặc hai bậc độ lớn dài hơn so với việc tính toán một hàm trên một trạng thái.
Tùy thuộc vào yêu cầu của hệ thống phát lại của bạn, có một số cách để thực hiện hệ thống phát lại, vì vậy chúng tôi có thể bắt đầu với cách đơn giản nhất. Tôi cũng sẽ làm một ví dụ nhỏ bằng cách sử dụng trò chơi cờ vua, được ghi trên các mảnh giấy.
Cách 1:
Cửa hàng s[0]...s[n]
. Điều này rất đơn giản, rất đơn giản. Do giả định 2, chi phí không gian của việc này khá cao.
Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ toàn bộ bảng cho mỗi lần di chuyển.
Cách 2:
Nếu bạn chỉ cần phát lại về phía trước, bạn có thể chỉ cần lưu trữ s[0]
và sau đó lưu trữ f[0]...f[n-1]
(hãy nhớ, đây chỉ là tên của id của hàm) và x[0]...x[n-1]
(đầu vào của mỗi hàm này là gì). Để phát lại, bạn chỉ cần bắt đầu với s[0]
và tính toán
s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])
và v.v.
Tôi muốn làm một chú thích nhỏ ở đây. Một số ý kiến khác nói rằng trò chơi "phải có tính quyết định". Bất cứ ai nói rằng cần phải học lại Khoa học máy tính 101, bởi vì trừ khi trò chơi của bạn được chạy trên máy tính lượng tử, TẤT CẢ CÁC CHƯƠNG TRÌNH MÁY TÍNH đều bị XÁC ĐỊNH. Đó là những gì làm cho máy tính rất tuyệt vời.
Tuy nhiên, vì chương trình của bạn rất có thể phụ thuộc vào các chương trình bên ngoài, từ thư viện đến việc triển khai thực tế của CPU, đảm bảo rằng các chức năng của bạn hoạt động giống nhau giữa các nền tảng có thể khá khó khăn.
Nếu bạn sử dụng các số giả ngẫu nhiên, bạn có thể lưu trữ các số được tạo như một phần của đầu vào của mình x
hoặc lưu trữ trạng thái của hàm prng như một phần của trạng thái của bạn s
và việc triển khai nó như là một phần của chức năng f
.
Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ bảng ban đầu (được biết đến) và sau đó mô tả từng nước cờ cho biết quân cờ đã đi đâu. Đây là cách họ thực sự làm điều đó.
Cách 3:
Bây giờ, rất có thể bạn muốn có thể tìm kiếm vào phát lại của bạn. Đó là, tính toán s[n]
cho một tùy ý n
. Bằng cách sử dụng phương pháp 2, bạn cần tính toán s[0]...s[n-1]
trước khi bạn có thể tính toán s[n]
, theo giả định 2, có thể khá chậm.
Để thực hiện điều này, phương thức 3 là tổng quát hóa các phương thức 1 và 2: lưu trữ f[0]...f[n-1]
và x[0]...x[n-1]
giống như phương pháp 2, nhưng cũng lưu trữ s[j]
, cho tất cả j % Q == 0
cho một hằng số đã cho Q
. Nói một cách dễ hiểu hơn, điều này có nghĩa là bạn lưu trữ một dấu trang ở một trong số các Q
tiểu bang. Ví dụ: cho Q == 100
, bạn lưu trữs[0], s[100], s[200]...
Để tính toán s[n]
một cách tùy ý n
, trước tiên bạn tải phần lưu trữ trước đó s[floor(n/Q)]
, sau đó tính toán tất cả các hàm từ floor(n/Q)
đến n
. Nhiều nhất, bạn sẽ được tính toán Q
chức năng. Các giá trị nhỏ Q
hơn nhanh hơn để tính toán nhưng tiêu tốn nhiều không gian hơn, trong khi các giá trị lớn hơn Q
tiêu thụ ít không gian hơn, nhưng mất nhiều thời gian hơn để tính toán.
Phương thức 3 với Q==1
giống như phương pháp 1, trong khi phương thức 3 với Q==inf
phương thức 2.
Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ mọi di chuyển, cũng như một trong mỗi 10 ván (cho Q==10
).
Cách 4:
Nếu bạn muốn thay đổi phát lại, bạn có thể tạo ra một sự thay đổi nhỏ của phương pháp 3. Giả sử Q==100
, và bạn muốn tính toán s[150]
thông qua s[90]
theo hướng ngược lại. Với phương pháp 3 chưa sửa đổi, bạn sẽ cần thực hiện 50 phép tính để có được s[150]
và sau đó thêm 49 phép tính nữa s[149]
, v.v. Nhưng vì bạn đã tính toán s[149]
để có được s[150]
, bạn có thể tạo bộ đệm s[100]...s[150]
khi bạn tính toán s[150]
lần đầu tiên và sau đó bạn đã có s[149]
trong bộ đệm khi bạn cần hiển thị nó.
Bạn chỉ cần tạo lại cache mỗi khi bạn cần phải tính toán s[j]
, đối với j==(k*Q)-1
đối với bất kỳ trao k
. Lần này, tăng Q
sẽ dẫn đến kích thước nhỏ hơn (chỉ cho bộ đệm), nhưng thời gian dài hơn (chỉ để tạo lại bộ đệm). Một giá trị tối ưu Q
có thể được tính nếu bạn biết kích thước và thời gian cần thiết để tính toán trạng thái và hàm.
Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ mỗi lần di chuyển, cũng như cứ 10 ván (thì Q==10
) sẽ yêu cầu vẽ một tờ giấy riêng, 10 ván cuối cùng bạn đã tính.
Phương pháp 5:
Nếu các trạng thái chỉ đơn giản tiêu tốn quá nhiều không gian hoặc các hàm tiêu tốn quá nhiều thời gian, bạn có thể tạo ra một giải pháp thực sự thực hiện (không phải giả) phát lại ngược lại. Để làm điều này, bạn phải tạo các hàm đảo ngược cho từng chức năng bạn có. Tuy nhiên, điều này đòi hỏi rằng mỗi chức năng của bạn là một mũi tiêm. Nếu điều này là có thể thực hiện được, thì để f'
biểu thị nghịch đảo của hàm f
, việc tính toán s[j-1]
đơn giản như
s[j-1] = f'[j-1](s[j], x[j-1])
Lưu ý rằng ở đây, chức năng và đầu vào là cả hai j-1
, không phải j
. Hàm và đầu vào tương tự này sẽ là những hàm bạn sẽ sử dụng nếu bạn đang tính toán
s[j] = f[j-1](s[j-1], x[j-1])
Tạo nghịch đảo của các chức năng này là phần khó khăn. Tuy nhiên, bạn thường không thể, vì một số dữ liệu trạng thái thường bị mất sau mỗi chức năng trong trò chơi.
Phương pháp này, như là, có thể đảo ngược tính toán s[j-1]
, nhưng chỉ khi bạn có s[j]
. Điều này có nghĩa là bạn chỉ có thể xem phát lại ngược, bắt đầu từ thời điểm mà bạn quyết định phát lại ngược. Nếu bạn muốn phát lại ngược từ một điểm tùy ý, bạn phải kết hợp điều này với phương pháp 4.
Đối với cờ vua, điều này không thể được thực hiện, vì với một ván nhất định và di chuyển trước đó, bạn có thể biết quân cờ nào đã được di chuyển, nhưng không phải nó di chuyển từ đâu.
Phương pháp 6:
Cuối cùng, nếu bạn không thể đảm bảo tất cả các chức năng của mình là tiêm, bạn có thể thực hiện một mẹo nhỏ để làm như vậy. Thay vì chỉ có mỗi hàm trả về một trạng thái mới, bạn cũng có thể yêu cầu nó trả về dữ liệu mà nó đã loại bỏ, như vậy:
s[j+1], r[j] = f[j](s[j], x[j])
Đâu r[j]
là dữ liệu bị loại bỏ. Và sau đó tạo các hàm nghịch đảo của bạn để chúng lấy dữ liệu bị loại bỏ, như vậy:
s[j] = f'[j](s[j+1], x[j], r[j])
Bên cạnh đó của f[j]
và x[j]
, bạn cũng phải lưu trữ r[j]
cho từng chức năng. Một lần nữa, nếu bạn muốn có thể tìm kiếm, bạn phải lưu trữ dấu trang, chẳng hạn như với phương pháp 4.
Đối với cờ vua, điều này sẽ giống như phương pháp 2, nhưng không giống như phương pháp 2, chỉ nói rằng quân cờ sẽ đi đâu, bạn cũng cần lưu trữ từng quân cờ xuất phát từ đâu.
Thực hiện:
Vì điều này hoạt động cho tất cả các loại trạng thái, với tất cả các loại chức năng, cho một trò chơi cụ thể, bạn có thể đưa ra một số giả định, điều đó sẽ giúp thực hiện dễ dàng hơn. Trên thực tế, nếu bạn thực hiện phương pháp 6 với toàn bộ trạng thái trò chơi, không chỉ bạn sẽ có thể phát lại dữ liệu mà còn quay ngược thời gian và tiếp tục chơi từ bất kỳ thời điểm nào. Điều đó sẽ khá tuyệt vời.
Thay vì lưu trữ tất cả trạng thái trò chơi, bạn chỉ cần lưu trữ mức tối thiểu mà bạn yêu cầu để vẽ một trạng thái nhất định và tuần tự hóa dữ liệu này sau mỗi khoảng thời gian cố định. Các trạng thái của bạn sẽ là các tuần tự hóa này và đầu vào của bạn bây giờ sẽ là sự khác biệt giữa hai tuần tự hóa. Chìa khóa để làm việc này là việc tuần tự hóa sẽ thay đổi rất ít nếu nhà nước thế giới cũng thay đổi rất ít. Sự khác biệt này là hoàn toàn không thể đảo ngược, vì vậy việc thực hiện phương pháp 5 bằng dấu trang là rất có thể.
Tôi đã thấy điều này được thực hiện trong một số trò chơi lớn, chủ yếu là phát lại dữ liệu gần đây ngay lập tức khi một sự kiện (một mảnh trong khung hình / giây hoặc điểm trong các trò chơi thể thao) xảy ra.
Tôi hy vọng lời giải thích này không quá nhàm chán.
Điều này không có nghĩa là một số chương trình hoạt động như chúng không mang tính quyết định (chẳng hạn như MS Windows ^^). Bây giờ, nghiêm túc, nếu bạn có thể tạo một chương trình không xác định trên máy tính xác định, bạn có thể chắc chắn rằng mình sẽ đồng thời giành được huy chương Trường, giải thưởng Turing và thậm chí có thể là giải Oscar và Grammy cho tất cả những gì đáng giá.