Katamari! userscript port

Launch with right click menu

  1. // ==UserScript==
  2. // @name Katamari! userscript port
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Launch with right click menu
  6. // @author Alex Leone, David Nufer, and David Truong (userscript port by Charlie Burnham)
  7. // @match *
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
  9. // @grant none
  10. // @run-at context-menu
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. /*
  15. Copyright Alex Leone, David Nufer, David Truong, 2011-03-11. kathack.com
  16.  
  17. javascript:var i,s,ss=['http://kathack.com/js/kh.js','http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js'];for(i=0;i!=ss.length;i++){s=document.createElement('script');s.src=ss[i];document.body.appendChild(s);}void(0);
  18.  
  19. */
  20. var BORDER_STYLE = "1px solid #bbb",
  21. CSS_TRANSFORM = null,
  22. CSS_TRANSFORM_ORIGIN = null,
  23. POSSIBLE_TRANSFORM_PREFIXES = ['-webkit-', '-moz-', '-o-', '-ms-', ''],
  24. khFirst = false;
  25.  
  26. /* When running twice on one page, update pick-uppable nodes instead of
  27. * creating more.
  28. */
  29. if (!window.khNodes) {
  30. khFirst = true;
  31. window.khNodes = new StickyNodes();
  32. }
  33.  
  34. function getCssTransform() {
  35. var i, d = document.createElement('div'), pre;
  36. for (i = 0; i < POSSIBLE_TRANSFORM_PREFIXES.length; i++) {
  37. pre = POSSIBLE_TRANSFORM_PREFIXES[i];
  38. d.style.setProperty(pre + 'transform', 'rotate(1rad) scaleX(2)', null);
  39. if (d.style.getPropertyValue(pre + 'transform')) {
  40. CSS_TRANSFORM = pre + 'transform';
  41. CSS_TRANSFORM_ORIGIN = pre + 'transform-origin';
  42. return;
  43. }
  44. }
  45. alert("Your browser doesn't support CSS tranforms!");
  46. throw "Your browser doesn't support CSS tranforms!";
  47. }
  48. getCssTransform();
  49.  
  50. /**
  51. * Returns true if the circle intersects the element rectangle.
  52. * 0 | 1 | 2
  53. * ------------------
  54. * 3 | 4 | 5
  55. * ------------------
  56. * 6 | 7 | 9
  57. */
  58. function circleGridObjInt(cx, cy, cr, cr2, go) {
  59. var dx, dy;
  60. if (cx < go.left) {
  61. dx = go.left - cx;
  62. if (cy < go.top) { /* zone 0. */
  63. dy = go.top - cy;
  64. return ((dx * dx + dy * dy) <= cr2);
  65. } else if (cy <= go.bottom) { /* zone 3. */
  66. return (dx <= cr);
  67. } else { /* zone 6. */
  68. dy = cy - go.bottom;
  69. return ((dx * dx + dy * dy) <= cr2);
  70. }
  71. } else if (cx <= go.right) {
  72. if (cy < go.top) { /* zone 1. */
  73. return ((go.top - cy) <= cr);
  74. } else if (cy <= go.bottom) { /* zone 4. */
  75. return true;
  76. } else { /* zone 7. */
  77. return ((cy - go.bottom) <= cr);
  78. }
  79. } else {
  80. dx = cx - go.right;
  81. if (cy < go.top) { /* zone 2. */
  82. dy = go.top - cy;
  83. return ((dx * dx + dy * dy) <= cr2);
  84. } else if (cy <= go.bottom) { /* zone 5. */
  85. return (dx <= cr);
  86. } else { /* zone 9. */
  87. dy = cy - go.bottom;
  88. return ((dx * dx + dy * dy) <= cr2);
  89. }
  90. }
  91. }
  92.  
  93. /**
  94. * Returns [x,y] where the rectangle is closest to (cx, cy).
  95. * 0 | 1 | 2
  96. * ------------------
  97. * 3 | 4 | 5
  98. * ------------------
  99. * 6 | 7 | 9
  100. */
  101. function getClosestPoint(cx, cy, go) {
  102. var dx, dy;
  103. if (cx < go.left) {
  104. dx = go.left - cx;
  105. if (cy < go.top) { /* zone 0. */
  106. return [go.left, go.top];
  107. } else if (cy <= go.bottom) { /* zone 3. */
  108. return [go.left, cy];
  109. } else { /* zone 6. */
  110. return [go.left, go.bottom];
  111. }
  112. } else if (cx <= go.right) {
  113. if (cy < go.top) { /* zone 1. */
  114. return [cx, go.top];
  115. } else if (cy <= go.bottom) { /* zone 4. */
  116. return [cx, cy];
  117. } else { /* zone 7. */
  118. return [cx, go.bottom];
  119. }
  120. } else {
  121. dx = cx - go.right;
  122. if (cy < go.top) { /* zone 2. */
  123. return [go.right, go.top];
  124. } else if (cy <= go.bottom) { /* zone 5. */
  125. return [go.right, cy];
  126. } else { /* zone 9. */
  127. return [go.right, go.bottom];
  128. }
  129. }
  130. }
  131.  
  132. /**
  133. * Returns the "volume" of the grid object.
  134. */
  135. function gridObjVol(go) {
  136. return go.w * go.h * Math.min(go.w, go.h);
  137. }
  138.  
  139. function StickyNodes() {
  140. var domNodes = [],
  141. grid = [],
  142. GRIDX = 100,
  143. GRIDY = 100,
  144. REPLACE_WORDS_IN = {
  145. a: 1, b: 1, big: 1, body: 1, cite:1, code: 1, dd: 1, div: 1,
  146. dt: 1, em: 1, font: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1,
  147. i: 1, label: 1, legend: 1, li: 1, p: 1, pre: 1, small: 1,
  148. span: 1, strong: 1, sub: 1, sup: 1, td: 1, th: 1, tt: 1
  149. };
  150.  
  151. function addDomNode(el) {
  152. if (el !== undefined && el !== null) {
  153. el.khIgnore = true;
  154. el.style.border = BORDER_STYLE;
  155. domNodes.push(el);
  156. }
  157. }
  158. this.addDomNode = addDomNode;
  159.  
  160. this.addWords = function (el) {
  161. var textEls = [];
  162.  
  163. function shouldAddChildren(el) {
  164. return el.tagName && REPLACE_WORDS_IN[el.tagName.toLowerCase()];
  165. }
  166.  
  167. function buildTextEls(el, shouldAdd) {
  168. var i, len;
  169. if (shouldAdd && el.nodeType === Node.TEXT_NODE &&
  170. el.nodeValue.trim().length > 0) {
  171. textEls.push(el);
  172. return;
  173. }
  174. if (!el.childNodes || el.khIgnore) {
  175. return;
  176. }
  177. shouldAdd = shouldAddChildren(el);
  178. for (i = 0, len = el.childNodes.length; i < len; i++) {
  179. buildTextEls(el.childNodes[i], shouldAdd);
  180. }
  181. }
  182.  
  183. function wordsToSpans(textEl) {
  184. var p = textEl.parentNode,
  185. words = textEl.nodeValue.split(/\s+/),
  186. ws = textEl.nodeValue.split(/\S+/),
  187. i, n, len = Math.max(words.length, ws.length);
  188. /* preserve whitespace for pre tags. */
  189. if (ws.length > 0 && ws[0].length === 0) {
  190. ws.shift();
  191. }
  192. for (i = 0; i < len; i++) {
  193. if (i < words.length && words[i].length > 0) {
  194. n = document.createElement('span');
  195. n.innerHTML = words[i];
  196. p.insertBefore(n, textEl);
  197. addDomNode(n);
  198. }
  199. if (i < ws.length && ws[i].length > 0) {
  200. n = document.createTextNode(ws[i]);
  201. p.insertBefore(n, textEl);
  202. }
  203. }
  204. p.removeChild(textEl);
  205. }
  206.  
  207. buildTextEls(el, shouldAddChildren(el));
  208. textEls.map(wordsToSpans);
  209. };
  210.  
  211. /* includes el. */
  212. this.addTagNames = function (el, tagNames) {
  213. var tname = el.tagName && el.tagName.toLowerCase(),
  214. i, j, els, len;
  215. if (el.khIgnore) {
  216. return;
  217. }
  218. if (tagNames.indexOf(tname) !== -1) {
  219. addDomNode(el);
  220. }
  221. if (!el.getElementsByTagName) {
  222. return;
  223. }
  224. for (i = 0; i < tagNames.length; i++) {
  225. els = el.getElementsByTagName(tagNames[i]);
  226. for (j = 0, len = els.length; j < len; j++) {
  227. if (!els[j].khIgnore) {
  228. addDomNode(els[j]);
  229. }
  230. }
  231. }
  232. };
  233.  
  234. this.finalize = function (docW, docH) {
  235. var xi, yi, i, len, startXI, startYI, el, go, off, w, h,
  236. endXI = Math.floor(docW / GRIDX) + 1,
  237. endYI = Math.floor(docH / GRIDY) + 1;
  238. /* initialize grid. */
  239. grid = new Array(endXI);
  240. for (xi = 0; xi < endXI; xi++) {
  241. grid[xi] = new Array(endYI);
  242. }
  243. /* add nodes into grid. */
  244. for (i = 0, len = domNodes.length; i < len; i++) {
  245. el = domNodes[i];
  246. if (el.khPicked) {
  247. continue;
  248. }
  249. off = jQuery(el).offset();
  250. w = jQuery(el).width();
  251. h = jQuery(el).height();
  252. go = {
  253. el: domNodes[i], /* dom element. */
  254. left: off.left,
  255. right: off.left + w,
  256. top: off.top,
  257. bottom: off.top + h,
  258. w: w,
  259. h: h,
  260. x: off.left + (w / 2), /* center x. */
  261. y: off.top + (h / 2), /* center y. */
  262. diag: Math.sqrt(((w * w) + (h * h)) / 4), /* center to corner */
  263.  
  264. /* these are for removing ourselves from the grid. */
  265. arrs: [], /* which arrays we're in (grid[x][y]). */
  266. idxs: [] /* what indexes. */
  267. };
  268. startXI = Math.floor(go.left / GRIDX);
  269. startYI = Math.floor(go.top / GRIDY);
  270. endXI = Math.floor((go.left + go.w) / GRIDX) + 1;
  271. endYI = Math.floor((go.top + go.h) / GRIDY) + 1;
  272. for (xi = startXI; xi < endXI; xi++) {
  273. for (yi = startYI; yi < endYI; yi++) {
  274. if (grid[xi] === undefined) {
  275. grid[xi] = [];
  276. }
  277. if (grid[xi][yi] === undefined) {
  278. grid[xi][yi] = [go];
  279. } else {
  280. grid[xi][yi].push(go);
  281. }
  282. go.arrs.push(grid[xi][yi]);
  283. go.idxs.push(grid[xi][yi].length - 1);
  284. }
  285. }
  286. }
  287. };
  288.  
  289. function removeGridObj(go) {
  290. var i;
  291. for (i = 0; i < go.arrs.length; i++) {
  292. go.arrs[i][go.idxs[i]] = undefined;
  293. }
  294. go.el.style.visibility = "hidden";
  295. go.el.khPicked = true;
  296. delete go.arrs;
  297. delete go.idxs;
  298. }
  299.  
  300. /**
  301. * cb(gridObj) -> boolean true if the object should be removed.
  302. */
  303. this.removeIntersecting = function (x, y, r, cb) {
  304. var xi, yi, arr, i, r2 = r * r, go,
  305. startXI = Math.floor((x - r) / GRIDX),
  306. startYI = Math.floor((y - r) / GRIDY),
  307. endXI = Math.floor((x + r) / GRIDX) + 1,
  308. endYI = Math.floor((y + r) / GRIDY) + 1;
  309. for (xi = startXI; xi < endXI; xi++) {
  310. if (grid[xi] === undefined) {
  311. continue;
  312. }
  313. for (yi = startYI; yi < endYI; yi++) {
  314. arr = grid[xi][yi];
  315. if (arr === undefined) {
  316. continue;
  317. }
  318. for (i = 0; i < arr.length; i++) {
  319. go = arr[i];
  320. if (go !== undefined &&
  321. circleGridObjInt(x, y, r, r2, go) &&
  322. cb(go)) {
  323. removeGridObj(go);
  324. }
  325. }
  326. }
  327. }
  328. };
  329. }
  330.  
  331. function PlayerBall(parentNode, stickyNodes, ballOpts, sounds) {
  332. var x = 300, y = 300,
  333. vx = 0, vy = 0,
  334. radius = 20,
  335. lastR = 0, /**< optimization: only resize when necessary. */
  336. docW = 10000, docH = 10000,
  337.  
  338. attached = [],
  339. attachedDiv, /* div to put attached nodes into. */
  340. canvas_el,
  341. canvas_ctx,
  342. color = ballOpts.color,
  343.  
  344. accelTargetX = 0, accelTargetY = 0,
  345. accel = false,
  346.  
  347. VOL_MULT = ballOpts.VOL_MULT,
  348. MAX_ATTACHED_VISIBLE = ballOpts.MAX_ATTACHED_VISIBLE,
  349. CHECK_VOLS = ballOpts.CHECK_VOLS,
  350.  
  351. /**
  352. * which direction the ball is facing in the xy axis, in radians.
  353. * th: 0 is facing dead East
  354. * th: 1/2 PI is facing dead South
  355. * note that this is like regular th on a graph with y inverted.
  356. * Same rotation as css transform.
  357. */
  358. th = 0,
  359.  
  360. /**
  361. * Ball angle in the rotation axis / z plane, in radians.
  362. * phi: 0 is pointing in the direction the ball is rolling.
  363. * phi: 1/2 PI is pointing straight up (out of the page).
  364. * note that forward rotation means phi -= 0.1.
  365. */
  366. phi = 0;
  367.  
  368. this.init = function () {
  369. canvas_el = document.createElement('canvas');
  370. canvas_el.width = radius * 2;
  371. canvas_el.height = radius * 2;
  372. canvas_el.style.cssText = 'position: absolute; z-index: 500;';
  373. parentNode.appendChild(canvas_el);
  374. canvas_ctx = canvas_el.getContext('2d');
  375.  
  376. attachedDiv = document.createElement('div');
  377. parentNode.appendChild(attachedDiv);
  378. };
  379.  
  380. this.setRadius = function (r) {
  381. radius = r;
  382. };
  383.  
  384. this.getState = function () {
  385. return {
  386. x: x,
  387. y: y,
  388. vx: vx,
  389. vy: vy,
  390. radius: radius,
  391. th: th,
  392. phi: phi,
  393. };
  394. };
  395.  
  396. this.setState = function (s) {
  397. x = s.x;
  398. y = s.y;
  399. vx = s.vx;
  400. vy = s.vy;
  401. radius = s.radius;
  402. th = s.th;
  403. phi = s.phi;
  404. };
  405.  
  406. this.setXY = function (sx, sy) {
  407. x = sx;
  408. y = sy;
  409. };
  410.  
  411. this.setTh = function (sth) {
  412. th = sth;
  413. };
  414.  
  415. this.setPhi = function (sphi) {
  416. phi = sphi;
  417. };
  418.  
  419. this.setColor = function (c) {
  420. color = c;
  421. };
  422.  
  423. this.setDocSize = function (w, h) {
  424. docW = w;
  425. docH = h;
  426. };
  427.  
  428. this.setAccel = function (bool) {
  429. accel = bool;
  430. };
  431.  
  432. this.setAccelTarget = function (tx, ty) {
  433. accelTargetX = tx;
  434. accelTargetY = ty;
  435. };
  436.  
  437. function getVol() {
  438. return (4 * Math.PI * radius * radius * radius / 3);
  439. }
  440.  
  441. function grow(go) {
  442. var newVol = getVol() + gridObjVol(go) * VOL_MULT;
  443. radius = Math.pow(newVol * 3 / (4 * Math.PI), 1 / 3);
  444. }
  445.  
  446. function attachGridObj(go) {
  447. var attXY = getClosestPoint(x, y, go),
  448. dx = attXY[0] - x,
  449. dy = attXY[1] - y,
  450. r = Math.sqrt(dx * dx + dy * dy),
  451. attTh = 0 - th,
  452. offLeft = attXY[0] - go.left,
  453. offTop = attXY[1] - go.top,
  454. offTh = Math.atan2(dy, dx) - th,
  455. attX = r * Math.cos(offTh),
  456. attY = r * Math.sin(offTh),
  457. el = go.el.cloneNode(true),
  458. go_jel = jQuery(go.el),
  459. newAtt = {
  460. el: el,
  461. attX: attX,
  462. attY: attY,
  463. attT: 'translate(' + Math.round(attX) + 'px,' +
  464. Math.round(attY) + 'px) ' +
  465. 'rotate(' + attTh + 'rad)',
  466. r: r,
  467. offTh: offTh,
  468. offPhi: 0 - phi,
  469. diag: go.diag,
  470. removeR: r + go.diag,
  471. visible: false,
  472. display: go_jel.css('display')
  473. };
  474. attached.push(newAtt);
  475. grow(go);
  476. el.style.position = 'absolute';
  477. el.style.left = (-offLeft) + 'px';
  478. el.style.top = (-offTop) + 'px';
  479. el.style.setProperty(CSS_TRANSFORM_ORIGIN,
  480. offLeft + 'px ' + offTop + 'px', null);
  481. el.style.display = 'none';
  482. /* copy computed styles from old object. */
  483. el.style.color = go_jel.css('color');
  484. el.style.textDecoration = go_jel.css('text-decoration');
  485. el.style.fontSize = go_jel.css('font-size');
  486. el.style.fontWeight = go_jel.css('font-weight');
  487. el.khIgnore = true;
  488. attachedDiv.appendChild(el);
  489. if (sounds) {
  490. sounds.play_pop();
  491. }
  492. }
  493.  
  494. /**
  495. * @return true if the object should be removed from stickyNodes.
  496. */
  497. function removeIntCb(go) {
  498. if (CHECK_VOLS && gridObjVol(go) > getVol()) {
  499. return false;
  500. }
  501. attachGridObj(go);
  502. return true;
  503. }
  504.  
  505. this.updatePhysics = function () {
  506. var oldX = x, oldY = y, dx, dy,
  507. bounce = false,
  508. accelTh;
  509. if (accel) {
  510. accelTh = Math.atan2(accelTargetY - y, accelTargetX - x);
  511. vx += Math.cos(accelTh) * 0.5;
  512. vy += Math.sin(accelTh) * 0.5;
  513. } else {
  514. vx *= 0.95;
  515. vy *= 0.95;
  516. }
  517. x += vx;
  518. y += vy;
  519. /* bounce ball on edges of document. */
  520. if (x - radius < 0) {
  521. bounce = true;
  522. x = radius + 1;
  523. vx = -vx;
  524. } else if (x + radius > docW) {
  525. bounce = true;
  526. x = docW - radius - 1;
  527. vx = -vx;
  528. }
  529. if (y - radius < 0) {
  530. bounce = true;
  531. y = radius + 1;
  532. vy = -vy;
  533. } else if (y + radius > docH) {
  534. bounce = true;
  535. y = docH - radius - 1;
  536. vy = -vy;
  537. }
  538. if (vx !== 0 || vy !== 0) {
  539. th = Math.atan2(vy, vx);
  540. dx = x - oldX;
  541. dy = y - oldY;
  542. /* arclen = th * r, so th = arclen / r. */
  543. phi -= Math.sqrt(dx * dx + dy * dy) / radius;
  544. }
  545. stickyNodes.removeIntersecting(x, y, radius, removeIntCb);
  546. this.draw();
  547. if (bounce && sounds) {
  548. sounds.play_bounce();
  549. }
  550. };
  551.  
  552. function drawBall() {
  553. var sx1, sy1, sx2, sy2, dx, dy, i, pct1, pct2, z1, z2;
  554. /* move/resize canvas element. */
  555. canvas_el.style.left = (x - radius) + 'px';
  556. canvas_el.style.top = (y - radius) + 'px';
  557. if (radius != lastR) {
  558. canvas_el.width = 2 * radius + 1;
  559. canvas_el.height = 2 * radius + 1;
  560. lastR = radius;
  561. }
  562. /* draw white circle. */
  563. canvas_ctx.clearRect(0, 0, 2 * radius, 2 * radius);
  564. canvas_ctx.fillStyle = "#fff";
  565. canvas_ctx.beginPath();
  566. canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true);
  567. canvas_ctx.fill();
  568. /* draw outer border. */
  569. canvas_ctx.strokeStyle = color;
  570. canvas_ctx.beginPath();
  571. canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true);
  572. canvas_ctx.stroke();
  573. /* draw stripes. */
  574. canvas_ctx.fillStyle = color;
  575. sx1 = radius + radius * Math.cos(th + Math.PI / 16);
  576. sy1 = radius + radius * Math.sin(th + Math.PI / 16);
  577. sx2 = radius + radius * Math.cos(th - Math.PI / 16);
  578. sy2 = radius + radius * Math.sin(th - Math.PI / 16);
  579. dx = (radius + radius * Math.cos(th + Math.PI * 15 / 16)) - sx1;
  580. dy = (radius + radius * Math.sin(th + Math.PI * 15 / 16)) - sy1;
  581. for (i = 0; i < Math.PI * 2; i += Math.PI / 7) {
  582. pct1 = (-Math.cos(phi + i) + 1) / 2;
  583. pct2 = (-Math.cos(phi + i + Math.PI / 32) + 1) / 2;
  584. z1 = Math.sin(phi + i);
  585. z2 = Math.sin(phi + i + Math.PI / 32);
  586. if (z1 > 0 && z2 > 0) {
  587. canvas_ctx.beginPath();
  588. canvas_ctx.moveTo(sx1 + pct1 * dx, sy1 + pct1 * dy);
  589. canvas_ctx.lineTo(sx1 + pct2 * dx, sy1 + pct2 * dy);
  590. canvas_ctx.lineTo(sx2 + pct2 * dx, sy2 + pct2 * dy);
  591. canvas_ctx.lineTo(sx2 + pct1 * dx, sy2 + pct1 * dy);
  592. canvas_ctx.fill();
  593. }
  594. }
  595. }
  596.  
  597. /**
  598. * @return true if the attached object is roughly visible.
  599. */
  600. function drawAttached(att) {
  601. var oth = th + att.offTh,
  602. ophi = phi + att.offPhi,
  603. ox = att.r * Math.cos(oth),
  604. oy = att.r * Math.sin(oth),
  605. dx = (att.r * Math.cos((th - att.offTh) + Math.PI)) - ox,
  606. dy = (att.r * Math.sin((th - att.offTh) + Math.PI)) - oy,
  607. pct = (-Math.cos(ophi) + 1) / 2,
  608. cx = ox + pct * dx,
  609. cy = oy + pct * dy,
  610. oz = att.r * Math.sin(ophi);
  611. if (oz < 0 && Math.sqrt(cx * cx + cy * cy) + att.diag < radius) {
  612. /* hidden behind circle. */
  613. if (att.visible) {
  614. att.visible = false;
  615. att.el.style.display = "none";
  616. }
  617. return false;
  618. }
  619. /* attached node is visible. */
  620. if (!att.visible) {
  621. att.visible = true;
  622. att.el.style.display = att.display;
  623. }
  624. //att.el.style.zIndex = 500 + Math.round(oz);
  625. att.el.style.zIndex = (oz > 0)? 501 : 499;
  626. att.el.style.setProperty(
  627. CSS_TRANSFORM,
  628. 'translate(' + x + 'px,' + y + 'px) ' +
  629. 'rotate(' + th + 'rad) ' +
  630. 'scaleX(' + Math.cos(ophi) + ') ' +
  631. att.attT, null);
  632. return true;
  633. }
  634.  
  635. function onAttachedRemoved(att) {
  636. attachedDiv.removeChild(att.el);
  637. delete att.el;
  638. }
  639.  
  640. this.draw = function () {
  641. var i, att, numAttachedVisible = 0;
  642. drawBall();
  643. for (i = attached.length; --i >= 0;) {
  644. att = attached[i];
  645. if (att.removeR < radius) {
  646. attached.splice(i, 1).map(onAttachedRemoved);
  647. } else if (drawAttached(att)) {
  648. if (++numAttachedVisible > MAX_ATTACHED_VISIBLE) {
  649. /* remove older items and stop. */
  650. attached.splice(0, i).map(onAttachedRemoved);
  651. break;
  652. }
  653. }
  654. }
  655. };
  656. }
  657.  
  658. function preventDefault(event) {
  659. event.preventDefault();
  660. event.returnValue = false;
  661. return false;
  662. }
  663.  
  664. function Game(gameDiv, stickyNodes, ballOpts) {
  665. var stickyNodes, player1, physicsInterval, resizeInterval, listeners = [];
  666. player1 = new PlayerBall(gameDiv, stickyNodes, ballOpts, false);
  667. player1.init();
  668. player1.setXY(300, 300);
  669. window.scrollTo(0, 200);
  670.  
  671. function on_resize() {
  672. player1.setDocSize(jQuery(document).width() - 5,
  673. jQuery(document).height() - 5);
  674. }
  675. on_resize();
  676.  
  677. /* touch events - always on? */
  678. document.addEventListener('touchstart', function (event) {
  679. if (event.touches.length === 1) {
  680. player1.setAccel(true);
  681. return preventDefault(event);
  682. }
  683. }, true);
  684. document.addEventListener('touchmove', function (event) {
  685. player1.setAccelTarget(event.touches[0].pageX,
  686. event.touches[0].pageY);
  687. }, true);
  688. document.addEventListener('touchend', function (event) {
  689. if (event.touches.length === 0) {
  690. player1.setAccel(false);
  691. return preventDefault(event);
  692. }
  693. }, true);
  694.  
  695. if (ballOpts.MOUSEB !== -5) {
  696. /* mouse buttons */
  697. document.addEventListener('mousemove', function (event) {
  698. player1.setAccelTarget(event.pageX, event.pageY);
  699. }, true);
  700. document.addEventListener('mousedown', function (event) {
  701. if (event.button === ballOpts.MOUSEB) {
  702. player1.setAccel(true);
  703. return preventDefault(event);
  704. }
  705. }, true);
  706. document.addEventListener('mouseup', function (event) {
  707. if (event.button === ballOpts.MOUSEB) {
  708. player1.setAccel(false);
  709. return preventDefault(event);
  710. }
  711. }, true);
  712.  
  713. if (ballOpts.MOUSEB === 0) {
  714. /* block click events. */
  715. document.addEventListener('click', function (event) {
  716. if (event.button === 0) {
  717. return preventDefault(event);
  718. }
  719. }, true);
  720. } else if (ballOpts.MOUSEB === 2) {
  721. /* block right-click context menu. */
  722. document.addEventListener('contextmenu', preventDefault, true);
  723. }
  724. }
  725.  
  726. physicsInterval = setInterval(function () {
  727. player1.updatePhysics();
  728. }, 25);
  729. resizeInterval = setInterval(on_resize, 1000);
  730. }
  731.  
  732. function whenAllLoaded(gameDiv, popup, stickyNodes) {
  733. stickyNodes.finalize(jQuery(document).width(), jQuery(document).height());
  734. jQuery('#loadingp').empty();
  735. jQuery('<button>Start!</button>').click(function () {
  736. var game, bgmusic, ballOpts;
  737. if (jQuery('#bgmusicc').attr('checked')) {
  738. if (!(bgmusic = document.getElementById('khbgmusic'))) {
  739. bgmusic = document.createElement('audio');
  740. bgmusic.id = 'khbgmusic';
  741. bgmusic.loop = 'loop';
  742. bgmusic.src = 'http://kathack.com/js/katamari.mp3';
  743. gameDiv.appendChild(bgmusic);
  744. }
  745. bgmusic.play();
  746. }
  747. ballOpts = {
  748. color: jQuery('#khcolor').val(),
  749. VOL_MULT: parseFloat(jQuery('#vol_mult').val()),
  750. MAX_ATTACHED_VISIBLE: parseInt(jQuery('#maxAtt').val(), 10),
  751. CHECK_VOLS: (jQuery('#checkv').attr('checked'))? true : false,
  752. MOUSEB: parseInt(jQuery('#mouseb').val(), 10)
  753. };
  754. gameDiv.removeChild(popup);
  755. game = new Game(gameDiv, stickyNodes, ballOpts);
  756. }).appendTo('#loadingp');
  757. }
  758.  
  759. function buildPopup(gameDiv) {
  760. var d = document.createElement('div'), b;
  761. d.style.cssText = '\
  762. position: fixed;\
  763. left: 50%;\
  764. top: 50%;\
  765. width: 400px;\
  766. margin-left:-200px;\
  767. margin-top:-150px;\
  768. border:1px solid black;\
  769. background-color:white;\
  770. color:black;\
  771. padding:20px;\
  772. font-size:13px;\
  773. text-align:left;\
  774. z-index:501;';
  775. d.innerHTML = '<h1 style="font-size:16pt">\
  776. <a href="http://kathack.com/" style="color:blue;text-decoration:none;">\
  777. Katamari!</a></h1>\
  778. <button style="position:absolute;top:0;right:0;">X</button>\
  779. <p>Controls: Hold down <b><select id="mouseb">\
  780. <option value="0">Left-Click</option>\
  781. <option value="2" selected="selected">Right-Click</option>\
  782. <option value="-5">Touch</option>\
  783. </select></b> to control the ball!</p>\
  784. <div><label>Background Music? \
  785. <input id="bgmusicc" type="checkbox" checked="checked" /></label></div>\
  786. <div style="text-align:right; color:gray;">\
  787. <label>Katamari Color: <select id="khcolor">\
  788. <option value="#ff0000" style="background-color:#ff0000;color:#ff0000"> r </option>\
  789. <option value="#00ff00" style="background-color:#00ff00;color:#00ff00"> g </option>\
  790. <option value="#0000ff" style="background-color:#0000ff;color:#0000ff"> b </option>\
  791. <option selected="selected" value="#7D26CD" style="background-color:#7D26CD;color:#7D26CD"> p \
  792. </option></select></label><br />\
  793. <label title="Lower this if the game gets slow.">\
  794. Max Attached Objects: <select id="maxAtt">\
  795. <option>25</option>\
  796. <option>50</option>\
  797. <option selected="selected">75</option>\
  798. <option>100</option>\
  799. <option>9000</option></select></label><br />\
  800. <label title="How much to grow when an object is picked up.">\
  801. Growth Speed: <input id="vol_mult" type="text" size="6" value="1.0" />\
  802. </label><br />\
  803. <label title="Bigger objects require a bigger katamari to pick up.">\
  804. Realistic Pickups? <input id="checkv" type="checkbox" checked="checked" />\
  805. </label></div>\
  806. <p id="loadingp">Loading!</p>';
  807. gameDiv.appendChild(d);
  808. d.getElementsByTagName('button')[0].addEventListener('click', function () {
  809. gameDiv.removeChild(d);
  810. }, true);
  811. return d;
  812. }
  813.  
  814. function main() {
  815. var gameDiv, checkInterval, stickyNodes, popup;
  816.  
  817. gameDiv = document.createElement('div');
  818. gameDiv.khIgnore = true;
  819. document.body.appendChild(gameDiv);
  820. popup = buildPopup(gameDiv);
  821.  
  822. /* setTimeout so that the popup displays before we freeze. */
  823. setTimeout(function () {
  824. var i, len, el;
  825. window.khNodes.addWords(document.body);
  826. for (i = 0, len = document.body.childNodes.length; i < len; i++) {
  827. el = document.body.childNodes[i];
  828. window.khNodes.addTagNames(el, [
  829. 'button', 'canvas', 'iframe', 'img', 'input', 'select',
  830. 'textarea'
  831. ]);
  832. }
  833.  
  834. checkInterval = setInterval(function () {
  835. if (window.jQuery) {
  836. clearInterval(checkInterval);
  837. whenAllLoaded(gameDiv, popup, window.khNodes);
  838. }
  839. }, 100);
  840. }, 0);
  841. }
  842.  
  843. if (!window.noMain) {
  844. main();
  845. }
  846.  
  847.  
  848. })();