Symmetric Shadowcasting
I recently posted an implementation of symmetric shadowcasting which I simply translated from a great tutorial.
This was a single day project that I undertook because the tutorial is really nice, with interactive components and a full implementation at the bottom. I figured it shouldn't be too hard to translate, and it would be nice to have it in case I could use it in the Rust Roguelike that I've been working on with my brother.
Translation
The Rust translation was mostly straightforward. Besides some small syntactic things, using 'let' and 'let mut', etc, there are some signifcant differences just from Rust's type system.
Dependencies
The Rust version is almost the same, using num-rational as its only dependency as Rust does not have rationals as part of its standard library.
Types
The Python version does not have to specify types for positions, or anything else. Rust is much more explicit, and forces you to make some choices.
For the positions, I considered making them generic, using num-traits. I left it as a pair of isize instead, at least for now. This makes the code a little simpler, if less generic, and makes the user cast to a from this type. However, at least it does not require a dependency.
The closures have fairly simple types, although I did struggle with getting the types correct for a while and ended up not being able to express the API I was hoping for.
Local Functions
The largest change was that while in Python can capture within its local environment without issue, Rust is quite a bit more strict about this. Local functions are not closures- they cannot capture. I could have written the functions as closures, but decided to use separate functions instead.
This required adding arguments to some functions that otherwise were passed by capture. I decided to not pass all the closures given to the main function (compute_fov) to all the local functions, as the type signatures would have been as long as the implemenation.
Instead I inlined several functions, such as 'reveal'.
Closure Inputs
While the Python version can take two functions, knowing that they may capture the same data, Rust will not allow that, as it means having a mutable borrow along with another mutable or an immutable borrow.
The is_blocking function is likely to take a closure that captures a grid or map of some kind so it can check the given position against it.
The mark_visible function is the problem here- it would be nice if it could capture the same map structure, but to be able to modify it. I was not able to get the lifetimes to work out, so I left it the way it is.
The unit tests show how to do this- they create a map as a
vector of vectors Vec<Vec
I did run into something I didn't expect, which was that at first I had the compute_fov take closures as owning, instead of "&mut". This caused ownership issues where I was using the functions multiple times in a loop, but they are "used up" in a previous iteration of the loop. I was copying pathfinding's API, so I didn't expect to run into a problem.
Testing
I added tests cases for the examples given in the tutorial. This works quite well, and it gave me a great deal of confidence being able to run the tests frequently.
Conclusion
This was a fun project. Perhaps it might be useful, but if nothing else it is gratefying to post a crate on crates.io.