Added dynamic array functioanlity. Refactored tail so that it displays a dynamic popup with log contents

This commit is contained in:
Daniel Xie 2017-06-14 20:19:52 -05:00
parent 4aa7edb576
commit 6fe0ec1ea5
13 changed files with 236 additions and 138 deletions

@ -10,10 +10,11 @@
overflow: auto; /* Enable scroll if needed */ overflow: auto; /* Enable scroll if needed */
/*background-color: black; /* Fallback color */ /*background-color: black; /* Fallback color */
/*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ /*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
background-color: var(--my-background-color); background-color: rbga(var(--my-background-color), 0.4);
} }
.dialog-box-container { .dialog-box-container,
#log-box-container {
display: block; display: block;
position: absolute; position: absolute;
z-index: 2; z-index: 2;
@ -28,14 +29,15 @@
border: 5px solid var(--my-highlight-color); border: 5px solid var(--my-highlight-color);
} }
.dialog-box-content { .dialog-box-content,
#log-box-content {
z-index: 2; z-index: 2;
background-color: var(--my-background-color); background-color: var(--my-background-color);
padding: 10px; padding: 10px;
border: 5px solid var(--my-highlight-color);;
} }
.dialog-box-close-button { .dialog-box-close-button,
#log-box-close {
color: #aaa; color: #aaa;
float: right; float: right;
font-size: 20px; font-size: 20px;
@ -49,7 +51,9 @@
} }
.dialog-box-close-button:hover, .dialog-box-close-button:hover,
.dialog-box-close-button:focus { .dialog-box-close-button:focus,
#log-box-close:hover,
#log-box-close:focus {
color: white; color: white;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@ -61,7 +65,7 @@
} }
#purchase-server-box-content { #purchase-server-box-content {
background-color: background-color: var(--my-background-color); background-color: var(--my-background-color);
margin: 15% auto; /* 15% from the top and centered */ margin: 15% auto; /* 15% from the top and centered */
padding: 12px; padding: 12px;
border: 5px solid var(--my-highlight-color);; border: 5px solid var(--my-highlight-color);;

@ -25,6 +25,7 @@
<script src="utils/TravelBox.js"></script> <script src="utils/TravelBox.js"></script>
<script src="utils/PurchaseRamForHomeBox.js"></script> <script src="utils/PurchaseRamForHomeBox.js"></script>
<script src="utils/GameOptions.js"></script> <script src="utils/GameOptions.js"></script>
<script src="utils/LogBox.js"></script>
<!-- Netscript --> <!-- Netscript -->
<script src="src/NetscriptWorker.js"></script> <script src="src/NetscriptWorker.js"></script>
@ -645,6 +646,14 @@
<a id="location-slums-heist" class="a-link-button tooltip"> Heist </a> <a id="location-slums-heist" class="a-link-button tooltip"> Heist </a>
</div> </div>
<!-- Log Box -->
<div id="log-box-container">
<div id="log-box-content">
<span id="log-box-close"> &times; </span>
<p id="log-box-text"> </p>
</div>
</div>
<!-- Purchase Server Pop-up Box --> <!-- Purchase Server Pop-up Box -->
<div id="purchase-server-box-container" class="popup-box-container"> <div id="purchase-server-box-container" class="popup-box-container">
<div id="purchase-server-box-content"> <div id="purchase-server-box-content">

@ -78,7 +78,7 @@ CONSTANTS = {
AugmentationRepMultiplier: 1.5, //Used for balancing rep cost without having to readjust every value AugmentationRepMultiplier: 1.5, //Used for balancing rep cost without having to readjust every value
//Maximum number of log entries for a script //Maximum number of log entries for a script
MaxLogCapacity: 40, MaxLogCapacity: 50,
//How much a TOR router costs //How much a TOR router costs
TorRouterCost: 200000, TorRouterCost: 200000,
@ -153,6 +153,7 @@ CONSTANTS = {
HelpText: 'alias [name="value"] Create aliases for Terminal commands, or list existing aliases<br>' + HelpText: 'alias [name="value"] Create aliases for Terminal commands, or list existing aliases<br>' +
"analyze Get statistics and information about current machine <br>" + "analyze Get statistics and information about current machine <br>" +
"cat [message] Display a .msg file<br>" + "cat [message] Display a .msg file<br>" +
"check [script] Print script logs to Terminal<br>" +
"clear Clear all text on the terminal <br>" + "clear Clear all text on the terminal <br>" +
"cls See 'clear' command <br>" + "cls See 'clear' command <br>" +
"connect [ip/hostname] Connects to the machine given by its IP or hostname <br>" + "connect [ip/hostname] Connects to the machine given by its IP or hostname <br>" +
@ -576,7 +577,11 @@ CONSTANTS = {
"v0.21.0<br>" + "v0.21.0<br>" +
"-Added basic theme functionality - All credit goes to /u/0x726564646974 who implemented the awesome feature<br>" + "-Added basic theme functionality - All credit goes to /u/0x726564646974 who implemented the awesome feature<br>" +
"-Optimized Script objects, which were causing save errors when the player had too many scripts<br>" + "-Optimized Script objects, which were causing save errors when the player had too many scripts<br>" +
"-Fixed bug where you could purchase Darkweb items without TOR router" "-Fixed bug where you could purchase Darkweb items without TOR router<br>" +
"-Slightly increased cost multiplier for Home Computer RAM<br>" +
"-Changed tail command so that it brings up a display box with dynamic log contents. To get " +
"old functionality where the logs are printed to the Terminal, use the new 'check' command<br>" +
"-A script's logs now get cleared when the script is run<br>"+
"v0.20.2<br>" + "v0.20.2<br>" +
"-Fixed several small bugs<br>" + "-Fixed several small bugs<br>" +
"-Added basic array functionality to Netscript<br>" + "-Added basic array functionality to Netscript<br>" +

@ -40,25 +40,13 @@ function evaluate(exp, workerScript) {
} }
try { try {
var res = env.get(exp.value); var res = env.get(exp.value);
if (exp.index) { if (res.constructor === Array || res instanceof Array) {
//If theres an index field, then this variable is supposed to be an array var evalArrayPromise = netscriptArray(exp, workerScript);
//and the user needs to be indexing it evalArrayPromise.then(function(res) {
if (res.constructor === Array || res instanceof Array) { resolve(res);
var iPromise = evaluate(exp.index.value, workerScript); }, function(e) {
iPromise.then(function(i) { reject(e);
if (i >= res.length || i < 0) { });
return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator"));
} else {
return evaluate(res[i], workerScript);
}
}).then(function(res) {
resolve(res);
}).catch(function(e) {
reject(e);
});
} else {
reject(makeRuntimeRejectMsg(workerScript, "Trying to access a non-array variable using the [] operator"));
}
} else { } else {
resolve(res); resolve(res);
} }
@ -763,7 +751,7 @@ function evaluateHacknetNode(exp, workerScript) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
setTimeout(function() { setTimeout(function() {
if (exp.index == null) { if (exp.index == null) {
if ((exp.op.type == "call" && exp.op.value == "length") || if ((exp.op.type == "call" && exp.op.func.value == "length") ||
(exp.op.type == "var" && exp.op.value == "length")) { (exp.op.type == "var" && exp.op.value == "length")) {
resolve(Player.hacknetNodes.length); resolve(Player.hacknetNodes.length);
workerScript.scriptRef.log("hacknetnodes.length returned " + Player.hacknetNodes.length); workerScript.scriptRef.log("hacknetnodes.length returned " + Player.hacknetNodes.length);
@ -978,6 +966,7 @@ function runScriptFromScript(server, scriptname, workerScript, threads=1) {
script.numTimesHackMap = new AllServersMap(); script.numTimesHackMap = new AllServersMap();
script.numTimesGrowMap = new AllServersMap(); script.numTimesGrowMap = new AllServersMap();
script.numTimesWeakenMap = new AllServersMap(); script.numTimesWeakenMap = new AllServersMap();
script.logs = [];
addWorkerScript(script, server); addWorkerScript(script, server);
resolve(true); resolve(true);
return; return;
@ -1022,7 +1011,10 @@ function scriptCalculateHackingTime(server) {
//The same as Player's calculateExpGain() function but takes in the server as an argument //The same as Player's calculateExpGain() function but takes in the server as an argument
function scriptCalculateExpGain(server) { function scriptCalculateExpGain(server) {
return (server.hackDifficulty * Player.hacking_exp_mult * 0.9); if (server.baseDifficulty == null) {
server.baseDifficulty = server.hackDifficulty;
}
return (server.baseDifficulty * Player.hacking_exp_mult * 0.5 + 4);
} }
//The same as Player's calculatePercentMoneyHacked() function but takes in the server as an argument //The same as Player's calculatePercentMoneyHacked() function but takes in the server as an argument

@ -1,72 +1,76 @@
/* Netscript Functions /* Netscript Functions
* Implementation for Netscript features */ * Implementation for Netscript features */
/* function netscriptArray(exp, workerScript) {
function netscriptAssign(exp, workerScript) {
var env = workerScript.env; var env = workerScript.env;
var arr = env.get(exp.value);
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
if (env.stopFlag) {return reject(workerScript);} if (exp.index == null || exp.index == undefined) {
if ((exp.op.type == "call" && exp.op.func.value == "length") ||
if (exp.left.type != "var") { (exp.op.type == "var" && exp.op.value == "length")) {
return reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to " + JSON.stringify(exp.left))); return resolve(arr.length);
} } else if ((exp.op.type == "call" && exp.op.func.value == "clear") ||
(exp.op.type == "var" && exp.op.value == "clear")) {
//Assigning an element in an array arr.length = 0;
if (exp.left.index) { return resolve(true);
try { } else if (exp.op.type == "call" && exp.op.func.value == "push") {
var res = env.get(exp.left.value); if (exp.op.args.length == 1) {
if (res.constructor === Array || res instanceof Array) { var entry = Object.assign({}, exp.op.args[0]);
var i = 0; arr.push(entry);
var iPromise = evaluate(exp.left.index.value, workerScript); return resolve(true);
iPromise.then(function(idx) {
if (idx >= res.length || idx < 0) {
return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator"));
} else {
//TODO evaluate exp.right here....and then determine its type and
//set the type and value below accordingly
i = idx;
return evaluate(exp.right, workerScript);
}
}).then(function(right) {
console.log("evaluate right with result: " + right);
if (right === true || right === false) {
res[i].type = "bool";
res[i].value = right;
} else if (!isNaN(right) || typeof right == 'number') {
res[i].type = "num";
res[i].value = right;
} else { //String
res[i].type = "str";
res[i].value = right.toString();
}
console.log(res);
return resolve(true);
}).then(function(finalRes) {
resolve(finalRes);
}).catch(function(e) {
return reject(e);
});
} else { } else {
return reject(makeRuntimeRejectMsg(workerScript, "Trying to access a non-array variable using the [] operator")); return reject(makeRuntimeRejectMsg(workerScript, "Invalid number of arguments passed into array.push() command. Takes 1 argument"));
} }
} catch(e) { } else {
return reject(makeRuntimeRejectMsg(workerScript, e.toString())); return reject(makeRuntimeRejectMsg(workerScript, "Invalid operation on an array"));
} }
} else {
var expRightPromise = evaluate(exp.right, workerScript);
expRightPromise.then(function(expRight) {
try {
env.set(exp.left.value, expRight);
} catch (e) {
return reject(makeRuntimeRejectMsg(workerScript, "Failed to set environment variable: " + e.toString()));
}
resolve(false); //Return false so this doesnt cause conditionals to evaluate
}, function(e) {
reject(e);
});
} }
//The array is being indexed
var indexPromise = evaluate(exp.index.value, workerScript);
indexPromise.then(function(i) {
if (isNaN(i)) {
return reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx));
} else if (i >= arr.length || i < 0) {
return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator"));
} else {
if (exp.op && exp.op.type == "call") {
switch(exp.op.func.value) {
case "insert":
if (exp.op.args.length == 1) {
var entry = Object.assign({}, exp.op.args[0]);
arr.splice(i, 0, entry);
return resolve(arr.length);
} else {
return reject(makeRuntimeRejectMsg(workerScript, "Invalid number of arguments passed into array insert() call. Takes 1 argument"));
}
break;
case "remove":
if (exp.op.args.length == 0) {
return resolve(arr.splice(i, 1));
} else {
return reject(makeRuntimeRejectMsg(workerScript, "Invalid number of arguments passed into array remove() call. Takes 1 argument"));
}
break;
default:
return reject(makeRuntimeRejectMsg(workerScript, "Invalid call on array element: " + exp.op.func.value));
break;
}
} else {
//Return the indexed element
var resPromise = evaluate(arr[i], workerScript);
resPromise.then(function(res) {
resolve(res);
}, function(e) {
reject(e);
});
}
}
}, function(e) {
reject(e);
});
}); });
} }
*/
function netscriptAssign(exp, workerScript) { function netscriptAssign(exp, workerScript) {
var env = workerScript.env; var env = workerScript.env;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
@ -84,7 +88,9 @@ function netscriptAssign(exp, workerScript) {
var i = 0; var i = 0;
var iPromise = evaluate(exp.left.index.value, workerScript); var iPromise = evaluate(exp.left.index.value, workerScript);
iPromise.then(function(idx) { iPromise.then(function(idx) {
if (idx >= res.length || idx < 0) { if (isNaN(idx)) {
return reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx));
} else if (idx >= res.length || idx < 0) {
return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator")); return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator"));
} else { } else {
//Clone res to be exp.right //Clone res to be exp.right

@ -216,8 +216,8 @@ function Parser(input) {
unexpected(); unexpected();
} }
function parse_array() { //Declaring a new array with Array[1,2,3]
//Declaring a new array with Array[1,2,3] function parse_arraydecl() {
var array = delimited("[", "]", ",", parse_expression); var array = delimited("[", "]", ",", parse_expression);
return {type: "var", return {type: "var",
value: "array", value: "array",
@ -225,14 +225,38 @@ function Parser(input) {
}; };
} }
//Parsing an operation on an array, such as accessing, push(), etc.
//tok is a reference to a token of type var
function parse_arrayop(tok) {
//Returns a variable node except with an extra "index" field so
//we can identify it as an index
if (is_punc("[")) {
var index = parse_arrayindex();
if (index.type != "index") {
unexpected();
}
}
var op = null;
if (is_punc(".")) {
checkPuncAndSkip(".");
op = maybe_call(function() {
var callTok = input.next();
return callTok;
});
}
tok.index = index;
tok.op = op; //Will be null if no operation
return tok;
}
function parse_arrayindex() { function parse_arrayindex() {
var index = delimited("[", "]", ";", parse_expression); var index = delimited("[", "]", ";", parse_expression);
var val = 0; var val = 0;
if (index.length == 1 && (index[0].type == "num" || index[0].type == "var")) { if (index.length == 1) {
val = index[0]; val = index[0];
} else { } else {
val = 0; unexpected();
console.log("WARNING: Extra indices passed in")
} }
return { type: "index", value: val }; return { type: "index", value: val };
@ -267,16 +291,9 @@ function Parser(input) {
var tok = input.next(); var tok = input.next();
if (tok.type == "var" && tok.value == "hacknetnodes") return parse_hacknetnodes(); if (tok.type == "var" && tok.value == "hacknetnodes") return parse_hacknetnodes();
if (tok.type == "var" && tok.value == "Array") return parse_array(); if (tok.type == "var" && tok.value == "Array") return parse_arraydecl();
if (tok.type == "var" && is_punc("[")) { if (tok.type == "var" && (is_punc("[") || is_punc("."))) {
//Returns a variable node except with an extra "index" field so return parse_arrayop(tok);
//we can identify it as an index
var index = parse_arrayindex();
if (index.type != "index") {
unexpected();
}
tok.index = index;
return tok;
} }
if (tok.type == "var" || tok.type == "num" || tok.type == "str") if (tok.type == "var" || tok.type == "num" || tok.type == "str")
return tok; return tok;

@ -31,7 +31,7 @@ function runScriptsLoop() {
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) { if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
try { try {
var ast = Parser(Tokenizer(InputStream(workerScripts[i].code))); var ast = Parser(Tokenizer(InputStream(workerScripts[i].code)));
//console.log(ast); console.log(ast);
} catch (e) { } catch (e) {
console.log("Error parsing script: " + workerScripts[i].name); console.log("Error parsing script: " + workerScripts[i].name);
dialogBoxCreate("Syntax ERROR in " + workerScripts[i].name + ":<br>" + e); dialogBoxCreate("Syntax ERROR in " + workerScripts[i].name + ":<br>" + e);

@ -254,7 +254,11 @@ PlayerObject.prototype.calculatePercentMoneyHacked = function() {
//The formula is: //The formula is:
// difficulty * requiredLevel * hacking_multiplier // difficulty * requiredLevel * hacking_multiplier
PlayerObject.prototype.calculateExpGain = function() { PlayerObject.prototype.calculateExpGain = function() {
return (this.getCurrentServer().hackDifficulty * this.hacking_exp_mult * 0.9); var s = this.getCurrentServer();
if (s.baseDifficulty == null) {
s.baseDifficulty = s.hackDifficulty;
}
return (s.baseDifficulty * this.hacking_exp_mult * 0.5 + 4);
} }
//Hack/Analyze a server. Return the amount of time the hack will take. This lets the Terminal object know how long to disable itself for //Hack/Analyze a server. Return the amount of time the hack will take. This lets the Terminal object know how long to disable itself for

@ -616,6 +616,29 @@ var Terminal = {
} }
} }
post("Error: No such file " + filename); post("Error: No such file " + filename);
break;
case "check":
if (commandArray.length != 2) {
post("Incorrect number of arguments. Usage: check [script]");
} else {
var scriptName = commandArray[1];
//Can only check script files
if (scriptName.endsWith(".script") == false) {
post("Error: check can only be called on .script files (filename must end with .script)"); return;
}
//Check that the script exists on this machine
var currScripts = Player.getCurrentServer().scripts;
for (var i = 0; i < currScripts.length; ++i) {
if (scriptName == currScripts[i].filename) {
currScripts[i].displayLog();
return;
}
}
post("Error: No such script exists");
}
break; break;
case "clear": case "clear":
case "cls": case "cls":
@ -947,7 +970,7 @@ var Terminal = {
var currScripts = Player.getCurrentServer().scripts; var currScripts = Player.getCurrentServer().scripts;
for (var i = 0; i < currScripts.length; ++i) { for (var i = 0; i < currScripts.length; ++i) {
if (scriptName == currScripts[i].filename) { if (scriptName == currScripts[i].filename) {
currScripts[i].displayLog(); logBoxCreate(currScripts[i]);
return; return;
} }
} }
@ -959,7 +982,8 @@ var Terminal = {
//todo support theme saving //todo support theme saving
var args = commandArray[1] ? commandArray[1].split(" ") : []; var args = commandArray[1] ? commandArray[1].split(" ") : [];
if(args.length != 1 && args.length != 3) { if(args.length != 1 && args.length != 3) {
post("Incorrect number of arguments. Usage: theme [default|muted|solarized] | [background color hex] [text color hex] [highlight color hex]"); post("Incorrect number of arguments.");
post("Usage: theme [default|muted|solarized] | [background color hex] [text color hex] [highlight color hex]");
}else if(args.length == 1){ }else if(args.length == 1){
var themeName = args[0]; var themeName = args[0];
if(themeName == "default"){ if(themeName == "default"){
@ -1244,6 +1268,9 @@ var Terminal = {
script.numTimesGrowMap = new AllServersMap(); script.numTimesGrowMap = new AllServersMap();
script.numTimesWeakenMap = new AllServersMap(); script.numTimesWeakenMap = new AllServersMap();
//Clear logs
script.logs = [];
addWorkerScript(script, server); addWorkerScript(script, server);
return; return;
} }

@ -545,6 +545,10 @@ var Engine = {
displayCreateProgramContent(); displayCreateProgramContent();
} }
if (logBoxOpened) {
logBoxUpdateText();
}
Engine.Counters.updateDisplays = 3; Engine.Counters.updateDisplays = 3;
} }

@ -2,22 +2,6 @@
dialogBoxes = []; dialogBoxes = [];
//Close dialog box when clicking outside //Close dialog box when clicking outside
/*
$(document).click(function(event) {
if (dialogBoxOpened) {
if (!$(event.target).closest('.dialog-box-container').length){
--dialogBoxCount;
dialogBoxes.splice(0, 1);
$(".dialog-box-container").remove();
if (dialogBoxes.length == 0) {
dialogBoxOpened = false;
} else {
dialogBoxes[0].style.display +
}
}
}
});*/
$(document).click(function(event) { $(document).click(function(event) {
if (dialogBoxOpened && dialogBoxes.length >= 1) { if (dialogBoxOpened && dialogBoxes.length >= 1) {
if (!$(event.target).closest(dialogBoxes[0]).length){ if (!$(event.target).closest(dialogBoxes[0]).length){
@ -34,18 +18,6 @@ $(document).click(function(event) {
//Dialog box close buttons //Dialog box close buttons
/*
$(document).on('click', '.dialog-box-close-button', function( event ) {
console.log("clicked close button");
if (dialogBoxOpened) {
$(this).closest('.dialog-box-container').remove();
--dialogBoxCount;
if (dialogBoxes.length == 0) {
dialogBoxOpened = false;
}
}
});
*/
$(document).on('click', '.dialog-box-close-button', function( event ) { $(document).on('click', '.dialog-box-close-button', function( event ) {
if (dialogBoxOpened && dialogBoxes.length >= 1) { if (dialogBoxOpened && dialogBoxes.length >= 1) {
dialogBoxes[0].remove(); dialogBoxes[0].remove();

58
utils/LogBox.js Normal file

@ -0,0 +1,58 @@
/* Log Box */
//Close box when clicking outside
$(document).click(function(event) {
if (logBoxOpened) {
if ( $(event.target).closest("#log-box-container").get(0) == null ) {
logBoxClose();
}
}
});
function logBoxInit() {
var closeButton = document.getElementById("log-box-close");
logBoxClose();
//Close Dialog box
closeButton.addEventListener("click", function() {
logBoxClose();
return false;
});
};
document.addEventListener("DOMContentLoaded", logBoxInit, false);
logBoxClose = function() {
logBoxOpened = false;
var logBox = document.getElementById("log-box-container");
logBox.style.display = "none";
}
logBoxOpen = function() {
logBoxOpened = true;
var logBox = document.getElementById("log-box-container");
logBox.style.display = "block";
}
var logBoxOpened = false;
var logBoxCurrentScript = null;
//ram argument is in GB
logBoxCreate = function(script) {
logBoxCurrentScript = script;
logBoxOpen();
logBoxUpdateText();
}
logBoxUpdateText = function() {
var txt = document.getElementById("log-box-text");
if (logBoxCurrentScript && logBoxOpened && txt) {
txt.innerHTML = logBoxCurrentScript.filename + ":<br>";
for (var i = 0; i < logBoxCurrentScript.logs.length; ++i) {
txt.innerHTML += logBoxCurrentScript.logs[i];
txt.innerHTML += "<br>";
}
}
}

@ -36,7 +36,7 @@ purchaseRamForHomeBoxCreate = function() {
//Calculate cost //Calculate cost
//Have cost increase by some percentage each time RAM has been upgraded //Have cost increase by some percentage each time RAM has been upgraded
var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome; var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome;
var mult = Math.pow(1.40, numUpgrades); var mult = Math.pow(1.43, numUpgrades);
cost = cost * mult; cost = cost * mult;
purchaseRamForHomeBoxSetText("Would you like to purchase additional RAM for your home computer? <br><br>" + purchaseRamForHomeBoxSetText("Would you like to purchase additional RAM for your home computer? <br><br>" +