So if you've been following recent posts, you know I've been messing with the logic for a simple sliding-tile game. In my last post I took some designs refined via a side trip into Dylan and brought them back into Objective-C, making them a little more idiomatic by pruning my tile classes that didn't hold their weight, and compensating for Objective-C's very limited method dispatch options.
But in addition to learning Objective-C, and Apple's APIs for writing an app, I'm also trying to further my knowledge of Haskell, which is somewhere just beyond "utter newbie." So I'm going to try to implement the game logic in Haskell, too. Since the game is decidedly stateful, there is a certain impedance mismatch here, at least with respect to the early chapters in most of the tutorials and guides. But I'm told that Haskell also makes a great imperative programming language, so let's give it a shot. And along the way I can try to mold my understanding of stateful versus imperative a little more.
For day one, which was a shorter-than-usual day, I did not get into the state monad or how to model mutation of a 2-D array yet. I wanted to consider whether I could model the tile classes the way I could in Dylan, and do something useful in them. It occurred to me that each move of the penguin, and all the subsequent actions including possibly pushing an object, possibly a collision, possibly an object sliding frictionlessly as long as it can and then another collision, actually takes place in a 1-dimensional vector, not a 2-dimensional array. So it might be interesting to handle a penguin move by extracting a vector (in the form of a list) from the array, and replacing it with an updated list.
I haven't worked that all out yet but here is the bare beginning of my experimentation. There's a way to represent tiles:
data Tile = Empty | Tree | Mountain | House | Ice_Block | Bomb | Heart | Edge deriving (Show)
Part of the confusion of learning Haskell is that, semantically, this isn't quite the equivalent of a set of enumerations, or of a set of class declarations. From what I can tell, this is more like a list of singleton factories -- constructors, where I've also derived them from Show, sort of the equivalent of mixing in a base class. But this is all an approximation, and Haskell is quite different than the other languages I'm most familiar with.
My next thought was that I wanted to be able to declare "base classes" so that, for example, I could have a Walkable class that comprised Empty and Tree. In Dylan I would do this by using classes, but there is different way: declaring a type-union of singletons. I think that this Haskell solution is more like the type-union. I looked in vain for an explicit type union. Instead I found class (which, in Haskell, does not correspond to a class in the sense that I'm used to, of a template for a run-time object that consists of data members and methods to operate on it, but a typeclass, something I clearly need to study more):
class Walkable a where walkable :: a -> Bool
And then this: which boils down to, I think, a function to determine whether a Tile is an instance of a Walkable typeclass:
instance Walkable Tile where walkable Empty = True walkable Tree = True walkable _ = False
Now I can write something like this (just a vague thought-in-progress at the moment):
slide :: [Tile] -> [Tile] slide  = error "slide empty list!" slide (t) = error "single item list!" slide (Empty:ts) = ts ++ slide ts collide :: [Tile] -> [Tile] collide  = error "traverse empty list!" collide [Edge] = [Edge] collide (Empty:ts) = ts collide (Bomb:Mountain:ts) = [Empty, Empty] ++ ts collide (Heart:House:ts) = [Empty, House] ++ ts step :: [Tile] -> Bool step  = error "step: empty list!" step (t:_) = if walkable t then True else False
Then after sticking in a dummy main I can load this into GHCI and interact with it a little:
*Main> :t Tree Tree :: Tile *Main> step [Mountain, Empty, Empty, Tree, Edge] False *Main> step [Tree, Empty, Empty, Tree, Edge] True *Main> collide [Heart, Mountain] *** Exception: arctic-slide.hs:(22,1)-(26,47): Non-exhaustive patterns in function collide (Um, yeah, OK, I have work to do there) *Main> collide [Heart, House] [Empty,House] *Main> slide [Empty, Empty, Empty, Empty, Mountain] *** Exception: single item list!
Anyway, that's not exactly what I want to do -- really, I want the functions to actually return a new list of the same length, so I'll have to build it up as I recurse down the list -- maybe in the List monad? But it's a start on the whole basic concept of matching on the "types" of my tiles in a "vector," such as it is. That whole bit with walkable -- which I admit I don't quite understand yet -- seems like far too much conditional logic when I really just want to pattern-match on a type union of Tile. In other words, I want to write something like this (not valid Haskell):
union Walkable = Empty | Tree step (Walkable:_) = True
That's a small example, but I have several other type union classes I need to use to categorize the tiles, so I have an incentive to make that as clear and simple as possible. It seems like I'm still fighting with Haskell's idioms here. Clearly, as they say, more research is needed...