diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..653c25a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+db_info.txt
diff --git a/css/index.css b/css/index.css
index 79f4003..6f6de3b 100644
--- a/css/index.css
+++ b/css/index.css
@@ -1,50 +1,50 @@
-@font-face {
- font-family: 'Share Tech';
- font-style: normal;
- font-weight: 400;
- src: local('Share Tech Regular'), local('ShareTech-Regular'), url(../fonts/ShareTech.woff2) format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-* {
- color: white;
- font-family: 'Share Tech';
- font-size: 1.05em;
-}
-
-body {
- background-image: url("../images/bg.jpg");
- background-size: cover;
-}
-
-h1 {
- font-size: 3em;
- margin: 20px;
- text-shadow: 3px 2px rgb(153, 145, 175);
- text-align: center;
-}
-
-button {
- color: black;
- width: 100%;
-}
-
-a {
- color: lightcyan;
- text-decoration: none;
-}
-
-#actions {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- grid-gap: 20px;
- margin: 80px auto;
- width: 700px;
- justify-items: left;
-}
-
-.play {
- text-align: center;
- text-shadow: 2px 1px rgb(153, 145, 175);
- font-size: 1.5em;
-}
+@font-face {
+ font-family: 'Share Tech';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Share Tech Regular'), local('ShareTech-Regular'), url(../fonts/ShareTech.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+* {
+ color: white;
+ font-family: 'Share Tech';
+ font-size: 1.05em;
+}
+
+body {
+ background-image: url("../images/bg.jpg");
+ background-size: cover;
+}
+
+h1 {
+ font-size: 3em;
+ margin: 20px;
+ text-shadow: 3px 2px rgb(153, 145, 175);
+ text-align: center;
+}
+
+button {
+ color: black;
+ width: 100%;
+}
+
+a {
+ color: lightcyan;
+ text-decoration: none;
+}
+
+#actions {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ grid-gap: 20px;
+ margin: 80px auto;
+ width: 700px;
+ justify-items: left;
+}
+
+.play {
+ text-align: center;
+ text-shadow: 2px 1px rgb(153, 145, 175);
+ font-size: 1.5em;
+}
diff --git a/css/webtris.css b/css/webtris.css
index 8dd8d1d..8e292e8 100644
--- a/css/webtris.css
+++ b/css/webtris.css
@@ -1,89 +1,89 @@
-@font-face {
- font-family: 'Share Tech';
- font-style: normal;
- font-weight: 400;
- src: local('Share Tech Regular'), local('ShareTech-Regular'), url(../fonts/ShareTech.woff2) format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-@font-face {
- font-family: 'Share Tech Mono';
- font-style: normal;
- font-weight: 400;
- src: local('Share Tech Mono Regular'), local('ShareTechMono-Regular'), url(../fonts/ShareTechMono.woff2) format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-* {
- padding: 0;
- margin: 0;
- color: white;
- font-family: 'Share Tech';
-}
-
-body {
- background-image: url("../images/bg.jpg");
- background-size: cover;
-}
-
-h1 {
- font-size: 3em;
- margin: 40px 20px 20px 20px;
- text-shadow: 3px 2px rgb(153, 145, 175);
- text-align: center;
-}
-
-canvas {
- display: block;
- flex-shrink: 0;
-}
-
-.flex-columns {
- display: flex;
- flex-direction: row;
- justify-content: center;
- margin: auto;
-}
-
-.flex-space {
- flex-grow: 2;
-}
-
-.flex-rows {
- display: flex;
- flex-direction: column;
- margin: 5% 2%;
- height: 400px;
- width: 150px;
-}
-
-#hold {
- width: 120px;
-}
-
-#stats {
- display: flex;
- flex-direction: row;
- margin: 10% 0;
- font-size: 1.2em;
-}
-
-#stats-names {
- font-family: 'Share Tech';
- text-align: left;
-}
-
-#stats-values {
- text-align: right;
- font-family: 'Share Tech Mono';
- min-width: 90px;
-}
-
-#matrix {
- margin: 5% 2%;
- border: 0.5px solid grey;
-}
-
-#next {
- width: 120px;
- margin: 5% 2%;
+@font-face {
+ font-family: 'Share Tech';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Share Tech Regular'), local('ShareTech-Regular'), url(../fonts/ShareTech.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+@font-face {
+ font-family: 'Share Tech Mono';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Share Tech Mono Regular'), local('ShareTechMono-Regular'), url(../fonts/ShareTechMono.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+* {
+ padding: 0;
+ margin: 0;
+ color: white;
+ font-family: 'Share Tech';
+}
+
+body {
+ background-image: url("../images/bg.jpg");
+ background-size: cover;
+}
+
+h1 {
+ font-size: 3em;
+ margin: 40px 20px 20px 20px;
+ text-shadow: 3px 2px rgb(153, 145, 175);
+ text-align: center;
+}
+
+canvas {
+ display: block;
+ flex-shrink: 0;
+}
+
+.flex-columns {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ margin: auto;
+}
+
+.flex-space {
+ flex-grow: 2;
+}
+
+.flex-rows {
+ display: flex;
+ flex-direction: column;
+ margin: 5% 2%;
+ height: 400px;
+ width: 150px;
+}
+
+#hold {
+ width: 120px;
+}
+
+#stats {
+ display: flex;
+ flex-direction: row;
+ margin: 10% 0;
+ font-size: 1.2em;
+}
+
+#stats-names {
+ font-family: 'Share Tech';
+ text-align: left;
+}
+
+#stats-values {
+ text-align: right;
+ font-family: 'Share Tech Mono';
+ min-width: 90px;
+}
+
+#matrix {
+ margin: 5% 2%;
+ border: 0.5px solid grey;
+}
+
+#next {
+ width: 120px;
+ margin: 5% 2%;
}
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index 841f7da..0000000
--- a/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- Webtris
-
-
-
-
- WEBTRIS
-
-
-
-
-
\ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..7a05154
--- /dev/null
+++ b/index.php
@@ -0,0 +1,36 @@
+
+
+
+
+ Webtris
+
+
+
+
+ WEBTRIS
+
+ "GAUCHE",
+ "moveRight" => "DROITE",
+ "softDrop" => "CHUTE LENTE",
+ "hardDrop" => "CHUTE RAPIDE",
+ "rotateCW" => "ROTATION HORAIRE",
+ "rotateCCW:" => "ROTATE INVERSE",
+ "hold" => "GARDE",
+ "pause" => "PAUSE",
+ );
+ foreach($actionLabel as $action => $label)
+ {
+ echo "
$label
\n";
+ echo "
\n";
+ echo " \n";
+ echo " \n";
+ }
+?>
+
+
+
+
\ No newline at end of file
diff --git a/js/excanvas.js b/js/excanvas.js
index 52829f8..b9f1cb2 100644
--- a/js/excanvas.js
+++ b/js/excanvas.js
@@ -1,1414 +1,1414 @@
-// Copyright 2006 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-// Known Issues:
-//
-// * Patterns only support repeat.
-// * Radial gradient are not implemented. The VML version of these look very
-// different from the canvas one.
-// * Clipping paths are not implemented.
-// * Coordsize. The width and height attribute have higher priority than the
-// width and height style values which isn't correct.
-// * Painting mode isn't implemented.
-// * Canvas width/height should is using content-box by default. IE in
-// Quirks mode will draw the canvas using border-box. Either change your
-// doctype to HTML5
-// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
-// or use Box Sizing Behavior from WebFX
-// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
-// * Non uniform scaling does not correctly scale strokes.
-// * Optimize. There is always room for speed improvements.
-
-// Only add this code if we do not already have a canvas implementation
-if (!document.createElement('canvas').getContext) {
-
-(function() {
-
- // alias some functions to make (compiled) code shorter
- var m = Math;
- var mr = m.round;
- var ms = m.sin;
- var mc = m.cos;
- var abs = m.abs;
- var sqrt = m.sqrt;
-
- // this is used for sub pixel precision
- var Z = 10;
- var Z2 = Z / 2;
-
- /**
- * This funtion is assigned to the elements as element.getContext().
- * @this {HTMLElement}
- * @return {CanvasRenderingContext2D_}
- */
- function getContext() {
- return this.context_ ||
- (this.context_ = new CanvasRenderingContext2D_(this));
- }
-
- var slice = Array.prototype.slice;
-
- /**
- * Binds a function to an object. The returned function will always use the
- * passed in {@code obj} as {@code this}.
- *
- * Example:
- *
- * g = bind(f, obj, a, b)
- * g(c, d) // will do f.call(obj, a, b, c, d)
- *
- * @param {Function} f The function to bind the object to
- * @param {Object} obj The object that should act as this when the function
- * is called
- * @param {*} var_args Rest arguments that will be used as the initial
- * arguments when the function is called
- * @return {Function} A new function that has bound this
- */
- function bind(f, obj, var_args) {
- var a = slice.call(arguments, 2);
- return function() {
- return f.apply(obj, a.concat(slice.call(arguments)));
- };
- }
-
- function encodeHtmlAttribute(s) {
- return String(s).replace(/&/g, '&').replace(/"/g, '"');
- }
-
- function addNamespace(doc, prefix, urn) {
- if (!doc.namespaces[prefix]) {
- doc.namespaces.add(prefix, urn, '#default#VML');
- }
- }
-
- function addNamespacesAndStylesheet(doc) {
- addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
- addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
-
- // Setup default CSS. Only add one style sheet per document
- if (!doc.styleSheets['ex_canvas_']) {
- var ss = doc.createStyleSheet();
- ss.owningElement.id = 'ex_canvas_';
- ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
- // default size is 300x150 in Gecko and Opera
- 'text-align:left;width:300px;height:150px}';
- }
- }
-
- // Add namespaces and stylesheet at startup.
- addNamespacesAndStylesheet(document);
-
- var G_vmlCanvasManager_ = {
- init: function(opt_doc) {
- var doc = opt_doc || document;
- // Create a dummy element so that IE will allow canvas elements to be
- // recognized.
- doc.createElement('canvas');
- doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
- },
-
- init_: function(doc) {
- // find all canvas elements
- var els = doc.getElementsByTagName('canvas');
- for (var i = 0; i < els.length; i++) {
- this.initElement(els[i]);
- }
- },
-
- /**
- * Public initializes a canvas element so that it can be used as canvas
- * element from now on. This is called automatically before the page is
- * loaded but if you are creating elements using createElement you need to
- * make sure this is called on the element.
- * @param {HTMLElement} el The canvas element to initialize.
- * @return {HTMLElement} the element that was created.
- */
- initElement: function(el) {
- if (!el.getContext) {
- el.getContext = getContext;
-
- // Add namespaces and stylesheet to document of the element.
- addNamespacesAndStylesheet(el.ownerDocument);
-
- // Remove fallback content. There is no way to hide text nodes so we
- // just remove all childNodes. We could hide all elements and remove
- // text nodes but who really cares about the fallback content.
- el.innerHTML = '';
-
- // do not use inline function because that will leak memory
- el.attachEvent('onpropertychange', onPropertyChange);
- el.attachEvent('onresize', onResize);
-
- var attrs = el.attributes;
- if (attrs.width && attrs.width.specified) {
- // TODO: use runtimeStyle and coordsize
- // el.getContext().setWidth_(attrs.width.nodeValue);
- el.style.width = attrs.width.nodeValue + 'px';
- } else {
- el.width = el.clientWidth;
- }
- if (attrs.height && attrs.height.specified) {
- // TODO: use runtimeStyle and coordsize
- // el.getContext().setHeight_(attrs.height.nodeValue);
- el.style.height = attrs.height.nodeValue + 'px';
- } else {
- el.height = el.clientHeight;
- }
- //el.getContext().setCoordsize_()
- }
- return el;
- }
- };
-
- function onPropertyChange(e) {
- var el = e.srcElement;
-
- switch (e.propertyName) {
- case 'width':
- el.getContext().clearRect();
- el.style.width = el.attributes.width.nodeValue + 'px';
- // In IE8 this does not trigger onresize.
- el.firstChild.style.width = el.clientWidth + 'px';
- break;
- case 'height':
- el.getContext().clearRect();
- el.style.height = el.attributes.height.nodeValue + 'px';
- el.firstChild.style.height = el.clientHeight + 'px';
- break;
- }
- }
-
- function onResize(e) {
- var el = e.srcElement;
- if (el.firstChild) {
- el.firstChild.style.width = el.clientWidth + 'px';
- el.firstChild.style.height = el.clientHeight + 'px';
- }
- }
-
- G_vmlCanvasManager_.init();
-
- // precompute "00" to "FF"
- var decToHex = [];
- for (var i = 0; i < 16; i++) {
- for (var j = 0; j < 16; j++) {
- decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
- }
- }
-
- function createMatrixIdentity() {
- return [
- [1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]
- ];
- }
-
- function matrixMultiply(m1, m2) {
- var result = createMatrixIdentity();
-
- for (var x = 0; x < 3; x++) {
- for (var y = 0; y < 3; y++) {
- var sum = 0;
-
- for (var z = 0; z < 3; z++) {
- sum += m1[x][z] * m2[z][y];
- }
-
- result[x][y] = sum;
- }
- }
- return result;
- }
-
- function copyState(o1, o2) {
- o2.fillStyle = o1.fillStyle;
- o2.lineCap = o1.lineCap;
- o2.lineJoin = o1.lineJoin;
- o2.lineWidth = o1.lineWidth;
- o2.miterLimit = o1.miterLimit;
- o2.shadowBlur = o1.shadowBlur;
- o2.shadowColor = o1.shadowColor;
- o2.shadowOffsetX = o1.shadowOffsetX;
- o2.shadowOffsetY = o1.shadowOffsetY;
- o2.strokeStyle = o1.strokeStyle;
- o2.globalAlpha = o1.globalAlpha;
- o2.font = o1.font;
- o2.textAlign = o1.textAlign;
- o2.textBaseline = o1.textBaseline;
- o2.arcScaleX_ = o1.arcScaleX_;
- o2.arcScaleY_ = o1.arcScaleY_;
- o2.lineScale_ = o1.lineScale_;
- }
-
- var colorData = {
- aliceblue: '#F0F8FF',
- antiquewhite: '#FAEBD7',
- aquamarine: '#7FFFD4',
- azure: '#F0FFFF',
- beige: '#F5F5DC',
- bisque: '#FFE4C4',
- black: '#000000',
- blanchedalmond: '#FFEBCD',
- blueviolet: '#8A2BE2',
- brown: '#A52A2A',
- burlywood: '#DEB887',
- cadetblue: '#5F9EA0',
- chartreuse: '#7FFF00',
- chocolate: '#D2691E',
- coral: '#FF7F50',
- cornflowerblue: '#6495ED',
- cornsilk: '#FFF8DC',
- crimson: '#DC143C',
- cyan: '#00FFFF',
- darkblue: '#00008B',
- darkcyan: '#008B8B',
- darkgoldenrod: '#B8860B',
- darkgray: '#A9A9A9',
- darkgreen: '#006400',
- darkgrey: '#A9A9A9',
- darkkhaki: '#BDB76B',
- darkmagenta: '#8B008B',
- darkolivegreen: '#556B2F',
- darkorange: '#FF8C00',
- darkorchid: '#9932CC',
- darkred: '#8B0000',
- darksalmon: '#E9967A',
- darkseagreen: '#8FBC8F',
- darkslateblue: '#483D8B',
- darkslategray: '#2F4F4F',
- darkslategrey: '#2F4F4F',
- darkturquoise: '#00CED1',
- darkviolet: '#9400D3',
- deeppink: '#FF1493',
- deepskyblue: '#00BFFF',
- dimgray: '#696969',
- dimgrey: '#696969',
- dodgerblue: '#1E90FF',
- firebrick: '#B22222',
- floralwhite: '#FFFAF0',
- forestgreen: '#228B22',
- gainsboro: '#DCDCDC',
- ghostwhite: '#F8F8FF',
- gold: '#FFD700',
- goldenrod: '#DAA520',
- grey: '#808080',
- greenyellow: '#ADFF2F',
- honeydew: '#F0FFF0',
- hotpink: '#FF69B4',
- indianred: '#CD5C5C',
- indigo: '#4B0082',
- ivory: '#FFFFF0',
- khaki: '#F0E68C',
- lavender: '#E6E6FA',
- lavenderblush: '#FFF0F5',
- lawngreen: '#7CFC00',
- lemonchiffon: '#FFFACD',
- lightblue: '#ADD8E6',
- lightcoral: '#F08080',
- lightcyan: '#E0FFFF',
- lightgoldenrodyellow: '#FAFAD2',
- lightgreen: '#90EE90',
- lightgrey: '#D3D3D3',
- lightpink: '#FFB6C1',
- lightsalmon: '#FFA07A',
- lightseagreen: '#20B2AA',
- lightskyblue: '#87CEFA',
- lightslategray: '#778899',
- lightslategrey: '#778899',
- lightsteelblue: '#B0C4DE',
- lightyellow: '#FFFFE0',
- limegreen: '#32CD32',
- linen: '#FAF0E6',
- magenta: '#FF00FF',
- mediumaquamarine: '#66CDAA',
- mediumblue: '#0000CD',
- mediumorchid: '#BA55D3',
- mediumpurple: '#9370DB',
- mediumseagreen: '#3CB371',
- mediumslateblue: '#7B68EE',
- mediumspringgreen: '#00FA9A',
- mediumturquoise: '#48D1CC',
- mediumvioletred: '#C71585',
- midnightblue: '#191970',
- mintcream: '#F5FFFA',
- mistyrose: '#FFE4E1',
- moccasin: '#FFE4B5',
- navajowhite: '#FFDEAD',
- oldlace: '#FDF5E6',
- olivedrab: '#6B8E23',
- orange: '#FFA500',
- orangered: '#FF4500',
- orchid: '#DA70D6',
- palegoldenrod: '#EEE8AA',
- palegreen: '#98FB98',
- paleturquoise: '#AFEEEE',
- palevioletred: '#DB7093',
- papayawhip: '#FFEFD5',
- peachpuff: '#FFDAB9',
- peru: '#CD853F',
- pink: '#FFC0CB',
- plum: '#DDA0DD',
- powderblue: '#B0E0E6',
- rosybrown: '#BC8F8F',
- royalblue: '#4169E1',
- saddlebrown: '#8B4513',
- salmon: '#FA8072',
- sandybrown: '#F4A460',
- seagreen: '#2E8B57',
- seashell: '#FFF5EE',
- sienna: '#A0522D',
- skyblue: '#87CEEB',
- slateblue: '#6A5ACD',
- slategray: '#708090',
- slategrey: '#708090',
- snow: '#FFFAFA',
- springgreen: '#00FF7F',
- steelblue: '#4682B4',
- tan: '#D2B48C',
- thistle: '#D8BFD8',
- tomato: '#FF6347',
- turquoise: '#40E0D0',
- violet: '#EE82EE',
- wheat: '#F5DEB3',
- whitesmoke: '#F5F5F5',
- yellowgreen: '#9ACD32'
- };
-
-
- function getRgbHslContent(styleString) {
- var start = styleString.indexOf('(', 3);
- var end = styleString.indexOf(')', start + 1);
- var parts = styleString.substring(start + 1, end).split(',');
- // add alpha if needed
- if (parts.length != 4 || styleString.charAt(3) != 'a') {
- parts[3] = 1;
- }
- return parts;
- }
-
- function percent(s) {
- return parseFloat(s) / 100;
- }
-
- function clamp(v, min, max) {
- return Math.min(max, Math.max(min, v));
- }
-
- function hslToRgb(parts){
- var r, g, b, h, s, l;
- h = parseFloat(parts[0]) / 360 % 360;
- if (h < 0)
- h++;
- s = clamp(percent(parts[1]), 0, 1);
- l = clamp(percent(parts[2]), 0, 1);
- if (s == 0) {
- r = g = b = l; // achromatic
- } else {
- var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
- var p = 2 * l - q;
- r = hueToRgb(p, q, h + 1 / 3);
- g = hueToRgb(p, q, h);
- b = hueToRgb(p, q, h - 1 / 3);
- }
-
- return '#' + decToHex[Math.floor(r * 255)] +
- decToHex[Math.floor(g * 255)] +
- decToHex[Math.floor(b * 255)];
- }
-
- function hueToRgb(m1, m2, h) {
- if (h < 0)
- h++;
- if (h > 1)
- h--;
-
- if (6 * h < 1)
- return m1 + (m2 - m1) * 6 * h;
- else if (2 * h < 1)
- return m2;
- else if (3 * h < 2)
- return m1 + (m2 - m1) * (2 / 3 - h) * 6;
- else
- return m1;
- }
-
- var processStyleCache = {};
-
- function processStyle(styleString) {
- if (styleString in processStyleCache) {
- return processStyleCache[styleString];
- }
-
- var str, alpha = 1;
-
- styleString = String(styleString);
- if (styleString.charAt(0) == '#') {
- str = styleString;
- } else if (/^rgb/.test(styleString)) {
- var parts = getRgbHslContent(styleString);
- var str = '#', n;
- for (var i = 0; i < 3; i++) {
- if (parts[i].indexOf('%') != -1) {
- n = Math.floor(percent(parts[i]) * 255);
- } else {
- n = +parts[i];
- }
- str += decToHex[clamp(n, 0, 255)];
- }
- alpha = +parts[3];
- } else if (/^hsl/.test(styleString)) {
- var parts = getRgbHslContent(styleString);
- str = hslToRgb(parts);
- alpha = parts[3];
- } else {
- str = colorData[styleString] || styleString;
- }
- return processStyleCache[styleString] = {color: str, alpha: alpha};
- }
-
- var DEFAULT_STYLE = {
- style: 'normal',
- variant: 'normal',
- weight: 'normal',
- size: 10,
- family: 'sans-serif'
- };
-
- // Internal text style cache
- var fontStyleCache = {};
-
- function processFontStyle(styleString) {
- if (fontStyleCache[styleString]) {
- return fontStyleCache[styleString];
- }
-
- var el = document.createElement('div');
- var style = el.style;
- try {
- style.font = styleString;
- } catch (ex) {
- // Ignore failures to set to invalid font.
- }
-
- return fontStyleCache[styleString] = {
- style: style.fontStyle || DEFAULT_STYLE.style,
- variant: style.fontVariant || DEFAULT_STYLE.variant,
- weight: style.fontWeight || DEFAULT_STYLE.weight,
- size: style.fontSize || DEFAULT_STYLE.size,
- family: style.fontFamily || DEFAULT_STYLE.family
- };
- }
-
- function getComputedStyle(style, element) {
- var computedStyle = {};
-
- for (var p in style) {
- computedStyle[p] = style[p];
- }
-
- // Compute the size
- var canvasFontSize = parseFloat(element.currentStyle.fontSize),
- fontSize = parseFloat(style.size);
-
- if (typeof style.size == 'number') {
- computedStyle.size = style.size;
- } else if (style.size.indexOf('px') != -1) {
- computedStyle.size = fontSize;
- } else if (style.size.indexOf('em') != -1) {
- computedStyle.size = canvasFontSize * fontSize;
- } else if(style.size.indexOf('%') != -1) {
- computedStyle.size = (canvasFontSize / 100) * fontSize;
- } else if (style.size.indexOf('pt') != -1) {
- computedStyle.size = fontSize / .75;
- } else {
- computedStyle.size = canvasFontSize;
- }
-
- // Different scaling between normal text and VML text. This was found using
- // trial and error to get the same size as non VML text.
- computedStyle.size *= 0.981;
-
- return computedStyle;
- }
-
- function buildStyle(style) {
- return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
- style.size + 'px ' + style.family;
- }
-
- var lineCapMap = {
- 'butt': 'flat',
- 'round': 'round'
- };
-
- function processLineCap(lineCap) {
- return lineCapMap[lineCap] || 'square';
- }
-
- /**
- * This class implements CanvasRenderingContext2D interface as described by
- * the WHATWG.
- * @param {HTMLElement} canvasElement The element that the 2D context should
- * be associated with
- */
- function CanvasRenderingContext2D_(canvasElement) {
- this.m_ = createMatrixIdentity();
-
- this.mStack_ = [];
- this.aStack_ = [];
- this.currentPath_ = [];
-
- // Canvas context properties
- this.strokeStyle = '#000';
- this.fillStyle = '#000';
-
- this.lineWidth = 1;
- this.lineJoin = 'miter';
- this.lineCap = 'butt';
- this.miterLimit = Z * 1;
- this.globalAlpha = 1;
- this.font = '10px sans-serif';
- this.textAlign = 'left';
- this.textBaseline = 'alphabetic';
- this.canvas = canvasElement;
-
- var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
- canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
- var el = canvasElement.ownerDocument.createElement('div');
- el.style.cssText = cssText;
- canvasElement.appendChild(el);
-
- var overlayEl = el.cloneNode(false);
- // Use a non transparent background.
- overlayEl.style.backgroundColor = 'red';
- overlayEl.style.filter = 'alpha(opacity=0)';
- canvasElement.appendChild(overlayEl);
-
- this.element_ = el;
- this.arcScaleX_ = 1;
- this.arcScaleY_ = 1;
- this.lineScale_ = 1;
- }
-
- var contextPrototype = CanvasRenderingContext2D_.prototype;
- contextPrototype.clearRect = function() {
- if (this.textMeasureEl_) {
- this.textMeasureEl_.removeNode(true);
- this.textMeasureEl_ = null;
- }
- this.element_.innerHTML = '';
- };
-
- contextPrototype.beginPath = function() {
- // TODO: Branch current matrix so that save/restore has no effect
- // as per safari docs.
- this.currentPath_ = [];
- };
-
- contextPrototype.moveTo = function(aX, aY) {
- var p = getCoords(this, aX, aY);
- this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
- this.currentX_ = p.x;
- this.currentY_ = p.y;
- };
-
- contextPrototype.lineTo = function(aX, aY) {
- var p = getCoords(this, aX, aY);
- this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
-
- this.currentX_ = p.x;
- this.currentY_ = p.y;
- };
-
- contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
- aCP2x, aCP2y,
- aX, aY) {
- var p = getCoords(this, aX, aY);
- var cp1 = getCoords(this, aCP1x, aCP1y);
- var cp2 = getCoords(this, aCP2x, aCP2y);
- bezierCurveTo(this, cp1, cp2, p);
- };
-
- // Helper function that takes the already fixed cordinates.
- function bezierCurveTo(self, cp1, cp2, p) {
- self.currentPath_.push({
- type: 'bezierCurveTo',
- cp1x: cp1.x,
- cp1y: cp1.y,
- cp2x: cp2.x,
- cp2y: cp2.y,
- x: p.x,
- y: p.y
- });
- self.currentX_ = p.x;
- self.currentY_ = p.y;
- }
-
- contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
- // the following is lifted almost directly from
- // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
-
- var cp = getCoords(this, aCPx, aCPy);
- var p = getCoords(this, aX, aY);
-
- var cp1 = {
- x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
- y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
- };
- var cp2 = {
- x: cp1.x + (p.x - this.currentX_) / 3.0,
- y: cp1.y + (p.y - this.currentY_) / 3.0
- };
-
- bezierCurveTo(this, cp1, cp2, p);
- };
-
- contextPrototype.arc = function(aX, aY, aRadius,
- aStartAngle, aEndAngle, aClockwise) {
- aRadius *= Z;
- var arcType = aClockwise ? 'at' : 'wa';
-
- var xStart = aX + mc(aStartAngle) * aRadius - Z2;
- var yStart = aY + ms(aStartAngle) * aRadius - Z2;
-
- var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
- var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
-
- // IE won't render arches drawn counter clockwise if xStart == xEnd.
- if (xStart == xEnd && !aClockwise) {
- xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
- // that can be represented in binary
- }
-
- var p = getCoords(this, aX, aY);
- var pStart = getCoords(this, xStart, yStart);
- var pEnd = getCoords(this, xEnd, yEnd);
-
- this.currentPath_.push({type: arcType,
- x: p.x,
- y: p.y,
- radius: aRadius,
- xStart: pStart.x,
- yStart: pStart.y,
- xEnd: pEnd.x,
- yEnd: pEnd.y});
-
- };
-
- contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- };
-
- contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
- var oldPath = this.currentPath_;
- this.beginPath();
-
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- this.stroke();
-
- this.currentPath_ = oldPath;
- };
-
- contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
- var oldPath = this.currentPath_;
- this.beginPath();
-
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- this.fill();
-
- this.currentPath_ = oldPath;
- };
-
- contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
- var gradient = new CanvasGradient_('gradient');
- gradient.x0_ = aX0;
- gradient.y0_ = aY0;
- gradient.x1_ = aX1;
- gradient.y1_ = aY1;
- return gradient;
- };
-
- contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
- aX1, aY1, aR1) {
- var gradient = new CanvasGradient_('gradientradial');
- gradient.x0_ = aX0;
- gradient.y0_ = aY0;
- gradient.r0_ = aR0;
- gradient.x1_ = aX1;
- gradient.y1_ = aY1;
- gradient.r1_ = aR1;
- return gradient;
- };
-
- contextPrototype.drawImage = function(image, var_args) {
- var dx, dy, dw, dh, sx, sy, sw, sh;
-
- // to find the original width we overide the width and height
- var oldRuntimeWidth = image.runtimeStyle.width;
- var oldRuntimeHeight = image.runtimeStyle.height;
- image.runtimeStyle.width = 'auto';
- image.runtimeStyle.height = 'auto';
-
- // get the original size
- var w = image.width;
- var h = image.height;
-
- // and remove overides
- image.runtimeStyle.width = oldRuntimeWidth;
- image.runtimeStyle.height = oldRuntimeHeight;
-
- if (arguments.length == 3) {
- dx = arguments[1];
- dy = arguments[2];
- sx = sy = 0;
- sw = dw = w;
- sh = dh = h;
- } else if (arguments.length == 5) {
- dx = arguments[1];
- dy = arguments[2];
- dw = arguments[3];
- dh = arguments[4];
- sx = sy = 0;
- sw = w;
- sh = h;
- } else if (arguments.length == 9) {
- sx = arguments[1];
- sy = arguments[2];
- sw = arguments[3];
- sh = arguments[4];
- dx = arguments[5];
- dy = arguments[6];
- dw = arguments[7];
- dh = arguments[8];
- } else {
- throw Error('Invalid number of arguments');
- }
-
- var d = getCoords(this, dx, dy);
-
- var w2 = sw / 2;
- var h2 = sh / 2;
-
- var vmlStr = [];
-
- var W = 10;
- var H = 10;
-
- // For some reason that I've now forgotten, using divs didn't work
- vmlStr.push(' ' ,
- ' ',
- ' ');
-
- this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
- };
-
- contextPrototype.stroke = function(aFill) {
- var lineStr = [];
- var lineOpen = false;
-
- var W = 10;
- var H = 10;
-
- lineStr.push('');
-
- if (!aFill) {
- appendStroke(this, lineStr);
- } else {
- appendFill(this, lineStr, min, max);
- }
-
- lineStr.push(' ');
-
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
-
- function appendStroke(ctx, lineStr) {
- var a = processStyle(ctx.strokeStyle);
- var color = a.color;
- var opacity = a.alpha * ctx.globalAlpha;
- var lineWidth = ctx.lineScale_ * ctx.lineWidth;
-
- // VML cannot correctly render a line if the width is less than 1px.
- // In that case, we dilute the color to make the line look thinner.
- if (lineWidth < 1) {
- opacity *= lineWidth;
- }
-
- lineStr.push(
- ' '
- );
- }
-
- function appendFill(ctx, lineStr, min, max) {
- var fillStyle = ctx.fillStyle;
- var arcScaleX = ctx.arcScaleX_;
- var arcScaleY = ctx.arcScaleY_;
- var width = max.x - min.x;
- var height = max.y - min.y;
- if (fillStyle instanceof CanvasGradient_) {
- // TODO: Gradients transformed with the transformation matrix.
- var angle = 0;
- var focus = {x: 0, y: 0};
-
- // additional offset
- var shift = 0;
- // scale factor for offset
- var expansion = 1;
-
- if (fillStyle.type_ == 'gradient') {
- var x0 = fillStyle.x0_ / arcScaleX;
- var y0 = fillStyle.y0_ / arcScaleY;
- var x1 = fillStyle.x1_ / arcScaleX;
- var y1 = fillStyle.y1_ / arcScaleY;
- var p0 = getCoords(ctx, x0, y0);
- var p1 = getCoords(ctx, x1, y1);
- var dx = p1.x - p0.x;
- var dy = p1.y - p0.y;
- angle = Math.atan2(dx, dy) * 180 / Math.PI;
-
- // The angle should be a non-negative number.
- if (angle < 0) {
- angle += 360;
- }
-
- // Very small angles produce an unexpected result because they are
- // converted to a scientific notation string.
- if (angle < 1e-6) {
- angle = 0;
- }
- } else {
- var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
- focus = {
- x: (p0.x - min.x) / width,
- y: (p0.y - min.y) / height
- };
-
- width /= arcScaleX * Z;
- height /= arcScaleY * Z;
- var dimension = m.max(width, height);
- shift = 2 * fillStyle.r0_ / dimension;
- expansion = 2 * fillStyle.r1_ / dimension - shift;
- }
-
- // We need to sort the color stops in ascending order by offset,
- // otherwise IE won't interpret it correctly.
- var stops = fillStyle.colors_;
- stops.sort(function(cs1, cs2) {
- return cs1.offset - cs2.offset;
- });
-
- var length = stops.length;
- var color1 = stops[0].color;
- var color2 = stops[length - 1].color;
- var opacity1 = stops[0].alpha * ctx.globalAlpha;
- var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
-
- var colors = [];
- for (var i = 0; i < length; i++) {
- var stop = stops[i];
- colors.push(stop.offset * expansion + shift + ' ' + stop.color);
- }
-
- // When colors attribute is used, the meanings of opacity and o:opacity2
- // are reversed.
- lineStr.push(' ');
- } else if (fillStyle instanceof CanvasPattern_) {
- if (width && height) {
- var deltaLeft = -min.x;
- var deltaTop = -min.y;
- lineStr.push(' ');
- }
- } else {
- var a = processStyle(ctx.fillStyle);
- var color = a.color;
- var opacity = a.alpha * ctx.globalAlpha;
- lineStr.push(' ');
- }
- }
-
- contextPrototype.fill = function() {
- this.stroke(true);
- };
-
- contextPrototype.closePath = function() {
- this.currentPath_.push({type: 'close'});
- };
-
- function getCoords(ctx, aX, aY) {
- var m = ctx.m_;
- return {
- x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
- y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
- };
- };
-
- contextPrototype.save = function() {
- var o = {};
- copyState(this, o);
- this.aStack_.push(o);
- this.mStack_.push(this.m_);
- this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
- };
-
- contextPrototype.restore = function() {
- if (this.aStack_.length) {
- copyState(this.aStack_.pop(), this);
- this.m_ = this.mStack_.pop();
- }
- };
-
- function matrixIsFinite(m) {
- return isFinite(m[0][0]) && isFinite(m[0][1]) &&
- isFinite(m[1][0]) && isFinite(m[1][1]) &&
- isFinite(m[2][0]) && isFinite(m[2][1]);
- }
-
- function setM(ctx, m, updateLineScale) {
- if (!matrixIsFinite(m)) {
- return;
- }
- ctx.m_ = m;
-
- if (updateLineScale) {
- // Get the line scale.
- // Determinant of this.m_ means how much the area is enlarged by the
- // transformation. So its square root can be used as a scale factor
- // for width.
- var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
- ctx.lineScale_ = sqrt(abs(det));
- }
- }
-
- contextPrototype.translate = function(aX, aY) {
- var m1 = [
- [1, 0, 0],
- [0, 1, 0],
- [aX, aY, 1]
- ];
-
- setM(this, matrixMultiply(m1, this.m_), false);
- };
-
- contextPrototype.rotate = function(aRot) {
- var c = mc(aRot);
- var s = ms(aRot);
-
- var m1 = [
- [c, s, 0],
- [-s, c, 0],
- [0, 0, 1]
- ];
-
- setM(this, matrixMultiply(m1, this.m_), false);
- };
-
- contextPrototype.scale = function(aX, aY) {
- this.arcScaleX_ *= aX;
- this.arcScaleY_ *= aY;
- var m1 = [
- [aX, 0, 0],
- [0, aY, 0],
- [0, 0, 1]
- ];
-
- setM(this, matrixMultiply(m1, this.m_), true);
- };
-
- contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
- var m1 = [
- [m11, m12, 0],
- [m21, m22, 0],
- [dx, dy, 1]
- ];
-
- setM(this, matrixMultiply(m1, this.m_), true);
- };
-
- contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
- var m = [
- [m11, m12, 0],
- [m21, m22, 0],
- [dx, dy, 1]
- ];
-
- setM(this, m, true);
- };
-
- /**
- * The text drawing function.
- * The maxWidth argument isn't taken in account, since no browser supports
- * it yet.
- */
- contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
- var m = this.m_,
- delta = 1000,
- left = 0,
- right = delta,
- offset = {x: 0, y: 0},
- lineStr = [];
-
- var fontStyle = getComputedStyle(processFontStyle(this.font),
- this.element_);
-
- var fontStyleString = buildStyle(fontStyle);
-
- var elementStyle = this.element_.currentStyle;
- var textAlign = this.textAlign.toLowerCase();
- switch (textAlign) {
- case 'left':
- case 'center':
- case 'right':
- break;
- case 'end':
- textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
- break;
- case 'start':
- textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
- break;
- default:
- textAlign = 'left';
- }
-
- // 1.75 is an arbitrary number, as there is no info about the text baseline
- switch (this.textBaseline) {
- case 'hanging':
- case 'top':
- offset.y = fontStyle.size / 1.75;
- break;
- case 'middle':
- break;
- default:
- case null:
- case 'alphabetic':
- case 'ideographic':
- case 'bottom':
- offset.y = -fontStyle.size / 2.25;
- break;
- }
-
- switch(textAlign) {
- case 'right':
- left = delta;
- right = 0.05;
- break;
- case 'center':
- left = right = delta / 2;
- break;
- }
-
- var d = getCoords(this, x + offset.x, y + offset.y);
-
- lineStr.push('');
-
- if (stroke) {
- appendStroke(this, lineStr);
- } else {
- // TODO: Fix the min and max params.
- appendFill(this, lineStr, {x: -left, y: 0},
- {x: right, y: fontStyle.size});
- }
-
- var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
- m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
-
- var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
-
- lineStr.push(' ',
- ' ',
- ' ');
-
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
-
- contextPrototype.fillText = function(text, x, y, maxWidth) {
- this.drawText_(text, x, y, maxWidth, false);
- };
-
- contextPrototype.strokeText = function(text, x, y, maxWidth) {
- this.drawText_(text, x, y, maxWidth, true);
- };
-
- contextPrototype.measureText = function(text) {
- if (!this.textMeasureEl_) {
- var s = ' ';
- this.element_.insertAdjacentHTML('beforeEnd', s);
- this.textMeasureEl_ = this.element_.lastChild;
- }
- var doc = this.element_.ownerDocument;
- this.textMeasureEl_.innerHTML = '';
- this.textMeasureEl_.style.font = this.font;
- // Don't use innerHTML or innerText because they allow markup/whitespace.
- this.textMeasureEl_.appendChild(doc.createTextNode(text));
- return {width: this.textMeasureEl_.offsetWidth};
- };
-
- /******** STUBS ********/
- contextPrototype.clip = function() {
- // TODO: Implement
- };
-
- contextPrototype.arcTo = function() {
- // TODO: Implement
- };
-
- contextPrototype.createPattern = function(image, repetition) {
- return new CanvasPattern_(image, repetition);
- };
-
- // Gradient / Pattern Stubs
- function CanvasGradient_(aType) {
- this.type_ = aType;
- this.x0_ = 0;
- this.y0_ = 0;
- this.r0_ = 0;
- this.x1_ = 0;
- this.y1_ = 0;
- this.r1_ = 0;
- this.colors_ = [];
- }
-
- CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
- aColor = processStyle(aColor);
- this.colors_.push({offset: aOffset,
- color: aColor.color,
- alpha: aColor.alpha});
- };
-
- function CanvasPattern_(image, repetition) {
- assertImageIsValid(image);
- switch (repetition) {
- case 'repeat':
- case null:
- case '':
- this.repetition_ = 'repeat';
- break
- case 'repeat-x':
- case 'repeat-y':
- case 'no-repeat':
- this.repetition_ = repetition;
- break;
- default:
- throwException('SYNTAX_ERR');
- }
-
- this.src_ = image.src;
- this.width_ = image.width;
- this.height_ = image.height;
- }
-
- function throwException(s) {
- throw new DOMException_(s);
- }
-
- function assertImageIsValid(img) {
- if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
- throwException('TYPE_MISMATCH_ERR');
- }
- if (img.readyState != 'complete') {
- throwException('INVALID_STATE_ERR');
- }
- }
-
- function DOMException_(s) {
- this.code = this[s];
- this.message = s +': DOM Exception ' + this.code;
- }
- var p = DOMException_.prototype = new Error;
- p.INDEX_SIZE_ERR = 1;
- p.DOMSTRING_SIZE_ERR = 2;
- p.HIERARCHY_REQUEST_ERR = 3;
- p.WRONG_DOCUMENT_ERR = 4;
- p.INVALID_CHARACTER_ERR = 5;
- p.NO_DATA_ALLOWED_ERR = 6;
- p.NO_MODIFICATION_ALLOWED_ERR = 7;
- p.NOT_FOUND_ERR = 8;
- p.NOT_SUPPORTED_ERR = 9;
- p.INUSE_ATTRIBUTE_ERR = 10;
- p.INVALID_STATE_ERR = 11;
- p.SYNTAX_ERR = 12;
- p.INVALID_MODIFICATION_ERR = 13;
- p.NAMESPACE_ERR = 14;
- p.INVALID_ACCESS_ERR = 15;
- p.VALIDATION_ERR = 16;
- p.TYPE_MISMATCH_ERR = 17;
-
- // set up externs
- G_vmlCanvasManager = G_vmlCanvasManager_;
- CanvasRenderingContext2D = CanvasRenderingContext2D_;
- CanvasGradient = CanvasGradient_;
- CanvasPattern = CanvasPattern_;
- DOMException = DOMException_;
-})();
-
-} // if
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ /**
+ * This funtion is assigned to the elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&').replace(/"/g, '"');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ },
+
+ init_: function(doc) {
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+ el.getContext = getContext;
+
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
+ break;
+ case 'height':
+ el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var decToHex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ cyan: '#00FFFF',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orange: '#FFA500',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
+ function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
+
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} canvasElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(canvasElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = getCoords(this, dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' ' ,
+ ' ',
+ ' ');
+
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var lineStr = [];
+ var lineOpen = false;
+
+ var W = 10;
+ var H = 10;
+
+ lineStr.push('');
+
+ if (!aFill) {
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
+
+ lineStr.push(' ');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ ' '
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push(' ');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push(' ');
+ }
+ } else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ lineStr.push(' ');
+ }
+ }
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ };
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ };
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
+ };
+
+ function matrixIsFinite(m) {
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push(' ',
+ ' ',
+ ' ');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = ' ';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+ DOMException = DOMException_;
+})();
+
+} // if
diff --git a/js/index.js b/js/index.js
index 745aa6d..7e615c0 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,53 +1,39 @@
-const actionLabel = [
- {name: "moveLeft", label: "GAUCHE"},
- {name: "moveRight", label: "DROITE"},
- {name: "softDrop", label: "CHUTE LENTE"},
- {name: "hardDrop", label: "CHUTE RAPIDE"},
- {name: "rotateCW", label: "ROTATION HORAIRE"},
- {name: "rotateCCW:", label: "ROTATE INVERSE"},
- {name: "hold", label: "GARDE"},
- {name: "pause", label: "PAUSE"}
-]
-const actionsDefaultKeys = {
- moveLeft: "ArrowLeft",
- moveRight: "ArrowRight",
- softDrop: "ArrowDown",
- hardDrop: " ",
- rotateCW: "ArrowUp",
- rotateCCW: "z",
- hold: "c",
- pause: "Escape",
-}
-var selectedButton = null
-var selectedAction = ""
-
-function getKey(action) {
- key = localStorage.getItem(action) || actionsDefaultKeys[action]
- if (key == ' ')
- return "Space"
- else
- return key
-}
-
-function changeKey(button, action) {
- button.innerHTML = "Touche ?"
- selectedButton = button
- selectedAction = action
- button.blur()
-}
-
-function keyUpHandler(e) {
- if (selectedButton) {
- localStorage.setItem(selectedAction, e.key)
- selectedButton.innerHTML = (e.key == " ") ? "Space" : e.key
- selectedButton = null
- }
-}
-
-window.onload = function() {
- document.getElementById("actions").innerHTML = actionLabel.map(action => `${action.label}
-${getKey(action.name)}
-`).join("\n")
-
- addEventListener("keyup", keyUpHandler, false)
+const actionsDefaultKeys = {
+ moveLeft: "ArrowLeft",
+ moveRight: "ArrowRight",
+ softDrop: "ArrowDown",
+ hardDrop: " ",
+ rotateCW: "ArrowUp",
+ rotateCCW: "z",
+ hold: "c",
+ pause: "Escape",
+}
+var selectedButton = null
+var selectedAction = ""
+
+function getKey(action) {
+ key = localStorage.getItem(action) || actionsDefaultKeys[action]
+ if (key == ' ') key = "Space"
+ document.open()
+ document.write(key)
+ document.close()
+}
+
+function changeKey(button, action) {
+ button.innerHTML = "Touche ?"
+ selectedButton = button
+ selectedAction = action
+ button.blur()
+}
+
+function keyUpHandler(e) {
+ if (selectedButton) {
+ localStorage.setItem(selectedAction, e.key)
+ selectedButton.innerHTML = (e.key == " ") ? "Space" : e.key
+ selectedButton = null
+ }
+}
+
+window.onload = function() {
+ addEventListener("keyup", keyUpHandler, false)
}
\ No newline at end of file
diff --git a/js/webtris.js b/js/webtris.js
index 2dd5ba8..42f36fc 100644
--- a/js/webtris.js
+++ b/js/webtris.js
@@ -1,783 +1,783 @@
-Array.prototype.add = function(other) {
- return this.map((x, i) => x + other[i])
-}
-
-Array.prototype.mul = function(k) {
- return this.map(x => k * x)
-}
-
-Array.prototype.translate = function(vector) {
- return this.map(pos => pos.add(vector))
-}
-
-Array.prototype.rotate = function(spin) {
- return [-spin*this[1], spin*this[0]]
-}
-
-Array.prototype.pick = function() {
- return this.splice(Math.floor(Math.random()*this.length), 1)[0]
-}
-
-
-const MINO_SIZE = 20
-const NEXT_PIECES = 5
-const HOLD_ROWS = 6
-const HOLD_COLUMNS = 6
-const MATRIX_ROWS = 20
-const MATRIX_COLUMNS = 10
-const NEXT_ROWS = 20
-const NEXT_COLUMNS = 6
-const HELD_PIECE_POSITION = [2, 2]
-const FALLING_PIECE_POSITION = [4, 0]
-const NEXT_PIECES_POSITIONS = Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+2])
-const LOCK_DELAY = 500
-const FALL_DELAY = 1000
-const AUTOREPEAT_DELAY = 250
-const AUTOREPEAT_PERIOD = 10
-const ANIMATION_DELAY = 100
-const TEMP_TEXTS_DELAY = 700
-const MOVEMENT = {
- LEFT: [-1, 0],
- RIGHT: [ 1, 0],
- DOWN: [ 0, 1]
-}
-const SPIN = {
- CW: 1,
- CCW: -1
-}
-const T_SPIN = {
- NONE: "",
- MINI: "MINI\nT-SPIN",
- T_SPIN: "T-SPIN"
-}
-const T_SLOT = {
- A: 0,
- B: 1,
- C: 3,
- D: 2
-}
-const T_SLOT_POS = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
-const SCORES = [
- {linesClearedName: "", "": 0, "MINI\nT-SPIN": 1, "T-SPIN": 4},
- {linesClearedName: "SINGLE", "": 1, "MINI\nT-SPIN": 2, "T-SPIN": 8},
- {linesClearedName: "DOUBLE", "": 3, "T-SPIN": 12},
- {linesClearedName: "TRIPLE", "": 5, "T-SPIN": 16},
- {linesClearedName: "TETRIS", "": 8},
-]
-const REPEATABLE_ACTIONS = [moveLeft, moveRight, softDrop]
-const STATE = {
- PLAYING: "PLAYING",
- PAUSED: "PAUSE",
- GAME_OVER: "GAME OVER"
-}
-const actionsDefaultKeys = {
- moveLeft: "ArrowLeft",
- moveRight: "ArrowRight",
- softDrop: "ArrowDown",
- hardDrop: " ",
- rotateCW: "ArrowUp",
- rotateCCW: "z",
- hold: "c",
- pause: "Escape",
-}
-var actions = {}
-
-
-class Scheduler {
- constructor() {
- this.intervalTasks = new Map()
- this.timeoutTasks = new Map()
- }
-
- setInterval(func, delay, ...args) {
- this.intervalTasks.set(func, window.setInterval(func, delay, ...args))
- }
-
- setTimeout(func, delay, ...args) {
- this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
- }
-
- clearInterval(func) {
- if (this.intervalTasks.has(func)) {
- window.clearInterval(this.intervalTasks.get(func))
- this.intervalTasks.delete(func)
- }
- }
-
- clearTimeout(func) {
- if (this.timeoutTasks.has(func)) {
- window.clearTimeout(this.timeoutTasks.get(func))
- this.timeoutTasks.delete(func)
- }
- }
-}
-
-
-shapes = []
-class Tetromino {
- constructor(position=null, shape=null) {
- this.pos = position
- this.orientation = 0
- this.rotatedLast = false
- this.rotationPoint5Used = false
- this.holdEnabled = true
- this.locked = false
- this.srs = {}
- this.srs[SPIN.CW] = [
- [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
- [[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
- [[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
- [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],
- ]
- this.srs[SPIN.CCW] = [
- [[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
- [[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
- [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
- [[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
- ]
- if (shape)
- this.shape = shape
- else {
- if (!shapes.length)
- shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
- this.shape = shapes.pick()
- }
- switch(this.shape) {
- case 'I':
- this.color = "cyan"
- this.lightColor = "rgb(234, 250, 250)"
- this.ghostColor = "rgba(234, 250, 250, 0.5)"
- this.minoesPos = [[-1, 0], [0, 0], [1, 0], [2, 0]]
- this.srs[SPIN.CW] = [
- [[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
- [[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
- [[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
- [[ 0, 1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
- ]
- this.srs[SPIN.CCW] = [
- [[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
- [[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
- [[ 0, -1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
- [[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
- ]
- break
- case 'J':
- this.color = "blue"
- this.lightColor = "rgb(230, 240, 255)"
- this.ghostColor = "rgba(230, 240, 255, 0.5)"
- this.minoesPos = [[-1, -1], [-1, 0], [0, 0], [1, 0]]
- break
- case 'L':
- this.color = "orange"
- this.lightColor = "rgb(255, 224, 204)"
- this.ghostColor = "rgba(255, 224, 204, 0.5)"
- this.minoesPos = [[-1, 0], [0, 0], [1, 0], [1, -1]]
- break
- case 'O':
- this.color = "yellow"
- this.lightColor = "rgb(255, 255, 230)"
- this.ghostColor = "rgba(255, 255, 230, 0.5)"
- this.minoesPos = [[0, 0], [1, 0], [0, -1], [1, -1]]
- this.srs[SPIN.CW] = [[]]
- this.srs[SPIN.CCW] = [[]]
- break
- case 'S':
- this.color = "green"
- this.lightColor = "rgb(236, 255, 230)"
- this.ghostColor = "rgba(236, 255, 230, 0.5)"
- this.minoesPos = [[-1, 0], [0, 0], [0, -1], [1, -1]]
- break
- case 'T':
- this.color = "magenta"
- this.lightColor= "rgb(242, 230, 255)"
- this.ghostColor = "rgba(242, 230, 255, 0.5)"
- this.minoesPos = [[-1, 0], [0, 0], [1, 0], [0, -1]]
- break
- case 'Z':
- this.color = "red"
- this.lightColor = "rgb(255, 230, 230)"
- this.ghostColor = "rgba(255, 230, 230, 0.5)"
- this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
- break
- }
- }
-
- get minoesAbsPos() {
- return this.minoesPos.translate(this.pos)
- }
-
- draw(context, ghost_pos=[0, 0]) {
- const color = this.locked ? this.lightColor : this.color
- if (ghost_pos[1]) {
- context.save()
- context.shadowColor = this.ghostColor
- context.shadowOffsetX = 0
- context.shadowOffsetY = (ghost_pos[1]-this.pos[1]) * MINO_SIZE
- context.shadowBlur = 3
- this.minoesAbsPos.forEach(pos => drawMino(context, pos, color))
- context.restore()
- }
- this.minoesAbsPos.forEach(pos => drawMino(context, pos, this.lightColor, color, ghost_pos))
- }
-}
-
-
-function drawMino(context, pos, color1, color2=null, spotlight=[0, 0]) {
- if (color2) {
- var center = pos.add([0.5, 0.5])
- spotlight = spotlight.add([0.5, 0.5])
- var glint = spotlight.mul(0.1).add(center.mul(0.9)).mul(MINO_SIZE)
- const gradient = context.createRadialGradient(
- ...glint, 2, ...glint.add([6, 4]), 2*MINO_SIZE
- )
- gradient.addColorStop(0, color1)
- gradient.addColorStop(1, color2)
- context.fillStyle = gradient
- } else
- context.fillStyle = color1
- var topLeft = pos.mul(MINO_SIZE)
- context.fillRect(...topLeft, MINO_SIZE, MINO_SIZE)
- context.lineWidth = 0.5
- context.strokeStyle = "white"
- context.strokeRect(...topLeft, MINO_SIZE, MINO_SIZE)
-}
-
-
-class HoldQueue {
- constructor() {
- this.context = document.getElementById("hold").getContext("2d")
- this.piece = null
- this.width = HOLD_COLUMNS*MINO_SIZE
- this.height = HOLD_ROWS*MINO_SIZE
- }
-
- draw() {
- this.context.clearRect(0, 0, this.width, this.height)
- if (state != STATE.PAUSED) {
- if (this.piece)
- this.piece.draw(this.context)
- }
- }
-}
-
-
-timeFormat = new Intl.DateTimeFormat("fr-FR", {
- minute: "2-digit", second: "2-digit", hourCycle: "h24", timeZone: "UTC"
-}).format
-
-
-class Stats {
- constructor () {
- this.div = document.getElementById("stats-values")
- this._score = 0
- this.highScore = localStorage.getItem('highScore') || 0
- this.goal = 0
- this.linesCleared = 0
- this.startTime = Date.now()
- this.pauseTime = 0
- this.combo = -1
- this.lockDelay = LOCK_DELAY
- this.fallDelay = FALL_DELAY
- }
-
- get score() {
- return this._score
- }
-
- set score(score) {
- this._score = score
- if (score > this.highScore)
- this.highScore = score
- }
-
- newLevel(level=null) {
- if (level)
- this.level = level
- else
- this.level++
- printTempTexts(["LEVEL", this.level])
- this.goal += 5 * this.level
- if (this.level <= 20)
- this.fallDelay = 1000 * Math.pow(0.8 - ((this.level - 1) * 0.007), this.level - 1)
- if (this.level > 15)
- this.lockDelay = 500 * Math.pow(0.9, this.level - 15)
- }
-
- locksDown(tSpin, linesCleared) {
- var patternName = []
- var patternScore = 0
- var combo_score = 0
-
- if (tSpin)
- patternName.push(tSpin)
- if (linesCleared) {
- patternName.push(SCORES[linesCleared].linesClearedName)
- this.combo++
- } else
- this.combo = -1
-
- if (linesCleared || tSpin) {
- this.linesCleared += linesCleared
- patternScore = SCORES[linesCleared][tSpin]
- this.goal -= patternScore
- patternScore *= 100 * this.level
- patternName = patternName.join("\n")
- }
- if (this.combo >= 1)
- combo_score = (linesCleared == 1 ? 20 : 50) * this.combo * this.level
-
- this.score += patternScore + combo_score
-
- if (patternScore)
- printTempTexts([patternName, patternScore])
- if (combo_score)
- printTempTexts([`COMBO x${this.combo}`, combo_score])
- }
-
- print() {
- this.div.innerHTML = `${this.score}
- ${this.highScore}
- ${timeFormat(Date.now() - this.startTime)}
- ${this.level}
- ${this.goal}
- ${this.linesCleared}`
- }
-}
-
-
-class Matrix {
- constructor() {
- this.context = document.getElementById("matrix").getContext("2d")
- this.context.textAlign = "center"
- this.context.textBaseline = "center"
- this.context.font = "3vw 'Share Tech', sans-serif"
- this.cells = Array.from(Array(MATRIX_ROWS+3), row => Array(MATRIX_COLUMNS))
- this.width = MATRIX_COLUMNS*MINO_SIZE
- this.height = MATRIX_ROWS*MINO_SIZE
- this.centerX = this.width / 2
- this.centerY = this.height / 2
- this.piece = null
- this.trail = {
- minoesPos: [],
- height: 0,
- gradient: null
- }
- this.linesCleared = []
- }
-
- cellIsOccupied(x, y) {
- return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.cells[y+3][x] : true
- }
-
- spaceToMove(minoesAbsPos) {
- return !minoesAbsPos.some(pos => this.cellIsOccupied(...pos))
- }
-
- draw() {
- this.context.clearRect(0, 0, this.width, this.height)
-
- // grid
- this.context.strokeStyle = "rgba(128, 128, 128, 128)"
- this.context.lineWidth = 0.5
- this.context.beginPath()
- for (var x = 0; x <= this.width; x += MINO_SIZE) {
- this.context.moveTo(x, 0);
- this.context.lineTo(x, this.height);
- }
- for (var y = 0; y <= this.height; y += MINO_SIZE) {
- this.context.moveTo(0, y);
- this.context.lineTo(this.width, y);
- }
- this.context.stroke()
-
- if (state != STATE.PAUSED) {
- // ghost position
- for (var ghost_pos = Array.from(this.piece.pos); this.spaceToMove(this.piece.minoesPos.translate(ghost_pos)); ghost_pos[1]++) {}
- ghost_pos[1]--
-
- // locked minoes
- this.cells.slice(3).forEach((row, y) => row.forEach((colors, x) => {
- if (colors) drawMino(this.context, [x, y], ...colors, ghost_pos)
- }))
-
- // trail
- if (this.trail.height) {
- this.context.fillStyle = this.trail.gradient
- this.trail.minoesPos.forEach(topLeft => {
- this.context.fillRect(...topLeft, MINO_SIZE, this.trail.height)
- })
- }
-
- // falling piece
- if (this.piece)
- this.piece.draw(this.context, ghost_pos)
-
- // Lines cleared
- if (this.linesCleared.length) {
- this.context.save()
- this.context.shadowColor = "white"
- this.context.shadowOffsetX = 0
- this.context.shadowOffsetY = 0
- this.context.shadowBlur = 5
- this.context.fillStyle = "white"
- this.linesCleared.forEach(y => this.context.fillRect(0, y, this.width, MINO_SIZE))
- this.context.restore()
- }
- }
-
- // text
- var texts = []
- switch(state) {
- case STATE.PLAYING:
- if (tempTexts.length)
- texts = tempTexts[0]
- break
- case STATE.PAUSED:
- texts = ["PAUSED"]
- break
- case STATE.GAME_OVER:
- texts = ["GAME", "OVER"]
- }
- if (texts.length) {
- this.context.save()
- this.context.shadowColor = "black"
- this.context.shadowOffsetX = 1
- this.context.shadowOffsetY = 1
- this.context.shadowBlur = 2
- this.context.fillStyle = "white"
- if (texts.length == 1)
- this.context.fillText(texts[0], this.centerX, this.centerY)
- else {
- this.context.fillText(texts[0], this.centerX, this.centerY - 20)
- this.context.fillText(texts[1], this.centerX, this.centerY + 20)
- }
- this.context.restore()
- }
- }
-}
-
-
-class NextQueue {
- constructor() {
- this.context = document.getElementById("next").getContext("2d")
- this.pieces = Array.from({length: NEXT_PIECES}, (v, k) => new Tetromino(NEXT_PIECES_POSITIONS[k]))
- this.width = NEXT_COLUMNS*MINO_SIZE
- this.height = NEXT_ROWS*MINO_SIZE
- }
-
- draw() {
- this.context.clearRect(0, 0, this.width, this.height)
- if (state != STATE.PAUSED) {
- this.pieces.forEach(piece => piece.draw(this.context))
- }
- }
-}
-
-
-function newLevel(startLevel) {
- stats.newLevel(startLevel)
- generationPhase()
-}
-
-function generationPhase(held_piece=null) {
- if (!held_piece) {
- matrix.piece = nextQueue.pieces.shift()
- nextQueue.pieces.push(new Tetromino())
- nextQueue.pieces.forEach((piece, i) => piece.pos = NEXT_PIECES_POSITIONS[i])
- }
- matrix.piece.pos = FALLING_PIECE_POSITION
- if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos)))
- fallingPhase()
- else
- gameOver()
-}
-
-function fallingPhase() {
- scheduler.clearTimeout(lockPhase)
- scheduler.clearTimeout(locksDown)
- matrix.piece.locked = false
- scheduler.setTimeout(lockPhase, stats.fallDelay)
-}
-
-function lockPhase() {
- if (!move(MOVEMENT.DOWN)) {
- matrix.piece.locked = true
- if (!scheduler.timeoutTasks.has(locksDown))
- scheduler.setTimeout(locksDown, stats.lockDelay)
- }
- requestAnimationFrame(draw)
-}
-
-function move(movement, testMinoesPos=matrix.piece.minoesPos) {
- const testPos = matrix.piece.pos.add(movement)
- if (matrix.spaceToMove(testMinoesPos.translate(testPos))) {
- matrix.piece.pos = testPos
- matrix.piece.minoesPos = testMinoesPos
- if (movement != MOVEMENT.DOWN)
- matrix.piece.rotatedLast = false
- if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos.add(MOVEMENT.DOWN))))
- fallingPhase()
- else {
- matrix.piece.locked = true
- scheduler.clearTimeout(locksDown)
- scheduler.setTimeout(locksDown, stats.lockDelay)
- }
- return true
- } else {
- return false
- }
-}
-
-function rotate(spin) {
- const test_minoes_pos = matrix.piece.minoesPos.map(pos => pos.rotate(spin))
- rotationPoint = 1
- for (const movement of matrix.piece.srs[spin][matrix.piece.orientation]) {
- if (move(movement, test_minoes_pos)) {
- matrix.piece.orientation = (matrix.piece.orientation + spin + 4) % 4
- matrix.piece.rotatedLast = true
- if (rotationPoint == 5)
- matrix.piece.rotationPoint5Used = true
- return true
- }
- rotationPoint++
- }
- return false
-}
-
-function locksDown(){
- scheduler.clearInterval(move)
- if (matrix.piece.minoesAbsPos.every(pos => pos.y < 0))
- game_over()
- else {
- matrix.piece.minoesAbsPos.forEach(pos => matrix.cells[pos[1]+3][pos[0]] = [matrix.piece.lightColor, matrix.piece.color])
-
- // T-Spin detection
- var tSpin = T_SPIN.NONE
- if (matrix.piece.rotatedLast && matrix.piece.shape == "T") {
- const tSlots = T_SLOT_POS.translate(matrix.piece.pos).map(pos => matrix.cellIsOccupied(...pos)),
- a = tSlots[(matrix.piece.orientation+T_SLOT.A)%4],
- b = tSlots[(matrix.piece.orientation+T_SLOT.B)%4],
- c = tSlots[(matrix.piece.orientation+T_SLOT.C)%4],
- d = tSlots[(matrix.piece.orientation+T_SLOT.D)%4]
- if (a && b && (c || d))
- tSpin = T_SPIN.T_SPIN
- else if (c && d && (a || b))
- tSpin = matrix.piece.rotationPoint5Used ? T_SPIN.T_SPIN : T_SPIN.MINI
- }
-
- // Complete lines
- matrix.linesCleared = []
- matrix.cells.forEach((row, y) => {
- if (row.filter(mino => mino.length).length == MATRIX_COLUMNS) {
- matrix.cells.splice(y, 1)
- matrix.cells.unshift(Array(MATRIX_COLUMNS))
- matrix.linesCleared.push((y-3) * MINO_SIZE)
- }
- })
-
- stats.locksDown(tSpin, matrix.linesCleared.length)
- requestAnimationFrame(draw)
- scheduler.setTimeout(clearLinesCleared, ANIMATION_DELAY)
-
- if (stats.goal <= 0)
- newLevel()
- else
- generationPhase()
- }
-}
-
-function clearLinesCleared() {
- matrix.linesCleared = []
- requestAnimationFrame(draw)
-}
-
-function gameOver() {
- state = STATE.GAME_OVER
- scheduler.clearTimeout(lockPhase)
- scheduler.clearTimeout(locksDown)
- scheduler.clearInterval(clock)
- requestAnimationFrame(draw)
-
- if (stats.score == stats.highScore) {
- alert("Bravo ! Vous avez battu votre précédent record.")
- localStorage.setItem('highScore', stats.highScore)
- }
-}
-
-function autorepeat() {
- if (actionsToRepeat.length) {
- actionsToRepeat[0]()
- requestAnimationFrame(draw)
- if (scheduler.timeoutTasks.has(autorepeat)) {
- scheduler.clearTimeout(autorepeat)
- scheduler.setInterval(autorepeat, AUTOREPEAT_PERIOD)
- }
- } else {
- scheduler.clearTimeout(autorepeat)
- scheduler.clearInterval(autorepeat)
- }
-}
-
-function keyDownHandler(e) {
- if (!pressedKeys.has(e.key)) {
- pressedKeys.add(e.key)
- if (e.key in actions[state]) {
- action = actions[state][e.key]
- action()
- requestAnimationFrame(draw)
- if (REPEATABLE_ACTIONS.includes(action)) {
- actionsToRepeat.unshift(action)
- scheduler.clearTimeout(autorepeat)
- scheduler.clearInterval(autorepeat)
- if (action == softDrop)
- scheduler.setInterval(autorepeat, stats.fallDelay / 20)
- else
- scheduler.setTimeout(autorepeat, AUTOREPEAT_DELAY)
- }
- }
- }
-}
-
-function keyUpHandler(e) {
- pressedKeys.delete(e.key)
- if (e.key in actions[state]) {
- action = actions[state][e.key]
- if (actionsToRepeat.includes(action)) {
- actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1)
- if (!actionsToRepeat.length) {
- scheduler.clearTimeout(autorepeat)
- scheduler.clearInterval(autorepeat)
- }
- }
- }
-}
-
-function moveLeft() {
- move(MOVEMENT.LEFT);
-}
-
-function moveRight() {
- move(MOVEMENT.RIGHT)
-}
-
-function softDrop() {
- if (move(MOVEMENT.DOWN))
- stats.score++
-}
-
-function hardDrop() {
- scheduler.clearTimeout(lockPhase)
- scheduler.clearTimeout(locksDown)
- matrix.trail.minoesPos = Array.from(matrix.piece.minoesAbsPos).map(pos => pos.mul(MINO_SIZE))
- for (matrix.trail.height=0; move(MOVEMENT.DOWN); matrix.trail.height += MINO_SIZE) {
- stats.score += 2
- }
- locksDown()
- matrix.trail.gradient = matrix.context.createLinearGradient(0, 0, 0, matrix.trail.height)
- matrix.trail.gradient.addColorStop(0,"rgba(255, 255, 255, 0)")
- matrix.trail.gradient.addColorStop(1, matrix.piece.ghostColor)
- scheduler.setTimeout(clearTrail, ANIMATION_DELAY)
-}
-
-function clearTrail() {
- matrix.trail.height = 0
- requestAnimationFrame(draw)
-}
-
-function rotateCW() {
- rotate(SPIN.CW)
-}
-
-function rotateCCW() {
- rotate(SPIN.CCW)
-}
-
-function hold() {
- if (this.matrix.piece.holdEnabled) {
- scheduler.clearInterval(move)
- scheduler.clearInterval(locksDown)
- var shape = this.matrix.piece.shape
- this.matrix.piece = this.holdQueue.piece
- this.holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape)
- this.holdQueue.piece.holdEnabled = false
- this.generationPhase(this.matrix.piece)
- }
-}
-
-function pause() {
- state = STATE.PAUSED
- stats.pauseTime = Date.now() - stats.startTime
- scheduler.clearTimeout(lockPhase)
- scheduler.clearTimeout(locksDown)
- scheduler.clearTimeout(autorepeat)
- scheduler.clearInterval(clock)
-}
-
-function resume() {
- state = STATE.PLAYING
- stats.startTime = Date.now() - stats.pauseTime
- scheduler.setTimeout(lockPhase, stats.fallDelay)
- if (matrix.piece.locked)
- scheduler.setTimeout(locksDown, stats.lockDelay)
- requestAnimationFrame(draw)
- scheduler.setInterval(clock, 1000)
-}
-
-function printTempTexts(texts) {
- tempTexts.push(texts)
- if (!scheduler.intervalTasks.has(delTempTexts))
- scheduler.setInterval(delTempTexts, TEMP_TEXTS_DELAY)
-}
-
-function delTempTexts(self) {
- if (tempTexts.length)
- tempTexts.shift()
- else
- scheduler.clearInterval(delTempTexts)
-}
-
-function clock() {
- stats.print()
-}
-
-function draw() {
- holdQueue.draw()
- stats.print()
- matrix.draw()
- nextQueue.draw()
-}
-
-function getKey(action) {
- return localStorage.getItem(action) || actionsDefaultKeys[action]
-}
-
-window.onload = function() {
- tempTexts = []
-
- holdQueue = new HoldQueue()
- stats = new Stats()
- matrix = new Matrix()
- nextQueue = new NextQueue()
-
- actions[STATE.PLAYING] = {}
- actions[STATE.PLAYING][getKey("moveLeft")] = moveLeft
- actions[STATE.PLAYING][getKey("moveRight")] = moveRight
- actions[STATE.PLAYING][getKey("softDrop")] = softDrop
- actions[STATE.PLAYING][getKey("hardDrop")] = hardDrop
- actions[STATE.PLAYING][getKey("rotateCW")] = rotateCW
- actions[STATE.PLAYING][getKey("rotateCCW")] = rotateCCW
- actions[STATE.PLAYING][getKey("hold")] = hold
- actions[STATE.PLAYING][getKey("pause")] = pause
- actions[STATE.PAUSED] = {}
- actions[STATE.PAUSED][getKey("pause")] = resume
- actions[STATE.GAME_OVER] = {}
- pressedKeys = new Set()
- actionsToRepeat = []
- addEventListener("keydown", keyDownHandler, false)
- addEventListener("keyup", keyUpHandler, false)
-
- state = STATE.PLAYING
- scheduler = new Scheduler()
- scheduler.setInterval(clock, 1000)
- this.newLevel(1)
+Array.prototype.add = function(other) {
+ return this.map((x, i) => x + other[i])
+}
+
+Array.prototype.mul = function(k) {
+ return this.map(x => k * x)
+}
+
+Array.prototype.translate = function(vector) {
+ return this.map(pos => pos.add(vector))
+}
+
+Array.prototype.rotate = function(spin) {
+ return [-spin*this[1], spin*this[0]]
+}
+
+Array.prototype.pick = function() {
+ return this.splice(Math.floor(Math.random()*this.length), 1)[0]
+}
+
+
+const MINO_SIZE = 20
+const NEXT_PIECES = 5
+const HOLD_ROWS = 6
+const HOLD_COLUMNS = 6
+const MATRIX_ROWS = 20
+const MATRIX_COLUMNS = 10
+const NEXT_ROWS = 20
+const NEXT_COLUMNS = 6
+const HELD_PIECE_POSITION = [2, 2]
+const FALLING_PIECE_POSITION = [4, 0]
+const NEXT_PIECES_POSITIONS = Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+2])
+const LOCK_DELAY = 500
+const FALL_DELAY = 1000
+const AUTOREPEAT_DELAY = 250
+const AUTOREPEAT_PERIOD = 10
+const ANIMATION_DELAY = 100
+const TEMP_TEXTS_DELAY = 700
+const MOVEMENT = {
+ LEFT: [-1, 0],
+ RIGHT: [ 1, 0],
+ DOWN: [ 0, 1]
+}
+const SPIN = {
+ CW: 1,
+ CCW: -1
+}
+const T_SPIN = {
+ NONE: "",
+ MINI: "MINI\nT-SPIN",
+ T_SPIN: "T-SPIN"
+}
+const T_SLOT = {
+ A: 0,
+ B: 1,
+ C: 3,
+ D: 2
+}
+const T_SLOT_POS = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
+const SCORES = [
+ {linesClearedName: "", "": 0, "MINI\nT-SPIN": 1, "T-SPIN": 4},
+ {linesClearedName: "SINGLE", "": 1, "MINI\nT-SPIN": 2, "T-SPIN": 8},
+ {linesClearedName: "DOUBLE", "": 3, "T-SPIN": 12},
+ {linesClearedName: "TRIPLE", "": 5, "T-SPIN": 16},
+ {linesClearedName: "TETRIS", "": 8},
+]
+const REPEATABLE_ACTIONS = [moveLeft, moveRight, softDrop]
+const STATE = {
+ PLAYING: "PLAYING",
+ PAUSED: "PAUSE",
+ GAME_OVER: "GAME OVER"
+}
+const actionsDefaultKeys = {
+ moveLeft: "ArrowLeft",
+ moveRight: "ArrowRight",
+ softDrop: "ArrowDown",
+ hardDrop: " ",
+ rotateCW: "ArrowUp",
+ rotateCCW: "z",
+ hold: "c",
+ pause: "Escape",
+}
+var actions = {}
+
+
+class Scheduler {
+ constructor() {
+ this.intervalTasks = new Map()
+ this.timeoutTasks = new Map()
+ }
+
+ setInterval(func, delay, ...args) {
+ this.intervalTasks.set(func, window.setInterval(func, delay, ...args))
+ }
+
+ setTimeout(func, delay, ...args) {
+ this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
+ }
+
+ clearInterval(func) {
+ if (this.intervalTasks.has(func)) {
+ window.clearInterval(this.intervalTasks.get(func))
+ this.intervalTasks.delete(func)
+ }
+ }
+
+ clearTimeout(func) {
+ if (this.timeoutTasks.has(func)) {
+ window.clearTimeout(this.timeoutTasks.get(func))
+ this.timeoutTasks.delete(func)
+ }
+ }
+}
+
+
+shapes = []
+class Tetromino {
+ constructor(position=null, shape=null) {
+ this.pos = position
+ this.orientation = 0
+ this.rotatedLast = false
+ this.rotationPoint5Used = false
+ this.holdEnabled = true
+ this.locked = false
+ this.srs = {}
+ this.srs[SPIN.CW] = [
+ [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
+ [[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
+ [[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
+ [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],
+ ]
+ this.srs[SPIN.CCW] = [
+ [[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
+ [[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
+ [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
+ [[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
+ ]
+ if (shape)
+ this.shape = shape
+ else {
+ if (!shapes.length)
+ shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
+ this.shape = shapes.pick()
+ }
+ switch(this.shape) {
+ case 'I':
+ this.color = "cyan"
+ this.lightColor = "rgb(234, 250, 250)"
+ this.ghostColor = "rgba(234, 250, 250, 0.5)"
+ this.minoesPos = [[-1, 0], [0, 0], [1, 0], [2, 0]]
+ this.srs[SPIN.CW] = [
+ [[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
+ [[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
+ [[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
+ [[ 0, 1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
+ ]
+ this.srs[SPIN.CCW] = [
+ [[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
+ [[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
+ [[ 0, -1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
+ [[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
+ ]
+ break
+ case 'J':
+ this.color = "blue"
+ this.lightColor = "rgb(230, 240, 255)"
+ this.ghostColor = "rgba(230, 240, 255, 0.5)"
+ this.minoesPos = [[-1, -1], [-1, 0], [0, 0], [1, 0]]
+ break
+ case 'L':
+ this.color = "orange"
+ this.lightColor = "rgb(255, 224, 204)"
+ this.ghostColor = "rgba(255, 224, 204, 0.5)"
+ this.minoesPos = [[-1, 0], [0, 0], [1, 0], [1, -1]]
+ break
+ case 'O':
+ this.color = "yellow"
+ this.lightColor = "rgb(255, 255, 230)"
+ this.ghostColor = "rgba(255, 255, 230, 0.5)"
+ this.minoesPos = [[0, 0], [1, 0], [0, -1], [1, -1]]
+ this.srs[SPIN.CW] = [[]]
+ this.srs[SPIN.CCW] = [[]]
+ break
+ case 'S':
+ this.color = "green"
+ this.lightColor = "rgb(236, 255, 230)"
+ this.ghostColor = "rgba(236, 255, 230, 0.5)"
+ this.minoesPos = [[-1, 0], [0, 0], [0, -1], [1, -1]]
+ break
+ case 'T':
+ this.color = "magenta"
+ this.lightColor= "rgb(242, 230, 255)"
+ this.ghostColor = "rgba(242, 230, 255, 0.5)"
+ this.minoesPos = [[-1, 0], [0, 0], [1, 0], [0, -1]]
+ break
+ case 'Z':
+ this.color = "red"
+ this.lightColor = "rgb(255, 230, 230)"
+ this.ghostColor = "rgba(255, 230, 230, 0.5)"
+ this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
+ break
+ }
+ }
+
+ get minoesAbsPos() {
+ return this.minoesPos.translate(this.pos)
+ }
+
+ draw(context, ghost_pos=[0, 0]) {
+ const color = this.locked ? this.lightColor : this.color
+ if (ghost_pos[1]) {
+ context.save()
+ context.shadowColor = this.ghostColor
+ context.shadowOffsetX = 0
+ context.shadowOffsetY = (ghost_pos[1]-this.pos[1]) * MINO_SIZE
+ context.shadowBlur = 3
+ this.minoesAbsPos.forEach(pos => drawMino(context, pos, color))
+ context.restore()
+ }
+ this.minoesAbsPos.forEach(pos => drawMino(context, pos, this.lightColor, color, ghost_pos))
+ }
+}
+
+
+function drawMino(context, pos, color1, color2=null, spotlight=[0, 0]) {
+ if (color2) {
+ var center = pos.add([0.5, 0.5])
+ spotlight = spotlight.add([0.5, 0.5])
+ var glint = spotlight.mul(0.1).add(center.mul(0.9)).mul(MINO_SIZE)
+ const gradient = context.createRadialGradient(
+ ...glint, 2, ...glint.add([6, 4]), 2*MINO_SIZE
+ )
+ gradient.addColorStop(0, color1)
+ gradient.addColorStop(1, color2)
+ context.fillStyle = gradient
+ } else
+ context.fillStyle = color1
+ var topLeft = pos.mul(MINO_SIZE)
+ context.fillRect(...topLeft, MINO_SIZE, MINO_SIZE)
+ context.lineWidth = 0.5
+ context.strokeStyle = "white"
+ context.strokeRect(...topLeft, MINO_SIZE, MINO_SIZE)
+}
+
+
+class HoldQueue {
+ constructor() {
+ this.context = document.getElementById("hold").getContext("2d")
+ this.piece = null
+ this.width = HOLD_COLUMNS*MINO_SIZE
+ this.height = HOLD_ROWS*MINO_SIZE
+ }
+
+ draw() {
+ this.context.clearRect(0, 0, this.width, this.height)
+ if (state != STATE.PAUSED) {
+ if (this.piece)
+ this.piece.draw(this.context)
+ }
+ }
+}
+
+
+timeFormat = new Intl.DateTimeFormat("fr-FR", {
+ minute: "2-digit", second: "2-digit", hourCycle: "h24", timeZone: "UTC"
+}).format
+
+
+class Stats {
+ constructor () {
+ this.div = document.getElementById("stats-values")
+ this._score = 0
+ this.highScore = localStorage.getItem('highScore') || 0
+ this.goal = 0
+ this.linesCleared = 0
+ this.startTime = Date.now()
+ this.pauseTime = 0
+ this.combo = -1
+ this.lockDelay = LOCK_DELAY
+ this.fallDelay = FALL_DELAY
+ }
+
+ get score() {
+ return this._score
+ }
+
+ set score(score) {
+ this._score = score
+ if (score > this.highScore)
+ this.highScore = score
+ }
+
+ newLevel(level=null) {
+ if (level)
+ this.level = level
+ else
+ this.level++
+ printTempTexts(["LEVEL", this.level])
+ this.goal += 5 * this.level
+ if (this.level <= 20)
+ this.fallDelay = 1000 * Math.pow(0.8 - ((this.level - 1) * 0.007), this.level - 1)
+ if (this.level > 15)
+ this.lockDelay = 500 * Math.pow(0.9, this.level - 15)
+ }
+
+ locksDown(tSpin, linesCleared) {
+ var patternName = []
+ var patternScore = 0
+ var combo_score = 0
+
+ if (tSpin)
+ patternName.push(tSpin)
+ if (linesCleared) {
+ patternName.push(SCORES[linesCleared].linesClearedName)
+ this.combo++
+ } else
+ this.combo = -1
+
+ if (linesCleared || tSpin) {
+ this.linesCleared += linesCleared
+ patternScore = SCORES[linesCleared][tSpin]
+ this.goal -= patternScore
+ patternScore *= 100 * this.level
+ patternName = patternName.join("\n")
+ }
+ if (this.combo >= 1)
+ combo_score = (linesCleared == 1 ? 20 : 50) * this.combo * this.level
+
+ this.score += patternScore + combo_score
+
+ if (patternScore)
+ printTempTexts([patternName, patternScore])
+ if (combo_score)
+ printTempTexts([`COMBO x${this.combo}`, combo_score])
+ }
+
+ print() {
+ this.div.innerHTML = `${this.score}
+ ${this.highScore}
+ ${timeFormat(Date.now() - this.startTime)}
+ ${this.level}
+ ${this.goal}
+ ${this.linesCleared}`
+ }
+}
+
+
+class Matrix {
+ constructor() {
+ this.context = document.getElementById("matrix").getContext("2d")
+ this.context.textAlign = "center"
+ this.context.textBaseline = "center"
+ this.context.font = "3vw 'Share Tech', sans-serif"
+ this.cells = Array.from(Array(MATRIX_ROWS+3), row => Array(MATRIX_COLUMNS))
+ this.width = MATRIX_COLUMNS*MINO_SIZE
+ this.height = MATRIX_ROWS*MINO_SIZE
+ this.centerX = this.width / 2
+ this.centerY = this.height / 2
+ this.piece = null
+ this.trail = {
+ minoesPos: [],
+ height: 0,
+ gradient: null
+ }
+ this.linesCleared = []
+ }
+
+ cellIsOccupied(x, y) {
+ return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.cells[y+3][x] : true
+ }
+
+ spaceToMove(minoesAbsPos) {
+ return !minoesAbsPos.some(pos => this.cellIsOccupied(...pos))
+ }
+
+ draw() {
+ this.context.clearRect(0, 0, this.width, this.height)
+
+ // grid
+ this.context.strokeStyle = "rgba(128, 128, 128, 128)"
+ this.context.lineWidth = 0.5
+ this.context.beginPath()
+ for (var x = 0; x <= this.width; x += MINO_SIZE) {
+ this.context.moveTo(x, 0);
+ this.context.lineTo(x, this.height);
+ }
+ for (var y = 0; y <= this.height; y += MINO_SIZE) {
+ this.context.moveTo(0, y);
+ this.context.lineTo(this.width, y);
+ }
+ this.context.stroke()
+
+ if (state != STATE.PAUSED) {
+ // ghost position
+ for (var ghost_pos = Array.from(this.piece.pos); this.spaceToMove(this.piece.minoesPos.translate(ghost_pos)); ghost_pos[1]++) {}
+ ghost_pos[1]--
+
+ // locked minoes
+ this.cells.slice(3).forEach((row, y) => row.forEach((colors, x) => {
+ if (colors) drawMino(this.context, [x, y], ...colors, ghost_pos)
+ }))
+
+ // trail
+ if (this.trail.height) {
+ this.context.fillStyle = this.trail.gradient
+ this.trail.minoesPos.forEach(topLeft => {
+ this.context.fillRect(...topLeft, MINO_SIZE, this.trail.height)
+ })
+ }
+
+ // falling piece
+ if (this.piece)
+ this.piece.draw(this.context, ghost_pos)
+
+ // Lines cleared
+ if (this.linesCleared.length) {
+ this.context.save()
+ this.context.shadowColor = "white"
+ this.context.shadowOffsetX = 0
+ this.context.shadowOffsetY = 0
+ this.context.shadowBlur = 5
+ this.context.fillStyle = "white"
+ this.linesCleared.forEach(y => this.context.fillRect(0, y, this.width, MINO_SIZE))
+ this.context.restore()
+ }
+ }
+
+ // text
+ var texts = []
+ switch(state) {
+ case STATE.PLAYING:
+ if (tempTexts.length)
+ texts = tempTexts[0]
+ break
+ case STATE.PAUSED:
+ texts = ["PAUSED"]
+ break
+ case STATE.GAME_OVER:
+ texts = ["GAME", "OVER"]
+ }
+ if (texts.length) {
+ this.context.save()
+ this.context.shadowColor = "black"
+ this.context.shadowOffsetX = 1
+ this.context.shadowOffsetY = 1
+ this.context.shadowBlur = 2
+ this.context.fillStyle = "white"
+ if (texts.length == 1)
+ this.context.fillText(texts[0], this.centerX, this.centerY)
+ else {
+ this.context.fillText(texts[0], this.centerX, this.centerY - 20)
+ this.context.fillText(texts[1], this.centerX, this.centerY + 20)
+ }
+ this.context.restore()
+ }
+ }
+}
+
+
+class NextQueue {
+ constructor() {
+ this.context = document.getElementById("next").getContext("2d")
+ this.pieces = Array.from({length: NEXT_PIECES}, (v, k) => new Tetromino(NEXT_PIECES_POSITIONS[k]))
+ this.width = NEXT_COLUMNS*MINO_SIZE
+ this.height = NEXT_ROWS*MINO_SIZE
+ }
+
+ draw() {
+ this.context.clearRect(0, 0, this.width, this.height)
+ if (state != STATE.PAUSED) {
+ this.pieces.forEach(piece => piece.draw(this.context))
+ }
+ }
+}
+
+
+function newLevel(startLevel) {
+ stats.newLevel(startLevel)
+ generationPhase()
+}
+
+function generationPhase(held_piece=null) {
+ if (!held_piece) {
+ matrix.piece = nextQueue.pieces.shift()
+ nextQueue.pieces.push(new Tetromino())
+ nextQueue.pieces.forEach((piece, i) => piece.pos = NEXT_PIECES_POSITIONS[i])
+ }
+ matrix.piece.pos = FALLING_PIECE_POSITION
+ if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos)))
+ fallingPhase()
+ else
+ gameOver()
+}
+
+function fallingPhase() {
+ scheduler.clearTimeout(lockPhase)
+ scheduler.clearTimeout(locksDown)
+ matrix.piece.locked = false
+ scheduler.setTimeout(lockPhase, stats.fallDelay)
+}
+
+function lockPhase() {
+ if (!move(MOVEMENT.DOWN)) {
+ matrix.piece.locked = true
+ if (!scheduler.timeoutTasks.has(locksDown))
+ scheduler.setTimeout(locksDown, stats.lockDelay)
+ }
+ requestAnimationFrame(draw)
+}
+
+function move(movement, testMinoesPos=matrix.piece.minoesPos) {
+ const testPos = matrix.piece.pos.add(movement)
+ if (matrix.spaceToMove(testMinoesPos.translate(testPos))) {
+ matrix.piece.pos = testPos
+ matrix.piece.minoesPos = testMinoesPos
+ if (movement != MOVEMENT.DOWN)
+ matrix.piece.rotatedLast = false
+ if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos.add(MOVEMENT.DOWN))))
+ fallingPhase()
+ else {
+ matrix.piece.locked = true
+ scheduler.clearTimeout(locksDown)
+ scheduler.setTimeout(locksDown, stats.lockDelay)
+ }
+ return true
+ } else {
+ return false
+ }
+}
+
+function rotate(spin) {
+ const test_minoes_pos = matrix.piece.minoesPos.map(pos => pos.rotate(spin))
+ rotationPoint = 1
+ for (const movement of matrix.piece.srs[spin][matrix.piece.orientation]) {
+ if (move(movement, test_minoes_pos)) {
+ matrix.piece.orientation = (matrix.piece.orientation + spin + 4) % 4
+ matrix.piece.rotatedLast = true
+ if (rotationPoint == 5)
+ matrix.piece.rotationPoint5Used = true
+ return true
+ }
+ rotationPoint++
+ }
+ return false
+}
+
+function locksDown(){
+ scheduler.clearInterval(move)
+ if (matrix.piece.minoesAbsPos.every(pos => pos.y < 0))
+ game_over()
+ else {
+ matrix.piece.minoesAbsPos.forEach(pos => matrix.cells[pos[1]+3][pos[0]] = [matrix.piece.lightColor, matrix.piece.color])
+
+ // T-Spin detection
+ var tSpin = T_SPIN.NONE
+ if (matrix.piece.rotatedLast && matrix.piece.shape == "T") {
+ const tSlots = T_SLOT_POS.translate(matrix.piece.pos).map(pos => matrix.cellIsOccupied(...pos)),
+ a = tSlots[(matrix.piece.orientation+T_SLOT.A)%4],
+ b = tSlots[(matrix.piece.orientation+T_SLOT.B)%4],
+ c = tSlots[(matrix.piece.orientation+T_SLOT.C)%4],
+ d = tSlots[(matrix.piece.orientation+T_SLOT.D)%4]
+ if (a && b && (c || d))
+ tSpin = T_SPIN.T_SPIN
+ else if (c && d && (a || b))
+ tSpin = matrix.piece.rotationPoint5Used ? T_SPIN.T_SPIN : T_SPIN.MINI
+ }
+
+ // Complete lines
+ matrix.linesCleared = []
+ matrix.cells.forEach((row, y) => {
+ if (row.filter(mino => mino.length).length == MATRIX_COLUMNS) {
+ matrix.cells.splice(y, 1)
+ matrix.cells.unshift(Array(MATRIX_COLUMNS))
+ matrix.linesCleared.push((y-3) * MINO_SIZE)
+ }
+ })
+
+ stats.locksDown(tSpin, matrix.linesCleared.length)
+ requestAnimationFrame(draw)
+ scheduler.setTimeout(clearLinesCleared, ANIMATION_DELAY)
+
+ if (stats.goal <= 0)
+ newLevel()
+ else
+ generationPhase()
+ }
+}
+
+function clearLinesCleared() {
+ matrix.linesCleared = []
+ requestAnimationFrame(draw)
+}
+
+function gameOver() {
+ state = STATE.GAME_OVER
+ scheduler.clearTimeout(lockPhase)
+ scheduler.clearTimeout(locksDown)
+ scheduler.clearInterval(clock)
+ requestAnimationFrame(draw)
+
+ if (stats.score == stats.highScore) {
+ alert("Bravo ! Vous avez battu votre précédent record.")
+ localStorage.setItem('highScore', stats.highScore)
+ }
+}
+
+function autorepeat() {
+ if (actionsToRepeat.length) {
+ actionsToRepeat[0]()
+ requestAnimationFrame(draw)
+ if (scheduler.timeoutTasks.has(autorepeat)) {
+ scheduler.clearTimeout(autorepeat)
+ scheduler.setInterval(autorepeat, AUTOREPEAT_PERIOD)
+ }
+ } else {
+ scheduler.clearTimeout(autorepeat)
+ scheduler.clearInterval(autorepeat)
+ }
+}
+
+function keyDownHandler(e) {
+ if (!pressedKeys.has(e.key)) {
+ pressedKeys.add(e.key)
+ if (e.key in actions[state]) {
+ action = actions[state][e.key]
+ action()
+ requestAnimationFrame(draw)
+ if (REPEATABLE_ACTIONS.includes(action)) {
+ actionsToRepeat.unshift(action)
+ scheduler.clearTimeout(autorepeat)
+ scheduler.clearInterval(autorepeat)
+ if (action == softDrop)
+ scheduler.setInterval(autorepeat, stats.fallDelay / 20)
+ else
+ scheduler.setTimeout(autorepeat, AUTOREPEAT_DELAY)
+ }
+ }
+ }
+}
+
+function keyUpHandler(e) {
+ pressedKeys.delete(e.key)
+ if (e.key in actions[state]) {
+ action = actions[state][e.key]
+ if (actionsToRepeat.includes(action)) {
+ actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1)
+ if (!actionsToRepeat.length) {
+ scheduler.clearTimeout(autorepeat)
+ scheduler.clearInterval(autorepeat)
+ }
+ }
+ }
+}
+
+function moveLeft() {
+ move(MOVEMENT.LEFT);
+}
+
+function moveRight() {
+ move(MOVEMENT.RIGHT)
+}
+
+function softDrop() {
+ if (move(MOVEMENT.DOWN))
+ stats.score++
+}
+
+function hardDrop() {
+ scheduler.clearTimeout(lockPhase)
+ scheduler.clearTimeout(locksDown)
+ matrix.trail.minoesPos = Array.from(matrix.piece.minoesAbsPos).map(pos => pos.mul(MINO_SIZE))
+ for (matrix.trail.height=0; move(MOVEMENT.DOWN); matrix.trail.height += MINO_SIZE) {
+ stats.score += 2
+ }
+ locksDown()
+ matrix.trail.gradient = matrix.context.createLinearGradient(0, 0, 0, matrix.trail.height)
+ matrix.trail.gradient.addColorStop(0,"rgba(255, 255, 255, 0)")
+ matrix.trail.gradient.addColorStop(1, matrix.piece.ghostColor)
+ scheduler.setTimeout(clearTrail, ANIMATION_DELAY)
+}
+
+function clearTrail() {
+ matrix.trail.height = 0
+ requestAnimationFrame(draw)
+}
+
+function rotateCW() {
+ rotate(SPIN.CW)
+}
+
+function rotateCCW() {
+ rotate(SPIN.CCW)
+}
+
+function hold() {
+ if (this.matrix.piece.holdEnabled) {
+ scheduler.clearInterval(move)
+ scheduler.clearInterval(locksDown)
+ var shape = this.matrix.piece.shape
+ this.matrix.piece = this.holdQueue.piece
+ this.holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape)
+ this.holdQueue.piece.holdEnabled = false
+ this.generationPhase(this.matrix.piece)
+ }
+}
+
+function pause() {
+ state = STATE.PAUSED
+ stats.pauseTime = Date.now() - stats.startTime
+ scheduler.clearTimeout(lockPhase)
+ scheduler.clearTimeout(locksDown)
+ scheduler.clearTimeout(autorepeat)
+ scheduler.clearInterval(clock)
+}
+
+function resume() {
+ state = STATE.PLAYING
+ stats.startTime = Date.now() - stats.pauseTime
+ scheduler.setTimeout(lockPhase, stats.fallDelay)
+ if (matrix.piece.locked)
+ scheduler.setTimeout(locksDown, stats.lockDelay)
+ requestAnimationFrame(draw)
+ scheduler.setInterval(clock, 1000)
+}
+
+function printTempTexts(texts) {
+ tempTexts.push(texts)
+ if (!scheduler.intervalTasks.has(delTempTexts))
+ scheduler.setInterval(delTempTexts, TEMP_TEXTS_DELAY)
+}
+
+function delTempTexts(self) {
+ if (tempTexts.length)
+ tempTexts.shift()
+ else
+ scheduler.clearInterval(delTempTexts)
+}
+
+function clock() {
+ stats.print()
+}
+
+function draw() {
+ holdQueue.draw()
+ stats.print()
+ matrix.draw()
+ nextQueue.draw()
+}
+
+function getKey(action) {
+ return localStorage.getItem(action) || actionsDefaultKeys[action]
+}
+
+window.onload = function() {
+ tempTexts = []
+
+ holdQueue = new HoldQueue()
+ stats = new Stats()
+ matrix = new Matrix()
+ nextQueue = new NextQueue()
+
+ actions[STATE.PLAYING] = {}
+ actions[STATE.PLAYING][getKey("moveLeft")] = moveLeft
+ actions[STATE.PLAYING][getKey("moveRight")] = moveRight
+ actions[STATE.PLAYING][getKey("softDrop")] = softDrop
+ actions[STATE.PLAYING][getKey("hardDrop")] = hardDrop
+ actions[STATE.PLAYING][getKey("rotateCW")] = rotateCW
+ actions[STATE.PLAYING][getKey("rotateCCW")] = rotateCCW
+ actions[STATE.PLAYING][getKey("hold")] = hold
+ actions[STATE.PLAYING][getKey("pause")] = pause
+ actions[STATE.PAUSED] = {}
+ actions[STATE.PAUSED][getKey("pause")] = resume
+ actions[STATE.GAME_OVER] = {}
+ pressedKeys = new Set()
+ actionsToRepeat = []
+ addEventListener("keydown", keyDownHandler, false)
+ addEventListener("keyup", keyUpHandler, false)
+
+ state = STATE.PLAYING
+ scheduler = new Scheduler()
+ scheduler.setInterval(clock, 1000)
+ this.newLevel(1)
}
\ No newline at end of file
diff --git a/webtris.html b/webtris.html
index 1dd7551..dad5545 100644
--- a/webtris.html
+++ b/webtris.html
@@ -1,38 +1,38 @@
-
-
-
-
- Webtris
-
-
-
-
-
- WEBTRIS
-
-
-
-
-
-
- SCORE
- RECORD
- TEMPS
- NIVEAU
- OBJECTIF
- LIGNES
-
-
-
-
-
-
Votre navigateur ne supporte pas HTML5, veuillez le mettre à jour pour jouer.
-
-
-
-
-
+
+
+
+
+ Webtris
+
+
+
+
+
+ WEBTRIS
+
+
+
+
+
+
+ SCORE
+ RECORD
+ TEMPS
+ NIVEAU
+ OBJECTIF
+ LIGNES
+
+
+
+
+
+
Votre navigateur ne supporte pas HTML5, veuillez le mettre à jour pour jouer.
+
+
+
+
+
\ No newline at end of file