1

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();
        }
    }

});
1
  • 2
    require, does work with runtime values in nodejs. var m = "fs"; var fs = require(m); is perfectly valid in node. Commented Aug 28, 2018 at 12:58

2 Answers 2

2

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

  1. Accumulate all your bot files/functions in a map that maps botname to the botfilepath
  2. In your logic first check if you have a botfile/botfunction related to the bot that is joining
  3. If yes then you can safely require the botfile 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.

Sign up to request clarification or add additional context in comments.

2 Comments

yes,this would be better approach than i suggested.
This is awesome!! Super helpful since I am planning to build a plugin system for my command line automation development platform! Thank you for sharing!! :-D
2

you can get this by fs package of node

 fs.readdirSync(PATHOFDIRECTORYCONTAININGFILEs)
       .forEach((file) =>  {
        var name = file.replace('.js', '');
        if(name === botname) {
          require('./' + file).PlayRound();
        }
    });

try this

 const readline = require('readline');
 const fs = require('fs);
 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];
    //
} 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

   fs.readdirSync(__diname)
   .forEach((file) =>  {
       for (var playerFile in playerFiles) {
         var name = file.replace('.js', '');
         if(name === playerFile) {
         require('./' + file).PlayRound();
        }
     }
  });
});

1 Comment

I think this would be really taxing on the application. Each time a bot is added there is a file op. Of course this won't blow up the server but there might be some room for optimisation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.