From eb9b8958eae3311290e8ab063c0ba8fe9b400301 Mon Sep 17 00:00:00 2001 From: sujeom Date: Wed, 15 Apr 2020 22:40:44 -0400 Subject: [PATCH 01/31] darkmode added using darkmodejs --- css/styles.css | 24 ++++++++++++++++++++++++ index.html | 2 ++ 2 files changed, 26 insertions(+) diff --git a/css/styles.css b/css/styles.css index 8ccf924..ff3e08d 100644 --- a/css/styles.css +++ b/css/styles.css @@ -423,3 +423,27 @@ input[type=number] { min-height: 40px; } } + +/*Darkmodjs*/ +.darkmode-layer, .darkmode-toggle { + z-index: 1; +} + +div.darkmode-background{ + background: #DEF2D9; + background-image: + radial-gradient(#fff 20%, transparent 0), + radial-gradient(#fff 20%, transparent 0); + position: inherit; +} + +body.darkmode--activated{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: 'Varela Round', sans-serif; + width: 100%; + margin: 0 0 0 0; + position: absolute +} diff --git a/index.html b/index.html index d4dc76e..0a106be 100644 --- a/index.html +++ b/index.html @@ -289,6 +289,8 @@ + + From 10306698f192ec9ec2f935a0ee5a6e6f7ece6f55 Mon Sep 17 00:00:00 2001 From: sujeom Date: Thu, 16 Apr 2020 01:07:33 -0400 Subject: [PATCH 02/31] addressd red coloring --- css/styles.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/css/styles.css b/css/styles.css index ff3e08d..2c60e0c 100644 --- a/css/styles.css +++ b/css/styles.css @@ -447,3 +447,12 @@ body.darkmode--activated{ margin: 0 0 0 0; position: absolute } + +body.darkmode--activated a, body.darkmode--activated b, body.darkmode--activated input[type=number]:not(:placeholder-shown){ + color: #b1301d; + +} + + body.darkmode--activated input[type="radio"]:checked+label{ + background: #565655; +} From 65262dad463f678d9fc01cb7733aa493e7a4a428 Mon Sep 17 00:00:00 2001 From: sujeom Date: Fri, 17 Apr 2020 07:03:10 -0400 Subject: [PATCH 03/31] Changed color palette from purple/navy to maastricht blue/black --- css/styles.css | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/css/styles.css b/css/styles.css index 6ecb505..3dc40c5 100644 --- a/css/styles.css +++ b/css/styles.css @@ -438,7 +438,7 @@ input[type=number] { } div.darkmode-background{ - background: #DEF2D9; + background: #fef0e3; background-image: radial-gradient(#fff 20%, transparent 0), radial-gradient(#fff 20%, transparent 0); @@ -446,21 +446,40 @@ div.darkmode-background{ } body.darkmode--activated{ - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-family: 'Varela Round', sans-serif; - width: 100%; - margin: 0 0 0 0; - position: absolute -} - -body.darkmode--activated a, body.darkmode--activated b, body.darkmode--activated input[type=number]:not(:placeholder-shown){ - color: #b1301d; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: 'Varela Round', sans-serif; + width: 100%; + margin: 0 0 0 0; + position: absolute +} + +body.darkmode--activated div[class^="dialog-box"], +body.darkmode--activated div[class^="nook-phone"], +body.darkmode--activated form[class^="input__form"], +{ + background: #fee0c4; + color: #010F1D; +} + +body.darkmode--activated svg[class^="waves"]{ + background: #fef0e3; +} + +body.darkmode--activated a, +body.darkmode--activated b, +body.darkmode--activated input[type=number]:not(:placeholder-shown){ + color: #586472; } - body.darkmode--activated input[type="radio"]:checked+label{ - background: #565655; +body.darkmode--activated input[type="radio"]+label, +body.darkmode--activated input[type=number]:placeholder-shown{ + background: #bda284; +} + +body.darkmode--activated input[type="radio"]:checked+label{ + background: #7b6955; } From f7892255b62be05155c8b3cf8765c6e6779ea438 Mon Sep 17 00:00:00 2001 From: sujeom Date: Fri, 17 Apr 2020 19:31:21 -0400 Subject: [PATCH 04/31] increase brightness of note text for better visability --- css/styles.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/styles.css b/css/styles.css index 3dc40c5..c99e7c3 100644 --- a/css/styles.css +++ b/css/styles.css @@ -483,3 +483,7 @@ body.darkmode--activated input[type=number]:placeholder-shown{ body.darkmode--activated input[type="radio"]:checked+label{ background: #7b6955; } + +body.darkmode--activated i{ + color: #7b6955; +} From 0c6e06f32898125ef43c589db750f4229863df3e Mon Sep 17 00:00:00 2001 From: KeruWolf <63780753+KeruWolf@users.noreply.github.com> Date: Sun, 19 Apr 2020 22:22:59 +0200 Subject: [PATCH 05/31] ES language fixes I spotted a couple of typos checking the Spanish live version and changed them. --- locales/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index 4538693..9dde2f4 100644 --- a/locales/es.json +++ b/locales/es.json @@ -28,7 +28,7 @@ "description": "¿Cuál fue el precio más alto que alcanzaron los nabos en tu isla durante la semana? (Si esta ha sido tu primera vez comprando nabos, este campo se desactivará)", "open": { "am": "AM - De 8:00 a 11:59", - "pm": "PM - De 12:00 a 10:00" + "pm": "PM - De 12:00 a 22:00" }, "copy-permalink": "Copiar permalink", "permalink-copied": "¡Permalink copiado!", @@ -65,7 +65,7 @@ "chart": { "input": "Precio de entrada", "minimum": "Mínimo garantizado", - "maximum": "´Máximo potencial" + "maximum": "Máximo potencial" } }, "textbox": { From a6483c408ebb5c7b177798fd43c8a32d8b028850 Mon Sep 17 00:00:00 2001 From: Ryan Carbotte Date: Sun, 19 Apr 2020 17:32:19 -0500 Subject: [PATCH 06/31] fix: Improve Spanish translation of week buy price Closes #193 --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 4538693..45aef74 100644 --- a/locales/es.json +++ b/locales/es.json @@ -25,7 +25,7 @@ "small-spike": "Pico moderado" }, "prices": { - "description": "¿Cuál fue el precio más alto que alcanzaron los nabos en tu isla durante la semana? (Si esta ha sido tu primera vez comprando nabos, este campo se desactivará)", + "description": "¿Cuál fue el precio de los nabos en su isla esta semana? (Si esta ha sido tu primera vez comprando nabos, este campo se desactivará)", "open": { "am": "AM - De 8:00 a 11:59", "pm": "PM - De 12:00 a 10:00" From a96a90c83aec66c48615b2a360dcd2b3069a3b97 Mon Sep 17 00:00:00 2001 From: Splash Date: Mon, 20 Apr 2020 10:01:12 +0800 Subject: [PATCH 07/31] Sort language list alphabetically --- js/translations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/translations.js b/js/translations.js index 5d6d029..762ca13 100644 --- a/js/translations.js +++ b/js/translations.js @@ -19,9 +19,9 @@ i18next ['es-ES', 'Español'], ['fr', 'Français'], ['it', 'Italiano'], - ['ru', 'Русский'], ['ja', '日本語'], ['pt-BR', 'Português'], + ['ru', 'Русский'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'] ], From 7b7e80ddcc015f80f1c97639965722da9ff80e48 Mon Sep 17 00:00:00 2001 From: Splash Date: Mon, 20 Apr 2020 10:41:46 +0800 Subject: [PATCH 08/31] Automatic sorting --- js/translations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/translations.js b/js/translations.js index 762ca13..4bb7306 100644 --- a/js/translations.js +++ b/js/translations.js @@ -24,7 +24,7 @@ i18next ['ru', 'Русский'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'] - ], + ].sort(), languageSelector = $('#language'); languages.map(([code, name]) => { languageSelector.append(``); From 76aee04cc8e7b38ca1829c54b209e4d37b93046f Mon Sep 17 00:00:00 2001 From: Splash Date: Tue, 21 Apr 2020 15:33:05 +0800 Subject: [PATCH 09/31] Fix #204 Fix https://github.com/mikebryant/ac-nh-turnip-prices/issues/204 --- js/chart.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/js/chart.js b/js/chart.js index 83e408c..64e96f4 100644 --- a/js/chart.js +++ b/js/chart.js @@ -18,34 +18,35 @@ const chart_options = { }; function update_chart(input_data, possibilities) { - var ctx = $("#chart"); - - datasets = [ - { + let ctx = $("#chart"), + datasets = [{ label: i18next.t("output.chart.input"), data: input_data.slice(1), fill: false, - }, - { + }, { label: i18next.t("output.chart.minimum"), data: possibilities[0].prices.slice(1).map(day => day.min), fill: false, - }, - { + }, { label: i18next.t("output.chart.maximum"), data: possibilities[0].prices.slice(1).map(day => day.max), fill: "-1", }, - ]; + ], + labels = [i18next.t("weekdays.sunday")].concat(...[i18next.t("weekdays.abr.monday"), i18next.t("weekdays.abr.tuesday"), i18next.t("weekdays.abr.wednesday"), i18next.t("weekdays.abr.thursday"), i18next.t("weekdays.abr.friday"), i18next.t("weekdays.abr.saturday")].map( + day => [i18next.t("times.morning"), + i18next.t("times.afternoon")].map( + time => `${day} ${time}`))); if (chart_instance) { chart_instance.data.datasets = datasets; + chart_instance.data.labels = labels; chart_instance.update(); } else { chart_instance = new Chart(ctx, { data: { datasets: datasets, - labels: [i18next.t("weekdays.sunday"), i18next.t("weekdays.abr.monday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.monday") + " " + i18next.t("times.afternoon"), i18next.t("weekdays.abr.tuesday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.tuesday") + " " + i18next.t("times.afternoon"), i18next.t("weekdays.abr.wednesday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.wednesday") + " " + i18next.t("times.afternoon"), i18next.t("weekdays.abr.thursday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.thursday") + " " + i18next.t("times.afternoon"), i18next.t("weekdays.abr.friday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.friday") + " " + i18next.t("times.afternoon"), i18next.t("weekdays.abr.saturday") + " " + i18next.t("times.morning"), i18next.t("weekdays.abr.saturday") + " " + i18next.t("times.afternoon")], + labels: labels }, options: chart_options, type: "line", From 725bba5ec9c22014ddf75d0bb11731d3ed90547c Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Mon, 20 Apr 2020 23:57:01 +0100 Subject: [PATCH 10/31] refactor: Move Predictor into class --- js/predictions.js | 1220 +++++++++++++++++++++++---------------------- js/scripts.js | 3 +- 2 files changed, 617 insertions(+), 606 deletions(-) diff --git a/js/predictions.js b/js/predictions.js index a6d00d2..80621e9 100644 --- a/js/predictions.js +++ b/js/predictions.js @@ -38,43 +38,14 @@ const PROBABILITY_MATRIX = { const RATE_MULTIPLIER = 10000; -function intceil(val) { - return Math.trunc(val + 0.99999); -} - -function minimum_rate_from_given_and_base(given_price, buy_price) { - return RATE_MULTIPLIER * (given_price - 0.99999) / buy_price; -} - -function maximum_rate_from_given_and_base(given_price, buy_price) { - return RATE_MULTIPLIER * (given_price + 0.00001) / buy_price; -} - -function rate_range_from_given_and_base(given_price, buy_price) { - return [ - minimum_rate_from_given_and_base(given_price, buy_price), - maximum_rate_from_given_and_base(given_price, buy_price) - ]; -} - -function get_price(rate, basePrice) { - return intceil(rate * basePrice / RATE_MULTIPLIER); -} - -function* multiply_generator_probability(generator, probability) { - for (const it of generator) { - yield {...it, probability: it.probability * probability}; - } +function range_length(range) { + return range[1] - range[0]; } function clamp(x, min, max) { return Math.min(Math.max(x, min), max); } -function range_length(range) { - return range[1] - range[0]; -} - function range_intersect(range1, range2) { if (range1[0] > range2[1] || range1[1] < range2[0]) { return null; @@ -89,54 +60,6 @@ function range_intersect_length(range1, range2) { return range_length(range_intersect(range1, range2)); } - -/* - * This corresponds to the code: - * for (int i = start; i < start + length; i++) - * { - * sellPrices[work++] = - * intceil(randfloat(rate_min / RATE_MULTIPLIER, rate_max / RATE_MULTIPLIER) * basePrice); - * } - * - * Would return the conditional probability given the given_prices, and modify - * the predicted_prices array. - * If the given_prices won't match, returns 0. - */ -function generate_individual_random_price( - given_prices, predicted_prices, start, length, rate_min, rate_max) { - rate_min *= RATE_MULTIPLIER; - rate_max *= RATE_MULTIPLIER; - - const buy_price = given_prices[0]; - const rate_range = [rate_min, rate_max]; - let prob = 1; - - for (let i = start; i < start + length; i++) { - let min_pred = get_price(rate_min, buy_price); - let max_pred = get_price(rate_max, buy_price); - if (!isNaN(given_prices[i])) { - if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { - // Given price is out of predicted range, so this is the wrong pattern - return 0; - } - // TODO: How to deal with probability when there's fudge factor? - // Clamp the value to be in range now so the probability won't be totally biased to fudged values. - const real_rate_range = - rate_range_from_given_and_base(clamp(given_prices[i], min_pred, max_pred), buy_price); - prob *= range_intersect_length(rate_range, real_rate_range) / - range_length(rate_range); - min_pred = given_prices[i]; - max_pred = given_prices[i]; - } - - predicted_prices.push({ - min: min_pred, - max: max_pred, - }); - } - return prob; -} - /* * Probability Density Function of rates. * Since the PDF is continuous*, we approximate it by a discrete probability function: @@ -274,618 +197,705 @@ class PDF { } } -/* - * This corresponds to the code: - * rate = randfloat(start_rate_min, start_rate_max); - * for (int i = start; i < start + length; i++) - * { - * sellPrices[work++] = intceil(rate * basePrice); - * rate -= randfloat(rate_decay_min, rate_decay_max); - * } - * - * Would return the conditional probability given the given_prices, and modify - * the predicted_prices array. - * If the given_prices won't match, returns 0. - */ -function generate_decreasing_random_price( - given_prices, predicted_prices, start, length, start_rate_min, - start_rate_max, rate_decay_min, rate_decay_max) { - start_rate_min *= RATE_MULTIPLIER; - start_rate_max *= RATE_MULTIPLIER; - rate_decay_min *= RATE_MULTIPLIER; - rate_decay_max *= RATE_MULTIPLIER; - - const buy_price = given_prices[0]; - let rate_pdf = new PDF(start_rate_min, start_rate_max); - let prob = 1; - - for (let i = start; i < start + length; i++) { - let min_pred = get_price(rate_pdf.min_value(), buy_price); - let max_pred = get_price(rate_pdf.max_value(), buy_price); - if (!isNaN(given_prices[i])) { - if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { - // Given price is out of predicted range, so this is the wrong pattern - return 0; - } - // TODO: How to deal with probability when there's fudge factor? - // Clamp the value to be in range now so the probability won't be totally biased to fudged values. - const real_rate_range = - rate_range_from_given_and_base(clamp(given_prices[i], min_pred, max_pred), buy_price); - prob *= rate_pdf.range_limit(real_rate_range); - if (prob == 0) { - return 0; - } - min_pred = given_prices[i]; - max_pred = given_prices[i]; - } +class Predictor { - predicted_prices.push({ - min: min_pred, - max: max_pred, - }); + constructor(prices, first_buy, previous_pattern) { + this.prices = prices; + this.first_buy = first_buy; + this.previous_pattern = previous_pattern; + } - rate_pdf.decay(rate_decay_min, rate_decay_max); + intceil(val) { + return Math.trunc(val + 0.99999); } - return prob; -} + minimum_rate_from_given_and_base(given_price, buy_price) { + return RATE_MULTIPLIER * (given_price - 0.99999) / buy_price; + } -/* - * This corresponds to the code: - * rate = randfloat(rate_min, rate_max); - * sellPrices[work++] = intceil(randfloat(rate_min, rate) * basePrice) - 1; - * sellPrices[work++] = intceil(rate * basePrice); - * sellPrices[work++] = intceil(randfloat(rate_min, rate) * basePrice) - 1; - * - * Would return the conditional probability given the given_prices, and modify - * the predicted_prices array. - * If the given_prices won't match, returns 0. - */ -function generate_peak_price( - given_prices, predicted_prices, start, rate_min, rate_max) { - rate_min *= RATE_MULTIPLIER; - rate_max *= RATE_MULTIPLIER; - - const buy_price = given_prices[0]; - let prob = 1; - let rate_range = [rate_min, rate_max]; - - // * Calculate the probability first. - // Prob(middle_price) - const middle_price = given_prices[start + 1]; - if (!isNaN(middle_price)) { - const min_pred = get_price(rate_min, buy_price); - const max_pred = get_price(rate_max, buy_price); - if (middle_price < min_pred - FUDGE_FACTOR || middle_price > max_pred + FUDGE_FACTOR) { - // Given price is out of predicted range, so this is the wrong pattern - return 0; - } - // TODO: How to deal with probability when there's fudge factor? - // Clamp the value to be in range now so the probability won't be totally biased to fudged values. - const real_rate_range = - rate_range_from_given_and_base(clamp(middle_price, min_pred, max_pred), buy_price); - prob *= range_intersect_length(rate_range, real_rate_range) / - range_length(rate_range); - if (prob == 0) { - return 0; - } + maximum_rate_from_given_and_base(given_price, buy_price) { + return RATE_MULTIPLIER * (given_price + 0.00001) / buy_price; + } - rate_range = range_intersect(rate_range, real_rate_range); + rate_range_from_given_and_base(given_price, buy_price) { + return [ + this.minimum_rate_from_given_and_base(given_price, buy_price), + this.maximum_rate_from_given_and_base(given_price, buy_price) + ]; } - const left_price = given_prices[start]; - const right_price = given_prices[start + 2]; - // Prob(left_price | middle_price), Prob(right_price | middle_price) - // - // A = rate_range[0], B = rate_range[1], C = rate_min, X = rate, Y = randfloat(rate_min, rate) - // rate = randfloat(A, B); sellPrices[work++] = intceil(randfloat(C, rate) * basePrice) - 1; - // - // => X->U(A,B), Y->U(C,X), Y-C->U(0,X-C), Y-C->U(0,1)*(X-C), Y-C->U(0,1)*U(A-C,B-C), - // let Z=Y-C, Z1=A-C, Z2=B-C, Z->U(0,1)*U(Z1,Z2) - // Prob(Z<=t) = integral_{x=0}^{1} [min(t/x,Z2)-min(t/x,Z1)]/ (Z2-Z1) - // let F(t, ZZ) = integral_{x=0}^{1} min(t/x, ZZ) - // 1. if ZZ < t, then min(t/x, ZZ) = ZZ -> F(t, ZZ) = ZZ - // 2. if ZZ >= t, then F(t, ZZ) = integral_{x=0}^{t/ZZ} ZZ + integral_{x=t/ZZ}^{1} t/x - // = t - t log(t/ZZ) - // Prob(Z<=t) = (F(t, Z2) - F(t, Z1)) / (Z2 - Z1) - // Prob(Y<=t) = Prob(Z>=t-C) - for (const price of [left_price, right_price]) { - if (isNaN(price)) { - continue; - } - const min_pred = get_price(rate_min, buy_price) - 1; - const max_pred = get_price(rate_range[1], buy_price) - 1; - if (price < min_pred - FUDGE_FACTOR || price > max_pred + FUDGE_FACTOR) { - // Given price is out of predicted range, so this is the wrong pattern - return 0; + get_price(rate, basePrice) { + return this.intceil(rate * basePrice / RATE_MULTIPLIER); + } + + * multiply_generator_probability(generator, probability) { + for (const it of generator) { + yield {...it, probability: it.probability * probability}; } - // TODO: How to deal with probability when there's fudge factor? - // Clamp the value to be in range now so the probability won't be totally biased to fudged values. - const rate2_range = rate_range_from_given_and_base(clamp(price, min_pred, max_pred)+ 1, buy_price); - const F = (t, ZZ) => { - if (t <= 0) { - return 0; + } + + /* + * This corresponds to the code: + * for (int i = start; i < start + length; i++) + * { + * sellPrices[work++] = + * intceil(randfloat(rate_min / RATE_MULTIPLIER, rate_max / RATE_MULTIPLIER) * basePrice); + * } + * + * Would return the conditional probability given the given_prices, and modify + * the predicted_prices array. + * If the given_prices won't match, returns 0. + */ + generate_individual_random_price( + given_prices, predicted_prices, start, length, rate_min, rate_max) { + rate_min *= RATE_MULTIPLIER; + rate_max *= RATE_MULTIPLIER; + + const buy_price = given_prices[0]; + const rate_range = [rate_min, rate_max]; + let prob = 1; + + for (let i = start; i < start + length; i++) { + let min_pred = this.get_price(rate_min, buy_price); + let max_pred = this.get_price(rate_max, buy_price); + if (!isNaN(given_prices[i])) { + if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { + // Given price is out of predicted range, so this is the wrong pattern + return 0; + } + // TODO: How to deal with probability when there's fudge factor? + // Clamp the value to be in range now so the probability won't be totally biased to fudged values. + const real_rate_range = + this.rate_range_from_given_and_base(clamp(given_prices[i], min_pred, max_pred), buy_price); + prob *= range_intersect_length(rate_range, real_rate_range) / + range_length(rate_range); + min_pred = given_prices[i]; + max_pred = given_prices[i]; } - return ZZ < t ? ZZ : t - t * (Math.log(t) - Math.log(ZZ)); - }; - const [A, B] = rate_range; - const C = rate_min; - const Z1 = A - C; - const Z2 = B - C; - const PY = (t) => (F(t - C, Z2) - F(t - C, Z1)) / (Z2 - Z1); - prob *= PY(rate2_range[1]) - PY(rate2_range[0]); - if (prob == 0) { - return 0; + + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); } + return prob; } - // * Then generate the real predicted range. - // We're doing things in different order then how we calculate probability, - // since forward prediction is more useful here. - // - // Main spike 1 - min_pred = get_price(rate_min, buy_price) - 1; - max_pred = get_price(rate_max, buy_price) - 1; - if (!isNaN(given_prices[start])) { - min_pred = given_prices[start]; - max_pred = given_prices[start]; - } - predicted_prices.push({ - min: min_pred, - max: max_pred, - }); - - // Main spike 2 - min_pred = predicted_prices[start].min; - max_pred = get_price(rate_max, buy_price); - if (!isNaN(given_prices[start + 1])) { - min_pred = given_prices[start + 1]; - max_pred = given_prices[start + 1]; - } - predicted_prices.push({ - min: min_pred, - max: max_pred, - }); - - // Main spike 3 - min_pred = get_price(rate_min, buy_price) - 1; - max_pred = predicted_prices[start + 1].max - 1; - if (!isNaN(given_prices[start + 2])) { - min_pred = given_prices[start + 2]; - max_pred = given_prices[start + 2]; + /* + * This corresponds to the code: + * rate = randfloat(start_rate_min, start_rate_max); + * for (int i = start; i < start + length; i++) + * { + * sellPrices[work++] = intceil(rate * basePrice); + * rate -= randfloat(rate_decay_min, rate_decay_max); + * } + * + * Would return the conditional probability given the given_prices, and modify + * the predicted_prices array. + * If the given_prices won't match, returns 0. + */ + generate_decreasing_random_price( + given_prices, predicted_prices, start, length, start_rate_min, + start_rate_max, rate_decay_min, rate_decay_max) { + start_rate_min *= RATE_MULTIPLIER; + start_rate_max *= RATE_MULTIPLIER; + rate_decay_min *= RATE_MULTIPLIER; + rate_decay_max *= RATE_MULTIPLIER; + + const buy_price = given_prices[0]; + let rate_pdf = new PDF(start_rate_min, start_rate_max); + let prob = 1; + + for (let i = start; i < start + length; i++) { + let min_pred = this.get_price(rate_pdf.min_value(), buy_price); + let max_pred = this.get_price(rate_pdf.max_value(), buy_price); + if (!isNaN(given_prices[i])) { + if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { + // Given price is out of predicted range, so this is the wrong pattern + return 0; + } + // TODO: How to deal with probability when there's fudge factor? + // Clamp the value to be in range now so the probability won't be totally biased to fudged values. + const real_rate_range = + this.rate_range_from_given_and_base(clamp(given_prices[i], min_pred, max_pred), buy_price); + prob *= rate_pdf.range_limit(real_rate_range); + if (prob == 0) { + return 0; + } + min_pred = given_prices[i]; + max_pred = given_prices[i]; + } + + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); + + rate_pdf.decay(rate_decay_min, rate_decay_max); + } + return prob; } - predicted_prices.push({ - min: min_pred, - max: max_pred, - }); - return prob; -} -function* - generate_pattern_0_with_lengths( - given_prices, high_phase_1_len, dec_phase_1_len, high_phase_2_len, - dec_phase_2_len, high_phase_3_len) { /* - // PATTERN 0: high, decreasing, high, decreasing, high - work = 2; - // high phase 1 - for (int i = 0; i < hiPhaseLen1; i++) - { - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + * This corresponds to the code: + * rate = randfloat(rate_min, rate_max); + * sellPrices[work++] = intceil(randfloat(rate_min, rate) * basePrice) - 1; + * sellPrices[work++] = intceil(rate * basePrice); + * sellPrices[work++] = intceil(randfloat(rate_min, rate) * basePrice) - 1; + * + * Would return the conditional probability given the given_prices, and modify + * the predicted_prices array. + * If the given_prices won't match, returns 0. + */ + generate_peak_price( + given_prices, predicted_prices, start, rate_min, rate_max) { + rate_min *= RATE_MULTIPLIER; + rate_max *= RATE_MULTIPLIER; + + const buy_price = given_prices[0]; + let prob = 1; + let rate_range = [rate_min, rate_max]; + + // * Calculate the probability first. + // Prob(middle_price) + const middle_price = given_prices[start + 1]; + if (!isNaN(middle_price)) { + const min_pred = this.get_price(rate_min, buy_price); + const max_pred = this.get_price(rate_max, buy_price); + if (middle_price < min_pred - FUDGE_FACTOR || middle_price > max_pred + FUDGE_FACTOR) { + // Given price is out of predicted range, so this is the wrong pattern + return 0; } - // decreasing phase 1 - rate = randfloat(0.8, 0.6); - for (int i = 0; i < decPhaseLen1; i++) - { - sellPrices[work++] = intceil(rate * basePrice); - rate -= 0.04; - rate -= randfloat(0, 0.06); + // TODO: How to deal with probability when there's fudge factor? + // Clamp the value to be in range now so the probability won't be totally biased to fudged values. + const real_rate_range = + this.rate_range_from_given_and_base(clamp(middle_price, min_pred, max_pred), buy_price); + prob *= range_intersect_length(rate_range, real_rate_range) / + range_length(rate_range); + if (prob == 0) { + return 0; } - // high phase 2 - for (int i = 0; i < (hiPhaseLen2and3 - hiPhaseLen3); i++) - { - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + + rate_range = range_intersect(rate_range, real_rate_range); + } + + const left_price = given_prices[start]; + const right_price = given_prices[start + 2]; + // Prob(left_price | middle_price), Prob(right_price | middle_price) + // + // A = rate_range[0], B = rate_range[1], C = rate_min, X = rate, Y = randfloat(rate_min, rate) + // rate = randfloat(A, B); sellPrices[work++] = intceil(randfloat(C, rate) * basePrice) - 1; + // + // => X->U(A,B), Y->U(C,X), Y-C->U(0,X-C), Y-C->U(0,1)*(X-C), Y-C->U(0,1)*U(A-C,B-C), + // let Z=Y-C, Z1=A-C, Z2=B-C, Z->U(0,1)*U(Z1,Z2) + // Prob(Z<=t) = integral_{x=0}^{1} [min(t/x,Z2)-min(t/x,Z1)]/ (Z2-Z1) + // let F(t, ZZ) = integral_{x=0}^{1} min(t/x, ZZ) + // 1. if ZZ < t, then min(t/x, ZZ) = ZZ -> F(t, ZZ) = ZZ + // 2. if ZZ >= t, then F(t, ZZ) = integral_{x=0}^{t/ZZ} ZZ + integral_{x=t/ZZ}^{1} t/x + // = t - t log(t/ZZ) + // Prob(Z<=t) = (F(t, Z2) - F(t, Z1)) / (Z2 - Z1) + // Prob(Y<=t) = Prob(Z>=t-C) + for (const price of [left_price, right_price]) { + if (isNaN(price)) { + continue; } - // decreasing phase 2 - rate = randfloat(0.8, 0.6); - for (int i = 0; i < decPhaseLen2; i++) - { - sellPrices[work++] = intceil(rate * basePrice); - rate -= 0.04; - rate -= randfloat(0, 0.06); + const min_pred = this.get_price(rate_min, buy_price) - 1; + const max_pred = this.get_price(rate_range[1], buy_price) - 1; + if (price < min_pred - FUDGE_FACTOR || price > max_pred + FUDGE_FACTOR) { + // Given price is out of predicted range, so this is the wrong pattern + return 0; } - // high phase 3 - for (int i = 0; i < hiPhaseLen3; i++) - { - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + // TODO: How to deal with probability when there's fudge factor? + // Clamp the value to be in range now so the probability won't be totally biased to fudged values. + const rate2_range = this.rate_range_from_given_and_base(clamp(price, min_pred, max_pred)+ 1, buy_price); + const F = (t, ZZ) => { + if (t <= 0) { + return 0; + } + return ZZ < t ? ZZ : t - t * (Math.log(t) - Math.log(ZZ)); + }; + const [A, B] = rate_range; + const C = rate_min; + const Z1 = A - C; + const Z2 = B - C; + const PY = (t) => (F(t - C, Z2) - F(t - C, Z1)) / (Z2 - Z1); + prob *= PY(rate2_range[1]) - PY(rate2_range[0]); + if (prob == 0) { + return 0; } - */ - - const buy_price = given_prices[0]; - const predicted_prices = [ - { - min: buy_price, - max: buy_price, - }, - { - min: buy_price, - max: buy_price, - }, - ]; - let probability = 1; - - // High Phase 1 - probability *= generate_individual_random_price( - given_prices, predicted_prices, 2, high_phase_1_len, 0.9, 1.4); - if (probability == 0) { - return; - } + } - // Dec Phase 1 - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, 2 + high_phase_1_len, dec_phase_1_len, - 0.6, 0.8, 0.04, 0.1); - if (probability == 0) { - return; - } + // * Then generate the real predicted range. + // We're doing things in different order then how we calculate probability, + // since forward prediction is more useful here. + // + // Main spike 1 + let min_pred = this.get_price(rate_min, buy_price) - 1; + let max_pred = this.get_price(rate_max, buy_price) - 1; + if (!isNaN(given_prices[start])) { + min_pred = given_prices[start]; + max_pred = given_prices[start]; + } + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); - // High Phase 2 - probability *= generate_individual_random_price(given_prices, predicted_prices, - 2 + high_phase_1_len + dec_phase_1_len, high_phase_2_len, 0.9, 1.4); - if (probability == 0) { - return; - } + // Main spike 2 + min_pred = predicted_prices[start].min; + max_pred = this.get_price(rate_max, buy_price); + if (!isNaN(given_prices[start + 1])) { + min_pred = given_prices[start + 1]; + max_pred = given_prices[start + 1]; + } + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); - // Dec Phase 2 - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, - 2 + high_phase_1_len + dec_phase_1_len + high_phase_2_len, - dec_phase_2_len, 0.6, 0.8, 0.04, 0.1); - if (probability == 0) { - return; - } + // Main spike 3 + min_pred = this.get_price(rate_min, buy_price) - 1; + max_pred = predicted_prices[start + 1].max - 1; + if (!isNaN(given_prices[start + 2])) { + min_pred = given_prices[start + 2]; + max_pred = given_prices[start + 2]; + } + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); - // High Phase 3 - if (2 + high_phase_1_len + dec_phase_1_len + high_phase_2_len + dec_phase_2_len + high_phase_3_len != 14) { - throw new Error("Phase lengths don't add up"); + return prob; } - const prev_length = 2 + high_phase_1_len + dec_phase_1_len + - high_phase_2_len + dec_phase_2_len; - probability *= generate_individual_random_price( - given_prices, predicted_prices, prev_length, 14 - prev_length, 0.9, 1.4); - if (probability == 0) { - return; - } + * generate_pattern_0_with_lengths( + given_prices, high_phase_1_len, dec_phase_1_len, high_phase_2_len, + dec_phase_2_len, high_phase_3_len) { + /* + // PATTERN 0: high, decreasing, high, decreasing, high + work = 2; + // high phase 1 + for (int i = 0; i < hiPhaseLen1; i++) + { + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + } + // decreasing phase 1 + rate = randfloat(0.8, 0.6); + for (int i = 0; i < decPhaseLen1; i++) + { + sellPrices[work++] = intceil(rate * basePrice); + rate -= 0.04; + rate -= randfloat(0, 0.06); + } + // high phase 2 + for (int i = 0; i < (hiPhaseLen2and3 - hiPhaseLen3); i++) + { + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + } + // decreasing phase 2 + rate = randfloat(0.8, 0.6); + for (int i = 0; i < decPhaseLen2; i++) + { + sellPrices[work++] = intceil(rate * basePrice); + rate -= 0.04; + rate -= randfloat(0, 0.06); + } + // high phase 3 + for (int i = 0; i < hiPhaseLen3; i++) + { + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + } + */ - yield { - pattern_description: i18next.t("patterns.fluctuating"), - pattern_number: 0, - prices: predicted_prices, - probability, - }; -} + const buy_price = given_prices[0]; + const predicted_prices = [ + { + min: buy_price, + max: buy_price, + }, + { + min: buy_price, + max: buy_price, + }, + ]; + let probability = 1; + + // High Phase 1 + probability *= this.generate_individual_random_price( + given_prices, predicted_prices, 2, high_phase_1_len, 0.9, 1.4); + if (probability == 0) { + return; + } -function* generate_pattern_0(given_prices) { - /* - decPhaseLen1 = randbool() ? 3 : 2; - decPhaseLen2 = 5 - decPhaseLen1; - hiPhaseLen1 = randint(0, 6); - hiPhaseLen2and3 = 7 - hiPhaseLen1; - hiPhaseLen3 = randint(0, hiPhaseLen2and3 - 1); - */ - for (var dec_phase_1_len = 2; dec_phase_1_len < 4; dec_phase_1_len++) { - for (var high_phase_1_len = 0; high_phase_1_len < 7; high_phase_1_len++) { - for (var high_phase_3_len = 0; high_phase_3_len < (7 - high_phase_1_len - 1 + 1); high_phase_3_len++) { - yield* multiply_generator_probability( - generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_phase_1_len, 7 - high_phase_1_len - high_phase_3_len, 5 - dec_phase_1_len, high_phase_3_len), - 1 / (4 - 2) / 7 / (7 - high_phase_1_len)); - } + // Dec Phase 1 + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, 2 + high_phase_1_len, dec_phase_1_len, + 0.6, 0.8, 0.04, 0.1); + if (probability == 0) { + return; } - } -} -function* generate_pattern_1_with_peak(given_prices, peak_start) { - /* - // PATTERN 1: decreasing middle, high spike, random low - peakStart = randint(3, 9); - rate = randfloat(0.9, 0.85); - for (work = 2; work < peakStart; work++) - { - sellPrices[work] = intceil(rate * basePrice); - rate -= 0.03; - rate -= randfloat(0, 0.02); - } - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); - sellPrices[work++] = intceil(randfloat(1.4, 2.0) * basePrice); - sellPrices[work++] = intceil(randfloat(2.0, 6.0) * basePrice); - sellPrices[work++] = intceil(randfloat(1.4, 2.0) * basePrice); - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); - for (; work < 14; work++) - { - sellPrices[work] = intceil(randfloat(0.4, 0.9) * basePrice); - } - */ - - const buy_price = given_prices[0]; - const predicted_prices = [ - { - min: buy_price, - max: buy_price, - }, - { - min: buy_price, - max: buy_price, - }, - ]; - let probability = 1; - - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, 2, peak_start - 2, 0.85, 0.9, 0.03, 0.05); - if (probability == 0) { - return; - } + // High Phase 2 + probability *= this.generate_individual_random_price(given_prices, predicted_prices, + 2 + high_phase_1_len + dec_phase_1_len, high_phase_2_len, 0.9, 1.4); + if (probability == 0) { + return; + } - // Now each day is independent of next - min_randoms = [0.9, 1.4, 2.0, 1.4, 0.9, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4] - max_randoms = [1.4, 2.0, 6.0, 2.0, 1.4, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9] - for (let i = peak_start; i < 14; i++) { - probability *= generate_individual_random_price( - given_prices, predicted_prices, i, 1, min_randoms[i - peak_start], - max_randoms[i - peak_start]); + // Dec Phase 2 + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, + 2 + high_phase_1_len + dec_phase_1_len + high_phase_2_len, + dec_phase_2_len, 0.6, 0.8, 0.04, 0.1); if (probability == 0) { return; } + + // High Phase 3 + if (2 + high_phase_1_len + dec_phase_1_len + high_phase_2_len + dec_phase_2_len + high_phase_3_len != 14) { + throw new Error("Phase lengths don't add up"); + } + + const prev_length = 2 + high_phase_1_len + dec_phase_1_len + + high_phase_2_len + dec_phase_2_len; + probability *= this.generate_individual_random_price( + given_prices, predicted_prices, prev_length, 14 - prev_length, 0.9, 1.4); + if (probability == 0) { + return; + } + + yield { + pattern_description: i18next.t("patterns.fluctuating"), + pattern_number: 0, + prices: predicted_prices, + probability, + }; } - yield { - pattern_description: i18next.t("patterns.large-spike"), - pattern_number: 1, - prices: predicted_prices, - probability, - }; -} -function* generate_pattern_1(given_prices) { - for (var peak_start = 3; peak_start < 10; peak_start++) { - yield* multiply_generator_probability(generate_pattern_1_with_peak(given_prices, peak_start), 1 / (10 - 3)); + * generate_pattern_0(given_prices) { + /* + decPhaseLen1 = randbool() ? 3 : 2; + decPhaseLen2 = 5 - decPhaseLen1; + hiPhaseLen1 = randint(0, 6); + hiPhaseLen2and3 = 7 - hiPhaseLen1; + hiPhaseLen3 = randint(0, hiPhaseLen2and3 - 1); + */ + for (var dec_phase_1_len = 2; dec_phase_1_len < 4; dec_phase_1_len++) { + for (var high_phase_1_len = 0; high_phase_1_len < 7; high_phase_1_len++) { + for (var high_phase_3_len = 0; high_phase_3_len < (7 - high_phase_1_len - 1 + 1); high_phase_3_len++) { + yield* this.multiply_generator_probability( + this.generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_phase_1_len, 7 - high_phase_1_len - high_phase_3_len, 5 - dec_phase_1_len, high_phase_3_len), + 1 / (4 - 2) / 7 / (7 - high_phase_1_len)); + } + } + } } -} -function* generate_pattern_2(given_prices) { - /* - // PATTERN 2: consistently decreasing - rate = 0.9; - rate -= randfloat(0, 0.05); - for (work = 2; work < 14; work++) + * generate_pattern_1_with_peak(given_prices, peak_start) { + /* + // PATTERN 1: decreasing middle, high spike, random low + peakStart = randint(3, 9); + rate = randfloat(0.9, 0.85); + for (work = 2; work < peakStart; work++) { sellPrices[work] = intceil(rate * basePrice); rate -= 0.03; rate -= randfloat(0, 0.02); } - break; - */ - - const buy_price = given_prices[0]; - const predicted_prices = [ - { - min: buy_price, - max: buy_price, - }, - { - min: buy_price, - max: buy_price, - }, - ]; - let probability = 1; - - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, 2, 14 - 2, 0.85, 0.9, 0.03, 0.05); - if (probability == 0) { - return; + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + sellPrices[work++] = intceil(randfloat(1.4, 2.0) * basePrice); + sellPrices[work++] = intceil(randfloat(2.0, 6.0) * basePrice); + sellPrices[work++] = intceil(randfloat(1.4, 2.0) * basePrice); + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + for (; work < 14; work++) + { + sellPrices[work] = intceil(randfloat(0.4, 0.9) * basePrice); + } + */ + + const buy_price = given_prices[0]; + const predicted_prices = [ + { + min: buy_price, + max: buy_price, + }, + { + min: buy_price, + max: buy_price, + }, + ]; + let probability = 1; + + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, 2, peak_start - 2, 0.85, 0.9, 0.03, 0.05); + if (probability == 0) { + return; + } + + // Now each day is independent of next + let min_randoms = [0.9, 1.4, 2.0, 1.4, 0.9, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4] + let max_randoms = [1.4, 2.0, 6.0, 2.0, 1.4, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9] + for (let i = peak_start; i < 14; i++) { + probability *= this.generate_individual_random_price( + given_prices, predicted_prices, i, 1, min_randoms[i - peak_start], + max_randoms[i - peak_start]); + if (probability == 0) { + return; + } + } + yield { + pattern_description: i18next.t("patterns.large-spike"), + pattern_number: 1, + prices: predicted_prices, + probability, + }; } - yield { - pattern_description: i18next.t("patterns.decreasing"), - pattern_number: 2, - prices: predicted_prices, - probability, - }; -} + * generate_pattern_1(given_prices) { + for (var peak_start = 3; peak_start < 10; peak_start++) { + yield* this.multiply_generator_probability(this.generate_pattern_1_with_peak(given_prices, peak_start), 1 / (10 - 3)); + } + } -function* generate_pattern_3_with_peak(given_prices, peak_start) { + * generate_pattern_2(given_prices) { + /* + // PATTERN 2: consistently decreasing + rate = 0.9; + rate -= randfloat(0, 0.05); + for (work = 2; work < 14; work++) + { + sellPrices[work] = intceil(rate * basePrice); + rate -= 0.03; + rate -= randfloat(0, 0.02); + } + break; + */ - /* - // PATTERN 3: decreasing, spike, decreasing - peakStart = randint(2, 9); - // decreasing phase before the peak - rate = randfloat(0.9, 0.4); - for (work = 2; work < peakStart; work++) - { - sellPrices[work] = intceil(rate * basePrice); - rate -= 0.03; - rate -= randfloat(0, 0.02); - } - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice); - sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); - rate = randfloat(1.4, 2.0); - sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; - sellPrices[work++] = intceil(rate * basePrice); - sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; - // decreasing phase after the peak - if (work < 14) - { + const buy_price = given_prices[0]; + const predicted_prices = [ + { + min: buy_price, + max: buy_price, + }, + { + min: buy_price, + max: buy_price, + }, + ]; + let probability = 1; + + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, 2, 14 - 2, 0.85, 0.9, 0.03, 0.05); + if (probability == 0) { + return; + } + + yield { + pattern_description: i18next.t("patterns.decreasing"), + pattern_number: 2, + prices: predicted_prices, + probability, + }; + } + + * generate_pattern_3_with_peak(given_prices, peak_start) { + + /* + // PATTERN 3: decreasing, spike, decreasing + peakStart = randint(2, 9); + // decreasing phase before the peak rate = randfloat(0.9, 0.4); - for (; work < 14; work++) + for (work = 2; work < peakStart; work++) { sellPrices[work] = intceil(rate * basePrice); rate -= 0.03; rate -= randfloat(0, 0.02); } - } - */ - - const buy_price = given_prices[0]; - const predicted_prices = [ - { - min: buy_price, - max: buy_price, - }, - { - min: buy_price, - max: buy_price, - }, - ]; - let probability = 1; - - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, 2, peak_start - 2, 0.4, 0.9, 0.03, 0.05); - if (probability == 0) { - return; - } + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice); + sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); + rate = randfloat(1.4, 2.0); + sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; + sellPrices[work++] = intceil(rate * basePrice); + sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; + // decreasing phase after the peak + if (work < 14) + { + rate = randfloat(0.9, 0.4); + for (; work < 14; work++) + { + sellPrices[work] = intceil(rate * basePrice); + rate -= 0.03; + rate -= randfloat(0, 0.02); + } + } + */ - // The peak - probability *= generate_individual_random_price( - given_prices, predicted_prices, peak_start, 2, 0.9, 1.4); - if (probability == 0) { - return; - } + const buy_price = given_prices[0]; + const predicted_prices = [ + { + min: buy_price, + max: buy_price, + }, + { + min: buy_price, + max: buy_price, + }, + ]; + let probability = 1; + + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, 2, peak_start - 2, 0.4, 0.9, 0.03, 0.05); + if (probability == 0) { + return; + } - probability *= generate_peak_price( - given_prices, predicted_prices, peak_start + 2, 1.4, 2.0); - if (probability == 0) { - return; - } + // The peak + probability *= this.generate_individual_random_price( + given_prices, predicted_prices, peak_start, 2, 0.9, 1.4); + if (probability == 0) { + return; + } - if (peak_start + 5 < 14) { - probability *= generate_decreasing_random_price( - given_prices, predicted_prices, peak_start + 5, 14 - (peak_start + 5), - 0.4, 0.9, 0.03, 0.05); + probability *= this.generate_peak_price( + given_prices, predicted_prices, peak_start + 2, 1.4, 2.0); if (probability == 0) { return; } - } - yield { - pattern_description: i18next.t("patterns.small-spike"), - pattern_number: 3, - prices: predicted_prices, - probability, - }; -} + if (peak_start + 5 < 14) { + probability *= this.generate_decreasing_random_price( + given_prices, predicted_prices, peak_start + 5, 14 - (peak_start + 5), + 0.4, 0.9, 0.03, 0.05); + if (probability == 0) { + return; + } + } -function* generate_pattern_3(given_prices) { - for (let peak_start = 2; peak_start < 10; peak_start++) { - yield* multiply_generator_probability(generate_pattern_3_with_peak(given_prices, peak_start), 1 / (10 - 2)); + yield { + pattern_description: i18next.t("patterns.small-spike"), + pattern_number: 3, + prices: predicted_prices, + probability, + }; } -} -function get_transition_probability(previous_pattern) { - if (typeof previous_pattern === 'undefined' || Number.isNaN(previous_pattern) || previous_pattern === null || previous_pattern < 0 || previous_pattern > 3) { - // TODO: Fill the steady state pattern (https://github.com/mikebryant/ac-nh-turnip-prices/pull/90) here. - return [0.346278, 0.247363, 0.147607, 0.258752]; + * generate_pattern_3(given_prices) { + for (let peak_start = 2; peak_start < 10; peak_start++) { + yield* this.multiply_generator_probability(this.generate_pattern_3_with_peak(given_prices, peak_start), 1 / (10 - 2)); + } } - return PROBABILITY_MATRIX[previous_pattern]; -} + get_transition_probability(previous_pattern) { + if (typeof previous_pattern === 'undefined' || Number.isNaN(previous_pattern) || previous_pattern === null || previous_pattern < 0 || previous_pattern > 3) { + // TODO: Fill the steady state pattern (https://github.com/mikebryant/ac-nh-turnip-prices/pull/90) here. + return [0.346278, 0.247363, 0.147607, 0.258752]; + } + + return PROBABILITY_MATRIX[previous_pattern]; + } -function* generate_all_patterns(sell_prices, previous_pattern) { - const generate_pattern_fns = [generate_pattern_0, generate_pattern_1, generate_pattern_2, generate_pattern_3]; - const transition_probability = get_transition_probability(previous_pattern); + * generate_all_patterns(sell_prices, previous_pattern) { + const generate_pattern_fns = [this.generate_pattern_0, this.generate_pattern_1, this.generate_pattern_2, this.generate_pattern_3]; + const transition_probability = this.get_transition_probability(previous_pattern); - for (let i = 0; i < 4; i++) { - yield* multiply_generator_probability(generate_pattern_fns[i](sell_prices), transition_probability[i]); + for (let i = 0; i < 4; i++) { + yield* this.multiply_generator_probability(generate_pattern_fns[i].bind(this)(sell_prices), transition_probability[i]); + } } -} -function* generate_possibilities(sell_prices, first_buy, previous_pattern) { - if (first_buy || isNaN(sell_prices[0])) { - for (var buy_price = 90; buy_price <= 110; buy_price++) { - sell_prices[0] = sell_prices[1] = buy_price; - if (first_buy) { - yield* generate_pattern_3(sell_prices); - } else { - // All buy prices are equal probability and we're at the outmost layer, - // so don't need to multiply_generator_probability here. - yield* generate_all_patterns(sell_prices, previous_pattern) + * generate_possibilities(sell_prices, first_buy, previous_pattern) { + if (first_buy || isNaN(sell_prices[0])) { + for (var buy_price = 90; buy_price <= 110; buy_price++) { + sell_prices[0] = sell_prices[1] = buy_price; + if (first_buy) { + yield* this.generate_pattern_3(sell_prices); + } else { + // All buy prices are equal probability and we're at the outmost layer, + // so don't need to multiply_generator_probability here. + yield* this.generate_all_patterns(sell_prices, previous_pattern) + } } + } else { + yield* this.generate_all_patterns(sell_prices, previous_pattern) } - } else { - yield* generate_all_patterns(sell_prices, previous_pattern) } -} -function analyze_possibilities(sell_prices, first_buy, previous_pattern) { - const generated_possibilities = Array.from(generate_possibilities(sell_prices, first_buy, previous_pattern)); - console.log(generated_possibilities); + analyze_possibilities() { + const sell_prices = this.prices; + const first_buy = this.first_buy; + const previous_pattern = this.previous_pattern; + const generated_possibilities = Array.from(this.generate_possibilities(sell_prices, first_buy, previous_pattern)); + console.log(generated_possibilities); - const total_probability = generated_possibilities.reduce((acc, it) => acc + it.probability, 0); - for (const it of generated_possibilities) { - it.probability /= total_probability; - } + const total_probability = generated_possibilities.reduce((acc, it) => acc + it.probability, 0); + for (const it of generated_possibilities) { + it.probability /= total_probability; + } - for (let poss of generated_possibilities) { - var weekMins = []; - var weekMaxes = []; - for (let day of poss.prices.slice(2)) { - // Check for a future date by checking for a range of prices - if(day.min !== day.max){ - weekMins.push(day.min); - weekMaxes.push(day.max); - } else { - // If we find a set price after one or more ranged prices, the user has missed a day. Discard that data and start again. - weekMins = []; - weekMaxes = []; + for (let poss of generated_possibilities) { + var weekMins = []; + var weekMaxes = []; + for (let day of poss.prices.slice(2)) { + // Check for a future date by checking for a range of prices + if(day.min !== day.max){ + weekMins.push(day.min); + weekMaxes.push(day.max); + } else { + // If we find a set price after one or more ranged prices, the user has missed a day. Discard that data and start again. + weekMins = []; + weekMaxes = []; + } } + if (!weekMins.length && !weekMaxes.length) { + weekMins.push(poss.prices[poss.prices.length -1].min); + weekMaxes.push(poss.prices[poss.prices.length -1].max); + } + poss.weekGuaranteedMinimum = Math.max(...weekMins); + poss.weekMax = Math.max(...weekMaxes); } - if (!weekMins.length && !weekMaxes.length) { - weekMins.push(poss.prices[poss.prices.length -1].min); - weekMaxes.push(poss.prices[poss.prices.length -1].max); - } - poss.weekGuaranteedMinimum = Math.max(...weekMins); - poss.weekMax = Math.max(...weekMaxes); - } - category_totals = {} - for (let i of [0, 1, 2, 3]) { - category_totals[i] = generated_possibilities - .filter(value => value.pattern_number == i) - .map(value => value.probability) - .reduce((previous, current) => previous + current, 0); - } + let category_totals = {} + for (let i of [0, 1, 2, 3]) { + category_totals[i] = generated_possibilities + .filter(value => value.pattern_number == i) + .map(value => value.probability) + .reduce((previous, current) => previous + current, 0); + } - for (let pos of generated_possibilities) { - pos.category_total_probability = category_totals[pos.pattern_number]; - } + for (let pos of generated_possibilities) { + pos.category_total_probability = category_totals[pos.pattern_number]; + } - generated_possibilities.sort((a, b) => { - return b.category_total_probability - a.category_total_probability || b.probability - a.probability; - }); + generated_possibilities.sort((a, b) => { + return b.category_total_probability - a.category_total_probability || b.probability - a.probability; + }); - global_min_max = []; - for (var day = 0; day < 14; day++) { - prices = { - min: 999, - max: 0, - } - for (let poss of generated_possibilities) { - if (poss.prices[day].min < prices.min) { - prices.min = poss.prices[day].min; + let global_min_max = []; + for (var day = 0; day < 14; day++) { + prices = { + min: 999, + max: 0, } - if (poss.prices[day].max > prices.max) { - prices.max = poss.prices[day].max; + for (let poss of generated_possibilities) { + if (poss.prices[day].min < prices.min) { + prices.min = poss.prices[day].min; + } + if (poss.prices[day].max > prices.max) { + prices.max = poss.prices[day].max; + } } + global_min_max.push(prices); } - global_min_max.push(prices); - } - generated_possibilities.unshift({ - pattern_description: i18next.t("patterns.all"), - pattern_number: 4, - prices: global_min_max, - weekGuaranteedMinimum: Math.min(...generated_possibilities.map(poss => poss.weekGuaranteedMinimum)), - weekMax: Math.max(...generated_possibilities.map(poss => poss.weekMax)) - }); + generated_possibilities.unshift({ + pattern_description: i18next.t("patterns.all"), + pattern_number: 4, + prices: global_min_max, + weekGuaranteedMinimum: Math.min(...generated_possibilities.map(poss => poss.weekGuaranteedMinimum)), + weekMax: Math.max(...generated_possibilities.map(poss => poss.weekMax)) + }); - return generated_possibilities; + return generated_possibilities; + } } diff --git a/js/scripts.js b/js/scripts.js index a60bc60..7533090 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -255,7 +255,8 @@ const calculateOutput = function (data, first_buy, previous_pattern) { return; } let output_possibilities = ""; - let analyzed_possibilities = analyze_possibilities(data, first_buy, previous_pattern); + let predictor = new Predictor(data, first_buy, previous_pattern); + let analyzed_possibilities = predictor.analyze_possibilities(); previous_pattern_number = "" for (let poss of analyzed_possibilities) { var out_line = "" + poss.pattern_description + "" From 8265ea4633db48e0a96d6516ce1141ba31994f39 Mon Sep 17 00:00:00 2001 From: gnehs Date: Tue, 21 Apr 2020 19:00:36 +0800 Subject: [PATCH 11/31] Update zh-TW.json --- locales/zh-TW.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index ad274e5..6135381 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -3,7 +3,7 @@ "daisy-mae": "曹賣" }, "welcome": { - "salutation": "你好,歡迎使用Nook手機上的 大頭菜預測工具 。", + "salutation": "你好,歡迎使用 Nook 手機上的 大頭菜預測工具 。", "description": "這個程式可以讓你每天追蹤你島上的大頭菜價格,但你必須自己記錄價格!", "conclusion": "接下來,大頭菜預測工具將 神奇地 預測本週剩餘時間的大頭菜價格。" }, @@ -25,7 +25,7 @@ "small-spike": "四期型" }, "prices": { - "description": "本週大頭菜的購買價格是多少?(如果這是您第一次購買大頭菜,則此欄位會被停用)", + "description": "本週大頭菜的購買價格是多少?(若這是您第一次購買大頭菜,該欄位將會被停用)", "open": { "am": "上午 - 08:00 to 11:59", "pm": "下午 - 12:00 to 22:00" @@ -71,9 +71,9 @@ "textbox": { "description": "在您紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示你島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", - "thanks": "如果沒有Ninji 的幫忙來弄清楚豆狸和粒狸如何對他們的大頭菜的估價,這一切都是不可能實現的。", - "support": "可透過 Github獲得支援、討論及貢獻", - "contributors-text": "哦!讓我們不要忘記感謝那些迄今為止作出貢獻的人!", + "thanks": "如果沒有 Ninji 的幫忙 來弄清楚豆狸和粒狸如何對他們的大頭菜的估價,這一切都是不可能實現的。", + "support": "可透過 GitHub獲得支援、討論及貢獻", + "contributors-text": "嘿!別忘了感謝那些迄今為止作出貢獻的人!", "contributors": "貢獻者", "language": "語言" } From 56edfc595617a537922dcc74089f209a650c1443 Mon Sep 17 00:00:00 2001 From: gnehs Date: Tue, 21 Apr 2020 22:16:54 +0800 Subject: [PATCH 12/31] Update zh-TW.json --- locales/zh-TW.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 6135381..ad735a2 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -3,9 +3,9 @@ "daisy-mae": "曹賣" }, "welcome": { - "salutation": "你好,歡迎使用 Nook 手機上的 大頭菜預測工具 。", + "salutation": "你好,歡迎使用 Nook 手機上的 Turnip Prophet。", "description": "這個程式可以讓你每天追蹤你島上的大頭菜價格,但你必須自己記錄價格!", - "conclusion": "接下來,大頭菜預測工具將 神奇地 預測本週剩餘時間的大頭菜價格。" + "conclusion": "接下來,Turnip Prophet 將 神奇地 預測本週剩餘時間的大頭菜價格。" }, "first-time": { "title": "首次購買", @@ -25,7 +25,7 @@ "small-spike": "四期型" }, "prices": { - "description": "本週大頭菜的購買價格是多少?(若這是您第一次購買大頭菜,該欄位將會被停用)", + "description": "本週自己島上的大頭菜買價?(若這是您第一次購買大頭菜,該欄位將會被停用)", "open": { "am": "上午 - 08:00 to 11:59", "pm": "下午 - 12:00 to 22:00" @@ -72,7 +72,7 @@ "description": "在您紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示你島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", "thanks": "如果沒有 Ninji 的幫忙 來弄清楚豆狸和粒狸如何對他們的大頭菜的估價,這一切都是不可能實現的。", - "support": "可透過 GitHub獲得支援、討論及貢獻", + "support": "可透過 GitHub 獲得支援、討論及貢獻", "contributors-text": "嘿!別忘了感謝那些迄今為止作出貢獻的人!", "contributors": "貢獻者", "language": "語言" From eaa0c610944ce8916261fe670e27c27244e50610 Mon Sep 17 00:00:00 2001 From: gnehs Date: Tue, 21 Apr 2020 22:22:08 +0800 Subject: [PATCH 13/31] Update zh-TW.json --- locales/zh-TW.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index ad735a2..b6840e3 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -25,7 +25,7 @@ "small-spike": "四期型" }, "prices": { - "description": "本週自己島上的大頭菜買價?(若這是您第一次購買大頭菜,該欄位將會被停用)", + "description": "本週自己島上的大頭菜買價?(若這是您第一次購買大頭菜,則此欄位會被停用)", "open": { "am": "上午 - 08:00 to 11:59", "pm": "下午 - 12:00 to 22:00" From c30f1f62a59ff31b9ab7c005610b493a2e4e1433 Mon Sep 17 00:00:00 2001 From: gnehs Date: Tue, 21 Apr 2020 22:46:06 +0800 Subject: [PATCH 14/31] Update zh-TW.json --- locales/zh-TW.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index b6840e3..f25c455 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -27,8 +27,8 @@ "prices": { "description": "本週自己島上的大頭菜買價?(若這是您第一次購買大頭菜,則此欄位會被停用)", "open": { - "am": "上午 - 08:00 to 11:59", - "pm": "下午 - 12:00 to 22:00" + "am": "上午 - 08:00 到 11:59", + "pm": "下午 - 12:00 到 22:00" }, "copy-permalink": "複製價格分享網址", "permalink-copied": "網址已複製!", @@ -71,7 +71,7 @@ "textbox": { "description": "在您紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示你島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", - "thanks": "如果沒有 Ninji 的幫忙 來弄清楚豆狸和粒狸如何對他們的大頭菜的估價,這一切都是不可能實現的。", + "thanks": "如果沒有 Ninji 的幫忙 來弄清楚豆狸和粒狸如何對大頭菜估價,這一切都不可能實現。", "support": "可透過 GitHub 獲得支援、討論及貢獻", "contributors-text": "嘿!別忘了感謝那些迄今為止作出貢獻的人!", "contributors": "貢獻者", From 5edcfc2482693299c0f56e3ff0f957d8ea219b54 Mon Sep 17 00:00:00 2001 From: gnehs Date: Tue, 21 Apr 2020 22:56:54 +0800 Subject: [PATCH 15/31] Update zh-TW.json --- locales/zh-TW.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index f25c455..2cd009b 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -71,8 +71,8 @@ "textbox": { "description": "在您紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示你島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", - "thanks": "如果沒有 Ninji 的幫忙 來弄清楚豆狸和粒狸如何對大頭菜估價,這一切都不可能實現。", - "support": "可透過 GitHub 獲得支援、討論及貢獻", + "thanks": "要不是 Ninji 協助釐清豆狸和粒狸的大頭菜估價方式,這一切都不可能實現。", + "support": "可於 GitHub 取得支援、討論及貢獻。", "contributors-text": "嘿!別忘了感謝那些迄今為止作出貢獻的人!", "contributors": "貢獻者", "language": "語言" From 687deae4ee72932e2b3c4bb82fa2569aca31eb2c Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Tue, 21 Apr 2020 16:05:31 +0100 Subject: [PATCH 16/31] refactor: Move fudge factor to class parameter --- js/predictions.js | 17 ++++++++--------- js/scripts.js | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/js/predictions.js b/js/predictions.js index 80621e9..880a3b6 100644 --- a/js/predictions.js +++ b/js/predictions.js @@ -1,7 +1,3 @@ -// The reverse-engineered code is not perfectly accurate, especially as it's not -// 32-bit ARM floating point. So, be tolerant of slightly unexpected inputs -const FUDGE_FACTOR = 5; - const PATTERN = { FLUCTUATING: 0, LARGE_SPIKE: 1, @@ -199,7 +195,10 @@ class PDF { class Predictor { - constructor(prices, first_buy, previous_pattern) { + constructor(prices, first_buy, previous_pattern, fudge_factor) { + // The reverse-engineered code is not perfectly accurate, especially as it's not + // 32-bit ARM floating point. So, be tolerant of slightly unexpected inputs + this.fudge_factor = fudge_factor; this.prices = prices; this.first_buy = first_buy; this.previous_pattern = previous_pattern; @@ -259,7 +258,7 @@ class Predictor { let min_pred = this.get_price(rate_min, buy_price); let max_pred = this.get_price(rate_max, buy_price); if (!isNaN(given_prices[i])) { - if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { + if (given_prices[i] < min_pred - this.fudge_factor || given_prices[i] > max_pred + this.fudge_factor) { // Given price is out of predicted range, so this is the wrong pattern return 0; } @@ -310,7 +309,7 @@ class Predictor { let min_pred = this.get_price(rate_pdf.min_value(), buy_price); let max_pred = this.get_price(rate_pdf.max_value(), buy_price); if (!isNaN(given_prices[i])) { - if (given_prices[i] < min_pred - FUDGE_FACTOR || given_prices[i] > max_pred + FUDGE_FACTOR) { + if (given_prices[i] < min_pred - this.fudge_factor || given_prices[i] > max_pred + this.fudge_factor) { // Given price is out of predicted range, so this is the wrong pattern return 0; } @@ -363,7 +362,7 @@ class Predictor { if (!isNaN(middle_price)) { const min_pred = this.get_price(rate_min, buy_price); const max_pred = this.get_price(rate_max, buy_price); - if (middle_price < min_pred - FUDGE_FACTOR || middle_price > max_pred + FUDGE_FACTOR) { + if (middle_price < min_pred - this.fudge_factor || middle_price > max_pred + this.fudge_factor) { // Given price is out of predicted range, so this is the wrong pattern return 0; } @@ -402,7 +401,7 @@ class Predictor { } const min_pred = this.get_price(rate_min, buy_price) - 1; const max_pred = this.get_price(rate_range[1], buy_price) - 1; - if (price < min_pred - FUDGE_FACTOR || price > max_pred + FUDGE_FACTOR) { + if (price < min_pred - this.fudge_factor || price > max_pred + this.fudge_factor) { // Given price is out of predicted range, so this is the wrong pattern return 0; } diff --git a/js/scripts.js b/js/scripts.js index 7533090..d386ea1 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -255,7 +255,7 @@ const calculateOutput = function (data, first_buy, previous_pattern) { return; } let output_possibilities = ""; - let predictor = new Predictor(data, first_buy, previous_pattern); + let predictor = new Predictor(data, first_buy, previous_pattern, 5); let analyzed_possibilities = predictor.analyze_possibilities(); previous_pattern_number = "" for (let poss of analyzed_possibilities) { From fed92c19748ae2ff6706eb849bad0d6ef9f5d417 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Tue, 21 Apr 2020 16:16:55 +0100 Subject: [PATCH 17/31] fix: Only use a fudge factor if we fail to find patterns normally --- js/predictions.js | 15 +++++++++++---- js/scripts.js | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/js/predictions.js b/js/predictions.js index 880a3b6..70cb996 100644 --- a/js/predictions.js +++ b/js/predictions.js @@ -195,10 +195,10 @@ class PDF { class Predictor { - constructor(prices, first_buy, previous_pattern, fudge_factor) { + constructor(prices, first_buy, previous_pattern) { // The reverse-engineered code is not perfectly accurate, especially as it's not // 32-bit ARM floating point. So, be tolerant of slightly unexpected inputs - this.fudge_factor = fudge_factor; + this.fudge_factor = 0; this.prices = prices; this.first_buy = first_buy; this.previous_pattern = previous_pattern; @@ -824,8 +824,15 @@ class Predictor { const sell_prices = this.prices; const first_buy = this.first_buy; const previous_pattern = this.previous_pattern; - const generated_possibilities = Array.from(this.generate_possibilities(sell_prices, first_buy, previous_pattern)); - console.log(generated_possibilities); + let generated_possibilities = [] + for (let i = 0; i < 6; i++) { + this.fudge_factor = i; + generated_possibilities = Array.from(this.generate_possibilities(sell_prices, first_buy, previous_pattern)); + if (generated_possibilities.length > 0) { + console.log("Generated possibilities using fudge factor %d: ", i, generated_possibilities); + break; + } + } const total_probability = generated_possibilities.reduce((acc, it) => acc + it.probability, 0); for (const it of generated_possibilities) { diff --git a/js/scripts.js b/js/scripts.js index d386ea1..7533090 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -255,7 +255,7 @@ const calculateOutput = function (data, first_buy, previous_pattern) { return; } let output_possibilities = ""; - let predictor = new Predictor(data, first_buy, previous_pattern, 5); + let predictor = new Predictor(data, first_buy, previous_pattern); let analyzed_possibilities = predictor.analyze_possibilities(); previous_pattern_number = "" for (let poss of analyzed_possibilities) { From e3f9938b5a44b54afc595598fbffc6f68193e0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lise=20Rubio?= <30841868+Lysoun@users.noreply.github.com> Date: Tue, 21 Apr 2020 22:14:56 +0200 Subject: [PATCH 18/31] Add Discord link in README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e602b0f..c380d68 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # ac-nh-turnip-prices + + + + Price calculator/predictor for Turnip prices - ## Scope This tool is: From b83d81c616b346c201f5ec821163751e6d76755c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lise=20Rubio?= <30841868+Lysoun@users.noreply.github.com> Date: Tue, 21 Apr 2020 22:25:33 +0200 Subject: [PATCH 19/31] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c380d68..f478271 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # ac-nh-turnip-prices - - - +[![discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/bRh74X8) Price calculator/predictor for Turnip prices ## Scope From 66433cb71f8068cb1747a3d851eabf5118a5e3eb Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Tue, 21 Apr 2020 22:19:42 +0100 Subject: [PATCH 20/31] fix: Remove animation --- index.html | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/index.html b/index.html index 6ddf79f..b381d94 100644 --- a/index.html +++ b/index.html @@ -54,25 +54,8 @@

Turnip Prophet

-
- - - - - - - - - - - -
-
- -
@@ -266,21 +249,6 @@
- -
- - - - - - - - - - - -
From f6ac9a2d7f5c013187f03d24f656d255ab0b80ed Mon Sep 17 00:00:00 2001 From: Ryan Carbotte Date: Tue, 21 Apr 2020 17:46:08 -0500 Subject: [PATCH 21/31] Revert "fix: Remove animation" This reverts commit 66433cb71f8068cb1747a3d851eabf5118a5e3eb. --- index.html | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/index.html b/index.html index b381d94..6ddf79f 100644 --- a/index.html +++ b/index.html @@ -54,8 +54,25 @@

Turnip Prophet

+
+ + + + + + + + + + + +
+
+ +
@@ -249,6 +266,21 @@
+ +
+ + + + + + + + + + + +
From d60f2cf812605d53a6c93e97008305991246a93f Mon Sep 17 00:00:00 2001 From: Ryan Carbotte Date: Tue, 21 Apr 2020 17:46:41 -0500 Subject: [PATCH 22/31] feat: removed cloud animations This is a different take on the perf improvements for #155 that allows the clouds to still exist, just without animation. Fixes #155 --- css/styles.css | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/css/styles.css b/css/styles.css index 65b1faf..6631770 100644 --- a/css/styles.css +++ b/css/styles.css @@ -489,39 +489,21 @@ input[type=number] { to {bottom: 0; opacity: 0;} } -/* Animation */ -.parallax>use { - animation: move-forever 25s cubic-bezier(.55, .5, .45, .5) infinite; -} - +/* Cloud SVG placement */ .parallax>use:nth-child(1) { - animation-delay: -2s; - animation-duration: 7s; + transform: translate3d(-30px, 0, 0); } .parallax>use:nth-child(2) { - animation-delay: -3s; - animation-duration: 10s; + transform: translate3d(-90px, 0, 0); } .parallax>use:nth-child(3) { - animation-delay: -4s; - animation-duration: 13s; + transform: translate3d(45px, 0, 0); } .parallax>use:nth-child(4) { - animation-delay: -5s; - animation-duration: 20s; -} - -@keyframes move-forever { - 0% { - transform: translate3d(-90px, 0, 0); - } - - 100% { - transform: translate3d(85px, 0, 0); - } + transform: translate3d(20px, 0, 0); } /*Shrinking for mobile*/ From b3638b22063ea299e838169633e7ae14edc65e48 Mon Sep 17 00:00:00 2001 From: Pupu Date: Wed, 22 Apr 2020 09:34:57 +0800 Subject: [PATCH 23/31] fixed zh-TW,Chinese (Taiwan) wording --- locales/zh-TW.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 2cd009b..213809a 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -4,18 +4,18 @@ }, "welcome": { "salutation": "你好,歡迎使用 Nook 手機上的 Turnip Prophet。", - "description": "這個程式可以讓你每天追蹤你島上的大頭菜價格,但你必須自己記錄價格!", + "description": "這個工具可以讓你每天追蹤自己島上的大頭菜價格,但你必須自己輸入價格!", "conclusion": "接下來,Turnip Prophet 將 神奇地 預測本週剩餘時間的大頭菜價格。" }, "first-time": { "title": "首次購買", - "description": "這是您第一次從自己的島上和曹賣購買大頭菜嗎?(將影響您這次的模型)", + "description": "這是您第一次從自己島上和曹賣購買大頭菜嗎?(將影響您這次的模型)", "yes": "是", "no": "否" }, "patterns": { "title": "上次的模型", - "description": "上週的大頭菜價格模型如何?(將影響您這次的模型)", + "description": "上週大頭菜的價格模型是什麼?(將影響您這次的模型)", "pattern": "模型", "all": "所有模型", "decreasing": "遞減型", From 97018bd2ba2352d6357596e7268f3393f05ff17d Mon Sep 17 00:00:00 2001 From: Pupu Date: Wed, 22 Apr 2020 11:44:32 +0800 Subject: [PATCH 24/31] fixed zh-TW,Chinese (Taiwan) wording --- locales/zh-TW.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 213809a..9e324b1 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -9,13 +9,13 @@ }, "first-time": { "title": "首次購買", - "description": "這是您第一次從自己島上和曹賣購買大頭菜嗎?(將影響您這次的模型)", + "description": "這是你第一次從自己島上和曹賣購買大頭菜嗎?(將影響這次的模型)", "yes": "是", "no": "否" }, "patterns": { "title": "上次的模型", - "description": "上週大頭菜的價格模型是什麼?(將影響您這次的模型)", + "description": "上週大頭菜的價格模型是什麼?(將影響這次的模型)", "pattern": "模型", "all": "所有模型", "decreasing": "遞減型", @@ -25,7 +25,7 @@ "small-spike": "四期型" }, "prices": { - "description": "本週自己島上的大頭菜買價?(若這是您第一次購買大頭菜,則此欄位會被停用)", + "description": "本週自己島上的大頭菜買價?(若這是你第一次購買大頭菜,則此欄位會被停用)", "open": { "am": "上午 - 08:00 到 11:59", "pm": "下午 - 12:00 到 22:00" @@ -69,7 +69,7 @@ } }, "textbox": { - "description": "在您紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示你島上可能出現的不同模型。", + "description": "在你紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示自己島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", "thanks": "要不是 Ninji 協助釐清豆狸和粒狸的大頭菜估價方式,這一切都不可能實現。", "support": "可於 GitHub 取得支援、討論及貢獻。", From fe5a7117127e1eea8ff57f1cbe2ad11a4612f440 Mon Sep 17 00:00:00 2001 From: Pupu Date: Wed, 22 Apr 2020 12:00:58 +0800 Subject: [PATCH 25/31] fixed zh-TW,Chinese (Taiwan) wording (follow #167) --- locales/zh-TW.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 9e324b1..aa0b330 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -69,7 +69,7 @@ } }, "textbox": { - "description": "在你紀錄了一些大頭菜價格後,大頭菜預測工具會預測,並顯示自己島上可能出現的不同模型。", + "description": "在你紀錄了一些大頭菜價格後,Turnip Prophet 會預測,並顯示自己島上可能出現的不同模型。", "development": "此工具仍在開發中,但會隨著時間的推移而改善!", "thanks": "要不是 Ninji 協助釐清豆狸和粒狸的大頭菜估價方式,這一切都不可能實現。", "support": "可於 GitHub 取得支援、討論及貢獻。", From 47c52954707691fda151be4d01f39ec3a8e6e01e Mon Sep 17 00:00:00 2001 From: Valerio Riva Date: Wed, 22 Apr 2020 09:03:41 +0200 Subject: [PATCH 26/31] fixed typo in it translation --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index a6c2112..88a4beb 100644 --- a/locales/it.json +++ b/locales/it.json @@ -65,7 +65,7 @@ "maximum": "Massimo Potenziale", "chart": { "input": "Prezzo Iniziale", - "minimum": "Minimo Gatantito", + "minimum": "Minimo Garantito", "maximum": "Massimo Potenziale" } }, From b72cedc5433a7a1ae4876da67aab3d1bbf37595a Mon Sep 17 00:00:00 2001 From: Splash Date: Wed, 22 Apr 2020 15:13:15 +0800 Subject: [PATCH 27/31] Add style for darkmode toggle button --- css/styles.css | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/css/styles.css b/css/styles.css index 6631770..1b66f45 100644 --- a/css/styles.css +++ b/css/styles.css @@ -519,6 +519,23 @@ input[type=number] { z-index: 1; } +.darkmode-toggle { + opacity: 0.7; + transition: opacity 0.15; +} +.darkmode-toggle:hover { + opacity: 0.9; +} + +.darkmode-toggle:active { + transition: opacity 0; + opacity: 1; +} + +.darkmode-toggle:focus { + outline: none; +} + div.darkmode-background{ background: #fef0e3; background-image: From 85928b29221c3adc1d7624c3951d68897923bfec Mon Sep 17 00:00:00 2001 From: Splash Date: Wed, 22 Apr 2020 15:41:01 +0800 Subject: [PATCH 28/31] Update styles.css --- css/styles.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/styles.css b/css/styles.css index 1b66f45..1dcca7e 100644 --- a/css/styles.css +++ b/css/styles.css @@ -521,14 +521,14 @@ input[type=number] { .darkmode-toggle { opacity: 0.7; - transition: opacity 0.15; + transition: opacity 0.15s; } .darkmode-toggle:hover { opacity: 0.9; } .darkmode-toggle:active { - transition: opacity 0; + transition: opacity 0s; opacity: 1; } From 58afb74d460599b59d7a54aaa40b4cd9f0cfedcb Mon Sep 17 00:00:00 2001 From: Rex Tsou Date: Thu, 23 Apr 2020 13:26:54 +0800 Subject: [PATCH 29/31] Update contributors.js for better performance append element to the DOM too many times may cause performance issue, so do it only once. --- js/contributors.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/contributors.js b/js/contributors.js index e3cf7eb..21cefc5 100644 --- a/js/contributors.js +++ b/js/contributors.js @@ -3,12 +3,14 @@ function getContributors() { const container = $('#contributors'); jQuery.ajax('https://api.github.com/repos/mikebryant/ac-nh-turnip-prices/contributors', {}) .done(function (data) { + const contributorList = []; data.forEach((contributor, idx) => { - container.append(`${contributor.login}`); + contributorList.push(`${contributor.login}`); if (idx < data.length - 1) { - container.append(', '); + contributorList.push(', '); } }); + container.append(contributorList.join('')); }); } } From 00a81fb9303a7f97ad2a5eb5a881a2a3be29b23c Mon Sep 17 00:00:00 2001 From: Gyusun Yeom Date: Wed, 22 Apr 2020 21:55:32 +0900 Subject: [PATCH 30/31] Add korean translation --- js/translations.js | 1 + locales/ko.json | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 locales/ko.json diff --git a/js/translations.js b/js/translations.js index 4bb7306..0319ace 100644 --- a/js/translations.js +++ b/js/translations.js @@ -20,6 +20,7 @@ i18next ['fr', 'Français'], ['it', 'Italiano'], ['ja', '日本語'], + ['ko', '한국어'], ['pt-BR', 'Português'], ['ru', 'Русский'], ['zh-CN', '简体中文'], diff --git a/locales/ko.json b/locales/ko.json new file mode 100644 index 0000000..72b8465 --- /dev/null +++ b/locales/ko.json @@ -0,0 +1,80 @@ +{ + "general": { + "daisy-mae": "무파니" + }, + "welcome": { + "salutation": "안녕하세요, Nook Inc. 스마트폰의 Turnip Prophet앱을 사용해주셔서 감사합니다.", + "description": "이 앱은 당신의 섬의 매일의 무 가격을 추적 할 수 있게 도와줍니다. 하지만 가격은 직접 입력해야 합니다!", + "conclusion": "가격을 입력하고 나면, 이번 주 동안의 무 가격을 마법처럼 예측해줍니다." + }, + "first-time": { + "title": "첫 구매", + "description": "당신의 섬에 방문한 무파니에게서 처음으로 무를 샀습니까?(패턴에 영향을 끼칩니다)", + "yes": "예", + "no": "아니오" + }, + "patterns": { + "title": "이전 패턴", + "description": "저번 주의 무 가격 패턴이 어떻게 됩니까?(패턴에 영향을 끼칩니다)", + "pattern": "패턴", + "all": "모든 패턴", + "decreasing": "감소", + "fluctuating": "파동형", + "unknown": "모름", + "large-spike": "큰 급등", + "small-spike": "작은 급등" + }, + "prices": { + "description": "이번 주에 당신의 섬에서 무를 샀을 때의 가격이 어떻게 됩니까? (첫 구매인 경우에는 입력란이 비활성화됩니다)", + "open": { + "am": "오전 - 오전 8:00 ~ 오전 11:59", + "pm": "오후 - 오후 12:00 ~ 오후 10:00" + }, + "copy-permalink": "고유주소 복사", + "permalink-copied": "고유주소가 복사되었습니다!", + "reset": "Turnip Prophet 초기화", + "reset-warning": "정말로 모든 입력한 값을 지우겠습니까?\n\n되돌릴 수 없습니다!" + }, + "weekdays": { + "monday": "월요일", + "tuesday": "화요일", + "wednesday": "수요일", + "thursday": "목요일", + "friday": "금요일", + "saturday" : "토요일", + "sunday": "일요일", + "abr": { + "monday": "월", + "tuesday": "화", + "wednesday": "수", + "thursday": "목", + "friday": "금", + "saturday" : "토" + } + }, + "times": { + "morning": "오전", + "afternoon": "오후" + }, + "output": { + "title": "결과", + "chance": "% 확률", + "to": "~", + "minimum": "최저 보장 가격", + "maximum": "가능한 최고 가격", + "chart": { + "input": "입력된 가격", + "minimum": "최저 보장 가격", + "maximum": "가능한 최고 가격" + } + }, + "textbox": { + "description": "지금까지의 무 가격을 입력하고 나면, Turnip Prophet이 당신의 섬에서 일어날 수 있는 가격 패턴을 보여줍니다.", + "development": "이 앱은 아직 개발 중입니다. 앞으로도 계속해서 개선될 겁니다!", + "thanks": "콩돌이와 밤돌이가 무 가격을 어떻게 결정하는지 알아낸 Ninji의 연구가 있었기에 이 앱이 만들어질 수 있었습니다.", + "support": "Github을 통해서 문의, 의견 제시, 기여가 가능합니다", + "contributors-text": "그리고 기여를 해주신 분들에게 감사를 표합니다!", + "contributors": "기여해 주신 분들", + "language": "언어" + } +} From f5e0a0bcd48d96750ae021abd80cbea165656bab Mon Sep 17 00:00:00 2001 From: mcpower Date: Thu, 23 Apr 2020 15:32:45 +1000 Subject: [PATCH 31/31] Use exact numbers for steady state probabilities --- js/predictions.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/predictions.js b/js/predictions.js index 70cb996..359d329 100644 --- a/js/predictions.js +++ b/js/predictions.js @@ -787,8 +787,12 @@ class Predictor { get_transition_probability(previous_pattern) { if (typeof previous_pattern === 'undefined' || Number.isNaN(previous_pattern) || previous_pattern === null || previous_pattern < 0 || previous_pattern > 3) { - // TODO: Fill the steady state pattern (https://github.com/mikebryant/ac-nh-turnip-prices/pull/90) here. - return [0.346278, 0.247363, 0.147607, 0.258752]; + // Use the steady state probabilities of PROBABILITY_MATRIX if we don't + // know what the previous pattern was. + // See https://github.com/mikebryant/ac-nh-turnip-prices/issues/68 + // and https://github.com/mikebryant/ac-nh-turnip-prices/pull/90 + // for more information. + return [4530/13082, 3236/13082, 1931/13082, 3385/13082]; } return PROBABILITY_MATRIX[previous_pattern];