19

This seems sort of complex so I'll do my best to be as clear as possible.The particular function I'm looking for dynamically creates a money spent | money won chart for a game of gambling.

I have a lottery of sorts that the user can bet on. There are 6 items the user can buy which each have 6 prizes:

enter image description here

These can be put into objects or arrays.

var prices = [5,10,28,50,56,280].

var possibleWins = [40,80,250,400,500,2500]

I'm trying to create a chart that calculates how much money you would have to spend on each particular item per game to guarantee you gain money - for 300 games out.

So here is an example of how the chart should start off:

enter image description here

investment = max possible winnings + total spent( which is negative )

The 2nd row is assuming the first game already happened and lost. And so on.

The idea is to start with the smallest item but give up once it can no longer get you positive even if you win. This is why on row 9 we switch to the rock. ( our investment is at 0 and if we play a twig again, the most we can win is 40. So even if we did win, we would actually have lost 5 overall. )

Also worth pointing out is that if you win on 1 item; you win on all items for that particular game. So you get all prizes combined.

I've been working on this for a few days now and some of these related questions have my initial attempts ( but I honestly have no idea ):

How to find the lowest possible combination of keys within an array

Counter that generates the lowest sum from a combination of indexes above the previous value

Add an arrays keys to themselves until exceeding a limit?

EDIT: At least 1 item(s) must be bought every game and games cannot be skipped

Community
  • 1
  • 1

3 Answers3

9

Basically this proposal relies on a function to get the next items

    getItems = function () {
        var price = 0,
            array = lottery.map(function (a) { return a.price; });

        return function () {
            var items;
            do {
                items = combine(array, price);
                price++;
            } while (!items.length)
            return items;
        }
    }(),

which starts at price with zero and increments the value by one until a combination of items is found. Then the items array is returned. The function works as generator.

The other important function is the combination of items with a given price and the try to get an array with the items.

function combine(array, sum) {

    function c(left, right, sum) {
        if (!sum) {
            result = right;
            return true;
        }
        return left.some(function (a, i, aa) {
            return a <= sum && c(aa.slice(i + (a > sum - a)), right.concat(a), sum - a);
        });
    }

    var result = [];
    c(array.sort(function (a, b) { return b - a; }), [], sum);
    return result;
}

combine takes an array with prices and a wanted sum to reach with combinating the given prices. If successfull, an array with the items is returned, otherwise an empty array.

The third part is to use the items as long as the investment is not negative. If that happens, a new items set is fetched.

function combine(array, sum) {

    function c(left, right, sum) {
        if (!sum) {
            result = right;
            return true;
        }
        return left.some(function (a, i, aa) {
            return a <= sum && c(aa.slice(i + (a > sum - a)), right.concat(a), sum - a);
        });
    }

    var result = [];
    c(array.sort(function (a, b) { return b - a; }), [], sum);
    return result;
}

var lottery = [{ name: 'twig', price: 5, win: 40 }, { name: 'rock', price: 10, win: 80 }, { name: 'shell', price: 28, win: 250 }, { name: 'chip', price: 50, win: 400 }, { name: 'gold', price: 56, win: 500 }, { name: 'diamond', price: 280, win: 2500 }],
    lotteryByPrice = lottery.reduce(function (r, a) { r[a.price] = a; return r; }, Object.create(null)),
    getItems = function () {
        var price = 0,
            array = lottery.map(function (a) { return a.price; });

        return function () {
            var temp;
            do {
                temp = combine(array, price);
                price++;
            } while (!temp.length)
            return temp;
        }
    }(),
    createTableRow = function (element) {
        var table = document.createElement('table'),
            tr = document.createElement('tr');

        ['Game', 'Items', 'Types', 'Spend Per Game', 'Total Spend', 'Max. Possible Winnigs', 'Investment'].forEach(function (a) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(a));
            tr.appendChild(th);
        });
        table.appendChild(tr);
        element.appendChild(table);

        return function (row) {
            var tr = document.createElement('tr');
            ['game', 'items', 'types', 'spend', 'total', 'potential', 'investment'].forEach(function (k) {
                var td = document.createElement('td');
                td.appendChild(document.createTextNode(row[k]));
                tr.appendChild(td);
            });
            if (row.topBorder) {
                tr.style.borderTop = '2px solid #666';
            }
            table.appendChild(tr);
        };
    }(document.body),
    row = { game: null, items: null, types: null, spend: null, total: 0, potential: null, investment: null },
    i,
    items = getItems(),
    add = function (a, b) { return a + b; },
    winP = function (a) { return lotteryByPrice[a].win; },
    nameP = function (a) { return lotteryByPrice[a].name; };

for (i = 1; i <= 70; i++) {
    row.topBorder = false;
    while (row.total - items.reduce(add) + items.map(winP).reduce(add) < 0) {
        items = getItems();
        row.topBorder = true;
    }
    row.game = i;
    row.items = items.length;
    row.types = items.map(nameP).join(' + ');
    row.spend = -items.reduce(add);
    row.total += row.spend;
    row.potential = items.map(winP).reduce(add);
    row.investment = row.potential + row.total;
    createTableRow(row);
}
table { border-collapse: collapse; font-family: Sans-Serif; }
th { border: 1px solid #ccc; padding: 0 10px; }
td { text-align: center; border: 1px solid #ccc; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This is elegantly done. Can you give a little direction on how one could use your results to generate the table given in the question? – Jeremy Kahan Oct 26 '16 at 12:46
  • i am afraid, i do not understand the real purpose of the table. – Nina Scholz Oct 26 '16 at 12:49
  • As I understand it, the idea of the game/table was that you always start with the smallest priced play (which may include multiple plays of each option) you can make that will allow you to finish ahead. If you do win, you stop. If not, and if you have not reached your game limit (300), you do it again. – Jeremy Kahan Oct 26 '16 at 12:56
  • you could use my result and reverse it for the table. – Nina Scholz Oct 26 '16 at 12:58
  • Not that straightforwardly. For example, your first line would never be in the table, because all the table elements have at least 1 twig. My hunch is also that I think playing 300 games, the player could get deep (much more than 300, look how debt is accelerating) into debt and would end up with more desperate plays (diamond+shell, for instance) than those in your table. – Jeremy Kahan Oct 26 '16 at 13:28
  • @NinaScholz What is the relationship between `Total Spend`, `Max. Possible Winnings` and `Investment`? How is `3040` derived? – guest271314 Jan 31 '17 at 19:31
  • `spend + possible === investment`. what is `3040`? – Nina Scholz Jan 31 '17 at 19:37
  • The last value in `Max. Possible Winnings`. Trying to determine which column displays positive result? – guest271314 Jan 31 '17 at 19:43
  • 1
    may be i should exlude the negative outcomes as well. – Nina Scholz Jan 31 '17 at 19:45
  • `3040` represents positive result at conclusion of `300` games? – guest271314 Jan 31 '17 at 19:48
  • 1
    it's a total value, but it might be wrong. the question has holes. – Nina Scholz Jan 31 '17 at 19:51
  • 1
    Agreed, the Question does leave several exact parameters left not fully clarified; for example, some days no purchase could be made, or only 1 or 2 purchases of diamond for entire 300 days. From perspective here, the general requirement is to attempt to assure positive result at close of 300 days. Though not entirely certain how to determine if "win" a game. The total possible combinations of purchases would probably be an expansive array of possibilities of purchases and results. – guest271314 Jan 31 '17 at 20:02
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134521/discussion-between-nina-scholz-and-guest271314). – Nina Scholz Jan 31 '17 at 20:07
  • I've done some tests and this meets all the requirements **perfectly**. You amaze me. The only issue which I'm assuming is unavoidable is: Not enough processing power to compute all 300 games! (browser freezes ). `totalSpent` is already close to 1 million after 100 games and it only has an exponential amount to rise 200 more games at that point! By computing only 1 value at a time though I hope to overcome this limitation and have the full 300 games out. I'm curious as to how big that last `totalSpent` number will be. Billions? Possibly. AND THANK YOU. –  Feb 01 '17 at 00:08
4

Here is my solution

let items = [{
  name: 'twig',
  price: 5,
  win: 40
}, {
  name: 'rock',
  price: 10,
  win: 80
}, {
  name: 'shell',
  price: 28,
  win: 250
}, {
  name: 'chip',
  price: 50,
  win: 400
}, {
  name: 'gold',
  price: 56,
  win: 500
}, {
  name: 'diamond',
  price: 280,
  win: 2500
}];

let moves = [];

Move.prototype.numberItems = function() {
  let count = 0;
  for (let n = 0; n < 6; n++) {
    count += this.counts[n];
  }
  return count;
}

Move.prototype.nameItems = function() {
  let name = '';
  for (let n = 0; n < 6; n++) {
    for (let x = 0; x < this.counts[n]; x++) {
      if (name != '') {
        name += ' - ';
      }
      name += items[n].name;
    }
  }
  return name;
}


Move.prototype.getWin = function() {
  let win = 0;
  for (let n = 0; n < 6; n++) {
    win += this.counts[n] * items[n].win;
  }
  return win;
}


function Move(cost, counts) {
  this.cost = cost;
  this.counts = counts.slice();
}

function run() {
  createMoves(100);
  moves.sort(function(a, b) {
    return (a.cost - b.cost);
  });
  print();
}

function createMoves(maxCost) {
  let counts = [];
  for (let n = 0; n < 6; n++) {
    counts.push(0);
  }
  counts[0] ++;
  while (true) {
    let cost = whatCost(counts);
    if (cost < maxCost) {
      moves.push(new Move(cost, counts));
      counts[0] ++;
      continue;
    }
    if (!escalate(counts)) {
      break;
    }
  }
}

function whatCost(counts) {
  let cost = 0;
  for (let n = 0; n < 6; n++) {
    cost += counts[n] * items[n].price;
  }
  return cost;
}

function escalate(counts) {
  for (let n = 0; n < 5; n++) {
    if (counts[n] != 0) {
      counts[n] = 0;
      counts[n + 1] ++;
      return true;
    }
  }
  return false;
}

function print() {
  let domResult = document.getElementById('results');
  let game = 1;
  let moveInx = 0;
  let spent = 0;
  for (let moveInx = 0; moveInx < moves.length; moveInx++) {
    let myMove = moves[moveInx];
    let items = myMove.numberItems();
    let win = myMove.getWin();
    let cost = myMove.cost;

    for (let repeat = 1;; repeat++) {

      let investment = win - spent - cost;

      if (investment < 0) {
        break;
      }
      spent += cost;

      let row = document.createElement('tr');
      if (repeat == 1) {
        row.className = 'first';
      }
      let cell = document.createElement('td');
      cell.innerHTML = game;
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = items;
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = myMove.nameItems();
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = cost;
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = spent;
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = win;
      row.appendChild(cell);

      cell = document.createElement('td');
      cell.innerHTML = win - spent;
      row.appendChild(cell);

      domResult.appendChild(row);

      game++;
      if (game > 300) {
        return;
      }
    }
  }
}
table {
  border-collapse: collapse;
}
tr * {
  border: solid 1px black;
}
.first {
  border-top: solid 4px blue;
}
<button onclick="run()">Run</button>
<table>
  <thead>
    <tr>
      <th>Game</th>
      <th>Items</th>
      <th>Types</th>
      <th>Spent</th>
      <th>Total Spent</th>
      <th>Max win</th>
      <th>Profit</th>
    </tr>
  </thead>
  <tbody id="results">
  </tbody>
</table>
vals
  • 61,425
  • 11
  • 89
  • 138
  • Can you include one or more possible routes to achieve positive results following 300+ "games" at Answer? See OP at _"I'm trying to create a chart that calculates how much money you would have to spend on each particular item per game to guarantee you gain money - for 300 games out."_ – guest271314 Jan 30 '17 at 20:37
  • I stopped at move 29 because there isn't any other posibility following the rules as I understand them ... Probably I am misunderstanding the rules. Can you explain which should be game number 30, so that I can understand what I am missing ? – vals Jan 30 '17 at 21:36
  • Perspective here could be incorrect, though requirement appears to be a graph of all possible combinations of routes to achieve positive outcome over span of 300 games. For example, in addition to the route at your Answer; `twig` at game 1, `diamond` at game 2, buy no item at game 3, etc. Mentioned the extent of listing all possible combinations at own Answer, though did not complete the math portion as to calculating all of the combinations. Perhaps @carb0nshel1 will chime in to clarify. – guest271314 Jan 31 '17 at 01:22
2

You can create an object where property names are set the values of possibleWins. Set all of the possible combinations of investing the limit at each round. The arrays do not contain all possible combinations of numbers less than the limit for that particular round. That is, the numbers are not dispersed in every possible combination. For example, at round 40, [10, 10, 10, 10, 0, 0, 0, 0] is included as an array; though the array could also be rearranged to [10, 0, 10, 10, 0, 10, 0, 10], or other combination of indexes totaling less than 40.

Additional of the possible allowed combinations less than the limit for that round be pushed to the array corresponding a specific round at the returned object.

This implementation does not attempt to locate the selection routes of each round which would lead to a positive outcome. The entire set of arrays can be iterated as to each matching index in each array, combination of random indexes, or every possible combination of indexes.

The approach is a base template from which possible selections can be made. Further optional arrays containing combinations of values less object property name, that is the particular round, or values within arrays from within arrays at properties of object having a property name value less than the current round, can be added to the array of arrays; to find the combinations of selections which lead to the expected outcome.

const [prices, possibleWins] = [
  [5, 10, 28, 50, 56, 280],
  [40, 80, 250, 400, 500, 2500]
];

const counteropts = (prices, possibleWins) => {
  let rounds = {};
  for (let price of prices) {
    let [chance, limit] = [[], possibleWins[prices.indexOf(price)]];
    for (let buyin = price - price; buyin <= limit; buyin += price) {
      chance[chance.length] = buyin;
    }
    if (chance[chance.length - 1] !== limit) {
      chance = [...chance, limit]
    }
    for (let odd of Array.of(chance)) {
      let options = Array();
      for (let choice of odd) {
        options[options.length] = [...odd.map(
          v => v !== choice && v + choice <= limit ? v + choice : 0
        )];
        if (options.length === prices.length -1) {
          for (let option of options[0]) {
            let keys = options[0].map((_, index) => index + 1)
                       .filter(key => key * option <= limit);
            let opt = Array(keys.length).fill(option);
            options = [...options
              , opt.length < options[0].length
                ? [...opt, ...Array(options[0].length - opt.length).fill(0)]
                : opt
              ];
          }
          rounds[limit] = [...options];
        }
      }
    }
  }
  return rounds
  
}

let opts = counteropts(prices, possibleWins);
console.log(opts);
guest271314
  • 1
  • 15
  • 104
  • 177