How to determine possible combinations of items based on item resources? - javascript

I'm a current software development student looking to get some help with my latest self-learning project.
My coworkers and I play Settlers of Catan, so a couple of weeks ago I thought it would be a neat idea if I could make a site that would tell a person what they can buy with the resource cards they have in their hand.
For those who don't know, the basic Catan purchases are as follows:
Building
Cost
City
2 wheat, 3 ore
Settlement
1 wood, 1 brick, 1 wool, 1 ore
Road
1 wood, 1 brick
Dev. Card
1 wool, 1 wheat, 1 ore
What I need to do is take a players hand, which has any number of resource cards, and run it through a function that will determine a list of possible options along with the cards left over.
As an example it should return something like:
Option 1: You can build 1 City, 1 Road. 1 Sheep left over.
Option 2: You can build 1 Settlement. 3 Ore and 1 Wheat left over.
The function that I am currently using in JavaScript is shown below:
/* Below dictionary is updated via HTML inputs
Also sheep == wool, ignore the lack of using the accurate card name
*/
var resources = { 'wood': 1, 'brick': 1, 'sheep': 1, 'wheat': 2, 'ore': 3 };
/* Takes resources (your hand), then tries to determine what
the player can build with that hand */
function basicBuilder(resources) {
var buildDict = { 'roads': 0, 'settlements': 0, 'cities': 0, 'dcards': 0 }
while (resources['wheat'] >= 2 && resources['ore'] >= 3) {
resources['wheat'] -= 2;
resources['ore'] -= 3;
buildDict['cities'] += 1;
}
while (resources['wood'] >= 1 && resources['brick'] >= 1 && resources['sheep'] >= 1 && resources['wheat'] >= 1) {
resources['wood'] -= 1;
resources['brick'] -= 1;
resources['sheep'] -= 1;
resources['wheat'] -= 1;
buildDict['settlements'] += 1;
}
while (resources['sheep'] >= 1 && resources['wheat'] >= 1 && resources['ore'] >= 1) {
resources['sheep'] -= 1;
resources['wheat'] -= 1;
resources['ore'] -= 1;
buildDict['dcards'] += 1;
}
while (resources['wood'] >= 1 && resources['brick'] >= 1) {
resources['wood'] -= 1;
resources['brick'] -= 1;
buildDict['roads'] += 1;
}
return buildDict;
}
This way would work fine if none of the buildings shared resources, but since some buildings use the same cards it doesn't suit my purpose. For example if you use the resources that I supplied, buildDict['settlements'] will equal 0 since all the wheat was already used up by the city.
There has to be a better and cleaner way to go about this, so my question to all of you is this:
How can I best determine the players possible build choices on their turn?
As a reminder, the input will be a hand of resources in dictionary form.
The output should be similar to:
Option {n}: You can build {x} Cities, {x} Settlements, {x} Roads, etc... with {x} cards leftover
Bonus points for any of the following:
Solutions that easily allow for additional buildings later on (expansion packs)
Solutions that can somehow accept resource trading (in Catan, 4 of a resource can be traded in for 1 of any other resource. If you own a port, this can be reduced to either 3 or 2 for one trades)
Solutions that will still allow for the display of what the leftover cards are
EDIT - Reworded the question to be more specific per bots request, very new to stackoverflow. See past question above
With a hand (dictionary) full of resource cards, what is a good JavaScript way to find all the possible combinations of buildings that could be purchased?
Each building could be purchased multiple times
There is no maximum or fixed hand size
When a resource is used for a building, it is removed from the hand

Related

Codewars Kata - 4th Kyu - Codewars style ranking system - "+1 problem"

I thought that this kata will be very cool and easy to get back to js after a break.
I was so wrong lol.
So the URL to kata is here with all of the logic and math informations, i'll put the necessary ones below.
URL:
https://www.codewars.com/kata/51fda2d95d6efda45e00004e/train/javascript
Code:
class User {
constructor() {
this.rank = -8;
this.progress = 0;
this.rankTable=[-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8];
this.rankIndicator=0;
}
incProgress(rankOfActivity) {
if (rankOfActivity == 0 || rankOfActivity > 8 || rankOfActivity < -8) throw new error("Rank input out of range");
if (rankOfActivity <= this.rank - 2) return;
let diff = this.rankTable.indexOf(rankOfActivity)-this.rankTable.indexOf(this.rank);
if (diff==0) this.progress+=3;
else if(diff==-1) this.progress+=1;
else if(this.rank!=8){
this.progress+= 10*diff*diff;
while(this.progress>=100 && this.rank<8){
this.progress-=100;
this.rankIndicator++;
this.rank=this.rankTable[this.rankIndicator];
}
}
if (this.rank==8) this.progress=0;
}
}
Completing an activity that is ranked one ranking lower than the user's will be worth 1 point
After i saw that, my point of view was:
If the difference between activity rank and user's rank was -1 ,for example:
User's rank is -2 (index 6 in rankTable array)
Activity ranked at -3 (index 5 in rankTable array)
The difference would be 5-6 = -1
So it should add up 1 point of progress, and it looks like it doesn't do that and i cant figure it out why it doesn't add up.
Here are bunch of errors to show that it happens on any rank.
After applying rank of -1 the progress was expected to be 21, but was actually 20
After applying rank of 3 the progress was expected to be 61, but was actually 60
After applying rank of 8 the progress was expected to be 51, but was actually 50
//...
//if (rankOfActivity <= (this.rank - 2)) return;
//bug
let diff = (this.rankTable.indexOf(rankOfActivity)) - (this.rankTable.indexOf(this.rank));
if (diff <=-2)return;//
//fixed
//...
Personally, i do not like array and indexOf use here. Using a rank counter of [0 to 15] would be a little better.

best way to loop through and combine products

I'm looking for your personal thoughts on the best way to do this as there a number of ways that I can see to get to the same end.
I have a store front that has multiple different booklets that can be ordered in any combination.
Each booklet item has L x W x H and weight in the database. They're all really the same L x W, it's the thickness and weight that varies.
I also have a set max box size.
I know I can fit a height of 10.5 in the largest box size.
I can combine the books however for shipping. So I want to fill the box to the max I can, then repeat until I only have a partial box.
The final result I need is an array of each of the boxes and their weight.
Example:
Items ordered
100 of Book 1 with a thickness of .05 and weight of .15
200 of Book 2 with a thickness of .07 and a weight of .23
Heights
100 * .05 = 5
200 * .07 = 14
Weights
100 * .15 = 15
200 * .23 = 46
Box 1 Contains
150 of Book 2
Height: 150 * .07 = 10.5
Weight: 150 * .23 = 34.5
Box 2 Contains
50 of Book 2 and 100 of Book 1
Height: (100 * .05) + (50 * .07) = 8.5
Weight: (100 * .15) + (50 * .25) = 27.5
How would you go about looping through the information to get the output?
Start with one product, figure the height, divide it out to full boxes, calculate the weight of those boxes and add each full box to the array, take the height of the partial box and move on to the next product? Or......??
Static coding would be "easy", but I'm trying to code this dynamically so that I don't have to come back in every time I add a new product.
-edit-
Here is my current loop, but this is only totaling the quantity and dividing into boxes. The current products are all the same size / shape, so this worked fine then.
if($zip_code != '')
{
my $item_list = enc_sql_select_multi("SELECT * FROM `$PREF{shopping_carts_table}` WHERE `cart_id` = '$cart_id' AND `sold` = '0' AND `deleted_from_cart` = '0'");
my $item_qty = '0';
foreach my $k (sort { $a <=> $b } keys %$item_list)
{
$item_qty =~ s/,//g;
my $item_id = $$item_list{$k}{id};
my $item_qty_new = $$item_list{$k}{option1value};
$item_qty_new =~ s/,//g;
$item_qty = $item_qty + $item_qty_new;
}
if ($item_qty != '0')
{
$item_qty =~ s/,//g;
if ( $item_qty == 25 )
{
my $tempCount = $carton_specs{25}{boxNo};
$tempCount++;
$carton_specs{25}{boxNo} = $tempCount;
}
elsif ( $item_qty == 50 )
{
my $tempCount = $carton_specs{50}{boxNo};
$tempCount++;
$carton_specs{50}{boxNo} = $tempCount;
}
elsif ( $item_qty == 100 )
{
my $tempCount = $carton_specs{100}{boxNo};
$tempCount++;
$carton_specs{100}{boxNo} = $tempCount;
}
elsif ($item_qty > 100 && $item_qty < 5000)
{
my $fullBoxCt = int($item_qty / 200);
my $tempCountFull = $carton_specs{200}{boxNo};
$tempCountFull = $tempCountFull + $fullBoxCt;
$carton_specs{200}{boxNo} = $tempCountFull;
my $partBoxQty = $item_qty - (200 * $fullBoxCt);
if ($partBoxQty != 0)
{
my $tempCountPart = $carton_specs{$partBoxQty}{boxNo};
$tempCountPart++;
$carton_specs{$partBoxQty}{boxNo} = $tempCountPart;
}
}
else
{
#shipDetails =
(
{
Code => 1000,
Price => '0.00'
},
{
Code => 1500,
Price => '0.00'
}
);
return (#shipDetails);
}
}
There is no getting away from this being a classic example of the bin-packing problem and while I am no mathematician either, a lot of people who are have given this considerable thought. So:
There is no known optimal solution to this problem that can be computed in polynomial time and it would appear the size of your inputs can be quite large. Therefore, you should adopt one of the following heuristics:
First Fit Algorithm: Process the booklets one by one placing it in the first box that has enough capacity to hold the booklet. If there are no such boxes, a new box must be started and added to the list of boxes available.
Best Fit Algorithm: Process the booklets one by one placing it in the box where it fits the tightest (has the smallest capacity left). If there are no boxes that can hold the booklet, a new box must be started and added to the list of boxes available.
First Fit Decreasing Algorithm: First order the booklets by decreasing height and then perform the First Fit Algorithm. In your example you would be packing up the "Book 2" booklets first.
Best Fit Decreasing Algorithm: First order the booklets by decreasing height and then perform the Best Fit Algorithm.
But, of course, your orders consist of N items of of a certain type of booklet. We will take the prescription "one at a time" in the above algorithms loosely. So if you are doing, for example, the First Fit Algorithm, then when you find the first box that has the capacity to hold the booklet, it is easy enough to calculate the maximum number of those booklets, M, that box could hold and process up to min(M, N_LEFT) booklets at one time where N_LEFT is the number of booklets of that size you still have left to pack up. Likewise for any of the other three algorithms.
You can survey other bin-packing heuristics of which the above are just a sample four. I can't say one is necessarily superior to the others in all cases. I think any reasonable implementation of any of them will be satisfactory for your purposes. Anyway, this is my take.
This is a bin-packing problem. The best optimal algorithm is Korf's. If this is too slow for the amount of data you have, there are various algorithms that provide good approximate answers faster.

CodeWars challenge. Which test is causing the fail?

Can anyone find out what is wrong with this code? I run the code on CodeWars and pass every test except one... sadly it does not display what the input was for that specific test so it is very difficult to figure it out.
Here are the challenge instructions:
The new "Avengers" movie has just been released! There are a lot of people at the cinema box office standing in a huge line. Each of them has a single 100, 50 or 25 dollars bill. A "Avengers" ticket costs 25 dollars.
Vasya is currently working as a clerk. He wants to sell a ticket to every single person in this line.
Can Vasya sell a ticket to each person and give the change if he initially has no money and sells the tickets strictly in the order people follow in the line?
Return YES, if Vasya can sell a ticket to each person and give the change. Otherwise return NO.
I found that the code works for ALL tests if I swap the check for amount50 >= 1 and amount25 >= 1 with the amount25 >= 3 but I am not sure WHY this works.
function tickets(peopleInLine){
let amount25 = 0;
let amount50 = 0;
let amount100 = 0;
for(let i = 0; i < peopleInLine.length; i++){
if(peopleInLine[i] === 100){
if(amount25 >= 3){
amount25 -= 3;
amount100++;
}else if(amount25 >= 1 && amount50 >= 1){
amount25 -= 1;
amount50 -= 1;
amount100++;
}else{
return "NO";
}
}
if(peopleInLine[i] === 50){
if(amount25 >= 1){
amount25--;
amount50++;
} else {
return "NO";
}
}
if(peopleInLine[i] === 25){
amount25++;
}
}
return "YES";
}
On Codewars you can put console.log statements (or equivalent statements in languages other than JavaScript) in your code to print the input parameters or other variables when you're having trouble figuring out what is going wrong in your code or with your assumptions about the input. I just did this with your code and saw that the test your code is failing on is [ 25, 25, 25, 25, 50, 100, 50 ], so that should show you exactly why your code would be failing when in response to getting a $100 you first try to return three $25s as change instead of checking for a $50 and $25 first -- you receive four $25s, one of which you give as change to the first person with a $50, but then because you give the remaining three $25s (rather than the $50 and one $25) as change to the person with the $100, you no longer have a $25 to make change for the last person's $50.

JavaScript code for a short computer task

I'm very new to JavaScript programming and would appreciate any sort of help on this.
I'm trying to program a task that is separated into 4 games. Each of the games has the same structure- a stimulus (different image in each game) appears on the screen, then you have 2 choices- either 1) press “enter” to keep playing to see the “result” image (different image in each game) or 2) press “space bar” stop playing and accumulate the points earned up to that point. If you decide to keep playing, the “result” image will show up which will show you whether you win or lose one point. You start the game with 50 points and over 100 trials, the percentage of “win”s per 10 trials drops from 90% to 0%.
What is the best way to set up these trials and collect this type of data? Currently, I have made an array of 100 responses corresponding to the 100 trials:
correctresponse = [“spacebar_code”, “enter_code”, “enter_code”, “enter_code” ….]
Similarly, I’ve made an array of 100 outcome responses:
outcome = [“image1.jpg”, “image2.jpg”…]
I’m also unsure as to setting up some key functions. My function to capture keypresses right now is:
function getKeyTest(keyStroke) {
isNetscape=(document.layers);
keyID = (window.event) ? event.keyCode : keyStroke.keyCode;
eventChooser = (isNetscape) ? keyStroke.which : keyID;
which = String.fromCharCode(eventChooser);
for (i=0;i<response_keys.length;i++) {
if (which == response_keys[i]) {
rtEndTime = new Date();
var currentRT = rtEndTime - rtStartTime;
trialRTs.push(currentRT);
if (currentRT < 500)
rtUnder500++;
} else if (currentRT > 10000) {
rtOver10000++;
}
if (response_keys[i] == correctResponses[trialNumber]) {
accuracy[trialNumber] = 1;
correctTotal = correctTotal + 1;
} else {
accuracy[trialNumber] = 0;
}
trial_outcome();
}
if (which == spacebar_code && trialTypes[trialNumber] == "test") { //this does not seem to end the trial
save_data(); }
}
}
Another question I have is:
There are 4 different conditions that have to be randomized across the 4 games.
cue, delay;
cue, no delay;
no cue, delay;
no cue, no delay.
Where
Cue = there’s a tally of points shown above the stimuli
Delay = there is a 5-second delay between the outcome of each trial and the next chance for the person to respond
I’m not quite sure what the best way to randomize the 4 conditions across the 4 games would be. I think I need to create an array and do a shuffle function, but how would I define the elements in that array to contain the two different aspects (cue and delay)?

Run a function as far as a variable reaches specific values

I have a canvas game which calls a function incScore every time an action is performed in the game to increase the score.
Inside incScore I have a few if statements to draw a particular image to represent a level number on the canvas.
I also want to have a sound play once per level up. The way I've gone about things the lvlup sound will play every time the score matches the if statement.
Can anyone please help me get this so that the sound will only play once when the level changes and not again until the next level change? I'm also mention I'm using jQuery incase it has anything that could help me.
incScore(); //everytime an action in the game causes the score to increase
function incScore(){
if (scoreTotal < 500){
lvlimg = "L01";
drawLevel(lvlimg);
lvlupSound();
}
else if (scoreTotal > 500 && scoreTotal < 1000){
lvlimg = "L02";
drawLevel(lvlimg);
lvlupSound();
}
else{
lvlimg = "L03";
drawLevel(lvlimg);
lvlupSound();
}
}
You could shorten your function and use a semi static property to save the state. Using that, you can compare the current level to the previous and play a sound if they differ.
function incScore(){
incScore.level = incScore.level || 'L0'; //< initialize property
lvlimg = "L0" + scoreTotal < 500 ? 1 : scoreTotal < 1000 ? 2 : 3;
drawLevel(lvlimg);
if (incScore.level!=='L0' &&
incScore.level !== lvlimg) { lvlupSound(); };
// ^compare local level to current
incScore.level = lvlimg;
// ^ update local level
}
[edit, based on comment] The third line is a so called ternary, or conditional operator. See MDN. You can use more conditions.
To avoid playing a sound before the score has reached a first level, you could use
if (incScore.level!=='L0' && incScore.level !== lvlimg).
I've created a mockup jsFiddle
A simple solution could be comparing the current level to the old one, to detect when the level changed:
function scoreToLevel(score)
if(score < 500){
return 1
}else if (score < 1000){
return 2
}else{
return 3
}
}
function incScore()
var next_level = scoreToLevel(scoreTotal)
if(next_level !== current_level){
lvlimg = "L0" + next_level;
drawLevel(lvlimg)
lvlupSound()
}
}
The easiest solution is to factor the sound out of those if statements. If the levels are nice and regular like that(every 500 points) and the points always increase in a way that you will always land exactly on an even multiple of 500 when you level up, something like this should do:
if(scoreTotal % 500 === 0 && scoreTotal < 1001)
{
lvlupSound();
}
If you won't always land directly on the gate to the next level(maybe the player can earn anywhere between 1 and 15 points at a time) then you should be able to get by using something along the lines of this before you increment the player's score:
if( (scoreTotal % 500) > ((scoreTotal + increment) % 500)
{
lvlupSound();
}
if your level boundries are not regular like that obviously it gets a little bit more complex, but that should get you started.
That is because you have the in every statement for every score (which means from 0 to infinite).
You will need to write inner if statements such as;
if (scoreTotal < 500){
lvlimg = "L01";
drawLevel(lvlimg);
if(scoreTotal x times of each the level) // That means for each level completed
{
lvlupSound();
}
}
If your score increment is only 1, then only play the tone when the score equals the threshold for a new level.
If they can increase their score by more than 1, then you could pass the number of points in and check the score before and after to see if the numbers fall on each side of the threshold.
If that still doesn't work, some more info on the "level" and points would be appreciated.
Try something like this (demo):
var scoreTotal,
lastLevel = 0,
levels = [500, 1000, 2500, 5000, 10000, 25000, 50000, 75000],
currentLevel = 0,
lvlImg;
function incScore() {
while (scoreTotal > levels[currentLevel]) {
currentLevel++;
}
if (lastLevel !== currentLevel) {
lastLevel = currentLevel;
// gives a two digit number with a leading zero
lvlImg = ('0' + currentLevel).slice(-2);
drawLevel("L" + lvlimg);
lvlupSound();
}
}
Then you can easily add additional levels by adding the score cutoff to the levels variable.

Categories