I wrote a small example program to show how to create a InSim button app using a Finite-State Machine. I got the inspiration when I was writing a simple game and saw how effective a FSM is for this kind of thing.
In this example each connection on the host is given their own ScreenManager, that manages what screen that player is currently being shown. Each screen handles displaying and deleting all its own buttons, and also all its own internal logic.
This example allows you to flip backwards and forwards between a welcome screen and an options screen.
Well anyway, it's pretty heavily commented.
import pyinsim
# Class to manage what screen is currently being shown to a
# player. Each connection on the host has their own copy of
# the ScreenManager.
class ScreenManager(object):
def __init__(self, insim, ncn):
self.insim = insim # Copy of the InSim connection.
self.ncn = ncn # Copy of the players connection packet.
self.current_screen = None # The currently loaded screen for this player.
# Transition this player to the next screen.
def transition(self, screen):
# First tell the current screen (if any) to unload its buttons.
if self.current_screen:
self.current_screen.unload_buttons()
# Set the screen the player is currently viewing.
self.current_screen = screen
# A few helpful variables to have in the screen.
self.current_screen.screen_manager = self
self.current_screen.insim = self.insim
self.current_screen.ncn = self.ncn
# Tell the new screen to send its buttons to LFS.
self.current_screen.load_buttons()
def button_click(self, btc):
# Pass on the button click event to the current screen.
self.current_screen.button_click(btc)
# TODO: Implement more packet handlers. Should define handlers
# for any packets the screens may need to handle, just like
# button_click event.
# Abstract class used to define a screen.
class Screen(object):
def __init__(self):
self.screen_manager = None
self.insim = None
self.ncn = None
# Called when the screen is being shown, used to send
# all buttons to be displayed on the screen.
def load_buttons(self):
pass
# Called when the screen is being hidden, used to delete
# any buttons that were sent.
def unload_buttons(self):
pass
# Called whenever a BTC packet is received. Can extend this class
# to handle any other packet events the derived screens might need.
def button_click(self, btc):
pass
# Example welcome screen, shown to players when they first connect
# to the host.
class WelcomeScreen(Screen):
def __init__(self):
Screen.__init__(self)
def load_buttons(self):
# Send buttons when screen first loaded.
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=1,
BStyle=pyinsim.ISB_DARK,
L=20, T=20, W=40, H=10,
Text='Welcome Screen')
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=2,
BStyle=pyinsim.ISB_LIGHT|pyinsim.ISB_CLICK,
L=20, T=32, W=40, H=10,
Text='Go to options screen')
def unload_buttons(self):
# When screen unloaded delete any buttons sent.
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=1)
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=2)
def button_click(self, btc):
# Handle button click event.
if btc.ClickID == 2:
# Tell the screen manager to transition to the OptionsScreen.
self.screen_manager.transition(OptionsScreen())
# Example options screen, same as WelcomeScreen really.
class OptionsScreen(Screen):
def __init__(self):
Screen.__init__(self)
def load_buttons(self):
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=1,
BStyle=pyinsim.ISB_DARK,
L=20, T=20, W=40, H=10,
Text='Options Screen')
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=2,
BStyle=pyinsim.ISB_LIGHT|pyinsim.ISB_CLICK,
L=20, T=32, W=40, H=10,
Text='Go to welcome')
def unload_buttons(self):
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=1)
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=2)
def button_click(self, btc):
if btc.ClickID == 2:
# Tell ScreenManager for this player to transition
# back to the WelcomeScreen.
self.screen_manager.transition(WelcomeScreen())
class InSimApp(object):
def __init__(self):
# Dict to store the ScreenManager for each player.
self.screen_managers = {}
# Create new InSim connection.
self.insim = pyinsim.insim('127.0.0.2', 29999, Admin='', IName='InSimFSM')
# Bind packet events.
self.insim.bind(pyinsim.ISP_ISM, self.insim_multi)
self.insim.bind(pyinsim.ISP_NCN, self.new_connection)
self.insim.bind(pyinsim.ISP_CNL, self.connection_leave)
self.insim.bind(pyinsim.ISP_BTC, self.button_click)
# Request multiplayer packet on connect.
self.insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_ISM)
def insim_multi(self, insim, ism):
# When connected to host request connections list.
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NCN)
def new_connection(self, insim, ncn):
# Create new ScreenManager for this connection and add it
# to the screen_managers dictionary.
screen_manager = ScreenManager(insim, ncn)
self.screen_managers[ncn.UCID] = screen_manager
# Tell ScreenManager to display the welcome screen for this player.
screen_manager.transition(WelcomeScreen())
def connection_leave(self, insim, cnl):
# When a connection leaves delete their screen manager.
del self.screen_managers[cnl.UCID]
def button_click(self, insim, btc):
# When packet received pass it on to the connections ScreenManager, which will
# then pass it on to the currently viewed screen.
self.screen_managers[btc.UCID].button_click(btc)
if __name__ == '__main__':
# Create new InSim app.
InSimApp()
# Start all InSim connections and poll for packet events.
pyinsim.run()
You can download the full program (without comments) below.