A => .gitignore +225 -0
@@ 1,225 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/node,emacs,macos
+# Edit at https://www.toptal.com/developers/gitignore?templates=node,emacs,macos
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### Node Patch ###
+# Serverless Webpack directories
+.webpack/
+
+# Optional stylelint cache
+
+# SvelteKit build / generate output
+.svelte-kit
+
+# End of https://www.toptal.com/developers/gitignore/api/node,emacs,macos<
\ No newline at end of file
A => README.md +31 -0
@@ 1,31 @@
+# Dice
+
+Here is a little class to roll dice given a string in standard dice notation, e.g. `2d20+3`.
+
+## How to use
+
+Instantiate an instance of the class for each player, and then go nuts!
+
+```javascript
+// instantiate yo' players
+const playerSarah = new diceRoller();
+const playerEli = new diceRoller();
+
+// play the game
+playerSarah.roll('2d10-4');
+playerEli.roll('2d12+12');
+playerSarah.roll('1d12');
+playerEli.roll('1d2');
+
+
+// check the log!
+console.log('==== Sarah\'s log ====');
+playerSarah.log.forEach(logEntry => {
+ console.log(logEntry.input + " : " + logEntry.result + " : " + logEntry.timestamp);
+});
+
+console.log('==== Eli\'s log ====');
+playerEli.log.forEach(logEntry => {
+ console.log(logEntry.input + " : " + logEntry.result + " : " + logEntry.timestamp);
+});
+```<
\ No newline at end of file
A => UNLICENSE +24 -0
@@ 1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/><
\ No newline at end of file
A => dice.js +49 -0
@@ 1,49 @@
+class diceRoller {
+
+ constructor() {
+ this.log = [];
+ }
+
+ validate(diceNotation) {
+ const match = /^(\d+)?d(\d+)([+-]\d+)?$/.exec(diceNotation);
+ if (!match) {
+ throw "Invalid dice notation: " + diceNotation;
+ } else {
+ return match;
+ }
+ }
+
+ parseDice(diceNotation) {
+ const match = this.validate(diceNotation);
+ if (match) {
+ const diceInfo = {
+ numberOfDice: typeof match[1] === "undefined" ? 1 : parseInt(match[1]),
+ sidesOfDice: parseInt(match[2]),
+ diceModifier: typeof match[3] === "undefined" ? 0 : parseInt(match[3])
+ };
+ return diceInfo;
+ }
+ }
+
+ tallyRolls(diceData) {
+ let total = 0;
+ for (let i = 0; i < diceData.numberOfDice; i++) {
+ total += Math.floor(Math.random() * diceData.sidesOfDice) + 1;
+ }
+ total += diceData.diceModifier;
+ return total;
+ }
+
+ roll(humanInput) {
+ const rollResult = this.tallyRolls(this.parseDice(humanInput));
+ const rightNow = new Date();
+ let logEntry = {
+ "timestamp": rightNow.toISOString(),
+ "input": humanInput,
+ "result": rollResult
+ }
+ this.log.push(logEntry);
+ return rollResult;
+ }
+
+};<
\ No newline at end of file
A => dice.ts +63 -0
@@ 1,63 @@
+interface Dice {
+ numberOfDice: number;
+ sidesOfDice: number;
+ diceModifier: number;
+}
+
+interface LogEntry {
+ timestamp: string;
+ input: string;
+ result: number;
+}
+
+class DiceRoller {
+
+ readonly log: LogEntry[];
+
+ constructor() {
+ this.log = [];
+ }
+
+ roll(humanInput: string): number {
+ const rollResult = this.tallyRolls(this.parseDice(humanInput));
+ const rightNow = new Date();
+ let logEntry = {
+ "timestamp": rightNow.toISOString(),
+ "input": humanInput,
+ "result": rollResult
+ }
+ this.log.push(logEntry);
+ return rollResult;
+ }
+
+ private parseDice(diceNotation: string): Dice | undefined {
+ const match = this.validate(diceNotation);
+ if (match) {
+ const diceInfo = {
+ numberOfDice: typeof match[1] === "undefined" ? 1 : parseInt(match[1]),
+ sidesOfDice: parseInt(match[2]),
+ diceModifier: typeof match[3] === "undefined" ? 0 : parseInt(match[3])
+ };
+ return diceInfo;
+ }
+ }
+
+ private validate(diceNotation: string): RegExpExecArray | undefined {
+ const match = /^(\d+)?d(\d+)([+-]\d+)?$/.exec(diceNotation);
+ if (!match) {
+ throw "Invalid dice notation: " + diceNotation;
+ } else {
+ return match;
+ }
+ }
+
+ private tallyRolls(diceData: Dice): number {
+ let total = 0;
+ for (let i = 0; i < diceData.numberOfDice; i++) {
+ total += Math.floor(Math.random() * diceData.sidesOfDice) + 1;
+ }
+ total += diceData.diceModifier;
+ return total;
+ }
+
+};<
\ No newline at end of file