initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
237
build/mockProcess.js
Normal file
237
build/mockProcess.js
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const mockdata_1 = require("./mockdata");
|
||||||
|
const cryptoRandomString = require("crypto-random-string");
|
||||||
|
const unique_names_generator_1 = require("unique-names-generator");
|
||||||
|
function handleEvent(eventLog) {
|
||||||
|
if (eventLog.eventName === 'bought') {
|
||||||
|
let vendingMachine = findVendingMachine(eventLog.data.offer);
|
||||||
|
let selection = vendingMachine.selections.find((sel) => {
|
||||||
|
return sel.id === eventLog.data.selection;
|
||||||
|
});
|
||||||
|
selection.current = selection.current - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function findVendingMachine(id) {
|
||||||
|
for (let i in mockdata_1.default.players) {
|
||||||
|
let player = mockdata_1.default.players[i];
|
||||||
|
for (let j in player.vendingMachines) {
|
||||||
|
if (player.vendingMachines[i].id === id) {
|
||||||
|
return player.vendingMachines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function personPassesByVendingMachine(person, machine) {
|
||||||
|
let isThursty = Math.random() < person.thirst;
|
||||||
|
let availableSelections = machine.selections.filter((selection) => {
|
||||||
|
return !!selection.current;
|
||||||
|
});
|
||||||
|
if (isThursty) {
|
||||||
|
if (!availableSelections.length) {
|
||||||
|
return wontBuy('NoneAvailable');
|
||||||
|
}
|
||||||
|
let acceptableOptions = availableSelections.filter((option) => {
|
||||||
|
return person.preferences.indexOf(option.drink) !== -1;
|
||||||
|
});
|
||||||
|
if (!acceptableOptions.length) {
|
||||||
|
return wontBuy('NoPreferredDrinks');
|
||||||
|
}
|
||||||
|
let options = acceptableOptions.filter((selection) => {
|
||||||
|
return selection.price <= person.highestPrice;
|
||||||
|
});
|
||||||
|
if (!options.length) {
|
||||||
|
return wontBuy('HighPrice');
|
||||||
|
}
|
||||||
|
let picked = pick(acceptableOptions);
|
||||||
|
let selectionTypeData = getSelectionType(picked.type);
|
||||||
|
return {
|
||||||
|
eventName: 'bought',
|
||||||
|
data: {
|
||||||
|
reason: 'Thirsty',
|
||||||
|
amount: picked.price,
|
||||||
|
drink: picked.drink,
|
||||||
|
selection: picked.id,
|
||||||
|
selectionType: picked.type,
|
||||||
|
oz: selectionTypeData ? selectionTypeData.oz : 12,
|
||||||
|
person: person.id,
|
||||||
|
offer: machine.id,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return wontBuy('NotThirsty');
|
||||||
|
}
|
||||||
|
function wontBuy(reason) {
|
||||||
|
return {
|
||||||
|
eventName: 'wontBuy',
|
||||||
|
data: {
|
||||||
|
reason: reason,
|
||||||
|
amount: 0,
|
||||||
|
drink: 'none',
|
||||||
|
selection: 'none',
|
||||||
|
selectionType: 'none',
|
||||||
|
oz: 0,
|
||||||
|
person: person.id,
|
||||||
|
offer: machine.id,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function generatePerson(location) {
|
||||||
|
let money = Math.random() * 100;
|
||||||
|
return {
|
||||||
|
id: genId('person'),
|
||||||
|
name: genPersonName(),
|
||||||
|
preferences: genPreferences(),
|
||||||
|
highestPrice: (money / 20),
|
||||||
|
money: money,
|
||||||
|
thirst: clamp(location.dailyTraffic.thirstBonus + (Math.random() / 10))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function genId(prefix) {
|
||||||
|
return `${prefix}-${cryptoRandomString({ length: 6, type: 'distinguishable' })}`;
|
||||||
|
}
|
||||||
|
function genPersonName() {
|
||||||
|
const config = {
|
||||||
|
dictionaries: [unique_names_generator_1.names, unique_names_generator_1.names],
|
||||||
|
length: 2,
|
||||||
|
separator: ' '
|
||||||
|
};
|
||||||
|
return unique_names_generator_1.uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
function genDrinkName() {
|
||||||
|
const config = {
|
||||||
|
dictionaries: [unique_names_generator_1.adjectives, unique_names_generator_1.colors],
|
||||||
|
length: 2,
|
||||||
|
separator: ' ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return unique_names_generator_1.uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
function genStoreName() {
|
||||||
|
const stores = [
|
||||||
|
'shop',
|
||||||
|
'reseller',
|
||||||
|
'department Store',
|
||||||
|
'chain Store',
|
||||||
|
'emporium',
|
||||||
|
'supermarket',
|
||||||
|
'hypermarket',
|
||||||
|
'superstore',
|
||||||
|
'mart',
|
||||||
|
'shed',
|
||||||
|
'big Box',
|
||||||
|
'convenience Store',
|
||||||
|
'corner Store',
|
||||||
|
'food Store',
|
||||||
|
'market',
|
||||||
|
'food Mart',
|
||||||
|
'bodega',
|
||||||
|
];
|
||||||
|
const config = {
|
||||||
|
dictionaries: [unique_names_generator_1.names, stores],
|
||||||
|
length: 2,
|
||||||
|
separator: '\'s ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return unique_names_generator_1.uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
function genCityName() {
|
||||||
|
const places = [
|
||||||
|
'town',
|
||||||
|
'city',
|
||||||
|
'township',
|
||||||
|
'borough',
|
||||||
|
'seat',
|
||||||
|
'metropolis',
|
||||||
|
'burg',
|
||||||
|
'municipality',
|
||||||
|
'hamlet',
|
||||||
|
];
|
||||||
|
const config = {
|
||||||
|
dictionaries: [Math.random() > 0.5 ? unique_names_generator_1.colors : unique_names_generator_1.names, places],
|
||||||
|
length: 2,
|
||||||
|
separator: ' ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return unique_names_generator_1.uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
function clamp(value, max = 1, min = 0) {
|
||||||
|
if (value > max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
if (value < min) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function genPreferences() {
|
||||||
|
const availableDrinks = mockdata_1.default.drinks;
|
||||||
|
let prefCount = Math.floor(Math.random() * availableDrinks.length);
|
||||||
|
prefCount = prefCount === 0 ? prefCount + 1 : prefCount;
|
||||||
|
let preferences = [];
|
||||||
|
while (preferences.length < prefCount) {
|
||||||
|
let newPref = pick(availableDrinks);
|
||||||
|
if (preferences.indexOf(newPref.id) === -1) {
|
||||||
|
preferences.push(newPref.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
function getSelectionType(typeName) {
|
||||||
|
return mockdata_1.default.selectionTypes.find((type) => {
|
||||||
|
return type.type === typeName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function randomNumber(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
function pick(arr) {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
|
function getStatsFromEvents(events) {
|
||||||
|
let purchaseCount = 0;
|
||||||
|
let offerEventCount = 0;
|
||||||
|
let amountEarned = 0;
|
||||||
|
let ozSold = 0;
|
||||||
|
let popularity = {};
|
||||||
|
let eventReasons = {};
|
||||||
|
for (let i in events) {
|
||||||
|
let eventLog = events[i];
|
||||||
|
if (!eventReasons[eventLog.data.reason]) {
|
||||||
|
eventReasons[eventLog.data.reason] = 0;
|
||||||
|
}
|
||||||
|
eventReasons[eventLog.data.reason] = eventReasons[eventLog.data.reason] + 1;
|
||||||
|
if (eventLog.eventName === 'bought' || eventLog.eventName === 'wontBuy') {
|
||||||
|
offerEventCount = offerEventCount + 1;
|
||||||
|
if (eventLog.eventName === 'bought') {
|
||||||
|
purchaseCount = purchaseCount + 1;
|
||||||
|
if (!popularity[eventLog.data.drink]) {
|
||||||
|
popularity[eventLog.data.drink] = 0;
|
||||||
|
}
|
||||||
|
popularity[eventLog.data.drink] = popularity[eventLog.data.drink] + 1;
|
||||||
|
amountEarned = amountEarned + eventLog.data.amount;
|
||||||
|
ozSold = ozSold + eventLog.data.oz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log({
|
||||||
|
purchaseCount,
|
||||||
|
purchaseRate: purchaseCount / offerEventCount,
|
||||||
|
offerEventCount,
|
||||||
|
amountEarned,
|
||||||
|
ozSold,
|
||||||
|
popularity,
|
||||||
|
eventReasons
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let events = [];
|
||||||
|
let traffic = randomNumber(mockdata_1.default.locations[0].dailyTraffic.min, mockdata_1.default.locations[0].dailyTraffic.max);
|
||||||
|
for (let i = 0; i < traffic; i++) {
|
||||||
|
let curEvent = personPassesByVendingMachine(generatePerson(mockdata_1.default.locations[0]), mockdata_1.default.players[0].vendingMachines[0]);
|
||||||
|
console.log(genCityName());
|
||||||
|
events.push(curEvent);
|
||||||
|
handleEvent(curEvent);
|
||||||
|
}
|
||||||
|
getStatsFromEvents(events);
|
220
build/mockdata.js
Normal file
220
build/mockdata.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
let dataDefinition = {
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
id: 'player-01',
|
||||||
|
name: 'payne8',
|
||||||
|
balance: 4000.00,
|
||||||
|
vendingMachines: [
|
||||||
|
{
|
||||||
|
id: 'vend-01',
|
||||||
|
name: "First One!",
|
||||||
|
location: 'loc-01',
|
||||||
|
placement: 'plac-01',
|
||||||
|
compatibleSelectionTypes: ['can', 'bottle'],
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
id: 'sel-01',
|
||||||
|
name: 'Diet Mtn. Dew',
|
||||||
|
drink: 'drink-01',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 3.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-02',
|
||||||
|
name: 'Mtn. Dew',
|
||||||
|
drink: 'drink-02',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 2.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-03',
|
||||||
|
name: 'Dr. Pepper',
|
||||||
|
drink: 'drink-03',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-04',
|
||||||
|
name: 'Sprite',
|
||||||
|
drink: 'drink-04',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 0.75,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-05',
|
||||||
|
name: 'Coke',
|
||||||
|
drink: 'drink-05',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-06',
|
||||||
|
name: 'Diet Coke',
|
||||||
|
drink: 'drink-06',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-07',
|
||||||
|
name: 'Orange Soda',
|
||||||
|
drink: 'drink-07',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 2.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-08',
|
||||||
|
name: 'Grape Soda',
|
||||||
|
drink: 'drink-08',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 0.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agreement: 0.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
deals: [
|
||||||
|
{
|
||||||
|
id: 'deal-01',
|
||||||
|
location: 'loc-01',
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
id: 'sel-09',
|
||||||
|
name: 'Diet Mtn. Dew - Dozen Cans',
|
||||||
|
drink: 'drink-01',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 7.00,
|
||||||
|
type: 'dozen-box'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agreement: 0.4,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectionTypes: [
|
||||||
|
{
|
||||||
|
type: 'can',
|
||||||
|
oz: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dozen-box',
|
||||||
|
os: 144
|
||||||
|
}
|
||||||
|
],
|
||||||
|
drinks: [
|
||||||
|
{
|
||||||
|
id: 'drink-01',
|
||||||
|
name: 'Diet Mtn. Dew',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-02',
|
||||||
|
name: 'Mtn. Dew',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-03',
|
||||||
|
name: 'Dr. Pepper',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-04',
|
||||||
|
name: 'Sprite',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-05',
|
||||||
|
name: 'Coke',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-06',
|
||||||
|
name: 'Diet Coke',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-07',
|
||||||
|
name: 'Orange Soda',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-08',
|
||||||
|
name: 'Grape Soda',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
id: 'loc-01',
|
||||||
|
name: 'General Store',
|
||||||
|
city: 'city-01',
|
||||||
|
dailyTraffic: {
|
||||||
|
max: 2000,
|
||||||
|
min: 400,
|
||||||
|
thirstBonus: 0.05,
|
||||||
|
},
|
||||||
|
deals: {
|
||||||
|
count: 2,
|
||||||
|
traffic: 0.4,
|
||||||
|
list: [
|
||||||
|
'deal-01'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
placements: [
|
||||||
|
{
|
||||||
|
id: 'plac-01',
|
||||||
|
traffic: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'plac-02',
|
||||||
|
traffic: 0.6,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cities: [
|
||||||
|
{
|
||||||
|
id: 'city-01',
|
||||||
|
name: 'Hometown',
|
||||||
|
population: 15000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
people: [
|
||||||
|
{
|
||||||
|
id: 'person-01',
|
||||||
|
name: 'Steve Klemm',
|
||||||
|
preferences: [
|
||||||
|
'drink-01',
|
||||||
|
'drink-04',
|
||||||
|
'drink-08',
|
||||||
|
],
|
||||||
|
highestPrice: 2.00,
|
||||||
|
wallet: {
|
||||||
|
cc: 500.00,
|
||||||
|
cash: 10.00,
|
||||||
|
coins: 1.25,
|
||||||
|
},
|
||||||
|
thirst: 0.8,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
exports.default = dataDefinition;
|
236
data-relationships.svg
Normal file
236
data-relationships.svg
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="210mm"
|
||||||
|
height="297mm"
|
||||||
|
viewBox="0 0 210 297"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="data-relationships.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.979899"
|
||||||
|
inkscape:cx="451.62995"
|
||||||
|
inkscape:cy="862.03561"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="3440"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
inkscape:window-x="3832"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 35.090844,51.835306 45.758187,-4.06414"
|
||||||
|
id="path956"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 129.22481,48.038434 49.04396,18.842501"
|
||||||
|
id="path958"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 35.090844,51.835306 67.446288,76.541908"
|
||||||
|
id="path956-9"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 88.906386,76.196101 178.26877,66.880935"
|
||||||
|
id="path956-2"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 20.619022,29.691203 21.342295,46.87499"
|
||||||
|
id="path956-0"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<g
|
||||||
|
id="g860"
|
||||||
|
transform="translate(12.428033,22.851544)">
|
||||||
|
<rect
|
||||||
|
y="18.747269"
|
||||||
|
x="68.663109"
|
||||||
|
height="12.077432"
|
||||||
|
width="48.42609"
|
||||||
|
id="rect839"
|
||||||
|
style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.21469432;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-0.80180858,-5.8799296)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot847"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion849"><rect
|
||||||
|
y="86.103172"
|
||||||
|
x="275.77164"
|
||||||
|
height="55.558392"
|
||||||
|
width="163.64471"
|
||||||
|
id="rect851" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara853">Vending Machines</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
id="g875"
|
||||||
|
transform="translate(-26.860587,-37.150464)">
|
||||||
|
<rect
|
||||||
|
y="84.174919"
|
||||||
|
x="34.399231"
|
||||||
|
height="8.7096987"
|
||||||
|
width="27.685835"
|
||||||
|
id="rect841"
|
||||||
|
style="fill:#00ffff;fill-opacity:1;stroke:#000000;stroke-width:0.1079253;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-0.80180858,-12.027129)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot862"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion864"><rect
|
||||||
|
y="349.75299"
|
||||||
|
x="144.45181"
|
||||||
|
height="48.487339"
|
||||||
|
width="91.923897"
|
||||||
|
id="rect866" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara868">Locations</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
id="g914"
|
||||||
|
transform="translate(43.164029,-33.94323)">
|
||||||
|
<rect
|
||||||
|
y="95.394814"
|
||||||
|
x="135.15442"
|
||||||
|
height="9.5223551"
|
||||||
|
width="26.093069"
|
||||||
|
id="rect843"
|
||||||
|
style="fill:#00ccff;fill-opacity:1;stroke:#000000;stroke-width:0.0970764;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,0.53453906,-11.49259)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot877"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion879"><rect
|
||||||
|
y="390.15909"
|
||||||
|
x="524.26917"
|
||||||
|
height="54.548248"
|
||||||
|
width="220.21323"
|
||||||
|
id="rect881" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara883">Players</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
id="g945"
|
||||||
|
transform="translate(31.671439,-93.009795)">
|
||||||
|
<rect
|
||||||
|
y="164.60806"
|
||||||
|
x="35.720619"
|
||||||
|
height="8.7396231"
|
||||||
|
width="21.568562"
|
||||||
|
id="rect845"
|
||||||
|
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:0.07800145;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-98.724537,57.6412)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot877-1"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion879-6"><rect
|
||||||
|
y="390.15909"
|
||||||
|
x="524.26917"
|
||||||
|
height="54.548248"
|
||||||
|
width="220.21323"
|
||||||
|
id="rect881-4" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara883-1">Deals</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
id="g954"
|
||||||
|
transform="translate(65.748303,-34.611403)">
|
||||||
|
<rect
|
||||||
|
y="156.89734"
|
||||||
|
x="55.004101"
|
||||||
|
height="10.530357"
|
||||||
|
width="31.110111"
|
||||||
|
id="rect947"
|
||||||
|
style="fill:#8dd35f;fill-opacity:1;stroke:#000000;stroke-width:0.15815331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-81.306054,50.584242)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot877-1-2"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion879-6-4"><rect
|
||||||
|
y="390.15909"
|
||||||
|
x="524.26917"
|
||||||
|
height="54.548248"
|
||||||
|
width="220.21323"
|
||||||
|
id="rect881-4-1" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara883-1-4">Placements</flowPara></flowRoot> </g>
|
||||||
|
<flowRoot
|
||||||
|
xml:space="preserve"
|
||||||
|
id="flowRoot996"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-16.437076,-4.1426777)"><flowRegion
|
||||||
|
id="flowRegion998"><rect
|
||||||
|
id="rect1000"
|
||||||
|
width="210.11172"
|
||||||
|
height="52.527935"
|
||||||
|
x="623.2641"
|
||||||
|
y="182.57274" /></flowRegion><flowPara
|
||||||
|
id="flowPara1002"
|
||||||
|
style="font-size:17.33333397px">Owned by</flowPara></flowRoot> <g
|
||||||
|
id="g1044"
|
||||||
|
transform="translate(0.80180858,1.3363476)">
|
||||||
|
<rect
|
||||||
|
y="19.619349"
|
||||||
|
x="10.869535"
|
||||||
|
height="8.7296562"
|
||||||
|
width="18.351358"
|
||||||
|
id="rect841-5"
|
||||||
|
style="fill:#80b3ff;fill-opacity:1;stroke:#000000;stroke-width:0.08796817;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-24.321526,-76.57272)"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
id="flowRoot862-3"
|
||||||
|
xml:space="preserve"><flowRegion
|
||||||
|
id="flowRegion864-9"><rect
|
||||||
|
y="349.75299"
|
||||||
|
x="144.45181"
|
||||||
|
height="48.487339"
|
||||||
|
width="91.923897"
|
||||||
|
id="rect866-6" /></flowRegion><flowPara
|
||||||
|
style="font-size:17.33333397px"
|
||||||
|
id="flowPara868-9">Cities</flowPara></flowRoot> </g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.6 KiB |
12
package.json
Normal file
12
package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "vending-tycoon-cli",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "Mason Payne <gomas.bmw@gmail.com>",
|
||||||
|
"license": "UNLICENCED",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"crypto-random-string": "^3.2.0",
|
||||||
|
"unique-names-generator": "^4.2.0"
|
||||||
|
}
|
||||||
|
}
|
317
src/mockProcess.ts
Normal file
317
src/mockProcess.ts
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import mockData from './mockdata';
|
||||||
|
import * as cryptoRandomString from 'crypto-random-string';
|
||||||
|
import { uniqueNamesGenerator, Config, adjectives, names, colors } from 'unique-names-generator';
|
||||||
|
|
||||||
|
// function calculateBusinessDay(player: any) {
|
||||||
|
// let lineItems = [];
|
||||||
|
// for (let i in player.vendingMachines) {
|
||||||
|
// let machine = player.vendingMachines[i];
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
function handleEvent(eventLog: EventLog) {
|
||||||
|
if (eventLog.eventName === 'bought') {
|
||||||
|
let vendingMachine = findVendingMachine(eventLog.data.offer);
|
||||||
|
let selection = vendingMachine.selections.find((sel) => {
|
||||||
|
return sel.id === eventLog.data.selection;
|
||||||
|
});
|
||||||
|
selection.current = selection.current - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findVendingMachine(id: string) {
|
||||||
|
for (let i in mockData.players) {
|
||||||
|
let player = mockData.players[i];
|
||||||
|
for (let j in player.vendingMachines) {
|
||||||
|
if (player.vendingMachines[i].id === id) {
|
||||||
|
return player.vendingMachines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function personPassesByVendingMachine(person: Person, machine): EventLog {
|
||||||
|
let isThursty: boolean = Math.random() < person.thirst;
|
||||||
|
let availableSelections = machine.selections.filter((selection) => {
|
||||||
|
return !!selection.current;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isThursty) {
|
||||||
|
if (!availableSelections.length) {
|
||||||
|
return wontBuy('NoneAvailable');
|
||||||
|
}
|
||||||
|
// pick a drink
|
||||||
|
let acceptableOptions = availableSelections.filter((option) => {
|
||||||
|
return person.preferences.indexOf(option.drink) !== -1;
|
||||||
|
});
|
||||||
|
if (!acceptableOptions.length) {
|
||||||
|
return wontBuy('NoPreferredDrinks');
|
||||||
|
}
|
||||||
|
let options = acceptableOptions.filter((selection) => {
|
||||||
|
return selection.price <= person.highestPrice;
|
||||||
|
});
|
||||||
|
if (!options.length) {
|
||||||
|
return wontBuy('HighPrice');
|
||||||
|
}
|
||||||
|
let picked = pick(acceptableOptions);
|
||||||
|
let selectionTypeData = getSelectionType(picked.type);
|
||||||
|
return {
|
||||||
|
eventName: 'bought',
|
||||||
|
data: {
|
||||||
|
reason: 'Thirsty',
|
||||||
|
amount: picked.price,
|
||||||
|
drink: picked.drink,
|
||||||
|
selection: picked.id,
|
||||||
|
selectionType: picked.type,
|
||||||
|
oz: selectionTypeData ? selectionTypeData.oz : 12,
|
||||||
|
person: person.id,
|
||||||
|
offer: machine.id,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return wontBuy('NotThirsty');
|
||||||
|
}
|
||||||
|
|
||||||
|
function wontBuy(reason): EventLog {
|
||||||
|
return {
|
||||||
|
eventName: 'wontBuy',
|
||||||
|
data: {
|
||||||
|
reason: reason,
|
||||||
|
amount: 0,
|
||||||
|
drink: 'none',
|
||||||
|
selection: 'none',
|
||||||
|
selectionType: 'none',
|
||||||
|
oz: 0,
|
||||||
|
person: person.id,
|
||||||
|
offer: machine.id,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePerson(location: Location): Person {
|
||||||
|
let money = Math.random() * 100;
|
||||||
|
return {
|
||||||
|
id: genId('person'),
|
||||||
|
name: genPersonName(),
|
||||||
|
preferences: genPreferences(),
|
||||||
|
highestPrice: (money / 20),
|
||||||
|
money: money,
|
||||||
|
thirst: clamp(location.dailyTraffic.thirstBonus + (Math.random() / 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function genId(prefix: string): string {
|
||||||
|
return `${prefix}-${cryptoRandomString({length: 6, type: 'distinguishable'})}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function genPersonName(): string {
|
||||||
|
const config: Config = {
|
||||||
|
dictionaries: [names, names],
|
||||||
|
length: 2,
|
||||||
|
separator: ' '
|
||||||
|
};
|
||||||
|
return uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function genDrinkName(): string {
|
||||||
|
const config: Config = {
|
||||||
|
dictionaries: [adjectives, colors],
|
||||||
|
length: 2,
|
||||||
|
separator: ' ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function genStoreName(): string {
|
||||||
|
const stores: string[] = [
|
||||||
|
'shop',
|
||||||
|
'reseller',
|
||||||
|
'department Store',
|
||||||
|
'chain Store',
|
||||||
|
'emporium',
|
||||||
|
'supermarket',
|
||||||
|
'hypermarket',
|
||||||
|
'superstore',
|
||||||
|
'mart',
|
||||||
|
'shed',
|
||||||
|
'big Box',
|
||||||
|
'convenience Store',
|
||||||
|
'corner Store',
|
||||||
|
'food Store',
|
||||||
|
'market',
|
||||||
|
'food Mart',
|
||||||
|
'bodega',
|
||||||
|
];
|
||||||
|
const config: Config = {
|
||||||
|
dictionaries: [names, stores],
|
||||||
|
length: 2,
|
||||||
|
separator: '\'s ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function genCityName(): string {
|
||||||
|
const places: string[] = [
|
||||||
|
'town',
|
||||||
|
'city',
|
||||||
|
'township',
|
||||||
|
'borough',
|
||||||
|
'seat',
|
||||||
|
'metropolis',
|
||||||
|
'burg',
|
||||||
|
'municipality',
|
||||||
|
'hamlet',
|
||||||
|
];
|
||||||
|
const config: Config = {
|
||||||
|
dictionaries: [Math.random() > 0.5 ? colors : names, places],
|
||||||
|
length: 2,
|
||||||
|
separator: ' ',
|
||||||
|
style: 'capital'
|
||||||
|
};
|
||||||
|
return uniqueNamesGenerator(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, max: number = 1, min: number = 0) {
|
||||||
|
if (value > max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
if (value < min) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function genPreferences(): string[] {
|
||||||
|
const availableDrinks: string[] = mockData.drinks;
|
||||||
|
let prefCount = Math.floor(Math.random() * availableDrinks.length);
|
||||||
|
prefCount = prefCount === 0 ? prefCount + 1 : prefCount; // have at least one preference
|
||||||
|
let preferences: string[] = [];
|
||||||
|
while (preferences.length < prefCount) {
|
||||||
|
let newPref: Drink = pick(availableDrinks);
|
||||||
|
if (preferences.indexOf(newPref.id) === -1) {
|
||||||
|
preferences.push(newPref.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectionType(typeName: string) {
|
||||||
|
return mockData.selectionTypes.find((type) => {
|
||||||
|
return type.type === typeName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomNumber(min: number, max: number): number {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pick(arr: any[]) {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatsFromEvents(events: EventLog[]) {
|
||||||
|
let purchaseCount: number = 0;
|
||||||
|
let offerEventCount: number = 0;
|
||||||
|
let amountEarned: number = 0;
|
||||||
|
let ozSold: number = 0;
|
||||||
|
let popularity: {[drink: string]: number} = {};
|
||||||
|
let eventReasons: {[reason: string]: number} = {};
|
||||||
|
|
||||||
|
for (let i in events) {
|
||||||
|
let eventLog: EventLog = events[i];
|
||||||
|
if (!eventReasons[eventLog.data.reason]) {
|
||||||
|
eventReasons[eventLog.data.reason] = 0;
|
||||||
|
}
|
||||||
|
eventReasons[eventLog.data.reason] = eventReasons[eventLog.data.reason] + 1;
|
||||||
|
if (eventLog.eventName === 'bought' || eventLog.eventName === 'wontBuy') {
|
||||||
|
offerEventCount = offerEventCount + 1;
|
||||||
|
if (eventLog.eventName === 'bought') {
|
||||||
|
purchaseCount = purchaseCount + 1;
|
||||||
|
if (!popularity[eventLog.data.drink]) {
|
||||||
|
popularity[eventLog.data.drink] = 0;
|
||||||
|
}
|
||||||
|
popularity[eventLog.data.drink] = popularity[eventLog.data.drink] + 1;
|
||||||
|
amountEarned = amountEarned + eventLog.data.amount;
|
||||||
|
ozSold = ozSold + eventLog.data.oz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
purchaseCount,
|
||||||
|
purchaseRate: purchaseCount / offerEventCount,
|
||||||
|
offerEventCount,
|
||||||
|
amountEarned,
|
||||||
|
ozSold,
|
||||||
|
popularity,
|
||||||
|
eventReasons
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Drink {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
costPerOz: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DailyTraffic {
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
thirstBonus: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Placement {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
traffic: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Location {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
dailyTraffic: DailyTraffic;
|
||||||
|
placements: Placement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
preferences: string[];
|
||||||
|
highestPrice: number;
|
||||||
|
money: number;
|
||||||
|
thirst: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventLog {
|
||||||
|
eventName: string;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PersonEvent {
|
||||||
|
reason: string;
|
||||||
|
amount: number;
|
||||||
|
drink: string;
|
||||||
|
selection: string;
|
||||||
|
selectionType: string;
|
||||||
|
oz: number;
|
||||||
|
person: string; // person involved in the event
|
||||||
|
offer: string; // id of deal or vending machine
|
||||||
|
}
|
||||||
|
|
||||||
|
let events: EventLog[] = [];
|
||||||
|
let traffic: number = randomNumber(mockData.locations[0].dailyTraffic.min, mockData.locations[0].dailyTraffic.max)
|
||||||
|
for (let i = 0; i < traffic; i++) {
|
||||||
|
// console.log(generatePerson(mockData.locations[0]));
|
||||||
|
let curEvent: EventLog = personPassesByVendingMachine(generatePerson(mockData.locations[0]), mockData.players[0].vendingMachines[0])
|
||||||
|
// console.log(genDrinkName());
|
||||||
|
// console.log(genStoreName());
|
||||||
|
console.log(genCityName());
|
||||||
|
events.push(curEvent);
|
||||||
|
handleEvent(curEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatsFromEvents(events);
|
219
src/mockdata.ts
Normal file
219
src/mockdata.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
let dataDefinition: any = {
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
id: 'player-01',
|
||||||
|
name: 'payne8',
|
||||||
|
balance: 4000.00,
|
||||||
|
vendingMachines: [
|
||||||
|
{
|
||||||
|
id: 'vend-01',
|
||||||
|
name: "First One!",
|
||||||
|
location: 'loc-01',
|
||||||
|
placement: 'plac-01',
|
||||||
|
compatibleSelectionTypes: ['can', 'bottle'],
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
id: 'sel-01',
|
||||||
|
name: 'Diet Mtn. Dew',
|
||||||
|
drink: 'drink-01',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 3.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-02',
|
||||||
|
name: 'Mtn. Dew',
|
||||||
|
drink: 'drink-02',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 2.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-03',
|
||||||
|
name: 'Dr. Pepper',
|
||||||
|
drink: 'drink-03',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-04',
|
||||||
|
name: 'Sprite',
|
||||||
|
drink: 'drink-04',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 0.75,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-05',
|
||||||
|
name: 'Coke',
|
||||||
|
drink: 'drink-05',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-06',
|
||||||
|
name: 'Diet Coke',
|
||||||
|
drink: 'drink-06',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 1.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-07',
|
||||||
|
name: 'Orange Soda',
|
||||||
|
drink: 'drink-07',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 2.00,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sel-08',
|
||||||
|
name: 'Grape Soda',
|
||||||
|
drink: 'drink-08',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 0.50,
|
||||||
|
type: 'can'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agreement: 0.0, // revenue share
|
||||||
|
}
|
||||||
|
],
|
||||||
|
deals: [
|
||||||
|
{
|
||||||
|
id: 'deal-01',
|
||||||
|
location: 'loc-01',
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
id: 'sel-09',
|
||||||
|
name: 'Diet Mtn. Dew - Dozen Cans',
|
||||||
|
drink: 'drink-01',
|
||||||
|
max: 56,
|
||||||
|
current: 24,
|
||||||
|
price: 7.00,
|
||||||
|
type: 'dozen-box'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agreement: 0.4, // revenue share
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectionTypes: [
|
||||||
|
{
|
||||||
|
type: 'can',
|
||||||
|
oz: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dozen-box',
|
||||||
|
os: 144
|
||||||
|
}
|
||||||
|
],
|
||||||
|
drinks: [
|
||||||
|
{
|
||||||
|
id: 'drink-01',
|
||||||
|
name: 'Diet Mtn. Dew',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-02',
|
||||||
|
name: 'Mtn. Dew',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-03',
|
||||||
|
name: 'Dr. Pepper',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-04',
|
||||||
|
name: 'Sprite',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-05',
|
||||||
|
name: 'Coke',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-06',
|
||||||
|
name: 'Diet Coke',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-07',
|
||||||
|
name: 'Orange Soda',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drink-08',
|
||||||
|
name: 'Grape Soda',
|
||||||
|
costPerOz: 0.005,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
id: 'loc-01',
|
||||||
|
name: 'General Store',
|
||||||
|
city: 'city-01',
|
||||||
|
dailyTraffic: {
|
||||||
|
max: 2000,
|
||||||
|
min: 400,
|
||||||
|
thirstBonus: 0.05,
|
||||||
|
},
|
||||||
|
deals: {
|
||||||
|
count: 2,
|
||||||
|
traffic: 0.4,
|
||||||
|
list: [
|
||||||
|
'deal-01'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
placements: [
|
||||||
|
{
|
||||||
|
id: 'plac-01',
|
||||||
|
traffic: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'plac-02',
|
||||||
|
traffic: 0.6,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cities: [
|
||||||
|
{
|
||||||
|
id: 'city-01',
|
||||||
|
name: 'Hometown',
|
||||||
|
population: 15000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
people: [
|
||||||
|
{
|
||||||
|
id: 'person-01',
|
||||||
|
name: 'Steve Klemm',
|
||||||
|
preferences: [
|
||||||
|
'drink-01',
|
||||||
|
'drink-04',
|
||||||
|
'drink-08',
|
||||||
|
],
|
||||||
|
highestPrice: 2.00,
|
||||||
|
wallet: {
|
||||||
|
cc: 500.00,
|
||||||
|
cash: 10.00,
|
||||||
|
coins: 1.25,
|
||||||
|
},
|
||||||
|
thirst: 0.8,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dataDefinition;
|
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"isolatedModules": false,
|
||||||
|
"jsx": "react",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"declaration": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noImplicitUseStrict": false,
|
||||||
|
"removeComments": true,
|
||||||
|
"noLib": false,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"outDir": "./build",
|
||||||
|
"rootDir": "./src/",
|
||||||
|
"sourceMap": false
|
||||||
|
},
|
||||||
|
"compileOnSave": true,
|
||||||
|
"buildOnSave": true,
|
||||||
|
"exclue": [
|
||||||
|
"node_modules",
|
||||||
|
"typings/browser",
|
||||||
|
"typings/browser.d.ts"
|
||||||
|
]
|
||||||
|
}
|
20
yarn.lock
Normal file
20
yarn.lock
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
crypto-random-string@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-3.2.0.tgz#d513ef0c2ac6ff7cad5769de585d9bf2ad5a2b4d"
|
||||||
|
integrity sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==
|
||||||
|
dependencies:
|
||||||
|
type-fest "^0.8.1"
|
||||||
|
|
||||||
|
type-fest@^0.8.1:
|
||||||
|
version "0.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
|
||||||
|
unique-names-generator@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.2.0.tgz#e1845155becb4f1d133ec12fc7efc32d0cb733b1"
|
||||||
|
integrity sha512-MaNgUy/U7jeIMr5n8tdYXFi5IJ9DVo3nc90Z0QDnbYmO0F/H3Swjb8H/bkHRQYBCNveBNU6rq90metcmxVdO6Q==
|
Reference in New Issue
Block a user