Package: clubsandwich
A roguelike framework for Python 3 usingBearLibTerminalSource codeAPI docsWhat is this?Imagine you want to write a roguelike, you don’t want to roll your own
framework from scratch, and you want to use Python.The most mature option for doing this islibtcod-cffi, which is a
kitchen sink of roguelike development utilities like terminal graphics,
pathfinding, random numbers, noise generators, config file parsers, and data
structures. But it’s pretty heavy, the docs aren’t great, and it’s an icky
C API. And while it covers a lot of things you need for a roguelike, it doesn’t
help you much with your UI or state management.If that sounds like a decent tradeoff, go trylibtcod-cffiand be happy!
But there’s another library,BearLibTerminal, that only
handles terminal graphics. It’s tiny and understandable. Clubsandwich
builds on BearLibTerminal (BLT…) to add a nice API, state management, easy
UI, and some other helpful stuff.FeaturesScroll down for examples.Event loopScene managerGeometry data structuresConvenience functionsUIKit-like interface frameworkScript runner to help you iterate quicklyDependenciesPython 3.4+And BearLibTerminal, of courseInstallationThis package is not yet on PyPI. It is under heavy development. But you can
expect reasonable API stability!pipinstall-e'git://github.com/irskep/clubsandwich.git@master#egg=clubsandwich'Feature examplesAn event loopclubsandwich uses theasynciolibrary to run an asynchronous update loop.
Theterminal_read(val)method is called whenever events are in the queue.terminal_update()is called every frame.fromclubsandwich.loopimportBearLibTerminalEventLoopclassBasicLoop(BearLibTerminalEventLoop):defterminal_read(self,val):print("User pressed a key:",val)defterminal_update(self):# this is called every frameself.draw()if__name__=='__main__':BasicLoop().run()A scene managerMost games have more than one screen! This simple “director” object manages
them for you in a really obvious way.frombearlibterminalimportterminalfromclubsandwich.directorimportDirectorLoop,SceneclassBasicLoop2(DirectorLoop):defget_initial_scene(self):returnMainMenuScene()classMainMenuScene(Scene):defterminal_update(self):print(0,0,"Press Enter to begin game, Esc to quit")defterminal_read(self,val):ifval==terminal.TK_ENTER:self.director.push_scene(GameScene())elifval==terminal.TK_ESCAPE:self.director.pop_scene()classGameScene(Scene):defterminal_update(self):print(0,0,"You are playing the game, it is so fun! Press Esc to stop.")defterminal_read(self,val):ifval==terminal.TK_ESCAPE:self.director.pop_scene()if__name__=='__main__':BasicLoop2().run()A geometry libraryBasic data structures forRect,Point, andSize, and a very thin
wrapper aroundbearlibterminal.terminalto support them.fromclubsandwich.blt.nice_terminalimportterminalfromclubsandwich.geomimportRect,Size,Pointterminal.open()terminal.bkcolor('#ff0000')terminal.clear_area(Rect(Point(2,2),Size(10,10)))terminal.refresh()terminal.read()terminal.close()Convenient terminal state accessA super simple wrapper aroundterminal.state()to conveniently read state
values.fromclubsandwich.blt.stateimportblt_statefromclubsandwich.blt.nice_terminalimportterminalfromclubsandwich.geomimportRect,Size,Pointterminal.open()terminal.bkcolor('#ff0000')terminal.clear_area(Rect(Point(2,2),Size(10,10)))terminal.refresh()# less verbose than terminal.state(terminal.TK_ESCAPE)!whilenotblt_state.escape:terminal.read()terminal.close()Transform stackEasily translate view coordinates between frames of reference.fromclubsandwich.blt.stateimportblt_statefromclubsandwich.blt.contextimportBearLibTerminalContextfromclubsandwich.geomimportRect,Size,Pointterminal=BearLibTerminalContext()terminal.open()terminal.bkcolor('#ff0000')withterminal.translate(Point(blt_state.width,blt_state.height)):terminal.clear_area(Rect(Point(2,2),Size(10,10)))terminal.refresh()# less verbose than terminal.state(terminal.TK_ESCAPE)!whilenotblt_state.escape:terminal.read()terminal.close()A UIKit-like user interface libraryThis is too sophisticated for a short demo, but here’s the code for the scene
in the first screenshot:fromclubsandwich.uiimport(LabelView,ButtonView,UIScene,)LOGO="""
  _______     __     ____             __       _     __
 / ___/ /_ __/ /    / __/__ ____  ___/ /    __(_)___/ /
/ /__/ / // / _ \  _\ \/ _ `/ _ \/ _  / |/|/ / / __/ _\\\___/_/\_,_/_.__/ /___/\_,_/_//_/\_,_/|__,__/_/\__/_//_/
"""classMainMenuScene(UIScene):def__init__(self,*args,**kwargs):views=[LabelView(LOGO[1:].rstrip(),layout_options=LayoutOptions.row_top(0.5)),LabelView("Try resizing the window!",layout_options=LayoutOptions.centered('intrinsic','intrinsic')),ButtonView(text="Play",callback=self.play,layout_options=LayoutOptions.row_bottom(4).with_updates(left=0.2,width=0.2,right=None)),ButtonView(text="Settings",callback=self.show_settings,layout_options=LayoutOptions.row_bottom(4).with_updates(left=0.4,width=0.2,right=None)),ButtonView(text="Quit",callback=lambda:self.director.pop_scene(),layout_options=LayoutOptions.row_bottom(4).with_updates(left=0.6,width=0.2,right=None)),]super().__init__(views,*args,**kwargs)defplay(self):self.director.push_scene(CharacterCreationScene())defshow_settings(self):self.director.push_scene(SettingsScene())Script runnerWhen you install clubsandwich, it adds a script calledbabysitto your
path. This script does one very simple thing: it runs a command, and when that
command exits, it runs it again. This repeats until you Ctrl+C thebabysitprocess.This is useful when writing games because you don’t want to have to keep
switching back to your terminal to run your script. Usingbabysit, you can
just quit your game whenever you make a change and it’ll start back up again.You can try it with the demo script like this:babysitpythondemo.py