I have some nodejs code that will act as an arbiter to a game played by bots.
Every bot will be it's own file, with a few predefined functions
with fixed names (every bot will call its functions the same names and arguments, like PlayRound()
).
Now I would like to, at runtime
, add the bots in the game. Like I would tell the arbiter botName1
, and it would look for a file called botName1.js inside the bots folder, then be able to call botName1.PlayRound()
later on.
Since require
only seems to work with literal static strings, and won't work with runtime values, is there even a way to do this?
sample code:
const readline = require('readline');
const readLine = readline.createInterface({ input: process.stdin });
var players = []
var playerFiles = [];
readLine.on('line', (ln) => {
var ws = ln.split(' ');
if (ws[0].toLowerCase() === 'add') {
players[players.length] = ws[1];
// I would like to add the file corresponding to the name here
} else if (ws[0].toLowerCase() === 'list'){
console.log('There are currently ' + players.length + ' players registered:');
for (var p in players) {
console.log(players[p]);
}
} else if (ws[0].toLowerCase() === 'start'){
// I would like to do this here
for (var playerFile in playerFiles) {
playerFiles[playerFile].PlayRound();
}
}
});
As @Kaito suggested, its possible to use dynamic requires. But I would never prefer that, well definitely not if you are unaware of what might go wrong. Dynamic requires leaves your application open to runtime errors that you might not have handled like requiring a file that is not present (the most common one).
I would like to build upon what @Kaito and @Yash have suggested/provided.
Solution
botname
to the botfilepath
botfile
/botfunction
related to the bot that is joiningbotfile
at runtime.In the example below I am assuming that all the bot files will be stored in bots
directory.
Example
const fs = require( "fs" ),
path = require( "path" ),
botsDir = path.join( __dirname, "bots" );
/** One time read to fetch all the bot files in bots dir */
const files = fs.readdirSync( botsDir ),
/** Here we create the map of bot and bot file path */
availableBots = files
.reduce( ( botMap, file ) => Object.assign( botMap, {
[ file.replace(".js", "") ] : path.join( botsDir, file )
} ), {} );
// Your code
const botThatWillBeJoiningAtRuntime = "BotC"; // This would be some calculation to determine bot that joins at runtime.
/** Here we safely require the bot file only if we have one corresponding to the bot that is joining */
if ( availableBots[botThatWillBeJoiningAtRuntime] ) {
const botFunc = require( availableBots[botThatWillBeJoiningAtRuntime] );
}
Benefit of this approach -
You make file op once throughout the application lifetime and accumulate the botfiles thereby reducing costly file ios and then the if
part safely requires the botfiles depending upon whether the application have a bot file for the bot joining in.
Downside is -
You need to make sure that the bot that is joining has the same name as the botfile
in the bots
directory.