static void Main(string[] args) { try { string on = "On", off = "Off"; var space = ' '; var onOffSwitch = new StateMachine<string, char>(off); onOffSwitch.Configure(off).Permit(space, on); onOffSwitch.Configure(on).Permit(space, off); Console.WriteLine("Press <space> to toggle the switch. Any other key will raise an error."); while (true) { Console.WriteLine("Switch is in state: " + onOffSwitch.State); var pressed = Console.ReadKey(true).KeyChar; onOffSwitch.Fire(pressed); } } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); Console.WriteLine("Press any key to continue..."); Console.ReadKey(true); } }
static void Main() { var sm = new StateMachine<State, Trigger>(State.A); sm.Configure(State.A) .Permit(Trigger.GoToB, State.B) .OnEntry(OnEnterA); // This never gets called. sm.Configure(State.B) .OnEntry(OnEnterB) .Permit(Trigger.GoToC, State.C); var goToCTrigger = sm.SetTriggerParameters<string>(Trigger.GoToC); sm.Configure(State.C) .OnEntryFrom(goToCTrigger, OnEnterC); Console.WriteLine(sm); sm.Fire(Trigger.GoToB); Console.WriteLine(sm); sm.Fire(goToCTrigger, "foo"); // Using Trigger.GoToC will throw an exception. Console.WriteLine(sm); }
public Story(EntityWorld world) { Acts = new List<IStoryAct>(new[] { new ActOne() }); State = new StateMachine<StoryUpdateFunction, string>(Acts[0].Master); State.OnTransitioned(OnTransition); for (int i = 0; i < Acts.Count; i++) { var act = Acts[i]; act.ConfigureStates(State); State.Configure(act.Master) .Permit(Triggers.NextAct, i < Acts.Count - 1 ? (StoryUpdateFunction)Acts[i + 1].Master : EndOfStory); } World = world; State.Fire(ActOne.Triggers.Start); }
static void Fire(StateMachine<State, Trigger> phoneCall, Trigger trigger) { Console.WriteLine("[Firing:] {0}", trigger); phoneCall.Fire(trigger); }
static void SetVolume(StateMachine<State, Trigger> phoneCall, int volume) { phoneCall.Fire(setVolumeTrigger, volume); }
/// <summary> /// Main client thread. /// </summary> private void ClientThread() { _gameState.Reset(); _connection = DotaGameConnection.CreateWith(_details); _handshake = new DotaHandshake(_details, _gameState, _connection); _signon = new DotaSignon(_gameState, _connection, _details); _game = new DotaGame(_gameState, _connection); _commandGenerator = new UserCmdGenerator(_gameState, _connection); foreach (var cont in Controllers) cont.Initialize(_details.SteamId, _gameState, _commandGenerator); long handshake_requested = 0; long handshake_giveup = new TimeSpan(0, 0, 0, 10).Ticks; // Map states in the StateMachine to handlers var metastates = new Dictionary<States, Metastates>() { { States.HANDSHAKE_REQUEST, Metastates.HANDSHAKE }, { States.HANDSHAKE_CONNECT, Metastates.HANDSHAKE }, { States.CONNECTED, Metastates.SIGNON }, { States.LOADING, Metastates.SIGNON }, { States.PRESPAWN, Metastates.SIGNON }, { States.SPAWN, Metastates.SIGNON }, { States.PLAY, Metastates.GAME }, }; var processors = new Dictionary<Metastates, IHandler>() { { Metastates.HANDSHAKE, _handshake }, { Metastates.SIGNON, _signon }, { Metastates.GAME, _game } }; _stateMachine = new StateMachine<States, Events>(States.DISCONNECTED); //temporary shit _stateMachine.OnTransitioned(transition => { if (transition.Source == transition.Destination) return; Callback?.Invoke(this, new CallbackEventArgs(new DotaGameClient.SessionStateTransition(transition.Source, transition.Destination))); }); _stateMachine.OnUnhandledTrigger((states, events) => { Console.WriteLine("Unhandled trigger: " + events.ToString("G")); }); var disconnected = new Action(() => { if (_connection == null) return; Running = true; Stop(); }); _stateMachine.Configure(States.DISCONNECTED) .Ignore(Events.TICK) .Ignore(Events.DISCONNECTED) .OnEntry(disconnected) .Permit(Events.REQUEST_CONNECT, States.HANDSHAKE_REQUEST); _stateMachine.Configure(States.HANDSHAKE_REJECTED) .Permit(Events.DISCONNECTED, States.DISCONNECTED) .OnEntry(() => { Callback?.Invoke(this, new CallbackEventArgs(new DotaGameClient.HandshakeRejected(_handshake.rejected_reason))); _stateMachine.Fire(Events.DISCONNECTED); }); _stateMachine.Configure(States.HANDSHAKE_REQUEST) .OnEntry(() => handshake_requested = DateTime.Now.Ticks) .OnEntry(_handshake.RequestHandshake) .Ignore(Events.TICK) .Permit(Events.HANDSHAKE_CHALLENGE, States.HANDSHAKE_CONNECT) .Permit(Events.REJECTED, States.HANDSHAKE_REJECTED) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.HANDSHAKE_CONNECT) .OnEntry(_handshake.RespondHandshake) .Ignore(Events.TICK) .PermitReentry(Events.HANDSHAKE_CHALLENGE) // possibly re-enter? .Permit(Events.HANDSHAKE_COMPLETE, States.CONNECTED) .Permit(Events.REJECTED, States.HANDSHAKE_REJECTED) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.CONNECTED) .OnEntry(_signon.EnterConnected) .Ignore(Events.TICK) .Permit(Events.LOADING_START, States.LOADING) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.LOADING) .OnEntry(_signon.EnterNew) .Ignore(Events.TICK) .Permit(Events.CONNECTED, States.CONNECTED) .Permit(Events.PRESPAWN_START, States.PRESPAWN) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.PRESPAWN) .OnEntry(_signon.EnterPrespawn) .Ignore(Events.TICK) .Permit(Events.SPAWNED, States.SPAWN) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.SPAWN) .OnEntry(_signon.EnterSpawn) .Ignore(Events.TICK) .Permit(Events.BASELINE, States.PLAY) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Configure(States.PLAY) .OnEntryFrom(Events.BASELINE, () => { _game.EnterGame(); _commandGenerator.Reset(); }) .OnEntryFrom(Events.TICK, () => { _gameState.Update(); foreach(var cont in Controllers) cont.Tick(); _commandGenerator.Tick(); _gameState.Created.Clear(); _gameState.Deleted.Clear(); }) .PermitReentry(Events.TICK) .Permit(Events.DISCONNECTED, States.DISCONNECTED); _stateMachine.Fire(Events.REQUEST_CONNECT); long next_tick = DateTime.Now.Ticks; while (Running && _stateMachine.State != States.DISCONNECTED && _stateMachine.State != States.HANDSHAKE_REJECTED) { try { if (next_tick > DateTime.Now.Ticks) { Thread.Sleep(1); continue; } if (_stateMachine == null) break; if (_stateMachine.State == States.HANDSHAKE_REQUEST && (DateTime.Now.Ticks - handshake_requested) > handshake_giveup) { _stateMachine.Fire(Events.DISCONNECTED); continue; } if (_connection.state == DotaGameConnection.State.Closed) { _stateMachine.Fire(Events.DISCONNECTED); continue; } if (_connection == null) break; List<byte[]> outBand = _connection.GetOutOfBand(); List<DotaGameConnection.Message> inBand = _connection.GetInBand(); foreach (byte[] message in outBand) { Nullable<Events> e = processors[metastates[_stateMachine.State]].Handle(message); if (e.HasValue) { _stateMachine.Fire(e.Value); } } foreach (DotaGameConnection.Message message in inBand) { Nullable<Events> e = processors[metastates[_stateMachine.State]].Handle(message); if (e.HasValue) { _stateMachine.Fire(e.Value); } } _stateMachine.Fire(Events.TICK); if (_gameState.TickInterval > 0) { next_tick += (uint)(_gameState.TickInterval * 1000 * 10000 /* ticks per ms */); } else { next_tick += 50 * 1000; } int remain = (int)(next_tick - DateTime.Now.Ticks) / 10000; if (remain > 0) { Thread.Sleep(1); } else if (remain < 0) { next_tick = DateTime.Now.Ticks; } } catch (Exception ex) { Callback?.Invoke(this, new CallbackEventArgs(new DotaGameClient.LogMessage("Ignored error in session loop, " + ex.Message))); } } if (Running) Stop(); }
private void ConfigureMachine() { m_GameMachine = new StateMachine<State, Trigger>(() => m_CurrentState, newState => m_CurrentState = newState); ProcessMessages(); #region WaitingForPlayers m_GameMachine.Configure(State.WaitingForPlayers) .Permit(Trigger.AllPlayersReady, State.AllPlayersReady) .OnEntryFrom(Trigger.PlayerNotReady, x => { GenericClientMessage waiting = new GenericClientMessage() { MessageEnum = ClientMessage.ClientMessageEnum.PlayerNotReady }; LookupPlayerById(m_GameCreatorClientId).Callback.SendMessage(waiting.ToXml()); }) .OnEntryFrom(Trigger.PlayerLeft, x => { SendUpdatedGameInfo(); }) .PermitReentry(Trigger.PlayerLeft) .Ignore(Trigger.PlayerNotReady) .Ignore(Trigger.CreatorPressedStart); #endregion #region AllPlayersReady m_GameMachine.Configure(State.AllPlayersReady) .OnEntry(x => { GenericClientMessage ready = new GenericClientMessage() { MessageEnum = ClientMessage.ClientMessageEnum.AllPlayersReady }; LookupPlayerById(m_GameCreatorClientId).Callback.SendMessage(ready.ToXml()); }) .Permit(Trigger.PlayerNotReady, State.WaitingForPlayers) .Permit(Trigger.PlayerLeft, State.WaitingForPlayers) .Permit(Trigger.CreatorPressedStart, State.GameStarted); #endregion #region GameStarted m_GameMachine.Configure(State.GameStarted) .OnEntry(x => { List<KeyValuePair<string, Army.ArmyTypeEnum>> PlayerInfos = new List<KeyValuePair<string, Army.ArmyTypeEnum>>(); foreach (Player player in Players) { PlayerInfos.Add(new KeyValuePair<string, Army.ArmyTypeEnum>(player.ClientId, player.ArmyType)); m_PlayerTurnOrder.Enqueue(player.ClientId); } GameState = new GameState(PlayerInfos); pathFinder = new PathFinder(GameState); ConsoleLogger.Push(String.Format("Game {0} has been started", Name)); NotifyAllPlayers(new GenericClientMessage() { MessageEnum = ClientMessage.ClientMessageEnum.TransitionToLoadingState }.ToXml()); // switch all the players to loading while we send the gamestate ClientGameStateMessage gamestate = new ClientGameStateMessage() { State = GameState }; GenericClientMessage start = new GenericClientMessage() { MessageEnum = ClientMessage.ClientMessageEnum.StartGame }; AggregateMessage aggregate = new AggregateMessage(); aggregate.MessageList.Add(gamestate); aggregate.MessageList.Add(start); NotifyAllPlayers(aggregate.ToXml()); m_GameMachine.Fire(Trigger.GameStarted); }) .Permit(Trigger.GameStarted, State.PlayerTurn) .Ignore(Trigger.PlayerNotReady); #endregion #region PlayerTurn m_GameMachine.Configure(State.PlayerTurn) .PermitReentry(Trigger.PlayerTurn) .Permit(Trigger.PlayerLeft, State.Reconfigure) .Permit(Trigger.StartBattlePhase, State.BattlePhase) .Permit(Trigger.Cheatcode, State.EndGame) .OnEntry(x => { // notify the current player that its their turn m_CurrentPlayerId = m_PlayerTurnOrder.Dequeue(); LookupPlayerById(m_CurrentPlayerId).Callback.SendMessage((new GenericClientMessage() { MessageEnum = ClientMessage.ClientMessageEnum.ActiveTurn }).ToXml()); // notify all other players that they have to wait foreach (string playerId in m_PlayerTurnOrder) { LookupPlayerById(playerId).Callback.SendMessage((new WaitingForTurnMessage() { ActivePlayerName = LookupPlayerById(m_CurrentPlayerId).Name }).ToXml()); } // add the current player back on the queue m_PlayerTurnOrder.Enqueue(m_CurrentPlayerId); }); #endregion m_GameMachine.Configure(State.BattlePhase) .OnEntry(x => { }); #region EndGame m_GameMachine.Configure(State.EndGame) .OnEntry(x => { var winner = LookupPlayerById(m_WinnerId); var gameOverMessage = new GameOverMessage() { WinnerId = m_WinnerId, WinnerName = winner.Name }; NotifyAllPlayers(gameOverMessage.ToXml()); }); #endregion #region Reconfigure m_GameMachine.Configure(State.Reconfigure) .OnEntry(x => { // this state allows us to reconfigure the players involved in the game in case someone leaves or is defeated Queue<string> tempPlayers = new Queue<string>(); foreach (string playerId in m_PlayerTurnOrder) { if (Players.FirstOrDefault(p => p.ClientId == playerId) != null) { tempPlayers.Enqueue(playerId); } } if (tempPlayers.Count > 0) { m_PlayerTurnOrder.Clear(); m_PlayerTurnOrder = tempPlayers; m_GameMachine.Fire(Trigger.PlayerTurn); } }) .Permit(Trigger.PlayerTurn, State.PlayerTurn); #endregion }