-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
369 lines (281 loc) · 11.2 KB
/
index.html
File metadata and controls
369 lines (281 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
<!DOCTYPE html>
<!--
This proof of concept aims to provide a self-contained application in
one html file. Constraints are:
* One file
* Cacheable by caching browsers, ie under 25K.
(See: http://www.yuiblog.com/blog/2008/02/06/iphone-cacheability/)
* Targeting webkit based native mobile browsers, ie iOS's and Android (2.2, 2.1)
* Other browsers optionally supported only via HTML5 (no custom extensions)
* Must be enjoyable to read and easy to understand.
* Easy to understand
* Interesting techniques will be linked to explanations, for further learning
and great goodness
* Not using libraries to abstract us from the Javascript / DOM. Let's see how far
we can go with what's there already
* Don't be too clever
-->
<html>
<head>
<!-- Vanity first. If you contribute, add your name -->
<meta name="author" content="Artur Honzawa (@_arturh)" />
<!-- Mobile viewport optimized: j.mp/bplateviewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- TODO?: create icons.
Icons break the app in one file constraint. Can they be base64 encoded?
Might need to break the rule ...
<link rel="shortcut icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
-->
<!--
-->
<style>
body {
background-color: #ccc;
}
body > button{
display: block;
height: 50%;
min-height: 160px;
min-width: 320px;
min-width: 100%;
text-align: center;
margin: 0;
padding: 0;
border: none;
-webkit-appearance: none;
}
#p1 {
background: pink;
background-image: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, red),
color-stop(1, pink)
);
}
#p2 {
-webkit-transform: rotate(180deg);
background-image: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, blue),
color-stop(1, lightblue)
);
}
.time {
font-size: 100px;
}
.conf {
background-color: green;
background-color: rgba(0, 128, 0, .90);
border-radius: 12px;
padding: 7%;
position: absolute;
top: 10%;
left: 10%;
height: 60%;
width: 60%;
}
.lost {
background: red;
}
</style>
</head>
<body>
<button id="p2"><span class="time"></span></button>
<button id="p1"><span class="time"></span></button>
<div class="conf"><form action="#">
<select>
<option value="120" >2:00</option>
<option value="180" >3:00</option>
<option value="300" selected>5:00</option>
<option value="600" >10:00</option>
<option value="1200" >20:00</option>
<option value="1800" >30:00</option>
<option value="2700" >45:00</option>
<option value="3600" >1 hour</option>
<option value="7200" >2 hours</option>
<option value="10800" >3 hours</option>
</select>
<input type=submit value="Start"></input>
</form></div>
<!--
The script tag is located at the end of the body, so that when it runs, needed
elements can be found in the DOM. This can also be achieved by running the
function in a handler tied to the onDOMReady event, for example.
The code is written so as to take advantage of new features in Ecmascript 5. Also,
it is written in a functional manner. Javascript programming can incorporate
useful functional programming constructs which make programming safer, if a
little bit odd before you get used to it.
For a presentation on Javascript, by the ultimate authority see:
http://vimeo.com/8691412
For a set of guidelines on how to use the languaje, by the same man, see:
http://www.jslint.com/lint.html
-->
<script>
// Directives for JSLint, Javascript checker:
/*jslint strict: true, maxerr: 1 */
/*global document, window*/
////////////////////////////////////////////////////////////////////////////////
//
// The code is surrounded by a self executing function, that is an anonymous
// function which is called just after being created: (function () {...}());
//
// Its purpose is twofold:
//
// 1. Avoid creating global variables: All variables declared in the scope of
// the function are invisible from outside the function. If you don't
// know why you should avoid global variables, see:
// http://c2.com/cgi/wiki?GlobalVariablesAreBad
//
// 2. Create a closure: Even after the anonymous function has executed, its
// values can be referenced afterwards. This allows us to share state
// without creating global objects. See:
// https://developer.mozilla.org/en/JavaScript/Guide/Closures
//
//
//
// This function is passed the document and window objects, which are
// external to our code. These are assigned short, one character variable
// names for easy access: d = document, w = window
//
(function (d, w) {
// strict mode prevents us from using questionable "features" in javascript.
// See: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
//
"use strict";
////////////////////////////////////////////////////////////////////////////////
// All variables are defined under the following var statement. Under strict mode,
// variables not declared under "var" will not create a global variable, they
// will throw an error instead.
//
// Some functional programming languages don't allow a variable to be assigned
// twice. This has some good properties and allows to think about the program
// more easily. This style has been adopted, up to where it makes sense. See:
// http://blog.objectmentor.com/articles/2007/02/26/outliving-the-great-variable-shortage
//
var
//
// Constants are writen in CAPS. These are not to be changed after being
// defined. These iconstants are not language enforced, but convention-enforced.
//
// on touch devices touchstart fires earlier than click
// double negation (!!) coerces to boolean
//
CLICK_EVENT = !!d.ontouchstart ? "touchstart" : "click",
// Default state. This object must not be chaged so it's frozen. Frozen objects
// throw an exception when they are modified. See:
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze
//
// State objects should be JSON serializable, so they can be stored
// in localStorage.
//
DEFAULT_STATE = w.Object.freeze({
currentPlayer: "p1", // player whose time is running
name: { p1: "red", // name of each player
p2: "blue" },
time: false // time remaining to each player. Not defined here.
}),
// Variables declared, but not assigned have value undefined.
interval /* = undefined */,
lastcall /* = undefined */,
state /* = undefined */,
// Alias for each of the players' buttons
p1_button = d.querySelector("#p1"),
p2_button = d.querySelector("#p2"),
//
// Functions.
// Named functions are a bit counter-intuitive so we assign them to variables.
// See: http://stackoverflow.com/questions/336859/javascript-var-functionname-function-vs-function-functionname
//
// converts from remaining milliseconds to readable string
format_time = function (t) {
// t (in ms) is decomposed in hours, minutes and seconds
var
hours = w.Math.floor(t / 3600000), // Math.floor discards the decimal part
minutes = w.Math.floor(t % 3600000 / 60000), // % is modulus / remainder operator
seconds = t % 60000 / 1000; // seconds not converted to integer, so float
return [ // The components are put in an array so that they can be
// .joined afterwards. This avoids reassigning to a variable
// or creating unnecessary intermediate variables.
hours, // .substr(-n) means "return n last characters"
("00" + minutes).substr(-2), // the minutes part always two digits
("00" + seconds.toFixed(1)).substr(-4) // the seconds part always 2 digits + 1 decimal
].slice(+!hours) // skip the hours if 0
.join(":"); // this creates the time string x:xx:xx.x
},
// displays remaining time on the button of each player
display_time = function () {
p1_button.querySelector(".time").innerHTML = format_time(state.time.p1);
p2_button.querySelector(".time").innerHTML = format_time(state.time.p2);
},
// called about every 100 ms, updates and checks the remaining time
tick = function () {
var now = w.Date.now(),
elapsed = now - lastcall;
if (lastcall) { // avoid first call edge-case
state.time[state.currentPlayer] = ( // update current player's time
w.Math.max( // expression in () allows multi-line expression
0, // avoid setting it to negative values
state.time[state.currentPlayer] - elapsed));
}
lastcall = now;
display_time();
if (state.time[state.currentPlayer] <= 0) { // currentPlayer lost
w.clearInterval(interval); // stop calling tick
w.alert(state.name[state.currentPlayer] + " lost"); // display end message
d.querySelector(".conf").style.display = "block"; // then display time selection again
}
},
//
// Event handlers. These functions are called when a registered event is
// triggered.
//
// Called after time selection, sets everything up.
start_button_handler = function (event) {
// time for each player in ms
var time = w.parseInt(d.querySelector(".conf select").value) * 1000;
// parse / stringify trick to clone serializable objects. Cloned object
// is sealed, so that no more properties can be added. This alerts us in case
// we make a typo in assigning to a property. See:
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal
//
state = w.Object.seal(w.JSON.parse(w.JSON.stringify(DEFAULT_STATE)));
// set each player's time
state.time = w.Object.seal({ p1: time,
p2: time });
// start calling tick every 100 ms
interval = w.setInterval(tick, 100);
// hide div.conf
d.querySelector(".conf").style.display = "none";
// prevent form submission
event.preventDefault();
},
// Called when the buttons are tapped / clicked
switch_user_handler = function (/* event, unused parameter */) {
// check if the tapped button is the current player's
if (state.currentPlayer === this.id) {
// if so switch players
if (state.currentPlayer === "p1") {
state.currentPlayer = "p2";
} else {
state.currentPlayer = "p1";
}
}
}
; // end var statement
// register event handler for the start button
d.querySelector(".conf form")
.addEventListener("submit", start_button_handler, false);
// register event handler for the player's buttons
p1_button.addEventListener(CLICK_EVENT, switch_user_handler, false);
p2_button.addEventListener(CLICK_EVENT, switch_user_handler, false);
// Self-executing anonymous function invocation. document and window are
// assigned to the d and w parameters.
}(document, window));
</script>
</body>
</html>