From 9d7fa052727892cfbc804eb1a0231b1a111db458 Mon Sep 17 00:00:00 2001 From: adrien Date: Sat, 26 Oct 2019 19:13:01 +0200 Subject: [PATCH] lot of changes --- css/style.css | 24 ++- fonts/ShareTechMono.woff2 | Bin 0 -> 13596 bytes index.html | 25 ++- js/webtris.js | 364 +++++++++++++++++++++++++++----------- 4 files changed, 296 insertions(+), 117 deletions(-) create mode 100644 fonts/ShareTechMono.woff2 diff --git a/css/style.css b/css/style.css index ac3b4a1..07e80b5 100644 --- a/css/style.css +++ b/css/style.css @@ -1,3 +1,11 @@ +@font-face { + font-family: 'Share Tech Mono'; + font-style: normal; + font-weight: 400; + src: local('Share Tech Mono'), 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; @@ -12,7 +20,7 @@ body { canvas { display: block; flex-shrink: 0; - } +} .columns { display: flex; @@ -20,7 +28,7 @@ canvas { justify-content: center; } -.side { +.space { flex-grow: 2; } @@ -28,19 +36,27 @@ canvas { display: flex; flex-direction: column; margin: 5% 2%; + height: 400px; } .hold { max-width: 120px; - height: initial; } .stats { + display: flex; + flex-direction: row; margin: 10% 0; + font-family: 'Share Tech Mono'; + font-size: 1.2em; +} + +.stats-values { + text-align: right; + min-width: 80px; } .matrix { - max-width: 200px; margin: 5% 2%; border: 0.5px solid grey; } diff --git a/fonts/ShareTechMono.woff2 b/fonts/ShareTechMono.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..cee230d7d6a904198e4652813e2e4aebd38d3734 GIT binary patch literal 13596 zcmV+%HRH;6Pew8T0RR9105u!{5C8xG0E|cg05rP*0RR9100000000000000000000 z0000RARK{Y24Db$R0u)|mTwUV3W4Jgfx~gs9&S5S$OQBOvYLR!*Vj-e{1a?=Q7 zH4ivAU6$CjC;h7`EdAZt_?=%ZMNyE73g_n?wDZpV#Mya!erDx%w5^WIuY}XJSNDa^ z(EFr$^N@oc^_3GxXzY{R<7v}=W|vLqJV>wRRGgZnZjlwp#t2P`*b%81x%1FeOd@7A zht_lVuVJNLh?vJpaM_# zgWj$A0Npw2 zEzf!CjSC`@XKj-Fe~>JCcL%$(Sb)G{NdPn=0UZy9x&r~~o-%cZ#R9A)M5!S0c=B&7 zb>2ecIqRa2oQa&vjLV#fblIn|OM>jLv3Xx0!vG7H^ql;)-Ud9L3q0I+(TwX6LLkRU z5(Gh<{@PBDHG4m{xbO92PF2vgFT&tZ1Xm^ z#$R>U(;gWMgSsUp0mhSo?flunk!CVS;PpQ=SPF*y{vbWtW&xul3c-Si<7EL&I-M3W z8ip89VILn+>XaQmuGI9xmvNfR+0M~T?VV!&_~>lJ@wW-b452mn>azy{aF0y@uo6s( z2TY975Uz=@5m=S;j(1Gjl{GLA44QSSCk5~q{J5&W0RHv?U?2t#_*7TJVKrk!jnBnc zR3rER&>JV-G#8a{Iz|dXx9M;B}GfFLxAN2)@z} z_{h}zr^WIAHjsl>Tke?iZaHD2O9EFWbxv8S50BCD@fL^6<4fGQl|s3b z%2}<^nq1tu_vn9ZHn`!myJq=;S~49IGYcyx7dMYd(&Q=NRj62rN>!@WsJB+HvwRL( z<_I;aRH$~tJuY`_mZi~EcRlq$o1HQ}vej*SJm$1TwvT-z!!h181RZ$MB6?i~2oxj) z5eXT_AWRG}7Q{IthzJNp6N^%0p4lW6=8{`ziPctEX%$%@kz|=*7NNpeg{u`INxTG! zQmvO}lw6lxlCMCXqaq1eiBZQ-8TuLU$WfX1{PPzR3FcYE1&D8K>wu?BpPtgZe6qZ? z=Gm8@A%62orQyFT4r4r)kdr!V(T>|UpRE?H z;`axB{%AGFF9qI%1NXihQiHxQ;n}Zr#@D!O>>3eypN1LYyYM1G@e%9MEI{rKx$`RU z&N)Rg=Y;s&PMyoCK?|~m2`K)`QpC`RQCd4v%E^cpjY*Q5K`wH}-yy1G`lzoucMlw~ z|NCo?rEAq4Ili|3l?rd16;}z5FLq!`BS|RF3k$T~>!9pHjBK%2KE~{<>+i1b!#$hY2wY6_XGxEGRgIl)ql;2L&n5O0d$T$ zn*ExiYj`DV)zi=)OVrlS?G1qChQz2%Dz-}X;W1-ElJ2;yqB|MK2kQ!%xRNUYEt|G$uQDPK&>N7mdhrqiSNKOeAV&~J$V$4 zDAy+B>5j1vnSd*kr=JSxz3WUsB@M}mCmjXKp6-gdgnISPG82QAkr)P{N(P>kf9Hce zI0<9o5C3It&)-eWyPP!%RWD}~I3g{l$c)ou&M{eVLYAB%9cRfA=g5llp-arck~S&e z7%oAN30ZTBY&cB@j>(o2vf~Un;Ve1j9695B>>O0FL_QeonJ3ipsn+h6X=3LA)wZW1 zn~|^2v*iL{2wO)blrl{fjtwd8tnae>7o-_4uB7RrX4(~pSly0Y1#2oQ*XrZwo^pg_ z9Ia8`f6)E{0KD9iGF=T#mh(EOFjO%_ywLn`3EiN$ROP(ctc{d)wGO{UQpLsFC{%

g?q;Coe3tVzL`zmH)K{kW-ZWkjpo;fO&F%zIbdQq75JbQq z2J-On$^KM_eZ4x8ao^IJ(Jkd1aUeXCGVo--N0=XB#7Vq@az7Dj4-@VB@CU`rk1l!1 zLqVXgiK7fcsf5l4RC)>m#`V~EWE_)uMz@DW!CXMef52pxE1Q^lQ}P$Ew&%mQgEt zg-PgY4}^QYUzneF^Xf~k}+u|t=?xOS6$L34F4sDk%zM5lrp)JQfXfs z+NsB$TY{R-y)d+FB@^8G!b?vxFasU#LHS&+_WE`Ayo$djfj<1a!F_Mwt&_aHGPJFr zz#tCUMS%mM=;zzlpbzPQX9_;z7z$)nJp!vau{C%w+*$<0uO0z^PAmYx78i(si1i3a zoR|z?hf@#`Q#}G^PAmvO&8Y}Ta6JMcoLDG;Z7vJ}39m=M!s*BeIL*e)`o+(g&#HAW zi$n%s24FT>@0(6YY>w$9$}wGyPVhU4shL4y9WzLrV+M(L%peIhz9iA%OOhPEj-UTGmrPL+0{`!EaIE{^y!1an{{@w)ozLrVL@tiF@gHhk zl`3fTPZjS@d8q`M7cMzNgF& zNH&0%S#Ji}4J9s5@`1-?N)NslE>oGsw=(H^e3AGf?n0|%+e~GIk8?(;#DhitIQq0p z;@GVGy_!c14OAF*_Z*qwLs}VsVdyBFV)9*3n zyaQZgDdPyhaTG&{Ar1b&Zau>nhgMSJRu5Qmz2^75Nh@COigZw_0X>k!e!8Odv;RCM z5}5M|aICWN=O>9^KC&M18N61f3;A?6V?Y@-pd7XEqFJ1neu)TO=t1DDg+FeKn=LDv zoQCn&NArY?M=@wFpp97g5&@lHHM>+wnW+>q0;UWzyXAb44TvM5V-aA$U?gN5skLbs z$9Se8UJ;lnnp-M*E%#i?OeN)R&amfl&(TyB4Rf6y4W~?%namUvhD!)m3ev>HjH%1i zg$QE}^`&o917xdZ=tLb!HNyhsUz}HXNvEPps%KFd>b^K%;02lcyNjfNzM%>ed*0mM zO?~r=-ljs$FN(g@(?qZ5LLB%VMqTO+!N6yaiMZ&jb%91ut5mB+prv)LYgWddQMi1r`#< zg2`s0&}T$t=0i3kHHb+{xJDQs6r}+Zr;2xD8rADCR?J*c`piq+ujs}zzo~T^qazl& z$bekO5w#^(S*$iALn!LFq~uc4e55k8$H)-+zq;Ksmb+8&NC_P4oA4eP3X?K#_C9hw zqAXLC7YHKHmwd6oi#<2B~?{Cv7#@ z8tj>}9EWKLoAQw-$qXZ8li0lbXY^y4XAhf4Pw;|p>6@sMsspG2)Iko0&)8pLrI0D6 zl)|KljKzjt;aK*SRU<&$i0@}SM=iJ~YLp7;t$Eq!w?E!nQu%{1NM(TyGBA)Jg zdvYYi0-*^R2L`WJs5`xuwA93}R3CTd$~#RE8~irmZynb>C|zw>TYR?7!) z<^32l1L0@);URg14K!E()5(1x1azqCu~9x^G<{mbV63osG}NctY?C60sIaHlb!_?h zr|}{5iDz<*;_{Z(xS@MZ5zwRSG*fSu0OD?550fBO#XYBpw^+L(QbQG%86CkiUKmRM zuyC(m{5t`BLy5i5hLa=L`)2XxmO!9l|Ib~Q#4T(i{R{;0x)Ux+XgR(SXH_K^A}4x^ zxAGBaky<(w#B{v4KRE1Jkb3h>!qhB$CF@8poZsImsloda< zBr{QwM9D&%neDq&fEj(!Es_$9m?UC3;zNR>W@irO8K*}=B9V}K4}U6hf?3tLsuMZ0 z{D3epKmWMby8$K|iKpH9a|1)aUrx@EMZ)Y1#4xeKZ}Ef*sT@R`CF4PdFLvsq#@QDD zj{B2K<6pQ?A8H%G&w^aE1YC|hnpSqxcgePo##IJefV>!xx5b-~c@e&G2eO98kD(k- zp3J9*u9uO7A2qw(L&D`+z#a^rU;`cg%vPBwk*w_BS!I$)iek52jbg@xfv_s1CL`^! zS5>S-W#c%8AhokQ{1IsG(Qz3JmRey#cQV>X4wRnq5;GKk@#qrmDf!8F_lfz60m^^o zl2Ysi#LSM)6_XYLVHkJw6OETCCocSH9IzJy%D?>bo^#e{ZBED(qP!*?k702u`*R7Dux zQ7`h|678;Sl=HQ&>21oW>V2Rj(S(>@oZ$!gXjVC1rK>&0`b5hfg|Q}g8Z!4#H%HG; ztKms~>BHsIC)JSf-6RYTNrT_f$fQ|&y3+80X%hk>kBLD1Sg_zClw@}n5m@9zUlb2N zbm|d}O~SJdo;}Qu@*YHYtu(r1+DPVzr=L;OON#3Gy#&Xh0xC&$rb;R^Q_xJPNh=49 zy(GBjX!=0JB^O3Z!K*O}Hzey6sTU&6H=A1$}p z1K+h%s1mX{3!hl!yvX_)1R4X z9eCpwT}SO--urc=?f3q&8pb*bNmKRI;pD+(aH`mUrgs*MED-lpPgxmCJ*xpVf2P;ZKG>uC?bT!5Hol+cYY<)&R`%*@^x9mzoM%ETWhIDID7^M%!;?OPWa zmRHKY?*!W`c5pY#6%Bn(UO(@cM!|fhjqvEk={&!?ScI9fmB;*tu$6=Ih-*bp=>!`n z&wB;BbeL)|I+eknRae?m?ao3&H9P=4SOHvH3r`TbDkw{uFq5xzSin%T~k?Q^|f_YQbxZBpcRK6V3GwAZwoZSowkr* zn`0$ti8c|dAYoAJ2_G74YRei*6Yf1<^khz$X{QO#zYn|;_bmaVG@_1|L*7SBH9%Dw zmbalkq4InREwjiQhqNyEOYxEMY~9+0|ES>#qR}_4xNoD_6SiI#+AgiHz4|2G-YL_MCpbovY%CVM)RN<-+Ix;)#;)C0yb(PK; z`TJEeC&epp;Svh8+7^Rh7+;di$W9vF>GO5ItC^K<@_a!?GtC)ALn=p9B6Or$c-Ve- zr+FzaA&R~$U_)qz+YBq?TW5e8sFY-r)uv!Qujn=`HFQ_d{@%rxr#Z*b*;7v*N0yKq zXg&JSiWHs1uA@|_X+6`z+tYUyuCZp6eyseMw$8GnD7)T|`b53IE^{W`51n+VP8~)J zGGV0=LLKU_!(xwRy8?)R=~0(IaTK!T%dS$yZBQlZgI9M<5kOXWHE4IH0Q*-=8CesX zq5lh4HkA}W5|HXi?uM0pjdL6YbF%t647+hgnOQ!^RUx0X1pU(1LwEnl^)5WYvexI9 z#*Lr)3g$Q(=S)!XVse_vOg|=>^^iNkYL?Mj!_KPiaqI+Rt)lbr?(l57w}FIAR4OIf z@kXUlrw~)0G73Qko1d+0Mn-9*Mq|z8tO#X}&dvOH{cDM%!`f(x4J_?ha+Ex3BnlZ< zq&A%gDZqK&hd#W?X1J_+^x*HKzn^C|!dB=ZWQF7SebG$mT2n(_zLedgA7L z33?sQR{YWL;Idrpuc)RM^;z)B@4?Jz&7O@U;p0k!zW#bjLO#^&O>sL_VJf#XWrp1f z9NBK-+hwCt$OjeBR_ba7k+GWkzT~I9li8d-C$`}2`PXiW}K#K zj_|j|;>6nflCr5c0&s%qtxdi6qi$lI>+_&_M5rxppbQTeR6(7Pu@008|HvfNuW`nJ8?I3;`!%uT*)Z~}6##~_WvM>}sig$GRvmru4 z`eLd^Hdub}HV+cs@J+fnY2SmZ&>yV!?m2Dz(9Prl-KD5!v^xs}lfu|*D=Y#u_cS z8r;fxg<<*V!xvE`d&%PG&^oyC72<7C)6P1VyU>%r?p_A`o%(bgSV->GrX)j>ZYk&{AI6E;k+4RUs0dX8v3?HtnRo}sz;D9Rm1>!c%`(=afiY?S{+Mc_ z%*JEzdBL&&mGU^XN>u^sxNHqhLlny7v14V*XxT75WQAjB>#$;!=N%%F$M7>iMg!W= z0XkxER(u|q#x-&2;kjK5^-ECgYzv;bHS)$E-&a>wd@a<*m$lZn=UC9#;AGChKy6_S^fE`KXMG=5C5h@C7x5MWLYS`kcT z!cmij;4iSSZQ4EgSj9bQi}apitUO$ua@*060CF$B3*So)2ss+&10z8&;PwnUfYHYZ z1Xt_Foyfj_5bfffa(EoZVB`KiHm}`{nu1NT{du`7KQe+DA0f=KiN|(sd37%*gn!r5 z7YeO2_!*U@Kn+-<6aLAsM>Nm?++ZkM*ga|yf=AsC7GL{p(7WLEdb8fs4CWyMq<~gO zD+XE6a#{*PLz+R&(pHtOb#I-W3wQQAiY{C8?linaUjiNI6mIA=VJt=u+>j8dC}4^`Z3j6%LbbGCpo|SSTP4JzE1-*y;|RH0n*ug z2fP2Z^jeao4DJbW#X!T+Fm)!{*!iu!i6DUZ@Ki(f)@9V{b(MhVS8DBd>+ARNwd3+c zKWBL|JVBkYb2=-7`sgA+vPtauA?xf&VorokKi|frgWm!1_>#BGS4v!hTq7iya6beX zkq{&Jfin?=8hHXv`HJ~AyeuvQX|Nwc(e(K0XfOje1Kijdjzm@0cmuGum5AFku0PnO z;4G7P;BDv+m*)3m)x-o;nvm&#rhx$fT{*}UvZq=d@BKp|>Gww|T>ZcI!!Wh{4`i-$ zk|`w}R02^4=@jmANNYVAo|sO}(ax$%`ZRQ_O}SVFwL1&D36*V-J|Z_JeVSVIi)fo} zn*$nvn+z4>M?$I5X|gsZSkca?Ff=LZ>ad!gkxISkpm9Ilrn#`q^vW43J zbFz6SHOVbXmoAd$s#Go7cspJ;`&9BZD-<;fkvdwTXwhhx&#kcT9?Tc2DYKdkt?&S3 zr@VxMU!>O)GQW{ngO(K;+B7J*;53d}lCrZp6=_%(Ak~a4Lvh8HW?l2Ri@F@0W}IeA za-UYu0K4gb+EkF)=-=Q!X=_0ZSU6H{t{eR2I+y~!9DRo6SI&rk@}>Re@fMg9=B7^GUpRSpy5ALHs%WmLCEZR_ z=$8}-x__YC7`+?!%RF6OWhRTydnEJ=79{Cpw(i}1qAI2aUn6R zW07j+Zl&^=^jQ7nF6>Lp>F=mc!h5`BgIRBz=9Goy8?jc09xVcO2n`9REr$kyxpZSh zNgb^n5g@7bG|9_amq}%xfSUDAwcVF$Z_~M44C~DkjL}xoUoHn^@ zSj5K2@3f_~?~%J75^m0)#=Xd$77c_J?A*ZKfEKJ-sE_aVyLA73S6p+k*CCQ*q-;(y zuHmia4{rK;Yu{xm8PXTiXuW{k{rMS>}?ITr(?|%(Nh1pRwu9Pps z;n?_9uE;w|n3Crfq7LfWa<;+}jJW2KHoKBLtomI1`eM#j@0nR~BZyK4d4eyP#T| zhAs*wFEUl-@m~A^pjZs7pORR2~Uh@Wx?$U7Y`H<(ba|iO0T5?wZZO$G~R|b*| z!KB+h3^G{>jtD%=SF3<11k|PS#>s`m$6*2>!Hi!BVXTNpiIN@3g^d7OuoZ%WT7%jc zl{O4flyj<$vI+oBzb#OcCz$b)&lJDnPikO6I}%w7EKa^1f(9ld$-$kf)8 z%81-Fz*aT0J_BT~cX&ix%O1->yyYqmST=L7y>b95lsn`rd9c4)hI9Prv-Z>X;s_H& z`74CrpB=y=e@9F+tnL!yF`?1Rq5J4FbaSWAI0|pKOOGYt@yEZnKj!I^@Ca|#NgRcN zXHhdLjw5Zo9QGnMdv7sbH7w=fbsmLO$P^ch+Z-jcu-PbF0N;91mZy!Z^dzxq766%y z2#TFP0b~J0*z`cAB#-vLe^#c#q$_kpB>xO7&b)vnD{)WDvz;My%G=+VDzlvDsUOq-Z)G+rT<`koE#38lHA) zt(9eHGEtO_!qlv0iZxBKP@VAA-W{?J-@WIm66hQD@l7SRLJp8)5je*b6?@|Gn>-q} z7M}|)!7#Zk5)_Ns0A>cj46O(;4v0QcKxH$aSTsO-c?FvVXD+c-1g!M6F+%|33eJc| zl+$ej@k&1RU>vy>#mIvSiYg~=i1Dq{UHU0FIdZG4C?Hk;1YsEReyVks@e+jIP!9+Z zht}zVm~2v18A;><&O%i!zeC5$5}>Lx)&w*e?R)nW5ybDv{p;tkl|dYVqQF~HosqQ_ zr4P`R_Tim`qwj!hVjwV;FHZ;^{ z^=jT*YOH^Mxs$Mfe&svVaI7t91+WFW{v(dhGL&#Hf3Lxj7Wn(#NvOX|_5f7Uoku=; zT_NXcAt9*wtu}o|&JW$L!eKK0u!x_ZW{L})~0X9&E*cpcHaJ5KL(a%xRh zYkT5#X)Uw${FK|yz1*Zb4$NhXfpI7nysdVj&2gybZ%|jjfGcG%1%7gCvl&gV4k)0$ z!aY&i29bxt1J21;#=fF+7{{`h<1O&s^k8g~q6uWyTV@LlVTMPd^eUWPPhEnE9d`HE z8XlZ41Y&6AK!@0B_nMDZlKn;@yDcQivF28tzNRs4YHeBFWUc+oX2N)ig0wb3SXkdSzswJoehQ{(;c%Ls)NodR=1KpGV`V064F~g z33rtctj?2NmXafM4dz9!$|GE>R{^|zH9$ecErMFC{t=LNFU)K(fkR8t9Ho?rI8>ty ztRO-g6-d9V@j}{4)1ztf^0y>IoBPL+t#Qe~`ZAuj#j@w)ONs9~jBY}DWlR;}qZ(Rj zO1Dc9Scr0qL6hFB5bZLUU9jDyOw*?XoF-HXNTIa~qQ@*BX)A4yw$01ml17+aTd*x8 zd%RYh4?AhFn}taq0jMEQ%@%=Pp^8x?P|Y&<3@kjZ+EtCJiPHh z7`~A8wbiwyld|gVoYtyyR(sg#A#y8V-9s~?O=h8k9JSS;wW#>FYbi^m%BD1%eYK(1 zzpxDrE+mPk38p?N)7M%wdfaUkg|Rw#6<|f9s+9UT?;{R3ZV=pf(mg+1+NL^^Q=Dwr zfH@yF@rs|+~N@lz`3hODC&&D5r4&0X7T(3)`r=hwVwzl;I~EZ zBL~X$GJAz- zBMwTUwx*+LXx%3vHsiLjQY?H$o;1H#WWzEgT_#l;fYzr$PbjB8M5-nuurMz6ya@*sjIF5K>tD9DyQJvBNBkMz}ImO$nk615JGPIJ!69p*cZ zC~W$XEXFN`yCiU)h0iK9NYz!(I-F21LL4v(bYlf4jT?x&ExUZtTh4W7Otzdb7NJ&o zuWWQTjH?UlE*$UFZWBizyLC0%a+V{>3)yCc*q2@kvqj!{-yxD6p;|!oq)C{Wk#tV> z3dbKj7G$!>J_NfYSXxKVAu9TT?9pitP3DE|hBY}uvMJXNY4U>eEGcsm4@OOhG}J6T zL{5B}`pauz-XX#EO~JB6Wk?1@G_6sWwAY?>m!O0O3>HhZ6I{nu5ZN`9uG03CBhdJi zjH5OOH*4{v&wgrYG)cO7C)W~XJ)6w*Xf?jFI|e`_hcJ1KMJGvNaUq61IGAC}<-{GX zVVM?lh_URc!=5l~*we?ix=ZzCdbOB~7a}aw(sJ1G%(?)!e4+1d{+lOi9c+oc2Qq8f z^>6ZE-sp$+VptukPI9eA(Hx07XHetPN z^d8h9pBcXD4m&`K>^fOGCQUK9Su>(bAWoEvTT@+lZwf#9ExDf-c zv0`0FokrB;^X=c^?#3RH?|1t?K+8)h0)i@;86gJZZ8 zzkj}c^-=7h5UyCU$tOeHFba(g2j~r3m`|?4mE24;^(6H7rc)s2d8;_!YH7DIo@L#n zMTG7=S%nTPELI4YEkIH7S^VfJj=g%EU3!Z*zyd5`M~jJV|d zC;Qdp2l^hvE%a2GW_iM?{a|g8%tvlMbAZ|-xnCC@L*#jb*&lCx5DQ5!^E9SUM^zs-&0(3h}F&%S!oM&E3sorSPD`5(v2scA4 zD-*s&Qtb0ZS@;Z7o_6aC5Q&;-gj?jyebe!*-R&HCn{>oHDd&VU50-^2rp8uLo@c;A zzG^m$%Mn+-Tu%M@6duA|{m||bY+(wMaGs80sh;|tdQ(6+L;PEj&jZyB|45Ncy-!Y? z!a?1z zY-_o**d~wHxt&tmZ66=9Bgb&+NM5NWdz`829)Y(Z!ST&c9IbFE3Zr8ZR6XP*)H_`B1H_c!*Sz!tbn22E(QVi z-_{S^D?pRu4cAv*67;3)N-AKbxBWkg7T)3QN1we{AFqR(ru!H0+w{%fqp6|vp8-I) zV62D$U8#+KHXwWh5Epb&zf%wLL|EsbH|f?I)p)Mz=FCj7h6tA!xLSuP@SCz2L}uwE zt;9{|>cUcUu#8RhO-uM*_gI(QLIoE?;~A_TB@wY7_VjN{*UBw?Nw;h(fd)5wPIs9n z%EP076p|H4hgIVKM3`tUql5{VUV@!Hez(Cbl6FDSWK*1#XHE_!YU9Dl21FM*Av~iR zJ^xz-ZvMN>BLD*AWvYJ-C*HK+qgM4xh5}@C8mhqSErfIjl1V)1-dpZUf-hZMoQC1+ zqXMzOw46z|QV)#zj01g1;5C8>ToRhHnXUnkJ;8Tb8*CaZ&&CK%g5sK{%3V_zJywHu zZ4j1CXp-Vg2CmHoNxX6wRzZEUJA1=h5TJ>LnPgWCq7p`Anr7Xi%#|YiuDE2w{hj0@ z=HoP!G%36afv`_eP(ohtFEs^Rh-APGqxvtxCNR~FOp%n+pW&M^y$K%`&RQ_1Nk@mI zc;PJv&AEo8K(W@|ekzVt(;A7dC8kz<##_y)LasBy@i^oi+m%{ z)_c3j*M0_jvz`lHlL;F=9`UND$uc(ww%EPbBP5dwP4tbSxUnlOCm?MXk{Tymsi5)f zw)j>v;%@711gic`O{&LMq^>D)jx?K&CH@5BhU2bwW~71GXr>DSi*rTec-9ld?>64N zNpSN?|9m%*8@Rq5^KHRLJ>(e_GPSj}=pkVsFhEc>Xi4p@=m@%PlxHbfVo`8BDq?cX z5jqL?Fe5HhdW1f$ZD+W@@YmscHnXVaYr#Eg_9wOJD({zh#j8v!xq6Ut?=t$d<9Jvo zL1vP_9>_Lp;QNlJrQ0ov+C*J(Ez_G^+DPl$=A4#ZcW|@Iw03IW&%Z%EA}Jd25ME;9 zxNdU}v#7F^ued3m&yRNl9!J~{M*kK8@Hhc9pz&MJK1Ift7VfzzbbER6Ep~lCkKj(- z&Wg2fcqi|M%R3&5yhS0N%g> z0008KThOnWULnu1Fdq};Twkyo=aAjY1`MFiQb+;%ZWTZgq=UuT0##b`38sP-szIcg z!S2-n2|V`nYsdxVqKp9IoI+<5ICrm7BjU9N0QTLJ4mp~ZbSxGC^B@y65b1QbX0w}5 z8VL_sV250&0}~W4DlGvSgn_)dbcZ6%bjXq14YQyCQXwA-p%I)=4_V*^hn9)c#wCD- z4p;_VpaB6q)Xr7VtA(F1(JTO_!Axk^9+Q9o0=317bCYwsM(wH<180Tvbn_X_uL?&O z-jEwMf)7MPFievRewi|d5*E|HqjZHkjijBCfBJk9132QT~1*cQ|jCBJV%| zFMLdEnBh2s2vh^u+A4s90JPTQ;ot%AC-`e;)Zd>wb9^%2nI{LaYw$Y@dS~m-UPk56 zJ@qj;4J?!>$tHHuY@(zv2$y3dB?Nf*N{OPdl7(irOB5|xvG-4xB$9zjq~wlj0uiJr zae0VZU9Qw_O1M%IpX;WW$C`%6%7IiogZj_0jK!%Vg{105OWCO@;+4ozNjsEvqoPre zFvGy4#w(d%vWfZvyJ?4pbVjl=QH@>HL}n?QRB`wmbCk6~jTXS{hUMF>jV~%opRY?J z(Hsl}w?U#}h{GH_Zx?o_JGv`1Mk(}`k^*60T;EyE>P00@(32K*!(a7XUcCAuhWnLN2R3dklrhHQYNRy8c_9h~hGXSc01K!ECB`r7jpFLA)~-`!IW{FD;YgrtY^b ze@p3Axur<6L8{g7UVY*=3b4r*TWxmMHiZIxOC*tfOkuK}L%JPrXuWH}zE&kfgGMKv za#$C^Z-N!yqWZr#gwM}@@v9&F=qGw}o%5l7FtTZ!=cN<6^#r89KnkJ4%^}0+6f5#p zI9!#bPQ6woN|hKBxmTV|sR>}C$B&mUssiIR|5ZTz@+eH4$QKO(1=j;ig^0V%$APq<1#40v$bw>6 z7L0;JOAV;ka@zft0|7s-$1bSU&f^`0TnAKqD;r^D9u^f2!H@i11@l@=Ut}FOEUilG i0Mh(MtaekqVs^KtbRb}~oNiPFdS+9TS;)a60000Ga|?_B literal 0 HcmV?d00001 diff --git a/index.html b/index.html index e9fd5ae..1b61414 100644 --- a/index.html +++ b/index.html @@ -9,18 +9,29 @@

-
+
- -
- SCORE: 123123123123
- HIGH SCORE:
- TIME:
+
+
+
+
+
+
+ SCORE
+ HIGH
+ LEVEL
+ GOAL
+ LINES
+ TIME
+
+
+
+
Votre navigateur ne supporte pas HTML5, veuillez le mettre à jour pour jouer. -
+
\ No newline at end of file diff --git a/js/webtris.js b/js/webtris.js index a2f3161..dcd6b79 100644 --- a/js/webtris.js +++ b/js/webtris.js @@ -1,6 +1,9 @@ Array.prototype.add = function(other) { return this.map((x, i) => x + other[i]) } +Array.prototype.translate = function(vector) { + return this.map(pos => pos.add(vector)) +} Array.prototype.rotate = function(spin) { return [-spin*this[1], spin*this[0]] } @@ -11,8 +14,12 @@ Array.prototype.sample = function() { const MINO_SIZE = 20 const NEXT_PIECES = 5 -const MATRIX_LINES = 20 +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]) @@ -21,12 +28,12 @@ const FALL_DELAY = 1000 const AUTOREPEAT_DELAY = 300 const AUTOREPEAT_PERIOD = 10 const MOVEMENT = { - LEFT: [-1, 0], - RIGHT: [1, 0], - DOWN: [0, 1] + LEFT: [-1, 0], + RIGHT: [ 1, 0], + DOWN: [ 0, 1] } const SPIN = { - CW: 1, + CW: 1, CCW: -1 } const T_SPIN = { @@ -47,16 +54,47 @@ const SCORES = [ {LINES_CLEAR_NAME: "TRIPLE", NO_T_SPIN: 5, T_SPIN: 16}, {LINES_CLEAR_NAME: "TETRIS", NO_T_SPIN: 8}, ] +const REPEATABLE_ACTIONS = [moveLeft, moveRight, softDrop] + + +class Scheduler { + constructor() { + this.intervalTasks = new Map() + this.timeoutTasks = new Map() + } + + setInterval(func, delay) { + this.intervalTasks.set(func, window.setInterval(func, delay)) + } + + setTimeout(func, delay) { + this.timeoutTasks.set(func, window.setTimeout(func, delay)) + } + + 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() { - this.pos = FALLING_PIECE_POSITION + constructor(position=null, shape=null) { + this.pos = position this.orientation = 0 - this.rotated_last = false - this.rotation_point_5_used = false - this.hold_enabled = true + this.rotatedLast = false + this.rotationPoint5Used = false + this.holdEnabled = true this.srs = { CW: [ [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], @@ -71,13 +109,18 @@ class Tetromino { [[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]], ], } - if (!shapes.lenght) - shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'] - this.shape = shapes.sample() + if (shape) + this.shape = shape + else { + if (!shapes.length) + shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'] + this.shape = shapes.sample() + } switch(this.shape) { case 'I': - this.color = "cyan" - this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [2, 0]] + this.color = "rgb(132, 225, 225)" + this.ghostColor = "rgba(40, 164, 164, 0.5)" + this.minoesPos = [[-1, 0], [0, 0], [1, 0], [2, 0]] this.srs = { CW: [ [[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]], @@ -94,47 +137,62 @@ class Tetromino { } break case 'J': - this.color = "blue" - this.minoes_pos = [[-1, -1], [-1, 0], [0, 0], [1, 0]] + this.color = "rgb(102, 163, 255)" + this.ghostColor = "rgba(0, 82, 204, 0.5)" + this.minoesPos = [[-1, -1], [-1, 0], [0, 0], [1, 0]] break case 'L': - this.color = "orange" - this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [1, -1]] + this.color = "rgb(255, 148, 77)" + this.ghostColor = "rgba(204, 82, 0, 0.5)" + this.minoesPos = [[-1, 0], [0, 0], [1, 0], [1, -1]] break case 'O': - this.color = "yellow" - this.minoes_pos = [[0, 0], [1, 0], [0, -1], [1, -1]] + this.color = "rgb(255, 255, 102)" + this.ghostColor = "rgba(204, 204, 0, 0.5)" + this.minoesPos = [[0, 0], [1, 0], [0, -1], [1, -1]] this.srs = { CW: [[]], CCW: [[]] } break case 'S': - this.color = "green" - this.minoes_pos = [[-1, -1], [0, -1], [0, 0], [1, 0]] + this.color = "rgb(159, 255, 128)" + this.ghostColor = "rgb(38, 153, 0, 0.5)" + this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]] break case 'T': - this.color = "magenta" - this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [0, -1]] + this.color = "rgb(179, 102, 255)" + this.ghostColor = "rgba(102, 0, 204, 0.5)" + this.minoesPos = [[-1, 0], [0, 0], [1, 0], [0, -1]] break case 'Z': - this.color = "red" - this.minoes_pos = [[-1, -1], [0, -1], [0, 0], [1, 0]] + this.color = "rgb(255, 51, 51)" + this.ghostColor = "rgba(204, 0, 0, 0.5)" + this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]] break } } - get abs_minoes_pos() { - return this.minoes_pos.map(pos => pos.add(this.pos)) + get absMinoesPos() { + return this.minoesPos.translate(this.pos) } - draw(context) { - this.abs_minoes_pos.map(pos => draw_mino(context, ...pos, this.color)) + draw(context, ghostYOffset=0) { + if (ghostYOffset) { + context.save() + context.shadowColor = this.ghostColor + context.shadowOffsetX = 0 + context.shadowOffsetY = ghostYOffset * MINO_SIZE + context.shadowBlur = 3 + } + this.absMinoesPos.map(pos => draw_mino(context, ...pos, this.color)) + if (ghostYOffset) + context.restore() } } -function draw_mino(context, x, y, color) { +function draw_mino(context, x, y, color, ghostYOffset) { context.fillStyle = color context.fillRect(x*MINO_SIZE, y*MINO_SIZE, MINO_SIZE, MINO_SIZE) context.lineWidth = 0.5 @@ -143,50 +201,132 @@ function draw_mino(context, x, y, color) { } +class HoldQueue { + constructor(context) { + this.context = context + 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 (this.piece) + this.piece.draw(this.context) + } +} + +timeFormat = new Intl.DateTimeFormat("en-US", { + hour: "numeric", minute: "2-digit", second: "2-digit", hourCycle: "h24", timeZone: "UTC" +}).format + +class Stats { + constructor (div, start_level=1) { + this.div = div + this._score = 0 + this.highScore = 0 + this.level = start_level - 1 + this.goal = 0 + this.linesCleared = 0 + this.startTime = Date.now() + 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 + } + + new_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) + } + + print() { + this.div.innerHTML = this.score + this.div.innerHTML += "
" + this.highScore + this.div.innerHTML += "
" + this.level + this.div.innerHTML += "
" + this.goal + this.div.innerHTML += "
" + this.linesCleared + this.div.innerHTML += "
" + timeFormat(Date.now() - this.startTime) + } +} + + class Matrix { constructor(context) { this.context = context - this.cells = Array.from({length: MATRIX_COLUMNS}, (v, k) => Array(MATRIX_LINES)) + this.cells = Array.from(Array(MATRIX_COLUMNS), x => Array(MATRIX_ROWS)) + this.width = MATRIX_COLUMNS*MINO_SIZE + this.height = MATRIX_ROWS*MINO_SIZE + this.piece = null } - cell_is_occupied(x, y) { - return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_LINES ? this.cells[x][y] : true + cellIsOccupied(x, y) { + return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.cells[x][y] : true } - space_to_move(piece_pos, minoes_pos) { - for (const pos of minoes_pos) { - if (this.cell_is_occupied(...pos.add(piece_pos))) + spaceToMove(absMinoesPos) { + for (const pos of absMinoesPos) { + if (this.cellIsOccupied(...pos)) return false } return true } draw() { + this.context.clearRect(0, 0, this.width, this.height) // grid - const width = MATRIX_COLUMNS*MINO_SIZE - const height = MATRIX_LINES*MINO_SIZE - this.context.clearRect(0, 0, width, height) this.context.strokeStyle = "rgba(128, 128, 128, 128)" this.context.lineWidth = 0.5 this.context.beginPath() - for (var x = 0; x <= width; x += MINO_SIZE) { + for (var x = 0; x <= this.width; x += MINO_SIZE) { this.context.moveTo(x, 0); - this.context.lineTo(x, height); + this.context.lineTo(x, this.height); } - for (var y = 0; y <= height; y += MINO_SIZE) { + for (var y = 0; y <= this.height; y += MINO_SIZE) { this.context.moveTo(0, y); - this.context.lineTo(width, y); + this.context.lineTo(this.width, y); } this.context.stroke() // falling piece - falling_piece.draw(this.context) + if (this.piece) + for (var ghostYOffset = 0; this.spaceToMove(this.piece.minoesPos.translate([this.piece.pos[0], this.piece.pos[1]+ghostYOffset])); ghostYOffset++) {} + this.piece.draw(this.context, --ghostYOffset) } } + +class NextQueue { + constructor(context) { + this.context = context + 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) + this.pieces.map(piece => piece.draw(this.context)) + } +} + + function move(movement) { - const test_pos = falling_piece.pos.add(movement) - if (matrix.space_to_move(test_pos, falling_piece.minoes_pos)) { - falling_piece.pos = test_pos + const test_pos = matrix.piece.pos.add(movement) + if (matrix.spaceToMove(matrix.piece.minoesPos.translate(test_pos))) { + matrix.piece.pos = test_pos return true } else { @@ -195,14 +335,14 @@ function move(movement) { } function rotate(spin) { - const test_minoes_pos = falling_piece.minoes_pos.map(pos => pos.rotate(spin)) + const test_minoes_pos = matrix.piece.minoesPos.map(pos => pos.rotate(spin)) rotation_point = 0 - for (const movement of falling_piece.srs[spin==SPIN.CW?"CW":"CCW"][falling_piece.orientation]) { - const test_pos = falling_piece.pos.add(movement) - if (matrix.space_to_move(test_pos, test_minoes_pos)) { - falling_piece.pos = test_pos - falling_piece.minoes_pos = test_minoes_pos - falling_piece.orientation = (falling_piece.orientation + spin + 4) % 4 + for (const movement of matrix.piece.srs[spin==SPIN.CW?"CW":"CCW"][matrix.piece.orientation]) { + const test_pos = matrix.piece.pos.add(movement) + if (matrix.spaceToMove(test_minoes_pos.translate(test_pos))) { + matrix.piece.pos = test_pos + matrix.piece.minoesPos = test_minoes_pos + matrix.piece.orientation = (matrix.piece.orientation + spin + 4) % 4 break; } rotation_point++ @@ -239,34 +379,47 @@ function rotateCCW() { rotate(SPIN.CCW) } -actions = { - "ArrowLeft": moveLeft, - "ArrowRight": moveRight, - "ArrowDown": softDrop, - " ": hardDrop, - "ArrowUp": rotateCW, - "z": rotateCCW +function hold() { + if (this.matrix.piece.holdEnabled) { + this.matrix.piece.holdEnabled = false + clearInterval(lockPhaseIntervalID) + var shape = this.matrix.piece.shape + this.matrix.piece = this.holdQueue.piece + this.holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape) + this.generationPhase(this.matrix.piece) + } } -pressedKeys = new Set() -repeatableActions = [moveLeft, moveRight, softDrop] -actionsToRepeat = [] -autorepeatTimeoutID = null -autorepeatIntervalID = null +function new_level() { + stats.new_level() + fallIntervalID = setInterval(fall, stats.fallDelay) + generationPhase() +} + +function generationPhase(held_piece=null) { + if (!held_piece) { + this.matrix.piece = this.nextQueue.pieces.shift() + this.nextQueue.pieces.push(new Tetromino()) + this.nextQueue.pieces.map((piece, i, pieces) => piece.pos = NEXT_PIECES_POSITIONS[i]) + } + this.matrix.piece.pos = FALLING_PIECE_POSITION + /*if (this.matrix.spaceToMove(this.matrix.piece.minoesPos.translate(this.matrix.piece.pos))) + fallingPhase() + else + gameOver()*/ +} function autorepeat() { if (actionsToRepeat.length) { actionsToRepeat[0]() - if (autorepeatTimeoutID) { - autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID) - autorepeatIntervalID = setInterval(autorepeat, AUTOREPEAT_PERIOD) + if (scheduler.timeoutTasks.has(autorepeat)) { + scheduler.clearTimeout(autorepeat) + scheduler.setInterval(autorepeat, AUTOREPEAT_PERIOD) } } else { - if (autorepeatTimeoutID) - autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID) - if (autorepeatIntervalID) - autorepeatIntervalID = clearInterval(autorepeatIntervalID) + scheduler.clearTimeout(autorepeat) + scheduler.clearInterval(autorepeat) } } @@ -276,18 +429,14 @@ function keyDownHandler(e) { pressedKeys.add(e.key) action = actions[e.key] action() - if (repeatableActions.includes(action)) { + if (REPEATABLE_ACTIONS.includes(action)) { actionsToRepeat.unshift(action) - if (autorepeatTimeoutID) { - autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID) - } - if (autorepeatIntervalID) { - autorepeatIntervalID = clearInterval(autorepeatIntervalID) - } + scheduler.clearTimeout(autorepeat) + scheduler.clearInterval(autorepeat) if (actionsToRepeat == softDrop) - autorepeatIntervalID = setInterval(autorepeat, FALL_DELAY / 20) + scheduler.setInterval(autorepeat, FALL_DELAY / 20) else - autorepeatTimeoutID = setTimeout(autorepeat, AUTOREPEAT_DELAY) + scheduler.setTimeout(autorepeat, AUTOREPEAT_DELAY) } } } @@ -300,39 +449,42 @@ function keyUpHandler(e) { if (actionsToRepeat.includes(action)) { actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1) if (!actionsToRepeat.length) { - if (autorepeatTimeoutID) { - autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID) - } - if (autorepeatIntervalID) { - autorepeatIntervalID = clearInterval(autorepeatIntervalID) - } + scheduler.clearTimeout(autorepeat) + scheduler.clearInterval(autorepeat) } } } } function draw() { - held_piece.draw(holdContext) + holdQueue.draw() + stats.print() matrix.draw() - next_pieces.map(piece => piece.draw(nextContext)) + nextQueue.draw() requestAnimationFrame(draw) } window.onload = function() { - holdContext = document.getElementById("hold").getContext("2d") - matrixContext = document.getElementById("matrix").getContext("2d") - nextContext = document.getElementById("next").getContext("2d") + holdQueue = new HoldQueue(document.getElementById("hold").getContext("2d")) + stats = new Stats(document.getElementById("stats-values")) + matrix = new Matrix(document.getElementById("matrix").getContext("2d")) + nextQueue = new NextQueue(document.getElementById("next").getContext("2d")) + scheduler = new Scheduler() - matrix = new Matrix(matrixContext) - held_piece = new Tetromino() - held_piece.pos = HELD_PIECE_POSITION - falling_piece = new Tetromino() - falling_piece.pos = FALLING_PIECE_POSITION - next_pieces = Array.from({length: NEXT_PIECES}, (v, k) => new Tetromino()) - next_pieces.map((piece, i, array) => piece.pos = NEXT_PIECES_POSITIONS[i]) - - document.addEventListener("keydown", keyDownHandler, false); - document.addEventListener("keyup", keyUpHandler, false); - setInterval(fall, FALL_DELAY); + actions = { + "ArrowLeft": moveLeft, + "ArrowRight": moveRight, + "ArrowDown": softDrop, + " ": hardDrop, + "ArrowUp": rotateCW, + "z": rotateCCW, + "c": hold + } + pressedKeys = new Set() + actionsToRepeat = [] + addEventListener("keydown", keyDownHandler, false) + addEventListener("keyup", keyUpHandler, false) requestAnimationFrame(draw) + + this.new_level() } \ No newline at end of file