我已經完成了一個簡單的紙牌遊戲,我對程序的整體流程有了想法。我關心的主要問題是卡片觸發器。交易卡遊戲中卡牌效果的基本邏輯/流程
比方說,我們有一張名爲「Guy」的牌,其效果是「每當你玩另一張牌時,獲得2點生命」。我將如何去將它合併到我的代碼中?每次我打牌時,我是否必須檢查蓋伊是否在場上,如果他是,它會運行代碼獲得2生命?這似乎是因爲所有潛在的觸發器,當我在遊戲中獲得更多卡時,它會使我的代碼的主要部分變得非常快。我想找出最佳的方式來做到這一點。
我已經完成了一個簡單的紙牌遊戲,我對程序的整體流程有了想法。我關心的主要問題是卡片觸發器。交易卡遊戲中卡牌效果的基本邏輯/流程
比方說,我們有一張名爲「Guy」的牌,其效果是「每當你玩另一張牌時,獲得2點生命」。我將如何去將它合併到我的代碼中?每次我打牌時,我是否必須檢查蓋伊是否在場上,如果他是,它會運行代碼獲得2生命?這似乎是因爲所有潛在的觸發器,當我在遊戲中獲得更多卡時,它會使我的代碼的主要部分變得非常快。我想找出最佳的方式來做到這一點。
在這種情況下防止代碼混亂的一個好方法是定義您的遊戲中可能會發生哪些類型的「事件」,您的卡可能需要做出反應。對於每一個這樣的事件,在你的卡片的基類中定義一個方法(我假設你知道繼承和抽象方法和類;否則,你需要首先閱讀),並適當地實現該方法每個子類。
public abstract class Card {
protected Player owner; // Assume there's a constructor that sets this
public abstract void anotherCardWasPlayed(Card theOtherCard);
}
public class GuyCard extends Card {
public void anotherCardWasPlayed(Card theOtherCard) {
owner.gainLife(2);
}
}
每當遊戲中發生特定事件時,循環遍歷所有卡片並調用相應的方法。
如果大多數卡片類別不需要爲特定事件做任何事情,您可能希望使方法非abstract
,以便您不必在卡片類別中實現它對它感興趣。
以控制事件流程的一些額外代碼爲代價,這會將每張卡片的特殊規則的代碼保留在該卡片的類別中。這種方法實質上是在Java框架中非常普遍的Observer pattern。
如果您對交互卡對有特殊規則,請查看Double dispatch模式。
正如Aasmund Eldhuset所說,觀察者模式是一個很好的選擇。解決這個問題的另一種方法是讓卡片自行註冊通知。這種方法的一個很好的例子是Swing的Observer和Observable。
他們有點老,並不是真正的類型安全,但我們可以做得更好,沒有太多的努力。
import com.google.common.collect.ImmutableList;
import java.util.HashSet;
import java.util.Set;
/** Like Observable, but type-safe */
public class Event<T> {
/** Like Observer, but type-safe */
public interface EventWatcher<T>{
void onEvent(Event<T> event, T arg);
}
private final Set<EventWatcher<T>> Watchers = new HashSet<>(10);
/** Adds a watcher that will be receive events. */
public void addWatcher (EventWatcher<T> watcher) {
if (null != watcher) {
synchronized(Watchers) {
Watchers.add(watcher);
}
}
}
/** Removes a watcher, so that it no longer receives events. */
public void removeWatcher (EventWatcher<T> watcher) {
if (null != watcher) {
synchronized(Watchers) {
Watchers.remove(watcher);
}
}
}
/** Removes all watchers attached to this instance. */
public void clearWatchers() {
synchronized(Watchers) {
Watchers.clear();
}
}
/** Notifies all of the watchers for this object, passing them 'arg'. */
public void fire(T arg) {
if (null == arg) {
return;
}
// Freeze the list of watchers to be notified
ImmutableList<EventWatcher<T>> copy_of_watchers;
synchronized(Watchers) {
copy_of_watchers = ImmutableList.copyOf(Watchers);
}
// Release the monitor before heading off to execute arbitrary code.
for(EventWatcher<T> watcher : copy_of_watchers) {
watcher.onEvent(this, arg);
}
}
}
對於Java 1.8 lambda來說,使用它非常簡單,如果沒有它們,那麼使用它就不是繁重任務。在你的情況下,它會看起來像這樣:
public interface Card {
void drawnFrom(Deck source);
}
public class AwesomeCard implements Card {
public void drawnFrom(Deck source) {
source.CardDrawnEvent.addWatcher((Event<Card> event, Card arg) -> {
// Give the owner Life
});
}
}
public class Deck {
public Event<Card> CardDrawnEvent = new Event<>();
public Card draw() {
Card drawn_card = new AwesomeCard();
drawn_card.drawnFrom(this);
CardDrawnEvent.fire(drawn_card);
return drawn_card;
}
}
太棒了,這似乎正是我所期待的。所以我只想確保自己明白,無論何時我玩一張牌,我都會循環播放該場上的每張牌,併爲該場上的每張牌調用另一個CardWasPlayed函數。那麼如果卡片沒有另外一個卡片功能(如果我有一張卡片在玩卡片時沒有做某些事情),它是如何工作的。我只是定義了這個函數,但保留空白?這會減慢程序運行空白功能嗎? – 2015-02-12 00:59:49
這是正確的。假設所有卡片類都擴展了'Card',他們必須擁有'anotherCardPlayed()',因爲該方法在'Card'中聲明爲抽象,所以所有非抽象子類都必須實現它。或者,如果你在'Card'中使用非空抽象的方式使它不抽象,那麼不重寫它的子類將繼承空方法。調用一個空方法應該花費幾十或幾百納秒,所以我不會擔心,除非你有數百萬張卡:-) – 2015-02-12 01:05:18
這是一個例子,你通過使用多態性和正確覆蓋的方便性:你可以有一個'Card'實例列表,並且不知道每個卡片究竟是什麼類型,您可以簡單地在每個卡片上調用另一個CardPlayed(),並確信正確的操作將會發生。 – 2015-02-12 01:10:37