Wednesday, January 30, 2013

Finite State Machine – Beverage Vending Machine


 Finite state machine is a mathematical computation model.

Considering a machine which has finite number of possible states, and there will be only one state in which the machine is said to be in.
Ex:- Vending machine could be in sleep mode or requesting coins from user etc…
The machine may change its state, which could depend upon the triggers.
Ex:- User inserting coins into the machine, might change the machine state from sleep to accepting the coins.
Each state would have next possible states which are associated.

State Machine Diagram :

Below is the sample code, which works using state machine algorithm.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StateMachine_BeverageVendingMachine
{
    class Program
    {
        static void Main(string[] args)
        {
            //creating vending machine instance and setting default product cost
            BeverageVendingMachine machine = new BeverageVendingMachine() { Cost = 10 };

            while (true)
            {
                try
                {
                    Console.WriteLine("Current State : " + machine.ToString());

                    //if machine state is sleep, then asking user to enter coins
                    if (machine.CurrentState == MachineState.Sleep)
                    {
                        Console.WriteLine("Please enter coins for amount : " + (machine.Cost - machine.Amount));
                        Int32 coin = GetValidOption((i) =>i>0 && i <= (machine.Cost - machine.Amount), "Please enter coins for amount : " + (machine.Cost - machine.Amount));
                        machine.AddCoins(coin);
                    }

                    //if machine state is coins status, then asking for the remaining amount or either giving a cancel option
                    if (machine.CurrentState == MachineState.Coin_Status)
                    {
                        Console.WriteLine("Please enter coins for amount : " + (machine.Cost - machine.Amount) + " or hit -1 to cancel");
                        Int32 coin = GetValidOption((i) => i > 0 && i <= (machine.Cost - machine.Amount) || i == -1, "Please enter coins for amount : " + (machine.Cost - machine.Amount) + " or hit -1 to cancel");
                        if (coin == -1)
                            machine.Cancel();
                        else
                            machine.AddCoins(coin);
                    }

                    //if machine state is active, then asking user to select beverage option
                    if (machine.CurrentState == MachineState.Active)
                    {
                        Console.WriteLine("Please select menu \n 1. coffee\n 2. Tea");
                        Int32 coin = GetValidOption((i) => i == 1 || i == 2, "Please select menu \n 1. coffee\n 2. Tea");
                        if (coin == 1)
                            machine.Product = "Coffee";
                        if (coin == 2)
                            machine.Product = "Tea";

                        //supplyin the selected product
                        machine.Supply();
                    }
                }
                catch (Exception exp)
                {
                    Console.WriteLine("Error : " + exp.Message);
                }
            }
        }


        public static Int32 GetValidOption(Func<int, bool> validate, string msg)
        {
            while (true)
            {
                Int32 option = 0;
                if (int.TryParse(Console.ReadLine(), out option))
                {
                    if (validate(option))
                        return option;
                    else
                        Console.WriteLine(msg);
                }
                else
                    Console.WriteLine("Please enter valid number");
            }
        }
    }

    public class BeverageVendingMachine
    {
        Dictionary<StateTransition, MachineState> machineTransitions = null;

        public MachineState CurrentState { get; set; }

        public int Amount { get; set; }

        public int Cost { get; set; }

        public string Product { get; set; }

        public BeverageVendingMachine()
        {
            //adding each possible statetransition for a given state to dictionary.
            machineTransitions = new Dictionary<StateTransition, MachineState>()
            {
                { new StateTransition(MachineState.Sleep,MachineCommand.Sleep2CoinCheck), MachineState.Coin_Check},
                { new StateTransition(MachineState.Coin_Status,MachineCommand.CoinStatus2Cancel), MachineState.Cancel},
                { new StateTransition(MachineState.Coin_Check,MachineCommand.CoinCheck2CointStatus), MachineState.Coin_Status},
                { new StateTransition(MachineState.Coin_Status,MachineCommand.CoinStatus2Active), MachineState.Active},
                { new StateTransition(MachineState.Active,MachineCommand.Active2Supply), MachineState.Supply},
                { new StateTransition(MachineState.Supply,MachineCommand.Supply2Sleep), MachineState.Sleep},
                { new StateTransition(MachineState.Cancel,MachineCommand.Cancel2Sleep), MachineState.Sleep}
            };
            this.CurrentState = MachineState.Sleep;
        }

        //when user inserts coins
        public void AddCoins(Int32 coin)
        {

            //In sleep state the amount is 0, hence changing the state to coin check state
            if (Amount == 0)
                GetNextState(MachineCommand.Sleep2CoinCheck);

            //adding the total amount inserted so far
            Amount += coin;

            //if amount collected is same as cost
            if (Amount == Cost)
            {
                //changing the coin check to coin status and then coin status to active
                if (CurrentState == MachineState.Coin_Check)
                    GetNextState(MachineCommand.CoinCheck2CointStatus);
                GetNextState(MachineCommand.CoinStatus2Active);
            }
            else
                //changing coin check to coin status, when user enters coins, but the amount is less than the actual cost
                if (CurrentState == MachineState.Coin_Check)
                    GetNextState(MachineCommand.CoinCheck2CointStatus);
        }

        //If user selects cancel operation, after entering few coins
        public void Cancel()
        {
            //changing the state to cancel and sleep
            GetNextState(MachineCommand.CoinStatus2Cancel);
            GetNextState(MachineCommand.Cancel2Sleep);

            //handovering the amout collected before cancel
            if (Amount > 0)
                Console.WriteLine("Please collect your amount : {0}", Amount);
            Amount = 0;
        }

        //after user selects product from menu
        public void Supply()
        {
            //supplying the product
            GetNextState(MachineCommand.Active2Supply);
            Console.WriteLine("Supplying {0} ...", Product);

            //going to sleep mode
            GetNextState(MachineCommand.Supply2Sleep);
            Console.WriteLine("Thanks for choosing this vending machine");
            Amount = 0;
        }

        //setting the next state based on current state and command issued
        public void GetNextState(MachineCommand command)
        {
            StateTransition trans = new StateTransition(CurrentState, command);
            MachineState nextState;
            if (machineTransitions.TryGetValue(trans, out nextState))
            {
                CurrentState = nextState;
            }
            else
                throw new Exception(string.Format("No valid state transition for state : {0}, command : {1}", CurrentState, command));
        }

        public override string ToString()
        {
            return CurrentState.ToString();
        }
    }


    //each state transition object is the reference of StateTransition class

    public class StateTransition
    {
        readonly MachineState CurrentState;
        readonly MachineCommand Command;
        public StateTransition(MachineState state, MachineCommand command)
        {
            this.CurrentState = state;
            this.Command = command;
        }
        public override bool Equals(object obj1)
        {
            StateTransition obj = obj1 as StateTransition;
            return (obj.Command == this.Command && obj.CurrentState == this.CurrentState);
        }
        public override int GetHashCode()
        {
            return (10 * CurrentState.GetHashCode()) + (10 * Command.GetHashCode());
        }
    }

    //Vending machine, finite possible states
    public enum MachineState
    {
        Sleep = 1,
        Coin_Check = 2,
        Cancel = 3,
        Coin_Status = 4,
        Active = 5,
        Supply = 6
    }

    //Vending machine, possible state transitions
    public enum MachineCommand
    {
        Sleep2CoinCheck = 1,
        CoinStatus2Cancel = 2,
        Cancel2Sleep = 3,
        CoinCheck2CointStatus = 4,
        CoinStatus2Active = 5,
        Active2Supply = 6,
        Supply2Sleep = 7
    }
}

Output :