MediaWiki:Gadget-fa-bid-calculator.js: Difference between revisions
Stack calculator rows on narrow screens |
The league is NBA RealGM |
||
| (3 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/** | /** | ||
* | * NBA RealGM — free agency bid calculator. | ||
* Implements the points system on [[NBA:Free Agency]]. If the two ever disagree, | * Implements the points system on [[NBA:Free Agency]]. If the two ever disagree, | ||
* the wiki page is the rule and this script is the bug. | * the wiki page is the rule and this script is the bug. | ||
| Line 11: | Line 11: | ||
var MIN_OFFER = 500000, | var MIN_OFFER = 500000, | ||
MIN_RAISE = 200000, | MIN_RAISE = 200000, | ||
MIN_RAISE_POINTS = 0.2, | |||
MAX_YEARS = 4, | MAX_YEARS = 4, | ||
MAX_YEARS_35 = 2, | MAX_YEARS_35 = 2, | ||
YEAR_POINTS = 1.5; | YEAR_POINTS = 1.5; | ||
function money( n ) { | function money( n ) { | ||
return '$' + n.toLocaleString( 'en-US' ); | return '$' + n.toLocaleString( 'en-US' ); | ||
} | |||
/** Digits only, so "4,000,000" and "4000000" both read as 4000000. */ | |||
function parseMoney( v ) { | |||
var digits = String( v ).replace( /[^0-9]/g, '' ); | |||
return digits === '' ? NaN : Number( digits ); | |||
} | |||
/** | |||
* Reformat a text input with thousands separators, keeping the caret where | |||
* the typist left it. A plain reformat would send it to the end, which makes | |||
* editing the middle of a figure unusable. | |||
*/ | |||
function formatWithCaret( el ) { | |||
var before = el.value.slice( 0, el.selectionStart ), | |||
digitsBefore = before.replace( /[^0-9]/g, '' ).length, | |||
n = parseMoney( el.value ); | |||
el.value = isNaN( n ) ? '' : n.toLocaleString( 'en-US' ); | |||
// Walk forward past the same number of digits to place the caret. | |||
var pos = 0, seen = 0; | |||
while ( pos < el.value.length && seen < digitsBefore ) { | |||
if ( /[0-9]/.test( el.value[ pos ] ) ) { | |||
seen++; | |||
} | |||
pos++; | |||
} | |||
el.setSelectionRange( pos, pos ); | |||
} | } | ||
| Line 117: | Line 148: | ||
var years = document.createElement( 'select' ); | var years = document.createElement( 'select' ); | ||
// A number input cannot display separators — browsers reject a comma as | |||
// invalid number syntax — so this is a text input formatted by hand. | |||
var salary = document.createElement( 'input' ); | var salary = document.createElement( 'input' ); | ||
salary.type = ' | salary.type = 'text'; | ||
salary. | salary.inputMode = 'numeric'; | ||
salary. | salary.autocomplete = 'off'; | ||
salary. | salary.className = 'tc-calc-money'; | ||
salary.value = MIN_OFFER.toLocaleString( 'en-US' ); | |||
salary.addEventListener( 'input', function () { | |||
formatWithCaret( salary ); | |||
} ); | |||
var age35 = document.createElement( 'input' ); | var age35 = document.createElement( 'input' ); | ||
| Line 138: | Line 175: | ||
var team = document.createElement( 'input' ); | var team = document.createElement( 'input' ); | ||
team.type = 'text'; | team.type = 'text'; | ||
team.placeholder = ' | team.placeholder = 'Sonics'; | ||
team.className = 'tc-calc-wide'; | team.className = 'tc-calc-wide'; | ||
| Line 174: | Line 211: | ||
function render() { | function render() { | ||
var y = Number( years.value ), | var y = Number( years.value ), | ||
s = | s = parseMoney( salary.value ), | ||
max = age35.checked ? MAX_YEARS_35 : MAX_YEARS; | max = age35.checked ? MAX_YEARS_35 : MAX_YEARS; | ||
| Line 221: | Line 258: | ||
box.className = 'tc-calc-note'; | box.className = 'tc-calc-note'; | ||
if ( total > | // A bid must clear the standing one by at least MIN_RAISE_POINTS, so | ||
// the bar is target + 0.2, not merely more than target. Everything | |||
// here is counted in tenths of a point: 0.1 points is exactly | |||
// $100,000 and a year is exactly 15 tenths, so integer arithmetic | |||
// gets the answer that 0.2 + 2.0 in binary floating point does not. | |||
var requiredTenths = Math.round( target * 10 ) + ( MIN_RAISE_POINTS * 10 ); | |||
if ( Math.round( total * 10 ) >= requiredTenths ) { | |||
box.textContent = 'This already beats ' + pts( target ) + ' points.'; | box.textContent = 'This already beats ' + pts( target ) + ' points.'; | ||
} else { | } else { | ||
var salaryTenths = requiredTenths - y * ( YEAR_POINTS * 10 ), | |||
var | newSalary = Math.max( salaryTenths * 100000, MIN_OFFER ); | ||
box.textContent = 'To beat ' + pts( target ) + ' points at ' + y + | box.textContent = 'To beat ' + pts( target ) + ' points at ' + y + | ||
( y === 1 ? ' year' : ' years' ) + ': ' + money( newSalary ) + | ( y === 1 ? ' year' : ' years' ) + ': ' + money( newSalary ) + | ||
| Line 240: | Line 279: | ||
use.textContent = 'Use ' + money( newSalary ); | use.textContent = 'Use ' + money( newSalary ); | ||
use.addEventListener( 'click', function () { | use.addEventListener( 'click', function () { | ||
salary.value = | salary.value = newSalary.toLocaleString( 'en-US' ); | ||
render(); | render(); | ||
} ); | } ); | ||
| Line 249: | Line 288: | ||
// --- ready-to-paste post --- | // --- ready-to-paste post --- | ||
// " | // "Sonics offer Caldwell Jones 2 years @ $500,000 = 3.5 points". | ||
// Whatever of team/player is missing simply drops out. | // Whatever of team/player is missing simply drops out. | ||
var tm = team.value.trim(), | var tm = team.value.trim(), | ||