From ead8996d162bebb830c214c619e591e01380fd79 Mon Sep 17 00:00:00 2001 From: Waleed Khan Date: Sat, 14 May 2016 15:37:48 -0400 Subject: [PATCH 1/2] Correct Makefile dependencies to be parallelizable This is useful if you want to run `make` with the `-j` option to speed up things somewhat. (It also helps to not confuse people like me who have `make` aliased to `make -j5`, for whom the build process doesn't quite work.) --- Makefile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 828d951..9061b02 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ +.PHONY: all install clean cards score js all: install clean cards score js node := ${CURDIR}/node_modules +all_sets := ${CURDIR}/data/AllSets.json +traceur := ${node}/.bin/traceur + +${traceur}: install install: npm install @@ -14,22 +19,22 @@ install: ln -sf ${node}/utils/utils.js public/lib clean: - rm -f data/AllSets.json + rm -f ${all_sets} -cards: data/AllSets.json +cards: ${all_sets} node src/make cards custom: node src/make custom -data/AllSets.json: - curl -so data/AllSets.json https://mtgjson.com/json/AllSets.json +${all_sets}: + curl -so ${all_sets} https://mtgjson.com/json/AllSets.json score: -node src/make score #ignore errors -js: - node_modules/.bin/traceur --out public/lib/app.js public/src/init.js +js: ${traceur} ${all_sets} + ${traceur} --out public/lib/app.js public/src/init.js run: js node run From d04e364019a7d0053ffd4c3a233c52b5a51c5c6d Mon Sep 17 00:00:00 2001 From: Waleed Khan Date: Sat, 14 May 2016 18:22:34 -0700 Subject: [PATCH 2/2] Implement automatic land suggestion Note that this commit requires a rebuild of the cards JSON files, as it now records each card's mana cost. --- public/src/app.js | 1 + public/src/cards.js | 110 ++++++++++++++++++++++++++++++ public/src/components/settings.js | 18 ++++- src/make/cards.js | 1 + 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/public/src/app.js b/public/src/app.js index 12935e6..9a8b229 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -33,6 +33,7 @@ let App = { beep: false, chat: true, cols: false, + deckSize: 40, filename: 'filename', filetype: 'txt', side: false, diff --git a/public/src/cards.js b/public/src/cards.js index f92c01a..91749d5 100644 --- a/public/src/cards.js +++ b/public/src/cards.js @@ -10,6 +10,13 @@ let Cards = { } export let BASICS = Object.keys(Cards) +let COLORS_TO_LANDS = { + 'W': 'Plains', + 'U': 'Island', + 'B': 'Swamp', + 'R': 'Mountain', + 'G': 'Forest', +} for (let name in Cards) Cards[name] = {name, @@ -140,6 +147,109 @@ let events = { delete Zones[zoneName][cardName] App.update() }, + deckSize(e) { + let n = Number(e.target.value) + if (n && n > 0) + App.state.deckSize = n + App.update() + }, + suggestLands() { + // Algorithm: count the number of mana symbols appearing in the costs of + // the cards in the pool, then assign lands roughly commensurately. + let colors = ['W', 'U', 'B', 'R', 'G'] + let colorRegex = /\{[^}]+\}/g + let manaSymbols = {} + colors.forEach(x => manaSymbols[x] = 0) + + // Count the number of mana symbols of each type. + for (let card of Object.keys(Zones['main'])) { + let quantity = Zones['main'][card] + card = Cards[card] + + if (!card.manaCost) + continue + let cardManaSymbols = card.manaCost.match(colorRegex) + + for (let color of colors) + for (let symbol of cardManaSymbols) + // Test to see if '{U}' contains 'U'. This also handles things like + // '{G/U}' triggering both 'G' and 'U'. + if (symbol.indexOf(color) !== -1) + manaSymbols[color] += quantity + } + + _resetLands() + // NB: We could set only the sideboard lands of the colors we are using to + // 5, but this reveals information to the opponent on Cockatrice (and + // possibly other clients) since it tells the opponent the sideboard size. + colors.forEach(color => Zones['side'][COLORS_TO_LANDS[color]] = 5) + + colors = colors.filter(x => manaSymbols[x] > 0) + colors.forEach(x => manaSymbols[x] = Math.max(3, manaSymbols[x])) + colors.sort((a, b) => manaSymbols[b] - manaSymbols[a]) + + // Round-robin choose the lands to go into the deck. For example, if the + // mana symbol counts are W: 2, U: 2, B: 1, cycle through the sequence + // [Plains, Island, Swamp, Plains, Island] infinitely until the deck is + // finished. + // + // This has a few nice effects: + // + // * Colors with greater mana symbol counts get more lands. + // + // * When in a typical two color deck adding 17 lands, the 9/8 split will + // be in favor of the color with slightly more mana symbols of that + // color. + // + // * Every color in the deck is represented, if it is possible to do so + // in the remaining number of cards. + // + // * Because of the minimum mana symbol count for each represented color, + // splashing cards doesn't add exactly one land of the given type + // (although the land count may still be low for that color). + // + // * The problem of deciding how to round land counts is now easy to + // solve. + let manaSymbolsToAdd = colors.map(color => manaSymbols[color]) + let colorsToAdd = [] + for (let i = 0; true; i = (i + 1) % colors.length) { + if (manaSymbolsToAdd.every(x => x === 0)) + break + if (manaSymbolsToAdd[i] === 0) + continue + colorsToAdd.push(colors[i]) + manaSymbolsToAdd[i]-- + } + + let mainDeckSize = Object.keys(Zones['main']) + .map(x => Zones['main'][x]) + .reduce((a, b) => a + b) + let landsToAdd = App.state.deckSize - mainDeckSize + + let j = 0 + for (let i = 0; i < landsToAdd; i++) { + let color = colorsToAdd[j] + let land = COLORS_TO_LANDS[color] + if (!Zones['main'].hasOwnProperty(land)) + Zones['main'][land] = 0 + Zones['main'][land]++ + + j = (j + 1) % colorsToAdd.length + } + + App.update() + }, + resetLands() { + _resetLands() + App.update() + }, +} + +function _resetLands() { + Object.keys(COLORS_TO_LANDS).forEach((key) => { + let land = COLORS_TO_LANDS[key] + Zones['main'][land] = Zones['side'][land] = 0 + }) } for (let event in events) diff --git a/public/src/components/settings.js b/public/src/components/settings.js index 4f8f1dd..7995c10 100644 --- a/public/src/components/settings.js +++ b/public/src/components/settings.js @@ -24,12 +24,28 @@ function Lands() { inputs) }) + let suggest = d.tr({}, + d.td({}, 'deck size'), + d.td({}, d.input({ + min: 0, + onChange: App._emit('deckSize'), + type: 'number', + value: App.state.deckSize, + })), + d.td({ colSpan: 2 }, d.button({ + onClick: App._emit('resetLands') + }, 'reset lands')), + d.td({colSpan: 2 }, d.button({ + onClick: App._emit('suggestLands') + }, 'suggest lands'))) + return d.table({}, d.tr({}, d.td(), symbols), main, - side) + side, + suggest) } function Sort() { diff --git a/src/make/cards.js b/src/make/cards.js index b7d9fd3..2348249 100644 --- a/src/make/cards.js +++ b/src/make/cards.js @@ -245,6 +245,7 @@ function doCard(rawCard, cards, code, set) { colors[0].toLowerCase() cards[name] = { color, name, + manaCost: rawCard.manaCost, type: rawCard.types[rawCard.types.length - 1], cmc: rawCard.cmc || 0, sets: {