Organizing source code
At some point, when you have enough code in your ship or stage control program, you'll probably want to spread your source code across multiple files. There are two ways to do this in Lua. Either one works in BerryBots, with some slight tweaks.
module / require
This is probably the cleaner way, and this is the style used by the sample ships and stages. The functions you want to reference go into their own source file with a module declaration at the top. For instance, in the sample stages, we have a library stages/battlestage.lua with the following declaration:
Then in a battle stage like stages/sample/battle1.lua, we require this module, and we can call its functions by prepending the module name.
require "battlestage" ... function run(stageSensors) battlestage.basicScoring(ships, world, admin, 437, 770, 16) end
Some points to keep in mind:
- The path to the module is relative to the stages directory, not to the calling file. (For ships, it's relative to the bots directory.)
- If the module is in a subdirectory, use periods (".") in place of slashes ("/" or "\") in your call to require. For instance, if voidious/sillybot.lua is using sample/sillylib.lua, the code would be: require "sample.sillylib"
- You may see in non-BerryBots Lua code that the module declaration is: module(..., package.seeall). The "..." is a shortcut to deduce the module name from the filename. If you do this, your code will work, but BerryBots will fail when trying to package it. (In fact, right now it even crashes, but that's a bug which I'll fix.) Just put the actual module name here.
loadfile / dofile
Another way of including source code from other files is with loadfile and dofile. You can read a bit more about them in Programming in Lua: Chapter 8. But basically, if you remove the module declaration from stages/battlestage.lua and update stages/sample/battle1.lua to use:
The code from battlestage.lua will be executed as if it were just copy/pasted into the calling file - it can modify variables in the global namespace and its functions will be accessible without prepending any module name. At a glance, this may seem preferable, and more intuitive if you're used to something like C/C++. It can get kind of messy, but you're certainly free to do things this way if you like.
Some points to keep in mind:
- The paths passed to loadfile and dofile are relative to the calling file.
- When BerryBots crawls ship or stage code during packaging, it will only find references made from top level code, outside of all functions. So if you are dynamically deciding within functions to call loadfile or dofile on a file that is not referenced at the top level, BerryBots will not find these references. Your packaged ship or stage will be missing source files and it won't run.
- I'm hopeful that most folks will agree that this is an ugly practice and it won't be an issue. If it's something people want, then I'll figure out a way to correctly crawl such references.
Security changes for BerryBots
BerryBots has some additional restrictions on how these functions work:
- You can use ".." and even absolute paths when calling loadfile and dofile, but any attempt to load a source file from outside of the BerryBots bots (for ships) or stages (for stages) directories is forbidden and will result in an error.
- For packaged ships and stages, the root is instead the subdirectory of cache where the packaged ship/stage was extracted, so it can only read whatever files it was packaged with.
- You cannot load Lua/LuaJIT bytecode, only source code.
- You cannot read from STDIN, which is the normal behavior of loadfile() (with no arguments).