How to write a java script for specific Test attempts - javascript

I am having a java scripts for a game, but this game is like a test and if the user failed the game he will retake the test (redo the game), but I want a specific number of attempts only for the user to re do the test. now what I have, makes the user start from the beginning without any limitations even if he re do it for like 100 times, he will keep go back to the beginning.
I need in the fail massage to give another attempt (form like max 4 attempts) if failed once then the next fail massage to give him 3 tries and then 2 and then last one, and then he can't redo the test.
Script 1;
var player=GetPlayer();
var textArray = [];
for (var i = 1; i <= 15; i++) {
textArray.push(i);
};
var itemsLeft = textArray.length;
textArray=textArray.map(String).toString();
player.SetVar("Text_Array", textArray);
player.SetVar("Items_Left", itemsLeft);
Script 2:
//get the StoryLine player
var player=GetPlayer();
//get Storyline variable value as a string
var textArray=player.GetVar("Text_Array");
//Convert string to a numeric array
numArray=textArray.split(",").map(Number);
//Get a random number from the array and send it to StoryLine
var randNum = numArray[Math.floor(Math.random() * numArray.length)];
player.SetVar("Random",randNum);
//Remove the random number from your array and get the array's length
numArray.splice(numArray.indexOf(randNum), 1);
var itemsLeft=numArray.length;
//Convert array to a string and send it back to SL along with the array's length
textArray=numArray.map(String).toString();
player.SetVar("Items_Left", itemsLeft);
player.SetVar("Text_Array", textArray);

Related

Advice on how to optimise Google AppScript code [duplicate]

I've just written my first google apps scripts, ported from VBA, which formats a column of customer order information (thanks to you all of your direction).
Description:
The code identifies state codes by their - prefix, then combines the following first name with a last name (if it exists). It then writes "Order complete" where the last name would have been. Finally, it inserts a necessary blank cell if there is no gap between the orders (see image below).
Problem:
The issue is processing time. It cannot handle longer columns of data. I am warned that
Method Range.getValue is heavily used by the script.
Existing Optimizations:
Per the responses to this question, I've tried to keep as many variables outside the loop as possible, and also improved my if statements. #MuhammadGelbana suggests calling the Range.getValue method just once and moving around with its value...but I don't understand how this would/could work.
Code:
function format() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var lastRow = s.getRange("A:A").getLastRow();
var row, range1, cellValue, dash, offset1, offset2, offset3;
//loop through all cells in column A
for (row = 0; row < lastRow; row++) {
range1 = s.getRange(row + 1, 1);
//if cell substring is number, skip it
//because substring cannot process numbers
cellValue = range1.getValue();
if (typeof cellValue === 'number') {continue;};
dash = cellValue.substring(0, 1);
offset1 = range1.offset(1, 0).getValue();
offset2 = range1.offset(2, 0).getValue();
offset3 = range1.offset(3, 0).getValue();
//if -, then merge offset cells 1 and 2
//and enter "Order complete" in offset cell 2.
if (dash === "-") {
range1.offset(1, 0).setValue(offset1 + " " + offset2);
//Translate
range1.offset(2, 0).setValue("Order complete");
};
//The real slow part...
//if - and offset 3 is not blank, then INSERT CELL
if (dash === "-" && offset3) {
//select from three rows down to last
//move selection one more row down (down 4 rows total)
s.getRange(row + 1, 1, lastRow).offset(3, 0).moveTo(range1.offset(4, 0));
};
};
}
Formatting Update:
For guidance on formatting the output with font or background colors, check this follow-up question here. Hopefully you can benefit from the advice these pros gave me :)
Issue:
Usage of .getValue() and .setValue() in a loop resulting in increased processing time.
Documentation excerpts:
Minimize calls to services:
Anything you can accomplish within Google Apps Script itself will be much faster than making calls that need to fetch data from Google's servers or an external server, such as requests to Spreadsheets, Docs, Sites, Translate, UrlFetch, and so on.
Look ahead caching:
Google Apps Script already has some built-in optimization, such as using look-ahead caching to retrieve what a script is likely to get and write caching to save what is likely to be set.
Minimize "number" of read/writes:
You can write scripts to take maximum advantage of the built-in caching, by minimizing the number of reads and writes.
Avoid alternating read/write:
Alternating read and write commands is slow
Use arrays:
To speed up a script, read all data into an array with one command, perform any operations on the data in the array, and write the data out with one command.
Slow script example:
/**
* Really Slow script example
* Get values from A1:D2
* Set values to A3:D4
*/
function slowScriptLikeVBA(){
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
//get A1:D2 and set it 2 rows down
for(var row = 1; row <= 2; row++){
for(var col = 1; col <= 4; col++){
var sourceCellRange = sh.getRange(row, col, 1, 1);
var targetCellRange = sh.getRange(row + 2, col, 1, 1);
var sourceCellValue = sourceCellRange.getValue();//1 read call per loop
targetCellRange.setValue(sourceCellValue);//1 write call per loop
}
}
}
Notice that two calls are made per loop(Spreadsheet ss, Sheet sh and range calls are excluded. Only including the expensive get/set value calls). There are two loops; 8 read calls and 8 write calls are made in this example for a simple copy paste of 2x4 array.
In addition, Notice that read and write calls alternated making "look-ahead" caching ineffective.
Total calls to services: 16
Time taken: ~5+ seconds
Fast script example:
/**
* Fast script example
* Get values from A1:D2
* Set values to A3:D4
*/
function fastScript(){
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
//get A1:D2 and set it 2 rows down
var sourceRange = sh.getRange("A1:D2");
var targetRange = sh.getRange("A3:D4");
var sourceValues = sourceRange.getValues();//1 read call in total
//modify `sourceValues` if needed
//sourceValues looks like this two dimensional array:
//[//outer array containing rows array
// ["A1","B1","C1",D1], //row1(inner) array containing column element values
// ["A2","B2","C2",D2],
//]
//#see https://stackoverflow.com/questions/63720612
targetRange.setValues(sourceValues);//1 write call in total
}
Total calls to services: 2
Time taken: ~0.2 seconds
References:
Best practices
What does the range method getValues() return and setValues() accept?
Using methods like .getValue() and .moveTo() can be very expensive on execution time. An alternative approach is to use a batch operation where you get all the column values and iterate across the data reshaping as required before writing to the sheet in one call. When you run your script you may have noticed the following warning:
The script uses a method which is considered expensive. Each
invocation generates a time consuming call to a remote server. That
may have critical impact on the execution time of the script,
especially on large data. If performance is an issue for the script,
you should consider using another method, e.g. Range.getValues().
Using .getValues() and .setValues() your script can be rewritten as:
function format() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var lastRow = s.getLastRow(); // more efficient way to get last row
var row;
var data = s.getRange("A:A").getValues(); // gets a [][] of all values in the column
var output = []; // we are going to build a [][] to output result
//loop through all cells in column A
for (row = 0; row < lastRow; row++) {
var cellValue = data[row][0];
var dash = false;
if (typeof cellValue === 'string') {
dash = cellValue.substring(0, 1);
} else { // if a number copy to our output array
output.push([cellValue]);
}
// if a dash
if (dash === "-") {
var name = (data[(row+1)][0]+" "+data[(row+2)][0]).trim(); // build name
output.push([cellValue]); // add row -state
output.push([name]); // add row name
output.push(["Order complete"]); // row order complete
output.push([""]); // add blank row
row++; // jump an extra row to speed things up
}
}
s.clear(); // clear all existing data on sheet
// if you need other data in sheet then could
// s.deleteColumn(1);
// s.insertColumns(1);
// set the values we've made in our output [][] array
s.getRange(1, 1, output.length).setValues(output);
}
Testing your script with 20 rows of data revealed it took 4.415 seconds to execute, the above code completes in 0.019 seconds

Using JS to sum a column of values from an external text file containing donation histories of a db of donors

I need some assistance figuring out how to sum a column of dynamic totals that could be a positive or negative dollar amount, or an indication of stock shares.
I have a tab-delimited text file of donor contributions for that I am matching up against a CSV file of other related customer data that I am using to create a statement letter which will show a "donation history" of a particular donor. Each donor has a different amount of donations, and to complicate things, the column of data for a particular donation record could show either "$1,000.00" or "($1,000.00)" or "2 Shares APPL". The number with the parentheticals is of course, representing a negative number.
At the end of this column, I need to show a string that will read either "Total: $1,000.00," or if any of the donation history contains a donation record that included shares of stock the returned string will simply read, "$1,000.00 & Stock."
I have been racking my brain trying to come up with the JS rule that can achieve this. I have the JS rule that is generating the donation history correctly, but summing the donation amount column is causing me to go crazy...
Here is the JS for generating my donation history list in the letter (this seems to be working fine):
var contributionList = new ExternalDataFileEx("/~wip/248839 Frontiers/Master Data/Double Data proof.txt", "\t");
var donor_id = Field("Supporter");
var lb = "<br>\n";
var matches = new Array();
for (var i = 0; i <= contributionList.recordCount; i++) {
var idVariable = contributionList.GetFieldValue(i, "Supporter");
var dateVariable = contributionList.GetFieldValue(i, "Donation Date");
var ministryVariable = contributionList.GetFieldValue(i, "Ministry Designation");
var giftVariable = contributionList.GetFieldValue(i, "Donation Amount");
var tsSettings = "<p tabstops=19550,Right,,;29600,Left,,;>";
var ts = "<t>";
if (donor_id == idVariable)
matches.push(tsSettings + dateVariable + ts + giftVariable + ts + ministryVariable);
}
//return matches;
return matches.join(lb);
Now here is the JS code that is not working just fine. I am trying to tally the donation amount column, it only returns "Total: $0.00 & Stock" every time (I have tried to explain my thought process via comments):
var contributionList = new ExternalDataFileEx("/~wip/248839 Frontiers/Master Data/Double Data proof.txt", "\t");
var donor_id = Field("Supporter");
for (var i = 0; i <= contributionList.recordCount; i++) {
var idVariable = contributionList.GetFieldValue(i, "Supporter");
var giftVariable = contributionList.GetFieldValue(i, "Donation Amount");
var sum = 0;
var shares = 0;
var tsSettings = "<p tabstops=19550,Right,,;29600,Left,,;>";
var ts = "<t>";
var totalStr = "Total ";
var stockStr = " & Stock";
var totalFormatted = FormatNumber("$#,###.00", Math.max(0, StringToNumber(sum)));
// Match data from linked file to current Supporter
if (donor_id == idVariable) {
// Look at current record and see if it contains the word "Share(s)"
// or not and act accordingly
if (giftVariable.match(/(^|\W)share($|\W)/i) || giftVariable.match(/(^|\W)shares($|\W)/i)) {
// Turn switch "on" if donation amount is a share or shares so
// we can have the " & Stock" appended to our string.
shares = 1;
// Because this donation is/are shares, we must "zero" this
// amount to make the math work when we sum everything up...
giftVariable = 0;
// This is where we are keeping our running total...
sum += giftVariable[i];
} else {
// This record was not a donation of share(s) so we now have to
// determine whether we are dealing with postive or negative numbers
// and then strip out all of the non-number characters, remove and
// replace the () whis just a "-," leaving us with a number we can
// work with...
// If number has parenthesis, then deal with it...
if (giftVariable.indexOf("(")) {
// Strip out all the ()$, characters...
giftVariable = giftVariable.replace(/[()$,]/g,"")
// Append the minus sign to the number...
giftVariable = "-" + giftVariable;
sum += giftVariable[i];
} else {
giftVariable = giftVariable.replace(/[$,]/g,"");
sum += giftVariable[i];
}
}
}
}
// Return Total...
if (shares == 1) {
return tsSettings + totalStr + ts + totalFormatted + stockStr;
} else {
return tsSettings + totalStr + ts + totalFormatted;
}
Any assistance would be greatly appreciated!
The problem (and code) needs to be broken into smaller, atomic steps. From your description it sounds like you should:
load a text file into memory
for each line in the file
extract: {
donor_id
charity
gift
and store the results in a contributions dictionary
for each item in the contributions dictionary
transform gift string into {
dollarAmount: float with a default of 0.0
stock: name with a default of ""
}
create an empty dictionary called totals
each item will have the shape {
id
dollarAmount as a float
stocks an an array
}
for each item in the contributions dictionary
lookup the id in the totals dictionary
if it exists
totals[id].dolarAmount += item.dollarAmount
totals[id].stocks.push(item.stock)
otherwise
totals[id].dollarAmount = item.dollarAmount
totals[id].stocks = [item.stock]
normalize your charities
for each item in totals dictionary
remove any empty strings from item.charities
create your report
for each item in totals dictionary
write`${item.id} donated `${item.dollarAmont}` ${item.stocks.length > 1 ? 'and stock' : ''
I believe you are trying to do too many things at once. Instead, the goal should be to normalize your data before you attempt to perform any calculations or aggrgrations, then normalize your aggregrations before writing your summaries or reports.
I would also stay away from using any direct string manipulation. You should have a dedicated function whose only purpose is to take a string like "($20.34) and 1 share of APPL" and return either 20.34, -20.34, or 0.0. And a different function whose only purpose is to take the same string and return either true or false is stock was present.

How to extract a specific text from a string. The hard part is the desired text changes periodically

I have an HTML document which contains this text somewhere in it
function deleteFolder() {
var mailbox = "CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com";
var path = "/Inbox/";
//string of interest: "CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
I just want to extract this text and store it in a variable in C#. My problem is that string of interest will slightly change each time the page is loaded, something like this:
"CN=John Urban,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
"CN=Jane Doe,OU=Sect-1,DC=TestServer ,DC=acme,DC=com"
etc....
How do I extract that ever changing string, without regular expression?
Is it always a function deleteFolder() which has its first line as var mailbox = "somestring"? And you are interested in somestring?
Based on the requirements you told us, could just search your string containing the HTML for var mailbox =" and then the next " and take all text between these two occurrences.
var htmlstring= "..."; //
var i1 = htmlstring.IndexOf("var mailbox = \"");
var i2 = i1 >= 0 ? htmlstring.IndexOf("\"", i1+15) : -1;
var result = i2 >= 0 ? htmlstring.Substring(i1+15, i2-(i1+15)): "not found";
VERY, VERY ugly, not maintainable, but without more information, I can't do any better. However Regex would be much nicer!

Replace Picture Source (Error Handling)

Hi I have a script wich changes the source of an image ie webpics/1.jpg to webpics/2.jpg on click of an image. One problem i am having though is that the script will still work for one more image after there is images, so if i have 11 images i can press next 11 times and will get an empty image, what i would like is for the script to run a error check and stay on the current image if the next on doesn't exist. Here is the script:
$("#prev, #next").click(function() {
var currentNumber = parseInt($("#image1").attr("src").split('gallery/')[1]); // get the
number
var newNumber = ($(this).attr("id")=="next")?currentNumber+1:currentNumber-1;
var testImage = new Image();
testImage.onload=function() {
var img = $("#image1");
img.attr("src",this.src);
img.css("visibility","visible");
}
testImage.onerror=function() {
$("#image1").css("visibility","hidden");
}
testImage.src="http://www.yogahealth.net.au/gallery/"+newNumber+".jpg";
return false;
});
Do you know how many images exist? If so, just test if the new number would exceed the total count (if newNumber ...) and do nothing (i.e. skip the part where you change the testImage.src.
For example, you need to have the total count in the variable totalImages, then you could do:
$("#prev, #next").click(function() {
var currentNumber = parseInt($("#image1").attr("src").split('gallery/')[1]); // get the number
var newNumber = ($(this).attr("id")=="next")?currentNumber+1:currentNumber-1;
var totalImages = // get the number of total images here
if ((newNumber > totalImages) || (newNumber <= 0)) {
return false; // just do nothing
}
// here comes the rest of your code
});
Note that I also added the possibility that your number becomes less than 0 or 0, depending on whether you have an image named 0. If yes, you can change the <= to <, otherwise it won't show the 0 image.
In order to get this number into your Javascript code, you could either render the code using PHP and insert it into your script with <?php echo $total; ?> or you extract it from another element from the HTML page, as you did with the currentNumber.

Parse HTML source for dollar amounts then set highest amount as var

I need a JavaScript function that will parse the HTML source of the page from which it is called as an external script, retrieve any dollar amounts in the source, and set the highest dollar amount to a JavaScript variable.
So for instance, if the page contains the text, "Your product is $40.32 and tax is $4.50, your total is $44.82.", the JS should parse those values and set $44.82 to "var total" as the highest amount. Possible?
Thanks based on the tips I wrote this, which works. Hopefully yours or my solution will help others:
var dochtml = document.getElementsByTagName('body')[0].innerHTML;
dochtml = dochtml.replace(/(\r\n|\n|\r)/gm,"");
var price_array = new Array;
var pattmatch = /(\$(([0-9]{0,1})?.[0-9]{1,2}))|(\$([1-9]{1}[0-9]{0,2}([,][0-9]{3})*)(.[0-9]{1,2})?)/gi;
price_array = dochtml.match(pattmatch);
if (price_array) {
for (var i=0; itotal || !total) {
var total=price_array[i];
}
}
document.write(total);
}
You can grab the HTML of the current document from the Javascript by grabbing the document's innerHtml, something like:
document.getElementsByTagName('html')[0].innerHTML
Then you can pull out all the currency values with a regular expression, something like:
((\$(([0-9]{0,1})?\.[0-9]{1,2}))|(\$([1-9]{1}[0-9]{0,2}([,][0-9]{3})*)(\.[0-9]{1,2})?))
Just loop through all the matches and every time the current match is greater than the value in total, set total to the current match.
Disclaimer: That regex was pulled from the community on http://gskinner.com/RegExr/ and I can't promise you it's 100% fullproof.
Take a look at this question here, which demonstrates how to extract numbers from a String: Javascript extracting number from string
Try this:
// get all content from page
var content = document.body.innerHTML;
// create an array of all dollar amounts in the content
arrayNum = content.match(/\$[0-9]+\.[0-9]+/g);
// display array of numbers
console.info(arrayNum);
var high = 0;
for(var i = 0; i < arrayNum.length; i++) {
// remove the dollar sign and cast the string to a float
arrayNum[i] = parseFloat(arrayNum[i].substring(1));
// get the high value - O(n) operation
high = ( (arrayNum[i]) > high ) ? arrayNum[i] : high;
}
alert("High value = " high);

Categories