1 /** 2 * @name Dominion Set Generator 3 * 4 * @fileOverview Generates sets for the card game Dominion. 5 * 6 * Copyright 2012, Wei-Hwa Huang 7 * Licensed for use by Funsockets, Inc. 8 * Ask Wei-Hwa for other licensing information. If you can't figure out how to 9 * ask Wei-Hwa, you're not allowed to use this code. 10 * 11 * Unimplemented but coming soon features: 12 * <ul> 13 * <li> Consider previous card sets 14 * <li> Add data for Guilds 15 * <li> Allow user to ban specific cards 16 * <li> Canonicalize card names? 17 * </ul> 18 * 19 * Warning: requires dominionSetGeneratorData.js to be loaded ! 20 * 21 * Last update: 2012-12-21 22 * 23 * @version 1.2.1 24 * @author Wei-Hwa Huang 25 */ 26 27 /** 28 * Creates a unit of DominionSetGeneratorAttributeData. 29 * A DominionSetGeneratorAttributeData is sort of like a 'parameter' for the 30 * set picker. It has four numeric values, and a 'Notes' section. 31 * 32 * @class 33 * @constructor 34 * @param {Number} min The 'minimum' threshhold: If this is undefined, 35 * we create an attribute with the default values of (0,0,1,1,'Unused'). 36 * @param {Number} max The 'maximum' threshhold. 37 * @param {Number} minW Weight for the 'minimum' threshhold. 38 * @param {Number} maxW Weight for the 'maximum' threshhold. 39 * @param {String} notes The 'notes' section. 40 */ 41 function DominionSetGeneratorAttributeData(min, max, minW, maxW, notes) { 42 if (min === undefined) { 43 this.min = 0; 44 this.max = 0; 45 this.minW = 1; 46 this.maxW = 1; 47 this.notes = 'Unused'; 48 } else { 49 this.min = min; 50 this.max = max; 51 this.minW = minW; 52 this.maxW = maxW; 53 this.notes = notes; 54 } 55 } 56 57 /** 58 * Creates a DominionSetGenerator. The same SetGenerator can be used to 59 * generate multiple sets. 60 * 61 * @constructor 62 */ 63 function DominionSetGenerator() { 64 65 this.maxSetSize = 10; 66 this.weight = new Object(); // weight of each card; changes a lot 67 this.commentary = new Object(); // explanation of the weights 68 this.owned = new Object(); // cards owned. Object, keys are cardnames. 69 this.cardset = new Object(); // the current set of cards so far. 70 71 this.tMin = new Object(); 72 this.tMax = new Object(); 73 this.tMinW = new Object(); 74 this.tMaxW = new Object(); 75 this.tNotes = new Object(); 76 this.isDefault = new Object(); // for each attribute (key), true if 77 // we should use the default value, 78 // false if we should use the user-supplied 79 // (t) value. 80 this.sgd = DominionSetGeneratorData; // convenience assignment 81 this.balanceFactor = 1.0; // how much to weight the overall effect 82 // of default values. 83 84 this.useShelters = false; // default values 85 this.platinumColony = false; 86 this.useSheltersDonline = false; // default values 87 this.platinumColonyDonline = false; 88 this.youngWitchBane = "Cellar"; 89 90 91 for (attr in this.sgd.Min) { 92 this.isDefault[attr] = true; 93 } 94 95 96 this.logData = new Array(); 97 } 98 99 /** 100 * Should we use shelters in this game? 101 * Note that this only changes when the set is regenerated. 102 * The official rules don't specify what to do when there are 103 * more than 10 cards in the set (such as for veto mode), so 104 * we calculate it based on all the cards in the set (but not 105 * the Bane for the Young Witch). Note that this means it is 106 * theoretically possible for there to be shelters but no 107 * Dark Ages cards in a veto game. 108 * 109 * @return {Boolean} Whether to use Shelters. 110 */ 111 DominionSetGenerator.prototype.getUseShelters = function() { 112 return this.useShelters; 113 }; 114 115 /** 116 * Should we have Platinum/Colony in this game? 117 * Note that this only changes when the set is regenerated. 118 * The official rules don't specify what to do when there are 119 * more than 10 cards in the set (such as for veto mode), so 120 * we calculate it based on all the cards in the set (but not 121 * the Bane for the Young Witch). Note that this means it is 122 * theoretically possible for there to be Platinum/Colony but no 123 * Prosperity cards in a veto game. 124 * 125 * @return {Boolean} Whether to use Platinum/Colony. 126 */ 127 DominionSetGenerator.prototype.getPlatinumColony = function() { 128 return this.platinumColony; 129 }; 130 131 /** 132 * Should we use shelters in this game? 133 * Note that this only changes when the set is regenerated. 134 * This uses "Donline rules", where the choice of whether to 135 * use is based on the proportion of total cards owned. 136 * 137 * @return {Boolean} Whether to use Shelters. 138 */ 139 DominionSetGenerator.prototype.getUseSheltersDonline = function() { 140 return this.useSheltersDonline; 141 }; 142 143 /** 144 * Should we have Platinum/Colony in this game? 145 * Note that this only changes when the set is regenerated. 146 * This uses "Donline rules", where the choice of whether to 147 * use is based on the proportion of total cards owned. 148 * 149 * @return {Boolean} Whether to use Platinum/Colony. 150 */ 151 DominionSetGenerator.prototype.getPlatinumColonyDonline = function() { 152 return this.platinumColonyDonline; 153 }; 154 155 /** 156 * What is the Bane for Young Witch? 157 * Note that this only changes when the set is regenerated. 158 * Also, sets that don't have a Young Witch still have a Bane 159 * (in case it's needed for Black Market or some other weird card). 160 * 161 * @return {Boolean} Whether to use Shelters. 162 */ 163 DominionSetGenerator.prototype.getYoungWitchBane = function() { 164 return this.youngWitchBane; 165 }; 166 167 /** 168 * What is the Bane for Young Witch? (Goko format) 169 * Note that this only changes when the set is regenerated. 170 * Also, sets that don't have a Young Witch still have a Bane 171 * (in case it's needed for Black Market or some other weird card). 172 * 173 * @return {Boolean} Whether to use Shelters. 174 */ 175 DominionSetGenerator.prototype.getYoungWitchBaneGoko = function() { 176 var card = this.youngWitchBane.toLowerCase() 177 .replace(/(?:^|\s|\-)\S/g, function(a) { return a.toUpperCase(); }) 178 .replace(/\(.*\)/g, '') 179 .replace(/\-\>.*/g, '') 180 .replace(/[^a-zA-Z]/g, ''); 181 return(card.charAt(0).toLowerCase() + card.slice(1)); 182 }; 183 184 /** 185 * Sets the effect strength of the "balanced" generator. 186 * The default value is 1.0; set this to 0.0 to get a 187 * generator that weights everything uniformly. Set this 188 * to a larger number to really exaggerate the selection 189 * towards balanced sets, and set this to a negative number 190 * to skew away from balanced sets. 191 * 192 * @param {Number} value The balance factor. 193 */ 194 DominionSetGenerator.prototype.setBalanceFactor = function(value) { 195 this.balanceFactor = value; 196 }; 197 198 /** 199 * Gets the effect strength of the "balanced" generator. 200 * 201 * @return {Number} The balance factor. 202 */ 203 DominionSetGenerator.prototype.getBalanceFactor = function() { 204 return this.balanceFactor; 205 }; 206 207 /** 208 /** 209 * Sets the size of the set to be generated. 210 * 211 * @private 212 * @param {Number} value The size of the set to be generated. 213 */ 214 DominionSetGenerator.prototype.setMaxSetSize_ = function(value) { 215 this.maxSetSize = value; 216 }; 217 218 /** 219 * Gets the size of the set to be generated. 220 * 221 * @return {Number} The size of the set to be generated. 222 */ 223 DominionSetGenerator.prototype.getMaxSetSize = function() { 224 return this.maxSetSize; 225 }; 226 227 /** 228 * Gets the set that is currently being generated. 229 * Note that 230 * generateSet() returns the set anyway, so you might never need 231 * to call this unless you need to see the same set again. 232 * 233 * @return {Object} The set of cards, in the form of an Object 234 * where the keys are Strings with the names of cards. 235 */ 236 DominionSetGenerator.prototype.getCardset = function() { 237 return (this.cardset); 238 }; 239 240 /** 241 * Gets the set that is currently being generated, in Goko camelCase form. 242 * Note that 243 * generateSet() returns the set anyway, so you might never need 244 * to call this unless you need to see the same set again. 245 * 246 * @return {Object} The set of cards, in the form of an Array 247 * where the contents are Strings with the names of cards. 248 */ 249 DominionSetGenerator.prototype.getGokoCardset = function() { 250 var answer = new Array(); 251 for (var card in this.cardset) { 252 var gcard = card.toLowerCase() 253 .replace(/(?:^|\s|\-)\S/g, function(a) { return a.toUpperCase(); }) 254 .replace(/\(.*\)/g, '') 255 .replace(/\-\>.*/g, '') 256 .replace(/[^a-zA-Z]/g, ''); 257 258 answer.push(gcard.charAt(0).toLowerCase() + gcard.slice(1)); 259 } 260 return answer; 261 }; 262 263 /** 264 * Clears the internal log. 265 */ 266 DominionSetGenerator.prototype.clearLog = function() { 267 this.logData = new Array(); 268 }; 269 270 /** 271 * Gets the internal log. 272 * @return {String} The internal log as one long string, 273 * with internal line-breaks. 274 */ 275 DominionSetGenerator.prototype.getLog = function() { 276 return this.logData.join('\n'); 277 }; 278 279 /** 280 * Adds a line to the internal log. 281 * 282 * @private 283 * @param {String} text The log line to add. 284 */ 285 DominionSetGenerator.prototype.log_ = function(text) { 286 this.logData.push(text); 287 }; 288 289 /** 290 * Dumps the internal log to the Javascript Console (as multiple lines). 291 */ 292 DominionSetGenerator.prototype.dumpLogToConsole = function() { 293 294 // Protection against the console not existing. 295 if (!window.console) { 296 (function() { 297 var names = ['log', 'debug', 'info', 'warn', 'error', 298 'assert', 'dir', 'dirxml', 'group', 'groupEnd', 'time', 299 'timeEnd', 'count', 'trace', 'profile', 'profileEnd'], 300 i, l = names.length; 301 302 window.console = {}; 303 304 for (i = 0; i < l; i++) { 305 window.console[names[i]] = function() {}; 306 } 307 }()); 308 } 309 310 for (var i = 0; i < this.logData.length; ++i) { 311 window.console.log(this.logData[i]); 312 } 313 }; 314 315 /** 316 * Logs a description of weights. 317 * 318 * @private 319 * @param {Number} len Integer showing number of cards (on each end) to log. 320 */ 321 DominionSetGenerator.prototype.logWeights_ = function(len) { 322 var threshhold = 0; 323 var sorted = Object.keys(this.sgd.cardNames); 324 var w = this.weight; 325 sorted.sort(function(a, b) { return (w[b] - w[a]); }); 326 327 this.log_(' ... Top ' + len + ' weights:'); 328 for (var lcv = 0; lcv < len; ++lcv) { 329 var card = sorted[lcv]; 330 this.log_(' ... ... ' + card + ' (' + this.weight[card] + 331 ') [' + this.commentary[card] + ']'); 332 threshhold = this.weight[card]; 333 } 334 var index = len; 335 var ties = 0; 336 while (this.weight[sorted[index]] == threshhold && index < sorted.length) { 337 ties++; 338 index++; 339 } 340 if (ties > 0) { 341 this.log_(' ... ... and ' + ties + ' other' + ((ties == 1) ? '' : 's')); 342 } 343 344 this.log_(' ... Bottom ' + len + ' weights:'); 345 346 index = sorted.length - 1; 347 while (this.weight[sorted[index]] == 0 && index >= 0) { 348 index--; 349 } 350 var lcv; 351 for (lcv = index; lcv >= index - len && lcv >= 0; --lcv) { 352 var card = sorted[lcv]; 353 this.log_(' ... ... ' + card + ' (' + this.weight[card] + 354 ') [' + this.commentary[card] + ']'); 355 threshhold = this.weight[card]; 356 } 357 ties = 0; 358 threshhold = this.weight[sorted[lcv]]; 359 while (this.weight[sorted[lcv]] == threshhold && lcv >= 0) { 360 ties++; 361 lcv--; 362 } 363 if (ties > 0) { 364 this.log_(' ... ... and ' + ties + ' other' + ((ties == 1) ? '' : 's')); 365 } 366 }; 367 368 /** 369 * Exponentiation convenience function. 370 * 371 * @private 372 * @param {Number} base Base. 373 * @param {Number} exp Exponent. 374 * @return {Number} The power! 375 */ 376 DominionSetGenerator.prototype.power_ = function(base, exp) { 377 return Math.exp(exp * Math.log(base)); 378 }; 379 380 381 /** 382 * Finds a random card (with the appropriate weights). 383 * If the weights are weird enough that there's a problem, returns 'ERROR'. 384 * 385 * @private 386 * @return {String} The name of the random card. 387 */ 388 DominionSetGenerator.prototype.randCard_ = function() { 389 // returns a randomly-chosen card based on the current set of weights. 390 var totalWeight = 0; 391 for (var card in this.sgd.cardNames) { 392 totalWeight += this.weight[card]; 393 } 394 var randValue = totalWeight * Math.random(); 395 for (var card in this.sgd.cardNames) { 396 if (randValue < this.weight[card]) { 397 return card; 398 } else { 399 randValue -= this.weight[card]; 400 } 401 } 402 return 'ERROR'; 403 }; 404 405 /** 406 * For the supplied attribute, add up the values of that attribute for all 407 * cards in the current set, and return that. 408 * 409 * @private 410 * @param {String} attribute The attribute to look at. 411 * @return {Number} The sum of the values of the attribute in the current set. 412 */ 413 DominionSetGenerator.prototype.getCardsetValue_ = function(attribute) { 414 var answer = 0; 415 for (var card in this.cardset) { 416 try { 417 if (this.sgd.cardData[card][attribute] != undefined) 418 answer += this.sgd.cardData[card][attribute]; 419 } catch (err) { 420 throw this.logData + "\nCan't get attribute " + attribute + " of " + card; 421 } 422 } 423 return answer; 424 }; 425 426 /** 427 * Calculate the correct multiplier for the given card, attribute pair. 428 * 429 * @private 430 * @param {String} card The card to calculate for. 431 * @param {String} attribute The card to calculate for. 432 * @param {Boolean} firstCard Is this the first card to be generated? 433 * @return {Number} The calculated multiplier. 434 */ 435 DominionSetGenerator.prototype.calcBalanceMultiplier_ = function(card, attribute, firstCard) { 436 var value = this.sgd.cardData[card][attribute]; 437 if (value == undefined || value == 0) return 1.0; 438 439 // for the first card, ignore any non-user-defined weights. 440 if (firstCard && this.isDefault[attribute]) return 1.0; 441 442 var answer = 1.0; 443 var attData = this.getAttributeData(attribute); 444 var cardsetValue = this.getCardsetValue_(attribute); 445 if (cardsetValue < attData.min) { 446 var multiplier = attData.minW; 447 answer *= multiplier; 448 } 449 if (cardsetValue + value > attData.max) { 450 var multiplier = this.power_(attData.maxW, cardsetValue); 451 answer *= multiplier; 452 } 453 return answer; 454 } 455 456 457 /** 458 * Calculate the correct multiplier for the given card. 459 * For each card, we only keep the most extreme multipliers using 460 * the following algorithm: 461 * (1) Sort all the multipliers. 462 * (2) Look at the two multipliers at the ends. If one is 463 * > 1.0 and one is < 1.0, keep both. 464 * (3) After all that, keep the three most extreme ones. 465 * 466 * @private 467 * @param {String} card The card to calculate for. 468 * @param {Boolean} firstCard Is this the first card to be generated? 469 * @return {Number} The calculated multiplier. 470 */ 471 DominionSetGenerator.prototype.calcEffectiveMultiplier_ = function(card, firstCard) { 472 // returns the appropriate multiplier for the card. 473 var attList = new Array(); 474 var multipliers = new Object(); 475 for (var attribute in this.sgd.cardAttributes) { 476 var multiplier = this.calcBalanceMultiplier_(card, attribute, firstCard); 477 if (multiplier != 1.0) { 478 attList.push(attribute); 479 multipliers[attribute] = multiplier; 480 } 481 } 482 attList.sort(function(a, b) { 483 return (multipliers[b] - multipliers[a]); 484 }); 485 486 var att; 487 var weight = 1.0; 488 while (attList.length > 0 && multipliers[attList[0]] > 1.0 489 && multipliers[attList[attList.length-1]] < 1.0) { 490 att = attList.shift(); 491 weight *= multipliers[att]; 492 this.commentary[card] += 'x' + multipliers[att] + 493 ' (' + att + '); '; 494 att = attList.pop(); 495 weight *= multipliers[att]; 496 this.commentary[card] += 'x' + multipliers[att] + 497 ' (' + att + '); '; 498 } 499 500 var extra = 0; 501 while (attList.length > 0 && extra < 3) { 502 if (multipliers[attList[0]] > 1.0) { 503 att = attList.shift(); 504 } else { 505 att = attList.pop(); 506 } 507 ++extra; 508 weight *= multipliers[att]; 509 this.commentary[card] += 'x' + multipliers[att] + 510 ' (' + att + '); '; 511 } 512 513 this.commentary[card] += 'ignoring ' + attList.length + ' attribs :'; 514 515 while (attList.length > 0) { 516 att = attList.shift(); 517 this.commentary[card] += 'x' + multipliers[att] + 518 ' (' + att + '); '; 519 } 520 521 if (isNaN(weight)) { 522 this.log_('Error: NaN in ' + card + ' ' + this.commentary[card]); 523 } 524 525 if (weight > 1e+200) { 526 return 1e+200; 527 } else { 528 return weight; 529 } 530 }; 531 532 /** 533 * Calculate the correct weight for the given card. 534 * Right now it's the same as the balance multiplier, 535 * with chosen cards and unowned cards filtered out. 536 * This will get more complex in a later version. 537 * 538 * @private 539 * @param {String} card The card to calculate for. 540 * @param {Boolean} firstCard Is this the first card to be generated? 541 * @return {Number} The calculated weight. 542 */ 543 DominionSetGenerator.prototype.calcWeight_ = function(card, firstCard) { 544 if (card in this.cardset) { 545 return 0; // already have the card 546 } 547 if (!(card in this.owned)) { 548 return 0; // don't own the card 549 } 550 return this.calcEffectiveMultiplier_(card, firstCard); 551 }; 552 553 /** 554 * Update the weights for all the cards. 555 * 556 * @private 557 * @param {Boolean} firstCard Is this the first card to be generated? 558 */ 559 DominionSetGenerator.prototype.updateWeights_ = function(firstCard) { 560 for (var card in this.sgd.cardNames) { 561 this.commentary[card] = ''; 562 this.weight[card] = this.calcWeight_(card, firstCard); 563 } 564 }; 565 566 /// externally visible functions 567 568 /** 569 * Get the current data for the given attribute. 570 * 571 * @param {String} attr Which attribute to get. 572 * @return {DominionSetGeneratorAttributeData} The data for the given attribute. 573 */ 574 DominionSetGenerator.prototype.getAttributeData = function(attr) { 575 if (this.isDefault[attr]) { 576 577 var min = this.sgd.Min[attr]; 578 var max = this.sgd.Max[attr]; 579 var minW = this.power_(this.sgd.MinW[attr], this.balanceFactor); 580 var maxW = this.power_(this.sgd.MaxW[attr], this.balanceFactor); 581 var notes = this.sgd.Notes[attr]; 582 583 // all ranges become "dense" (min=0, max=0, minW = 1, maxW > 1) 584 // if balanceFactor < 0; 585 586 if (this.balanceFactor < 0 && (min != 0 || max != 0)) { 587 min = 0; 588 max = 0; 589 maxW = 1.0/minW; 590 minW = 1; 591 } 592 593 return (new DominionSetGeneratorAttributeData(min, max, minW, maxW, notes)); 594 } else { 595 return (new DominionSetGeneratorAttributeData( 596 this.tMin[attr], 597 this.tMax[attr], 598 this.tMinW[attr], 599 this.tMaxW[attr], 600 this.tNotes[attr])); 601 } 602 }; 603 604 /** 605 * Set the current data for the given attribute. 606 * 607 * @param {String} attr Which attribute to set. 608 * @param {DominionSetGeneratorAttributeData} data The data 609 * for the given attribute. 610 */ 611 DominionSetGenerator.prototype.setAttributeData = function(attr, data) { 612 this.tMin[attr] = data.min; 613 this.tMax[attr] = data.max; 614 this.tMinW[attr] = data.minW; 615 this.tMaxW[attr] = data.maxW; 616 this.tNotes[attr] = data.notes; 617 this.isDefault[attr] = false; 618 }; 619 620 /** 621 * Set the given attribute to 'Avoid' -- don't pick these cards 622 * if at all possible. 623 * 624 * @param {String} attr Which attribute to set. 625 */ 626 DominionSetGenerator.prototype.attrAvoid = function(attr) { 627 this.tMin[attr] = 10 * this.maxSetSize; 628 this.tMax[attr] = 10 * this.maxSetSize; 629 this.tMinW[attr] = 0.00001; 630 this.tMaxW[attr] = 100000; 631 this.tNotes[attr] = 'User-modified: Avoid'; 632 this.isDefault[attr] = false; 633 }; 634 635 /** 636 * Set the given attribute to 'Fewer' -- try to not pick these 637 * cards (by a factor of 10, currently). 638 * 639 * @param {String} attr Which attribute to set. 640 */ 641 DominionSetGenerator.prototype.attrFewer = function(attr) { 642 this.tMin[attr] = 10 * this.maxSetSize; 643 this.tMax[attr] = 10 * this.maxSetSize; 644 this.tMinW[attr] = 0.1; 645 this.tMaxW[attr] = 10; 646 this.tNotes[attr] = 'User-modified: Fewer'; 647 this.isDefault[attr] = false; 648 }; 649 650 /** 651 * Set the given attribute to 'More' -- don't pick these cards 652 * if at all possible. 653 * 654 * @param {String} attr Which attribute to set. 655 */ 656 DominionSetGenerator.prototype.attrMore = function(attr) { 657 this.tMin[attr] = 10 * this.maxSetSize; 658 this.tMax[attr] = 10 * this.maxSetSize; 659 this.tMinW[attr] = 10; 660 this.tMaxW[attr] = 0.1; 661 this.tNotes[attr] = 'User-modified: More'; 662 this.isDefault[attr] = false; 663 }; 664 665 /** 666 * Set the given attribute to 'Always' -- try to not pick these 667 * cards (by a factor of 10, currently). 668 * 669 * @param {String} attr Which attribute to set. 670 */ 671 DominionSetGenerator.prototype.attrAlways = function(attr) { 672 this.tMin[attr] = 10 * this.maxSetSize; 673 this.tMax[attr] = 10 * this.maxSetSize; 674 this.tMinW[attr] = 100000; 675 this.tMaxW[attr] = 0.00001; 676 this.tNotes[attr] = 'User-modified: Always'; 677 this.isDefault[attr] = false; 678 }; 679 680 /** 681 * Set the given attribute to 'Range' -- try to pick so that the 682 * attribute shows up in a certain range. 683 * 684 * @param {String} attr Which attribute to set. 685 * @param {Number} min Try to add cards to make the set exceed this value. 686 * @param {Number} max Try not to add cards that make the set exceed this value. 687 * @param {Number} importance The multiplier for this attribute. 688 */ 689 DominionSetGenerator.prototype.attrRange = function(attr, min, 690 max, importance) { 691 this.tMin[attr] = min; 692 this.tMax[attr] = max; 693 this.tMinW[attr] = importance; 694 this.tMaxW[attr] = 1.0 / importance; 695 this.tNotes[attr] = 'User-modified: Range'; 696 this.isDefault[attr] = false; 697 }; 698 699 /** 700 * Set the given attribute to 'Dense' -- try to pick so that the 701 * attribute either doesn't show up or shows up en masse. 702 * 703 * @param {String} attr Which attribute to set. 704 * @param {Number} importance The multiplier for this attribute. 705 */ 706 DominionSetGenerator.prototype.attrDense = function(attr, importance) { 707 this.tMin[attr] = 0; 708 this.tMax[attr] = 0; 709 this.tMinW[attr] = 1; 710 this.tMaxW[attr] = importance; 711 this.tNotes[attr] = 'User-modified: Dense'; 712 this.isDefault[attr] = false; 713 }; 714 715 /** 716 * Set the given attribute to 'Sparse' -- try to pick so that the 717 * attribute either doesn't show up or shows up en masse. 718 * 719 * @param {String} attr Which attribute to set. 720 * @param {Number} importance The multiplier for this attribute. 721 */ 722 DominionSetGenerator.prototype.attrSparse = function(attr, importance) { 723 this.tMin[attr] = 0; 724 this.tMax[attr] = 0; 725 this.tMinW[attr] = 1; 726 this.tMaxW[attr] = importance; 727 this.tNotes[attr] = 'User-modified: Sparse'; 728 this.isDefault[attr] = false; 729 }; 730 731 /** 732 * Reset the current data for the given attribute to 733 * the original (internal) value. 734 * 735 * @param {String} attr Which attribute to set. 736 */ 737 DominionSetGenerator.prototype.resetAttribute = function(attr) { 738 this.isDefault[attr] = true; 739 }; 740 741 /** 742 * Reset the current data for all attributes. 743 */ 744 DominionSetGenerator.prototype.resetAllAttributes = function() { 745 for (attr in this.sgd.cardAttributes) { 746 this.resetAttribute(attr); 747 } 748 }; 749 750 /** 751 * Clear the current set. 752 * 753 */ 754 DominionSetGenerator.prototype.clearSet = function() { 755 this.cardset = new Object(); 756 }; 757 758 /** 759 * Canonicalize a card string. 760 * Try to find a card in sgd.cardNames that matches the given string. 761 * Right now we just lowercase everything and remove alphanumerics and 762 * see if things match. 763 * 764 * @private 765 * @param {String} userCard A name to try to match. 766 * @return {String} The matching card, or "" if no match. 767 */ 768 DominionSetGenerator.prototype.canonName_ = function(userCard) { 769 for (var realCard in this.sgd.cardNames) { 770 if (userCard.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '') == 771 realCard.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '')) { 772 return realCard; 773 } 774 } 775 return ""; 776 }; 777 778 779 /** 780 * Tells the generator what cards are 'owned'. 781 * Cards that are 782 * not owned have zero chance of being picked (as opposed to 783 * the 'Try to Exclude' setting, which sets the probability of being 784 * picked to be very small but not zero). 785 * Note that if you own fewer cards than the size of the set, the 786 * generator may give unexpected results! 787 * 788 * @param {Object} data An Object where the keys are the names 789 * of the cards 'owned'. 790 * @return {Object} An Object where the keys are the cards that weren't 791 * understodd. 792 */ 793 DominionSetGenerator.prototype.setOwned = function(data) { 794 var failed = new Object(); 795 796 this.owned = new Object(); 797 for (userCard in data) { 798 var realCard = this.canonName_(userCard); 799 if (realCard != "") { 800 this.owned[realCard] = true; 801 } else { 802 failed[userCard] = true; 803 } 804 } 805 806 return failed; 807 }; 808 809 /** 810 * Put in some cards into the set. 811 * 812 * @param {Object} cards An Object where the keys are the cards that have to be 813 * put into the set. A card will be considered a "match" if after being 814 * changed to all lower-case with non-alphanumeric characters removed, 815 * it is the same string in sgd.cardNames. For example, "Worker's Village" 816 * matches "workersvillage". 817 * @return {Object} An Object where the keys are the cards that weren't 818 * understodd. 819 */ 820 DominionSetGenerator.prototype.putIntoSet = function(cards) { 821 var failed = new Object(); 822 823 for (var userCard in cards) { 824 var realCard = this.canonName_(userCard); 825 if (realCard != "") { 826 this.cardset[realCard] = 1; 827 } else { 828 failed[userCard] = true; 829 } 830 } 831 832 return failed; 833 }; 834 835 /** 836 * Get a random card from the chosen set (uniformly). 837 * 838 * @private 839 */ 840 DominionSetGenerator.prototype.randCardInSet_ = function() { 841 var setSize = Object.keys(this.cardset).length; 842 var randValue = Math.floor(setSize * Math.random()); 843 for (var card in this.cardset) { 844 if (randValue == 0) { 845 return card; 846 } 847 randValue--; 848 } 849 // shouldn't get here 850 throw("Error in randCardInSet: " + setSize + " " + randValue); 851 }; 852 853 854 /** 855 * Fill out a set of cards, assuming that the internal set is partially done. 856 * 857 * @param {Number} maxSetSize Number of cards to put in the set. 858 * @return {Object} An Object where all the keys are the cards that are chosen. 859 * (The values will be 1, but we might change this to something more 860 * fancy in the future). 861 */ 862 DominionSetGenerator.prototype.completeSet = function(maxSetSize) { 863 // generates a set of the given size and returns it. 864 865 this.setMaxSetSize_(maxSetSize); 866 this.weight = new Object(); 867 868 for (var card in this.sgd.cardNames) { 869 this.weight[card] = 1.0; 870 } 871 872 while (Object.keys(this.cardset).length < this.getMaxSetSize()) { 873 this.updateWeights_(Object.keys(this.cardset).length == 0); 874 var card = this.randCard_(); 875 if (card == 'ERROR') { 876 this.log_('Aborting ... ERROR from randCard_'); 877 break; 878 } 879 this.cardset[card] = 1; 880 if (Object.keys(this.cardset).length > 0) { 881 this.logWeights_(10); 882 } 883 this.log_('Adding ' + card + ' (' + this.weight[card] + ') to set.'); 884 } 885 886 this.useShelters = (this.sgd.cardData[this.randCardInSet_()]['DarkAges'] == 1); 887 this.platinumColony = (this.sgd.cardData[this.randCardInSet_()]['Prosperity'] == 1); 888 889 // get the Bane 890 this.updateWeights_(false); 891 for (var card in this.sgd.cardNames) { 892 if (this.sgd.cardData[card]['Cost2'] != 1 && this.sgd.cardData[card]['Cost3'] != 1) { 893 this.weight[card] = 0; 894 } 895 } 896 this.youngWitchBane = this.randCard_(); 897 898 // get Donline rules 899 for (var card in this.sgd.cardNames) { 900 if (card in this.owned) { 901 this.weight[card] = 1.0; 902 } else { 903 this.weight[card] = 0.0; 904 } 905 } 906 this.useSheltersDonline = (this.sgd.cardData[this.randCard_()]['DarkAges'] == 1); 907 this.platinumColonyDonline = (this.sgd.cardData[this.randCard_()]['Prosperity'] == 1); 908 909 return this.cardset; 910 }; 911 912 /** 913 * Generate a new set of cards, using the most current values. 914 * 915 * @param {Number} maxSetSize Number of cards to put in the set. 916 * @return {Object} An Object where all the keys are the cards that are chosen. 917 * (The values will be 1, but we might change this to something more 918 * fancy in the future). 919 */ 920 DominionSetGenerator.prototype.generateSet = function(maxSetSize) { 921 this.clearSet(); 922 return this.completeSet(maxSetSize); 923 }; 924