I need a function to calculate an amount of frames in the user input. I have some code from a Electron GUI that does the job, but i'm unsure how to do it in my HTML. I want it to grab the input from #id_desiredFrames in the html and then calculate the amount of total frames from that.
Code that needs to be rewritten:
* #example
* "55;58-60,2" -> 55,58,60 -> 3 Frames
* "55;58-60" -> 55,58,59,60 -> 4 Frames
*/
function calculateFrameAmount(_frame){
const notationArray = _frame.match(/(\d+)(-)?(\d+)?(\,\d)?/g)
const calculateNotation = item => {
if (!isNaN(item))
return 1
if (item.includes(",")) {
[item, diff] = item.split(",");
}
const splitItem = item.split("-")
return Math.floor((Math.max(...splitItem) - Math.min(...splitItem)) / diff) + 1
}
let diff = 1;
return notationArray
.map(calculateNotation)
.reduce((total, amount) => total += amount)
}
export default calculateFrameAmount
How would I go on about this? I know I can get the value by using document.getElementById("id_desiredFrames").value; but how do I add the value into the function??
If you're looking for the value to be computed on change, then you can do something like the following:
var desiredFrames = document.getElementById("id_desiredFrames");
desiredFrames.onchange = function(e) {
calculateFrameAmount(e.target.value);
}
Additionally, you can run the calculation onkeyup instead if you need it to be real-time:
desiredFrames.onkeyup = function(e) {
calculateFrameAmount(e.target.value);
}
Related
I have two values on my html page, that I got from a form.
The person completes the amount of money they have on one input, and on the other input, the number of people.
So, my question is:
How do i divide the amount.value by the people.value, and distribute it, in a way it appears as shown in the example below?
Amount: 150 / Number of People: 3
-Person 1 - 50
-Person 2 - 50
-Person 3 - 50
What i'm actually struggling with, is to create a function that will add to the HMTL another person + the result, depending on the number of people added previously.
The code down below just finds the total share of all people and distributes the amount among them dependin on their shares.
/*----- Define your variables in here ------*/
var amount = 150;
var people = {
1: 0.75,
2: 0.15,
3: 0.10
}
var totalSharePerc = 0;
/*----- Loop thruogh people to calculate total sum ------*/
for (const [key, value] of Object.entries(people)) {
totalSharePerc = totalSharePerc + value;
}
/*----- Loop thruogh people and split the amount ------*/
for (const [key, value] of Object.entries(people)) {
/*------ Calculate share of person -----*/
var share = (amount*value/totalSharePerc);
/*----- Do whatever you want ------*/
console.log("Person"+key+" => "+share);
}
You can use Array#reduce() to calculate the total share for every person involved.
This assumes you have a mapping defined of which person has to cover which share (which you will need to have if you want to distribute in non-equal shares).
const amount = 150;
// define mapping of percentages to persons
const sharesPerPersonPct = {
1: 0.75,
2: 0.15,
3: 0.1,
};
// create a mapping of total values to persons
const sharesPerPersonTotal = Object.entries(sharesPerPersonPct).reduce(
(allShares, [personId, share]) => {
allShares[personId] = {
pct: share, // add percentage (optional)
total: amount * share // add total share
}
return allShares;
},
{}
);
console.log("Resulting JS object:")
console.log(sharesPerPersonTotal);
Object.entries(sharesPerPersonTotal).forEach(([personId, share]) => console.log(`Person ${personId} has to cover ${(share.pct * 100).toFixed(2)}% which amounts to ${share.total}$.`))
Updated answer to reflect your edit
The following is for an equal distribution of an amount to a number of people. The challenge is that e.g 10$ cannot be distributed 3.33$ for each of 3 persons as then penny would be missing. This is the sort of stuff you get when using floating point arithmetic. To prevent that use integer arithmetic instead. So multiply 10$ by 100 so you get 1000p and you can then assign each person their floored share (Math.floor(1000 / 3) = 333) use modulo to get the remainder (10 % 3 = 1) and distribute that remainder among the persons involved. The current implementation isn't quite fair either though because it always assigns that one penny more starting from the front, but you could use something like this to account for that.
The rest is just input validation using RegEx and displaying the results doing some DOM manipulation.
function handleUpdateDistribution() {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
if (!isMoneyValid(amountMoney.value)) {
console.log("Money value can only have two decimal places!");
return;
}
if (!isNoPersonValid(amountMoney.value)) {
console.log("Number of persons must be an integer greater than one!");
return;
}
const distribution = updateDistribution(
Number.parseInt(noPersons.value),
Number.parseFloat(amountMoney.value)
);
showDistribution(distribution);
}
function isMoneyValid(money) {
const matches = money.match(/^[0-9]+(\.[0-9]{1,2})?$/g);
if (matches === null) return null;
else return matches[0];
}
function isNoPersonValid(noPersons) {
const matches = noPersons.match(/[1-9]*/g);
if (matches === null) return null;
else return matches[0];
}
function showDistribution(distribution) {
const list = document.createElement("ul");
const listItems = Object.entries(distribution).map(([personId, share]) => {
const item = document.createElement("li");
item.textContent = `Person ${personId} has to cover ${share}$.`;
return item;
});
list.append(...listItems);
document.getElementById("result").replaceChildren(list);
}
/**
*
* #param {number} noPersons number of persons to split between
* #param {number} amountMoney amount of money to split
*/
function updateDistribution(noPersons, amountMoney) {
// use integer arithmetic as floating point arithmetic is not very suitable for task at hand
amountMoney *= 100;
const share = Math.floor(amountMoney / noPersons);
let remainder = amountMoney % noPersons;
const persons = {};
for (let i = 1; i <= noPersons; i++) {
const shareInInts = share + (remainder > 0 ? 1 : 0);
remainder--;
persons[i] = (shareInInts / 100).toFixed(2);
}
return persons;
}
window.addEventListener("DOMContentLoaded", (e) => {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
amountMoney.addEventListener("input", handleUpdateDistribution);
noPersons.addEventListener("input", handleUpdateDistribution);
});
input:invalid {
border: red solid 3px;
}
<form>
<input id="no-persons" placeholder="Number of persons" type="number" required />
<input id="amount-money" placeholder="Amount of money" type="text" pattern="^[0-9]+(\.[0-9]{1,2})?$" required />
</form>
<div id="result"></div>
Please note you can and should do more to give the user a nice user experience (Show popovers instead of console.log(), nicer styling etc). See MDN docs on form validation. You should also probably restrict the number of persons as rendering thousands of list items will seriously impact the performance.
I am looking for a way to paginate in pouchdb by specifying the number of the page that I want.
The closest example I came across is this:
var options = {limit : 5};
function fetchNextPage() {
pouch.allDocs(options, function (err, response) {
if (response && response.rows.length > 0) {
options.startkey = response.rows[response.rows.length - 1].id;
options.skip = 1;
}
});
}
It assumes however that you are paginating one page after the other and calling this consecutively several times.
What I need instead is a way to retrieve page 5 for example, with a single query.
There is no easy answer to the question. A small slice from 3.2.5.6. Jump to Page
One drawback of the linked list style pagination is that you can’t
pre-compute the rows for a particular page from the page number and
the rows per page. Jumping to a specific page doesn’t really work. Our
gut reaction, if that concern is raised, is, “Not even Google is doing
that!” and we tend to get away with it. Google always pretends on the
first page to find 10 more pages of results. Only if you click on the
second page (something very few people actually do) might Google
display a reduced set of pages. If you page through the results, you
get links for the previous and next 10 pages, but no more.
Pre-computing the necessary startkey and startkey_docid for 20 pages
is a feasible operation and a pragmatic optimization to know the rows
for every page in a result set that is potentially tens of thousands
of rows long, or more.
If you are lucky and every document has an ordered sequence number, then a view could be constructed to easily navigate pages.
Another strategy is to precompute (preload) a range of keys, which is more reasonable but is complicated. The snippet below creates a trivial database which can be paged through via nav links.
There are 5 documents per page, and each "chapter" has 10 pages. computePages performs the look ahead
// look ahead and cache startkey for pages.
async function computePages(startPage, perPage, lookAheadPages, startKey) {
let options = {
limit: perPage * lookAheadPages,
include_docs: false,
reduce: false
};
// adjust. This happens when a requested page has no key cached.
if (startKey !== undefined) {
options.startkey = startKey;
options.skip = perPage; // not ideal, but tolerable probably?
}
const result = await db.allDocs(options);
// use max to prevent result overrun
// only the first key of each page is stored
const max = Math.min(options.limit, result.rows.length)
for (let i = 0; i < max; i += perPage) {
page_keys[startPage++] = result.rows[i].id;
}
}
page_keys provides a key/value store mapping page number to start key. Usually anything other than 1 for skip is red flag however this is reasonable here - we won't be skipping say a 100 documents right?
I just threw this together so it is imperfect and likely buggy, but it does demonstrate page navigation generally.
function gel(id) {
return document.getElementById(id);
}
// canned test documents
function getDocsToInstall() {
let docs = [];
// doc ids are a silly sequence of characters.
for (let i = 33; i < 255; i++) {
docs.push({
_id: `doc-${String.fromCharCode(i)}`
});
}
return docs;
}
// init db instance
let db;
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
await db.bulkDocs(getDocsToInstall());
}
// documents to show per page
const rows_per_page = 5;
// how many pages to preload into the page_keys list.
const look_ahead_pages = 10;
// page key cache: key = page number, value = document key
const page_keys = {};
// the current page being viewed
let page_keys_index = 0;
// track total rows available to prevent rendering links beyond available pages.
let total_rows = undefined;
async function showPage(page) {
// load the docs for this page
let options = {
limit: rows_per_page,
include_docs: true,
startkey: page_keys[page] // page index is computed
};
let result = await db.allDocs(options);
// see renderNav. Here, there is NO accounting for live changes to the db.
total_rows = total_rows || result.total_rows;
// just display the doc ids.
const view = gel('view');
view.innerText = result.rows.map(row => row.id).join("\n");
}
// look ahead and cache startkey for pages.
async function computePages(startPage, perPage, lookAheadPages, startKey) {
let options = {
limit: perPage * lookAheadPages,
include_docs: false,
reduce: false
};
// adjust. This happens when a requested page has no key cached.
if (startKey !== undefined) {
options.startkey = startKey;
options.skip = perPage; // not ideal, but tolerable probably?
}
const result = await db.allDocs(options);
// use max to prevent result overrun
// only the first key of each page is stored
const max = Math.min(options.limit, result.rows.length)
for (let i = 0; i < max; i += perPage) {
page_keys[startPage++] = result.rows[i].id;
}
}
// show page links and optional skip backward/forward links.
let last_chapter;
async function renderNav() {
// calculate which page to start linking.
const chapter = Math.floor(page_keys_index / look_ahead_pages);
if (chapter !== last_chapter) {
last_chapter = chapter;
const start = chapter * look_ahead_pages;
let html = "";
// don't render more page links than possible.
let max = Math.min(start + look_ahead_pages, total_rows / rows_per_page);
// render prev link if nav'ed past 1st chapter.
if (start > 0) {
html = `< `;
}
for (let i = start; i < max; i++) {
html += `${i+1} `;
}
// if more pages available, render the 'next' link
if (max % look_ahead_pages === 0) {
html += ` > `;
}
gel("nav").innerHTML = html;
}
}
async function navTo(page) {
if (page_keys[page] === undefined) {
// page key not cached - compute more page keys.
await computePages(page, rows_per_page, look_ahead_pages, page_keys[page - 1]);
}
page_keys_index = page;
await showPage(page_keys_index);
renderNav();
}
initDb().then(async() => {
await navTo(0);
});
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<pre id="view"></pre>
<hr/>
<div id="nav">
</nav>
I have implemented a countup which animates up to the data-target. How can I adjust the counter so that all counters stop at the same time?
Or is there a better way to implement this?
function animationEffect(){
const counters = document.querySelectorAll('.counter');
const speed = 2000;
counters.forEach((counter) => {
const updateCount = () => {
const target = + counter.getAttribute('data-target');
const count = + counter.innerText;
const inc = target / speed;
if(count < target) {
counter.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>
Updated Answer
Looks like I misunderstood what you mean by at the same time, but using setTimeout like this is still a really bad practice. Here is my take on it:
const counters = Array.from(document.querySelectorAll(".counter"));
const counterValues = [];
const speed = 500;
const updateCount = (counter, target, count, index) => {
const inc = target / speed;
counterValues[index] = count + inc;
if (count < target) {
counter.innerText = Math.floor(counterValues[index]);
} else {
counter.innerText = target;
}
};
counters.forEach((counter, index) => {
counterValues.push(0)
const interval = setInterval(() => {
const target = +counter.getAttribute("data-target");
const count = counterValues[index];
if (target !== count) {
updateCount(counter, target, count, index)
} else {
clearInterval(interval);
}
}, 1)
});
<div class="counter" data-target="32">0</div>
<div class="counter" data-target="3000">0</div>
<div class="counter" data-target="10">0</div>
Please check my old answer for why setInterval is better for this problem.
Other than that here is what is going on in this snippet:
I defined an array called counterValues which will hold the count values in a float format. In your example when you store the ceiled number to be used in your calculation later again, you are not doing a correct calculation.
If I remember correct one of your counters must be increased by 0.145, while you are incrementing it by 1 every time. By the way flooring is the right method for this as it won't reach to the target until it really reaches to it. If the target is 10, but your counter is at 9.5 it will be written as 10 in your code although it is not there yet.
updateCount is almost the same function. It now uses floor. It updates the amount of counter by using the previous floating value and then while writing to the DOM, it uses the floored value.
For each counter, it adds an interval which will update the counter and cancel itself whenever the counter reaches to the target value.
I used a shared state and indexed calculation for simplicity.
Old Answer
If you paste this code to the top of your code and run, when you log window.activeTimers you will see that there are hundreds of timers defined. It is because every time updateCount is called you are setting a new timer to updateCount. Although your animationEffect function is like the main function of your program, if you invoke it every time I mouseover your counters, it will set new timers which means faster update on your counters every time. To sum up, you don't have control at all at the moment.
For periodic calls you should use setInterval. It takes a function and a delay parameter (there are also optional args. You can check the docs). It repeatedly calls a function or executes a code snippet, with a fixed time delay between each call (From Mozilla docs). It also returns an interval ID so that you can cancel it later (meaning we can control it).
So in your case, just to stop in a desired moment first thing you should do is to get rid of onmouseover calls to the animationEffect and add a button to stop execution of updateCounters.
<div class="counter" data-target="299">0</div>
<div class="counter" data-target="1299">0</div>
<div class="counter" data-target="99">0</div>
<button id="stop">Stop</button>
After assigning variables counters and speed we can define an array to hold the intervals' IDs to cancel them later.
const counters = document.querySelectorAll(".counter");
const speed = 2000;
const cancelButton = document.getElementById('stop')
const countIntervals = [];
cancelButton.addEventListener('click', () => {
countIntervals.forEach(interval => clearInterval(interval))
})
As you can see I defined an event listener to our button. When you click on it it will iterate the interval IDs in countIntervals and clear those intervals. For simplicity, I didn't implement features like pausing and resetting and tried not to change your code much. You can experiment later.
Now first thing you should do is to comment or delete setTimeout line in your if statement. Then we will push the returned interval ID to our array countIntervals:
counters.forEach((counter) => {
const updateCount = () => {
const target = +counter.getAttribute("data-target");
const count = +counter.innerText;
const inc = target / speed;
if (count < target) {
counter.innerText = Math.ceil(count + inc);
// setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
};
countIntervals.push(setInterval(updateCount, 10))
});
Now your counters will stop once you hit the Stop button. I ignored the speed feature, but If you understand setInterval you can implement it easily.
I don't know if it helps, but i got it working when i change this line:
const inc = target / speed;
to this:
const inc = 1/(speed / target);
AND get rid of the Math.ceil() because this const speed = 2000; which is actually a step creates floating values. Maybe try smaller speed value
EDIT
A little more tweaking and we have a smooth answer without floating values >:D
function animationEffect(){
if(animationEffect.called){return null}
animationEffect.called=true
//these 2 lines prevent this function from being called multiple times to prevent overlapping
//the code works without these lines though so remove them if u don't want them
const counters = document.querySelectorAll('.counter');
const incrementConstant = 2; //the higher the number the faster the count rate, the lower the number the slower the count rate
const speed = 2000;
counters.forEach((counter) => {
const updateCount = () => {
const target = + counter.getAttribute('data-target');
counter.storedValue=counter.storedValue||counter.innerText-0; //saving a custom value in the element(the floating value)
const count = + counter.storedValue; //accessing custom value(and rounding it)
const inc = incrementConstant/(speed / target); //the math thanks to #Tidus
if(count < target) {
counter.storedValue=count+inc
counter.innerText = Math.round(counter.storedValue);
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>
How do I ensure that I don't get a repeat of a random number? Right now, this isn't working. I'm using a local array to store previous results.
getUniqueRandomNumber(x){
var index;
var viewedIndices = [];
index = Math.floor(Math.random() * (x));
if(viewedIndices.includes(index))
{
viewedIndices.push(index);
this.getUniqueRandomNumber(x);
}
else {
console.log(index);
return index;
}
}
You need to make viewedIndicies persistent, so that further calls of getUniqueRandomNumber can see elements previously added. Rather than keeping track of the indicies, it would probably be easier to keep track of just the plain numbers chosen. You can use a Set instead of an array for less computational complexity (.has is O(1), .includes is O(N)).
const makeGetUniqueRandomNumber = (x) => {
const chosenNumbers = new Set();
return () => {
if (chosenNumbers.size === x) {
throw new Error('No more uniques!');
}
let num;
do {
num = Math.floor(Math.random() * x);
} while (chosenNumbers.has(num));
chosenNumbers.add(num);
return num;
};
};
const getRand5 = makeGetUniqueRandomNumber(5);
console.log(
getRand5(),
getRand5(),
getRand5(),
getRand5(),
getRand5()
);
try {
getRand5();
} catch(e) {
console.log(e.message);
}
const anotherGetRand5 = makeGetUniqueRandomNumber(5);
console.log(
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5(),
anotherGetRand5()
);
You may also generate the whole array of random numbers ahead of time, and then splice each time another is chosen, but that'll be inefficient when the number of possibilities is large but you only need a few random numbers. The right choice depends on the proportion of unique numbers needed in one session to the size of the random range.
If developing in an ancient environment which doesn't understand ES6 (ES2015) syntax, then you can use an array instead of a Set, and pass the code through Babel:
"use strict";
var makeGetUniqueRandomNumber = function makeGetUniqueRandomNumber(x) {
var chosenNumbers = [];
return function () {
if (chosenNumbers.length === x) {
throw new Error('No more uniques!');
}
var num;
do {
num = Math.floor(Math.random() * x);
} while (chosenNumbers.includes(num));
chosenNumbers.push(num);
return num;
};
};
var getRand5 = makeGetUniqueRandomNumber(5);
console.log(getRand5(), getRand5(), getRand5(), getRand5(), getRand5());
try {
getRand5();
} catch (e) {
console.log(e.message);
}
var anotherGetRand5 = makeGetUniqueRandomNumber(5);
console.log(anotherGetRand5(), anotherGetRand5(), anotherGetRand5(), anotherGetRand5(), anotherGetRand5());
You have 2 mistakes, oné is the array inside the function this cleared for each try, and then there is wrong logic ending up in an infinite loop.
const usedIndexes = [];
function getUniqueRandomNumber(x) {
const index = Math.floor(Math.random() * (x));
if (usedIndexes.includes(index)) {
return this.getUniqueRandomNumber(x);
} else {
console.log(index);
usedIndexes.push(index);
return index;
}
}
Also, I would think about using Set, in this situation instead of the array.
const usedIndexes = new Set();
function getUniqueRandomNumber(max, min = 0) {
const newNumber = Math.floor(Math.random() * (max - min) + min);
if (usedIndexes.has(newNumber)) {
return this.getUniqueRandomNumber(max, min);
} else {
usedIndexes.add(newNumber);
return newNumber;
}
}
I have also edited variables names to better reflect their actual use and added a minimum for a random number.
This is not working because every time you call getUniqueRandomNumber it re-initializes your viewedIndices array to empty array. So to make your code work declare this array above the function call.
Do you just want the code you wrote to work or do you want a better solution? Picking random numbers until you don't get a repeat is a recipe for disaster down the line as your program stalls for several seconds trying to find a number that hasn't been used. Sure if you're only asking for a few numbers maybe it won't take forever but then the code sits in your code base and 5 years from now someone else is using it not knowing there is a time bomb in the code. Imagine there are 10000 elements in the array and 9999 have been picked. It could easily take 1 million re-tries before it ends up picking the one unused index.
The code appears to be choosing indices with variable names like index and viewedIndices
One way to pick random elements is just just remove then from the array at random. If you need to make copy of the array
const array = ["a", "b", "c", "d", "e", "f", "g"];
while (array.length) {
const ndx = Math.random() * array.length | 0;
const elem = array.splice(ndx, 1)[0];
console.log(elem);
}
Note: using Math.random() * value | 0 to get a random 0 -> positive integer is faster than Math.floor(Math.random() * value) as | is an operator, not a function attached to the Math object that has to be checked on every call to see if it has been replaced.
I have this object resources:
var resources = { //Handles resources of all kinds.
number: 100,
money: 23000000,
science: 1000,
popularity: {
amount: 0,
upgProd: 0 //Amount produced from upgrades.
}
};
This looks like a normal object.
However, I'm trying to display a certain quantity popularity. Every time I try to display it, I get a NaN instead of a number.
I try to return console.log(resources.popularity.upgProd); but I still end up getting an undefined. I have no clue why since I define the variable but I still get undefined...
No errors in the IDE or in the console, just undefined when I console.log().
EDIT: Here is some surrounding context... this is my update function, that updates every 1/7 second. ALSO, the first value of resources.popularity.upgProd is 0, then the next become NaN.
function update() {
buffer++;
if (buffer == 35) {
checkVisibilityOnBuildings();
checkVisiblityOnUpgrades();
checkVisibilityOnResources();
buffer = 0;
} // Every 5 seconds (35 ticks) visibility will be checked and updated.
/* Number increasing. A bit tedious but no other way to do it yet. */
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) + //Now it's easy! Just do UPGRADES[n+1] for the next building.
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
//Science gained per tick. Var used to make the "scienceProductionTotalDisp" work properly
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
resources.science += scienceTotalPerTick;
//Display vars for html so that rounding errors don't happen.
var numDisp = Math.floor(resources.number);
var sciDisp = Math.floor(resources.science * 100) / 100;
var popDisp = Math.floor(resources.popularity.amount * 100) / 100;
console.log(Number(resources.popularity.upgProd));
var moneyTotalPerTick = Math.pow(resources.number, (1/(player.moneyRatio))) + 1; //Cash flow per 143ms (7n for total / sec ish)
var popularityTotalPerTick = (Number(resources.popularity.upgProd)) + 0;
resources.popularity += popularityTotalPerTick;
console.log(resources.popularity.upgProd);
resources.money += moneyTotalPerTick;
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
getId('scienceProductionTotalDisp').innerHTML =
numFormat(Math.floor(scienceTotalPerTick * 700) / 100);
getId('popularityProductionTotalDisp').innerHTML =
numFormat(Math.floor(popularityTotalPerTick * 700) / 100);
Thank you!
Here is your problem:
resources.popularity += popularityTotalPerTick;
popularity is an object, and that doesn't do what you want.
Since you overwrite it with the result of an object added by a value, you assign it s string [object Object]9 where the last digit is whatever was in popularityTotalPerTick.
You get NaN (Not a number) since you are using Number(x)in console.log(Number(resources.popularity.upgProd));. Why are you doing that?
Does getId do a lookup of the element in the dom every time your function is called? Have the object changed or are you querying the DOM for the same element 7 times per second?
Some thoughts about the other tings in your code:
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) +
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
I'm assuming that BUILDINGS is an array with all the buildings, and that you want to calculate the number of all buildings in the array. There is a function for that: reduce that takes two parameters: a function and the start value:
resources.number += // do you really want += here and not just = ?
BUILDINGS.reduce( (sum, item) => sum + (item.numProdBase * item.count), 0 );
If your aren't familiar with arrow-functions it could be replaced with:
function (sum, item) { return sum + (item.numProdBase * item.count) }
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
I'm not sure why you are only doing it for two buildings, and not for all, but you could use reduce here too, with slice
var scienceTotalPerTick =
BUILDINGS.slice(2,4).reduce( (sum, item) => sum + (item.sciProdBase * item.count), 0);
Notice that the parameters to slice is begin to end (end not included), therefor 2,4 gives you element 2 and 3.
With this part of the code...
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
I assume that getId is a function that fetches the element via document.getElementById or document.querySelector, and that you do this for every frame, and you get the same element every time. Now imagine that the element is a box, and that I demand that you get it from the warehouse that is on the other side of the world. When you deliver the box, I open the box, replace the contents and send it back to the warehouse on the other side of the world. When you come back, I demand that you get the same box again, and you now have to travel to the other side of the world once again... and when you come back the story repeats...
My point here is that it is very wasteful to travel to the other side of the world each time to get the same box, or to get the same element from the DOM at each update. You could cache the elements by looking them up ONE time before you start the update, and you can put the result in an object:
function getElementsToUpdate() {
return {
moneyProductionTotalDisp: getId('moneyProductionTotalDisp'),
moneyDisp: getId('moneyDisp'),
// and so on...
}
}
If you name the property in the object the same as the id, you can put all the names in an array, and then reduce it to an object. This saves you some typing, and hard to find bugs because of a misspelled name:
function getElementsToUpdate() {
return [
'moneyProductionTotalDisp',
'moneyDisp',
'numberDisp',
'scienceDisp',
'popularityDisp',
// and so on....
].reduce(
(out, id) => { out[id] = getId(id); return out; }, {}
)
}
NOTE: This function should be run ONE time at startup, not for every update of the frame. It returns an object with the elements.
Somewhere in your code I assume that you use setInterval to call your update function. Since setInteval can take extra parameters that will be given to the called function, you can do something like this:
var timerHandle = setInterval( update, 1000/7, getElementsToUpdate() );
where update is your function, 1000/7 gives you the interval for 7 times a second, and getElementsToUpdate is the function that makes the time-expensive call to get the elements from the DOM one time.
You need to change the update function to take a parameter (the name is not important, but should be short and descriptive, so I use elem). This is the object that getElementsToUpdate() have returned with all the html-elements.
function update(elem) {
// ... your code ....
// Old code, that makes an expensive lookup into the DOM, and start a HTML parser.
// getId('numberDisp').innerHTML = numFormat(numDisp);
// New code, that gets the pre-looked up element and set the text.
elem.numberDisp.textContent = numFormat(numDisp);
}
I'm not a fan of using .innerHTML when it isn't html that is inserted. Always use .textContent instead, if is isn't html. In this case it would be better if you use the output-element, and set the .value property.
try using console.log(resources["popularity"]["upgProd"])