我正在做一個文字冒險。以下代碼是顯示我的問題的玩具模型。
public class Player:
private Location location;
private Inventory inventory;
public take(Item item) throws ActionException {
location.remove(item); // throws ActionException if item isn't in location
inventory.add(item); // throws ActionException if item can't be picked up
}
}
我的問題是:如果物品可以從該位置移除,但無法添加到玩家的庫存中怎么辦?目前,該代碼將從該位置移除該專案,但無法將其添加到玩家的庫存中。
本質上,我希望兩者都發生,或者兩者都不發生。
知道我該怎么做嗎?任何幫助表示贊賞。
uj5u.com熱心網友回復:
您通常要尋找的是交易的概念。
這是不平凡的。通常的策略是使用本地支持它的資料庫。
如果你不想去那里,你可以從 DBs 那里獲得靈感。
他們使用版本控制方案。想想像 git 這樣的區塊鏈或版本控制系統:您實際上從未在任何地方添加或洗掉任何內容,而是進行克隆。這樣,對某些游戲狀態的參考永遠不會改變,這很好,因為請仔細考慮:
即使洗掉有效,添加也有效,如果涉及其他執行緒或這兩個操作之間有任何代碼,它們可以“見證”專案剛剛消失的情況。它已被洗掉,location但尚未添加到inventory。
想象一個檔案系統。假設您有一個包含 2 個檔案的目錄。您想從第一個檔案中洗掉第一行并將該行添加到第二個檔案中。如果您實際上只是這樣做(編輯兩個檔案),那么任何其他觀察目錄的程式都可以觀察到無效狀態(它要么在任一檔案中都沒有看到該行,要么看到它)同時)。
因此,您應該這樣做:創建一個新目錄,復制兩個檔案,調整兩個檔案,然后在一個原子操作中將新創建的目錄重命名為其舊名稱。任何程式,假設它們“抓住目錄的句柄”(這就是它在例如 linux 上的作業方式),不可能觀察到無效狀態。它們要么獲得舊狀態(行在檔案 1 中而不在檔案 2 中),要么獲得新狀態(行在檔案 2 中而不在檔案 1 中)。
您可以對代碼使用相同的方法,其中所有狀態都是不可變的,所有修改都通過構建器(可變變體)或一次一步完成,中間有不可變的樹,一旦完成,您所做的就是采取型別的單個欄位GameState并更新它以參考新狀態 - java 保證如果你寫: someObj = someExpr,其他執行緒要么看到舊狀態,要么看到新狀態,他們看不到一半的指標或其他類似的廢話。(您仍然需要volatile或synchronized或其他東西來確保所有執行緒及時獲得更新)。
如果執行緒無關緊要,還有另一種選擇:
游戲狀態動作。
而不是僅僅呼叫location.remove,您可以做的是使用游戲狀態操作。這樣的操作既知道如何完成作業(從該位置移除專案),也知道如何準確地撤消該作業。
然后,您可以撰寫一個小框架,在其中列出游戲狀態操作串列(此處:可以執行或撤消“從位置移除”的操作,以及可以執行或撤消“將其添加到庫存”的操作)。然后,您將操作串列交給框架。然后,該框架將遍歷每個操作,一次一個,捕獲例外。如果它捕獲到一個,它將以相反的順序運行并在每個游戲狀態操作上呼叫 undo。
這樣,你就有了一個可管理的策略,你可以按順序運行一堆操作,知道如果其中一個失敗,到目前為止所做的一切都將被正確地撤消。
在沒有全域狀態鎖定的情況下,這種策略在多執行緒環境中完全不可能正常作業,因此請確保您將來不需要它。
這一切都相當復雜。結果……大多數人只會使用資料庫引擎來做這些事情;他們有交易支持。作為獎勵,保存您的游戲現在是微不足道的(DB 一直在為您保存它)。
請注意,h2是一個免費的、開源的、全 Java(不需要服務器,只需要一個在程式運行時需要在那里的 jar)、基于檔案的(如,所有資料庫都是一個檔案)資料庫引擎支持事務和大量 SQL 語法。那將是一種方法。為了方便訪問,將它與 Java 核心 DB 訪問層(如JDBI)上的一個很好的抽象結合起來,你就有了一個系統:
- 可以輕松保存檔案。
- 讓您以快速的方式運行復雜的查詢,例如“找到所有有流血怪物的游戲房間”。
- 完全支持交易。
您只需運行以下命令:
START TRANSACTION;
DELETE FROM itemsAtLocation WHERE loc = 18 AND item = 356;
INSERT INTO inventory (itemId) VALUES (356);
COMMIT;
and either both happen or neither happens. As long as you can express rules in terms of DB constraints, the DB will check for you and refuse to commit if you violate a rule. For example, you can trivially state that any given itemId can be in inventory no more than once.
最后一個可能是最簡單但最不靈活的選擇是對其進行編碼:您所有的“游戲狀態修改代碼”都應該首先檢查它是否 100% 確定它可以以非破壞性方式執行序列中的每項任務. 只有當它知道這是可能的,然后所有的作業才算完成。如果其中一個在中途失敗,只是嚴重崩潰,那么您的游戲現在處于未知的不穩定狀態。拋出例外的重點現在被歸為錯誤檢測:現在例外只是意味著你搞砸了,你的檢查代碼沒有涵蓋所有的基礎。假設您的游戲沒有錯誤,則永遠不會發生例外。當然,這個也不能以多執行緒方式作業。真的,如果這是您想要的,或者處理 DB 的大部分功能,那么只有 DB 才是可靠的答案。
uj5u.com熱心網友回復:
理想情況下,您希望有一個inventory.canAddItem(item) 函式,無論它被呼叫什么,它都會回傳一個布林值,您可以在從該位置洗掉之前呼叫它。正如評論者指出的那樣,將例外用于控制流并不是一個好主意。
如果添加回該位置不是問題,則類似于:
public take(Item item) throws ActionException {
location.remove(item);
try{
inventory.add(item);
}
catch(ActionException e){
location.add(item);
}
}
可以為你作業。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/324104.html
