I am trying to create a visual selection sort. finMin() will go through the array one by one displaying the new min when found. I want to use this function in a loop for selection sort. If the function is run one time, then everything is fine, but if findMin() is run in a loop, then the function has bugs.
If a function is run in a loop such as for(let i=0; i<3; i++){findMin();} does the second iteration of the loop run immediately or does it wait for findMin to return before i == 1? I believe that this loop should be sequential, but I do not know why the code does not work in a loop then.
var gBars = [];
var gSelected = 19;
var gFinished = 19;
var changed = false;
var step = 0;
function Bar(index, height){
this.index = index;
this.height = height;
this.getIndex = function(){
console.log(this.index);
};
this.getHeight = function(){
console.log(this.height);
};
this.getStats = function(){
console.log(this.index + ' ' + this.height);
}
this.setHeight = function(h){
this.height = h;
}
this.setIndex = function(i){
this.index = i;
}
}
function insertAfter(newNode, referenceNode){
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function setHeight(i, h){
document.getElementById(i).style.height = h + 'em';
}
function addBar(i, h){
//base case i = 0
//first bar
if(i === 0){
var currentDiv = document.getElementById("root");
d = document.createElement('div');
d.setAttribute("id", 'block'+i);
d.setAttribute("class", 'block');
gBars[i] = new Bar(i, h);
currentDiv.appendChild(d);
setHeight('block'+i,h);
}
else {
let last = i-1;
var currentDiv = document.getElementById('block'+last);
d = document.createElement('div');
d.setAttribute("id", 'block'+i);
d.setAttribute("class", 'block');
gBars[i] = new Bar(i, h);
insertAfter(d, currentDiv);
setHeight('block'+i,h);
}
}
function selSort(){
for(let i=0; i<10; i++){
findMin(gFinished);
}
}
function findMin(gFinished) {
let min = gBars[gFinished].height;
//start at 18 because bars are rotated 180deg
//go backwards so it appears to go forwards
var delay = 500;
let i = gFinished - 1;
min = setTimeout(timeout(i, min), delay);
return min;
}
function timeoutchange(){
var swapped = document.getElementById('block'+gFinished);
var selected = document.getElementById('block'+gSelected);
let temp = gBars[gFinished].height;
swapped.style.height = gBars[gSelected].height + 'em';
selected.style.height = temp + 'em';
selected.style.backgroundColor = "grey";
var selected = document.getElementById('block'+gFinished);
selected.style.backgroundColor = "green";
gFinished--;
var selected = document.getElementById('block'+gFinished);
selected.style.backgroundColor = "blue";
gSelected = gFinished;
}
function timeout(i, min) {
console.log("Next loop: " + i);
if(i==18){
var selected = document.getElementById('block19');
selected.style.backgroundColor = "blue";
}
if(min > gBars[i].height) {
min = gBars[i].height;
var selected = document.getElementById('block'+i);
selected.style.backgroundColor = "blue";
console.log('new min ' + min);
selected = document.getElementById('block'+gSelected);
selected.style.backgroundColor = "grey";
gSelected = i;
}
i--;
if (i == 0) {
console.log("End");
var swapped = document.getElementById('block'+gFinished);
swapped.style.backgroundColor = "red";
setTimeout(function(){
return timeoutchange();
},1000)
step++;
return min;
} else {
setTimeout(function(){
return timeout(i, min);
},500)
}
}
function init(){
for(let i=0; i<20; i++){
let ran = Math.floor(Math.random() * 50 + 1);
gBars[i] = new Bar(i,ran);
addBar(i,ran);
}
for(let i=0; i<20; i++){
gBars[i].getStats();
}
//works
findMin(gFinished);
//findMin does not work in loop
//why?
//selSort();
return;
}
init();
.selected{
background-color:blue;
}
.block{
border:1px solid rgba(0,0,0,.4);
width:20px;
background-color:grey;
}
#root{
display:flex;
transform:rotate(180deg);
position:absolute;
left:10%;
}
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<button>sort</button>
<div id="root"></div>
</body>
<script src="selectionsort.js"></script>
</html>
What you want to do is use JavaScript Promises. (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) There you have a concept called chaining so that you can chain your functions one after the other based on execution (in this case resolved). For an example Let say you have to functions:
function a() {
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
}
function b() {
setTimeout( function() {
resolve("Success 2!") // Yay! Everything went well!
}, 250)
}
you can make these promises and chain them one after the other:
let a = new Promise((resolve, reject) => {
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
let b = new Promise((resolve, reject) => {
setTimeout( function() {
resolve("Success2!") // Yay! Everything went well!
}, 250)
})
let c = new Promise((resolve, reject) => {
setTimeout( function() {
resolve("Success3!") // Yay! Everything went well!
}, 250)
})
a().then(()=>{
return b();
}).then(()=>{
return c();
});
setTimeout returns a number representing the id of the timer, so when running findMin() in a loop it will return that, and immediately after execute the next iteration.
To make the loop wait for the timeout you'll have to await for a promise that is resolved after the delay
for (let i = 0; i < 3; i++) {
min = await new Promise((resolve) => {
setTimeout(() => {
resolve(timeout(i, min))
}, 500);
})
}
Related
I have had trouble trying to get my function to finish before calling it again. This is my code now:
function TypeAnimation(str, time)
{
var text = document.getElementById('loading');
for (let i = 0; i < str.length; i++) {
let k = i;
setTimeout(function(){
text.innerHTML += str.charAt(i);
}, time * (k + 1));
}
}
function StartGame()
{
TypeAnimation('first line of text', 50)
TypeAnimation('next line of text but slower', 100)
}
window.onload = StartGame;
When doing this I get the output as finrset xlitne olf itenxte of text but slower.
I have tried promises by using the following code:
function TypeAnimation(str, time)
{
return new Promise((resolve) => {
var text = document.getElementById('loading');
for (let i = 0; i < str.length; i++) {
let k = i;
setTimeout(function(){
text.innerHTML += str.charAt(i);
}, time * (k + 1));
}
resolve();
});
}
function StartGame()
{
TypeAnimation('first line of text', 50).then(TypeAnimation('next line of text but slower', 100))
}
window.onload = StartGame;
But when I do this I still get the same result
On top of this, I also tried the same thing but with with a different StartGame()
async function StartGame()
{
await TypeAnimation('first line of text', 50)
TypeAnimation('next line of text but slower', 100)
}
Once again, the same issue arises and I get the same output
If it helps, this is all created with an electron js app.
Your code doesn't work, because you resolve the promise before timeouts even happen
The approach like this will make it easier
async function TypeAnimation(str, time) {
const text = document.getElementById('loading');
for (let i = 0; i < str.length; i++) {
text.innerHTML += str.charAt(i);
await wait(time)
}
}
async function StartGame() {
await TypeAnimation('first line of text', 50)
await TypeAnimation('next line of text but slower', 100)
}
StartGame()
function wait(time) {
return new Promise(r => setTimeout(r, time))
}
<span id="loading"></span>
I have been working on this project for a while now and I would appreciate it if you guys could help me out.
The quick sort algorithm works but since it uses recursion I don't know how or where I should rearrange the HTML divs created.
I have tried rearranging the divs after each swap function is called but that does not seem to work any ideas on how I might solve this problem?
class Element{
constructor(value , id){
this.value = value;
this.id = id;
}
}
function generateElemenets(){
for(let i = 0 ; i < 10; i++ ){
let randomValue = generateRandomValue();
values[i] = new Element(randomValue , i);
const newDiv = document.createElement("div");
newDiv.classList.add("element");
newDiv.setAttribute("id", i);
container.appendChild(newDiv);
document.getElementById(i).style.height = `${randomValue}px`;
}
}
function generateRandomValue(){
let x = Math.floor((Math.random() * 100) + 20);
return x;
}
function quickSort(arr, start, end){
if(start >= end){
return;
}
let index = partition(arr,start, end);
quickSort(arr, start, index-1);
quickSort(arr, index + 1, end);
return arr;
}
function partition(arr, start, end){
let pivotIndex = start;
let pivotValue = arr[end].value;
for (let i = start; i < end ; i++){
if(arr[i].value < pivotValue){
swap(arr, i, pivotIndex);
pivotIndex ++;
}
}
swap(arr,pivotIndex, end);
return pivotIndex;
}
function swap(items, leftIndex, rightIndex){
var temp = items[leftIndex];
items[leftIndex] = items[rightIndex];
items[rightIndex] = temp;
}
You could:
give the div elements an absolute position style, and set a transition effect when their left-coordinate changes.
let the swap function return a promise that resolves when the transition effect is complete.
Make the quickSort, partition functions asynchronous so they can await the swap promises to resolve
Here is an implementation:
class Element{
constructor(container, value, i) {
this.value = value;
const div = document.createElement("div");
div.classList.add("element");
div.textContent = value;
container.appendChild(div);
div.style.height = `${value}px`;
div.style.left = `${30*i}px`;
div.style.top = `${120-value}px`;
this.div = div;
}
move(i) {
// Return promise that resolves when move is complete
return new Promise(resolve => {
this.div.addEventListener('transitionend', (e) => {
this.div.classList.toggle('highlight', false);
resolve();
}, { once: true });
this.div.classList.toggle('highlight', true);
this.div.style.left = `${30*i}px`; // trigger transition effect
});
}
}
const generateRandomValue = () => Math.floor((Math.random() * 100) + 20);
function generateElements(container, length=10) {
container.innerHTML = "";
return Array.from({length}, (_, i) =>
new Element(container, generateRandomValue(), i)
);
}
async function quickSort(arr, start, end){
if (start >= end) {
return;
}
const index = await partition(arr, start, end);
await quickSort(arr, start, index-1);
await quickSort(arr, index + 1, end);
return arr;
}
async function partition(arr, start, end) {
let pivotIndex = start;
const pivotValue = arr[end].value;
for (let i = start; i < end ; i++) {
if (arr[i].value < pivotValue) {
await swap(arr, i, pivotIndex);
pivotIndex++;
}
}
await swap(arr,pivotIndex, end);
return pivotIndex;
}
function swap(items, leftIndex, rightIndex) {
if (leftIndex === rightIndex) return; // Nothing to do
var temp = items[leftIndex];
items[leftIndex] = items[rightIndex];
items[rightIndex] = temp;
return Promise.all([items[leftIndex].move(leftIndex),
items[rightIndex].move(rightIndex)]);
}
const elements = generateElements(document.querySelector("#container"));
setTimeout(() =>
quickSort(elements, 0, elements.length - 1),
1000);
.element {
border: 1px solid;
width: 25px;
position: absolute;
transition: left 1s;
background: white;
}
#container {
margin: 10px;
position: relative;
}
.highlight {
background: yellow;
z-index: 1;
}
<div id="container"></div>
I am working on a pathfinding visualizer for Breadth-First Search, and the algorithm works while "start" and "end" are around 15 units apart but afterward it either takes really long to compute or just fails to work. I think my code may be inefficient but I am confused about how to fix it. Any help is very appreciated.
This is the JavaScript code I have right now for BFS which uses ids to locate the nodes in the table:
var start = document.getElementsByClassName("start")[0];
let q = new Queue();
q.enqueue(start);
while(!q.isEmpty()){
var x = q.dequeue();
var row = parseInt(x.id.split('-')[0]);
var col = parseInt(x.id.split('-')[1]);
//go up
if((row-1)>=0){
var u = document.getElementById((row-1)+"-"+col);
if(u.className=="end"){
break;
}else if(u.className!="start" || u.className!="visited"){
u.className="visited";
q.enqueue(u);
}
}
//go down
if((row+1)<=20){
var d = document.getElementById((row+1)+"-"+col);
if(d.className=="end"){
break;
}else if(d.className!="start" || d.className!="visited"){
d.className="visited";
q.enqueue(d);
}
}
//go right
if((col+1)<=50){
var r = document.getElementById(row+"-"+(col+1));
if(r.className=="end"){
break;
}else if(r.className!="start" || r.className!="visited"){
r.className="visited";
q.enqueue(r);
}
}
//go left
if((col-1)>=0){
var l = document.getElementById(row+"-"+(col-1));
if(l.className=="end"){
break;
}else if(l.className!="start" || l.className!="visited"){
l.className="visited";
q.enqueue(l);
}
}
This is the interface where green is the start node and red is the end node and the blue is the bfs algorithm
A good way to not get confused, is to separate the breath first search algorithm from the rest of the code like this. It will allow you to test each part separately.
function bfs () {
let q = new Queue();
q.enqueue(start);
while (! q.isEmpty()) {
let x = q.dequeue();
if (! visited(x)) {
process(x);
if (exit()) break;
q.enqueue( adjacent(x) );
}
}
}
Also if you want to animate the algorithm you will need to use setTimeout and execute only 1 turn of the loop per setTimeout call. If you don't, you will only see the result after the algorithm has found the "end" because of the nature of the event loop of the browser. You see, while the javascript is running the browser can't refresh the screen. If you want to see something change, you need to execute a few instructions then let the browser refresh and continue the execution of a few more instructions. Rinse and repeat, and you have an animation. The setTimeout is the function that allow you to restart the execution.
Here is an example:
let start = "x16y2";
let end = "x25y4";
function main () {
init();
step1();
function step1 () {
bfs(step2);
}
function step2 () {
dfs(step1);
}
}
// breath first search (bfs)
function bfs (cont) {
clearVisited();
let queue = new STACK_QUEUE("shift");
queue.add(start);
bfs_cont();
/*
while (! queue.isEmpty()) {
let x = queue.retrieve();
if (! visited(x)) {
process(x);
if (exit()) break;
queue.add( adjacent(x) );
}
}
*/
function bfs_cont () {
if (queue.isEmpty()) return;
let x = queue.retrieve();
if (! visited(x)) {
process(x);
if (exit()) {setTimeout(cont, 2000); return;}
queue.add( adjacent(x) );
}
setTimeout(bfs_cont, 10);
}
}
// depth first search (dfs)
function dfs (cont) {
clearVisited();
let stack = new STACK_QUEUE("pop");
stack.add(start);
dfs_cont();
/*
while (! stack.isEmpty()) {
let x= stack.retrieve();
if (! visited(x)) {
process(x);
if (exit()) break;
stack.add( adjacent(x) );
}
}
*/
function dfs_cont () {
if (stack.isEmpty()) return;
let x = stack.retrieve();
if (! visited(x)) {
process(x);
if (exit()) {setTimeout(cont, 2000); return;}
stack.add( adjacent(x) );
}
setTimeout(dfs_cont, 10);
}
}
function exit () {
return visited(end);
}
function visited (id) {
let e = document.getElementById(id);
return hasClass(e, "visited");
}
function process (id) {
let e = document.getElementById(id);
addClass(e, "visited");
}
function adjacent (id) {
let pos_y = id.indexOf('y');
let x = 1 * id.substring(1,pos_y);
let y = 1 * id.substr(pos_y+1);
let res = [];
// up
if (x > 0) res.push("x"+(x-1)+"y"+y);
// left
if (y > 0) res.push("x"+x+"y"+(y-1));
// down
if (x < 32-1) res.push("x"+(x+1)+"y"+y);
// right
if (y < 20-1) res.push("x"+x+"y"+(y+1));
return radomizeIt(res);
}
function clearVisited () {
var list;
while (true) {
list = document.getElementsByClassName("visited");
if (list.length <= 0) break;
for (let i = 0 ; i < list.length ; i += 1) {
removeClass(list[i], "visited");
}
}
}
function init() {
let c = document.createElement("div");
c.setAttribute("class", "container");
c.setAttribute("id", "container");
document.body.appendChild(c);
let container = document.getElementById("container");
for (let y = 0 ; y < 20 ; y += 1) {
for (let x = 0 ; x < 32 ; x += 1) {
let e = document.createElement("div");
e.setAttribute("class", "cell");
e.setAttribute("id", "x"+x+"y"+y);
e.style.top = ""+(y*8)+"px";
e.style.left = ""+(x*8)+"px";
container.appendChild(e);
}
}
addClass(document.getElementById(start), "start");
addClass(document.getElementById(end), "end");
}
function addClass(e, cssClass) {
if (!e) return;
let _class = e.getAttribute("class");
if (! hasClass(e, cssClass)) {
_class += " "+cssClass;
e.setAttribute("class", _class);
}
}
function removeClass(e, cssClass) {
if (!e) return;
let _class = e.getAttribute("class");
if (hasClass(e, cssClass)) {
_class = _class.replace(cssClass, "");
_class = _class.replace(" ", " ");
e.setAttribute("class", _class);
}
}
function hasClass(e, cssClass) {
if (!e) return false;
let _class = e.getAttribute("class");
if (_class === null) return false;
return _class.indexOf(cssClass) >= 0;
}
function STACK_QUEUE (fn) {
var self = this;
self.arr = [];
self.isEmpty = function () {
return self.arr.length === 0;
};
self.add = function (e) {
if (Array.isArray(e)) {
self.arr = self.arr.concat(e);
} else {
self.arr.push(e);
}
return self;
};
self.retrieve = function () {
return self.arr[fn]();
};
}
function radomizeIt (arr) {
let py0 = start.indexOf('y');
let x0 = 1* start.substring(0,py0);
let y0 = 1* start.substr(py0);
let res = arr.slice();
res.sort((a,b)=>(dist(b)-dist(a)));
/*
for (let i = 0 ; i < arr.length-1 ; i += 1) {
let j = Math.floor(Math.random() * (arr.length - i));
swap(i,j);
}
*/
return res;
function swap (i,j) {
if (i === j) return;
var t = res[i];
res[i] = res[j];
res[j] = t;
}
function dist (id) {
let py = id.indexOf('y');
let x = 1* id.substring(0,py0);
let y = 1* id.substr(py0);
let dx = (x-x0);
let dy = (y-y0);
return dx*dx+dy*dy;
}
}
main();
.container {
position: relative;
width: 320px;
height: 200px;
}
.cell {
position: absolute;
width: 5px;
height: 5px;
border: 1px solid black;
backgound-color: white;
}
.visited {
background-color: #39cccc;
}
.cell.start {
background-color: #3c9970;
}
.end {
background-color: #ff7066;
}
.visited.end {
background-color: #ff0000;
}
This is bothering my mind for a few weeks now. I have a working example of some Javascript that updates the DOM within a loop.
But I can't get this "trick" to work for my real code.
This is the link to my Plunk: https://plnkr.co/edit/oRf6ES74TJatPRetZEec?p=preview
The HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="style.css">
<script src="http://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="js/script.js"></script>
<script src="js/script2.js"></script>
<script src="js/json-util.js"></script>
<script src="js/scorito-dal.js"></script>
<script src="js/matching.js"></script>
</head>
<body>
<div id="log">..log here..</div>
<button onclick="doHeavyJob();">do heavy job</button>
<button onclick="doHeavyJobJquery();">do heavy job with jQuery</button>
<button onclick="match();">match</button>
</body>
</html>
The script in js/script.js, which is called from the button "do heavy job" works:
async function doHeavyJob() {
$('#log').html('');
for (var j=0; j<10; j++) {
await sleep(1000)
console.log('Iteration: ' + j);
(function (j) {
setTimeout(function() { // pause the loop, and update the DOM
var logPanel = document.getElementById('log');
var txt = 'DOM update' + j;
logPanel.innerHTML += txt + ', ';
}, 0);
})(j);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
The exact same code does not work within a jQuery each(), this is appearantly due to jQuery, and easy to work around by using the for-loop. If you're interested, check script2.js in my plunk.
My real script in js/matching, which is called from the button "match" does NOT work.
var _keeperCombinations = [];
var _defenderCombinations = [];
var _midfielderCombinations = [];
var _attackerCombinations = [];
var _mostPoints = 0;
var _bestCombination = [];
function updateStatus(keeperCount, ixKeeper, msg) {
msg = '%gereed: ' + Math.round(ixKeeper / keeperCount * 100, 0);
console.log(msg);
$('#log').html(msg);
}
function match() {
$('#log').html('');
updateStatus(1, 1, 'Match started');
var playersData = scoritoDal.getPlayersData();
this.determineKeepers(playersData);
this.determineDefenders(playersData);
this.determineMidfielders(playersData);
this.determineAttackers(playersData);
var keeperCount = _keeperCombinations.length
for (var ixKeeper=0; ixKeeper<keeperCount; ixKeeper++) {
var keepers = _keeperCombinations[ixKeeper];
doMatching(keepers, keeperCount, ixKeeper);
}
if (_bestCombination.length === 0) {
alert('Er kon geen beste combinatie worden bepaald');
}
else {
alert('Ready: ' + _bestCombination.toString());
$(_bestCombination).each(function(ix, playerId) {
});
}
}
/*
* Match 2 keepers, 5 defenders, 6 midfielders and 5 attackers
* First pick the 5 best keepers, 10 best defenders, 12 best midfielders and 10 best attackers.
* 3 / 2 (k), 7 / 3 (d & a) and 8 / 4 (m) >> most points / most points per prices
*/
var _confirmed = false;
function doMatching(keepers, keeperCount, ixKeeper) {
// Make a new promise
let p = new Promise(
// The executor function is called with the ability to resolve or reject the promise
(resolve, reject) => {
for (var ixDefenders=0; ixDefenders<_defenderCombinations.length; ixDefenders++) {
var defenders = _defenderCombinations[ixDefenders];
for (var ixMidfielders=0; ixMidfielders<_midfielderCombinations.length; ixMidfielders++) {
var midfielders = _midfielderCombinations[ixMidfielders];
for (var ixAttackers=0; ixAttackers<_attackerCombinations.length; ixAttackers++) {
var attackers = _attackerCombinations[ixAttackers];
procesPlayers(keepers, defenders, midfielders, attackers);
}
}
resolve(ixDefenders);
}
});
p.then(
function(ixDefenders){
console.log('LOG: ' + keeperCount + " - " + ixKeeper);
updateStatus(keeperCount, ixKeeper);
}
);
}
async function procesPlayers(keepers, defenders, midfielders, attackers) {
var totals = calculateTotals(keepers, defenders, midfielders, attackers);
// check if total price is within budget
if (totals.TotalPrice <= 56500000) {
if (totals.TotalPoints > _mostPoints) {
_mostPoints = totals.TotalPoints;
setBestCombination(keepers, defenders, midfielders, attackers);
}
}
}
function calculateTotals(keepers, defenders, midfielders, attackers) {
var playerIds = [];
var totalPoints = 0;
var totalPrice = 0;
var allPlayers = keepers.concat(defenders, midfielders, attackers);
var checkTeams = [];
$(allPlayers).each(function(ix, player){
var club = checkTeams.find(t => t.Name === player.Club);
if (!club) {
club = {"Name":player.Club, "Count":1};
checkTeams.push(club);
}
else club.Count++;
if (club.Count > 4) {
totalPoints = 0;
return false;
}
playerIds.push(player.ID);
var factor = 1;
if (player.Position === 'Keeper' && ix > 0) factor = 0.5; // 2nd keeper gets less points
if (player.Position === 'Defender' && ix > 2) factor = 0.5; // 4th defender gets less points
if (player.Position === 'Midfielder' && ix > 3) factor = 0.5; // 5th midfielder gets less points
if (player.Position === 'Attacker' && ix > 2) factor = 0.5; // 4th attacker gets less points
var playerPoints = player.Points * factor;
totalPoints += playerPoints;
totalPrice += player.Price;
});
return {"TotalPoints":totalPoints,"TotalPrice":totalPrice};
}
function determineKeepers(playersData) {
console.log('Determining keepers');
$('#progres').text('Determine 5 best keepers');
var bestKeepers = this.determineBestPlayers(playersData, 'Keeper', 3, 2); // 3, 2
if (bestKeepers && $(bestKeepers).length > 0) {
// now determine all combinations
this.determineCombinations(bestKeepers, 2);
_keeperCombinations = this._combinations;
}
}
function determineDefenders(playersData) {
console.log('Determining defenders');
$('#progres').text('Determining 10 best defenders');
var bestDefenders = this.determineBestPlayers(playersData, 'Defender', 5, 3); // 6, 3
if (bestDefenders && $(bestDefenders).length > 0) {
// now determine all combinations
this.determineCombinations(bestDefenders, 5);
_defenderCombinations = this._combinations;
}
}
function determineMidfielders(playersData) {
console.log('Determining midfielders');
$('#progres').text('Determine 12 best midfielders');
var bestMidfielders = this.determineBestPlayers(playersData, 'Midfielder', 5, 3); // 7, 3
if (bestMidfielders && $(bestMidfielders).length > 0) {
// now determine all combinations
console.log('*** Determining all midfielder combinations ***');
this.determineCombinations(bestMidfielders, 6);
_midfielderCombinations = this._combinations;
}
}
function determineAttackers(playersData) {
console.log('Determining attackers');
$('#progres').text('Determine 10 best attackers');
var bestAttackers = this.determineBestPlayers(playersData, 'Attacker', 5, 3); // 6, 3
if (bestAttackers && $(bestAttackers).length > 0) {
// now determine all combinations
this.determineCombinations(bestAttackers, 5);
_attackerCombinations = this._combinations;
}
}
/*
* Determine the best players for a position
* - position: Keeper|Defender|Midfielder|Attacker
* - byPoints: the nr of best players by points
* - byFactor: the nr of best players by factor
*/
function determineBestPlayers(playersData, position, byPoints, byFactor) {
var players = $.grep(playersData, function(p) {return p.Position === position});
var playersByPoints = players.sort(jsonUtil.sortByProperty('Points', true));
var bestPlayersByPoints = playersByPoints.slice(0, byPoints);
var playersByFactor = players.sort(jsonUtil.sortByProperty('Factor', true));
var bestPlayersByFactor = playersByFactor.slice(0, byFactor);
// players could be in both lists, make it unique
var bestPlayers = jsonUtil.joinArrays(bestPlayersByPoints, bestPlayersByFactor, true, 'ID');
// if not the nr wanted, add extra players
// take theze by turn on points / on factor
var cnt = $(bestPlayers).length;
var nextByPoints = true;
var cntExtra = 0;
while (cnt < byPoints + byFactor) {
cntExtra++;
var isOK = false;
while (!isOK) {
var ix=0; // if the next player is already chosen, move to the next
var extraPlayer = {};
if (nextByPoints) {
extraPlayer = playersByPoints[byPoints+cntExtra+ix-1];
}
else {
extraPlayer = playersByFactor[byFactor+cntExtra+ix-1];
}
if (!bestPlayers.find(x => x.ID === extraPlayer.ID)) {
bestPlayers.push(extraPlayer);
isOK = true;
}
else {
ix++;
}
}
nextByPoints = !nextByPoints;
cnt++;
}
bestPlayers = bestPlayers.sort(jsonUtil.sortByProperty('Points', true)); // add the end we want the players sorted by total points
console.log('Best player for position ' + position);
console.log(bestPlayersToString(bestPlayers));
return bestPlayers;
}
function bestPlayersToString(bestPlayers) {
var result = '';
var sep = '';
$(bestPlayers).each(function(ix, player) {
result += sep;
result += JSON.stringify(player);
if (sep === '') sep = ',';
});
return result;
}
function setBestCombination(keepers, defenders, midfielders, attackers) {
_bestCombination = [];
$(keepers).each(function(ix, keeper){
_bestCombination.push(keeper.ID);
});
$(defenders).each(function(ix, defender){
_bestCombination.push(defender.ID);
});
$(midfielders).each(function(ix, midfielder){
_bestCombination.push(midfielder.ID);
});
$(attackers).each(function(ix, attacker){
_bestCombination.push(attacker.ID);
});
}
/* Combinations */
var _combinations = [];
var _curCombination = [];
function determineCombinations(players, cnt) {
_combinations = [];
_curCombination = [];
this.addCombinations(players, cnt);
}
/*
* Take 2 from 5 (keepers), 5 from 10 (defenders, attackera) or 6 from 12 (midfielders)
* It is a rather complex recursive method with nested iterations over the
* (remaining players).
*/
var _curLevel = 1;
function addCombinations(players, cnt) {
for (var i=0; i<players.length; i++) {
var player = players[i];
_curCombination.push(player);
if (_curCombination.length == cnt) {
_combinations.push(_curCombination.slice(0)); // slicing creates a clone
//console.log(curCombinationToString());
_curCombination.pop();
continue;
}
var curPlayers = players.slice(i+1);
_curLevel++;
addCombinations(curPlayers, cnt);
};
_curCombination.pop();
_curLevel--;
}
function curCombinationToString() {
var result = '';
var sep = '';
$(_curCombination).each(function(ix, player) {
result += sep;
result += JSON.stringify(player);
if (sep === '') sep = ',';
});
return result;
}
Any ideas will be greatly appreciated!!!!
finally got this to work, after numerous, NUMEROUS attempts.
And off course I'd like to share this with you.
Here's the essence to getting this to work:
the loop must be inside an async function
the code with the logic for 1 loop must be inside a Promise
the code also needs to be within setTimeout
the call for this logic must be 'annotated' with await
So this works:
'use strict';
async function testPromise() {
var msg;
for (var ix=1; ix<10; ix++) {
msg = 'Before loop #'+ix
$('#log').html(msg);
await doActualWork();
msg = 'After loop #'+ix
$('#log').html(msg);
}
}
/*
* Perform logic for one loop
*/
function doActualWork() {
return new Promise(
function (resolve, reject) {
window.setTimeout(
function () {
// do the actual work like calling an API
resolve();
}, 100);
}
);
}
Checkout this demo/game i am building http://awebdeveloper.github.io/runners/
I wish to know when will all the players finish running. The Game class needs to know when all the player stop and who came first.
My question is is there a way to inform back the calling class when each of the animate finish after all the settimeout
Player Class
function Players(ele, ptimeout)
{
this.movePositions = [0, 40, 80, 120],
this.moveBy = 5
this.el = ele;
this.i = 0;
this.stop = 1;
this.timeout = ptimeout;
this.position = 0;
this.animate = function(){
/* Stop if stopped */
playerPosition = this.el.getBoundingClientRect();
if(this.stop || playerPosition.left > (racetrack.offsetWidth - 30)){
this.el.style.backgroundPosition = '120px 0px';
return ;
}
playerPosition = this.el.getBoundingClientRect();
if(this.stop || playerPosition.left > (racetrack.offsetWidth - 30)){
this.el.style.backgroundPosition = '120px 0px';
return ;
}
/* Prepare Next Move */
setTimeout(function(_this){
if(_this.i < _this.movePositions.length ){
_this.i ++;
}
else{
_this.i = 0;
}
_this.move();
_this.animate();
},this.timeout,this);
};
this.play = function(){
if(this.stop === 1){
this.stop = 0;
this.animate();
}
};
this.move = function(to,positionIndex){
this.position = to;
this.el.style.backgroundPosition = '-'+this.movePositions[positionIndex]+'px 0px';
this.el.style[getSupportedPropertyName('transform')] = 'translate('+to+'px)';
}
}
Game Class
function Game(noOfPlayers){
var track_tmpl = '<div class="track"><div id="player{{ x }}" class="runner"></div></div>';
this.noOfPlayers = noOfPlayers;
this.players = new Array();
for (var i = 0; i < this.noOfPlayers ; i++){
var timeout = 120 + getRandomInt(1, (this.noOfPlayers*2));
racetrack.appendChild(createNode(track_tmpl.replace('{{ x }}', i)));
this.players.push(new Players(document.getElementById('player' + i), timeout));
}
this.play = function(){
for (var i = 0; i < this.noOfPlayers; i++){
this.players[i].play();
}
};
}
on Player.play pass the this argument, to call from Person a function from Game.
So you will have a code like this
this.play = function(){
for (var i = 0; i < this.noOfPlayers; i++){
this.players[i].play(this);
}
};
then on Player modify the code like this
this.play = function(game){
if(this.stop === 1){
this.stop = 0;
this.animate(game);
}
};
after on method animate call the game.playerFinished(Player)
on Game class write a method
var i = 0;
this.playerFinished = function(player) {
console.log(player.name + " finished on position " + (i++));
}
where player.name a name you give on players class