Did you know that it is the 50th anniversary of the BASIC programming language today? (1st May) No? Well why not! BASIC is the language that brought computers to the mainstream. Back in the day, if you had a computer, you learnt how to program it in basic. Infact, when you switched it on, that’s what you were presented with. These computers wanted to be programmed. Thousands of people purchased these expensive computers purely to learn how to program them.
I figured I should do something to mark the anniversary. Many of you will already know about my various crazy type providers, including MineSweeper and Choose Your Own Adventrue. So, I thought to myself, wouldn’t it be great if we could write some equivalents of the very early BASIC games via a re-usable and extensible type provider? One which doesn’t require you to write any type-providing code, but is abstracted away from all that …
Enter InteractiveProvider (which I just wrote this afternoon). Unfortunately, it doesn’t yet support any BASIC (although Phil Trelford’s Small Basic interpreter is looking like a good fit). What it does do is abstract away all the voodoo magic of infinitely-recursive-type-providers, and allow you to write some fairly sophisticated type provider games by implementing a couple of interfaces. The type provider itself will scan assemblies in a given location to find types that implement the interfaces it uses, and the rest happens JUST LIKE MAGIC! There’s not much content yet, I have converted some simple BASIC games from the legendary 101 BASIC games book, and some of the my other type providers to work with it as examples, although I have some special plans up my sleeve for new games !
To use it, grab and build the source from my github and do something like the following in your friendly FSI session
1 2 3 4 5 6 7
#r @"F:\git\InteractiveProvider\InteractiveProvider\bin\Debug\InteractiveProvider.dll" open PinkSquirrels.Interactive type GamesType = InteractiveProvider< @"F:\git\InteractiveProvider\BASIC_Anniversary\bin\Debug\"> let games = GamesType()
The static parameter here is the directory to search for assemblies containing compatible games. Now when you press dot on the gamesvalue, you will be presented with a series of properties named ‘Start <game>’, one for each type it found implementing said interfaces (more on that below)
There are currently a massive 3 games.
Ronseal. See this postfor more informations
Rock Paper Scissors
Try your hand against the computer in this classic game!
My personal favourite, this introduces another ground-breaking type provider mechanic, the ability to input any sized number via successive properties! In this game you are a chemist and you must correctly dilute the fictional KRYPTOCYANIC acid.
By this point you must be just crying out to write your own games. And I too am crying out for your pull requests. The way it works is as follows.
There are two interfaces. IInteractiveState and IInteractiveServer
1 2 3 4 5 6 7
type IInteractiveState= abstract member DisplayText : string abstract member DisplayOptions : (string * obj) list type IInteractiveServer = abstract member NewState : IInteractiveState abstract member ProcessResponse : IInteractiveState * obj -> IInteractiveState
The type provider will pick up on any types that implement the server interface. Upon doing so, it will call Activator.CreateInstance and create an instance of the server type. From this type it will get an initial state from the NewState property.
The state interface returns information to the type provider via DisplayOptionsabout what choices it should surface as properties, along with objects to pass back to the server when a property is selected. It also has a property DisplayText which is a string that will be placed on the property that leads to this state. In some games this is not desirable as it gives the player a preview of the next state, however there is a way around that (see Rock Paper Scissors)
The objects themselves that implement these interfaces can be literally anything you like at all. The only caveat is that you must store all the information in the state which you require. When a property is accessed, the old state gets passed to the ProcessResponsefunction along with the object representing the selection, which can be anything, as previously defined in the DisplayOptions . In MineSweeperthis is the int * int tuple of the grid square that was selected. In Chemistry it is a discriminated union type. Rock Paper Scissorsuses something different again.
Often it is a good choice to use a record type or discriminated union for your state. Rock Paper Scissorssimply holds everything it needs to know about the game in one record type which both interfaces use to define their next behaviour. Chemistryuses multiple discriminated union cases and lots of pattern matching to work out the behaviour.
Here’s a very simple example that has the player guess a number from 1 to 100. It uses a discriminated union to model the different states. There is no fail condition, you can just keep guessing until you win :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
type ExampleState = | Start of target: int | Guess of lastGuess : int * target : int | Success interface IInteractiveState with member this.DisplayText = // create the text that will appear on the property match this with | Start _ -> "I HAVE PICKED A NUMBER FROM 1 TO 100! SEE IF YOU CAN GUESS IT!" | Guess(last,targ) -> if last > targ then "WRONG!! MY NUMBER IS LESS THAN THAT! GUESS AGAIN FOOL!" else "WRONG!! MY NUMBER IS MORE THAN THAT! GUESS AGAIN FOOL!" | Success -> "YOU WIN!!" member this.DisplayOptions = match this with | Start _ | Guess(_,_) -> // in all cases except for a win, show 1 - 100 properties [for x in 1..100 -> (x.ToString(),box x)] | Success ->  // game over type ExampleGame() = interface IInteractiveServer with member this.NewState = // create the inital state Start (Utils.rnd.Next(1,101)) :> IInteractiveState member this.ProcessResponse(state,choice) = let newGuess = unbox<int> choice match state :?> ExampleState with | Start target | Guess(_,target) when target = newGuess -> Success :> IInteractiveState | Success -> failwith "this case is not possible" | Start target | Guess(_,target) -> Guess(newGuess,target) :> IInteractiveState
That's it! No ProvidedTypes or any other craziness in sight. Now if I fire up the type provider, I can play this smashing game as follows, it seems the computer picked exactly 50 on this first go!
Watch this space for more exciting, much more complex games in the future! In the meantime, please have a go and write your own games, and submit me pull requests!