| 1 | "use strict"; |
| 2 | |
| 3 | function Timer(id) { |
| 4 | var precision = 100; // milliseconds |
| 5 | var defaultTime = (15 * 60 + 0) * 1000; // milliseconds |
| 6 | var time = defaultTime; |
| 7 | var intervalID; |
| 8 | this.isTicking = false; |
| 9 | var increment = 0; |
| 10 | var defaultIncrement = 0; |
| 11 | |
| 12 | var outputTime = function() { |
| 13 | document.getElementById(id).innerHTML = timeToTimeStr(time); |
| 14 | var flagid = "flag-" + id.split('-')[1]; |
| 15 | var flagelem = document.getElementById(flagid); |
| 16 | flagelem.style.visibility = time <= 0 ? "visible" : "hidden"; |
| 17 | var incid = "increment-" + id.split('-')[1]; |
| 18 | var incelem = document.getElementById(incid); |
| 19 | incelem.innerHTML = "+" + (increment / 1000).toString(); |
| 20 | incelem.style.visibility = increment > 0 ? "visible" : "hidden"; |
| 21 | }; |
| 22 | |
| 23 | var startTickingUnixTime = null; |
| 24 | var remainingTimeAtTickingStart = null; |
| 25 | |
| 26 | var thisTimer = this; // workaround to use this in setInterval |
| 27 | this.tick = function() { |
| 28 | var currentPassedTime = new Date() - startTickingUnixTime; |
| 29 | time = remainingTimeAtTickingStart - currentPassedTime; |
| 30 | if(time <= 0) { |
| 31 | time = 0; |
| 32 | // time's up |
| 33 | clearInterval(intervalID); |
| 34 | thisTimer.isTicking = false; |
| 35 | } |
| 36 | outputTime(); |
| 37 | }; |
| 38 | this.start = function() { |
| 39 | startTickingUnixTime = new Date(); |
| 40 | remainingTimeAtTickingStart = time; |
| 41 | intervalID = setInterval(this.tick, precision); |
| 42 | this.isTicking = true; |
| 43 | }; |
| 44 | this.stop = function(toggle = false) { |
| 45 | clearInterval(intervalID); |
| 46 | this.isTicking = false; |
| 47 | if (toggle && this.getTime() > 0) { |
| 48 | this.setTime(this.getTime() + increment, increment, true); |
| 49 | } |
| 50 | }; |
| 51 | this.setTime = function(t, inc = 0, toggle = false) { |
| 52 | time = t; |
| 53 | increment = inc; |
| 54 | if (!toggle) { |
| 55 | defaultTime = t; |
| 56 | defaultIncrement = inc; |
| 57 | } |
| 58 | outputTime(); |
| 59 | }; |
| 60 | this.getTime = function() { |
| 61 | return time; |
| 62 | }; |
| 63 | this.reset = function() { |
| 64 | if(this.isTicking) { |
| 65 | this.stop(); |
| 66 | } |
| 67 | this.setTime(defaultTime, defaultIncrement); |
| 68 | }; |
| 69 | this.getDefaultTime = function() { |
| 70 | return defaultTime; |
| 71 | }; |
| 72 | } |
| 73 | |
| 74 | var currentTimers = new (function() { |
| 75 | this.leftTimer = new Timer("timer-left"); |
| 76 | this.rightTimer = new Timer("timer-right"); |
| 77 | this.active = this.leftTimer; |
| 78 | this.passive = this.rightTimer; |
| 79 | this.isPaused = true; |
| 80 | this.swap = function() { |
| 81 | var tmp = this.passive; |
| 82 | this.passive = this.active; |
| 83 | this.active = tmp; |
| 84 | var icon = document.getElementById("play"); |
| 85 | icon.innerHTML = (icon.innerHTML == "⏴" ? "⏵" : "⏴"); |
| 86 | }; |
| 87 | this.pause = function() { |
| 88 | this.active.stop(); |
| 89 | this.isPaused = true; |
| 90 | document.getElementById("pause").style.visibility = "visible"; |
| 91 | }; |
| 92 | this.resume = function() { |
| 93 | this.active.start(); |
| 94 | this.isPaused = false; |
| 95 | document.getElementById("pause").style.visibility = "hidden"; |
| 96 | }; |
| 97 | })(); |
| 98 | |
| 99 | function timeStrToTime(timeStr) { |
| 100 | var minute = parseInt(timeStr.substr(0, 2)); |
| 101 | var second = parseInt(timeStr.substr(3, 2)); |
| 102 | return (minute * 60 + second) * 1000; |
| 103 | } |
| 104 | |
| 105 | function timeToTimeStr(time) { |
| 106 | var minute = Math.floor(time / 1000 / 60); |
| 107 | var second = Math.floor(time / 1000) % 60; |
| 108 | if(minute < 10) { |
| 109 | minute = '0' + minute.toString(); |
| 110 | } |
| 111 | if(second < 10) { |
| 112 | second = '0' + second.toString(); |
| 113 | } |
| 114 | return minute + ':' + second; |
| 115 | } |
| 116 | |
| 117 | document.onkeydown = function(e) { |
| 118 | if(e.key) { |
| 119 | switch(e.key) { |
| 120 | case ' ': return toggle(); |
| 121 | case 'S': |
| 122 | case 's': return setTime(); |
| 123 | case 'R': |
| 124 | case 'r': return reset(); |
| 125 | case 'p': |
| 126 | case 'P': return pauseResume(); |
| 127 | default: return; |
| 128 | } |
| 129 | } else if(e.keyCode) { |
| 130 | switch(e.keyCode) { |
| 131 | case 32: return toggle(); // space |
| 132 | case 83: return setTime(); // S |
| 133 | case 82: return reset(); // R |
| 134 | case 80: return pauseResume(); // P |
| 135 | default: return; |
| 136 | } |
| 137 | } else { |
| 138 | alert("Browser not supported"); |
| 139 | } |
| 140 | }; |
| 141 | |
| 142 | function toggle() { |
| 143 | if(!currentTimers.isPaused) { |
| 144 | currentTimers.active.stop(true); |
| 145 | currentTimers.passive.start(); |
| 146 | } |
| 147 | currentTimers.swap(); |
| 148 | var lbt = document.getElementById('left-button-top'); |
| 149 | var leftIsLong = lbt.getAttribute('class') == 'button-long'; |
| 150 | var lbt_d = lbt.getAttribute('d').split(' '); |
| 151 | var lbb = document.getElementById('left-button-body'); |
| 152 | var rbt = document.getElementById('right-button-top'); |
| 153 | var rbt_d = rbt.getAttribute('d').split(' '); |
| 154 | var rbb = document.getElementById('right-button-body'); |
| 155 | |
| 156 | var y_diff = 50 * (leftIsLong ? 1 : -1); |
| 157 | var classes = ['button-short', 'button-long']; |
| 158 | |
| 159 | lbt_d[2] = (parseInt(lbt_d[2]) + y_diff).toString(); |
| 160 | lbt_d[10] = (parseInt(lbt_d[10]) + y_diff).toString(); |
| 161 | lbb.setAttribute('height', |
| 162 | (parseInt(lbb.getAttribute('height')) - y_diff).toString()); |
| 163 | lbb.setAttribute('y', (parseInt(lbb.getAttribute('y')) + y_diff).toString()); |
| 164 | rbt_d[2] = (parseInt(rbt_d[2]) - y_diff).toString(); |
| 165 | rbt_d[10] = (parseInt(rbt_d[10]) - y_diff).toString(); |
| 166 | rbb.setAttribute('height', |
| 167 | (parseInt(rbb.getAttribute('height')) + y_diff).toString()); |
| 168 | rbb.setAttribute('y', (parseInt(rbb.getAttribute('y')) - y_diff).toString()); |
| 169 | lbt.setAttribute('class', classes[(leftIsLong + 1) % 2]); |
| 170 | lbb.setAttribute('class', classes[(leftIsLong + 1) % 2]); |
| 171 | rbt.setAttribute('class', classes[(leftIsLong + 1) % 2]); |
| 172 | rbb.setAttribute('class', classes[(leftIsLong + 1) % 2]); |
| 173 | lbt.setAttribute('d', lbt_d.join(' ')); |
| 174 | rbt.setAttribute('d', rbt_d.join(' ')); |
| 175 | |
| 176 | lbt.setAttribute('onclick', leftIsLong ? '' : 'toggle()'); |
| 177 | lbb.setAttribute('onclick', leftIsLong ? '' : 'toggle()'); |
| 178 | lbt.setAttribute('cursor', leftIsLong ? 'default' : 'pointer'); |
| 179 | lbb.setAttribute('cursor', leftIsLong ? 'default' : 'pointer'); |
| 180 | rbt.setAttribute('onclick', leftIsLong ? 'toggle()' : ''); |
| 181 | rbb.setAttribute('onclick', leftIsLong ? 'toggle()' : ''); |
| 182 | rbt.setAttribute('cursor', leftIsLong ? 'pointer' : 'default'); |
| 183 | rbb.setAttribute('cursor', leftIsLong ? 'pointer' : 'default'); |
| 184 | } |
| 185 | |
| 186 | function pauseResume() { |
| 187 | if(currentTimers.isPaused) { |
| 188 | currentTimers.resume(); |
| 189 | } else { |
| 190 | currentTimers.pause(); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | function setTime() { |
| 195 | var leftstart, rightstart; |
| 196 | var def = timeToTimeStr(currentTimers.leftTimer.getDefaultTime()); |
| 197 | var regex = /[0-9][0-9]:[0-5][0-9](\+[0-9]+)?/; |
| 198 | |
| 199 | leftstart = prompt("Time for LEFT player in MM:SS or MM:SS+S", def); |
| 200 | if(leftstart === null) return; // Cancel |
| 201 | while(!leftstart.match(regex)) { |
| 202 | leftstart = prompt("Invalid value\nTime for LEFT player in MM:SS or MM:SS+S", def); |
| 203 | if(leftstart === null) return; // Cancel |
| 204 | } |
| 205 | def = leftstart; |
| 206 | rightstart = prompt("Time for RIGHT player in MM:SS or MM:SS+S", def); |
| 207 | if(rightstart === null) return; // Cancel |
| 208 | while(!rightstart.match(regex)) { |
| 209 | rightstart = prompt("Invalid value\nTime for RIGHT player in MM:SS or MM:SS+S", def); |
| 210 | if(rightstart === null) return; // Cancel |
| 211 | } |
| 212 | |
| 213 | if(leftstart.indexOf('+') > 0) { |
| 214 | var baseTime = leftstart.split('+')[0]; |
| 215 | var inc = leftstart.split('+')[1]; |
| 216 | currentTimers.leftTimer.setTime(timeStrToTime(baseTime), parseInt(inc) * 1000); |
| 217 | } |
| 218 | else { |
| 219 | currentTimers.leftTimer.setTime(timeStrToTime(leftstart)); |
| 220 | } |
| 221 | if(rightstart.indexOf('+') > 0) { |
| 222 | var baseTime = rightstart.split('+')[0]; |
| 223 | var inc = rightstart.split('+')[1]; |
| 224 | currentTimers.rightTimer.setTime(timeStrToTime(baseTime), parseInt(inc) * 1000); |
| 225 | } |
| 226 | else { |
| 227 | currentTimers.rightTimer.setTime(timeStrToTime(leftstart)); |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | function reset() { |
| 232 | currentTimers.pause(); |
| 233 | currentTimers.active.reset(); |
| 234 | currentTimers.passive.reset(); |
| 235 | } |
| 236 | |
| 237 | function init() { |
| 238 | insertcss(); |
| 239 | setTimeout(fitscreen, 2000); // hack to allow DOM to be redrawn with new css |
| 240 | } |
| 241 | |
| 242 | function fitscreen() { |
| 243 | /* |
| 244 | * Scale body so as to fit the screen |
| 245 | */ |
| 246 | var heightRatio = window.innerHeight / document.body.scrollHeight; |
| 247 | var widthRatio = window.innerWidth / document.body.scrollWidth; |
| 248 | var scaleRatio = Math.min(heightRatio, widthRatio, 1); |
| 249 | document.body.style.transform = 'scale(' + scaleRatio + ')'; |
| 250 | } |
| 251 | |
| 252 | function insertcss() { |
| 253 | /* fix positions for browsers that don't support flex */ |
| 254 | var cssfile = document.getElementById("style"); |
| 255 | if(!('align-items' in document.body.style)) { |
| 256 | cssfile.setAttribute("href", "noflex.css"); |
| 257 | } |
| 258 | } |