Squashed commit of the following:

commit efffda8fc2ae9ebf5df051c33159fc4334075470
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 19:03:49 2020 -0400

    Fixed Reset Button

    Fingers crossed on last commit

commit ff6b739533c20e1eb3f4942b06def9fdfcf58548
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 18:49:54 2020 -0400

    Copy Update + Radio Button Fix

    Changed the copy under "First-Time Buyer" and made the selected radio button a solid fill rather than only coloring the text.

commit 95887a68b671dcf231e4e7aea6710bfe4e2eae29
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 18:31:18 2020 -0400

    Removed a Console Log

commit 84f1ef8b96730818903aaa0e12ab12be642f8bd1
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 18:27:03 2020 -0400

    Added Probabilities

    Chart will now also display % chance of that pattern.

commit d047d7749ca2809165068fe3f787c83ffba8b45e
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 18:08:35 2020 -0400

    Added "First Buy" and "Previous Pattern"

    All CSS needed for these fields have also been included, as well as some Javascript updated to reflect the new radios.

commit 95b60e1458df72c86cf91c2017cb8208e0829d9b
Merge: 1d6046b be09f8e
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 13:51:53 2020 -0400

    Merge branch 'front-end'

commit be09f8e6085dccf90683c819a3deec44852a54b1
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 13:51:43 2020 -0400

    Requested changes

    This commit adds changes as requested in https://github.com/mikebryant/ac-nh-turnip-prices/pull/34

commit 1d6046bfe283e6a779e2e2f49edd12b6fdf128bd
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 02:34:12 2020 -0400

    typo

commit a414b8fdc22aaad82265d502601c0d2a67050f5d
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 02:22:36 2020 -0400

    Update scripts.js

commit fbc3878d831e0f9abeb2de40337dedb8ee20f794
Merge: d521944 3cad0d4
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 02:18:57 2020 -0400

    Merge branch 'master' of https://github.com/Trevor-Welch/ac-nh-turnip-prices

commit d521944eae82eeb0644b13ffa75a23ec3d2f52ef
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Wed Apr 8 02:15:37 2020 -0400

    Massive UI Rework

    This commit completely changes the UI of the app, giving it a more Animal Crossing touch and make it feel like an app on the Nook Phone.

commit 3cad0d4f2a127ba46e946a9b0bebfc1cfb7001f2
Author: Trevor Welch <welch.trevor@yahoo.com>
Date:   Mon Apr 6 20:43:38 2020 -0400

    Refactored scripts.js

    Scripts.JS was very hard to read initially.

    This commity cleans things up, abstracts functions, and makes it a bit more easier to read.
master
Mike Bryant 4 years ago
parent f30e1e751e
commit 54b8458a5f

@ -1,20 +1,420 @@
#turnipTable th, #turnipTable td { @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@800&family=Varela+Round&display=swap');
padding: 5px;
/* - Global Styles - */
html {
font-size: 14px;
background: #DEF2D9;
background-image:
radial-gradient(#fff 20%, transparent 0),
radial-gradient(#fff 20%, transparent 0);
background-size: 30px 30px;
background-position: 0 0, 15px 15px;
/* background: #FFFAE5; */
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Varela Round', sans-serif;
padding-bottom: 20vh;
}
h1 {
text-align: center;
font-size: 1.8rem;
}
h2 {
text-align: center;
font-size: 1.6rem;
}
.nook-phone {
width: 100%;
box-sizing: border-box;
margin: 16px auto;
border-radius: 40px;
padding: 16px 0px;
padding-bottom: 32px;
background: #F5F8FF;
color: #686868;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.08);
}
.nook-phone-center {
background: white;
display: flex;
flex-direction: column;
align-items: center;
}
.dialog-box {
background: #FFFAE5;
box-sizing: border-box;
padding: 16px 24px;
margin: 32px auto;
position: relative;
border-radius: 40px;
max-width: 800px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.08);
}
.dialog-box p {
font-family: 'Raleway', sans-serif;
font-weight: 800;
font-size: 1.2rem;
color: #837865;
letter-spacing: 0.2px;
line-height: 1.8rem;
}
.dialog-box b,
.dialog-box a {
color: #0AB5CD;
transition: 0.2s all;
}
.dialog-box i {
font-style: normal;
color: #aaa;
}
.dialog-box a:hover {
color: #5ECEDB
}
.dialog-box .dialog-box__name {
position: absolute;
left: 16px;
top: -28px;
font-size: 1.1rem;
color: #BA3B1F;
padding: 4px 24px;
background: #FF9A40;
border-radius: 40px;
}
.input__form {
background: white;
display: flex;
flex-direction: column;
padding: 32px;
align-items: center;
}
.form__row {
display: flex;
flex-wrap: wrap;
margin-bottom: 32px;
justify-content: center;
align-items: center;
}
.form__row h6 {
width: 100%;
display: block;
font-weight: 800;
font-size: 1.5rem;
margin: 16px auto;
color: #845E44;
text-align: center;
}
.form__flex-wrap {
margin-top: 16px;
display: flex;
flex-wrap: wrap;
max-width: 100%;
justify-content: center;
}
.input__group {
display: flex;
flex-direction: column;
margin: 8px;
align-items: center;
}
.input__group label {
font-size: 1.2rem;
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: #aaa;
font-size: 0.9rem;
margin: 8px auto;
}
.input__form>.form__row input {
margin: 0px auto;
}
input {
border: 0px solid white;
border-radius: 40px;
padding: 16px 24px;
font-size: 2rem;
font-family: inherit;
color: inherit;
font-weight: bold;
transition: 0.2s all;
margin: 8px 0px;
}
input[type=number]:placeholder-shown {
background: #f3f3f3;
}
input[type=number]:not(:placeholder-shown) {
background: transparent;
color: #0AB5CD;
}
input[type=number]:placeholder-shown:hover {
cursor: pointer;
transform: scale(1.1);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05), 0 3px 6px rgba(0, 0, 0, 0.09);
}
input[type=number]:focus {
outline: none;
transform: scale(1.1);
background: white;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05), 0 3px 6px rgba(0, 0, 0, 0.09);
}
input[type=number]:focus::placeholder {
opacity: 0;
}
input[type=number] {
width: 60px;
text-align: center;
}
input[type=number]:disabled {
background: inherit;
} }
#turnipTable thead tr:first-child { input[type=number]:disabled:hover {
border: none; box-shadow: none;
transform: none;
cursor: default;
} }
#turnipTable th { input::-webkit-outer-spin-button,
text-align: center; input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
} }
#turnipTable td:first-child { input[type=number] {
white-space: nowrap; -moz-appearance: textfield;
text-align: left; }
.input__radio-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}
.input__radio-buttons input[type=radio] {
display: none;
}
.input__radio-buttons input[type="radio"]+label {
opacity: 1;
border: none;
border-radius: 40px;
background: #F3F3F3;
padding: 16px 24px;
font-size: 1.5rem;
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: #F5F8FF;
transform: scale(1.1);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05), 0 3px 6px rgba(0, 0, 0, 0.09);
}
.input__radio-buttons input[type="radio"]:checked+label {
background: #0AB5CD;
color: #FFF;
}
.reset-button {
font-family: inherit;
font-weight: bold;
color: #FFBABA;
padding: 8px 16px;
border-width: 0px;
border-radius: 40px;
background: transparent;
font-size: 1.2rem;
transition: 0.2s all;
position: relative;
margin: 32px auto;
}
.reset-button:hover {
transform: scale(1.1);
cursor: pointer;
opacity: 1;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05), 0 3px 6px rgba(0, 0, 0, 0.09);
}
.table-wrapper {
display: inline-block;
max-width: 98%;
padding: 16px;
margin: 0px auto;
box-sizing: border-box;
overflow-x: auto;
border-radius: 2px;
}
.table-wrapper::-webkit-scrollbar {
height: 8px;
width: 5px;
}
.table-wrapper::-webkit-scrollbar-track {
height: 8px;
width: 5px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
}
.table-wrapper::-webkit-scrollbar-thumb {
height: 8px;
width: 5px;
background: rgba(0, 0, 0, 0.2);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
}
.table-wrapper::-webkit-scrollbar-thumb:window-inactive {
height: 8px;
width: 5px;
background: rgba(0, 0, 0, 0.2);
}
#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 { #turnipTable td {
text-align: center; max-width: 100px;
padding: 6px 4px;
text-align: center;
border-right: 1px solid rgba(0, 0, 0, 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
}
#turnipTable tbody tr {
opacity: 0.8;
}
#turnipTable tbody tr:hover {
cursor: default;
opacity: 1;
}
#turnipTable .table-pattern {
white-space: normal;
}
.waves {
position: relative;
width: 100%;
height: 5vh;
margin-bottom: -7px;
/*Fix for safari gap*/
max-height: 150px;
} }
/* Animation */
.parallax>use {
animation: move-forever 25s cubic-bezier(.55, .5, .45, .5) infinite;
}
.parallax>use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
}
.parallax>use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
}
.parallax>use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
}
.parallax>use:nth-child(4) {
animation-delay: -5s;
animation-duration: 20s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px, 0, 0);
}
100% {
transform: translate3d(85px, 0, 0);
}
}
/*Shrinking for mobile*/
@media (max-width: 768px) {
.waves {
height: 40px;
min-height: 40px;
}
}

@ -1,22 +1,24 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Animal Crossing Turnip Price Forecaster</title> <title>Animal Crossing - Turnip Prophet</title>
<meta name="description" content="Animal Crossing Turnip Price Forecaster"> <meta name="description" content="Animal Crossing - Turnip Prophet">
<meta name="author" content="Mike Bryant"> <meta name="author" content="Mike Bryant">
<link rel="stylesheet" href="css/styles.css?v=1.0"> <link rel="stylesheet" href="css/styles.css">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-162470902-1"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-162470902-1"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'UA-162470902-1'); gtag('config', 'UA-162470902-1');
@ -24,196 +26,268 @@
</head> </head>
<body> <body>
<div class="container">
<div class="col s12 m5"> <div class="dialog-box">
<div class="card-panel z-depth-0"> <h2 class="dialog-box__name">Daisy Mae</h2>
<span> <p>Hello, and welcome to the <b>Turnip Prophet</b> app on your Nook Phone.</p>
To use, enter as much data as you can from <b>your own island</b>. The tool will figure out all possible patterns and predict the lowest and highest prices for each day. <p>This app let's you track your island's turnip prices daily, but you'll have to put the prices in yourself!
<p /> </p>
I couldn't have done this without <a href="https://twitter.com/_Ninji/status/1244818665851289602?s=20">Ninji's work extracting the code</a> <p>After that, the Turnip Prophet app will <b>magically</b> predict the turnip prices you'll have for the rest of
<p /> the week.
Support, comments and contributions are available through <a href="https://github.com/mikebryant/ac-nh-turnip-prices/issues">Github</a> </p>
</span> </div>
</div>
<div class="nook-phone">
<h1>Turnip Prophet</h1>
<div>
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<defs>
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.6" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.4)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.2)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
</g>
</svg>
</div> </div>
<div class="row">
<div class="col s12"> <div class="nook-phone-center">
<h5>Probability</h5>
<div class="row">
<div class="col s4">
<div class="input-field"> <form class="input__form">
<select id="previous_pattern"> <div class="form__row">
<option value="-1" selected>(unknown)</option> <h6>First-Time Buyer</h6>
<!-- Choices are ordered by increasing desirability, not code ID order --> <div class="input__group">
<option value="2">Decreasing</option> <label>Is this your first time buying turnips from Daisy Mae on your island?<i>(This affects your
<option value="0">Fluctuating</option> pattern)</i></label>
<option value="3">Small spike</option> <div class="input__radio-buttons">
<option value="1">Large spike</option> <input type="radio" id="first-time-radio-no" name="first-time" value="no" checked>
</select> <label for="first-time-radio-no">No</label>
<label for="previous_pattern">Last weeks pattern</label> <input type="radio" id="first-time-radio-yes" name="first-time" value="yes">
<label for="first-time-radio-yes">Yes</label>
</div> </div>
</div> </div>
<div class="col s6"> </div>
<div class="input-field">
<span> <div class="form__row">
<b>Last weeks pattern</b> affects the probability of patterns for this week. If you know it, select it to see probabilities for this week. <em>(optional)</em> <h6>Previous Pattern</h6>
</span> <div class="input__group">
<label for="">What was last week's turnip price pattern?<i>(This affects your pattern)</i></label>
<div class="input__radio-buttons">
<input type="radio" id="pattern-radio-unknown" name="pattern" value="unknown" checked>
<label for="pattern-radio-unknown">I don't know</label>
<input type="radio" id="pattern-radio-fluctuating" name="pattern" value="fluctuating">
<label for="pattern-radio-fluctuating">Fluctuating</label>
<input type="radio" id="pattern-radio-small-spike" name="pattern" value="small-spike">
<label for="pattern-radio-small-spike">Small Spike</label>
<input type="radio" id="pattern-radio-large-spike" name="pattern" value="large-spike">
<label for="pattern-radio-large-spike">Large Spike</label>
<input type="radio" id="pattern-radio-decreasing" name="pattern" value="decreasing">
<label for="pattern-radio-decreasing">Decreasing</label>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> <div class="form__row">
<div class="row"> <h6>Sunday</h6>
<div class="col s12"> <div class="input__group">
<h5>Daisy Mae</h5> <label>What was the price of turnips this week? <i>(If this is your first time buying turnips, this field
<div class="row"> will be disabled)</i></label>
<div class="col s2"> <input type="number" id="buy" placeholder="..." />
<div class="input-field">
<label for="buy">Buy price</label> </div>
<input type="number" id="buy"> </div>
<i>AM - 8:00 am to 11:59 am</i>
<i>PM - 12:00 pm to 10:00 pm</i>
<div class="form__flex-wrap">
<div class="form__row">
<h6>Monday</h6>
<div class="input__group">
<label for="sell_2">AM</label>
<input type="number" id="sell_2" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_3">PM</label>
<input type="number" id="sell_3" placeholder="..." />
</div> </div>
</div> </div>
<div class="col s2">
<div class="input-field"> <div class="form__row">
<label> <h6>Tuesday</h6>
<input id="first_buy" type="checkbox" class="filled-in" /> <div class="input__group">
<span>First time buyer</span> <label for="sell_4">AM</label>
</label> <input type="number" id="sell_4" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_5">PM</label>
<input type="number" id="sell_5" placeholder="..." />
</div> </div>
</div> </div>
<div class="col s6">
<div class="input-field"> <div class="form__row">
<span> <h6>Wednesday</h6>
Check this box if you bought from Daisy on your own island <b>for the first time ever</b>. If you bought from her on your own island in a previous week, leave this box unchecked. <div class="input__group">
</span> <label for="sell_6">AM</label>
<input type="number" id="sell_6" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_7">PM</label>
<input type="number" id="sell_7" placeholder="..." />
</div>
</div>
<div class="form__row">
<h6>Thursday</h6>
<div class="input__group">
<label for="sell_8">AM</label>
<input type="number" id="sell_8" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_9">PM</label>
<input type="number" id="sell_9" placeholder="..." />
</div>
</div>
<div class="form__row">
<h6>Friday</h6>
<div class="input__group">
<label for="sell_10">AM</label>
<input type="number" id="sell_10" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_11">PM</label>
<input type="number" id="sell_11" placeholder="..." />
</div>
</div>
<div class="form__row">
<h6>Saturday</h6>
<div class="input__group">
<label for="sell_12">AM</label>
<input type="number" id="sell_12" placeholder="..." />
</div>
<div class="input__group">
<label for="sell_13">PM</label>
<input type="number" id="sell_13" placeholder="..." />
</div> </div>
</div> </div>
</div> </div>
</div> <button id="reset" class="reset-button" name="action">
</div> Reset Turnip Prophet
<div class="row">
<div class="col s2">
<h6>Monday</h6>
<div class="input-field col s6">
<label for="sell_2">AM</label>
<input type="number" id="sell_2">
</div>
<div class="input-field col s6">
<label for="sell_3">PM</label>
<input type="number" id="sell_3">
</div>
</div>
<div class="col s2">
<h6>Tuesday</h6>
<div class="input-field col s6">
<label for="sell_4">AM</label>
<input type="number" id="sell_4">
</div>
<div class="input-field col s6">
<label for="sell_5">PM</label>
<input type="number" id="sell_5">
</div>
</div>
<div class="col s2">
<h6>Wednesday</h6>
<div class="input-field col s6">
<label for="sell_6">AM</label>
<input type="number" id="sell_6">
</div>
<div class="input-field col s6">
<label for="sell_7">PM</label>
<input type="number" id="sell_7">
</div>
</div>
<div class="col s2">
<h6>Thursday</h6>
<div class="input-field col s6">
<label for="sell_8">AM</label>
<input type="number" id="sell_8">
</div>
<div class="input-field col s6">
<label for="sell_9">PM</label>
<input type="number" id="sell_9">
</div>
</div>
<div class="col s2">
<h6>Friday</h6>
<div class="input-field col s6">
<label for="sell_10">AM</label>
<input type="number" id="sell_10">
</div>
<div class="input-field col s6">
<label for="sell_11">PM</label>
<input type="number" id="sell_11">
</div>
</div>
<div class="col s2">
<h6>Saturday</h6>
<div class="input-field col s6">
<label for="sell_12">AM</label>
<input type="number" id="sell_12">
</div>
<div class="input-field col s6">
<label for="sell_13">PM</label>
<input type="number" id="sell_13">
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<button id="reset" class="btn waves-effect waves-light" name="action">
Reset
</button> </button>
</div> </form>
</div>
<div class="divider"></div>
<div class="card-panel z-depth-0"> <h2>Output</h2>
<span>Your turnip prices will be one of the following patterns. Each cell contains the minimum..maximum price for that half-day. If you don't have a pattern, something's gone wrong, sorry &#128531;. Check back next week!</span>
<div class="table-wrapper">
<table id="turnipTable">
<thead>
<tr>
<th valign="bottom">Pattern</th>
<th valign="bottom">% Chance</th>
<th valign="bottom">Sunday</th>
<th colspan="2">
<div>Monday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</th>
<th colspan="2">
<div>Tuesday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</th>
<th colspan="2">
<div>
<div>Wednesday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</div>
</th>
<th colspan="2">
<div>Thursday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</th>
<th colspan="2">
<div>Friday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</th>
<th colspan="2">
<div>Saturday</div>
<div>
<span>AM</span>
<span>PM</span>
</div>
</th>
<th valign="bottom">Guaranteed Minimum</th>
<th valign="bottom">Potential Maximum</th>
</tr>
</thead>
<tbody id="output"></tbody>
</table>
</div>
</div> </div>
<div class="section"> <div style="transform:rotate(180deg)">
<table id="turnipTable" class="highlight"> <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
<thead> viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<tr> <defs>
<th rowspan="2">Pattern</th> <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
<th rowspan="2">% Chance</th> </defs>
<th rowspan="2">Sunday</th> <g class="parallax">
<th colspan="2">Monday</th> <use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.6" />
<th colspan="2">Tuesday</th> <use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.4)" />
<th colspan="2">Wednesday</th> <use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.2)" />
<th colspan="2">Thursday</th> <use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
<th colspan="2">Friday</th> </g>
<th colspan="2">Saturday</th> </svg>
<th rowspan="2">Guaranteed Minimum</th>
<th rowspan="2">Potential Maximum</th>
</tr>
<tr>
<th>AM</th>
<th>PM</th>
<th>AM</th>
<th>PM</th>
<th>AM</th>
<th>PM</th>
<th>AM</th>
<th>PM</th>
<th>AM</th>
<th>PM</th>
<th>AM</th>
<th>PM</th>
</tr>
</thead>
<tbody id="output"></tbody>
</table>
</div> </div>
</div>
<div class="dialog-box">
<h2 class="dialog-box__name">Daisy Mae</h2>
<p>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.</p>
<p>This app is still in development, but will improve over time!</p>
</div>
<div class="dialog-box">
<h2 class="dialog-box__name">Daisy Mae</h2>
<p>
None of this would have been possible without <a
href="https://twitter.com/_Ninji/status/1244818665851289602?s=20">Ninji's work</a> figuring out just how Timmy
and Tommy value their turnips.
</p>
<p>
Support, comments and contributions are available through <a
href="https://github.com/mikebryant/ac-nh-turnip-prices/issues">Github</a>
</p>
</div> </div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="js/predictions.js"></script> <script src="js/predictions.js"></script>
<script src="js/scripts.js"></script> <script src="js/scripts.js"></script>
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</body> </body>
</html> </html>

@ -48,17 +48,14 @@ function maximum_rate_from_given_and_base(given_price, buy_price) {
} }
function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_phase_1_len, high_phase_2_len, dec_phase_2_len, high_phase_3_len) { function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_phase_1_len, high_phase_2_len, dec_phase_2_len, high_phase_3_len) {
/* /*
// PATTERN 0: high, decreasing, high, decreasing, high // PATTERN 0: high, decreasing, high, decreasing, high
work = 2; work = 2;
// high phase 1 // high phase 1
for (int i = 0; i < hiPhaseLen1; i++) for (int i = 0; i < hiPhaseLen1; i++)
{ {
sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice);
} }
// decreasing phase 1 // decreasing phase 1
rate = randfloat(0.8, 0.6); rate = randfloat(0.8, 0.6);
for (int i = 0; i < decPhaseLen1; i++) for (int i = 0; i < decPhaseLen1; i++)
@ -67,13 +64,11 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
rate -= 0.04; rate -= 0.04;
rate -= randfloat(0, 0.06); rate -= randfloat(0, 0.06);
} }
// high phase 2 // high phase 2
for (int i = 0; i < (hiPhaseLen2and3 - hiPhaseLen3); i++) for (int i = 0; i < (hiPhaseLen2and3 - hiPhaseLen3); i++)
{ {
sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice);
} }
// decreasing phase 2 // decreasing phase 2
rate = randfloat(0.8, 0.6); rate = randfloat(0.8, 0.6);
for (int i = 0; i < decPhaseLen2; i++) for (int i = 0; i < decPhaseLen2; i++)
@ -82,7 +77,6 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
rate -= 0.04; rate -= 0.04;
rate -= randfloat(0, 0.06); rate -= randfloat(0, 0.06);
} }
// high phase 3 // high phase 3
for (int i = 0; i < hiPhaseLen3; i++) for (int i = 0; i < hiPhaseLen3; i++)
{ {
@ -107,7 +101,7 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
min_pred = Math.floor(0.9 * buy_price); min_pred = Math.floor(0.9 * buy_price);
max_pred = Math.ceil(1.4 * buy_price); max_pred = Math.ceil(1.4 * buy_price);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -130,7 +124,7 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -154,7 +148,7 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
min_pred = Math.floor(0.9 * buy_price); min_pred = Math.floor(0.9 * buy_price);
max_pred = Math.ceil(1.4 * buy_price); max_pred = Math.ceil(1.4 * buy_price);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -177,7 +171,7 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -204,7 +198,7 @@ function* generate_pattern_0_with_lengths(given_prices, high_phase_1_len, dec_ph
min_pred = Math.floor(0.9 * buy_price); min_pred = Math.floor(0.9 * buy_price);
max_pred = Math.ceil(1.4 * buy_price); max_pred = Math.ceil(1.4 * buy_price);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -228,7 +222,6 @@ function* generate_pattern_0(given_prices) {
/* /*
decPhaseLen1 = randbool() ? 3 : 2; decPhaseLen1 = randbool() ? 3 : 2;
decPhaseLen2 = 5 - decPhaseLen1; decPhaseLen2 = 5 - decPhaseLen1;
hiPhaseLen1 = randint(0, 6); hiPhaseLen1 = randint(0, 6);
hiPhaseLen2and3 = 7 - hiPhaseLen1; hiPhaseLen2and3 = 7 - hiPhaseLen1;
hiPhaseLen3 = randint(0, hiPhaseLen2and3 - 1); hiPhaseLen3 = randint(0, hiPhaseLen2and3 - 1);
@ -285,7 +278,7 @@ function* generate_pattern_1_with_peak(given_prices, peak_start) {
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -312,7 +305,7 @@ function* generate_pattern_1_with_peak(given_prices, peak_start) {
max_pred = Math.ceil(max_randoms[i - peak_start] * buy_price); max_pred = Math.ceil(max_randoms[i - peak_start] * buy_price);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -373,7 +366,7 @@ function* generate_pattern_2(given_prices) {
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -403,7 +396,6 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
/* /*
// PATTERN 3: decreasing, spike, decreasing // PATTERN 3: decreasing, spike, decreasing
peakStart = randint(2, 9); peakStart = randint(2, 9);
// decreasing phase before the peak // decreasing phase before the peak
rate = randfloat(0.9, 0.4); rate = randfloat(0.9, 0.4);
for (work = 2; work < peakStart; work++) for (work = 2; work < peakStart; work++)
@ -412,14 +404,12 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
rate -= 0.03; rate -= 0.03;
rate -= randfloat(0, 0.02); rate -= randfloat(0, 0.02);
} }
sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice); sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice);
sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice); sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice);
rate = randfloat(1.4, 2.0); rate = randfloat(1.4, 2.0);
sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;
sellPrices[work++] = intceil(rate * basePrice); sellPrices[work++] = intceil(rate * basePrice);
sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1; sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;
// decreasing phase after the peak // decreasing phase after the peak
if (work < 14) if (work < 14)
{ {
@ -454,7 +444,7 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -475,11 +465,11 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
// The peak // The peak
for (var i = peak_start; i < peak_start+2; i++) { for (var i = peak_start; i < peak_start + 2; i++) {
min_pred = Math.floor(0.9 * buy_price); min_pred = Math.floor(0.9 * buy_price);
max_pred = Math.ceil(1.4 * buy_price); max_pred = Math.ceil(1.4 * buy_price);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -496,13 +486,13 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
// Main spike 1 // Main spike 1
min_pred = Math.floor(1.4 * buy_price) - 1; min_pred = Math.floor(1.4 * buy_price) - 1;
max_pred = Math.ceil(2.0 * buy_price) - 1; max_pred = Math.ceil(2.0 * buy_price) - 1;
if (!isNaN(given_prices[peak_start+2])) { if (!isNaN(given_prices[peak_start + 2])) {
if (given_prices[peak_start+2] < min_pred || given_prices[peak_start+2] > max_pred ) { if (given_prices[peak_start + 2] < min_pred || given_prices[peak_start + 2] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
min_pred = given_prices[peak_start+2]; min_pred = given_prices[peak_start + 2];
max_pred = given_prices[peak_start+2]; max_pred = given_prices[peak_start + 2];
} }
predicted_prices.push({ predicted_prices.push({
min: min_pred, min: min_pred,
@ -510,15 +500,15 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
}); });
// Main spike 2 // Main spike 2
min_pred = predicted_prices[peak_start+2].min; min_pred = predicted_prices[peak_start + 2].min;
max_pred = Math.ceil(2.0 * buy_price); max_pred = Math.ceil(2.0 * buy_price);
if (!isNaN(given_prices[peak_start+3])) { if (!isNaN(given_prices[peak_start + 3])) {
if (given_prices[peak_start+3] < min_pred || given_prices[peak_start+3] > max_pred ) { if (given_prices[peak_start + 3] < min_pred || given_prices[peak_start + 3] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
min_pred = given_prices[peak_start+3]; min_pred = given_prices[peak_start + 3];
max_pred = given_prices[peak_start+3]; max_pred = given_prices[peak_start + 3];
} }
predicted_prices.push({ predicted_prices.push({
min: min_pred, min: min_pred,
@ -527,31 +517,31 @@ function* generate_pattern_3_with_peak(given_prices, peak_start) {
// Main spike 3 // Main spike 3
min_pred = Math.floor(1.4 * buy_price) - 1; min_pred = Math.floor(1.4 * buy_price) - 1;
max_pred = predicted_prices[peak_start+3].max - 1; max_pred = predicted_prices[peak_start + 3].max - 1;
if (!isNaN(given_prices[peak_start+4])) { if (!isNaN(given_prices[peak_start + 4])) {
if (given_prices[peak_start+4] < min_pred || given_prices[peak_start+4] > max_pred ) { if (given_prices[peak_start + 4] < min_pred || given_prices[peak_start + 4] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
min_pred = given_prices[peak_start+4]; min_pred = given_prices[peak_start + 4];
max_pred = given_prices[peak_start+4]; max_pred = given_prices[peak_start + 4];
} }
predicted_prices.push({ predicted_prices.push({
min: min_pred, min: min_pred,
max: max_pred, max: max_pred,
}); });
if (peak_start+5 < 14) { if (peak_start + 5 < 14) {
var min_rate = 4000; var min_rate = 4000;
var max_rate = 9000; var max_rate = 9000;
for (var i = peak_start+5; i < 14; i++) { for (var i = peak_start + 5; i < 14; i++) {
min_pred = Math.floor(min_rate * buy_price / 10000); min_pred = Math.floor(min_rate * buy_price / 10000);
max_pred = Math.ceil(max_rate * buy_price / 10000); max_pred = Math.ceil(max_rate * buy_price / 10000);
if (!isNaN(given_prices[i])) { if (!isNaN(given_prices[i])) {
if (given_prices[i] < min_pred || given_prices[i] > max_pred ) { if (given_prices[i] < min_pred || given_prices[i] > max_pred) {
// Given price is out of predicted range, so this is the wrong pattern // Given price is out of predicted range, so this is the wrong pattern
return; return;
} }
@ -660,7 +650,7 @@ function analyze_possibilities(sell_prices, first_buy, previous_pattern) {
weekMins.push(day.min); weekMins.push(day.min);
weekMaxes.push(day.max); weekMaxes.push(day.max);
} }
poss.weekGuaranteedMinimum = Math.max(...weekMins); poss.weekGuaranteedMinimum = Math.max(...weekMins);
poss.weekMax = Math.max(...weekMaxes); poss.weekMax = Math.max(...weekMaxes);
} }

@ -7,15 +7,41 @@ const getSellFields = function () {
return fields 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) {
radio_array.forEach(radio => radio.checked = false)
radio_array.find(radio => radio.value == value).checked = true
}
const sell_inputs = getSellFields() const sell_inputs = getSellFields()
const buy_input = $("#buy") const buy_input = $("#buy")
const first_buy_field = $("#first_buy"); const first_buy_radios = getFirstBuyRadios()
const previous_pattern_input = $("#previous_pattern"); const previous_pattern_radios = getPreviousPatternRadios()
//Functions //Functions
const fillFields = function (prices, first_buy, previous_pattern) { const fillFields = function (prices, first_buy, previous_pattern) {
first_buy_field.prop("checked", first_buy); first_buy == 'yes' ? checkRadioByValue(first_buy_radios, 'yes') : checkRadioByValue(first_buy_radios, 'no')
previous_pattern_input.val(previous_pattern); checkRadioByValue(previous_pattern_radios, previous_pattern);
buy_input.focus(); buy_input.focus();
buy_input.val(prices[0] || '') buy_input.val(prices[0] || '')
@ -50,9 +76,9 @@ const initialize = function () {
} }
$("#reset").on("click", function () { $("#reset").on("click", function () {
first_buy_field.prop('checked', false); sell_inputs.forEach(input => input.value = '')
$("select").val(null); fillFields([], false, 'unknown')
$("input").val(null).trigger("input"); update()
}) })
$('select').formSelect(); $('select').formSelect();
@ -75,11 +101,11 @@ const isEmpty = function (arr) {
} }
const getFirstBuyState = function () { const getFirstBuyState = function () {
return JSON.parse(localStorage.getItem('first_buy')) return JSON.parse(localStorage.getItem('first_buy')) || 'no'
} }
const getPreviousPatternState = function () { const getPreviousPatternState = function () {
return JSON.parse(localStorage.getItem('previous_pattern')) return JSON.parse(localStorage.getItem('previous_pattern')) || 'unknown'
} }
const getPricesFromLocalstorage = function () { const getPricesFromLocalstorage = function () {
@ -130,37 +156,61 @@ const calculateOutput = function (data, first_buy, previous_pattern) {
} }
let output_possibilities = ""; let output_possibilities = "";
for (let poss of analyze_possibilities(data, first_buy, previous_pattern)) { for (let poss of analyze_possibilities(data, first_buy, previous_pattern)) {
var out_line = "<tr><td>" + poss.pattern_description + "</td>" var out_line = "<tr><td class='table-pattern'>" + poss.pattern_description + "</td>"
console.log(poss.probability)
out_line += `<td>${Number.isFinite(poss.probability) ? ((poss.probability * 100).toPrecision(3) + '%') : '—'}</td>`; out_line += `<td>${Number.isFinite(poss.probability) ? ((poss.probability * 100).toPrecision(3) + '%') : '—'}</td>`;
for (let day of poss.prices.slice(1)) { for (let day of poss.prices.slice(1)) {
if (day.min !== day.max) { if (day.min !== day.max) {
out_line += `<td>${day.min}..${day.max}</td>`; out_line += `<td>${day.min} to ${day.max}</td>`;
} else { } else {
out_line += `<td class="one">${day.min}</td>`; out_line += `<td>${day.min}</td>`;
} }
} }
out_line += `<td class="one">${poss.weekGuaranteedMinimum}</td><td class="one">${poss.weekMax}</td></tr>`; out_line += `<td>${poss.weekGuaranteedMinimum}</td><td>${poss.weekMax}</td></tr>`;
output_possibilities += out_line output_possibilities += out_line
} }
$("#output").html(output_possibilities) $("#output").html(output_possibilities)
} }
const convertPatternToInt = function (pattern) {
switch (pattern) {
case 'unknown':
return -1;
case 'fluctuating':
return 0;
case 'large-spike':
return 1;
case 'decreasing':
return 2;
case 'small-spike':
return 3;
default:
return -1;
}
}
const update = function () { const update = function () {
const sell_prices = getSellPrices(); const sell_prices = getSellPrices();
const buy_price = parseInt(buy_input.val()); const buy_price = parseInt(buy_input.val());
const first_buy = first_buy_field.is(":checked"); const first_buy = getCheckedRadio(first_buy_radios);
const previous_pattern = parseInt(previous_pattern_input.val()); const first_buy_boolean = first_buy == 'yes'
const previous_pattern = getCheckedRadio(previous_pattern_radios);
buy_input.prop('disabled', first_buy);
buy_input[0].disabled = first_buy_boolean;
buy_input[0].placeholder = first_buy_boolean ? '—' : '...'
const prices = [buy_price, buy_price, ...sell_prices]; const prices = [buy_price, buy_price, ...sell_prices];
if (!window.price_from_query) { if (!window.price_from_query) {
updateLocalStorage(prices, first_buy, previous_pattern); updateLocalStorage(prices, first_buy, previous_pattern);
} }
calculateOutput(prices, first_buy, previous_pattern);
calculateOutput(prices, first_buy_boolean, parseInt(convertPatternToInt(previous_pattern)));
} }
$(document).ready(initialize); $(document).ready(initialize);
$(document).on("input", update); $(document).on("input", update);
$(previous_pattern_input).on("change", update); $('input[type = radio]').on("change", update);

Loading…
Cancel
Save