moved Watchers to Ability and moved Counters to CardState

This commit is contained in:
betasteward 2015-03-01 21:17:23 -05:00
parent 620a3b9a52
commit 632573fc3e
204 changed files with 1516 additions and 347 deletions

View file

@ -50,6 +50,7 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import mage.watchers.Watcher;
/**
* Practically everything in the game is started from an Ability. This
@ -360,6 +361,9 @@ public interface Ability extends Controllable, Serializable {
boolean canChooseTarget(Game game);
List<Watcher> getWatchers();
void addWatcher(Watcher watcher);
/**
* Returns true if this abilities source is in the zone for the ability
*

View file

@ -75,6 +75,7 @@ import mage.game.stack.StackAbility;
import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import mage.watchers.Watcher;
import org.apache.log4j.Logger;
/**
@ -84,6 +85,7 @@ import org.apache.log4j.Logger;
public abstract class AbilityImpl implements Ability {
private static final transient Logger logger = Logger.getLogger(AbilityImpl.class);
private static final List<Watcher> emptyWatchers = new ArrayList<>();
protected UUID id;
protected UUID originalId;
@ -107,6 +109,7 @@ public abstract class AbilityImpl implements Ability {
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected MageObject sourceObject;
protected List<Watcher> watchers = null;
public AbilityImpl(AbilityType abilityType, Zone zone) {
this.id = UUID.randomUUID();
@ -136,6 +139,12 @@ public abstract class AbilityImpl implements Ability {
for (AlternativeCost cost: ability.alternativeCosts) {
this.alternativeCosts.add((AlternativeCost)cost.copy());
}
if (ability.watchers != null) {
this.watchers = new ArrayList<>();
for (Watcher watcher: ability.watchers) {
watchers.add(watcher.copy());
}
}
this.modes = ability.modes.copy();
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
@ -548,6 +557,11 @@ public abstract class AbilityImpl implements Ability {
@Override
public void setControllerId(UUID controllerId) {
this.controllerId = controllerId;
if (watchers != null) {
for (Watcher watcher: watchers) {
watcher.setControllerId(controllerId);
}
}
}
@ -565,6 +579,11 @@ public abstract class AbilityImpl implements Ability {
this.sourceId = sourceId;
}
}
if (watchers != null) {
for (Watcher watcher: watchers) {
watcher.setSourceId(sourceId);
}
}
}
@Override
@ -624,6 +643,23 @@ public abstract class AbilityImpl implements Ability {
return zone;
}
@Override
public List<Watcher> getWatchers() {
if (watchers != null)
return watchers;
else
return emptyWatchers;
}
@Override
public void addWatcher(Watcher watcher) {
if (watchers == null)
watchers = new ArrayList<>();
watcher.setSourceId(this.sourceId);
watcher.setControllerId(this.controllerId);
watchers.add(watcher);
}
@Override
public boolean isUsesStack() {
return usesStack;

View file

@ -140,12 +140,17 @@ public class TriggeredAbilities extends ConcurrentHashMap<String, TriggeredAbili
* @param attachedTo - the object that gained the ability
*/
public void add(TriggeredAbility ability, UUID sourceId, MageObject attachedTo) {
this.add(ability, attachedTo);
List<UUID> uuidList = new LinkedList<>();
uuidList.add(sourceId);
// if the object that gained the ability moves zone, also then the triggered ability must be removed
uuidList.add(attachedTo.getId());
sources.put(getKey(ability, attachedTo), uuidList);
if (sourceId == null) {
add(ability, attachedTo);
}
else {
this.add(ability, attachedTo);
List<UUID> uuidList = new LinkedList<>();
uuidList.add(sourceId);
// if the object that gained the ability moves zone, also then the triggered ability must be removed
uuidList.add(attachedTo.getId());
sources.put(getKey(ability, attachedTo), uuidList);
}
}
public void add(TriggeredAbility ability, MageObject attachedTo) {

View file

@ -74,7 +74,7 @@ public class SourceHasCounterCondition implements Condition {
if (from != -1) { //range compare
int count;
if (card != null) {
count = card.getCounters().getCount(counterType);
count = card.getCounters(game).getCount(counterType);
} else {
count = permanent.getCounters().getCount(counterType);
}
@ -84,7 +84,7 @@ public class SourceHasCounterCondition implements Condition {
return count >= from && count <= to;
} else { // single compare (lte)
if (card != null) {
return card.getCounters().getCount(counterType) >= amount;
return card.getCounters(game).getCount(counterType) >= amount;
} else {
return permanent.getCounters().getCount(counterType) >= amount;
}

View file

@ -71,7 +71,7 @@ public class SuspendedCondition implements Condition {
}
if (found) {
if (game.getState().getZone(card.getId()) == Zone.EXILED &&
card.getCounters().getCount(CounterType.TIME) > 0) {
card.getCounters(game).getCount(CounterType.TIME) > 0) {
return true;
}
}

View file

@ -32,7 +32,6 @@ import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.game.Game;
import mage.watchers.common.PlayerGainedLifeWatcher;
@ -53,8 +52,7 @@ public class ControllerGotLifeCount implements DynamicValue, MageSingleton {
return fINSTANCE;
}
public static ControllerGotLifeCount getInstance(Card card) {
card.addWatcher(new PlayerGainedLifeWatcher());
public static ControllerGotLifeCount getInstance() {
return fINSTANCE;
}

View file

@ -65,11 +65,11 @@ public class RemoveCounterSourceEffect extends OneShotEffect {
return true;
}
Card c = game.getCard(source.getSourceId());
if (c != null && c.getCounters().getCount(counter.getName()) >= counter.getCount()) {
if (c != null && c.getCounters(game).getCount(counter.getName()) >= counter.getCount()) {
c.removeCounters(counter.getName(), counter.getCount(), game);
game.informPlayers(new StringBuilder("Removed ").append(counter.getCount()).append(" ").append(counter.getName())
.append(" counter from ").append(c.getName())
.append(" (").append(c.getCounters().getCount(counter.getName())).append(" left)").toString());
.append(" (").append(c.getCounters(game).getCount(counter.getName())).append(" left)").toString());
return true;
}
return false;

View file

@ -66,11 +66,11 @@ public class RemoveCounterTargetEffect extends OneShotEffect {
return true;
}
Card c = game.getCard(targetPointer.getFirst(game, source));
if (c != null && c.getCounters().getCount(counter.getName()) >= counter.getCount()) {
if (c != null && c.getCounters(game).getCount(counter.getName()) >= counter.getCount()) {
c.removeCounters(counter.getName(), counter.getCount(), game);
game.informPlayers(new StringBuilder("Removed ").append(counter.getCount()).append(" ").append(counter.getName())
.append(" counter from ").append(c.getName())
.append(" (").append(c.getCounters().getCount(counter.getName())).append(" left)").toString());
.append(" (").append(c.getCounters(game).getCount(counter.getName())).append(" left)").toString());
return true;
}
return false;

View file

@ -52,7 +52,6 @@ public class DealtDamageToCreatureBySourceDies extends ReplacementEffectImpl {
public DealtDamageToCreatureBySourceDies(Card card, Duration duration) {
super(Duration.WhileOnBattlefield, Outcome.Exile);
card.addWatcher(new DamagedByWatcher());
if (card.getCardType().contains(CardType.CREATURE)) {
staticText = "If a creature dealt damage by {this} this turn would die, exile it instead";
} else {

View file

@ -98,7 +98,7 @@ public class MiracleAbility extends TriggeredAbilityImpl {
@SuppressWarnings("unchecked")
public MiracleAbility(Card card, ManaCosts miracleCosts) {
super(Zone.HAND, new MiracleEffect((ManaCosts<ManaCost>)miracleCosts), true);
card.addWatcher(new MiracleWatcher());
addWatcher(new MiracleWatcher());
ruleText = "Miracle " + miracleCosts.getText() + staticRule;
}

View file

@ -72,7 +72,7 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
name = PROWL_KEYWORD;
setReminderText(card);
this.addProwlCost(manaString);
card.addWatcher(new ProwlWatcher());
addWatcher(new ProwlWatcher());
}

View file

@ -290,7 +290,7 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl {
if (event.getTargetId().equals(getSourceId())) {
Card card = game.getCard(getSourceId());
if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED
&& card.getCounters().getCount(CounterType.TIME) == 0) {
&& card.getCounters(game).getCount(CounterType.TIME) == 0) {
return true;
}
}

View file

@ -49,11 +49,9 @@ public interface Card extends MageObject {
Rarity getRarity();
void setOwnerId(UUID ownerId);
void addAbility(Ability ability);
void addWatcher(Watcher watcher);
void setSpellAbility(SpellAbility ability);
SpellAbility getSpellAbility();
List<String> getRules();
List<Watcher> getWatchers();
String getExpansionSetCode();
String getTokenSetCode();
void setFaceDown(boolean value);
@ -119,7 +117,7 @@ public interface Card extends MageObject {
* @return true if there exists various art images for this card
*/
boolean getUsesVariousArt();
Counters getCounters();
Counters getCounters(Game game);
void addCounters(String name, int amount, Game game);
void addCounters(String name, int amount, Game game, ArrayList<UUID> appliedEffects);

View file

@ -76,7 +76,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected UUID ownerId;
protected int cardNumber;
protected List<Watcher> watchers = new ArrayList<>();
protected String expansionSetCode;
protected String tokenSetCode;
protected Rarity rarity;
@ -90,7 +89,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected int zoneChangeCounter = 1;
protected Map<String, String> info;
protected boolean usesVariousArt = false;
protected Counters counters;
protected boolean splitCard;
protected boolean morphCard;
@ -119,7 +117,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
abilities.add(ability);
}
this.usesVariousArt = Character.isDigit(this.getClass().getName().charAt(this.getClass().getName().length()-1));
this.counters = new Counters();
this.morphCard = false;
}
@ -134,14 +131,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected CardImpl(UUID ownerId, String name) {
this.ownerId = ownerId;
this.name = name;
this.counters = new Counters();
}
protected CardImpl(UUID id, UUID ownerId, String name) {
super(id);
this.ownerId = ownerId;
this.name = name;
this.counters = new Counters();
}
public CardImpl(final CardImpl card) {
@ -150,10 +145,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
cardNumber = card.cardNumber;
expansionSetCode = card.expansionSetCode;
rarity = card.rarity;
this.watchers.clear();
for (Watcher watcher: (List<Watcher>)card.getWatchers()) {
watchers.add(watcher.copy());
}
faceDown = card.faceDown;
canTransform = card.canTransform;
@ -170,7 +161,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
flipCardName = card.flipCardName;
splitCard = card.splitCard;
usesVariousArt = card.usesVariousArt;
counters = card.counters.copy();
morphCard = card.isMorphCard();
}
@ -241,14 +231,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
ability.setSourceId(this.getId());
abilities.add(ability);
}
@Override
public void addWatcher(Watcher watcher) {
watcher.setSourceId(this.getId());
watcher.setControllerId(this.ownerId);
watchers.add(watcher);
}
protected void addAbility(Ability ability, Watcher watcher) {
addAbility(ability);
ability.addWatcher(watcher);
}
@Override
public SpellAbility getSpellAbility() {
if (spellAbility == null) {
@ -268,11 +256,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
abilities.setControllerId(ownerId);
}
@Override
public List<Watcher> getWatchers() {
return watchers;
}
@Override
public String getExpansionSetCode() {
return expansionSetCode;
@ -664,8 +647,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
}
@Override
public Counters getCounters() {
return counters;
public Counters getCounters(Game game) {
return game.getState().getCardState(this.objectId).getCounters();
}
@Override
@ -682,7 +665,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, name, 1);
event.setAppliedEffects(appliedEffects);
if (!game.replaceEvent(event)) {
counters.addCounter(name, 1);
game.getState().getCardState(this.objectId).getCounters().addCounter(name, 1);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, name, 1));
}
}
@ -706,7 +689,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, counter.getName(), 1);
event.setAppliedEffects(appliedEffects);
if (!game.replaceEvent(event)) {
counters.addCounter(eventCounter);
game.getState().getCardState(this.objectId).getCounters().addCounter(eventCounter);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, counter.getName(), 1));
}
}
@ -716,7 +699,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public void removeCounters(String name, int amount, Game game) {
for (int i = 0; i < amount; i++) {
counters.removeCounter(name, 1);
game.getState().getCardState(this.objectId).getCounters().removeCounter(name, 1);
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, ownerId);
event.setData(name);
game.fireEvent(event);

View file

@ -42,7 +42,6 @@ import static mage.constants.SpellAbilityType.SPLIT_LEFT;
import static mage.constants.SpellAbilityType.SPLIT_RIGHT;
import mage.constants.Zone;
import mage.game.Game;
import mage.watchers.Watcher;
/**
*
@ -166,14 +165,6 @@ public abstract class SplitCard extends CardImpl {
}
@Override
public List<Watcher> getWatchers() {
List<Watcher> allWatchers = new ArrayList<>();
allWatchers.addAll(super.getWatchers());
allWatchers.addAll(leftHalfCard.getWatchers());
allWatchers.addAll(rightHalfCard.getWatchers());
return allWatchers;
}
}
/*

View file

@ -46,7 +46,7 @@ public class CounterPredicate implements Predicate<Card> {
@Override
public boolean apply(Card input, Game game) {
return input.getCounters().containsKey(counter);
return input.getCounters(game).containsKey(counter);
}
@Override

View file

@ -0,0 +1,64 @@
package mage.game;
import java.util.HashMap;
import java.util.Map;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.counters.Counters;
/**
*
* @author BetaSteward
*/
public class CardState {
protected Map<String, String> info;
protected Counters counters;
private static final Map<String, String> emptyInfo = new HashMap<>();
public CardState() {
counters = new Counters();
}
public CardState(final CardState state) {
if (state.info != null) {
info = new HashMap<>();
info.putAll(state.info);
}
counters = state.counters.copy();
}
public CardState copy() {
return new CardState(this);
}
public Counters getCounters() {
return counters;
}
public void addInfo(String key, String value) {
if (info == null) {
info = new HashMap<>();
}
if (value == null || value.isEmpty()) {
info.remove(key);
} else {
info.put(key, value);
}
}
public Map<String, String> getInfo() {
if (info == null) {
return emptyInfo;
}
return info;
}
public void clear() {
counters.clear();
info = null;
}
}

View file

@ -105,6 +105,7 @@ public class GameState implements Serializable, Copyable<GameState> {
private Map<String, Object> values = new HashMap<>();
private Map<UUID, Zone> zones = new HashMap<>();
private List<GameEvent> simultaneousEvents = new ArrayList<>();
private Map<UUID, CardState> cardState = new HashMap<>();
public GameState() {
players = new Players();
@ -160,6 +161,9 @@ public class GameState implements Serializable, Copyable<GameState> {
}
this.paused = state.paused;
this.simultaneousEvents.addAll(state.simultaneousEvents);
for (Map.Entry<UUID, CardState> entry: state.cardState.entrySet()) {
cardState.put(entry.getKey(), entry.getValue().copy());
}
}
@Override
@ -420,7 +424,10 @@ public class GameState implements Serializable, Copyable<GameState> {
}
public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) {
effects.addEffect(effect, sourceId, source);
if (sourceId == null)
effects.addEffect(effect, source);
else
effects.addEffect(effect, sourceId, source);
}
// public void addMessage(String message) {
@ -491,6 +498,7 @@ public class GameState implements Serializable, Copyable<GameState> {
origPlayer.restore(copyPlayer);
}
this.simultaneousEvents = state.simultaneousEvents;
this.cardState = state.cardState;
}
public void addSimultaneousEvent(GameEvent event, Game game) {
@ -527,11 +535,6 @@ public class GameState implements Serializable, Copyable<GameState> {
public void addCard(Card card) {
setZone(card.getId(), Zone.OUTSIDE);
for (Watcher watcher: card.getWatchers()) {
watcher.setControllerId(card.getOwnerId());
watcher.setSourceId(card.getId());
watchers.add(watcher);
}
for (Ability ability: card.getAbilities()) {
addAbility(ability, card);
}
@ -553,6 +556,10 @@ public class GameState implements Serializable, Copyable<GameState> {
* @param ability
* @param attachedTo
*/
public void addAbility(Ability ability, Card attachedTo) {
addAbility(ability, null, attachedTo);
}
public void addAbility(Ability ability, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
for (Mode mode: ability.getModes().values()) {
@ -574,7 +581,7 @@ public class GameState implements Serializable, Copyable<GameState> {
* @param sourceId
* @param attachedTo
*/
public void addAbility(Ability ability, UUID sourceId, MageObject attachedTo) {
public void addAbility(Ability ability, UUID sourceId, Card attachedTo) {
if (ability instanceof StaticAbility) {
for (Mode mode: ability.getModes().values()) {
for (Effect effect: mode.getEffects()) {
@ -588,6 +595,11 @@ public class GameState implements Serializable, Copyable<GameState> {
// TODO: add sources for triggers - the same way as in addEffect: sources
this.triggers.add((TriggeredAbility)ability, sourceId, attachedTo);
}
for (Watcher watcher: ability.getWatchers()) {
watcher.setControllerId(attachedTo.getOwnerId());
watcher.setSourceId(attachedTo.getId());
watchers.add(watcher);
}
}
public void addCommandObject(CommandObject commandObject) {
@ -732,6 +744,7 @@ public class GameState implements Serializable, Copyable<GameState> {
gameOver = false;
specialActions.clear();
otherAbilities.clear();
cardState.clear();
combat.clear();
turnMods.clear();
watchers.clear();
@ -767,4 +780,16 @@ public class GameState implements Serializable, Copyable<GameState> {
public TriggeredAbilities getTriggers() {
return triggers;
}
public CardState getCardState(UUID cardId) {
if (!cardState.containsKey(cardId)) {
cardState.putIfAbsent(cardId, new CardState());
}
return cardState.get(cardId);
}
public void addWatcher(Watcher watcher) {
this.watchers.add(watcher);
}
}

View file

@ -36,6 +36,7 @@ import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.counters.Counters;
import mage.game.Controllable;
import mage.game.Game;
@ -79,6 +80,8 @@ public interface Permanent extends Card, Controllable {
void setFlipCardName(String flipCardName);
void setSecondCardFace(Card card);
Counters getCounters();
List<UUID> getAttachments();
UUID getAttachedTo();
void attachTo(UUID permanentId, Game game);

View file

@ -90,8 +90,6 @@ public class PermanentCard extends PermanentImpl {
this.abilities.clear();
this.abilities.addAll(card.getAbilities().copy());
this.abilities.setControllerId(this.controllerId);
this.watchers.clear();
this.watchers.addAll(card.getWatchers());
this.cardType.clear();
this.cardType.addAll(card.getCardType());
this.color = card.getColor().copy();

View file

@ -61,6 +61,7 @@ import mage.constants.Rarity;
import mage.constants.Zone;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.game.Game;
import mage.game.events.DamageCreatureEvent;
import mage.game.events.DamagePlaneswalkerEvent;
@ -104,6 +105,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected List<UUID> dealtDamageByThisTurn;
protected UUID attachedTo;
protected UUID pairedCard;
protected Counters counters;
protected List<Counter> markedDamage;
protected int timesLoyaltyUsed = 0;
@ -113,12 +115,14 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
super(ownerId, name);
this.originalControllerId = controllerId;
this.controllerId = controllerId;
this.counters = new Counters();
}
public PermanentImpl(UUID id, UUID ownerId, UUID controllerId, String name) {
super(id, ownerId, name);
this.originalControllerId = controllerId;
this.controllerId = controllerId;
this.counters = new Counters();
}
public PermanentImpl(final PermanentImpl permanent) {
@ -148,6 +152,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
}
}
this.counters = permanent.counters.copy();
this.attachedTo = permanent.attachedTo;
this.minBlockedBy = permanent.minBlockedBy;
this.maxBlockedBy = permanent.maxBlockedBy;
@ -251,6 +256,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
getAbilities().clear();
}
@Override
public Counters getCounters() {
return counters;
}
@Override
public void addCounters(String name, int amount, Game game) {
addCounters(name, amount, game, null);
}
@Override
public void addCounters(String name, int amount, Game game, ArrayList<UUID> appliedEffects) {
GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, controllerId, name, amount);
@ -295,6 +310,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
game.fireEvent(event);
}
}
@Override
public void removeCounters(Counter counter, Game game) {
removeCounters(counter.getName(), counter.getCount(), game);
}
@Override
public int getTurnsOnBattlefield() {
return turnsOnBattlefield;

View file

@ -667,9 +667,6 @@ public class Spell implements StackObject, Card {
@Override
public void addAbility(Ability ability) {}
@Override
public void addWatcher(Watcher watcher) {}
@Override
public SpellAbility getSpellAbility() {
return ability;
@ -691,11 +688,6 @@ public class Spell implements StackObject, Card {
return card.getRules();
}
@Override
public List<Watcher> getWatchers() {
return card.getWatchers();
}
@Override
public String getExpansionSetCode() {
return card.getExpansionSetCode();
@ -925,8 +917,8 @@ public class Spell implements StackObject, Card {
public void build() {}
@Override
public Counters getCounters() {
return card.getCounters();
public Counters getCounters(Game game) {
return card.getCounters(game);
}
@Override

View file

@ -57,6 +57,7 @@ import java.util.UUID;
import mage.cards.Card;
import mage.constants.AbilityWord;
import mage.players.Player;
import mage.watchers.Watcher;
/**
*
@ -475,6 +476,16 @@ public class StackAbility implements StackObject, Ability {
public void setWorksFaceDown(boolean worksFaceDown) {
this.ability.setWorksFaceDown(worksFaceDown);
}
@Override
public List<Watcher> getWatchers() {
return this.ability.getWatchers();
}
@Override
public void addWatcher(Watcher watcher) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public MageObject getSourceObject(Game game) {