Actions

Can’t sleep at night cus of this action mess that looked promosing at the start… let me explain.
I have an action object for each ‘action’ in my game like selecting a unit, moving from a to b, Attacking,etc
Each action has a doAction and undoAction method.
The idea for this was to allow to go back 1 step in a undo list, this worked using the swing Undomanager.

An example of that
Before the action is executed I set the (first) clicked tile in session, actions are stored in a map by name:

session.setClick(1,tile)
Action selectAction = actions.get("Select")
selectAction.doAction()
if selectAction.canUndo()
  undoManager.add(selectAction)

selectAction.java


/**
 * Select a unit and make it the active unit in the game. This is the first action
 * for a unit.
 */
public class SelectAction extends AbstractAction {
  private MapRenderer mapRenderer;
  private InGameSession inGameSession;
  private Game game;

  public SelectAction(Game game, MapRenderer mapRenderer, InGameSession inGameSession) {
    super("Select");
    this.game = game;
    this.inGameSession = inGameSession;
    this.mapRenderer = mapRenderer;
  }

  public void doAction() {
    selectUnit((Unit) inGameSession.getClick(1).getUnit());
    inGameSession.setMode(InGameSession.MODE.UNIT_SELECT);
  }

  private void selectUnit(Unit selectedUnit) {
    game.setActiveUnit(selectedUnit);
    mapRenderer.setActiveUnit(selectedUnit);
    mapRenderer.removeZones();
    mapRenderer.showMoveZone();
    mapRenderer.showArrows(true);
  }

  public void undoAction() {
    deselectActiveUnit();
    inGameSession.setMode(InGameSession.MODE.DEFAULT);
  }

  private void deselectActiveUnit() {
    game.setActiveUnit(null);
    mapRenderer.setActiveUnit(null);
    mapRenderer.removeZones();
    mapRenderer.showArrows(false);
  }
}

Now I want to replay the actions that happened in the game.
But I always reuse the same Action, I don’t create new action objects… Action objects have a lot of constructor parameters and I want to execute actions @ places that don’t have access to these objects.
Also the actions get their input from a session object limiting it’s use. a MoveAction can only move from session tile 1 to session tile 2.
Following the Command pattern an Action can’t have setters, it should get all information from the constructor parameters that’s why I used the session object.

When I would save a replay it probably look like
Select 0 0
Move 0 0 11
Select 2 2

Every game has a replay system, how do they do it? & how can I use Action objects to replay all the actions.

Typically you don’t need to store every single “Action” if all you want is a replay feature. Pretty much you need to know only those things that will affect graphical playback. Lots of logic and whatnot can be completely cut out. Typically the way to do this is simply to have one object that you can call basically a State or something like that. Every second or so (or more often if you like), you create a new State and then append that to a list. A State holds all relevant information for that given moment, like what X/Y position everything has, what animation it is currently playing and what frame in that animation it has, what rotation it has, etc. Then all you need to do later when you’re playing back your game is to iterate over the States and simply draw what you see within it.

I didn’t say but the game doesn’t change unless through an action.

Just create new action instances. You could treat actions.get() as a factory method, so you don’t have to invoke the constructor. Either make your actions instance hold the needed context for your Actions constructors or store prototypes in actions and return the clone() result of the stored prototype in get().

And if you differ your Actions by subclassing them, you can have a typesafe lookup:


public class ActionsMap
{
  private final HashMap<Class, Action> prototypeMap = new HashMap<Class, Action>();

  public <ActionType extends Action> void registerPrototype(Class<ActionType> type, ActionType action)
  {
    prototypeMap.put(type, action);
  }

  public <ActionType extends Action> ActionType get(Class<ActionType> type)
  {
    Action prototype = prototypeMap.get(type);
    return (ActionType) prototype==null?null:prototype.clone();
  }
}

So you can do


  SelectAction action = actions.get(SelectAction.class);
  action.click(1);

without casting or weaving your logic into a generic doAction() method which needs to get it’s arguments backdoor via the session.

Edit:
Having said that, it would probably be even better to dump your actions down, so you only have simple constructors expecting only values you know at places you would execute them and hold the additional context you need for execution in the session. Then you can do something along the lines of this:


  public class ClickAction extends Action
  {
     private int clickCount;
     public ClickAction(int clickCount)
     {
          this.clickCount = clickCount;
     }
     public Action invoke(Session session)
     {
          session.get(<whatever you need here>);
          // do something with the clickCount.
          (...)

          // return this for convinience
          return this;
     }
  }


   // invoke a single action
   new ClickAction(2).invoke(currentSession);


   // invoke some actions and store them in the replay
   LinkedList<Action> replay = LinkedList<Action>();
   replay.add(new ClickAction(1).invoke(currentSession));
   replay.add(new SpeakAction("Hello you!").invoke(currentSession));
   replay.add(new FooAction(4711).invoke(currentSession));
   replay.add(new BarAction(42).invoke(currentSession));
   // ...
   // save it 
   save(replay);


   // load a replay
   List<Action> replay = load("savegamename");
   // invoke them all
   for(Action a : replay)
   {
       a.invoke(currentSession);
   }

I would use “context” instead of “session” though :wink:

You may want to consider only storing actions that affect the game at the model level, rather than at the view level which you currently seem to have it at. Say your game is an RTS, then you would not bother storing “select” actions, but would store model-changing actions like “move”. Unless, of course, you expect replays to be in a specific player’s viewpoint, and that you want it to follow the player’s view exactly.

And cylab’s second approach gets my vote. Just a simple stack of actions that you can move the game back and forward through seems sufficient. Oh yeah, don’t forget timestamps.

That’s awesome cylab, you come up with 2 solutions! I had to read them a couple of times to understand what you are saying but I got it now.
Actually storing all these game objects into 1 context object really simplified code on other places as well, think 1 parameter instead of 6…

and Jono you already came to the conclusion, I want to replay from the user perspective, and time is not an issue since it is an tbs, maybe i’ll save the turn.
Solved!