Trying to replace random array characters within an array - javascript

This is my first ever question, please give me feedback if I suck at this. Thank you
I am trying to create a JavaScript terminal game from a paid course. The game is called 'Find My Hat'.
One section of the problem requires me to create a static method called generateField(). This method generates a field, on the field there are 4 possible characters:
hat = '^';
hole = 'O';
fieldCharacter = '░';
pathCharacter = '*';
The fieldCharacter is the background for the game area. The game area is comprised of nested arrays. I haven't had problems creating the field, but I also want to replace random fieldCharacters with holes. I tried this with Math.random and nested loops to iterate over the nested arrays. I have not been able to figure out how to get this to work.
Here is the code below, sorry if I missed any details, I will try respond to everyone I can.
class Field {
constructor(field) {
this._field = field;
}
print() {
this._field = field.join('\n');
console.log(this._field.replace(/,/g, ''))
}
static generateField(height, width) {
let finalArray = []
for (let i = 0; i < height; i++) {
finalArray.push([fieldCharacter.repeat(width)]);
}
for (let i = 0; i < finalArray.length; i++) {
let randomHole = () => {
let result = Math.random() * width;
return result;
}
for (let j = 0; j < finalArray[i].length; j++) {
if (randomHole() > width - 2) {
finalArray[i][j] = hole;
}
}
}
return finalArray.join('\n').replace(/,/g, '');
}
}
console.log(Field.generateField(5, 10));
One Random Output:
O
O
░░░░░░░░░░
░░░░░░░░░░
O
Example for good output:
░░░░O░░░░░
░░░O░OO░░░
░░O░░░O░░░
░░░░O░░░░░
OO░░░░O░░O

I did some changes to your code and use the stackoverflow snippet to show you how it works
String.prototype.replaceAt = function(index, replacement) {
return this.substr(0, index) + replacement + this.substr(index + replacement.length);
}
hat = '^';
hole = 'O';
fieldCharacter = '░';
pathCharacter = '*';
class Field {
constructor(field) {
this._field = field;
}
print() {
this._field = field.join('\n');
console.log(this._field.replace(/,/g, ''))
}
static generateField(height, width) {
let finalArray = []
for (let i = 0; i < height; i++) {
finalArray.push(fieldCharacter.repeat(width));
}
//i dont know why you did this loop but i changed the inside for marking random holes
for (let i = 0; i < finalArray.length; i++) {
//pick random x and y
let randomHoleX = Math.floor(Math.random() * width);
let randomHoleY = Math.floor(Math.random() * height);
//becouse you save array of strings you need to find the correct array and replace the char at the correct index.
finalArray[randomHoleY] = finalArray[randomHoleY].replaceAt(randomHoleX, hole);
}
return finalArray.join('\n').replace(/,/g, '');
}
}
console.log(Field.generateField(5, 10));

Related

Array not being updated after switching indexs

I'm working on a visualizer for sorting algorithms. Everything is working as intended until I got to the Selection Sort. I understand that the Selection sort will make a pass and search for the MINIMUM value and then swap that the index that it started at in the array. However, each time it makes a pass, the i value doesn't change. I tested it by changing the color of the block the i index represents in my loop and it never changes, so the MINIMUM value just keeps switching to where ever the i is.
You can view my project here on GitHub Pages, just use the left Navbar to choose Selection Sort and you can see the problem I'm having. The bottom snippet is my swap function, it didn't do this with any of the other sort methods, only the selection sort.
Github Pages -- https://kevin6767.github.io/sorting-algorithm-visualization/
Selection function
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
for (let i = 0; i < blocks.length; i++) {
// Assume a minimum value
let min = i;
for (let j = i + 1; j < blocks.length; j++) {
blocks[j].style.backgroundColor = '#FF4949';
blocks[min].style.backgroundColor = '#13CE66';
blocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
const value1 = Number(blocks[j].childNodes[0].innerHTML);
const value2 = Number(blocks[min].childNodes[0].innerHTML);
if (value1 < value2) {
blocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
blocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = blocks[i];
blocks[i] = blocks[min];
blocks[min] = tmp;
await swap(blocks[i], blocks[min]);
blocks = document.querySelectorAll('.block');
}
// Swap if new minimun value found
blocks[i].style.backgroundColor = '#58B7FF';
}
}
Swap function
function swap(el1, el2) {
return new Promise((resolve) => {
const style1 = window.getComputedStyle(el1);
const style2 = window.getComputedStyle(el2);
const transform1 = style1.getPropertyValue('transform');
const transform2 = style2.getPropertyValue('transform');
el1.style.transform = transform2;
el2.style.transform = transform1;
// Wait for the transition to end!
window.requestAnimationFrame(function () {
setTimeout(() => {
container.insertBefore(el2, el1);
resolve();
}, 300);
});
});
}
I ended up fixing it. It seems that I had to take the Nodelist array I was getting from just .querySelectorAll and convert that into an array using .Arrayfrom() which was pretty simple after some googling. From then on I needed to figure out how to update the array each pass, which once again was as simple as just moving one index from another.
The interesting part of the answer was how I was going to update the Nodelist itself that way all my css code would still work (This is a sorting visualizer, so it would show you what element it was on and highlight it with a color). The answer however was right in front of me. Even though I turned the Nodelist array into a regular array, I was still able to apply styles to it. This meant I didn't have to mutate the Nodelist array at all and was just able to keep a seperate array within the function to work with.
PS. The algorithm did have a lot of trouble in the above snippet because I was comparing 2 strings in the if statement (value1 and value2) this is what caused a lot of the actual algorithm erroring and was simply fixed by adding a Number() function around my innerhtml code.
Selection
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
let convertedBlocks = Array.from(blocks);
let len = convertedBlocks.length;
for (let i = 0; i < len; i++) {
let min = i;
for (let j = i + 1; j < len; j++) {
convertedBlocks[j].style.backgroundColor = 'red';
convertedBlocks[min].style.backgroundColor = 'green';
convertedBlocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
if (
Number(convertedBlocks[min].childNodes[0].innerHTML) >
Number(convertedBlocks[j].childNodes[0].innerHTML)
) {
convertedBlocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
convertedBlocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = convertedBlocks[i];
convertedBlocks[i] = convertedBlocks[min];
convertedBlocks[min] = tmp;
await swap(convertedBlocks[i], convertedBlocks[min]);
}
convertedBlocks[min].style.backgroundColor = '#58B7FF';
convertedBlocks[i].style.backgroundColor = '#58B7FF';
}
}

updatePixels() not actually updating the pixels

I'm trying to make a program that takes a string, converts it to base64, and then to binary. It then takes the binary and changes the pixels a black pixel for 0 and a white pixel for 1.
I've gotten the pixel array to change to what I want, but it's not actually changing when I call updatePixels().
My goal is to then take the canvas and export it as an image.
My sketch:
let hw;
let input, button;
let binaryOut;
function setup() {
createCanvas(140,140);
input=createInput();
pixelDensity(1);
button = createButton("get image");
button.mousePressed(txtTo64ToBin)
loadPixels();
}
function txtTo64ToBin(){
str = input.value();
str = btoa(str);
let output = '';
str = str.split("")
for(let i=0;i<str.length;i++){
let base = str[i].charCodeAt(0).toString(2)
while(base.length < 8){
base = "0"+base;
}
output += base;
}
binaryOut = output;
console.log(binaryOut)
updateImage(binaryOut.split(''))
}
function updateImage(binArr){
hw = factors(binArr.length);
hw = hw[hw.length-1];
console.log(hw);
resizeCanvas(...hw,false)
pixels = []
for(let i=0; i<binArr.length; i++){
pixels[i*4] = map(binArr[i],0,1,0,255);
pixels[i*4+1] = map(binArr[i],0,1,0,255);
pixels[i*4+2] = map(binArr[i],0,1,0,255);
pixels[i*4+3] = 255;
}
console.log(pixels)
updatePixels() //here is the updatePixels function call
}
function draw() {
noLoop();
}
function factors(num) {
var half = Math.floor(num / 2),
arr = [],
i, j;
num % 2 === 0 ? (i = 2, j = 1) : (i = 3, j = 2);
for (i; i <= half; i += j) {
if(num % i === 0 && i <= num/i){
arr.push([i,num/i]);
}
}
return arr;
}
I'm very confused and any help would be much appreciated.
Please try to break your problem down into smaller steps and isolate the problem in a smaller example.
Here is an example sketch that shows the same problem:
let button;
function setup() {
createCanvas(140,140);
button = createButton("test");
button.mousePressed(updateImage);
loadPixels();
}
function updateImage(){
pixels = [];
for(let i=0; i < width * height; i++){
pixels[i*4] = 255;
pixels[i*4+1] = 0;
pixels[i*4+2] = 0;
pixels[i*4+3] = 255;
}
updatePixels();
}
function draw() {
noLoop();
}
We might expect this to turn the canvas red when we click the button, but it does not. See how this example is easier to play with, because we don't have to think about any of your logic?
Anyway, the problem is caused by this line:
pixels = [];
Take that line out, and the example program works.
My guess is this is because pixels is not a standard JavaScript array. From the reference:
Uint8ClampedArray containing the values for all the pixels in the display window.
...
Note that this is not a standard javascript array.

JS - Create smart auto complete

Given a sorted Array of Strings, and user input I need to return the most relevant result.
Example: Array =['Apple','Banana and Melon','Orange'] and user input = 'Mellllon' the returned value should be 'Banana and Melon'
I'm looking for the right algorithm to implement an efficient auto complete solution, and not an out of the box one.
Levenshtein distance seems to be right for this problem. You need to calculate distance between every word in array, check it out
function findClosestString(arr, inputvalue) {
let closestOne = "";
let floorDistance = 0.1;
for (let i = 0; i < arr.length; i++) {
let dist = distance(arr[i], inputvalue);
if (dist > floorDistance) {
floorDistance = dist;
closestOne = arr[i];
}
}
return closestOne;
}
function distance(val1, val2) {
let longer, shorter, longerlth, result;
if (val1.length > val2.length) {
longer = val1;
shorter = val2;
} else {
longer = val2;
shorter = val1;
}
longerlth = longer.length;
result = ((longerlth - editDistance(longer, shorter)) / parseFloat(longerlth));
return result;
}
function editDistance(val1, val2) {
val1 = val1.toLowerCase();
val2 = val2.toLowerCase();
let costs = [];
for(let i = 0; i <= val1.length; i++) {
let lastVal = i;
for(let j = 0; j <= val2.length; j++) {
if (i === 0) {
costs[j] = j;
} else if (j > 0) {
let newVal = costs[j - 1];
if (val1.charAt(i - 1) !== val2.charAt(j - 1)) {
newVal = Math.min(Math.min(newVal, lastVal), costs[j]) + 1;
}
costs[j - 1] = lastVal;
lastVal = newVal;
}
}
if (i > 0) { costs[val2.length] = lastVal }
}
return costs[val2.length];
}
findClosestString(['Apple','Banana and Melon','Orange'], 'Mellllon');
One possible solution is to:
1) convert every value into some simple code (using the same simple rules e.g. convert uppercase char to lowercase, if a char is the same as the previews, it won't get written and so on..) so you have ['aple','banana and melon', 'orange']
2) then you convert the user input , Mellllon => melon
3) now you can simple run
return match_array.filter((x) => {
x.indexOf(match_input)!=-1)
);
Well, as elaborately explained in the topic cited in my comment a fuzzy search regex might come handy provided you have all letters of the searched string (case insensitive "m", "e", "l", "o", "n") are present in the input string in the order of appearance. So according to the generated /M[^e]*e[^l]*l[^o]*o[^n]*n/i regexp from "Melon", "Maellion", "MElllloon" or "nMelrNnon" should all return true.
function fuzzyMatch(s,p){
p = p.split("").reduce((a,b) => a+'[^'+b+']*'+b);
return RegExp(p,"i").test(s);
}
var arr = ['Apple','Banana and Melon','Orange'],
inp = "MaellL;loin",
res = arr.filter(s => s.split(" ").some(w => fuzzyMatch(inp,w)));
console.log(res);
Combining the fuzzyMatch function with a trie type data structure you might in fact obtain quite reasonable elastic auto complete functionality.

Perform a merge on two strings

I'm trying to build a collaborative doc editor and implement operational transformation. Imagine we have a string that is manipulated simultaneously by 2 users. They can only add characters, not remove them. We want to incorporate both of their changes.
The original string is: catspider
The first user does this: cat<span id>spider</span>
The second user does this: c<span id>atspi</span>der
I'm trying to write a function that will produce: c<span id>at<span id>spi</span>der</span>
The function I've written is close, but it produces c<span id>at<span i</span>d>spider</span> codepen here
String.prototype.splice = function(start, newSubStr) {
return this.slice(0, start) + newSubStr + this.slice(start);
};
function merge(saved, working, requested) {
if (!saved || !working || !requested) {
return false;
}
var diffSavedWorking = createDiff(working, saved);
var diffRequestedWorking = createDiff(working, requested);
var newStr = working;
for (var i = 0; i < Math.max(diffRequestedWorking.length, diffSavedWorking.length); i++) {
//splice does an insert `before` -- we will assume that the saved document characters
//should always appear before the requested document characters in this merger operation
//so we first insert requested and then saved, which means that the final string will have the
//original characters first.
if (diffRequestedWorking[i]) {
newStr = newStr.splice(i, diffRequestedWorking[i]);
//we need to update the merge arrays by the number of
//inserted characters.
var length = diffRequestedWorking[i].length;
insertNatX(diffSavedWorking, length, i + 1);
insertNatX(diffRequestedWorking, length, i + 1);
}
if (diffSavedWorking[i]) {
newStr = newStr.splice(i, diffSavedWorking[i]);
//we need to update the merge arrays by the number of
//inserted characters.
var length = diffSavedWorking[i].length;
insertNatX(diffSavedWorking, length, i + 1);
insertNatX(diffRequestedWorking, length, i + 1);
}
}
return newStr;
}
//arr1 should be the shorter array.
//returns inserted characters at their
//insertion index.
function createDiff(arr1, arr2) {
var diff = [];
var j = 0;
for (var i = 0; i < arr1.length; i++) {
diff[i] = "";
while (arr2[j] !== arr1[i]) {
diff[i] += arr2[j];
j++;
}
j++;
}
var remainder = arr2.substr(j);
if (remainder) diff[i] = remainder;
return diff;
}
function insertNatX(arr, length, pos) {
for (var j = 0; j < length; j++) {
arr.splice(pos, 0, "");
}
}
var saved = 'cat<span id>spider</span>';
var working = 'catspider';
var requested = 'c<span id>atspi</span>der';
console.log(merge(saved, working, requested));
Would appreciate any thoughts on a better / simpler way to achieve this.

Javascript: Random number out of 5, no repeat until all have been used

I am using the below code to assign a random class (out of five) to each individual image on my page.
$(this).addClass('color-' + (Math.floor(Math.random() * 5) + 1));
It's working great but I want to make it so that there are never two of the same class in a row.
Even better would be if there were never two of the same in a row, and it also did not use any class more than once until all 5 had been used... As in, remove each used class from the array until all of them have been used, then start again, not allowing the last of the previous 5 and the first of the next 5 to be the same color.
Hope that makes sense, and thanks in advance for any help.
You need to create an array of the possible values and each time you retrieve a random index from the array to use one of the values, you remove it from the array.
Here's a general purpose random function that will not repeat until all values have been used. You can call this and then just add this index onto the end of your class name.
var uniqueRandoms = [];
var numRandoms = 5;
function makeUniqueRandom() {
// refill the array if needed
if (!uniqueRandoms.length) {
for (var i = 0; i < numRandoms; i++) {
uniqueRandoms.push(i);
}
}
var index = Math.floor(Math.random() * uniqueRandoms.length);
var val = uniqueRandoms[index];
// now remove that value from the array
uniqueRandoms.splice(index, 1);
return val;
}
Working demo: http://jsfiddle.net/jfriend00/H9bLH/
So, your code would just be this:
$(this).addClass('color-' + (makeUniqueRandom() + 1));
Here's an object oriented form that will allow more than one of these to be used in different places in your app:
// if only one argument is passed, it will assume that is the high
// limit and the low limit will be set to zero
// so you can use either r = new randomeGenerator(9);
// or r = new randomGenerator(0, 9);
function randomGenerator(low, high) {
if (arguments.length < 2) {
high = low;
low = 0;
}
this.low = low;
this.high = high;
this.reset();
}
randomGenerator.prototype = {
reset: function() {
this.remaining = [];
for (var i = this.low; i <= this.high; i++) {
this.remaining.push(i);
}
},
get: function() {
if (!this.remaining.length) {
this.reset();
}
var index = Math.floor(Math.random() * this.remaining.length);
var val = this.remaining[index];
this.remaining.splice(index, 1);
return val;
}
}
Sample Usage:
var r = new randomGenerator(1, 9);
var rand1 = r.get();
var rand2 = r.get();
Working demo: http://jsfiddle.net/jfriend00/q36Lk4hk/
You can do something like this using an array and the splice method:
var classes = ["color-1", "color-2", "color-3", "color-4", "color-5"];
for(i = 0;i < 5; i++){
var randomPosition = Math.floor(Math.random() * classes.length);
var selected = classes.splice(randomPosition,1);
console.log(selected);
alert(selected);
}
var used = [];
var range = [0, 5];
var generateColors = (function() {
var current;
for ( var i = range[0]; i < range[5]; i++ ) {
while ( used.indexOf(current = (Math.floor(Math.random() * 5) + 1)) != -1 ) ;
used.push(current);
$(" SELECTOR ").addClass('color-' + current);
}
});
Just to explain my comment to jfriend00's excellent answer, you can have a function that returns the members of a set in random order until all have been returned, then starts again, e.g.:
function RandomList(list) {
var original = list;
this.getOriginal = function() {
return original;
}
}
RandomList.prototype.getRandom = function() {
if (!(this.remainder && this.remainder.length)) {
this.remainder = this.getOriginal().slice();
}
return this.remainder.splice(Math.random() * this.remainder.length | 0,1);
}
var list = new RandomList([1,2,3]);
list.getRandom(); // returns a random member of list without repeating until all
// members have been returned.
If the list can be hard coded, you can keep the original in a closure, e.g.
var randomItem = (function() {
var original = [1,2,3];
var remainder;
return function() {
if (!(remainder && remainder.length)) {
remainder = original.slice();
}
return remainder.splice(Math.random() * remainder.length | 0, 1);
};
}());

Categories

Resources