5 min read
·
By André Wolden
·
December 4, 2019
I'm a sucker for math (not necessarily good at it, I just like it) and really dislike null pointer exceptions. If you can relate, then functional programming is probably something you will like too. Now I've (almost) replicated minesweeper in Elm, and I thought I'd write a little bit about it.
Before I started working at Bekk I hadn't heard about functional programming. Mostly by coincidence I joined that group, and started practicing Elm. It was a struggle in the beginning. Actually for quite some time. After I had practiced for a while and maybe gotten past the noob level, I decided that I wanted to try and write the classic Minesweeper game in Elm. And I wanted to do so without getting any hints. Like following a tutorial of some sort. The idea was that if I did it that way, then through struggle I would have as steep a learning curve as I could get...hopefully.
I guess I believe that this is an efficient way to practice and learn new things, and thought I would try to persuade some of the readers to try something similar. Hopefully something that involves functional programming :)
Here's what it looks like at the moment:
There's still some stuff and features missing. And it's not exactly clean code. Far from it I'm afraid. But the point was to learn, fast, so I hope I'm excused ^^. The entire design is manually written in SVG. That part I'm pretty happy about.
I thought that I would pick one part of the code and dig into it (instead of trying to cover too much). This is the part that I found to be most challenging. If you have a black belt in recursion, then this will most likely be an easy task for you. I was more of a white belt, maybe yellow. So it gave me one of those great nerd-rushes when I got it working in the end.
The task:
When I click a square which contains nothing, then all its neighbours should automatically open. And if any of those neighbours also contains nothing, then all their neighbours should automatically open. And so on. I can't just open all empty squares. It has to be only a collection of neighbours.
My solution:
I started out with this sketch:
My model is a matrix, where I use coordinates i and j, and every square has an id.
stepping through the example:
...and so on
putting this into something which can be done recursively
open 3
open all neighbours (2, 4, 8, 9, 10)
save 3 to a list named something like list_of_completed_squares=[3]
save empty neighbours in a list named something like todo_list_of_empty_neighbours=[9], except those that are already in the list_of_completed_squares
case todo_list_of_empty_neighbours is empty ->
case todo_list_of_empty_neighbours is not empty ->
Here's how I wrote that in Elm:
goAgain : AlgDat -> AlgDat
goAgain ag =
let
current_matrix =
ag.current_matrix
current_square =
ag.current_square
remaining =
ag.remaining
done =
ag.done
newEmptyNeighbours =
getListOfEmptyNeighbours current_matrix current_square.i current_square.j
matrix_updated =
openAllNeighbours current_matrix current_square
done_updated =
current_square :: done
remaining_updated =
updateRemainingList remaining newEmptyNeighbours done
current_square_updated =
List.head remaining_updated
in
case current_square_updated of
Nothing ->
{ remaining = remaining_updated --: List Square
, current_matrix = matrix_updated --: Matrix
, current_square = current_square --: Square
, done = done_updated --: List Square
, debug_remaining = newEmptyNeighbours
}
Just nextSquare ->
goAgain
{ remaining = remaining_updated --: List Square
, current_matrix = matrix_updated --: Matrix
, current_square = nextSquare --: Square
, done = done_updated --: List Square
, debug_remaining = newEmptyNeighbours
}
If the list remaining_updated has any members left, then current_square_updated will be an actual Square (not Nothing) and "Just nextSquare" will be the case. Which leads to the stupidly named function goAgain calling itself :)
I take self criticism for bad naming and all messy things you can find. The name of the type 'AlgDat' for example is just a stupid name that I chose, since I didn't want to try and come up with something better. After I got it to work I should have cleaned it up. But.... I still havn't done that.
If could probably be more efficient. But I guess it could turn out to be a lot more inefficient as well, so I guess I'm happy with it at the moment.
At least it was a lot of fun, seriously :D
React+redux+typescript is great. That's what I mostly use at work. There's a ton of great modules that can so easily be used. It's so famous that it's hard to get the project to use anything else.
But then again, it is so hard not to create bugs! And many of them were/are my fault. I have to admit that. Often they are very hard to find. And it compiles anyway. Almost every time I track down bugs, usually something that caused something to be undefined or null, I dream of a compiler that tells me where it is.
Elm does that.
No forgotten side effects. No any
type. No accidental mutations. Just pure functions that make it so much easier to get it to do what I wan't it to do.
And I've just scratched the surface of functional programming. Applicatives, Functors, Monads. Don't know what they are yet. But I bet they are awesome.