Skip to content

Commit

Permalink
Merge pull request #2 from seanohue/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Sean authored Mar 30, 2018
2 parents d9aa3ea + 136c887 commit cdde256
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 58 deletions.
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,43 @@ Axolemma

## How to Use

Axolemma is usable as a CLI tool and can also be used as a library.

To use it as a CLI tool, navigate to the working directory where you would like Axolemma to generate the map files, and type `axolemma`.

Axolemma will ask a series of questions and, as a result, generate an area for you.

![screenshot](./screenshot.png)

Here is a recipe for using Axolemma programmatically:

```javascript
// Require-able like any other library.
const Axolemma = require('axolemma')

Axolemma.generate({ // Programmatically pass in options
const {graphic, rooms, yaml} = Axolemma.generate({ // Programmatically pass in options
type: 'Digger' // Uses ROT-js well-documented map generation algorithms.
writeToFile: true // Can write YAML definitions to file for static persistence
}).then(function(data) { // Returns an await-able Promise
const {graphic, rooms, yaml} = data
console.log(graphic) // Returns an old-school ASCII map of your area.
console.log(yaml) // Returns parsed YAML.
return rooms.map(
roomDef => new Room(roomDef) // Returns Ranvier-compatible room definitions.
)
})

// Returns an old-school ASCII map of your area.
console.log(graphic)

// Returns YAML string.
console.log(yaml)

// Returns Ranvier-compatible room definitions.
const newRooms = rooms.map(
roomDef => new Room(roomDef)
);

```

## Configuration

When using Axolemma programmatically (or eventually through the CLI), you can customize the default options using either a `.axolemmaconfig` file or by adding an "axolemma" field in your package.json.

Your `.axolemmaconfig` can be either a JavaScript module or a JSON file. Axolemma will crawl up the tree to find the file so it can be in the root of your Ranvier bundle, the root of your fork of Ranvier, or even in your home directory.
Your `.axolemmaconfig` can be either a JavaScript module or a JSON file. Axolemma will crawl up the directory tree to find the file so it can be in the root directory of your Ranvier bundle, the root of your fork of Ranvier, or even in your user home directory. It will use the 'nearest' config it finds, so you can have multiple configurations at different nesting levels.

Configuration precedence goes as follows:
* Options passed in programmatically
Expand All @@ -51,6 +66,8 @@ Axolemma accepts the following options:
* @property {string} [filepath] Path to write YAML to. Defaults to current working directory
* @property {string} [areaTitle] Title of area to generate. Defaults to 'Generated Area'
* @property {Object} [areaInfo] Info object for area manifest. Defaults to object with respawnInterval property set to 60.
* @property {string} [genericRoomTitle] A title to be used for all of the rooms in your generated area. Defaults to 'An Empty Room'.
* @property {string} [genericRoomDesc] A description to be used for all of the rooms in your generated area. Defaults to 'There is nothing particularly interesting about this place.'
* @property {string} [type] The 'type' of map creator to use. This must be the name of a ROT-js Map constructor. Defaults to 'Uniform'.
* @property {number} [roomDugPercentage] Percentage in decimal of map coordinates to be turned into rooms. Defaults to 0.25 (25%).
* @property {timeLimit} [number] Amount of ms to wait for the ROT-js map generator algorithms to complete before giving up. Defaults to 60,000 (one minute).
Expand Down
227 changes: 220 additions & 7 deletions bin/axolemma
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ const cli = require('commander')
const inquirer = require('inquirer')
const semver = require('semver')
const requiresNode = require('requires-node-version')
const pretty = require('pretty-print')
const {bold, green, blue, red} = require('chalk')

requiresNode('8.0.0')

const {version} = require('../package.json')

const ax = require('../')
const {isAlphanumeric, isNumber} = require('./validation')
const {isAlphanumeric, isPositiveNumber, isMapType, isPercentage} = require('./validation')
const {toInteger, toPercentage} = require('./filters')

cli.version(version)
cli.on('--help', () => {
console.log(blue(`Type ${bold(green('axolemma'))} to start creating.`))
})

const {Separator} = inquirer
const STATE = {}

const questions = [

Expand Down Expand Up @@ -46,10 +49,26 @@ const questions = [
when(answers) {
return Boolean(answers.customizeAreaInfo)
},
validate: isNumber,
filter: toInteger,
validate: isPositiveNumber,
default: 60
},

/** Room information, generic **/
{
type: 'input',
name: 'genericRoomTitle',
message: blue('What would you like to use as the title for your generated rooms?'),
default: 'An Empty Room'
},

{
type: 'input',
name: 'genericRoomDesc',
message: blue('What would you like to use as the `look` description for your generated rooms?'),
default: 'An Empty Room'
},

/** Area generation algo. input **/
{
type: 'input',
Expand All @@ -58,7 +77,8 @@ const questions = [
const {areaTitle = 'this area'} = answers
return blue(`What is the maximum width (x coordinate) of ${areaTitle}?`);
},
validate: isNumber,
filter: toInteger,
validate: isPositiveNumber,
default: 10
},
{
Expand All @@ -68,7 +88,8 @@ const questions = [
const {areaTitle = 'this area'} = answers
return blue(`What is the maximum height (y coordinate) of ${areaTitle}?`);
},
validate: isNumber,
filter: toInteger,
validate: isPositiveNumber,
default: 10
},
{
Expand All @@ -78,8 +99,9 @@ const questions = [
const {areaTitle = 'this area'} = answers
return blue(`What is the maximum depth (z coordinate) of ${areaTitle}?`);
},
validate: isNumber,
default: 10,
validate: isPositiveNumber,
filter: toInteger,
default: 1,
when: false // Not supported yet.
},
{
Expand All @@ -99,11 +121,202 @@ const questions = [
'Arena',
'Cellular'
]
},

/** Algorihm-specific settings **/

/** IceyMaze settings**/
{
when: isMapType('IceyMaze'),
type: 'input',
name: 'regularity',
message(answers) {
const {type} = answers
return blue(`[${type}] What will the regularity of this maze be? 0 = most random, higher numbers = less random.`)
},
validate(...args) {
const isNonNegativeInt = Math.floor(args[0]) >= -1
return isNonNegativeInt || 'Input a non-negative number, including zero.'
},
default: 0
},

/** Digger & Uniform settings**/
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'roomHeightMaximum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the maximum height (y value) for each generated "room"?`)
},
filter: toInteger,
validate(input, answers) {
const {height} = answers
return (isPositiveNumber(input) && input < height) || `Please enter a numeric value that is less than ${height}.`
}
},
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'roomHeightMinimum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the minimum height (y value) for each generated "room"?`)
},
validate(input, answers) {
const {roomHeightMaximum} = answers
return (isPositiveNumber(input) && input < roomHeightMaximum - 1) || `Please enter a numeric value that is less than ${roomHeightMaximum - 1}.`
},
filter: toInteger
},
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'roomHeightMaximum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the maximum width (x value) for each generated "room"?`)
},
filter: toInteger,
validate(input, answers) {
const {height} = answers
return (isPositiveNumber(input) && input < height) || `Please enter a numeric value that is less than ${height}.`
}
},
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'roomHeightMinimum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the minimum width (x value) for each generated "room"?`)
},
validate(input, answers) {
const {roomHeightMaximum, height} = answers
return (isPositiveNumber(input) && input < roomHeightMaximum - 1) || `Please enter a numeric value that is less than ${roomHeightMaximum - 1}.`
},
filter: toInteger
},
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'dugPercentage',
message(answers) {
const {type} = answers
return blue(`[${type}] What percentage of the map area should be turned into rooms?`)
},
default: 0.25,
validate: isPercentage,
filter: toPercentage
},
{
when: isMapType('Digger', 'Uniform'),
type: 'input',
name: 'timeLimit',
message(answers) {
const {type} = answers
return blue(`[${type}] How long do you want to wait before causing the algorithm to time out (in ms)?`)
},
default: 60 * 1000,
validate: isPositiveNumber
},

/** Digger-only settings **/
{
when: isMapType('Digger'),
type: 'input',
name: 'corridorLengthMaximum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the maximum length for each generated "corridor"?`)
},
validate(input, answers) {
const {height, width} = answers
const max = Math.min(height, width)
return (isPositiveNumber(input) && input < max) || `Please enter a positive numeric value that is less than ${max}.`
},
filter: toInteger,
},
{
when: isMapType('Digger'),
type: 'input',
name: 'corridorLengthMinimum',
message(answers) {
const {type} = answers
return blue(`[${type}] What will be the minimum length for each generated "corridor"?`)
},
validate(input, answers) {
const {height, width, corridorLengthMaximum} = answers
const max = Math.min(height, width, corridorLengthMaximum - 1)
return (isPositiveNumber(input) && input < max) || `Please enter a positive numeric value that is less than ${max}.`
},
filter: toInteger
},

/** Confirmation steps. **/
{
type: 'confirm',
name: 'confirmedSettings',
message(answers) {
const printable = Object.assign({}, answers)

// Delete props we won't pass to the final builder.
delete printable.customizeAreaInfo;

pretty(printable)
return blue('Confirm settings before generating?')
}
},

{
type: 'confirm',
name: 'confirmedGeneratedMap',
when(answers) {
const {confirmedSettings} = answers;

// Cheap. Have it restart from beginning instead
// or allow user to edit options directly using editor mode.
if (!confirmedSettings) {
console.log(red('Try, try again.'));
process.exit(1);
}
return true;
},
message(answers) {
console.log(blue('Generating map...'));
const overrides = {
writeToFile: false //true if not in testing.
}

// Same option for Digger and Uniform dungeons, but different key name.
answers.roomDugPercentage = answers.dugPercentage;

const axOptions = Object.assign({},
answers,
overrides
)
const {graphic, buildCallback, rooms} = ax.generate(axOptions, true)
STATE.buildCallback = buildCallback
STATE.rooms = rooms
console.log('LEGEND:\t. = room\t# = empty')
console.log(bold(graphic))
return blue('Would you like to write this map to disk?')
}
}
]

inquirer.prompt(questions)
.then(console.log)
.then(function(answers) {
if (answers.confirmedGeneratedMap && STATE.buildCallback) {
const build = STATE.buildCallback(true);
} else {
console.log(red('Try, try again...'))
console.log('Here are the rooms in case you change your mind:')
process.exit(1)
}
process.exit(0)
})
.catch(console.error)


15 changes: 15 additions & 0 deletions bin/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
toInteger (input) {
return parseInt(input, 10)
},
toPercentage (input) {
if (input < 1) {
return input
}
return input / 100
},
toArrayOfNumbers (input) {
return input.split(',')
.map(s => parseInt(s.trim(), 10))
}
}
Loading

0 comments on commit cdde256

Please sign in to comment.