Put your CS skills to good use and craft burgers.

This past weekend I made a game for Ludum Dare 43. Tools used: Aseprite, quicksilver. Inspired by Zachtronics.

It is written in Rust and compiled to WebAssembly. Checkout the source code.

How the game is implemented

The core of the game is a pretty standard LL(1) parser whose grammar is defined dynamically in game by player.

type RuleID = usize ; struct Grammar < T : Debug + Clone + PartialEq + Hash + Eq > { start : String , rules : Vec < Rule < T >> , first_sets : Option < HashMap < String , HashSet < ( Token < T > , Rule < T > ) >>> , } struct Rule < T : Debug + Clone + PartialEq + Hash + Eq > { name : String , id : RuleID , production : Vec < Token < T >> , } enum Token < T : Debug + Clone + PartialEq + Hash + Eq > { Terminal ( T ), Epsilon , NonTerminal ( String ), }

Note the rule has an id field so the the parse result is traceable, i.e. which paths the parser took.

enum AbstractBurgerTree < T : Debug + Clone + PartialEq + Hash + Eq > { NonTerm (( RuleID , Vec < Box < AbstractBurgerTree < T >>> )), Term ( Token < T > ), /// errors: IncompleteParse , WrongToken , Cyclic , AdditionalTokens ( Box < AbstractBurgerTree < T >> ), }

The parser errors are also valid AST elements.

Next, there are two helper functions that operates on the AST:

fn to_burger() converts the parse tree, which may or may not include errors, back into a burger.

pub fn to_burger ( & self ) -> Burger { let mut bg = Burger :: new (); bg .toks = self .to_burger_aux (); bg } fn to_burger_aux ( & self ) -> Vec < Token < BurgerItem >> { use self :: AbstractBurgerTree :: * ; let mut ret = vec! []; match & self { Term ( Token :: Epsilon ) => { } Term ( t ) => { ret .push ( t .clone ()); } NonTerm (( _ , t )) => { for i in t .iter () { ret .extend ( i .to_burger_aux ()); } } AdditionalTokens ( i ) => { ret .extend ( i .to_burger_aux ()); } _ => (), // all the errors are ignored } ret }

fn to_delta_seq() converts an AST into an sequence of animations …

fn to_delta_seq ( & self ) -> Vec < AnimDelta > { use self :: AbstractBurgerTree :: * ; let mut ret = vec! []; match self { Term ( Token :: Epsilon ) => { ret .push ( AnimDelta :: Noop ); } Term ( Token :: Terminal ( _ t )) => { ret .push ( AnimDelta :: Incr ); ret .push ( AnimDelta :: StepAnim ); } Term ( Token :: NonTerminal ( _ )) => panic! ( "Impossible" ), NonTerm ( t ) => { ret .push ( AnimDelta :: Incr ); ret .push ( AnimDelta :: EnterPtr ( t . 0 )); for i in & t . 1 { ret .extend ( i .to_delta_seq ()); } ret .push ( AnimDelta :: ExitPtr ( t . 0 )); } IncompleteParse | WrongToken | Cyclic => { ret .push ( AnimDelta :: PauseIndefinitely ); } AdditionalTokens ( i ) => { ret .extend ( i .to_delta_seq ()); ret .push ( AnimDelta :: PauseIndefinitely ); } } ret }

which are then dispatched by the main game state.

Everything else(~1400 loc) is game UI which is pretty tedious to write. I’ve never written a game before so it took several refactors.