diff --git a/blog/dec20/06.html b/blog/dec20/06.html new file mode 100644 index 0000000..0b87b3d --- /dev/null +++ b/blog/dec20/06.html @@ -0,0 +1,20 @@ + + + + December 06, 2020 + + +

KiiwiiWasTaken Blog

+

December 06, 2020

+
+

Hello everyone! As y'all know, the end of the year is coming up, and you know what that means...

+

TIME FOR SOME FUCKING NEW YEAR'S RESOLUTIONS

+

Now I've never really had any set resolutions since I find them a bit odd, but I think I may try it this year... and here's my whole list! ^^

+ +

That's all the resolutions I have for 2021! I'm hoping I can stick to those last two as they're a bit important to me. I'm not starting the game project until Janurary 01, 2021 -- and I'll make a blog post and have webpage on it ^^

+ + diff --git a/blog/dec20/index.html b/blog/dec20/index.html index 84892ce..2ddf5ba 100644 --- a/blog/dec20/index.html +++ b/blog/dec20/index.html @@ -12,6 +12,7 @@

December 2020 Blog Archive

diff --git a/fun-shit/turnips/.github/FUNDING.yml b/fun-shit/turnips/.github/FUNDING.yml new file mode 100644 index 0000000..b125e30 --- /dev/null +++ b/fun-shit/turnips/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: +- mikebryant +- theRTC204 diff --git a/fun-shit/turnips/.github/ISSUE_TEMPLATE/bug_report.md b/fun-shit/turnips/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..72c88ef --- /dev/null +++ b/fun-shit/turnips/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Things to check** +- Have you forced a refresh in your browser (e.g. ctrl+F5 in Chrome/Firefox)? + - yes/no +- Have you made sure the first time buyer option is correct? If you're unsure, try it both ways + - yes/no +- Have you time-travelled this week? Travelling backwards resets the prices, and therefore you must not put prices in from both before & after the time-travel + - yes/no + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Permalink to your prices** +Please click the `Copy Permalink` button and paste it here + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/fun-shit/turnips/.github/ISSUE_TEMPLATE/feature_request.md b/fun-shit/turnips/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/fun-shit/turnips/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/fun-shit/turnips/.github/no-response.yml b/fun-shit/turnips/.github/no-response.yml new file mode 100644 index 0000000..ab52138 --- /dev/null +++ b/fun-shit/turnips/.github/no-response.yml @@ -0,0 +1,10 @@ +# Configuration for probot-no-response - https://github.com/probot/no-response + +daysUntilClose: 7 +responseRequiredLabel: more information required +closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. Please reach out if you have or find the answers we need so + that we can investigate further. diff --git a/fun-shit/turnips/CNAME b/fun-shit/turnips/CNAME new file mode 100644 index 0000000..1028e81 --- /dev/null +++ b/fun-shit/turnips/CNAME @@ -0,0 +1 @@ +turnipprophet.io \ No newline at end of file diff --git a/fun-shit/turnips/COPYRIGHT b/fun-shit/turnips/COPYRIGHT new file mode 100644 index 0000000..9e5c3ef --- /dev/null +++ b/fun-shit/turnips/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright 2020 Mike Bryant + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not files from this repository except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/fun-shit/turnips/LICENSE b/fun-shit/turnips/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/fun-shit/turnips/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fun-shit/turnips/NOTICE b/fun-shit/turnips/NOTICE new file mode 100644 index 0000000..f01ec2a --- /dev/null +++ b/fun-shit/turnips/NOTICE @@ -0,0 +1,3 @@ +Originally developed by Mike Bryant, with great thanks to Ninji + +Original project location: https://github.com/mikebryant/ac-nh-turnip-prices diff --git a/fun-shit/turnips/README.md b/fun-shit/turnips/README.md new file mode 100644 index 0000000..d1140ce --- /dev/null +++ b/fun-shit/turnips/README.md @@ -0,0 +1,66 @@ +# Animal Crossing New Horizons: Turnip Prophet +[![discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=for-the-badge)](https://discord.gg/bRh74X8) +[![issues](https://img.shields.io/github/issues/mikebryant/ac-nh-turnip-prices?style=for-the-badge)](https://github.com/mikebryant/ac-nh-turnip-prices/issues) +[![pull requests](https://img.shields.io/github/issues-pr/mikebryant/ac-nh-turnip-prices?style=for-the-badge)](https://github.com/mikebryant/ac-nh-turnip-prices/pulls) +[![contributors](https://img.shields.io/github/contributors/mikebryant/ac-nh-turnip-prices?style=for-the-badge)](https://github.com/mikebryant/ac-nh-turnip-prices/graphs/contributors) + +Turnip Prophet is a price calculator/price predictor for Animal Crossing: New Horizons turnip prices. + +## Support + +If you have any questions, feel free to join our [Discord server](https://discord.gg/bRh74X8) to ask or [open a new issue](https://github.com/mikebryant/ac-nh-turnip-prices/issues). + +If you have a prediction issue, please open an issue describing your problem and give the permalink to your prediction. Otherwise, please search the issues before opening a new one to make sure you are not opening a duplicate issue. + +Please create issues in English language only. + +## What about feature X? + +At first please have a look at our current project scope: + +| Turnip Prophet is | Turnip Prophet is not | +|----|----| +| A predictor for future prices that week | A calculator for how much money you'll make | +| Able to calculate probabilities for different futures | A way to count your turnips | +| Able to show data from a query string | A way to store multiple people's islands | +| A single page web-based app | Something with a backend | + +If your idea, suggestion or improvement is anything out of the above named, feel free to [open a new issue](https://github.com/mikebryant/ac-nh-turnip-prices/issues) or contribute by a [new pull request](https://github.com/mikebryant/ac-nh-turnip-prices/pulls). + +## How to run the project locally? + +To run the project locally you will have to clone it and then, from the folder you just cloned, you will have to execute a command. There are multiple options, listed below: + +### Using Python + +For Python 2.7: + +```python -m SimpleHTTPServer``` + +For Python 3: + +```python3 -m http.server``` + +### Using Node.js + +```npx serve``` + +### Using Chrome + +```google-chrome --allow-file-access-from-files``` + + +## Adding a new language + +Turnip Prophet is already available in some languages. If your local language is not listed you may go on to create a JSON file corresponding to your language in the folder [locales](https://github.com/mikebryant/ac-nh-turnip-prices/tree/master/locales). You may copy the [English localisation](https://github.com/mikebryant/ac-nh-turnip-prices/blob/master/locales/en.json) and translate it. + +Please make sure **not to translate** "Turnip Prophet" and include the new language in the selector inside [js/translations.js](https://github.com/mikebryant/ac-nh-turnip-prices/blob/master/js/translations.js). + +If you have any remaining questions, feel free to stop by the Discord server and ask. + + +## Final statement + +A special thanks to all who [contribute](https://github.com/mikebryant/ac-nh-turnip-prices/graphs/contributors) to this project, helping to improve it and spend their time. + +Stay awesome guys. diff --git a/fun-shit/turnips/css/styles.css b/fun-shit/turnips/css/styles.css new file mode 100644 index 0000000..e6f6716 --- /dev/null +++ b/fun-shit/turnips/css/styles.css @@ -0,0 +1,670 @@ +@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@800&family=Varela+Round&display=swap'); + +/* - Variables - */ + +:root { + --color-blue: #0AB5CD; + --color-light-blue: #5ECEDB; + + --bg-color: #DEF2D9; + --bg-dot-color: #FFF; + + --shadow-3: rgba(0, 0, 0, 0.03); + --shadow-5: rgba(0, 0, 0, 0.05); + --shadow-6: rgba(0, 0, 0, 0.06); + --shadow-8: rgba(0, 0, 0, 0.08); + --shadow-9: rgba(0, 0, 0, 0.09); + --shadow-10: rgba(0, 0, 0, 0.10); + --shadow-15: rgba(0, 0, 0, 0.16); + --shadow-16: rgba(0, 0, 0, 0.16); + --shadow-20: rgba(0, 0, 0, 0.20); + + --center-bg-color: #FFF; + + --wave-1: rgba(255, 255, 255, 0); + --wave-2: rgba(255, 255, 255, 0.2); + --wave-3: rgba(255, 255, 255, 0.4); + --wave-4: rgba(255, 255, 255, 0.6); + + --nook-phone-bg-color: #F5F8FF; + --nook-phone-text-color: #686868; + + --dialog-bg-color: #FFFAE5; + --dialog-text-color: #837865; + + --dialog-name-bg-color: #FF9A40; + --dialog-name-text-color: #BA3B1F; + + --chart-fill-color: var(--bg-color); + --chart-line-color: rgba(0, 0, 0, 0.1); + --chart-point-color: rgba(0, 0, 0, 0.1); + + --select-text-color: var(--dialog-text-color); + --select-border-color: var(--bg-color); + --select-bg-color-hover: #EBFEFD; + + --italic-color: #AAA; + + --form-h6-text-color: #845E44; + + --radio-hover-bg-color: var(--nook-phone-bg-color); + --radio-checked-text-color: #FFF; + + --input-bg-color: #F3F3F3; + --input-focus-bg-color: white; + --input-focus-text-color: var(--color-blue); + + --input-now-bg-color: var(--dialog-name-bg-color); + --input-now-text-color: var(--dialog-name-text-color); + + --button-text-color: var(--nook-phone-text-color); + --button-reset-text-color: #E45B5B; + + --table-range0: hsl(140, 80%, 85%); + --table-range1: hsl(90, 80%, 85%); + --table-range2: hsl(60, 80%, 85%); + --table-range3: hsl(30, 80%, 85%); + --table-range4: hsl(0, 80%, 85%); +} + +[data-theme="dark"] { + --bg-color: #1A1A1A; + --bg-dot-color: #222; + + --shadow-3: rgba(255, 255, 255, 0.03); + --shadow-15: rgba(255, 255, 255, 0.03); + + --center-bg-color: #101010; + + --wave-1: rgba(16, 16, 16, 0); + --wave-2: rgba(16, 16, 16, 0.2); + --wave-3: rgba(16, 16, 16, 0.4); + --wave-4: rgba(16, 16, 16, 0.6); + + --nook-phone-bg-color: #000F33; + --nook-phone-text-color: #CCC; + + --dialog-bg-color: #252422; + --dialog-text-color: #BCB5A9; + + --dialog-name-bg-color: #BA3B1F; + --dialog-name-text-color: #FF9A40; + + --chart-fill-color: #2D5F21; + --chart-line-color: rgba(200, 200, 200, 0.4); + --chart-point-color: rgba(200, 200, 200, 0.6); + + --select-text-color: #837865; + --select-border-color: var(--bg-color); + --select-bg-color-hover: #EBFEFD; + + --italic-color: #666; + + --form-h6-text-color: #E18B51; + + --radio-hover-bg-color: #00174D; + --radio-checked-text-color: #FFF; + + --input-bg-color: #333; + --input-focus-bg-color: #999; + --input-focus-text-color: var(--radio-hover-bg-color); + + --button-text-color: var(--nook-phone-text-color); + --button-reset-text-color: #E45B5B; + + --table-range0: hsl(140, 80%, 27%); + --table-range1: hsl(90, 80%, 20%); + --table-range2: hsl(60, 80%, 20%); + --table-range3: hsl(30, 80%, 20%); + --table-range4: hsl(0, 80%, 22%); +} + +/* - Global Styles - */ + +html { + font-size: 14px; + background: var(--bg-color); + background-image: + radial-gradient(var(--bg-dot-color) 20%, transparent 0), + radial-gradient(var(--bg-dot-color) 20%, transparent 0); + background-size: 30px 30px; + background-position: 0 0, 15px 15px; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: 'Varela Round', sans-serif; +} + +h1 { + text-align: center; + font-size: 1.8rem; +} + +h2 { + text-align: center; + font-size: 1.6rem; +} + +.nook-phone { + width: 100%; + max-width: 1400px; + box-sizing: border-box; + margin: 16px auto; + border-radius: 40px; + padding: 16px 0px; + padding-bottom: 16px; + background: var(--nook-phone-bg-color); + color: var(--nook-phone-text-color); + overflow: hidden; + box-shadow: 0 1px 3px var(--shadow-6), 0 1px 2px var(--shadow-8); +} + +.nook-phone-center { + background: var(--center-bg-color); + display: flex; + flex-direction: column; + align-items: center; +} + +.dialog-box { + background: var(--dialog-bg-color); + box-sizing: border-box; + padding: 16px 24px; + margin: 32px auto; + position: relative; + border-radius: 40px; + max-width: 800px; + box-shadow: 0 1px 3px var(--shadow-6), 0 1px 2px var(--shadow-8); +} + +.dialog-box-option { + text-align: center; +} + +.dialog-box-option p, +.dialog-box-option select { + display: inline; +} + +.dialog-box-option select { + font-size: 1rem; + padding: 4px; + font-weight: bold; + border-radius: 4px; + border-color: var(--select-border-color); + color: var(--select-text-color); + cursor: pointer; + transition: 0.2s all; +} + +.dialog-box-option select:hover { + background-color: var(--select-bg-color-hover); + border-color: var(--color-light-blue); + box-shadow: 0 2px 4px var(--shadow-16); +} + +.dialog-box-option select:focus { + outline: none; +} + +.dialog-box p, +.dialog-box label { + font-family: 'Raleway', sans-serif; + font-weight: 800; + font-size: 1rem; + color: var(--dialog-text-color); + letter-spacing: 0.2px; + line-height: 1.8rem; +} + +.dialog-box b, +.dialog-box a { + color: var(--color-blue); + transition: 0.2s all; +} + +.dialog-box i { + font-style: normal; + color: var(--italic-color); +} + +.dialog-box a:hover { + color: var(--color-light-blue); +} + +.dialog-box .dialog-box__name { + position: absolute; + left: 16px; + top: -28px; + font-size: 1rem; + color: var(--dialog-name-text-color); + padding: 4px 16px; + background: var(--dialog-name-bg-color); + border-radius: 40px; +} + +.dialog-box.error { + display: none; +} + +.input__form { + background: var(--center-bg-color); + display: flex; + flex-direction: column; + padding: 16px; + align-items: center; +} + +.form__row { + display: flex; + flex-wrap: wrap; + margin-bottom: 16px; + justify-content: center; + align-items: center; +} + +.form__row h6 { + width: 100%; + display: block; + font-weight: 800; + font-size: 1.25rem; + margin: 8px auto; + color: var(--form-h6-text-color); + text-align: center; +} + +.form__flex-wrap { + margin-top: 8px; + display: flex; + flex-wrap: wrap; + width: 100%; + max-width: 1080px; + justify-content: center; +} + +.input__group { + display: flex; + flex-direction: column; + margin: 8px; + align-items: center; +} + +.input__group label { + font-size: 1rem; + font-weight: bold; + margin-bottom: 8px; + opacity: 0.7; + text-align: center; +} + +.form__flex-wrap .input__group label { + margin-left: 0px; + margin-bottom: 8px; +} + +.input__form i { + text-align: center; + display: block; + font-style: normal; + color: var(--italic-color); + font-size: 0.9rem; + margin: 8px auto; +} + +.input__form>.form__row input { + margin: 0px auto; +} + +input { + border: 0px solid white; + border-radius: 40px; + padding: 8px 16px; + font-size: 1.25rem; + font-family: inherit; + color: inherit; + font-weight: bold; + transition: 0.2s all; + margin: 8px 0px; +} + +input[type=number]:placeholder-shown { + background: var(--input-bg-color); +} + +input[type=number]:not(:placeholder-shown) { + background: transparent; + color: var(--color-blue); +} + +input[type=number]:placeholder-shown:hover { + cursor: pointer; + background: var(--radio-hover-bg-color); + transform: scale(1.1); + box-shadow: 0 1px 6px var(--shadow-5), 0 3px 6px var(--shadow-9); +} + +input[type=number]:focus { + outline: none; + transform: scale(1.1); + color: var(--input-focus-text-color); + background: var(--input-focus-bg-color); + box-shadow: 0 1px 6px var(--shadow-5), 0 3px 6px var(--shadow-9); +} + +input[type=number]:focus::placeholder { + opacity: 0; +} + +input[type=number] { + width: 60px; + text-align: center; +} + +input[type=number]:disabled { + background: inherit; +} + +input[type=number]:disabled:hover { + box-shadow: none; + transform: none; + cursor: default; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type=number] { + -moz-appearance: textfield; +} + +.input__radio-buttons { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-top: 8px; +} + +.input__radio-buttons input[type=radio] { + display: none; +} + +.input__radio-buttons input[type="radio"]+label { + opacity: 1; + border: none; + border-radius: 40px; + background: var(--input-bg-color); + padding: 8px 16px; + font-size: 1.25rem; + font-family: inherit; + font-weight: bold; + transition: 0.2s all; + margin: 8px; +} + +.input__radio-buttons input[type="radio"]:not(:checked)+label:hover { + cursor: pointer; + background: var(--radio-hover-bg-color); + transform: scale(1.1); + box-shadow: 0 1px 6px var(--shadow-5), 0 3px 6px var(--shadow-9); +} + +.input__radio-buttons input[type="radio"]:checked+label { + background: var(--color-blue); + color: var(--radio-checked-text-color); +} + +input[class=now]:placeholder-shown { + background: var(--input-now-bg-color); +} + +input[class=now]:placeholder-shown::placeholder { + color: var(--input-now-text-color); +} + +.button { + color: var(--button-text-color); + font-family: inherit; + font-weight: bold; + padding: 8px 16px; + border-width: 0px; + border-radius: 40px; + background: var(--input-bg-color); + font-size: 1.2rem; + transition: 0.2s all; + position: relative; + margin: 16px auto; +} + +.button:hover { + transform: scale(1.1); + cursor: pointer; + background: var(--radio-hover-bg-color); + opacity: 1; + box-shadow: 0 1px 6px var(--shadow-5), 0 3px 6px var(--shadow-9); +} + +.button.button--reset { + color: var(--button-reset-text-color); +} + +.table-wrapper { + display: inline-block; + max-width: 98%; + padding: 16px; + margin: 0px auto; + box-sizing: border-box; + overflow-x: auto; + scrollbar-width: thin; +} + +@media only screen and (max-width: 1440px) and (pointer: fine) { + .table-wrapper { + max-height: calc(75vh - 40px); + } +} + +.table-wrapper::-webkit-scrollbar { + height: 8px; + width: 5px; +} + +.table-wrapper::-webkit-scrollbar-track { + height: 8px; + width: 5px; + box-shadow: inset 0 0 6px var(--shadow-20); + -webkit-box-shadow: inset 0 0 6px var(--shadow-20); +} + +.table-wrapper::-webkit-scrollbar-thumb { + height: 8px; + width: 5px; + background: var(--shadow-20); + box-shadow: inset 0 0 6px var(--shadow-20); + -webkit-box-shadow: inset 0 0 6px var(--shadow-10); +} + +.table-wrapper::-webkit-scrollbar-thumb:window-inactive { + height: 8px; + width: 5px; + background: var(--shadow-20); +} + +#turnipTable { + border-collapse: collapse; +} + +#turnipTable th div:nth-of-type(1) { + margin-bottom: 2px; +} + +#turnipTable th div:nth-of-type(2) { + display: flex; + justify-content: space-around; + opacity: 0.4; +} + +#turnipTable td { + white-space: nowrap; + max-width: 150px; + padding: 6px 4px; + text-align: center; + border-right: 1px solid var(--shadow-3); + border-bottom: 1px solid var(--shadow-15); +} + +#turnipTable tbody tr { + opacity: 0.8; +} + +#turnipTable tbody tr:hover { + cursor: default; + opacity: 1; +} + +#turnipTable .table-pattern { + white-space: nowrap; +} + +#turnipTable td.range4 { + background-color: var(--table-range4); +} + +#turnipTable td.range3{ + background-color: var(--table-range3); +} + +#turnipTable td.range2 { + background-color: var(--table-range2); +} + +#turnipTable td.range1 { + background-color: var(--table-range1); +} + +#turnipTable td.range0 { + background-color: var(--table-range0); +} + +.chart-wrapper { + margin-top: 8px; + display: flex; + flex-wrap: wrap; + height: 400px; + width: 100%; + max-width: 1080px; + justify-content: center; +} + +.waves { + position: relative; + width: 100%; + height: 5vh; + margin-bottom: -7px; + /*Fix for safari gap*/ + max-height: 150px; +} + +#permalink-input { + display: none; + position: fixed; +} + +.permalink { + display: none; + white-space: nowrap; + font-size: 18px; + user-select: none; + cursor: pointer; +} + +.permalink .fa-copy { + margin: 0 8px; + height: 20px; + color: var(--color-blue); +} + +/* The snackbar - position it at the bottom and in the middle of the screen */ +#snackbar { + visibility: hidden; /* Hidden by default. Visible on click */ + min-width: 250px; /* Set a default minimum width */ + background-color: var(--dialog-bg-color); /* Black background color */ + font-family: 'Raleway', sans-serif; + font-weight: 800; + font-size: 1rem; + color: var(--dialog-text-color); + letter-spacing: 0.2px; + line-height: 1.8rem; + text-align: center; /* Centered text */ + border-radius: 40px; /* Rounded borders */ + padding: 16px 24px; /* Padding */ + position: fixed; /* Sit on top of the screen */ + z-index: 1; /* Add a z-index if needed */ + bottom: 30px; /* 30px from the bottom */ + box-shadow: 0 1px 3px var(--shadow-6), 0 1px 2px var(--shadow-8); +} + +/* Show the snackbar when clicking on a button (class added with JavaScript) */ +#snackbar.show { + visibility: visible; /* Show the snackbar */ + /* Add animation: Take 0.5 seconds to fade in and out the snackbar. + However, delay the fade out process for 2.5 seconds */ + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; +} + +/* Animations to fade the snackbar in and out */ +@-webkit-keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@-webkit-keyframes fadeout { + from {bottom: 30px; opacity: 1;} + to {bottom: 0; opacity: 0;} +} + +@keyframes fadeout { + from {bottom: 30px; opacity: 1;} + to {bottom: 0; opacity: 0;} +} + +/* Cloud SVG placement */ +.parallax>use:nth-child(1) { + transform: translate3d(-30px, 0, 0); + fill: var(--wave-4); +} + +.parallax>use:nth-child(2) { + transform: translate3d(-90px, 0, 0); + fill: var(--wave-3); +} + +.parallax>use:nth-child(3) { + transform: translate3d(45px, 0, 0); + fill: var(--wave-2); +} + +.parallax>use:nth-child(4) { + transform: translate3d(20px, 0, 0); + fill: var(--wave-1); +} + +/*Shrinking for mobile*/ +@media (max-width: 768px) { + .waves { + height: 40px; + min-height: 40px; + } +} diff --git a/fun-shit/turnips/favicon.ico b/fun-shit/turnips/favicon.ico new file mode 100644 index 0000000..b06b446 Binary files /dev/null and b/fun-shit/turnips/favicon.ico differ diff --git a/fun-shit/turnips/img/favicon-128.png b/fun-shit/turnips/img/favicon-128.png new file mode 100644 index 0000000..08a2116 Binary files /dev/null and b/fun-shit/turnips/img/favicon-128.png differ diff --git a/fun-shit/turnips/img/favicon-152.png b/fun-shit/turnips/img/favicon-152.png new file mode 100644 index 0000000..4137fda Binary files /dev/null and b/fun-shit/turnips/img/favicon-152.png differ diff --git a/fun-shit/turnips/img/favicon-167.png b/fun-shit/turnips/img/favicon-167.png new file mode 100644 index 0000000..cee09d8 Binary files /dev/null and b/fun-shit/turnips/img/favicon-167.png differ diff --git a/fun-shit/turnips/img/favicon-180.png b/fun-shit/turnips/img/favicon-180.png new file mode 100644 index 0000000..47147bc Binary files /dev/null and b/fun-shit/turnips/img/favicon-180.png differ diff --git a/fun-shit/turnips/img/favicon-192.png b/fun-shit/turnips/img/favicon-192.png new file mode 100644 index 0000000..876941d Binary files /dev/null and b/fun-shit/turnips/img/favicon-192.png differ diff --git a/fun-shit/turnips/img/favicon-196.png b/fun-shit/turnips/img/favicon-196.png new file mode 100644 index 0000000..1d27fa5 Binary files /dev/null and b/fun-shit/turnips/img/favicon-196.png differ diff --git a/fun-shit/turnips/img/favicon-32.png b/fun-shit/turnips/img/favicon-32.png new file mode 100644 index 0000000..764bb36 Binary files /dev/null and b/fun-shit/turnips/img/favicon-32.png differ diff --git a/fun-shit/turnips/img/favicon-512.png b/fun-shit/turnips/img/favicon-512.png new file mode 100644 index 0000000..124adf7 Binary files /dev/null and b/fun-shit/turnips/img/favicon-512.png differ diff --git a/fun-shit/turnips/index.html b/fun-shit/turnips/index.html new file mode 100644 index 0000000..dcaf2e8 --- /dev/null +++ b/fun-shit/turnips/index.html @@ -0,0 +1,361 @@ + + + + + + + + Animal Crossing - Turnip Prophet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

+

+

+
+ +
+

Turnip Prophet

+ +
+ + + + + + + + + + + +
+ +
+ + + +
+
+
+
+ +
+ + + + +
+
+
+ +
+
+
+ +
+ + + + + + + + + + +
+
+
+ +
+
+
+ + + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+
+ + + +
+ + + +

+ +
+

+

+

+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + +
+
+ + + +
+

+

+

+
+ +
+

+

+

+

+

:

+

+
+

:

+
+
+

:

+
+
+ +
Some text some message...
+ + + + + + + + + + + + + + + + diff --git a/fun-shit/turnips/js/chart.js b/fun-shit/turnips/js/chart.js new file mode 100644 index 0000000..70c2dad --- /dev/null +++ b/fun-shit/turnips/js/chart.js @@ -0,0 +1,69 @@ +let chart_instance = null; + +Chart.defaults.global.defaultFontFamily = "'Varela Round', sans-serif"; + +const chart_options = { + elements: { + line: { + get backgroundColor() { + return getComputedStyle(document.documentElement).getPropertyValue('--chart-fill-color'); + }, + get borderColor() { + return getComputedStyle(document.documentElement).getPropertyValue('--chart-line-color'); + }, + cubicInterpolationMode: "monotone", + }, + }, + maintainAspectRatio: false, + tooltips: { + intersect: false, + mode: "index", + }, +}; + +function update_chart(input_data, possibilities) { + let ctx = $("#chart"), + datasets = [{ + label: i18next.t("output.chart.input"), + get pointBorderColor() { + return getComputedStyle(document.documentElement).getPropertyValue('--chart-point-color'); + }, + data: input_data.slice(1), + fill: false, + }, { + label: i18next.t("output.chart.minimum"), + get pointBorderColor() { + return getComputedStyle(document.documentElement).getPropertyValue('--chart-point-color'); + }, + data: possibilities[0].prices.slice(1).map(day => day.min), + fill: false, + }, { + label: i18next.t("output.chart.maximum"), + get pointBorderColor() { + return getComputedStyle(document.documentElement).getPropertyValue('--chart-point-color'); + }, + 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.options = chart_options; + chart_instance.update(); + } else { + chart_instance = new Chart(ctx, { + data: { + datasets: datasets, + labels: labels + }, + options: chart_options, + type: "line", + }); + } +} diff --git a/fun-shit/turnips/js/contributors.js b/fun-shit/turnips/js/contributors.js new file mode 100644 index 0000000..8961f1e --- /dev/null +++ b/fun-shit/turnips/js/contributors.js @@ -0,0 +1,26 @@ +function getContributors(page) { + const PER_PAGE = 100 + if (window.jQuery) { + const container = $('#contributors'); + jQuery.ajax(`https://api.github.com/repos/mikebryant/ac-nh-turnip-prices/contributors?page=${page}&per_page=${PER_PAGE}`, {}) + .done(function (data) { + const contributorList = []; + data.forEach((contributor, idx) => { + if (idx === 0 && page > 1) { + contributorList.push(', '); + } + + contributorList.push(`${contributor.login}`); + if (idx < data.length - 1) { + contributorList.push(', '); + } + }); + container.append(contributorList.join('')); + // If the length of the data is < PER_PAGE, we know we are processing the last page of data. + if (data.length < PER_PAGE) return; + getContributors(page + 1); + }); + } +} + +$(document).ready(getContributors(1)); diff --git a/fun-shit/turnips/js/predictions.js b/fun-shit/turnips/js/predictions.js new file mode 100644 index 0000000..eb680b4 --- /dev/null +++ b/fun-shit/turnips/js/predictions.js @@ -0,0 +1,999 @@ +const PATTERN = { + FLUCTUATING: 0, + LARGE_SPIKE: 1, + DECREASING: 2, + SMALL_SPIKE: 3, +}; + +const PROBABILITY_MATRIX = { + [PATTERN.FLUCTUATING]: { + [PATTERN.FLUCTUATING]: 0.20, + [PATTERN.LARGE_SPIKE]: 0.30, + [PATTERN.DECREASING]: 0.15, + [PATTERN.SMALL_SPIKE]: 0.35, + }, + [PATTERN.LARGE_SPIKE]: { + [PATTERN.FLUCTUATING]: 0.50, + [PATTERN.LARGE_SPIKE]: 0.05, + [PATTERN.DECREASING]: 0.20, + [PATTERN.SMALL_SPIKE]: 0.25, + }, + [PATTERN.DECREASING]: { + [PATTERN.FLUCTUATING]: 0.25, + [PATTERN.LARGE_SPIKE]: 0.45, + [PATTERN.DECREASING]: 0.05, + [PATTERN.SMALL_SPIKE]: 0.25, + }, + [PATTERN.SMALL_SPIKE]: { + [PATTERN.FLUCTUATING]: 0.45, + [PATTERN.LARGE_SPIKE]: 0.25, + [PATTERN.DECREASING]: 0.15, + [PATTERN.SMALL_SPIKE]: 0.15, + }, +}; + +const RATE_MULTIPLIER = 10000; + +function range_length(range) { + return range[1] - range[0]; +} + +function clamp(x, min, max) { + return Math.min(Math.max(x, min), max); +} + +function range_intersect(range1, range2) { + if (range1[0] > range2[1] || range1[1] < range2[0]) { + return null; + } + return [Math.max(range1[0], range2[0]), Math.min(range1[1], range2[1])]; +} + +function range_intersect_length(range1, range2) { + if (range1[0] > range2[1] || range1[1] < range2[0]) { + return 0; + } + return range_length(range_intersect(range1, range2)); +} + +/** + * Accurately sums a list of floating point numbers. + * See https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + * for more information. + * @param {number[]} input + * @returns {number} The sum of the input. + */ +function float_sum(input) { + // Uses the improved Kahan–Babuska algorithm introduced by Neumaier. + let sum = 0; + // The "lost bits" of sum. + let c = 0; + for (let i = 0; i < input.length; i++) { + const cur = input[i]; + const t = sum + cur; + if (Math.abs(sum) >= Math.abs(cur)) { + c += (sum - t) + cur; + } else { + c += (cur - t) + sum; + } + sum = t; + } + return sum + c; +} + +/** + * Accurately returns the prefix sum of a list of floating point numbers. + * See https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + * for more information. + * @param {number[]} input + * @returns {[number, number][]} The prefix sum of the input, such that + * output[i] = [sum of first i integers, error of the sum]. + * The "true" prefix sum is equal to the sum of the pair of numbers, but it is + * explicitly returned as a pair of numbers to ensure that the error portion + * isn't lost when subtracting prefix sums. + */ +function prefix_float_sum(input) { + const prefix_sum = [[0, 0]]; + let sum = 0; + let c = 0; + for (let i = 0; i < input.length; i++) { + const cur = input[i]; + const t = sum + cur; + if (Math.abs(sum) >= Math.abs(cur)) { + c += (sum - t) + cur; + } else { + c += (cur - t) + sum; + } + sum = t; + prefix_sum.push([sum, c]); + } + return prefix_sum; +} + +/* + * Probability Density Function of rates. + * Since the PDF is continuous*, we approximate it by a discrete probability function: + * the value in range [x, x + 1) has a uniform probability + * prob[x - value_start]; + * + * Note that we operate all rate on the (* RATE_MULTIPLIER) scale. + * + * (*): Well not really since it only takes values that "float" can represent in some form, but the + * space is too large to compute directly in JS. + */ +class PDF { + /** + * Initialize a PDF in range [a, b], a and b can be non-integer. + * if uniform is true, then initialize the probability to be uniform, else initialize to a + * all-zero (invalid) PDF. + * @param {number} a - Left end-point. + * @param {number} b - Right end-point end-point. + * @param {boolean} uniform - If true, initialise with the uniform distribution. + */ + constructor(a, b, uniform = true) { + // We need to ensure that [a, b] is fully contained in [value_start, value_end]. + /** @type {number} */ + this.value_start = Math.floor(a); + /** @type {number} */ + this.value_end = Math.ceil(b); + const range = [a, b]; + const total_length = range_length(range); + /** @type {number[]} */ + this.prob = Array(this.value_end - this.value_start); + if (uniform) { + for (let i = 0; i < this.prob.length; i++) { + this.prob[i] = + range_intersect_length(this.range_of(i), range) / total_length; + } + } + } + + /** + * Calculates the interval represented by this.prob[idx] + * @param {number} idx - The index of this.prob + * @returns {[number, number]} The interval representing this.prob[idx]. + */ + range_of(idx) { + // We intentionally include the right end-point of the range. + // The probability of getting exactly an endpoint is zero, so we can assume + // the "probability ranges" are "touching". + return [this.value_start + idx, this.value_start + idx + 1]; + } + + min_value() { + return this.value_start; + } + + max_value() { + return this.value_end; + } + + /** + * @returns {number} The sum of probabilities before normalisation. + */ + normalize() { + const total_probability = float_sum(this.prob); + for (let i = 0; i < this.prob.length; i++) { + this.prob[i] /= total_probability; + } + return total_probability; + } + + /* + * Limit the values to be in the range, and return the probability that the value was in this + * range. + */ + range_limit(range) { + let [start, end] = range; + start = Math.max(start, this.min_value()); + end = Math.min(end, this.max_value()); + if (start >= end) { + // Set this to invalid values + this.value_start = this.value_end = 0; + this.prob = []; + return 0; + } + start = Math.floor(start); + end = Math.ceil(end); + + const start_idx = start - this.value_start; + const end_idx = end - this.value_start; + for (let i = start_idx; i < end_idx; i++) { + this.prob[i] *= range_intersect_length(this.range_of(i), range); + } + + this.prob = this.prob.slice(start_idx, end_idx); + this.value_start = start; + this.value_end = end; + + // The probability that the value was in this range is equal to the total + // sum of "un-normalised" values in the range. + return this.normalize(); + } + + /** + * Subtract the PDF by a uniform distribution in [rate_decay_min, rate_decay_max] + * + * For simplicity, we assume that rate_decay_min and rate_decay_max are both integers. + * @param {number} rate_decay_min + * @param {number} rate_decay_max + * @returns {void} + */ + decay(rate_decay_min, rate_decay_max) { + // In case the arguments aren't integers, round them to the nearest integer. + rate_decay_min = Math.round(rate_decay_min); + rate_decay_max = Math.round(rate_decay_max); + // The sum of this distribution with a uniform distribution. + // Let's assume that both distributions start at 0 and X = this dist, + // Y = uniform dist, and Z = X + Y. + // Let's also assume that X is a "piecewise uniform" distribution, so + // x(i) = this.prob[Math.floor(i)] - which matches our implementation. + // We also know that y(i) = 1 / max(Y) - as we assume that min(Y) = 0. + // In the end, we're interested in: + // Pr(i <= Z < i+1) where i is an integer + // = int. x(val) * Pr(i-val <= Y < i-val+1) dval from 0 to max(X) + // = int. x(floor(val)) * Pr(i-val <= Y < i-val+1) dval from 0 to max(X) + // = sum val from 0 to max(X)-1 + // x(val) * f_i(val) / max(Y) + // where f_i(val) = + // 0.5 if i-val = 0 or max(Y), so val = i-max(Y) or i + // 1.0 if 0 < i-val < max(Y), so i-max(Y) < val < i + // as x(val) is "constant" for each integer step, so we can consider the + // integral in integer steps. + // = sum val from max(0, i-max(Y)) to min(max(X)-1, i) + // x(val) * f_i(val) / max(Y) + // for example, max(X)=1, max(Y)=10, i=5 + // = sum val from max(0, 5-10)=0 to min(1-1, 5)=0 + // x(val) * f_i(val) / max(Y) + // = x(0) * 1 / 10 + + // Get a prefix sum / CDF of this so we can calculate sums in O(1). + const prefix = prefix_float_sum(this.prob); + const max_X = this.prob.length; + const max_Y = rate_decay_max - rate_decay_min; + const newProb = Array(this.prob.length + max_Y); + for (let i = 0; i < newProb.length; i++) { + // Note that left and right here are INCLUSIVE. + const left = Math.max(0, i - max_Y); + const right = Math.min(max_X - 1, i); + // We want to sum, in total, prefix[right+1], -prefix[left], and subtract + // the 0.5s if necessary. + // This may involve numbers of differing magnitudes, so use the float sum + // algorithm to sum these up. + const numbers_to_sum = [ + prefix[right + 1][0], prefix[right + 1][1], + -prefix[left][0], -prefix[left][1], + ]; + if (left === i-max_Y) { + // Need to halve the left endpoint. + numbers_to_sum.push(-this.prob[left] / 2); + } + if (right === i) { + // Need to halve the right endpoint. + // It's guaranteed that we won't accidentally "halve" twice, + // as that would require i-max_Y = i, so max_Y = 0 - which is + // impossible. + numbers_to_sum.push(-this.prob[right] / 2); + } + newProb[i] = float_sum(numbers_to_sum) / max_Y; + } + + this.prob = newProb; + this.value_start -= rate_decay_max; + this.value_end -= rate_decay_min; + // No need to normalise, as it is guaranteed that the sum of this.prob is 1. + } +} + +class Predictor { + + 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 = 0; + this.prices = prices; + this.first_buy = first_buy; + this.previous_pattern = previous_pattern; + } + + intceil(val) { + return Math.trunc(val + 0.99999); + } + + minimum_rate_from_given_and_base(given_price, buy_price) { + return RATE_MULTIPLIER * (given_price - 0.99999) / buy_price; + } + + maximum_rate_from_given_and_base(given_price, buy_price) { + return RATE_MULTIPLIER * (given_price + 0.00001) / buy_price; + } + + 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) + ]; + } + + 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}; + } + } + + /* + * 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 - 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; + } + // 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]; + } + + predicted_prices.push({ + min: min_pred, + max: max_pred, + }); + } + return prob; + } + + /* + * 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 - 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; + } + // 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; + } + + + /* + * 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 - 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; + } + // 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; + } + + 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; + } + 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 - this.fudge_factor || price > max_pred + this.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 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; + } + } + + // * 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, + }); + + // 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, + }); + + // 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, + }); + + return prob; + } + + * 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); + } + */ + + 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; + } + + // 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; + } + + // 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; + } + + // 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_number: 0, + prices: predicted_prices, + probability, + }; + } + + * 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)); + } + } + } + } + + * 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 *= 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_number: 1, + 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)); + } + } + + * 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; + */ + + 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_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 = 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) + { + rate = randfloat(0.9, 0.4); + for (; work < 14; 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 *= 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; + } + + // The peak + probability *= this.generate_individual_random_price( + given_prices, predicted_prices, peak_start, 2, 0.9, 1.4); + if (probability == 0) { + return; + } + + probability *= this.generate_peak_price( + given_prices, predicted_prices, peak_start + 2, 1.4, 2.0); + if (probability == 0) { + return; + } + + 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; + } + } + + yield { + pattern_number: 3, + prices: predicted_prices, + probability, + }; + } + + * 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)); + } + } + + get_transition_probability(previous_pattern) { + if (typeof previous_pattern === 'undefined' || Number.isNaN(previous_pattern) || previous_pattern === null || previous_pattern < 0 || previous_pattern > 3) { + // 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]; + } + + * 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* this.multiply_generator_probability(generate_pattern_fns[i].bind(this)(sell_prices), transition_probability[i]); + } + } + + * 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++) { + const temp_sell_prices = sell_prices.slice(); + temp_sell_prices[0] = temp_sell_prices[1] = buy_price; + if (first_buy) { + yield* this.generate_pattern_3(temp_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(temp_sell_prices, previous_pattern); + } + } + } else { + yield* this.generate_all_patterns(sell_prices, previous_pattern); + } + } + + analyze_possibilities() { + const sell_prices = this.prices; + const first_buy = this.first_buy; + const previous_pattern = this.previous_pattern; + 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) { + 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 = []; + } + } + 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); + } + + 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]; + } + + generated_possibilities.sort((a, b) => { + return b.category_total_probability - a.category_total_probability || b.probability - a.probability; + }); + + let global_min_max = []; + for (let day = 0; day < 14; day++) { + const prices = { + min: 999, + max: 0, + }; + 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); + } + + generated_possibilities.unshift({ + 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; + } +} diff --git a/fun-shit/turnips/js/scripts.js b/fun-shit/turnips/js/scripts.js new file mode 100644 index 0000000..01f6e19 --- /dev/null +++ b/fun-shit/turnips/js/scripts.js @@ -0,0 +1,418 @@ +//Reusable Fields +const getSellFields = function () { + let fields = []; + var now = new Date(); + for (var i = 2; i < 14; i++) { + fields.push($("#sell_" + i)[0]); + if (i == now.getDay() * 2 + (now.getHours() >= 12 ? 1 : 0)) { + fields[fields.length - 1].classList.add("now"); + } + } + if (now.getDay() == 0) { + buy_input[0].classList.add("now"); + } + return fields; +}; + +const getFirstBuyRadios = function () { + return [ + $("#first-time-radio-no")[0], + $("#first-time-radio-yes")[0] + ]; +}; + +const getPreviousPatternRadios = function () { + return [ + $("#pattern-radio-unknown")[0], + $("#pattern-radio-fluctuating")[0], + $("#pattern-radio-small-spike")[0], + $("#pattern-radio-large-spike")[0], + $("#pattern-radio-decreasing")[0] + ]; +}; + +const getCheckedRadio = function (radio_array) { + return radio_array.find(radio => radio.checked === true).value; +}; + +const checkRadioByValue = function (radio_array, value) { + if (value === null) { + return; + } + value = value.toString(); + radio_array.find(radio => radio.value == value).checked = true; +}; + +const state = { + initialized: false, +}; + +const buy_input = $("#buy"); +const sell_inputs = getSellFields(); +const first_buy_radios = getFirstBuyRadios(); +const previous_pattern_radios = getPreviousPatternRadios(); +const permalink_input = $('#permalink-input'); +const permalink_button = $('#permalink-btn'); +const snackbar = $('#snackbar'); + +//Functions +const fillFields = function (prices, first_buy, previous_pattern) { + checkRadioByValue(first_buy_radios, first_buy); + checkRadioByValue(previous_pattern_radios, previous_pattern); + + buy_input.focus(); + buy_input.val(prices[0] || ''); + buy_input.blur(); + const sell_prices = prices.slice(2); + + sell_prices.forEach((price, index) => { + if (!price) { + return; + } else { + const element = $("#sell_" + (index + 2)); + element.focus(); + element.val(price); + element.blur(); + } + }); +}; + +const initialize = function () { + try { + const previous = getPrevious(); + const first_buy = previous[0]; + const previous_pattern = previous[1]; + const prices = previous[2]; + if (prices === null) { + fillFields([], first_buy, previous_pattern); + } else { + fillFields(prices, first_buy, previous_pattern); + } + } catch (e) { + console.error(e); + } + + $(document).trigger("input"); + + $("#permalink-btn").on("click", copyPermalink); + + $("#reset").on("click", function () { + if (window.confirm(i18next.t("prices.reset-warning"))) { + sell_inputs.forEach(input => input.value = ''); + fillFields([], false, -1); + update(); + } + }); + + console.log('finished initializing'); + state.initialized = true; +}; + +const updateLocalStorage = function (prices, first_buy, previous_pattern) { + try { + if (prices.length !== 14) throw "The data array needs exactly 14 elements to be valid"; + localStorage.setItem("sell_prices", JSON.stringify(prices)); + localStorage.setItem("first_buy", JSON.stringify(first_buy)); + localStorage.setItem("previous_pattern", JSON.stringify(previous_pattern)); + } catch (e) { + console.error(e); + } +}; + +const isEmpty = function (arr) { + const filtered = arr.filter(value => value !== null && value !== '' && !isNaN(value)); + return filtered.length == 0; +}; + +const getFirstBuyStateFromQuery = function (param) { + try { + const params = new URLSearchParams(window.location.search.substr(1)); + const firstbuy_str = params.get(param); + + if (firstbuy_str == null) { + return null; + } + + firstbuy = null; + if (firstbuy_str == "1" || firstbuy_str == "yes" || firstbuy_str == "true") { + firstbuy = true; + } else if (firstbuy_str == "0" || firstbuy_str == "no" || firstbuy_str == "false") { + firstbuy = false; + } + + return firstbuy; + + } catch (e) { + return null; + } +}; + +const getFirstBuyStateFromLocalstorage = function () { + return JSON.parse(localStorage.getItem('first_buy')); +}; + +const getPreviousPatternStateFromLocalstorage = function () { + return JSON.parse(localStorage.getItem('previous_pattern')); +}; + +const getPreviousPatternStateFromQuery = function (param) { + try { + const params = new URLSearchParams(window.location.search.substr(1)); + const pattern_str = params.get(param); + + if (pattern_str == null) { + return null; + } + + if (pattern_str == "0" || pattern_str == "fluctuating") { + pattern = 0; + } else if (pattern_str == "1" || pattern_str == "large-spike") { + pattern = 1; + } else if (pattern_str == "2" || pattern_str == "decreasing") { + pattern = 2; + } else if (pattern_str == "3" || pattern_str == "small-spike") { + pattern = 3; + } else { + pattern = -1; + } + + return pattern; + + } catch (e) { + return null; + } +}; + +const getPricesFromLocalstorage = function () { + try { + const sell_prices = JSON.parse(localStorage.getItem("sell_prices")); + + if (!Array.isArray(sell_prices) || sell_prices.length !== 14) { + return null; + } + + return sell_prices; + } catch (e) { + return null; + } +}; + +const getPricesFromQuery = function (param) { + try { + const params = new URLSearchParams(window.location.search.substr(1)); + const sell_prices = params.get(param).split(".").map((x) => parseInt(x, 10)); + + if (!Array.isArray(sell_prices)) { + return null; + } + + // Parse the array which is formatted like: [price, M-AM, M-PM, T-AM, T-PM, W-AM, W-PM, Th-AM, Th-PM, F-AM, F-PM, S-AM, S-PM, Su-AM, Su-PM] + // due to the format of local storage we need to double up the price at the start of the array. + sell_prices.unshift(sell_prices[0]); + + // This allows us to fill out the missing fields at the end of the array + for (let i = sell_prices.length; i < 14; i++) { + sell_prices.push(0); + } + + return sell_prices; + } catch (e) { + return null; + } +}; + +const getPreviousFromQuery = function () { + /* Check if valid prices are entered. Exit immediately if not. */ + const prices = getPricesFromQuery("prices"); + if (prices == null) { + return null; + } + + console.log("Using data from query."); + window.populated_from_query = true; + return [ + getFirstBuyStateFromQuery("first"), + getPreviousPatternStateFromQuery("pattern"), + prices + ]; +}; + +const getPreviousFromLocalstorage = function () { + return [ + getFirstBuyStateFromLocalstorage(), + getPreviousPatternStateFromLocalstorage(), + getPricesFromLocalstorage() + ]; +}; + + +/** + * Gets previous values. First tries to parse parameters, + * if none of them match then it looks in local storage. + * @return {[first time, previous pattern, prices]} + */ +const getPrevious = function () { + return getPreviousFromQuery() || getPreviousFromLocalstorage(); +}; + +const getSellPrices = function () { + //Checks all sell inputs and returns an array with their values + return res = sell_inputs.map(function (input) { + return parseInt(input.value || ''); + }); +}; + +const getPriceClass = function(buy_price, max) { + const priceBrackets = [200, 30, 0, -30, -99]; + let diff = max - buy_price; + for(var i=0; i= priceBrackets[i]) { + return "range" + i; + } + } + return ""; +}; + +const displayPercentage = function(fraction) { + if (Number.isFinite(fraction)) { + let percent = fraction * 100; + if (percent >= 1) { + return percent.toPrecision(3) + '%'; + } else if (percent >= 0.01) { + return percent.toFixed(2) + '%'; + } else { + return '<0.01%'; + } + } else { + return '—'; + } +}; + +const hideChart = function() { + $("#output").html(""); + $(".chart-wrapper").hide() +} + +const calculateOutput = function (data, first_buy, previous_pattern) { + if (isEmpty(data)) { + hideChart() + return; + } + let pat_desc = {0:"fluctuating", 1:"large-spike", 2:"decreasing", 3:"small-spike", 4:"all"}; + let output_possibilities = ""; + let predictor = new Predictor(data, first_buy, previous_pattern); + let analyzed_possibilities = predictor.analyze_possibilities(); + if (analyzed_possibilities[0].weekGuaranteedMinimum === Number.POSITIVE_INFINITY) { + hideChart() + $(".error:hidden").show() + return; + } + $(".error:visible").hide() + $(".chart-wrapper:hidden").show() + let buy_price = parseInt(buy_input.val()); + previous_pattern_number = ""; + for (let poss of analyzed_possibilities) { + var out_line = "" + i18next.t("patterns." + pat_desc[poss.pattern_number]) + ""; + const style_price = buy_price || poss.prices[0].min; + if (previous_pattern_number != poss.pattern_number) { + previous_pattern_number = poss.pattern_number; + pattern_count = analyzed_possibilities + .filter(val => val.pattern_number == poss.pattern_number) + .length; + out_line += `${displayPercentage(poss.category_total_probability)}`; + } + out_line += `${displayPercentage(poss.probability)}`; + for (let day of poss.prices.slice(2)) { + let price_class = getPriceClass(style_price, day.max); + if (day.min !== day.max) { + out_line += `${day.min} ${i18next.t("output.to")} ${day.max}`; + } else { + out_line += `${day.min}`; + } + } + + var min_class = getPriceClass(style_price, poss.weekGuaranteedMinimum); + var max_class = getPriceClass(style_price, poss.weekMax); + out_line += `${poss.weekGuaranteedMinimum}${poss.weekMax}`; + output_possibilities += out_line; + } + + $("#output").html(output_possibilities); + + update_chart(data, analyzed_possibilities); +}; + +const generatePermalink = function (buy_price, sell_prices, first_buy, previous_pattern) { + let searchParams = new URLSearchParams(); + let pricesParam = buy_price ? buy_price.toString() : ''; + + if (!isEmpty(sell_prices)) { + const filtered = sell_prices.map(price => isNaN(price) ? '' : price).join('.'); + pricesParam = pricesParam.concat('.', filtered); + } + + if (pricesParam) { + searchParams.append('prices', pricesParam); + } + + if (first_buy) { + searchParams.append('first', true); + } + + if (previous_pattern !== -1) { + searchParams.append('pattern', previous_pattern); + } + + return searchParams.toString() && window.location.origin.concat('?', searchParams.toString()); +}; + +const copyPermalink = function () { + let text = permalink_input[0]; + + permalink_input.show(); + text.select(); + text.setSelectionRange(0, 99999); /* for mobile devices */ + + document.execCommand('copy'); + permalink_input.hide(); + + flashMessage(i18next.t("prices.permalink-copied")); +}; + +const flashMessage = function(message) { + snackbar.text(message); + snackbar.addClass('show'); + + setTimeout(function () { + snackbar.removeClass('show'); + snackbar.text(''); + }, 3000); +}; + +const update = function () { + if(!state.initialized){ + console.log('update function called before initial data load'); + // calls to update before the previous data has been initialized / loaded will reset the data. + return; + } + const sell_prices = getSellPrices(); + const buy_price = parseInt(buy_input.val()); + const first_buy = getCheckedRadio(first_buy_radios) == 'true'; + const previous_pattern = parseInt(getCheckedRadio(previous_pattern_radios)); + + const permalink = generatePermalink(buy_price, sell_prices, first_buy, previous_pattern); + if (permalink) { + permalink_button.show(); + } else { + permalink_button.hide(); + } + permalink_input.val(permalink); + + const prices = [buy_price, buy_price, ...sell_prices]; + + if (!window.populated_from_query) { + updateLocalStorage(prices, first_buy, previous_pattern); + } + + calculateOutput(prices, first_buy, previous_pattern); +}; diff --git a/fun-shit/turnips/js/themes.js b/fun-shit/turnips/js/themes.js new file mode 100644 index 0000000..6f1b45a --- /dev/null +++ b/fun-shit/turnips/js/themes.js @@ -0,0 +1,58 @@ +function updateTheme(theme) { + if (theme == "auto") { + theme = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light"; + } + + if (theme != "light") { + document.documentElement.setAttribute("data-theme", theme); + } else { + document.documentElement.removeAttribute("data-theme"); + } + + if (chart_instance && chart_options) { + chart_instance.options = chart_options; + chart_instance.update(); + } +} + +function setupTheming() { + const themeSelector = $("#theme"); + const supportsAutoTheming = (window.matchMedia && window.matchMedia("(prefers-color-scheme)").matches); + let preferredTheme = localStorage.getItem("theme"); + let selectorVal = preferredTheme ? preferredTheme : + supportsAutoTheming ? "auto" : "light"; + + // Build theme option menu. + if (supportsAutoTheming) { + themeSelector.append(``); + } + themeSelector.append(``); + themeSelector.append(``); + + themeSelector.val(selectorVal); + + // Listen to system changes in theme + window.matchMedia("(prefers-color-scheme: dark)").addListener(() => { + if (preferredTheme && preferredTheme != "auto") { return; } + updateTheme("auto"); + }); + + // Preference listener + themeSelector.on('change', function () { + preferredTheme = this.value; + updateTheme(preferredTheme); + + if ((preferredTheme != "light" && !supportsAutoTheming) || + (preferredTheme != "auto" && supportsAutoTheming)) { + localStorage.setItem("theme", preferredTheme); + } else { + localStorage.removeItem("theme"); + } + }); +} + +$(document).ready(function() { + i18next.init((err, t) => { + setupTheming(); + }); +}); \ No newline at end of file diff --git a/fun-shit/turnips/js/translations.js b/fun-shit/turnips/js/translations.js new file mode 100644 index 0000000..2b2c635 --- /dev/null +++ b/fun-shit/turnips/js/translations.js @@ -0,0 +1,75 @@ +function updateContent() { + update(); + $('body').localize(); +} +const defaultLanguage = 'en'; +const LANGUAGES = { + 'ca': 'Català', + 'cs': 'Česky', + 'de': 'Deutsch', + 'en': 'English', + 'es': 'Español', + 'fr': 'Français', + 'gl': 'Galego', + 'hu': 'magyar', + 'id': 'Bahasa Indonesia', + 'it': 'Italiano', + 'ja': '日本語', + 'ko': '한국어', + 'nl': 'Nederlands', + 'ph': 'Filipino', + 'pl': 'Polski', + 'pt-BR': 'Português', + 'ru': 'Русский', + 'ua': 'Українська', + 'th': 'ไทย', + 'zh-CN': '简体中文', + 'zh-TW': '繁體中文' +}; +i18next +.use(i18nextXHRBackend) +.use(i18nextBrowserLanguageDetector) +.init({ + fallbackLng: defaultLanguage, + debug: true, + backend: { + loadPath: 'locales/{{lng}}.json', + }, +}, (err, t) => { + languageSelector = $('#language'); + for (let [code, name] of Object.entries(LANGUAGES)) { + languageSelector.append(``); + } + for (let code of i18next.languages) { + if (code in LANGUAGES) { + languageSelector.val(code); + $('html').attr('lang', code); + break; + } + } + languageSelector.on('change', function () { + if (this.value == i18next.language) + return; + i18next.changeLanguage(this.value); + $('html').attr('lang', this.value); + }); + jqueryI18next.init(i18next, $); + i18next.on('languageChanged', lng => { + updateContent(); + }); + // init set content + $(document).ready(initialize); + + let delayTimer; + $(document).on('input', function(event) { + //prevent radio input from updating content twice per input change + if(event.target.type === 'radio'){ return } + // adding short delay after input to help mitigate potential lag after keystrokes + clearTimeout(delayTimer); + delayTimer = setTimeout(function() { + updateContent(); + }, 500); + }); + + $('input[type = radio]').on('change', updateContent); +}); diff --git a/fun-shit/turnips/locales/ca.json b/fun-shit/turnips/locales/ca.json new file mode 100644 index 0000000..9dc3d41 --- /dev/null +++ b/fun-shit/turnips/locales/ca.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Juliana" + }, + "welcome": { + "salutation": "Hola! Et donem la benvinguda a l'app Turnip Prophet pel teu Nookòfon.", + "description": "Aquesta app et permet predir els preus dels naps a la teva illa, però hauràs d'introduir-hi tu els preus passats!", + "conclusion": "Fet això, l'app Turnip Prophet endevinarà els preus que tindràs durant la resta de la setmana." + }, + "first-time": { + "title": "Primer cop que compres", + "description": "És la primera vegada que li compres naps a la Juliana a la teva illa?(Afecta al patró de preus)", + "yes": "Sí", + "no": "No" + }, + "patterns": { + "title": "Patró anterior", + "description": "Quin patró tenien els teus preus la setmana passada?(Afecta al patró actual)", + "pattern": "Patró", + "all": "Tots els patrons", + "decreasing": "Decreixent", + "fluctuating": "Fluctuant", + "unknown": "No ho sé", + "large-spike": "Pic gran", + "small-spike": "Pic petit" + }, + "prices": { + "description": "Quin ha sigut el preu de compra de naps a la teva illa aquesta setmana?", + "open": { + "am": "AM - De les 8:00 am a les 11:59 am", + "pm": "PM - De les 12:00 pm a les 10:00 pm" + }, + "copy-permalink": "Copiar permalink", + "permalink-copied": "Permalink copiat!", + "reset": "Reiniciar Turnip Prophet", + "reset-warning": "Segur que vols reiniciar tots els camps?\n\nNo pots desfer aquesta acció!" + }, + "weekdays": { + "monday": "Dilluns", + "tuesday": "Dimarts", + "wednesday": "Dimecres", + "thursday": "Dijous", + "friday": "Divendres", + "saturday" : "Dissabte", + "sunday": "Diumenge", + "abr": { + "monday": "Dl", + "tuesday": "Dm", + "wednesday": "Dc", + "thursday": "Dj", + "friday": "Dv", + "saturday" : "Ds" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Resultat", + "chance": "Probabilitat (%)", + "to": "a", + "minimum": "Mínim garantit", + "maximum": "Màxim potencial", + "chart": { + "input": "Preu d'entrada", + "minimum": "Mínim garantit", + "maximum": "Màxim potencial" + } + }, + "textbox": { + "description": "Quan hagis posat els preus dels naps, el Turnip Prophet farà comptes i et mostrarà els possibles patrons que puguin haver a la teva illa.", + "development": "L'app encara està en desenvolupament, però l'anirem millorant!", + "thanks": "Aquest projecte ha estat possible gràcies a en Ninji, que va descobrir com calculen en Tendo i en Nendo els preus dels naps.", + "support": "A GitHub hi trobaràs suport, comentaris, i contribucions.", + "sponsor": "Vols patrocinar als desenvolupadors del projecte? Ves a GitHub i clica damunt de '❤ Sponsor'", + "contributors-text": "Ah! I no ens oblidem dels que ja han ajudat amb les seves contribucions!", + "contributors": "Contribuidors", + "language": "Llenguatge", + "theme": { + "title": "Tema", + "auto": "Automàtic", + "light": "Clar", + "dark": "Fosc" + } + } +} diff --git a/fun-shit/turnips/locales/cs.json b/fun-shit/turnips/locales/cs.json new file mode 100644 index 0000000..61aef80 --- /dev/null +++ b/fun-shit/turnips/locales/cs.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Ahoj, vítej v aplikaci Turnip Prophet tvého Nook Phone.", + "description": "Tahle aplikace by ti měla pomoct sledovat vývoj cen tuřínů na tvém ostrově. Musíš si ale sám zadávat ceny, které už znáš!", + "conclusion": "Po zadání Turnip Prophet kouzelným způsobem předpoví, jak by se ceny měly vyvíjet v průběhu týdne." + }, + "first-time": { + "title": "Poprvé kupující", + "description": "Je to poprvé, co kupuješ tuříny od Daisy na vlastním ostrově?(Ovlivňuje model vývoje cen)", + "yes": "Ano", + "no": "Ne" + }, + "patterns": { + "title": "Předchozí model", + "description": "Jaký byl model vývoje cen tuřínů na tvém ostrově minulý týden?(Ovlivňuje model vývoje cen)", + "pattern": "Model", + "all": "Všechny modely", + "decreasing": "Klesající", + "fluctuating": "Kolísavý", + "unknown": "Nevím", + "large-spike": "S velkou špicí", + "small-spike": "S malou špicí" + }, + "prices": { + "description": "Jaké byly ceny tuřínů v průběhu tohoto týdne?", + "open": { + "am": "dop. - od 8:00 do 11:59", + "pm": "odp. - od 12:00 do 22:00" + }, + "copy-permalink": "Kopírovat permalink", + "permalink-copied": "Permalink zkopírován!", + "reset": "Resetovat Turnip Prophet", + "reset-warning": "Opravdu chceš resetovat všechny pole?\n\nNejde to vrátit zpět!" + }, + "weekdays": { + "monday": "Pondělí", + "tuesday": "Úterý", + "wednesday": "Středa", + "thursday": "Čtvrtek", + "friday": "Pátek", + "saturday" : "Sobota", + "sunday": "Neděle", + "abr": { + "monday": "Po", + "tuesday": "Út", + "wednesday": "St", + "thursday": "Čt", + "friday": "Pá", + "saturday" : "So" + } + }, + "times": { + "morning": "dop.", + "afternoon": "odp." + }, + "output": { + "title": "Výstup", + "chance": "Šance %", + "to": "až", + "minimum": "Jisté Minimum", + "maximum": "Možné Maximum", + "chart": { + "input": "Vstupní cena", + "minimum": "Jisté Minimum", + "maximum": "Možné Maximum" + } + }, + "textbox": { + "description": "Po zadání několika cen tuřínů, Turnip Prophet provede nějaké výpočty a zobrazí všechny možné modely vývoje cen, které mohou na tvém ostrově nastat.", + "development": "Aplikace je pořád ve vývoji, časem se stále zlepšuje!", + "thanks": "Nic z toho by nebylo možné bez Ninjiho výzkumu, jak vlastně Timmy a Tommy hodnotí cenu tuřínů.", + "support": "Podpora, komentáře a příspěvky jsou možné přes GitHub", + "sponsor": "Chceš podpořit vývojáře stojící za tímto projektem? Jdi na GitHub a klikni na tlačítko '❤ Sponsor'", + "contributors-text": "Jo! A nesmíme zapomínat poděkovat těm, kteří se na vývoji tohoto projektu zatím podíleli!", + "contributors": "Na projektu se podílí", + "language": "Jazyk", + "theme": { + "title": "Barevné schéma", + "auto": "Automaticky", + "light": "Světlé", + "dark": "Tmavé" + } + } +} diff --git a/fun-shit/turnips/locales/de.json b/fun-shit/turnips/locales/de.json new file mode 100644 index 0000000..2e9831a --- /dev/null +++ b/fun-shit/turnips/locales/de.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Jorna" + }, + "welcome": { + "salutation": "Hallo, und Willkommen bei der Turnip Prophet App auf deinem Nook Phone.", + "description": "Mit dieser App kannst du die Rübenpreise deiner Insel täglich verfolgen, aber du musst die Preise selbst eingeben!", + "conclusion": "Danach wird die Turnip Prophet App magisch deine Rübenpreise vorhersagen, welche du den Rest der Woche haben wirst." + }, + "first-time": { + "title": "Erstmaliger Einkäufer", + "description": "Kaufst du zum ersten Mal Rüben von Jorna auf deiner Insel? (Dies beeinflusst dein Verkaufsmuster)", + "yes": "Ja", + "no": "Nein" + }, + "patterns": { + "title": "Vorheriges Verkaufsmuster", + "description": "Wie war das Verkaufsmuster der letzten Woche? (Dies beeinflusst dein Verkaufsmuster)", + "pattern": "Verkaufsmuster", + "all": "Alle Verkaufsmuster", + "decreasing": "Absteigend", + "fluctuating": "Schwankend", + "unknown": "Ich weiß nicht", + "large-spike": "Stark Ansteigend", + "small-spike": "Leicht Ansteigend" + }, + "prices": { + "description": "Wie hoch war der Preis für Rüben diese Woche auf deiner Insel?", + "open": { + "am": "Vorm. (Vormittag) - 8:00 Uhr bis 11:59 Uhr", + "pm": "Nachm. (Nachmittag) - 12:00 Uhr bis 22:00 Uhr" + }, + "copy-permalink": "Seite teilen", + "permalink-copied": "Seitenlink kopiert!", + "reset": "Eingegebene Daten zurücksetzen", + "reset-warning": "Bist du sicher, dass du deine eingegebenen Daten zurücksetzen möchtest?\n\nDies kann nicht rückgängig gemacht werden!" + }, + "weekdays": { + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday" : "Samstag", + "sunday": "Sonntag", + "abr": { + "monday": "Mo", + "tuesday": "Di", + "wednesday": "Mi", + "thursday": "Do", + "friday": "Fr", + "saturday" : "Sa" + } + }, + "times": { + "morning": "Vorm.", + "afternoon": "Nachm." + }, + "output": { + "title": "Berechnung", + "chance": "% Chance", + "to": "bis", + "minimum": "Garantiertes Minimum", + "maximum": "Potentielles Maximum", + "chart": { + "input": "Eingegebener Preis", + "minimum": "Garantiertes Minimum", + "maximum": "Potentielles Maximum" + } + }, + "textbox": { + "description": "Nachdem du einige Rübenpreise eingegeben hast, macht der Turnip Prophet etwas Magie und zeigt dir die verschiedenen möglichen Verkaufsmuster an, die auf deiner Insel auftreten können.", + "development": "Diese App befindet sich noch in der Entwicklung, wird sich aber mit der Zeit verbessern!", + "thanks": "Nichts von all dem wäre möglich gewesen, ohne das Ninji herausgefunden hätte, wie Nepp und Schlepp ihre Rübenpreise kalkulieren.", + "support": "Hilfe, Kommentare und Beiträge sind auffindbar über GitHub.com (nur in Englisch).", + "sponsor": "Möchtest du die Entwickler hinter diesem Projekt unterstützen? Gehe zu GitHub.com und klicke auf den '❤ Sponsor' Button für mehr Informationen.", + "contributors-text": "Oh! Und vergessen wir nicht, denen zu danken, die bis jetzt dazu beigetragen haben!", + "contributors": "Mitwirkende", + "language": "Sprache" + } +} diff --git a/fun-shit/turnips/locales/en.json b/fun-shit/turnips/locales/en.json new file mode 100644 index 0000000..a7706fe --- /dev/null +++ b/fun-shit/turnips/locales/en.json @@ -0,0 +1,91 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Hello, and welcome to the Turnip Prophet app on your Nook Phone.", + "description": "This app lets you track your island's turnip prices daily, but you'll have to put the prices in yourself!", + "conclusion": "After that, the Turnip Prophet app will magically predict the turnip prices you'll have for the rest of the week." + }, + "first-time": { + "title": "First-Time Buyer", + "description": "Is this the first time a resident is buying turnips from Daisy Mae on your own island?(This affects your pattern)", + "yes": "Yes", + "no": "No" + }, + "patterns": { + "title": "Previous Pattern", + "description": "What was last week's turnip price pattern?(This affects your pattern)", + "pattern": "Pattern", + "all": "All patterns", + "decreasing": "Decreasing", + "fluctuating": "Fluctuating", + "unknown": "I don't know", + "large-spike": "Large Spike", + "small-spike": "Small Spike" + }, + "prices": { + "description": "What was the price of turnips this week on your island?", + "open": { + "am": "AM - 8:00 am to 11:59 am", + "pm": "PM - 12:00 pm to 10:00 pm" + }, + "copy-permalink": "Copy permalink", + "permalink-copied": "Permalink copied!", + "reset": "Reset Turnip Prophet", + "reset-warning": "Are you sure you want to reset all fields?\n\nThis cannot be undone!" + }, + "weekdays": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday" : "Saturday", + "sunday": "Sunday", + "abr": { + "monday": "Mon", + "tuesday": "Tue", + "wednesday": "Wed", + "thursday": "Thu", + "friday": "Fri", + "saturday" : "Sat" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Output", + "chance": "% Chance", + "to": "to", + "minimum": "Guaranteed Minimum", + "maximum": "Potential Maximum", + "chart": { + "input": "Input Price", + "minimum": "Guaranteed Minimum", + "maximum": "Potential Maximum" + } + }, + "textbox": { + "description": "After you've listed some turnip prices, the Turnip Prophet will run some numbers and display the different possible patterns that your island may experience.", + "development": "This app is still in development, but will improve over time!", + "thanks": "None of this would have been possible without Ninji's work figuring out just how Timmy and Tommy value their turnips.", + "support": "Support, comments and contributions are available through GitHub", + "sponsor": "Want to sponsor the developers behind this project? Go to the GitHub page and click '❤ Sponsor'", + "contributors-text": "Oh! And let's not forget to thank those who have contributed so far!", + "contributors": "Contributors", + "language": "Language", + "theme": { + "title": "Theme", + "auto": "Automatic", + "light": "Light", + "dark": "Dark" + } + }, + "errors": { + "impossible-values": "Oops! There seems to be an error with your turnip predictions. Please check your inputs.", + "github": "If your turnip prices are correct and you have not time traveled, please submit a report." + } +} diff --git a/fun-shit/turnips/locales/es.json b/fun-shit/turnips/locales/es.json new file mode 100644 index 0000000..6b102fd --- /dev/null +++ b/fun-shit/turnips/locales/es.json @@ -0,0 +1,80 @@ +{ + "general": { + "daisy-mae": "Juliana" + }, + "welcome": { + "salutation": "¡Hola! Te damos la bienvenida a la aplicación Turnip Prophet para tu Nookófono.", + "description": "Esta aplicación te permite monitorizar la fluctuación del precio de los nabos en tu isla, ¡pero tendrás que introducir tú manualmente los precios diarios!", + "conclusion": "Una vez hecho, la aplicación Turnip Prophet predecirá mágicamente el precio que tendrán los nabos el resto de la semana." + }, + "first-time": { + "title": "Comprador(a) primerizo(a)", + "description": "¿Ha sido esta la primera vez que compras nabos?(Esta información afectará a tu patrón)", + "yes": "Sí", + "no": "No" + }, + "patterns": { + "title": "Patrón anterior", + "description": "¿Qué patrón describió el precio de los nabos la semana pasada?(Esta información afectará a tu patrón)", + "pattern": "Patrón", + "all": "Todos los patrones", + "decreasing": "Decreciente", + "fluctuating": "Fluctuante", + "unknown": "No lo sé", + "large-spike": "Pico alto", + "small-spike": "Pico moderado" + }, + "prices": { + "description": "¿Cuál fue el precio de los nabos en tu isla esta semana?", + "open": { + "am": "AM - De 8:00 a 11:59", + "pm": "PM - De 12:00 a 22:00" + }, + "copy-permalink": "Copiar permalink", + "permalink-copied": "¡Permalink copiado!", + "reset": "Reiniciar Turnip Prophet", + "reset-warning": "¿Seguro que quieres reiniciar todos los campos?\n\n¡Esto no se puede deshacer!" + }, + "weekdays": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday" : "Sábado", + "sunday": "Domingo", + "abr": { + "monday": "LU", + "tuesday": "MA", + "wednesday": "MI", + "thursday": "JU", + "friday": "VI", + "saturday" : "SA" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Predicción", + "chance": "Probabilidad (%)", + "to": "a", + "minimum": "Mínimo garantizado", + "maximum": "Máximo potencial", + "chart": { + "input": "Precio de entrada", + "minimum": "Mínimo garantizado", + "maximum": "Máximo potencial" + } + }, + "textbox": { + "description": "Cuando introduzcas algunos precios, Turnip Prophet empezará a hacer sus cálculos y te mostrará algunos posibles patrones para el precio de los nabos en tu isla.", + "development": "Esta aplicación está aún en desarrollo, ¡pero la seguiremos mejorando!", + "thanks": "Nada de esto habría sido posible sin el trabajo de Ninji para averiguar cómo Tendo y Nendo valoran los nabos.", + "support": "Para asistencia, comentarios y contribuciones, no dudes en pasarte por GitHub.", + "contributors-text": "¡Ah! ¡Y no nos olvidemos de todos los que han puesto su granito de arena hasta ahora!", + "contributors": "Contribuidores", + "language": "Idioma" + } +} diff --git a/fun-shit/turnips/locales/fr.json b/fun-shit/turnips/locales/fr.json new file mode 100644 index 0000000..f2ac6bf --- /dev/null +++ b/fun-shit/turnips/locales/fr.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Porcelette" + }, + "welcome": { + "salutation": "Bonjour et bienvenue sur l'application du Turnip Prophet de ton Nook Phone.", + "description": "Cette appli te permet de garder un œil quotidien sur le cours du navet de ton île, en le renseignant ici par toi-même !", + "conclusion": "Une fois les prix renseignés, l'appli du Turnip Prophet va magiquement prédire le cours du navet de ton île pour le reste de la semaine." + }, + "first-time": { + "title": "Premier Achat", + "description": "Est-ce la toute première fois que t'achètes des navets à Porcelette sur ton île ?(Cela influencera le type de ta courbe actuelle)", + "yes": "Oui", + "no": "Non" + }, + "patterns": { + "title": "Courbe du cours précédent", + "description": "De quel type était la courbe de ton cours du navet la semaine dernière ?(Cela influencera le type de ta courbe actuelle)", + "pattern": "Type de Courbe", + "all": "Tous les types", + "decreasing": "Décroissante", + "fluctuating": "Variable", + "unknown": "Je ne sais pas", + "large-spike": "Grand Pic", + "small-spike": "Petit Pic" + }, + "prices": { + "description": "À quel prix Porcelette vendait ses navets sur ton île cette semaine ?", + "open": { + "am": "Matin (AM) - de 8:00 à 11:59", + "pm": "Après-midi (PM) - de 12:00 à 22:00" + }, + "copy-permalink": "Copier le permalien", + "permalink-copied": "Permalien copié !", + "reset": "Réinitialiser les données", + "reset-warning": "Es-tu sûr·e de vouloir réinitialiser tous les champs ?\n\nCe choix est définitif !" + }, + "weekdays": { + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday" : "Samedi", + "sunday": "Dimanche", + "abr": { + "monday": "Lun", + "tuesday": "Mar", + "wednesday": "Mer", + "thursday": "Jeu", + "friday": "Ven", + "saturday" : "Sam" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Résultats", + "chance": "% Chance", + "to": "à", + "minimum": "Minimum Garanti", + "maximum": "Maximum Potentiel", + "chart": { + "input": "Prix renseigné", + "minimum": "Minimum Garanti", + "maximum": "Maximum Potentiel" + } + }, + "textbox": { + "description": "Après avoir renseigné quelques prix, le Turnip Prophet fera des calculs et affichera les différents types possibles pour les courbes que ton île pourrait avoir.", + "development": "Cette appli est en développement, mais elle s'améliore jour après jour !", + "thanks": "Rien de tout ça n'aurait pu être possible sans le travail de Ninji et son analyse sur comment Méli et Mélo déterminent la valeur des Navets.", + "support": "Aide, commentaires et contributions sont disponibles via GitHub", + "sponsor": "T'aimerais sponsoriser les développeurs derrière ce projet ? Alors va sur la page GitHub du projet et clique sur '❤ Sponsor'", + "contributors-text": "Oh ! Et n'oublions pas de remercier toutes les personnes ayant contribué jusqu'à maintenant !", + "contributors": "Contributeurs", + "language": "Langue", + "theme": { + "title": "Thème", + "auto": "Automatique", + "light": "Clair", + "dark": "Sombre" + } + } +} diff --git a/fun-shit/turnips/locales/gl.json b/fun-shit/turnips/locales/gl.json new file mode 100644 index 0000000..9d8ea8c --- /dev/null +++ b/fun-shit/turnips/locales/gl.json @@ -0,0 +1,80 @@ +{ + "general": { + "daisy-mae": "Juliana" + }, + "welcome": { + "salutation": "Ola, estás no aplicativo Turnip Prophet no teu Nookófono.", + "description": "Este aplicativo permíteche facer un seguemento do prezo dos nabos na túa illa. Porén, tes que introducir os prezos manualmente.", + "conclusion": "Depois, o aplicativo predirá maxicamente os prezos dos nabos na túa illa para o resto da semana." + }, + "first-time": { + "title": "Primeira compra", + "description": "É a primeira vez que compras nabos a Juliana na túa illa? (Isto afecta aos prezos)", + "yes": "Si", + "no": "Non" + }, + "patterns": { + "title": "Tendencia anterior", + "description": "Cal foi a tendencia de prezos na túa illa a semana pasada? (Isto afecta aos prezos)", + "pattern": "Tendencia", + "all": "Todas as tendencias", + "decreasing": "Decrecente", + "fluctuating": "Fluctuante", + "unknown": "Descoñecido", + "large-spike": "Pico grande", + "small-spike": "Pico pequeno" + }, + "prices": { + "description": "Que prezos tiveron os nabos esta semana na túa illa?", + "open": { + "am": "Mañá - 8:00 a 11:59", + "pm": "Tarde - 12:00 a 22:00" + }, + "copy-permalink": "Copiar ligazón permanente", + "permalink-copied": "Ligazón permanente copiada!", + "reset": "Restablecer Turnip Prophet", + "reset-warning": "Seguro que desexas restablecer todos os campos?\n\nIsto non se pode desfacer!" + }, + "weekdays": { + "monday": "Luns", + "tuesday": "Martes", + "wednesday": "Mércores", + "thursday": "Xoves", + "friday": "Venres", + "saturday" : "Sábado", + "sunday": "Domingo", + "abr": { + "monday": "Lun", + "tuesday": "Mar", + "wednesday": "Mér", + "thursday": "Xov", + "friday": "Venr", + "saturday" : "Sáb" + } + }, + "times": { + "morning": "Mañá", + "afternoon": "Tarde" + }, + "output": { + "title": "Resultado", + "chance": "% Probabilidade", + "to": "a", + "minimum": "Mínimo garantido", + "maximum": "Máximo potencial", + "chart": { + "input": "Prezo introducido", + "minimum": "Mínimo garantido", + "maximum": "Máximo potencial" + } + }, + "textbox": { + "description": "Despois de introducir algúns prezos, o Turnip Prophet calculará que prezos poden ter no futuro.", + "development": "Este aplicativo aínda está en desenvolvemento, mais mellorará co tempo!", + "thanks": "Nada disto sería posible sen o traballo de Ninji achando como se calculan os prezos.", + "support": "Soporte, comentarios e contribucións están dispoñibles en GitHub", + "contributors-text": "Oh! E non esqueceremos dar as grazas aos que xa contribuíron.", + "contributors": "Contribuidores", + "language": "Lingua" + } +} diff --git a/fun-shit/turnips/locales/hu.json b/fun-shit/turnips/locales/hu.json new file mode 100644 index 0000000..99d2296 --- /dev/null +++ b/fun-shit/turnips/locales/hu.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Üdvözöllek a Nookfonod Turnip Prophet alkalmazásában!", + "description": "Ezzel az alkalmazással követheted a szigeted naponta változó retekárait, viszont az árakat Neked kell megadnod!", + "conclusion": "Az árak megadása után a Turnip Prophet varázslatosan megjósolja a hét elkövetkezendő árait." + }, + "first-time": { + "title": "Első vásárlás", + "description": "Most vásárol valaki először retket Daisy Mae-től a Te szigeteden?(Ez hatással van a mintádra)", + "yes": "Igen", + "no": "Nem" + }, + "patterns": { + "title": "Előző minta", + "description": "Milyen volt az előző hét mintája?(Ez hatással van a mintádra)", + "pattern": "Minta", + "all": "Minden minta", + "decreasing": "Csökkenő", + "fluctuating": "Ingadozó", + "unknown": "Nem tudom", + "large-spike": "Nagy kiugrás", + "small-spike": "Kis kiugrás" + }, + "prices": { + "description": "Mennyiért vettél ezen a héten retket?", + "open": { + "am": "Délelőtt - 08:00-tól 11:59-ig", + "pm": "Délután - 12:00-től 22:00-ig" + }, + "copy-permalink": "Árak megosztása", + "permalink-copied": "Hivatkozás kimásolva!", + "reset": "Mezők kiürítése", + "reset-warning": "Biztosan ki akarsz üríteni minden mezőt?\n\nEz a művelet nem vonható vissza!" + }, + "weekdays": { + "monday": "Hétfő", + "tuesday": "Kedd", + "wednesday": "Szerda", + "thursday": "Csütörtök", + "friday": "Péntek", + "saturday" : "Szombat", + "sunday": "Vasárnap", + "abr": { + "monday": "H", + "tuesday": "K", + "wednesday": "Sze", + "thursday": "Cs", + "friday": "P", + "saturday" : "Szo" + } + }, + "times": { + "morning": "de.", + "afternoon": "du." + }, + "output": { + "title": "Eredmény", + "chance": "% Esély", + "to": "-", + "minimum": "Garantált minimum", + "maximum": "Lehetséges maximum", + "chart": { + "input": "Megadott ár", + "minimum": "Garantált minimum", + "maximum": "Lehetséges maximum" + } + }, + "textbox": { + "description": "Miután megadtál néhány árat, a Turnip Prophet kiszámolja és megjeleníti az összes lehetséges mintát ami a héten megjelenhet.", + "development": "Ez az alkalmazás fejlesztés alatt áll, de idővel javulni fog!", + "thanks": "Ez nem lett volna lehetséges Ninji munkája nélkül, aki megfejtette, hogy Timmy és Tommy hogyan árazzák a retket.", + "support": "Minden hibajelentést, megjegyzést és hozzájárulást a GitHub-on várunk.", + "sponsor": "Szeretnéd szponzorálni a projekt fejlesztőit? Menj a GitHub oldalra és nyomd meg a „❤ Sponsor” gombot!", + "contributors-text": "Ó, és ne felejtsünk el köszönetet mondani az eddigi hozzájárulóknak!", + "contributors": "Hozzájárulók", + "language": "Nyelv", + "theme": { + "title": "Téma", + "auto": "Automatikus", + "light": "Világos", + "dark": "Sötét" + } + } +} diff --git a/fun-shit/turnips/locales/id.json b/fun-shit/turnips/locales/id.json new file mode 100644 index 0000000..eb7863b --- /dev/null +++ b/fun-shit/turnips/locales/id.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Halo, dan selamat datang di aplikasi Turnip Prophet pada Nook Phone kalian.", + "description": "Aplikasi ini dapat digunakan untuk mencatat harga turnip harian di pulau kalian, tapi kalian harus memasukkan harganya sendiri!", + "conclusion": "Setelah itu, aplikasi Turnip Prophet akan secara ajaib memprediksi harga turnip kalian selama satu minggu ke depan." + }, + "first-time": { + "title": "Pertama Kali Beli", + "description": "Apakah ini pertama kalinya seorang penduduk membeli turnip dari Daisy Mae di pulau kalian sendiri?(Akan memengaruhi pola harga)", + "yes": "Iya", + "no": "Tidak" + }, + "patterns": { + "title": "Pola Sebelumnya", + "description": "Bagaimana bentuk pola harga turnip pada minggu sebelumnya?(Akan memengaruhi pola harga)", + "pattern": "Pola", + "all": "Semua pola", + "decreasing": "Menurun", + "fluctuating": "Fluktuatif", + "unknown": "Tidak tahu", + "large-spike": "Peningkatan Tajam", + "small-spike": "Peningkatan Kecil" + }, + "prices": { + "description": "Berapa harga turnip di pulau kalian minggu ini?", + "open": { + "am": "AM - 8.00 pagi sampai 11:59 siang", + "pm": "PM - 12.00 siang sampai 22.00 malam" + }, + "copy-permalink": "Salin tautan", + "permalink-copied": "Tautan telah disalin!", + "reset": "Reset Turnip Prophet", + "reset-warning": "Apakah kamu yakin ingin me-reset semua kolom?\n\nSetelah di-reset, tidak dapat dikembalikan lagi!" + }, + "weekdays": { + "monday": "Senin", + "tuesday": "Selasa", + "wednesday": "Rabu", + "thursday": "Kamis", + "friday": "Jumat", + "saturday" : "Sabtu", + "sunday": "Minggu", + "abr": { + "monday": "Sen", + "tuesday": "Sel", + "wednesday": "Rab", + "thursday": "Kam", + "friday": "Jum", + "saturday" : "Sab" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Output", + "chance": "% Peluang", + "to": "-", + "minimum": "Jaminan Minimal", + "maximum": "Potensi Maksimal", + "chart": { + "input": "Input Harga", + "minimum": "Jaminan Minimal", + "maximum": "Potensi Maksimal" + } + }, + "textbox": { + "description": "Setelah kalian mendaftarkan harga turnip-nya, Turnip Prophet akan melakukan perhitungan dan menampilkan beberapa kemungkinan dengan pola yang berbeda yang mungkin terjadi di pulau kalian.", + "development": "Aplikasi ini masih dalam tahap pengembangan, tapi akan selalu bertambah baik seiring dengan berjalannya waktu!", + "thanks": "Aplikasi ini tidak akan terwujud tanpa adanya hasil kerja Ninji yang menemukan cara bagaimana Timmy dan Tommy menentukan harga turnip.", + "support": "Dukungan, komentar, dan kontribusi dapat dilakukan melalui GitHub", + "sponsor": "Ingin mensponsori pengembang di balik proyek ini? Silakan menuju halaman GitHub dan klik '❤ Sponsor'", + "contributors-text": "Oh! Jangan lupa juga untuk berterima kasih pada semuanya yang telah berkontribusi sejauh ini!", + "contributors": "Kontributor", + "language": "Bahasa" + } +} diff --git a/fun-shit/turnips/locales/it.json b/fun-shit/turnips/locales/it.json new file mode 100644 index 0000000..9fd4283 --- /dev/null +++ b/fun-shit/turnips/locales/it.json @@ -0,0 +1,82 @@ +{ + "general": { + "name": "Turnip Prophet", + "daisy-mae": "Brunella" + }, + "welcome": { + "salutation": "Ciao e benvenuto nell'app Turnip Prophet del tuo Nook Phone.", + "description": "Questa applicazione ti permetterà di tenere traccia del prezzo giornaliero delle rape. Ma dovrai inserire i prezzi da te!", + "conclusion": "Se lo fai Turnip Prophet predirrà magicamente i prezzi delle rape che avrai per il resto della settimana." + }, + "first-time": { + "title": "Primo Acquisto", + "description": "È la prima volta che acquisti sulla tua isola le rape da Brunella?(influisce sul comportamento dei prezzi)", + "yes": "Sì", + "no": "No" + }, + "patterns": { + "title": "Comportamento Precedente", + "description": "Qual è stato il comportamento dei prezzi delle rape nella scorsa settimana?(influisce sul comportamento dei prezzi)", + "pattern": "Comportamento", + "all": "Tutti i comportamenti", + "decreasing": "Decrescente", + "fluctuating": "Oscillante", + "unknown": "Non lo so!", + "large-spike": "Grande picco", + "small-spike": "Piccolo picco" + }, + "prices": { + "description": "Qual era il prezzo di acquisto delle rape sulla tua isola questa settimana?", + "open": { + "am": "Mattina - dalle 8:00 alle 11:59", + "pm": "Pomeriggio - dalle 12:00 alle 22:00" + }, + "copy-permalink": "Copia permalink", + "permalink-copied": "Permalink copiato!", + "reset": "Resetta Turnip Prophet", + "reset-warning": "Sei sicuro di voler resettare tutti i campi?\n\nNon può essere annullato!" + }, + "weekdays": { + "monday": "Lunedì", + "tuesday": "Martedì", + "wednesday": "Mercoledì", + "thursday": "Giovedì", + "friday": "Venerdì", + "saturday": "Sabato", + "sunday": "Domenica", + "abr": { + "monday": "Lun", + "tuesday": "Mar", + "wednesday": "Mer", + "thursday": "Gio", + "friday": "Ven", + "saturday": "Sab" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Risultati", + "chance": "Probabilità %", + "to": "a", + "minimum": "Minimo Garantito", + "maximum": "Massimo Potenziale", + "chart": { + "input": "Prezzo Iniziale", + "minimum": "Minimo Garantito", + "maximum": "Massimo Potenziale" + } + }, + "textbox": { + "description": "Dopo aver inserito alcuni prezzi, Turnip Prophet calcolerà e mostrerà i possibili comportamenti del prezzo delle rape nella tua isola.", + "development": "Quest'applicazione è in ancora in sviluppo ma migliorerà col tempo!", + "thanks": "Niente di questo sarebbe possibile senza il lavoro di Ninji nello scoprire come Mirko e Marco valutano le rape.", + "support": "Chiedi supporto o lascia commenti e contributi su GitHub", + "sponsor": "Vuoi sponsorizzare gli sviluppatori di questo progetto? Vai nella pagina GitHub e clicca '❤ Sponsor'", + "contributors-text": "Oh! Non dimentichiamoci di ringraziare chi ha contribuito fin'ora!", + "contributors": "Collaboratori", + "language": "Lingua" + } +} diff --git a/fun-shit/turnips/locales/ja.json b/fun-shit/turnips/locales/ja.json new file mode 100644 index 0000000..e36c891 --- /dev/null +++ b/fun-shit/turnips/locales/ja.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "ウリ" + }, + "welcome": { + "salutation": "こんにちは!タヌキ開発特製スマホの最新アプリ Turnip Prophet へようこそ。", + "description": "このアプリは、無人島のカブ価を毎日予測することができます。そのためには、まず自分でデータを入力することが必要です!", + "conclusion": "そうすれば、Turnip Prophetは魔法のように、あなたのこの一週間のカブ価を予測します。" + }, + "first-time": { + "title": "はじめての購入", + "description": "あなたが自分の島でカブを購入したのは今回が初めてですか?(答えによってパターンが変化します)", + "yes": "はい", + "no": "いいえ" + }, + "patterns": { + "title": "先週のパターン", + "description": "先週のカブ価変化パターンを選んでください。(答えによってパターンが変化します)", + "pattern": "パターン", + "all": "全てのパターン", + "decreasing": "ジリ貧型", + "fluctuating": "波型", + "unknown": "わかりません", + "large-spike": "跳ね大型(3期型)", + "small-spike": "跳ね小型(4期型)" + }, + "prices": { + "description": "今週のカブ価は?", + "open": { + "am": "午前 - AM 8:00 ~ AM 11:59", + "pm": "午後 - PM 12:00 ~ PM 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までお願いします。", + "sponsor": "このプロジェクトに関わったソフトウェア開発者をスポンサーしたい場合、GitHubのページで「❤ Sponsor」をクリックしてください!", + "contributors-text": "そして、このアプリの作成を手伝っていただいた方々に感謝します!", + "contributors": "貢献者", + "language": "言語", + "theme": { + "title": "テーマ", + "auto": "自動", + "light": "ライト", + "dark": "ダーク" + } + } +} diff --git a/fun-shit/turnips/locales/ko.json b/fun-shit/turnips/locales/ko.json new file mode 100644 index 0000000..57bb6b3 --- /dev/null +++ b/fun-shit/turnips/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": "언어" + } +} diff --git a/fun-shit/turnips/locales/nl.json b/fun-shit/turnips/locales/nl.json new file mode 100644 index 0000000..b1f3fb9 --- /dev/null +++ b/fun-shit/turnips/locales/nl.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Hallo, en welkom bij de Turnip Prophet app op je Nook Phone.", + "description": "Deze app laat je toe de knol-prijzen op je eiland dagelijks bij te houden, maar je moet ze zelf ingeven!", + "conclusion": "Daarna zal de Turnip Prophet app op magische wijze de knol-prijzen berekenen die je de rest van de week zal krijgen." + }, + "first-time": { + "title": "Beginnende Koper", + "description": "Is dit de eerste keer dat je knollen koopt van Daisy Mae op je eiland?(Dit beïnvloedt je patroon)", + "yes": "Ja", + "no": "Nee" + }, + "patterns": { + "title": "Vorige Patroon", + "description": "Wat was het patroon van je knol-prijzen vorige week?(Dit beïnvloedt je patroon)", + "pattern": "Patroon", + "all": "Alle patronen", + "decreasing": "Dalend", + "fluctuating": "Schommelend", + "unknown": "Ik weet het niet", + "large-spike": "Grote Piek", + "small-spike": "Kleine Piek" + }, + "prices": { + "description": "Wat was de prijs van knollen op je eiland deze week?", + "open": { + "am": "Voormiddag - 8:00 tot 11:59", + "pm": "Namiddag - 12:00 tot 22:00" + }, + "copy-permalink": "Kopieer deelbare link", + "permalink-copied": "Deelbare link gekopieerd!", + "reset": "Maak Turnip Prophet leeg", + "reset-warning": "Ben je zeker dat je alle velden wil leegmaken?\n\nDit kan niet ongedaan gemaakt worden!" + }, + "weekdays": { + "monday": "Maandag", + "tuesday": "Dinsdag", + "wednesday": "Woensdag", + "thursday": "Donderdag", + "friday": "Vrijdag", + "saturday" : "Zaterdag", + "sunday": "Zondag", + "abr": { + "monday": "Ma", + "tuesday": "Di", + "wednesday": "Woe", + "thursday": "Do", + "friday": "Vr", + "saturday" : "Za" + } + }, + "times": { + "morning": "VM", + "afternoon": "NM" + }, + "output": { + "title": "Resultaat", + "chance": "% Kans", + "to": "tot", + "minimum": "Gegarandeerd Minimum", + "maximum": "Potentieel Maximum", + "chart": { + "input": "Ingevoerde prijs", + "minimum": "Gegarandeerd Minimum", + "maximum": "Potentieel Maximum" + } + }, + "textbox": { + "description": "Nadat je wat knol-prijzen hebt ingevoerd, zal de Turnip Prophet wat berekeningen doen en de verschillende mogelijke patronen voorstellen die je eiland kan vertonen.", + "development": "Deze applicatie is nog in ontwikkeling, en zal met de tijd beter worden!", + "thanks": "Dit alles zou niet mogelijk zijn zonder het werk van Ninji om uit te zoeken hoe Timmy en Tommy de waarde van knollen bepalen.", + "support": "Ondersteuning, feedback en bijdrages zijn mogelijk via GitHub", + "sponsor": "Wil je de ontwikkelaars van dit project sponsoren? Ga naar de GitHub pagina en klik op '❤ Sponsor'", + "contributors-text": "Oh! En laten we degenen die tot nu toe bijgedragen hebben niet vergeten te bedanken!", + "contributors": "Bijdragers", + "language": "Taal" + } +} diff --git a/fun-shit/turnips/locales/ph.json b/fun-shit/turnips/locales/ph.json new file mode 100644 index 0000000..7392ae6 --- /dev/null +++ b/fun-shit/turnips/locales/ph.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Kumusta, at maligayang pagdating sa Turnip Prophet app sa iyong Nook Phone.", + "description": "Sa pamamagitan ng app na ito, puwede mo na subaybayan ang mga presyo ng turnip ng iyong isla araw-araw, ngunit kailangan mong ilagay ang mga presyo sa iyong sarili!", + "conclusion": "Pagkatapos nito, ang Turnip Prophet app ay huhulaan ang magiging presyo ng singil sa iyo para sa natitirang linggo, sa pamamagitan ng mahika!" + }, + "first-time": { + "title": "Unang beses na Mamimili", + "description": "Ito ba ang unang pagkakataon na ang isang residente ay namimili ng mga turnip mula kay Daisy Mae sa iyong sariling isla?(Nakakaapekto ito sa iyong pattern)", + "yes": "Oo", + "no": "Hindi" + }, + "patterns": { + "title": "Nakaraang Pattern", + "description": "Ano ang pattern ng presyo ng turnip noong nakaraang linggo?(Nakakaapekto ito sa iyong pattern)", + "pattern": "Pattern", + "all": "Lahat ng mga pattern", + "decreasing": "Pababa", + "fluctuating": "Taas-Baba", + "unknown": "Hindi ko alam", + "large-spike": "Malaki na Pag-taas", + "small-spike": "Maliit na Pag-taas" + }, + "prices": { + "description": "Ano ang presyo ng mga turnip sa linggong ito sa iyong isla?", + "open": { + "am": "AM - 8:00 am - 11:59 am", + "pm": "PM - 12:00 pm - 10:00 pm" + }, + "copy-permalink": "Kopyahin ang permalink", + "permalink-copied": "Nakopya ang permalink!", + "reset": "I-reset ang Turnip Prophet", + "reset-warning": "Sigurado ka bang nais mong i-reset ang lahat ng mga patlang?\n\nHindi na ito maibabalik sa dati!" + }, + "weekdays": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miyerkules", + "thursday": "Huwebes", + "friday": "Biyernes", + "saturday" : "Sabado", + "sunday": "Linggo", + "abr": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miyerkules", + "thursday": "Huwebes", + "friday": "Biyernes", + "saturday" : "Sabado" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Resulta", + "chance": "% ng Tiyansa", + "to": "-", + "minimum": "Garantisadong Minimum", + "maximum": "Potensyal na Maximum", + "chart": { + "input": "Na-input na Presyo", + "minimum": "Garantisadong Minimum", + "maximum": "Potensyal na Maximum" + } + }, + "textbox": { + "description": "Pagkatapos mong mailista ang ilang mga presyo ng turnip, kakalkulahin ng Turnip Prophet ang mga numero at lilitaw ang iba't ibang mga posibleng pattern na maaaring maranasan ng iyong isla.", + "development": "Ang app na ito ay nasa pag-unlad pa rin, ngunit mapapabuti sa paglipas ng panahon!", + "thanks": "Lahat nang ito ay hindi posible kung wala ang tulong ni Ninji sa pag-alam kung paano pinepresyuhan ni Timmy at Tommy ang kanilang mga turnip.", + "support": "Pumunta lamang sa GitHub para sa suporta, puna, at kontribusyon.", + "sponsor": "Nais mo ba i-sponsor ang mga developer sa likod ng proyektong ito? Pumunta lamang sa GitHub page at i-click ang '❤ Sponsor'", + "contributors-text": "Siya nga pala! Huwag din natin kalimutan na pasalamatan ang mga nag-bigay ng kanilang kontribusyon sa ngayon!", + "contributors": "Contributors", + "language": "Wika", + "theme": { + "title": "Tema", + "auto": "Automatic", + "light": "Light", + "dark": "Dark" + } + } +} diff --git a/fun-shit/turnips/locales/pl.json b/fun-shit/turnips/locales/pl.json new file mode 100644 index 0000000..d79c11b --- /dev/null +++ b/fun-shit/turnips/locales/pl.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Cześć, witaj w aplikacji Turnip Prophet na Twoim Nook Phone.", + "description": "Ta aplikacja pozwoli Ci codziennie śledzić ceny rzep na twojej wyspie, jednak będziesz musiał uzupełniać je samodzielnie.", + "conclusion": "Następnie aplikacja Turnip Prophet w magiczny sposób przewidzi jakie ceny rzep będziesz mieć przez resztę tygodnia" + }, + "first-time": { + "title": "Kupujący po raz pierwszy", + "description": "Czy jest to pierwszy raz gdy kupujesz rzepy od Daisy Mae na własnej wyspie?(Ta opcja wpłynie na Twój schemat)", + "yes": "Tak", + "no": "Nie" + }, + "patterns": { + "title": "Poprzedni schemat", + "description": "Jaka była tendencja cen rzep w poprzednim tygodniu?(Ta opcja wpłynie na Twój wzór)", + "pattern": "Wzór", + "all": "Wszystkie wzory", + "decreasing": "Malejąca", + "fluctuating": "Zmienna", + "unknown": "Nie pamiętam", + "large-spike": "Duży wzrost", + "small-spike": "Mały wzrost" + }, + "prices": { + "description": "Jakie ceny za rzepy były w tym tygodniu na Twojej wyspie?", + "open": { + "am": "Przed południem - 8:00 do 11:59 ", + "pm": "Po południu - 12:00 do 22:00 " + }, + "copy-permalink": "Skopiuj permalink", + "permalink-copied": "Permalink skopiowany!", + "reset": "Wyzeruj Turnip Prophet", + "reset-warning": "Czy jesteś pewien, że chcesz wyzerować wszystkie pola?\n\nTej opcji nie można cofnąć!" + }, + "weekdays": { + "monday": "Poniedziałek", + "tuesday": "Wtorek", + "wednesday": "Środa", + "thursday": "Czwartek", + "friday": "Piątek", + "saturday" : "Sobota", + "sunday": "Niedziela", + "abr": { + "monday": "Pon", + "tuesday": "Wt", + "wednesday": "Śr", + "thursday": "Czw", + "friday": "Pt", + "saturday" : "Sob" + } + }, + "times": { + "morning": "Rano", + "afternoon": "Popołudnie" + }, + "output": { + "title": "Wyniki", + "chance": "Szansa %", + "to": "do", + "minimum": "Gwarantowana cena minimalna", + "maximum": "Potencjalna najwyższa cena", + "chart": { + "input": "Cena wejściowa", + "minimum": "Gwarantowana cena minimalna", + "maximum": "Potencjalna najwyższa cena" + } + }, + "textbox": { + "description": "Po tym jak wprowadzisz kilka cen rzep, Turnip Prophet dokona obliczeń i wyświetli różne prawdopodobne szablony cen, których może doświadczyć Twoja wyspa", + "development": "Ta aplikacja wciąż jest w produkcji, lecz z czasem zostanie poprawiona!", + "thanks": "Nie udałoby nam się to bez pracy Ninji, który dowiedział się jak Timmy and Tommy wyceniają rzepy.", + "support": "Wsparcie, komentarze i wpłaty są możliwe pod adresem GitHub", + "sponsor": "Chcesz wesprzeć twórców tego projektu? Wejdź na stronę GitHub i wciśnij '❤ Sponsor'", + "contributors-text": "Aha! I nie zapominajmy o tych, którzy dotychczas nas wspierali!", + "contributors": "Współautorzy", + "language": "Język" + } +} diff --git a/fun-shit/turnips/locales/pt-BR.json b/fun-shit/turnips/locales/pt-BR.json new file mode 100644 index 0000000..e1187c0 --- /dev/null +++ b/fun-shit/turnips/locales/pt-BR.json @@ -0,0 +1,80 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "Olá, e bem-vindo ao aplicativo Turnip Prophet em seu Nook Phone.", + "description": "Este aplicativo lhe permite acompanhar os preços diários de nabo em sua ilha, porém você terá que colocar os preços você mesmo!", + "conclusion": "Depois disso, o aplicativo Turnip Prophet irá magicamente prever os preços dos nabos que você terá pelo resto da semana." + }, + "first-time": { + "title": "Comprador de primeira viagem", + "description": "Esta é a primeira vez que você compra nabos de Daisy Mae em sua ilha? (Isso afeta seu padrão)", + "yes": "Sim", + "no": "Não" + }, + "patterns": { + "title": "Padrão Anterior", + "description": "Qual foi o padrão de preços de nabo da semana passada? (Isso afeta seu padrão)", + "pattern": "Padrão", + "all": "Todos padrões", + "decreasing": "Diminuindo", + "fluctuating": "Flutuante", + "unknown": "Eu não sei", + "large-spike": "Grande Pico", + "small-spike": "Pequeno Pico" + }, + "prices": { + "description": "Qual foi o preço dos nabos esta semana em sua ilha?", + "open": { + "am": "AM - 8:00 am até 11:59 am", + "pm": "PM - 12:00 pm até 10:00 pm" + }, + "copy-permalink": "Copiar permalink", + "permalink-copied": "Permalink copiado!", + "reset": "Redefinir Turnip Prophet", + "reset-warning": "Tem certeza de que deseja redefinir todos os campos?\n\nIsso não pode ser desfeito!" + }, + "weekdays": { + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday" : "Sábado", + "sunday": "Domingo", + "abr": { + "monday": "Seg", + "tuesday": "Ter", + "wednesday": "Qua", + "thursday": "Qui", + "friday": "Sex", + "saturday" : "Sab" + } + }, + "times": { + "morning": "AM", + "afternoon": "PM" + }, + "output": { + "title": "Resultado", + "chance": "% Chance", + "to": "to", + "minimum": "Mínimo Garantido", + "maximum": "Potencial Máximo", + "chart": { + "input": "Preço de entrada", + "minimum": "Mínimo Garantido", + "maximum": "Potencial Máximo" + } + }, + "textbox": { + "description": "Depois de listar alguns preços de nabo, o Turnip Prophet irá calcular alguns números e exibirá os diferentes padrões possíveis que sua ilha pode experienciar.", + "development": "Este aplicativo ainda está em desenvolvimento, mas melhorará com o tempo!", + "thanks": "Nada disso seria possível sem o trabalho de Ninji's descobrindo como Timmy e Tommy valorizam seus nabos.", + "support": "Suporte, comentários e contribuições estão disponíveis através do GitHub", + "contributors-text": "Oh! E não vamos nos esquecer de agradecer àqueles que contribuíram até agora!", + "contributors": "Contribuidores", + "language": "Linguagem" + } +} diff --git a/fun-shit/turnips/locales/ru.json b/fun-shit/turnips/locales/ru.json new file mode 100644 index 0000000..81799ff --- /dev/null +++ b/fun-shit/turnips/locales/ru.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Дейзи Мэй" + }, + "welcome": { + "salutation": "Добрый день! Добро пожаловать в приложение Препсказатель на Вашем Нукофоне.", + "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 - 22:00" + }, + "copy-permalink": "Скопировать постоянную ссылку", + "permalink-copied": "Постоянная ссылка скопирована!", + "reset": "Перезагрузить Препсказателя", + "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": "После того, как Вы введете уже известную Вам стоимость репы, Препсказатель посчитает и покажет различные вероятные для Вашего острова модели стоимости репы.", + "development": "Это приложение пока еще в разработке, но со временем обязательно станет лучше!", + "thanks": "Если бы Ninji не смог разузнать, как Тимми и Томми определяют стоимость репы, данного приложения могло бы и не быть!", + "support": "Поддержать нас, прокомментировать, а также внести свой вклад Вы можете на GitHub", + "sponsor": "Хотели бы поддержать разработчиков проекта? Для этого Вы можете перейти на GitHub и нажать '❤ Sponsor'", + "contributors-text": "Точно! Нельзя забывать тех, кто уже помог проекту!", + "contributors": "Вклад внесли", + "language": "Язык" + } +} diff --git a/fun-shit/turnips/locales/th.json b/fun-shit/turnips/locales/th.json new file mode 100644 index 0000000..da44cee --- /dev/null +++ b/fun-shit/turnips/locales/th.json @@ -0,0 +1,81 @@ +{ + "general": { + "daisy-mae": "Daisy Mae" + }, + "welcome": { + "salutation": "สวัสดีจ้า ยินดีต้อนรับสู่โปรแกรม Turnip Prophet บน Nook Phone ของคุณ", + "description": "โปรแกรมนี้จะช่วยให้คุณสามารถจดบันทึกราคาหัวผักกาดรายวันบนเกาะของคุณ แต่ต้องจดเองนะ!", + "conclusion": "หลังจากนั้น Turnip Prophet จะทำการพยากรณ์ราคาหัวผักกาดตลอดทั้งสัปดาห์ที่เป็นไปได้ให้คุณ" + }, + "first-time": { + "title": "ซื้อครั้งแรก", + "description": "นี่เป็นครั้งแรกที่คุณซื้อหัวผักกาดจากน้องหมูอู๊ดๆ Daisy Mae บนเกาะของคุณรีเปล่า?(มีผลกับการคำนวณรูปแบบราคา)", + "yes": "ใช่", + "no": "ไม่" + }, + "patterns": { + "title": "รูปแบบก่อนหน้า", + "description": "รูปแบบของราคาสัปดาห์ที่แล้ว?(มีผลกับการคำนวณรูปแบบราคา)", + "pattern": "รูปแบบ", + "all": "รูปแบบทั้งหมด", + "decreasing": "ลดลง", + "fluctuating": "ผันผวน", + "unknown": "ไม่รู้สิ!", + "large-spike": "พุ่งขึ้นสูงมาก", + "small-spike": "พุ่งขึ้นเล็กน้อย" + }, + "prices": { + "description": "ราคาหัวผักกาดบนเกาะของคุณสัปดาห์นี้?", + "open": { + "am": "เช้า - 08.00 นาฬิกา ถึง 11.59 นาฬิกา", + "pm": "บ่าย - 12:00 นาฬิกา to 22:00 นาฬิกา" + }, + "copy-permalink": "คัดลอก Permalink", + "permalink-copied": "Permalink คัดลอกแล้วจ้า!", + "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 ที่ค้นพบวิธีการให้ราคาหัวผักกาดของทานุกิน้อย Timmy และ Tommy", + "support": "สนับสนุน, แสดงความเห็นและช่วยพัฒนาได้ที่ GitHub", + "sponsor": "ต้องการเป็นสปอนเซอร์แก่นักพัฒนา? ไปที่ GitHub และเลือก '❤ Sponsor'", + "contributors-text": "อ้อ! และต้องไม่ลืมที่จะขอบคุณผู้ที่ช่วยร่วมพัฒนามาจนถึงตอนนี้!", + "contributors": "ผู้ร่วมพัฒนา", + "language": "ภาษา" + } + } diff --git a/fun-shit/turnips/locales/ua.json b/fun-shit/turnips/locales/ua.json new file mode 100644 index 0000000..2f5239d --- /dev/null +++ b/fun-shit/turnips/locales/ua.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "Дейзі Мей" + }, + "welcome": { + "salutation": "Добрий день! Вітаємо в додатку Turnip Prophet на Вашому Нукофоні.", + "description": "Цей додаток дозволяє Вам відслідковувати ціни ріпи на Вашому острові щоденно, але Вам доведеться вписати ціни власноруч!", + "conclusion": "Після цього, додаток Turnip Prophet магічним способом передбачить ціни ріпи на Вашому острові на решту тижня." + }, + "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 до 22: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", + "sponsor": "Хочете заспонсорувати розробників проекту? Відвідайте сторінку GitHub і натисніть '❤ Sponsor'", + "contributors-text": "До речі! Не забудьмо подякувати людям, які зробили внесок до проекту!", + "contributors": "Зробили внесок", + "language": "Мова", + "theme": { + "title": "Тема", + "auto": "Автоматична", + "light": "Світла", + "dark": "Темна" + } + } +} diff --git a/fun-shit/turnips/locales/zh-CN.json b/fun-shit/turnips/locales/zh-CN.json new file mode 100644 index 0000000..0e2088c --- /dev/null +++ b/fun-shit/turnips/locales/zh-CN.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "曹卖" + }, + "welcome": { + "salutation": "大家好,欢迎使用Nook手机上的大头菜预测工具。", + "description": "这个APP可以让你每天跟踪自己岛上大头菜的价格,但你得自己把价格填写进去!", + "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 ~ 22:00" + }, + "copy-permalink": "复制价格分享链接", + "permalink-copied": "链接已复制!", + "reset": "重置大头菜预测工具", + "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": "在填写一些大头菜价格后,大头菜预测工具将预测大头菜的价格并显示本周可能的趋势。", + "development": "APP仍在开发中,但会随着时间的推移不断完善!", + "thanks": "如果不是 Ninji 发现豆狸和粒狸如何给大头菜定价的,这一切将不可能实现。", + "support": "可以在 GitHub 获得支持,或讨论和贡献", + "sponsor": "想要赞助这个项目的开发者?进入 GitHub 并点击 ❤ Sponsor", + "contributors-text": "哦!别忘记感谢那些至今为止做出过贡献的人。", + "contributors": "贡献者", + "language": "语言", + "theme": { + "title": "主题", + "auto": "自动", + "light": "亮色", + "dark": "暗色" + } + } +} diff --git a/fun-shit/turnips/locales/zh-TW.json b/fun-shit/turnips/locales/zh-TW.json new file mode 100644 index 0000000..249fa62 --- /dev/null +++ b/fun-shit/turnips/locales/zh-TW.json @@ -0,0 +1,87 @@ +{ + "general": { + "daisy-mae": "曹賣" + }, + "welcome": { + "salutation": "你好,歡迎使用 Nook 手機上的 Turnip Prophet。", + "description": "這個工具可以讓你每天追蹤自己島上的大頭菜價格,但你必須自己輸入價格!", + "conclusion": "接下來,Turnip Prophet 將 神奇地 預測本週剩餘時間的大頭菜價格。" + }, + "first-time": { + "title": "首次購買", + "description": "這是你第一次從自己島上和曹賣購買大頭菜嗎?(將影響這次的模型)", + "yes": "是", + "no": "否" + }, + "patterns": { + "title": "上次的模型", + "description": "上週大頭菜的價格模型是什麼?(將影響這次的模型)", + "pattern": "模型", + "all": "所有模型", + "decreasing": "遞減型", + "fluctuating": "波型", + "unknown": "不知道", + "large-spike": "三期型", + "small-spike": "四期型" + }, + "prices": { + "description": "本週自己島上的大頭菜買價?", + "open": { + "am": "上午 - 08:00 到 11:59", + "pm": "下午 - 12:00 到 22:00" + }, + "copy-permalink": "複製價格分享網址", + "permalink-copied": "網址已複製!", + "reset": "清除資料", + "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 取得支援、討論及貢獻。", + "sponsor": "想要贊助這個專案的開發者?進入 GitHub 並按下 ❤ Sponsor", + "contributors-text": "嘿!別忘了感謝那些迄今為止作出貢獻的人!", + "contributors": "貢獻者", + "language": "語言", + "theme": { + "title": "主題", + "auto": "自動", + "light": "亮色", + "dark": "暗色" + } + } +} diff --git a/fun-shit/turnips/manifest.json b/fun-shit/turnips/manifest.json new file mode 100644 index 0000000..b6763ed --- /dev/null +++ b/fun-shit/turnips/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "Turnip Prophet - ACNH Turnip Tracker", + "short_name": "Turnip Prophet", + "description": "An app to track your Animal Crossing: New Horizons turnip prices daily!", + "start_url": "index.html", + "display": "standalone", + "background_color": "#def2d9", + "theme_color": "#def2d9", + "icons": [ + { + "src": "/img/favicon-192.png", + "sizes": "192x192" + }, + { + "src": "/img/favicon-512.png", + "sizes": "512x512" + } + ] +} diff --git a/fun-shit/turnips/service-worker.js b/fun-shit/turnips/service-worker.js new file mode 100644 index 0000000..499d0ca --- /dev/null +++ b/fun-shit/turnips/service-worker.js @@ -0,0 +1,75 @@ +// PWA Code adapted from https://github.com/pwa-builder/PWABuilder +const CACHE = "pwa-precache-v1"; +const precacheFiles = [ + "/index.html", + "/js/predictions.js", + "/js/scripts.js", + "/css/styles.css", + "https://code.jquery.com/jquery-3.4.1.min.js", +]; + +self.addEventListener("install", function (event) { + console.log("[PWA] Install Event processing"); + + console.log("[PWA] Skip waiting on install"); + self.skipWaiting(); + + event.waitUntil( + caches.open(CACHE).then(function (cache) { + console.log("[PWA] Caching pages during install"); + return cache.addAll(precacheFiles); + }) + ); +}); + +// Allow sw to control of current page +self.addEventListener("activate", function (event) { + console.log("[PWA] Claiming clients for current page"); + event.waitUntil(self.clients.claim()); +}); + +// If any fetch fails, it will look for the request in the cache and serve it from there first +self.addEventListener("fetch", function (event) { + if (event.request.method !== "GET") return; + + event.respondWith( + (async () => { + let response; + try { + // Fetch from network first. + response = await fetch(event.request); + event.waitUntil(updateCache(event.request, response.clone())); + } catch (error) { + try { + // Try if there's locally cached version. + response = await fromCache(event.request); + } catch (error) { + console.log("[PWA] Network request failed and no cache." + error); + throw error; + } + } + return response; + })() + ); +}); + +function fromCache(request) { + // Check to see if you have it in the cache + // Return response + // If not in the cache, then return + return caches.open(CACHE).then(function (cache) { + return cache.match(request).then(function (matching) { + if (!matching || matching.status === 404) { + return Promise.reject("no-match"); + } + + return matching; + }); + }); +} + +function updateCache(request, response) { + return caches.open(CACHE).then(function (cache) { + return cache.put(request, response); + }); +}