From: Joe Wreschnig Date: Sat, 6 Sep 2014 10:25:48 +0000 (+0200) Subject: Total conversion to FF2. X-Git-Url: https://git.yukkurigames.com/?p=featherfall2.git;a=commitdiff_plain;h=c6b5fcbed00096406ca526ec55f5e945d35c916a Total conversion to FF2. --- diff --git a/Makefile b/Makefile index 06f7e3f..ded43da 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ #!/usr/bin/make -f -node-webkit-version := 0.10.2 -node-webkit-version-win-ia32.zip := 0.8.6 +node-webkit-version := 0.10.4 .DELETE_ON_ERROR: include rules/programs.mk @@ -13,7 +12,7 @@ include rules/pngcrush.mk .PHONY: all check distclean lint test dist clean serve -APPLICATION := pwl6 +APPLICATION := featherfall DISTDIR := build/dist VERSION := $(call git-describe) DISTROOT := $(DISTDIR)/$(APPLICATION)-$(VERSION) diff --git a/src/data/images/book.xcf.gz b/src/data/images/book.xcf.gz deleted file mode 100644 index cc06d1f..0000000 Binary files a/src/data/images/book.xcf.gz and /dev/null differ diff --git a/src/data/images/circle-inner.png b/src/data/images/circle-inner.png deleted file mode 100644 index 9069a43..0000000 Binary files a/src/data/images/circle-inner.png and /dev/null differ diff --git a/src/data/images/circle-outer-ee.png b/src/data/images/circle-outer-ee.png deleted file mode 100644 index 7040514..0000000 Binary files a/src/data/images/circle-outer-ee.png and /dev/null differ diff --git a/src/data/images/circle-outer.png b/src/data/images/circle-outer.png deleted file mode 100644 index 0c2e236..0000000 Binary files a/src/data/images/circle-outer.png and /dev/null differ diff --git a/src/data/images/circle-rim.png b/src/data/images/circle-rim.png deleted file mode 100644 index 366d020..0000000 Binary files a/src/data/images/circle-rim.png and /dev/null differ diff --git a/src/data/images/circle.xcf.gz b/src/data/images/circle.xcf.gz deleted file mode 100644 index 4c2fa51..0000000 Binary files a/src/data/images/circle.xcf.gz and /dev/null differ diff --git a/src/data/images/hand.png b/src/data/images/hand.png deleted file mode 100644 index 96e07b2..0000000 Binary files a/src/data/images/hand.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_128x128.png b/src/data/images/icons.iconset/icon_128x128.png deleted file mode 100644 index 6c93aa7..0000000 Binary files a/src/data/images/icons.iconset/icon_128x128.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_128x128@2x.png b/src/data/images/icons.iconset/icon_128x128@2x.png deleted file mode 100644 index 08bd681..0000000 Binary files a/src/data/images/icons.iconset/icon_128x128@2x.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_16x16.png b/src/data/images/icons.iconset/icon_16x16.png deleted file mode 100644 index 570b181..0000000 Binary files a/src/data/images/icons.iconset/icon_16x16.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_256x256.png b/src/data/images/icons.iconset/icon_256x256.png deleted file mode 100644 index 08bd681..0000000 Binary files a/src/data/images/icons.iconset/icon_256x256.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_32x32.png b/src/data/images/icons.iconset/icon_32x32.png deleted file mode 100644 index 6813ad9..0000000 Binary files a/src/data/images/icons.iconset/icon_32x32.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_32x32@2x.png b/src/data/images/icons.iconset/icon_32x32@2x.png deleted file mode 100644 index a8dc504..0000000 Binary files a/src/data/images/icons.iconset/icon_32x32@2x.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_64x64.png b/src/data/images/icons.iconset/icon_64x64.png deleted file mode 100644 index a8dc504..0000000 Binary files a/src/data/images/icons.iconset/icon_64x64.png and /dev/null differ diff --git a/src/data/images/icons.iconset/icon_64x64@2x.png b/src/data/images/icons.iconset/icon_64x64@2x.png deleted file mode 100644 index 6c93aa7..0000000 Binary files a/src/data/images/icons.iconset/icon_64x64@2x.png and /dev/null differ diff --git a/src/data/images/left.png b/src/data/images/left.png new file mode 100644 index 0000000..c0b2d7f Binary files /dev/null and b/src/data/images/left.png differ diff --git a/src/data/images/player.png b/src/data/images/player.png new file mode 100644 index 0000000..a171fe3 Binary files /dev/null and b/src/data/images/player.png differ diff --git a/src/data/images/right.png b/src/data/images/right.png new file mode 100644 index 0000000..82fc819 Binary files /dev/null and b/src/data/images/right.png differ diff --git a/src/data/images/sigils.png b/src/data/images/sigils.png deleted file mode 100644 index c69871a..0000000 Binary files a/src/data/images/sigils.png and /dev/null differ diff --git a/src/data/images/star.png b/src/data/images/star.png new file mode 100644 index 0000000..831fea2 Binary files /dev/null and b/src/data/images/star.png differ diff --git a/src/data/shaders/noise.glsl b/src/data/shaders/noise.glsl deleted file mode 100644 index 84944be..0000000 --- a/src/data/shaders/noise.glsl +++ /dev/null @@ -1,180 +0,0 @@ -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : ijm -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// -/* - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -precision highp float; - -vec2 mod289(vec2 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec3 mod289(vec3 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec4 mod289(vec4 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec3 permute(vec3 x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec4 permute(vec4 x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec4 taylorInvSqrt(vec4 r) -{ - return 1.79284291400159 - 0.85373472095314 * r; -} - -float snoise(vec3 v) - { - const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; - const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); - -// First corner - vec3 i = floor(v + dot(v, C.yyy) ); - vec3 x0 = v - i + dot(i, C.xxx) ; - -// Other corners - vec3 g = step(x0.yzx, x0.xyz); - vec3 l = 1.0 - g; - vec3 i1 = min( g.xyz, l.zxy ); - vec3 i2 = max( g.xyz, l.zxy ); - - // x0 = x0 - 0.0 + 0.0 * C.xxx; - // x1 = x0 - i1 + 1.0 * C.xxx; - // x2 = x0 - i2 + 2.0 * C.xxx; - // x3 = x0 - 1.0 + 3.0 * C.xxx; - vec3 x1 = x0 - i1 + C.xxx; - vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y - vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y - -// Permutations - i = mod289(i); - vec4 p = permute( permute( permute( - i.z + vec4(0.0, i1.z, i2.z, 1.0 )) - + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) - + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); - -// Gradients: 7x7 points over a square, mapped onto an octahedron. -// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float n_ = 0.142857142857; // 1.0/7.0 - vec3 ns = n_ * D.wyz - D.xzx; - - vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) - - vec4 x_ = floor(j * ns.z); - vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) - - vec4 x = x_ *ns.x + ns.yyyy; - vec4 y = y_ *ns.x + ns.yyyy; - vec4 h = 1.0 - abs(x) - abs(y); - - vec4 b0 = vec4( x.xy, y.xy ); - vec4 b1 = vec4( x.zw, y.zw ); - - //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; - //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; - vec4 s0 = floor(b0)*2.0 + 1.0; - vec4 s1 = floor(b1)*2.0 + 1.0; - vec4 sh = -step(h, vec4(0.0)); - - vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; - vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; - - vec3 p0 = vec3(a0.xy,h.x); - vec3 p1 = vec3(a0.zw,h.y); - vec3 p2 = vec3(a1.xy,h.z); - vec3 p3 = vec3(a1.zw,h.w); - -//Normalise gradients - vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - -// Mix final noise value - vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); - m = m * m; - return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), - dot(p2,x2), dot(p3,x3) ) ); - } - -float snoise(vec2 v) - { - const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 - 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) - -0.577350269189626, // -1.0 + 2.0 * C.x - 0.024390243902439); // 1.0 / 41.0 -// First corner - vec2 i = floor(v + dot(v, C.yy) ); - vec2 x0 = v - i + dot(i, C.xx); - -// Other corners - vec2 i1; - //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 - //i1.y = 1.0 - i1.x; - i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - // x0 = x0 - 0.0 + 0.0 * C.xx ; - // x1 = x0 - i1 + 1.0 * C.xx ; - // x2 = x0 - 1.0 + 2.0 * C.xx ; - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - -// Permutations - i = mod289(i); // Avoid truncation effects in permutation - vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) -+ i.x + vec3(0.0, i1.x, 1.0 )); - - vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); - m = m*m ; - m = m*m ; - -// Gradients: 41 points uniformly over a line, mapped onto a diamond. -// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - -// Normalise gradients implicitly by scaling m -// Approximation of: m *= inversesqrt( a0*a0 + h*h ); - m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); - -// Compute final noise value at P - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); -} diff --git a/src/data/shaders/noisyblocks.frag b/src/data/shaders/noisyblocks.frag deleted file mode 100644 index be6e051..0000000 --- a/src/data/shaders/noisyblocks.frag +++ /dev/null @@ -1,23 +0,0 @@ -/* This is free and unencumbered software released into the public - domain. To the extent possible under law, the author of this file - waives all copyright and related or neighboring rights to it. -*/ - -precision highp float; - -varying vec2 fTexCoord; -varying vec4 fColor; -uniform sampler2D tex; - -uniform vec2 resolution; -uniform float cut; -uniform float range; - -void main(void) { - vec2 coord = floor(fTexCoord * resolution); - vec3 n = vec3(coord.xy, cut); - float p = 1.0 - range * abs(snoise(n)); - vec3 modulated = fColor.rgb * p; - vec4 texColor = texture2D(tex, fTexCoord); - gl_FragColor = vec4(modulated * fColor.a, fColor.a) * texColor; -} diff --git a/src/data/shaders/noisyquads.vert b/src/data/shaders/noisyquads.vert deleted file mode 100644 index 8388349..0000000 --- a/src/data/shaders/noisyquads.vert +++ /dev/null @@ -1,26 +0,0 @@ -/* This is free and unencumbered software released into the public - domain. To the extent possible under law, the author of this file - waives all copyright and related or neighboring rights to it. -*/ - -precision mediump float; - -attribute vec3 position; -attribute vec2 texCoord; -attribute vec4 color; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -uniform float cut; -uniform float range; - -varying vec4 fColor; - -void main(void) { - gl_Position = projection * view * model * vec4(position, 1.0); - vec2 n = vec2(texCoord.x, cut); - float p = 1.0 - range * abs(snoise(n)); - fColor = vec4(p * color.rgb, color.a); -} diff --git a/src/data/sound/explode.wav b/src/data/sound/explode.wav new file mode 100644 index 0000000..7bd3162 Binary files /dev/null and b/src/data/sound/explode.wav differ diff --git a/src/data/sound/land.wav b/src/data/sound/land.wav new file mode 100644 index 0000000..fa8c68b Binary files /dev/null and b/src/data/sound/land.wav differ diff --git a/src/data/sound/pickup.wav b/src/data/sound/pickup.wav new file mode 100644 index 0000000..5c74507 Binary files /dev/null and b/src/data/sound/pickup.wav differ diff --git a/src/data/sound/start.wav b/src/data/sound/start.wav new file mode 100644 index 0000000..8262262 Binary files /dev/null and b/src/data/sound/start.wav differ diff --git a/src/data/sound/starting-line.mp3 b/src/data/sound/starting-line.mp3 new file mode 100644 index 0000000..b579023 Binary files /dev/null and b/src/data/sound/starting-line.mp3 differ diff --git a/src/data/sound/takeoff.mp3 b/src/data/sound/takeoff.mp3 new file mode 100644 index 0000000..50df115 Binary files /dev/null and b/src/data/sound/takeoff.mp3 differ diff --git a/src/index.html b/src/index.html index 1fedd23..c1b8032 100644 --- a/src/index.html +++ b/src/index.html @@ -1,13 +1,13 @@ + data-appid="com.yukkurigames.featherfall"> - Pixel Witch Lesson #6 + Feather Fall - + --> - + @@ -32,7 +33,7 @@ - + @@ -70,7 +71,7 @@ data-yuu-animation="yuu-from-top-right" data-yuu-dismiss-key="f10 escape">
-

Pixel Witch Lesson #6

+

Feather Fall

@@ -113,9 +114,13 @@ data-yuu-animation="yuu-from-top" data-yuu-dismiss-key="escape">
-

Pixel Witch Lesson #6

+

Feather Fall

Designed & Implemented
+
Yukkuri Games
+
Art, Design
+
Jessicatz Fairymeadow
+
Design, Programming
Joe Wreschnig
Additional Programming
@@ -126,10 +131,6 @@ Christoph Burgmer (ayepromise)
-
- Ian McEwan, Ashima Arts - (WebGL Noise) -
Jorik Tangelder (Hammer.js) @@ -143,20 +144,11 @@ Dave Gandy (Font Awesome)
-
Special Thanks
-
Amelia Gorman
-
Jessicatz Fairymeadow
-
Kenney.nl
-
- Richard - "The Clock Guy" - Oliver -

- Copyright ©2014 + Copyright ©2009, 2014 Yukkuri Games and others

@@ -194,7 +186,6 @@


diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..2b55cb0 --- /dev/null +++ b/src/main.css @@ -0,0 +1,65 @@ +body { + background-color: black; +} + +.yuu-toast { + background-color: hsla(276, 33%, 10%, 1.0); + border: solid hsla(276, 33%, 48%, 1.0) 2px; + color: hsla(276, 33%, 95%, 1.0); +} + +.yuu-overlay { + background-color: hsla(276, 33%, 10%, 0.9); + border: solid hsla(276, 33%, 48%, 1.0) 2px; + color: hsla(276, 33%, 95%, 1.0); +} + +.yuu-overlay *:focus { + box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + outline: none; +} + +input[type=range][data-yuu-command] { + background-color: hsla(276, 33%, 25%, 1.0); + border: solid hsla(276, 33%, 48%, 0.33) 1px; +} + +input[type=range][data-yuu-command]::-webkit-slider-thumb { + background-color: hsla(276, 33%, 65%, 1.0); +} + +input[type=range][data-yuu-command]::-moz-range-track { + background-color: hsla(276, 33%, 25%, 1.0); +} + +input[type=range][data-yuu-command]::-moz-range-thumb { + background: hsla(276, 33%, 65%, 1.0); +} + +input[type=checkbox][data-yuu-command]:focus + label[for] { + box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); + outline: 0; +} + +input[data-yuu-command] + label[for]:before { + color: hsla(276, 33%, 65%, 1.0); +} + +a[href], [data-yuu-command] { + color: hsla(276, 33%, 65%, 1.0); +} + +hr { + border: solid hsla(276, 33%, 48%, 0.75) 1px; +} + +.link-footer { + list-style-type: none; + text-align: center; + margin-left: 0; + padding-left: 0; +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..26ec069 --- /dev/null +++ b/src/main.js @@ -0,0 +1,59 @@ +"use strict"; + +var storage; + +/*var PlayerController = new yT(yuu.C, { + constructor: function () { + + }, + + TAPS: ['tick'], +});*/ + +var GameScene = yT(yuu.Scene, { + constructor: function () { + yuu.Scene.call(this); + + this.layer0.resize(-0.5, -0.5, 1, 1); + + this.player = new yuu.E(new yuu.Transform(), + new yuu.QuadC('@player')); + var leftWing = new yuu.E(new yuu.Transform(), + new yuu.QuadC('@left')); + var rightWing = new yuu.E(new yuu.Transform(), + new yuu.QuadC('@right')); + this.player.addChildren(leftWing, rightWing); + this.entity0.addChild(this.player); + + this.ready = yuu.ready([ + new yuu.Material('@player'), + new yuu.Material('@left'), + new yuu.Material('@right')]); + }, + + KEYBINDS: { + space: '+up', + up: '+up', + q: '+dleft_left', + w: '+dleft_right', + o: '+dright_left', + p: '+dright_right', + } +}); + +function start () { + yuu.director.start(); +} + +function load () { + storage = ystorage.getStorage(); + yuu.audio.storage = storage; + var game = new GameScene(); + yuu.director.pushScene(game); +} + +window.addEventListener("load", function() { + yuu.registerInitHook(load); + yuu.init({ backgroundColor: [0, 0, 0, 1], antialias: false }) + .then(start); +}); diff --git a/src/pwl6.css b/src/pwl6.css deleted file mode 100644 index 2b55cb0..0000000 --- a/src/pwl6.css +++ /dev/null @@ -1,65 +0,0 @@ -body { - background-color: black; -} - -.yuu-toast { - background-color: hsla(276, 33%, 10%, 1.0); - border: solid hsla(276, 33%, 48%, 1.0) 2px; - color: hsla(276, 33%, 95%, 1.0); -} - -.yuu-overlay { - background-color: hsla(276, 33%, 10%, 0.9); - border: solid hsla(276, 33%, 48%, 1.0) 2px; - color: hsla(276, 33%, 95%, 1.0); -} - -.yuu-overlay *:focus { - box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - outline: none; -} - -input[type=range][data-yuu-command] { - background-color: hsla(276, 33%, 25%, 1.0); - border: solid hsla(276, 33%, 48%, 0.33) 1px; -} - -input[type=range][data-yuu-command]::-webkit-slider-thumb { - background-color: hsla(276, 33%, 65%, 1.0); -} - -input[type=range][data-yuu-command]::-moz-range-track { - background-color: hsla(276, 33%, 25%, 1.0); -} - -input[type=range][data-yuu-command]::-moz-range-thumb { - background: hsla(276, 33%, 65%, 1.0); -} - -input[type=checkbox][data-yuu-command]:focus + label[for] { - box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0); - outline: 0; -} - -input[data-yuu-command] + label[for]:before { - color: hsla(276, 33%, 65%, 1.0); -} - -a[href], [data-yuu-command] { - color: hsla(276, 33%, 65%, 1.0); -} - -hr { - border: solid hsla(276, 33%, 48%, 0.75) 1px; -} - -.link-footer { - list-style-type: none; - text-align: center; - margin-left: 0; - padding-left: 0; -} diff --git a/src/pwl6.js b/src/pwl6.js deleted file mode 100644 index dc50206..0000000 --- a/src/pwl6.js +++ /dev/null @@ -1,2149 +0,0 @@ -"use strict"; - -var storage; -var SIGILS; -var BOOK; -var handScene; -var circleScene; -var NOISY_BLOCKS; - -var sounds; - -var TOP = 0; -var LEFT = 1; -var BOTTOM = 2; -var RIGHT = 3; - -var MenuScene, CircleScene, HandScene, BookScene, GridScene; - -yuu.Texture.DEFAULTS.magFilter = yuu.Texture.DEFAULTS.minFilter = "nearest"; - -var TICK_ROT = quat.rotateZ(quat.create(), quat.create(), Math.PI / 30); -var TICK_REV = quat.invert(quat.create(), TICK_ROT); -var TICK_ROT2 = quat.rotateZ(quat.create(), quat.create(), Math.PI / 60); - -function sawtooth (p) { - /** Sawtooth wave, Û = 1, T = 2π, f(0) = 0, f′(0) > 0 */ - var _2PI = 2 * Math.PI; - return 2 * (p / _2PI - Math.floor(0.5 + p / _2PI)); -} - -function triangle (p) { - /** Triangle wave, Û = 1, T = 2π, f(0) = 0, f′(0) > 0 */ - return 2 * Math.abs(sawtooth(p + Math.PI / 2)) - 1; -} - -function waveshift (period, peak, xoffset, yoffset) { - period /= 2 * Math.PI; - xoffset = xoffset || 0; - yoffset = yoffset || 0; - return function (f) { - return function (p) { - return yoffset + peak * f.call(this, (p + xoffset) / period); - }; - }; -} - -function cycler (scale) { - var f = waveshift(scale, 0.5, -Date.now(), 0.5)(triangle); - return function () { return f(Date.now()); }; -} - -function load () { - storage = ystorage.getStorage(); - yuu.audio.storage = storage; - - NOISY_BLOCKS = new yuu.ShaderProgram(null, ["@noise.glsl", "@noisyblocks"]); - - SIGILS = new yuu.Material("@sigils"); - BOOK = new yuu.Material("@book", NOISY_BLOCKS); - BOOK.uniforms.cut = yf.volatile(cycler(20000)); - BOOK.uniforms.range = 0.06; - BOOK.texture.ready.then(function (texture) { - BOOK.uniforms.resolution = new Float32Array( - [texture.width / 4, texture.height / 4]); - }); - - sounds = { - tick: new yuu.Instrument("@tick"), - tock: new yuu.Instrument("@tock"), - regear: new yuu.Instrument("@regear"), - winding: new yuu.Instrument("@winding"), - slam: new yuu.Instrument("@slam"), - switch: new yuu.Instrument("@switch"), - clicking: new yuu.Instrument("@clicking"), - bookAppear: new yuu.Instrument("@book-appear"), - switchBroke: new yuu.Instrument({ - sample: { "@switch": { duration: 0.27, offset: 0.1 } } }), - switchOn: new yuu.Instrument({ - sample: { "@switch": { duration: 0.2 } } }), - switchOff: new yuu.Instrument({ - sample: { "@switch": { offset: 0.2 } } }), - chime: new yuu.Instrument({ - envelope: { "0": 1, "0.7": 0.2, "3": 0 }, - modulator: { - envelope: { "0": 1, "0.7": 0.2, "3": 0 }, - frequency: "x1.5", - } - }), - }; - - yuu.director.pushScene(circleScene = new CircleScene()); - yuu.director.pushScene(handScene = new HandScene()); - yuu.director.pushScene(new MenuScene()); - if (!storage.getFlag("instructions")) { - yuu.director.entity0.attach(new yuu.Ticker(function () { - yuu.director.pushScene(new BookScene()); - }, 60)); - } - - return yuu.ready( - [SIGILS, BOOK] - .concat(yf.map(yf.getter.bind(sounds), Object.keys(sounds))) - ); -} - -function start () { - yuu.director.start(); -} - -window.addEventListener("load", function() { - yuu.registerInitHook(load); - yuu.init({ backgroundColor: [0, 0, 0, 1], antialias: false }).then(start); -}); - -var PALETTE = [[ 0.76, 0.13, 0.13 ], - [ 0.33, 0.49, 0.71 ], - [ 0.45, 0.68, 0.32 ], - [ 0.51, 0.32, 0.63 ], - [ 0.89, 0.49, 0.11 ], - [ 1.00, 1.00, 0.30 ]]; - -var LEVELS = [ - { name: "12345654321", - randomSlammer: [3, 5], - deps: "50040@easy 53535@easy 44404@easy 1302@easy 12321@easy 20124@easy" - }, - - { slammer: [1, 1], sets: "tutorial", - scramble: { easy: "01", hard: "0122" } }, - { slammer: [1, 1, 1], deps: "tutorial", - scramble: { easy: "11", hard: "1212" } }, - { slammer: [2, 1], deps: "tutorial", sets: "asymmetric", - scramble: { easy: "32", hard: "3321" } }, - { slammer: [1, 2, 1], deps: "tutorial", sets: "unequal", - scramble: { easy: "112", hard: "3210" } }, - { slammer: [2, 0], deps: "asymmetric", sets: "zero", - scramble: { easy: "23", hard: "032" } }, - { slammer: [2, 0, 2], deps: "zero", - scramble: { easy: "11", hard: "2211" } }, - { slammer: [1, 1, 1, 1], deps: "tutorial" }, - { slammer: [2, 1, 1], deps: "asymmetric" }, - { slammer: [1, 2, 1, 2], deps: "asymmetric", - scramble: { easy: "012" } }, - { slammer: [1, 2, 3, 4], deps: "asymmetric", sets: "solid", - scramble: { easy: "110" } }, - { slammer: [5, 0, 0, 4, 0], deps: "unequal zero", - scramble: { easy: "112" } }, - { slammer: [5, 3, 5, 3, 5], deps: "unequal solid", - scramble: { easy: "3232" } }, - { slammer: [4, 4, 4, 0, 4], deps: "solid zero", - scramble: { easy: "0321" } }, - { slammer: [1, 3, 0, 2], deps: "unequal zero" }, - { slammer: [1, 2, 3, 2, 1], deps: "unequal", - scramble: { easy: "3333" } }, - { slammer: [2, 0, 1, 2, 4], deps: "unequal zero" }, -]; - -function levelName (level) { - return (level.name || level.slammer.join("")).trim(); -} - -function wonLevel (level, difficulty) { - if (level.sets) - storage.setFlag(level.sets); - storage.setFlag(levelName(level) + "@" + difficulty); -} - -function hasBeaten (level, difficulty) { - return storage.getFlag(levelName(level) + "@" + difficulty); -} - -function scrambleForLevel (rnd, level, difficulty) { - var c = difficulty === "easy" ? 0 : 1; - if (difficulty === "random") - c = rnd.randrange(2, 5); - var length = level.slammer.length; - return rnd.randrange(length * c, length * (c + 1)) + 2; -} - -function difficultyForLevel (level) { - if (level.deps && !level.deps.split(" ").every(storage.getFlag, storage)) - return null; - if (hasBeaten(level, "hard")) - return "random"; - if (hasBeaten(level, "easy")) - return "hard"; - else - return "easy"; -} - -function levelRandom (level, difficulty) { - if (difficulty === "random") - return yuu.random; - else - return new yuu.Random(yuu.createLCG(+level.slammer.join(""))); -} - -function generateBoard (rnd, level) { - var size = level.length; - var board = new Array(size); - for (var i = 0; i < size; ++i) - board[i] = yf.repeat(i % PALETTE.length + 1, size); - if (rnd.randbool()) - yuu.transpose2d(board); - return board; -} - -function generateSlammer (rnd, level) { - var s = new Array(level.length); - for (var i = 0; i < s.length; ++i) - s[i] = yf.repeat(0, level[i]); - if (rnd.randbool()) - s.reverse(); - return s; -} - -var AnimationQueue = yT(yuu.C, { - constructor: function () { - this._queue = []; - }, - - attached: function () { - this._queue = []; - }, - - _runNext: function () { - var next = this._queue[0]; - if (next && this.entity) - this.entity.attach(new yuu.Animation( - next.timeline, next.params, this._complete.bind(this))); - }, - - _complete: function () { - var next = this._queue.shift(); - next.resolve(); - this._runNext(); - }, - - enqueue: function (timeline, params) { - return new Promise(function (resolve) { - this._queue.push({ - timeline: timeline, - params: params, - resolve: resolve - }); - // FIXME: Simply chaining the promise doesn't work here - // because the tick between the two handlers is often long - // enough to render a frame, and that frame will have some - // undesirable intermediate state. - if (this._queue.length === 1) - this._runNext(); - }.bind(this)); - }, - - SLOTS: ["animationQueue"] -}); - -var SLAMMER_ROTATE = { - 0: { tween1: { yaw: "yaw" }, duration: 10 } -}; - -var ROTATE_ALL = { - 0: { tweenAll: { yaw: "yaws" }, duration: 10 } -}; - -var SLAMMER_BOUNCE = { - 0: { tween1: { y: 0.5 }, duration: 5, repeat: -1 } -}; - -var SLIDE_BLOCKS = { - 0: { tweenAll: { position: "positions" }, - duration: 8, easing: "linear" }, -}; - -var SLAMMER_SLAM = { - 0: { tween1: { y: -1.5 }, easing: "linear", duration: 6 }, - 6: { event: "slideBoardBlocks" }, - 15: { event: "slam", - tween1: { y: 0 }, easing: "linear", duration: 8 } -}; - -var GRID_DISMISS = { - 0: { tween1: { yaw: 2 * Math.PI, x: "x", y: "y", scale: [0.3, 0.3, 1] }, - duration: 45 } -}; - -var GRID_FINISHED = { - 0: { tween: { arm: { scale: [0, 0, 1], yaw: "armYaw", y: "armY" }, - board: { y: "boardY" } }, - duration: 45 } -}; - -function rotateCw (d) { return (--d + 4) % 4; } -function rotateCcw (d) { return ++d % 4; } -function opposite (d) { return (d + 2) % 4; } - -var FlagSet = yT({ - /** Manage a set of semaphore-like counting flags. */ - - constructor: function () { - /** Construct a flag set for the provided flags. - - Flags are initialized to 0 by default. - */ - this._counts = {}; - for (var i = 0; i < arguments.length; ++i) - this._counts[arguments[i]] = 0; - }, - - increment: function () { - /** Increment the provided flags. */ - for (var i = 0; i < arguments.length; ++i) - this._counts[arguments[i]]++; - }, - - decrement: function () { - /** Decrement the provided flags. - - No underflow checks are performed. A flag with a negative - value is considered set exactly as a flag with a positive - value. - */ - for (var i = 0; i < arguments.length; ++i) - this._counts[arguments[i]]--; - }, - - some: function () { - /** Return true if any of the provided flags are set. */ - return yf.some.call(this._counts, yf.getter, arguments); - }, - - every: function () { - /** Return true if all of the provided flags are set. */ - return yf.every.call(this._counts, yf.getter, arguments); - }, - - none: function () { - /** Return true if none of the provided flags are set. */ - return !this.some.apply(this, arguments); - }, - - incrementer: function () { - /** Provide a bound 0-ary function to increment the provided flags. - - Useful for wrapps around context-free callbacks. - */ - var that = this, args = arguments; - return function () { that.increment.apply(that, args); }; - }, - - decrementer: function () { - /** Provide a bound 0-ary function to decrement the provided flags. - - Useful for wrapps around context-free callbacks. - */ - var that = this, args = arguments; - return function () { that.decrement.apply(that, args); }; - } -}); - -var BoardController = yT(yuu.C, { - constructor: function (rnd, level, colors) { - this.contents = generateBoard(rnd, level.slammer); - this.colors = colors; - }, - updateChildren: function () { - this.entity.data.quads.forEach(function (q) { - q.quad.position = [q.x, q.y]; - var i = this.contents[q.x][q.y]; - q.quad.color = this.colors[i]; - q.quad.texBounds = [i / 6, 0.5, (i + 1) / 6, 1.0]; - }, this); - }, - isComplete: function() { - var x, y; - var rows = true, cols = true; - for (x = 1; x < this.contents.length && rows; ++x) - for (y = 0; y < this.contents[x].length && rows; ++y) - rows = this.contents[x - 1][y] === this.contents[x][y]; - for (x = 0; x < this.contents.length && cols; ++x) - for (y = 1; y < this.contents[x].length && cols; ++y) - cols = this.contents[x][y - 1] === this.contents[x][y]; - return rows || cols; - }, - - shift: [ - function (x, replacement) { - var lost = this.contents[x].pop(); - this.contents[x].unshift(replacement); - return lost; - }, - function (y, replacement) { - yuu.transpose2d(this.contents); - var lost = this.shift[BOTTOM].call(this, y, replacement); - yuu.transpose2d(this.contents); - return lost; - }, - function (x, replacement) { - var lost = this.contents[x].shift(); - this.contents[x].push(replacement); - return lost; - }, - function (y, replacement) { - yuu.transpose2d(this.contents); - var lost = this.shift[TOP].call(this, y, replacement); - yuu.transpose2d(this.contents); - return lost; - } - ], - - SLOTS: ["controller"] -}); - -var SlammerController = yT(yuu.C, { - constructor: function (rnd, level, colors) { - this.blocks = generateSlammer(rnd, level.slammer); - this.orientation = TOP; - this.colors = colors; - this._undoRecord = []; - }, - isComplete: function() { - return yf.none(yf.some.bind(null, null), this.blocks); - }, - updateChildren: function () { - this.entity.data.quads.forEach(function (q) { - var i = this.blocks[q.x][q.y]; - q.quad.position = [q.x, q.y]; - q.quad.color = this.colors[i]; - q.quad.texBounds = [i / 6, 0.5, (i + 1) / 6, 1.0]; - }, this); - }, - - lastUndoRecord: { get: function () { - return yf.last(this._undoRecord); - } }, - - clearUndoRecord: function () { - this._undoRecord = []; - }, - - slam: function (board) { - var undoable = (this.orientation !== this.lastUndoRecord); - var length = this.blocks.length; - this.orientation = opposite(this.orientation); - this.blocks = yf.mapr.call(this, function (a, y) { - return yf.map(board.shift[this.orientation].bind(board, y), a) - .reverse(); - }, this.blocks, (this.orientation & 2) - ? yf.range(length) - : yf.range(length - 1, -1, -1)); - yf.each(function (i) { - i.x = length - (i.x + 1); - }, this.entity.data.quads); - this.updateChildren(); - board.updateChildren(); - if (undoable) - this._undoRecord.push(this.orientation); - else - this._undoRecord.pop(); - }, - - SLOTS: ["controller"] -}); - -function randSide (rnd, except) { - return (rnd || yuu.random).choice( - yf.without([TOP, LEFT, BOTTOM, RIGHT], except)); -} - -var HANDS_LEFT = { - 0: { tween: { left: { yaw: -0.3 } }, duration: 3 }, - 3: { tween: { left: { yaw: 0.0 } }, duration: 7 }, -}; - -var HANDS_RIGHT = { - 0: { tween: { right: { yaw: -0.3 } }, duration: 3 }, - 3: { tween: { right: { yaw: 0.0 } }, duration: 7 }, -}; - -var HANDS_UNDO = { - 0: { tween: { left: { yaw: 0.2 }, right: { yaw: 0.2 } }, - duration: 3 }, - 3: { tween: { left: { yaw: 0.0 }, right: { yaw: 0.0 } }, - duration: 7 } -}; - -var HANDS_MENU_CHOICE = { - 0: { tween: { left: { x: -1.3 }, - right: { x: -1.3 } }, - duration: 15, easing: "ease_in" - }, - - 10: { tween: { left: { scaleX: 1 }, - right: { scaleX: 1 } }, - duration: 20 }, - - 20: { set: { leftQuad: { color: "frontColor" }, - rightQuad: { color: "frontColor" } }, - tween: { left: { x: 0 }, right: { x: 0 } }, - duration: 15 - }, -}; - -var HANDS_RETURN = { - 0: { tween: { left: { x: -1.3 }, - right: { x: -1.3 } }, - duration: 20 - }, - - 10: { tween: { left: { scaleX: -1 }, - right: { scaleX: -1 } }, - duration: 20 }, - - 20: { set: { leftQuad: { color: "backColor" }, - rightQuad: { color: "backColor" } }, - tween: { left: { x: -1 }, right: { x: -1 } }, - duration: 10 - }, -}; - -var HANDS_SLAM = [ - // TOP - { 0: { tween: { left: { yaw: -0.2, scaleX: 0.8, y: -0.1 }, - right: { yaw: -0.2, scaleX: 0.8, y: -0.1 }, - }, duration: 10, repeat: -1 }, - }, - - // LEFT - { 0: { tween: { left: { scaleX: 0.8, x: 0.1 }, - right: { scaleX: 0.9 }, - }, duration: 10, repeat: -1 }, - }, - - // BOTTOM - { 0: { tween: { left: { yaw: 0.2, scaleX: 0.8 }, - right: { yaw: 0.2, scaleX: 0.8 }, - }, duration: 10, repeat: -1 }, - }, - - // RIGHT - { 0: { tween: { left: { scaleX: 0.9 }, - right: { scaleX: 0.8, x: 0.1 }, - }, duration: 10, repeat: -1 }, - }, -]; - -var HANDS_ROTATE_CW = { - 0: { tween: { left: { scaleX: 0.8 } }, duration: 5 }, - 5: { tween: { left: { scaleX: 1.0 } }, duration: 5 }, -}; - -var HANDS_ROTATE_CCW = { - 0: { tween: { right: { scaleX: 0.8 } }, duration: 5 }, - 5: { tween: { right: { scaleX: 1 } }, duration: 5 }, -}; - -var BUTTONS_IN = { - 0: { tween: { a: { x: 0 }, b: { x: 1.5 } }, duration: 25 } -}; - -var BUTTONS_OUT = { - 0: { tween: { a: { x: -1.5 }, b: { x: 0 } }, duration: 25 } -}; - -HandScene = yT(yuu.Scene, { - constructor: function () { - yuu.Scene.call(this); - var hands = new yuu.Material("@hand"); - this.left = new yuu.E(new yuu.Transform()); - var l = new yuu.E( - new yuu.Transform([-0.5, 0.5, 0]), - new yuu.DataC({ command: "left" }), - this.leftQuad = new yuu.QuadC(hands)); - this.left.addChild(l); - var r = new yuu.E( - new yuu.Transform([-0.5, 0.5, 0]), - new yuu.DataC({ command: "right" }), - this.rightQuad = new yuu.QuadC(hands)); - this.right = new yuu.E(new yuu.Transform()); - this.right.addChild(r); - var SIZE_X = yuu.random.gauss(1.2, 0.15) * 0.35; - var SIZE_Y = yuu.random.gauss(1.1, 0.05) * 0.51; - var hand = yuu.random.randrange(3); - this.leftQuad.texBounds = this.rightQuad.texBounds = [ - hand / 2.99, 0, (hand + 1) / 3.01, 1]; - this.layer0.resize(-0.75, 0, 1.5, 1.5); - var leftWrist = new yuu.E( - new yuu.Transform([-0.20, 0, 0], null, - [SIZE_X, SIZE_Y, 1])); - var rightWrist = new yuu.E( - new yuu.Transform([0.20, 0, 0], null, - [-SIZE_X, SIZE_Y, 1])); - leftWrist.addChild(this.left); - rightWrist.addChild(this.right); - this.addEntities(leftWrist, rightWrist); - this.backColor = yuu.hslToRgb( - (yuu.random.gauss(0.1, 0.1) + 10) % 1, - yuu.random.uniform(0.2, 0.7), - yuu.random.uniform(0.2, 0.6), - 1.0); - this.leftQuad.alpha = this.rightQuad.alpha = 0.2; - var hsl = yuu.rgbToHsl(this.backColor); - hsl[2] = hsl[2].lerp(1, 0.15); - hsl[1] = hsl[1].lerp(0, 0.30); - hsl[3] = 0.4; - this.frontColor = yuu.hslToRgb(hsl); - this.leftQuad.color = this.rightQuad.color = this.frontColor; - this.ready = hands.ready; - - function Button (i, command) { - return new yuu.E( - new yuu.Transform(), - new yuu.DataC({ command: command }), - new yuu.QuadC(SIGILS) - .setTexBounds([i / 6, 0, (i + 1) / 6, 0.5]) - .setColor(PALETTE[i])); - } - - this.helpButton = new Button(1, "help"); - this.backButton = new Button(3, "back"); - this.backButton.transform.x -= 1.5; - this.leftButtons = new yuu.E(new yuu.Transform()); - this.leftButtons.addChildren(this.helpButton, this.backButton); - this.rightButton = new Button(2, "showOverlay preferences"); - this.leftButtons.transform.scale - = this.rightButton.transform.scale - = [0.075, 0.075, 1]; - this.entity0.addChildren(this.leftButtons, this.rightButton); - this.buttons = [this.helpButton, this.backButton, this.rightButton, - l, r]; - }, - - inputs: { - resize: function () { - var base = new yuu.AABB(-0.75, 0, 0.75, 1.5); - var vp = base.matchAspectRatio(yuu.viewport); - vp.y1 -= vp.y0; - vp.y0 = 0; - this.leftButtons.transform.xy = [ - vp.x0 + this.leftButtons.transform.scaleX, - vp.y1 - this.leftButtons.transform.scaleY]; - this.rightButton.transform.xy = [ - vp.x1 - this.rightButton.transform.scaleX, - vp.y1 - this.rightButton.transform.scaleY]; - this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h); - }, - - mousemove: function (p) { - p = this.layer0.worldFromDevice(p); - this.cursor = ""; - for (var i = 0; i < this.buttons.length; ++i) { - if (this.buttons[i].transform.contains(p)) { - this.cursor = "pointer"; - } - } - }, - - tap: function (p) { - p = this.layer0.worldFromDevice(p); - for (var i = 0; i < this.buttons.length; ++i) { - if (this.buttons[i].transform.contains(p)) { - yuu.director.execute(this.buttons[i].data.command); - return true; - } - } - }, - - doubletap: function () { - return this.inputs.tap.apply(this, arguments); - }, - }, - - _anim: function (timeline) { - this.entity0.attach(new yuu.Animation( - timeline, { - left: this.left.transform, - right: this.right.transform, - leftQuad: this.leftQuad, - rightQuad: this.rightQuad, - frontColor: this.frontColor, - backColor: this.backColor - })); - }, - - undo: function () { this._anim(HANDS_UNDO); }, - movedLeft: function () { this._anim(HANDS_LEFT); }, - movedRight: function () { this._anim(HANDS_RIGHT); }, - slam: function (o) { this._anim(HANDS_SLAM[o]); }, - rotatedCw: function () { this._anim(HANDS_ROTATE_CW); }, - rotatedCcw: function () { this._anim(HANDS_ROTATE_CCW); }, - menuChoice: function () { - this.entity0.attach(new yuu.Animation( - BUTTONS_IN, { - a: this.backButton.transform, - b: this.helpButton.transform - })); - this._anim(HANDS_MENU_CHOICE); - }, - finished: function () { - this.entity0.attach(new yuu.Animation( - BUTTONS_OUT, { - a: this.backButton.transform, - b: this.helpButton.transform - })); - this._anim(HANDS_RETURN); - }, -}); - -var GRID_APPEAR = { - 0: { set1: { y: 5 }, - tween1: { y: 0 }, duration: 10 }, -}; - -GridScene = yT(yuu.Scene, { - constructor: function (level, difficulty) { - yuu.Scene.call(this); - this.entity0.attach(new yuu.Transform()); - this.level = level; - this.difficulty = difficulty; - this._locks = new FlagSet("slam", "spin", "quit"); - var rnd = levelRandom(level, difficulty); - var colors = yuu.random.shuffle(PALETTE.slice()); - colors.unshift([1.0, 1.0, 1.0]); - this.board = new yuu.E(new BoardController(rnd, level, colors), - new yuu.Transform(), - new yuu.DataC({ quads: [] })); - this.slammer = new yuu.E(new SlammerController(rnd, level, colors), - new yuu.Transform(), - new yuu.DataC({ quads: [] })); - this.slammerHead = new yuu.E(new yuu.Transform()); - this.slammerRoot = new yuu.E(new yuu.Transform()); - var length = level.slammer.length; - var maxSize = length * length; - var slammerBatch = new yuu.QuadBatchC(maxSize); - slammerBatch.material = SIGILS; - var boardBatch = new yuu.QuadBatchC(maxSize); - boardBatch.material = SIGILS; - this.slammerRoot.transform.xy = [length / 2 - 0.5, length / 2 - 0.5]; - this.slammerHead.transform.xy = [-length / 2 + 0.5, length / 2 + 2]; - this.slammerRoot.addChild(this.slammerHead); - this.slammerHead.addChild(this.slammer); - this.slammer.attach(slammerBatch); - this.board.attach(boardBatch); - yf.irange.call(this, function (x) { - yf.irange.call(this, function (y) { - var quad = boardBatch.createQuad(); - quad.color = colors[this.board.controller.contents[x][y]]; - quad.position = [x, y]; - this.board.data.quads.push({ quad: quad, x: x, y: y }); - }, length); - }, length); - - for (var x = 0; x < this.slammer.controller.blocks.length; ++x) { - for (var y = 0; y < this.slammer.controller.blocks[x].length; ++y) { - var quad = slammerBatch.createQuad(); - quad.color = colors[this.slammer.controller.blocks[x][y]]; - quad.position = [x, y]; - this.slammer.data.quads.push({ quad: quad, x: x, y: y }); - } - } - this.addEntities(this.board, this.slammerRoot); - this.scramble(rnd); - if (!(this.cheating = yuu.director.input.pressed["`"])) { - this.slammer.controller.clearUndoRecord(); - } - this._dragging = 0; - - this.gridBB = new yuu.AABB(-0.5, -0.5, length - 0.5, length - 0.5); - this.leftBB = new yuu.AABB( - -Infinity, this.gridBB.y0, this.gridBB.x0, this.gridBB.y1); - this.rightBB = new yuu.AABB( - this.gridBB.x1, this.gridBB.y0, Infinity, this.gridBB.y1); - this.topBB = new yuu.AABB( - this.gridBB.x0, this.gridBB.y1, this.gridBB.x1, Infinity); - this.bottomBB = new yuu.AABB( - this.gridBB.x0, -Infinity, this.gridBB.x1, this.gridBB.y0); - }, - - init: function () { - this._locks.increment("slam"); - this.entity0.attach(new yuu.Animation( - GRID_APPEAR, { $: this.slammer.transform }, - this._locks.decrementer("slam"))); - }, - - scramble: function (rnd) { - var scramble = (this.level.scramble || {})[this.difficulty]; - var slammerCon = this.slammer.controller; - var boardCon = this.board.controller; - if (!scramble) { - var count = scrambleForLevel(rnd, this.level, this.difficulty); - while (this.isComplete()) { - var c = count; - while (c--) { - slammerCon.orientation = randSide(rnd, slammerCon.orientation); - slammerCon.slam(boardCon); - } - } - } else { - for (var i =0; i < scramble.length; ++i) { - slammerCon.orientation = +scramble[i]; - slammerCon.slam(boardCon); - } - } - slammerCon.orientation = randSide(); - this.slammerRoot.transform.yaw = slammerCon.orientation * Math.PI / 2; - }, - - isComplete: function () { - return this.slammer.controller.isComplete() - && this.board.controller.isComplete(); - }, - - rotateTo: yuu.cmd(function (orientation) { - return new Promise(function (resolve) { - if (this._locks.some("spin")) - return this; - this.slammer.controller.orientation = orientation; - this._locks.increment("slam"); - var yaw0 = this.slammerRoot.transform.yaw; - var yaw1 = orientation * Math.PI / 2; - - sounds.clicking.play(); - this.entity0.attach(new yuu.Animation( - SLAMMER_ROTATE, { - $: this.slammerRoot.transform, - yaw: yaw0 + yuu.normalizeRadians(yaw1 - yaw0) - }, function () { - this._locks.decrement("slam"); - resolve(this); - }.bind(this))); - }.bind(this)); - }, "", "move the slammer to the top"), - - rotateCw: yuu.cmd(function () { - handScene.rotatedCw(); - circleScene.rotated(); - this.rotateTo(rotateCw(this.slammer.controller.orientation)); - }, "", "rotate the active piece clockwise"), - - rotateCcw: yuu.cmd(function () { - handScene.rotatedCcw(); - circleScene.rotated(); - this.rotateTo(rotateCcw(this.slammer.controller.orientation)); - }, "", "rotate the active piece counter-clockwise"), - - left: yuu.cmd(function () { this.rotateCw(); }), - right: yuu.cmd(function () { this.rotateCcw(); }), - - undo: yuu.cmd(function (v) { - var con = this.slammer.controller; - var _ = function () { this.undo(this._undo); }.bind(this); - if ((this._undo = v) && con.lastUndoRecord !== undefined) { - if (con.orientation !== con.lastUndoRecord) { - circleScene.reverse(); - handScene.undo(); - this.rotateTo(con.lastUndoRecord) - .then(this.slam.bind(this)) - .then(_); - } else { - this.slam().then(_); - } - } - }, "", "rotate the active piece counter-clockwise"), - - checkWon: function () { - if (this.isComplete() && !this._locks.some("quit")) { - this._locks.increment("quit", "slam", "spin"); - var firstTime = !hasBeaten(this.level, this.difficulty); - if (!this.cheating) - wonLevel(this.level, this.difficulty); - var scene = new MenuScene(this.level); - yuu.director.pushScene(scene); - scene.didWinLevel(this.level, this.difficulty, firstTime); - this.entity0.attach(new yuu.Animation( - GRID_FINISHED, { - arm: this.slammerRoot.transform, - armYaw: this.slammerRoot.transform.yaw + 3 * Math.PI, - armY: this.slammerRoot.transform.y + 1.5, - board: this.board.transform, - boardY: this.level.slammer.length * 3 - }, function () { - yuu.director.removeScene(this); - }.bind(this) - )); - } - }, - - slideBoardBlocks: function (anim, params) { - var dx = 0, dy = 0; - var orientation = this.slammer.controller.orientation; - switch (orientation) { - case LEFT: dx = 1.5; break; - case TOP: dy = -1.5; break; - case RIGHT: dx = -1.5; break; - case BOTTOM: dy = 1.5; break; - } - var sgnx = Math.sign(dx); - var sgny = Math.sign(dy); - var $s = []; - var positions = []; - var blocks = this.slammer.controller.blocks; - this.slammer.data.quads.forEach(function (q) { - var d = blocks[q.x].length; - $s.push(q.quad); - positions.push([q.quad.position[0], q.quad.position[1] - d]); - }, this); - this.board.data.quads.forEach(function (q) { - var x = orientation === TOP ? q.x : blocks.length - (q.x + 1); - var y = orientation === LEFT ? q.y : blocks.length - (q.y + 1); - - $s.push(q.quad); - positions.push([q.quad.position[0] + sgnx * blocks[y].length, - q.quad.position[1] + sgny * blocks[x].length]); - }, this); - this.entity0.attach(new yuu.Animation(SLIDE_BLOCKS, { - $s: $s, - positions: positions - })); - }, - - slam: yuu.cmd(function () { - var r = new Promise(function (resolve, reject) { - if (this._locks.some("slam")) { - reject("slamming is locked"); - return; - } - this._locks.increment("spin", "slam"); - circleScene.slam(); - sounds.slam.play(); - handScene.slam(this.slammer.controller.orientation); - this.entity0.attach(new yuu.Animation( - SLAMMER_SLAM, { - $: this.slammer.transform, - slam: function () { - this._locks.decrement("spin"); - this.slammer.controller.slam(this.board.controller); - this.slammerRoot.transform.yaw = Math.PI / 2 * - this.slammer.controller.orientation; - }.bind(this), - slideBoardBlocks: this.slideBoardBlocks.bind(this) - }, function () { - this.checkWon(); - this._locks.decrement("slam"); - resolve(this); - }.bind(this))); - }.bind(this)); - return r; - }, "", "slam the active piece"), - - back: yuu.cmd(function (x, y) { - if (this._locks.some("quit")) - return; - this._locks.increment("quit", "slam", "spin"); - var scene = new MenuScene(this.level); - yuu.director.pushScene(scene); - var v = [x || yuu.random.uniform(-1, 1), - y || yuu.random.uniform(-1, 1)]; - var size = this.board.controller.contents.length * 5; - vec2.scale(v, vec2.normalize(v, v), size); - this.entity0.attach(new yuu.Animation( - GRID_DISMISS, { - $: this.entity0.transform, - x: v[0], y: v[1] - }, function () { - yuu.director.removeScene(this); - }.bind(this) - )); - circleScene.lose(); - }, "", "go back to the menu"), - - slammerBB: { get: function (p) { - var length = this.level.slammer.length; - switch (this.slammer.controller.orientation) { - case LEFT: - return new yuu.AABB(-Infinity, -0.5, -1, length - 0.5); - case RIGHT: - return new yuu.AABB(length + 1, -0.5, Infinity, length - 0.5); - case TOP: - return new yuu.AABB(-0.5, length + 1, length - 0.5, Infinity); - case BOTTOM: - return new yuu.AABB(-0.5, -Infinity, length - 0.5, -1); - } - } }, - - _swipe: function (p0, p1) { - p0 = this.layer0.worldFromDevice(p0); - p1 = this.layer0.worldFromDevice(p1); - if (this.slammerBB.contains(p0)) { - this.slam(); - return true; - } - if (this.gridBB.contains(p0) && !this.gridBB.contains(p1)) { - this.back(p1.x - p0.x, p1.y - p0.y); - return true; - } - }, - - inputs: { - resize: function () { - var length = this.level.slammer.length; - var base = new yuu.AABB(-length - 2.5, -length - 2.5, - 2 * length + 1.5, 2 * length + 1.5); - var vp = base.matchAspectRatio(yuu.viewport); - this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h); - }, - - tap: function (p) { - p = this.layer0.worldFromDevice(p); - if (this.gridBB.contains(p)) { - this.slam(); - return true; - } - }, - - touch: function (p) { - var length = this.level.slammer.length; - var middle = (length - 1) / 2; - p = this.layer0.worldFromDevice(p); - if (this.slammerBB.contains(p)) { - this.slammer.attach(new yuu.Animation( - SLAMMER_BOUNCE, { $: this.slammer.transform })); - } else if (this.leftBB.contains(p)) { - this.rotateTo(LEFT); - handScene.rotatedCw(); - return true; - } else if (this.rightBB.contains(p)) { - this.rotateTo(RIGHT); - handScene.rotatedCcw(); - return true; - } else if (this.topBB.contains(p)) { - this.rotateTo(TOP); - if (p.x < middle) - handScene.rotatedCw(); - else - handScene.rotatedCcw(); - return true; - } else if (this.bottomBB.contains(p)) { - this.rotateTo(BOTTOM); - if (p.x < middle) - handScene.rotatedCw(); - else - handScene.rotatedCcw(); - return true; - } - }, - - doubletap: function () { - return this.inputs.tap.apply(this, arguments); - }, - - hold: function (p) { - p = this.layer0.worldFromDevice(p); - if (this.gridBB.contains(p)) { - this.undo(true); - return true; - } - }, - - dragstart: function (p) { - p = this.layer0.worldFromDevice(p); - this._dragging = this.slammerBB.contains(p); - }, - - drag: function (p0, p1) { - var p = this.layer0.worldFromDevice(p1); - if (this._dragging && !this._locks.some("slam")) { - var inGrid = this.gridBB.contains(p); - var length = this.level.slammer.length; - var o; - if (this._dragging === true && inGrid) { - this.slam(); - } else if (p.x > 0 && p.x < length && !inGrid) { - o = p.y < 0 ? BOTTOM : TOP; - if (o !== this.slammer.controller.orientation) { - this.rotateTo(o); - this._dragging = 2; - } - } else if (p.y > 0 && p.y < length && !inGrid) { - o = p.x < 0 ? LEFT : RIGHT; - if (o !== this.slammer.controller.orientation) { - this.rotateTo(o); - this._dragging = 2; - } - } - } - return this._dragging; - }, - - dragend: function (p0, p1) { - this._dragging = false; - }, - - release: function () { - this.undo(false); - }, - - swipeleft: function (p0, p1) { - return this._swipe(p0, p1); - }, - swiperight: function (p0, p1) { - return this._swipe(p0, p1); - }, - swipeup: function (p0, p1) { - return this._swipe(p0, p1); - }, - swipedown: function (p0, p1) { - return this._swipe(p0, p1); - }, - }, - - KEYBINDS: { - w: "rotateCw", - a: "rotateCcw", - s: "rotateCcw", - d: "rotateCw", - left: "rotateCcw", - right: "rotateCw", - up: "rotateCw", - down: "rotateCcw", - shift: "slam", - space: "slam", - z: "slam", - backspace: "+undo", - c: "+undo", - escape: "back", - back: "back", - gamepadbutton0: "slam", - gamepadbutton1: "+undo", - gamepadbutton2: "slam", - gamepadbutton3: "+undo", - gamepadbutton4: "rotateCcw", - gamepadbutton5: "rotateCw", - gamepadbutton8: "back", - gamepadbutton14: "rotateCcw", - gamepadbutton15: "rotateCw", - } -}); - -var MENU_APPEAR = { - 0: [{ set1: { x: 5, y: 5, scaleX: 0, scaleY: 0 } }, - { tween1: { x: 0, y: 0, scaleX: 1 }, duration: 24 }, - { tween1: { scaleY: 1 }, - duration: 55, easing: yuu.Tween.METASPRING(1, 10)}], -}; - -var MENU_SLIDE = { - 0: { tween1: { x: "x" }, duration: "duration" } -}; - -var FLASH = { - 0: { tween1: { luminance: 1, alpha: 1 }, duration: 32, repeat: -1 } -}; - -var MENU_SLAM = { - 0: { tween: { cursor: { y: "mid" } }, duration: 8 }, - 8: { tween: { cursor: { y: "line" }, - select: { y: -1.0 } - }, duration: 12 }, - 20: { tween: { scene: { y: 10 }, - select: { y: -11.5, scale: [3, 3, 1] } - }, duration: 18 }, - 38: { event: "appear", - tween: { select: { y: 0, scale: [1, 0, 1] } }, - duration: 20 } -}; - -function menuEntityForLevel (level, i) { - var activated = false; - function randomizeSlammer () { - var min = level.randomSlammer[0]; - var max = level.randomSlammer[1]; - var size = yuu.random.randrange(min, max + 1); - level.slammer = []; - do { - for (var i = 0; i < size; ++i) - level.slammer[i] = yuu.random.randrange(0, size); - } while (Math.min.apply(Math, level.slammer) === max - || Math.max.apply(Math, level.slammer) === 0); - } - - function generateQuads() { - batch.disposeAll(); - var rgb = PALETTE[i % PALETTE.length]; - var fit = level.slammer.length + 1; - batch.createQuad().color = rgb; - level.slammer.forEach(function (size, y) { - var c = batch.createQuad(); - c.color = [0, 0, 0]; - c.alpha = hasBeaten(level, "easy") ? 0.5 : 1.0; - c.size = [size / fit, 1 / fit]; - c.position = [0, -0.5 + (y + 1) / fit]; - }); - ce.data.flasher = batch.createQuad(); - ce.data.flasher.alpha = 0; - return ce; - } - - if (level.randomSlammer) - randomizeSlammer(); - - // 14 = maximum slammer size + 1 background + 1 flasher - var batch = new yuu.QuadBatchC(14); - var ce = new yuu.E( - new yuu.Transform([2 * i, 0, 0]), - batch, - new yuu.DataC({ - activate: function () { - activated = true; - var scene = new GridScene(level, difficultyForLevel(level)); - yuu.director.insertUnderScene(scene); - } - }) - ); - - if (level.randomSlammer) { - ce.attach(new yuu.Ticker(function () { - if (!activated && yuu.random.randbool(0.7)) { - randomizeSlammer(); - ++i; - generateQuads(); - } - return !activated; - }, 30, 15)); - } - - generateQuads(); - return ce; -} - -var HAND_TICK_BACK = { - 0: { tween1: { rotation: "rotation" }, duration: 6, repeat: -1 } -}; - -MenuScene = yT(yuu.Scene, { - constructor: function (initialLevel) { - yuu.Scene.call(this); - this.entity0.attach(new yuu.Transform(), - new AnimationQueue()); - - this.pointer = new yuu.E( - new yuu.Transform([5, 8, 0]), - new yuu.QuadC()); - - var menu = this.menu = new yuu.E(new yuu.Transform([5, 6.5, 0])); - this.addEntities(menu, this.pointer); - this.availableLevels = LEVELS.filter(difficultyForLevel); - this.availableLevels - .map(menuEntityForLevel) - .forEach(menu.addChild, menu); - - var initialIdx = this.availableLevels.indexOf(initialLevel); - this._locks = new FlagSet("slam", "move"); - this.activeIndex = Math.max(initialIdx, 0); - menu.transform.x = 5 - 2 * this.activeIndex; - this.changeActiveIndex(this.activeIndex, false); - this._dragStartX = null; - - this.entity0.attach( - new yuu.Ticker(this._animation.bind(this), 60)); - }, - - _animation: function (count) { - var length = this.availableLevels.length; - var range = Math.pow(2, length); - var rand = yuu.random.randrange(range); - var targets = []; - var yaws = []; - for (var i = 0; i < length; ++i) { - var child = this.menu.children[i]; - var level = this.availableLevels[i]; - var won = hasBeaten(level, "hard"); - if ((won || ((count ^ i) & 1)) && ((count ^ rand) & (1 << i))) { - var dyaw = won - ? yuu.random.randsign(Math.PI / 2) - : -Math.PI / 2; - targets.push(child.transform); - yaws.push(child.transform.yaw + dyaw); - } - } - if (targets.length) { - this.entity0.attach(new yuu.Animation( - ROTATE_ALL, { $s: targets, yaws: yaws })); - } - circleScene.clockTick(TICK_ROT2, HAND_TICK_BACK); - sounds[["tick", "tock"][count & 1]] - .createSound(yuu.audio, yuu.audio.currentTime, 0, 0.2, 1.0) - .connect(yuu.audio.music); - - return true; - }, - - init: function () { - circleScene.toBottom(); - handScene.finished(); - this._locks.increment("slam", "move"); - this.entity0.animationQueue.enqueue( - MENU_APPEAR, - { $: this.entity0.transform }) - .then(this._locks.decrementer("slam", "move")); - }, - - didWinLevel: function (level, difficulty, firstTime) { - var idx = this.availableLevels.indexOf(level); - circleScene.win(); - if (firstTime) - this.entity0.animationQueue.enqueue( - FLASH, { $: this.menu.children[idx].data.flasher }); - for (var i = idx; i < this.availableLevels.length; ++i) { - if (!hasBeaten(this.availableLevels[i], difficulty)) { - this._locks.increment("move"); - this.changeActiveIndex(i, true) - .then(this._locks.decrementer("move")); - - break; - } - } - }, - - changeActiveIndex: function (index, animate) { - var oldIndex = this.activeIndex; - var p; - this.activeIndex = index = yf.clamp( - index, 0, this.menu.children.length - 1); - if (index !== oldIndex && animate) { - this._locks.increment("slam"); - var duration = Math.ceil(8 * Math.abs(oldIndex - index)); - p = this.entity0.animationQueue.enqueue( - MENU_SLIDE, { - $: this.menu.transform, - x: 5 - 2 * index, - duration: duration - }); - p.then(this._locks.decrementer("slam")); - } - return p || Promise.resolve(); - }, - - left: yuu.cmd(function () { - if (!this._locks.some("move")) { - sounds[this.activeIndex === 0 ? "switchBroke" : "switch"].play(); - handScene.movedLeft(); - this.changeActiveIndex(this.activeIndex - 1, true); - } - }, "move the cursor left"), - right: yuu.cmd(function () { - if (!this._locks.some("move")) { - sounds[this.activeIndex === this.availableLevels.length - 1 - ? "switchBroke" : "switch"].play(); - handScene.movedRight(); - this.changeActiveIndex(this.activeIndex + 1, true); - } - }, "move the cursor right"), - - slam: yuu.cmd(function () { - if (this._locks.some("slam")) - return; - var activeChild = this.menu.children[this.activeIndex]; - this._locks.increment("slam", "move"); - handScene.menuChoice(); - circleScene.toBack(); - circleScene.slam(); - sounds.winding.play(); - this.entity0.animationQueue.enqueue( - MENU_SLAM, { - cursor: this.pointer.transform, - select: activeChild.transform, - scene: this.entity0.transform, - mid: this.pointer.transform.y - 0.5, - line: this.pointer.transform.y - 1.5, - appear: activeChild.data.activate - }).then(function () { - this._locks.decrementer("slam", "move"); - yuu.director.removeScene(this); - }.bind(this)); - }, "choose the active menu item"), - - inputs: { - resize: function () { - var base = new yuu.AABB(0, 0, 10, 10); - var vp = base.matchAspectRatio(yuu.viewport); - this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h); - }, - - pinchout: function (p0, p1) { - p0 = this.layer0.worldFromDevice(p0); - p1 = this.layer0.worldFromDevice(p1); - if (vec2.sqrDist(p0, p1) > 1) { - this.slam(); - return true; - } - }, - - hold: function (p) { - return this.inputs.dragstart.call(this, p); - }, - - release: function (p) { - if (this._dragStartX !== null) - return this.inputs.dragend.call(this, p); - }, - - dragstart: function (p) { - if (this._locks.some("move")) - return false; - p = this.layer0.worldFromDevice(p); - if (p.y > 6 && p.y < 8.5 && p.inside && this._dragStartX === null) { - sounds.switchOn.play(); - this._locks.increment("move"); - this._dragStartX = this.menu.transform.x; - return true; - } - }, - - dragdown: function (p0, p1) { - p0 = this.layer0.worldFromDevice(p0); - p1 = this.layer0.worldFromDevice(p1); - - if (p0.x >= 4.5 && p0.x <= 5.5 - && p0.y >= 6.0 && p0.y <= 8.5 - && p0.y - p1.y > 1) { - this.slam(); - return true; - } - }, - - drag: function (p0, p1) { - if (this._dragStartX !== null) { - p0 = this.layer0.worldFromDevice(p0); - p1 = this.layer0.worldFromDevice(p1); - this.menu.transform.x = this._dragStartX + (p1.x - p0.x); - var index = Math.round((5 - this.menu.transform.x) / 2); - this.changeActiveIndex(index); - return true; - } - }, - - dragend: function (p0, p1) { - if (this._dragStartX !== null) { - sounds.switchOff.play(); - this._locks.decrement("move"); - this._dragStartX = null; - var index = this.activeIndex; - this.activeIndex = (5 - this.menu.transform.x) / 2; - this.changeActiveIndex(index, true); - return true; - } - }, - - tap: function (p) { - p = this.layer0.worldFromDevice(p); - if (p.y > 6 && p.y < 7 && p.inside) { - var dx = Math.round((p.x - 5) / 2); - if (dx === 0) this.slam(); - else if (dx < 0) handScene.movedLeft(); - else if (dx > 0) handScene.movedRight(); - var idx = this.activeIndex; - this.changeActiveIndex(this.activeIndex + dx, true); - if (idx !== this.activeIndex) - sounds.switch.play(); - else - sounds.switchBroke.play(); - return true; - } - - }, - - doubletap: function (p) { - p = this.layer0.worldFromDevice(p); - if (p.x >= 4.5 && p.x <= 5.5 && p.y >= 6.0 && p.y <= 8.5) { - this.slam(); - return true; - } - }, - }, - - resetEverything: yuu.cmd(function () { - storage.clear(); - yuu.director.stop(); - start(); - }, "reset all saved data"), - - unlock: yuu.cmd(function (d) { - LEVELS.forEach(function (level) { wonLevel(level, d); }); - yuu.director.pushPopScene(new MenuScene()); - }, "", "unlock all levels to the given difficulty"), - - KEYBINDS: { - left: "left", - right: "right", - up: "right", - down: "left", - w: "right", - a: "left", - s: "left", - d: "right", - shift: "slam", - space: "slam", - z: "slam", - "`+r+e": "resetEverything", - "`+u+e": "unlock easy", - "`+u+h": "unlock hard", - gamepadbutton0: "slam", - gamepadbutton8: "help", - gamepadbutton9: "slam", - gamepadbutton13: "slam", - gamepadbutton14: "left", - gamepadbutton15: "right", - } -}); - - -var BOOK_APPEAR = { - 0: { set1: { y: 1.5, x: -1.5 }, - tween: { bgQuad: { alpha: 0.75 }, $: { y: 0, x: 0 }, }, - duration: 30 } -}; - -var BOOK_DISMISS = { - 0: { tween: { bgQuad: { alpha: 0 }, $: { y: 1.5, x: -1.5, } }, - duration: 30 } -}; - -var KEYBOARD_PAGE = [0.25, 0.50, 0.50, 1.00]; -var POINTERS_PAGE = [0.25, 0.00, 0.50, 0.50]; -var GAMEPAD_PAGE = [0.00, 0.00, 0.25, 0.50]; - -var BOOK_FORWARD = [ - { 0: { set: { page2Quad: { color: [0.2, 0.2, 0.2, 1], texBounds: "page" } }, - tween: { page1: { x: -1/3 / 2, scaleX: 0 }, - page2: { x: +1/3 / 2 }, - page2Quad: { color: [1, 1, 1, 1] }, - }, duration: 15, easing: "linear" }, - 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] }, - page2Quad: { z: 0, texBounds: "page" } }, - tween: { page1: { x: -1/3, scaleX: -2/3 }, - page2: { x: +1/3 } - }, duration: 15, easing: "linear" }, - }, - - { 0: { tween: { page1: { x: -1/3 / 2 }, - page2: { x: +1/3 / 2, scaleX: 0 } - }, duration: 15, easing: "linear" }, - 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] }, - page2Quad: { z: 1, texBounds: [1.00, 0.5, 0.75, 1] } }, - tween: { page1Quad: { color: [0.2, 0.2, 0.2, 1] }, - page1: { x: 0 }, - page2: { x: 0, scaleX: -2/3 }, - }, duration: 15, easing: "linear" }, - }, - - BOOK_DISMISS -]; - -var BOOK_BACKWARD = [ - { 0: { tween: { page1: { x: -1/3 / 2, scaleX: 0 }, - page2: { x: +1/3 / 2 }, - }, duration: 15, easing: "linear" }, - 15: { set: { page1Quad: { z: 1, texBounds: [0.50, 0.5, 0.75, 1] }, - page2Quad: { z: 0 } }, - tween: { page2Quad: { color: [0.2, 0.2, 0.2, 1] }, - page1: { x: 0, scaleX: 2/3 }, - page2: { x: 0 }, - }, duration: 15, easing: "linear" }, - }, - - { 0: { set: { page1Quad: { color: [0.2, 0.2, 0.2, 1] } }, - tween: { page1Quad: { color: [1.0, 1.0, 1.0, 1] }, - page1: { x: -1/3 / 2 }, - page2: { x: +1/3 / 2, scaleX: 0 } - }, duration: 15, easing: "linear" }, - - 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] }, - page2Quad: { z: 0, texBounds: "page" } }, - tween: { page1: { x: -1/3 }, - page2: { x: +1/3, scaleX: 2/3 }, - }, duration: 15, easing: "linear" }, - }, -]; - -BookScene = new yT(yuu.Scene, { - constructor: function () { - yuu.Scene.call(this); - var bg = new yuu.E( - new yuu.Transform().setScale([20, 20, 1]), - this.bgQuad = new yuu.QuadC() - .setColor([0, 0, 0, 0]) - .setZ(-1)); - this.page1 = new yuu.E(new yuu.Transform(), - this.page1Quad = new yuu.QuadC(BOOK)); - this.page1Quad.texBounds = [0.50, 0.5, 0.75, 1]; - this.page1Quad.z = 1; - this.page2 = new yuu.E(new yuu.Transform(), - this.page2Quad = new yuu.QuadC(BOOK)); - this.page2Quad.texBounds = [0.25, 0.5, 0.50, 1]; - this.page1.transform.scale = [2/3, 1, 1]; - this.page2.transform.scale = [2/3, 1, 1]; - this.entity0.attach(new yuu.Transform()); - this.current = 0; - this._locks = new FlagSet("turn"); - this.addEntities(bg, this.page1, this.page2); - - this.dismissSound = new yuu.Instrument("@book-dismiss"); - this.pageSounds = [new yuu.Instrument("@page-turn-1"), - new yuu.Instrument("@page-turn-2"), - new yuu.Instrument("@page-turn-3")]; - - this.ready = yuu.ready([this.dismissSound].concat(this.pageSounds)); - }, - - help: yuu.cmd(function () { - this.skip(); - }, "dismiss the help screen"), - - licensing: yuu.cmd(function () { - var licensing = document.getElementById("yuu-licensing"); - var parent = licensing.parentNode; - var spinner = document.createElement("div"); - spinner.className = "yuu-spinner"; - spinner.id = licensing.id; - parent.replaceChild(spinner, licensing); - Promise.all( - yf.map(yuu.GET, - [yuu.PATH + "data/license.txt", "data/license.txt"])) - .then(function (texts) { - var text = texts.join("\n-- \n\n"); - var p = document.createElement("pre"); - p.textContent = text; - p.id = spinner.id; - parent.replaceChild(p, spinner); - }); - }, "why would you ever want to run this?"), - - init: function () { - this._anim(BOOK_APPEAR); - storage.setFlag("instructions"); - }, - - _anim: function (anim) { - this._locks.increment("turn"); - // FIXME: Need hooks from animations to audio - var completion = this._locks.decrementer("turn"); - switch (anim) { - case BOOK_DISMISS: - this.dismissSound.play(); - completion = yuu.director.removeScene.bind(yuu.director, this); - break; - case BOOK_APPEAR: - sounds.bookAppear.play(); - break; - default: - yuu.random.choice(this.pageSounds).play(); - break; - } - - var device = yuu.director.preferredDevice(); - this.entity0.attach(new yuu.Animation( - anim, { - $: this.entity0.transform, - page: device === "keyboard" ? KEYBOARD_PAGE - : device === "gamepad" ? GAMEPAD_PAGE - : POINTERS_PAGE, - page1: this.page1.transform, - page2: this.page2.transform, - page1Quad: this.page1Quad, - page2Quad: this.page2Quad, - bgQuad: this.bgQuad - }, completion)); - }, - - advance: yuu.cmd(function () { - if (this._locks.some("turn")) - return; - this._anim(BOOK_FORWARD[this.current++]); - }), - - skip: yuu.cmd(function () { - if (this._locks.some("turn")) - return; - this._anim(BOOK_DISMISS); - }), - - back: yuu.cmd(function () { - if (this._locks.some("turn")) - return; - if (this.current > 0) - this._anim(BOOK_BACKWARD[--this.current]); - }), - - LOGOTYPE: new yuu.AABB(-0.16, -0.41, 0.12, -0.33), - COLOPHON: new yuu.AABB(-0.06, -0.41, 0.11, -0.28), - - inputs: { - resize: function () { - var base = new yuu.AABB(-0.7, -0.55, 0.7, 0.55); - var vp = base.matchAspectRatio(yuu.viewport); - this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h); - }, - - mousemove: function (p) { - p = this.layer0.worldFromDevice(p); - if (this.current === BOOK_FORWARD.length - 1 - && this.LOGOTYPE.contains(p)) { - this.cursor = "pointer"; - } else if (this.current === 0 && this.COLOPHON.contains(p)) { - this.cursor = "pointer"; - } else if (this.current === 0 || p.x >= -0.2) { - this.cursor = ""; - } else { - this.cursor = "W-resize"; - } - }, - - tap: function (p) { - p = this.layer0.worldFromDevice(p); - if (this.current === BOOK_FORWARD.length - 1 - && this.LOGOTYPE.contains(p)) { - yuu.openURL("http://www.yukkurigames.com/"); - } else if (this.current === 0 && this.COLOPHON.contains(p)) { - yuu.director.showOverlay("colophon"); - } else if (this.current === 0 || p.x >= -0.2) { - this.advance(); - } else { - this.back(); - } - return true; - }, - swipeleft: function (event) { this.advance(); return true; }, - swiperight: function (event) { this.back(); return true; }, - dragleft: function (event) { this.advance(); return true; }, - dragright: function (event) { this.back(); return true; }, - swipeup: function (event) { this.skip(); return true; }, - dragup: function (event) { this.skip(); return true; }, - - consume: yuu.Director.prototype.GESTURES - .concat(yuu.Director.prototype.CANVAS_EVENTS) - }, - - KEYBINDS: { - space: "advance", - shift: "advance", - z: "advance", - x: "advance", - right: "advance", - left: "back", - back: "skip", - escape: "skip", - gamepadbutton0: "advance", - gamepadbutton1: "skip", - gamepadbutton4: "back", - gamepadbutton5: "advance", - gamepadbutton8: "skip", - gamepadbutton9: "skip", - gamepadbutton14: "back", - gamepadbutton15: "advance", - } -}); - -var OUTER_FLIP_TICK = { - 0: { tween1: { yaw: "yaw" }, duration: 15 } -}; - -var CIRCLE_TO_BOTTOM = { - 0: { tween1: { pitch: Math.PI * 0.35, y: -0.3 }, duration: 35 } -}; - -var CIRCLE_TO_BACK = { - 0: { tween1: { pitch: Math.PI * 0.15, y: -0.1 }, duration: 35 } -}; - -var CIRCLE_INNER_RATCHET = { - 0: { tween1: { rotation: "rotation1" }, duration: 15 }, - 10: { tween1: { rotation: "rotation2" }, duration: 10 }, - 20: { tween1: { rotation: "rotation1" }, duration: 20, - easing: yuu.Tween.STEPPED(5) }, - 40: { tween1: { rotation: "rotation2" }, duration: 15 } -}; - -var CIRCLE_INNER_WIND = { - 0: { tween1: { rotation: "rotation1" }, duration: 8 }, - 15: { tween1: { rotation: "rotation2" }, duration: 20 }, -}; - -var BACKGROUND_DRIFT = { - 0: [{ tween1: { yaw: Math.PI * 2 }, - duration: 13 * 60 * 60, repeat: -Infinity, easing: "linear" }, - { tween1: { scaleX: 0.5 }, - duration: 11 * 60 * 60, repeat: -Infinity }, - { tween1: { scaleY: 0.5 }, - duration: 7 * 60 * 60, repeat: -Infinity }] -}; - -var HAND_TICK = { - 0: { tween1: { rotation: "rotation" }, duration: 6 } -}; - -var CHIMES = [ - // Nearly all derived from - // http://www.clockguy.com/SiteRelated/SiteReferencePages/ClockChimeTunes.html - // - // All transposition & transcription errors are mine. - - { name: "Westminster", - keys: ["D4", "E4"], - bars: ["0 2 1 -3", - "0 1 2 0", - "2 0 1 -3", - "2 1 0 -3", - "-3 1 2 0"] - }, - - { name: "Wittington", - keys: ["Eb4", "E4"], - bars: ["1 2 3 5 4 6 7 0", - "1 3 5 7 6 4 2 0", - "3 1 2 4 5 6 7 0", - "4 3 5 2 6 1 7 0", - "6 7 2 5 4 1 3 0", - "7 1 6 2 5 3 4 0", - "7 3 2 1 4 5 6 0", - "7 3 6 2 5 1 4 0", - "7 5 3 1 6 4 2 0", - "7 5 6 4 1 3 2 0", - "7 6 3 2 5 4 1 0", - "7 6 5 4 3 2 1 0"] - }, - - { name: "Canterbury", - keys: ["D4", "E4"], - bars: ["2 0 5 3 1 4", - "3 5 1 4 0 2", - "3 5 4 2 1 0", - "5 3 1 4 2 0", - "1 3 5 2 0 4", - "0 5 3 1 2 4", - "5 3 1 2 4 0"] - }, - - { name: "Trinity", - keys: ["F3", "D4"], - bars: ["5 4 3 2 1 0", - "2 4 3 1 2 0", - "5 3 4 2 1 0", - "4 3 2 1 5 2", - "5 0 4 3 2 1"] - }, - - /* - { name: "St. Michael's", - keys: ["F3", "C4"], - bars: ["7 6 5 4 3 2 1 0", - "7 1 2 3 6 4 5 0", - "4 3 2 5 1 6 7 0", - "6 7 2 3 1 4 5 0", - "4 6 2 7 3 1 5 0"] - }, - */ - - { name: "Winchester", - keys: ["C4", "E4"], - bars: ["5 3 1 0 2 4", - "0 1 3 5 4 2", - "5 3 1 4 2 0", - "1 2 5 4 0 1", - "5 1 3 2 4 0"] - } - -]; - -function third (s) { - return "Q " + s.replace(/([^ ]+ [^ ]+)/g, "$1 Z"); -} - -function silence (s) { - return "Q " + s.replace(/[^ ]+/g, "Z"); -} - -var TIMES1 = ["Q", "H", "H", "H", "H.", "H.", "W", "W"]; -var TIMES2 = ["Q", "Q", "Q", "Q", "Q", "Q", "Q", "Q", "Q", - third, third, "Q.", "Q.", - "H", "H"]; -var TIMES3 = ["Q", "Q", silence, silence, - third, third, third, third, - "Q.", "Q.", - "H", "H"]; - -function deck (pack, random) { - random = random || yuu.random; - var stock = []; - return function () { - if (stock.length === 0) - stock = random.shuffle(pack.slice()); - return stock.pop(); - }; -} - -function generateScore () { - var chimes = yuu.random.choice(CHIMES); - var bar = deck(chimes.bars); - function draw (t) { - return yf.isFunction(t) ? t(bar()) : t + " " + bar(); - } - - function line (times) { - return yf.map(draw, yuu.random.shuffle(times)).join(" "); - } - - var track = "{ - W HZ " + line(TIMES1) - + " { W HZ Z " + line(TIMES2) - + " { W HZ Z Z I Z " + line(TIMES3); - var key = yuu.random.choice(chimes.keys); - yuu.log("messages", "Playing " + chimes.name + " in " + key + " major."); - var score = yuu.parseScore(track, yuu.Scales.MAJOR, key); - score.key = key; - return score; -} - -CircleScene = yT(yuu.Scene, { - constructor: function () { - yuu.Scene.call(this); - this.layer0.resize(-0.6, -0.6, 1.2, 1.2); - var arm = this.arm = new yuu.E(new yuu.Transform()); - this.outer = new yuu.E( - new yuu.Transform([Math.sqrt(2) / 5, -Math.sqrt(2) / 5, 0]), - this.outerQuad = new yuu.QuadC(new yuu.Material("@circle-outer")) - .setZ(1) - .setLuminance(0.4) - .setSize([0.35417, 0.35417])); - arm.addChild(this.outer); - - var rim = new yuu.E( - new yuu.Transform(), - this.rimQuad = new yuu.QuadC(new yuu.Material("@circle-rim")) - .setLuminance(0.2)); - var inner = this.inner = new yuu.E( - new yuu.Transform(), - this.innerQuad = new yuu.QuadC(new yuu.Material("@circle-inner")) - .setLuminance(0.3)); - - var NOISY_QUADS = new yuu.ShaderProgram( - ["@noise.glsl", "@noisyquads"], ["yuu/@color"]); - - var bgMat = new yuu.Material( - yuu.Texture.DEFAULT, NOISY_QUADS, { range: 0.8 }); - bgMat.uniforms.cut = yf.volatile(cycler(100000)); - var DIM = 16; - var batch = new yuu.QuadBatchC(DIM * DIM); - batch.material = bgMat; - var bg = new yuu.E(new yuu.Transform(), batch); - yf.irange(function (x) { - yf.irange(function (y) { - var quad = batch.createQuad(); - quad.size = [1/4, 1/4]; - quad.position = [(x - DIM / 2) * 1/4, - (y - DIM / 2) * 1/4]; - quad.color = [0.12, 0.08, 0.16]; - quad.texBounds = yf.repeat(x * DIM + y, 4); - }, DIM); - }, DIM); - - this.entity0.addChild(bg); - this.entity0.attach(new yuu.Animation( - BACKGROUND_DRIFT, { $: bg.transform })); - - this.ground = new yuu.E(new yuu.Transform()); - this.ground.addChildren(rim, inner, arm); - this.entity0.addChild(this.ground); - - this.music = yuu.audio.createGain(); - this.music.gain.value = 0.3; - this.music.connect(yuu.audio.music); - this._finished = false; - - this.ready = yuu.ready([ - this.outerQuad.material, - this.innerQuad.material, - this.rimQuad.material, - bgMat.ready - ]); - }, - - help: yuu.cmd(function () { - yuu.director.pushScene(new BookScene()); - }, "bring up the help screen"), - - yuu: yuu.cmd(function () { - this.outerQuad.material = new yuu.Material("@circle-outer-ee"); - }, "yuu~"), - - KEYBINDS: { - slash: "help", - f1: "help", - gamepadbutton6: "help", - f10: "showOverlay preferences", - "shift+y+u+`": "yuu", - "gamepadbutton10+gamepadbutton11": "yuu", - }, - - inputs: { - resize: function () { - var vp = new yuu.AABB(-0.6, -0.6, 0.6, 0.6) - .matchAspectRatio(yuu.viewport); - this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h); - } - }, - - toBottom: function () { - this.entity0.attach( - new yuu.Animation(CIRCLE_TO_BOTTOM, { $: this.ground.transform })); - }, - - wind: function () { - var rot1 = this.inner.transform.rotation; - quat.rotateZ(rot1, rot1, Math.PI / (2 * Math.E)); - var rot2 = quat.rotateX(quat.create(), rot1, -Math.PI / 2); - quat.rotateY(rot2, rot2, Math.PI / 2); - quat.rotateX(rot2, rot2, -Math.PI / 2); - this.entity0.attach( - new yuu.Animation(CIRCLE_INNER_WIND, { - $: this.inner.transform, - rotation1: rot1, - rotation2: rot2 - })); - this.tension = 0.5; - this.reversed = 0; - var score = []; - score.key = this.score && this.score.key; - this.score = score; - }, - - _musicSchedule: function (count) { - var t = yuu.director.currentAudioTime; - var note; - - if (this._finished) { - if (this._finished === "won" && this.score.key) { - var score = yuu.parseScore( - yuu.random.choice([ - "1 3 2 Z 0 { - 1 Z 2 Z 0", - "1 2 3 Z 0 { - 1 Z 3 Z 0", - "0 1 2 Z 4 { - 0 Z 2 Z 4", - ]), - yuu.Scales.MAJOR, this.score.key); - while ((note = score.shift())) { - sounds.chime.createSound( - yuu.audio, - t + note.time / 4, - note.hz, - 1.0, note.duration - ).connect(this.music); - } - } - this._finished = false; - return false; - } - - if (!(this.score && this.score.length)) { - this.score = generateScore(); - this.playing = 0; - } - - ++this.playing; - while (this.score.length && this.score[0].time < this.playing) { - note = this.score.shift(); - sounds.chime.createSound( - yuu.audio, - t + note.time % 1 + yuu.random.gauss(0, 0.015), - note.hz, - 1.0, note.duration - ).connect(this.music); - } - - if ((this.tension *= 0.95) > 1) { - this.tension /= 2; - sounds.winding.createSound(yuu.audio, t, 0, 1.0, 1.0) - .connect(this.music); - var flip = !this.outer.transform.yaw * yuu.random.randsign(Math.PI); - this.entity0.attach( - new yuu.Animation(OUTER_FLIP_TICK, { - $: this.outer.transform, - yaw: flip - })); - } else { - [sounds.tick, sounds.tock][count & 1] - .createSound(yuu.audio, t, 0, 0.5, 1.0) - .connect(this.music); - } - - this.clockTick(this.reversed-- > 0 ? TICK_REV : TICK_ROT); - - return true; - }, - - clockTick: function (amount, anim) { - var rot = this.arm.transform.rotation; - quat.multiply(rot, rot, amount || TICK_ROT); - this.arm.attach(new yuu.Animation( - anim || HAND_TICK, - { $: this.arm.transform, rotation: rot })); - }, - - toBack: function () { - this.wind(); - this.entity0.attach( - new yuu.Animation(CIRCLE_TO_BACK, { $: this.ground.transform })); - - this.playing = 4; - this.arm.attach( - new yuu.Ticker(this._musicSchedule.bind(this), 60)); - }, - - win: function () { - this._finished = "won"; - this.wind(); - this.entity0.attach( - new yuu.Animation(FLASH, { $: this.innerQuad }), - new yuu.Animation(FLASH, { $: this.rimQuad }, null, 32), - new yuu.Animation(FLASH, { $: this.outerQuad }, null, 48) - ); - }, - - lose: function () { - this._finished = "lose"; - var rot1 = this.inner.transform.rotation; - quat.rotateZ(rot1, rot1, -Math.PI / Math.E); - var rot2 = quat.rotateZ(quat.create(), rot1, Math.PI / Math.E); - this.entity0.attach( - new yuu.Animation(CIRCLE_INNER_RATCHET, { - $: this.inner.transform, - rotation1: rot1, - rotation2: rot2 - })); - sounds.regear.play(); - }, - - rotated: function () { - this.tension += yuu.random.uniform(0.1); - }, - - slam: function () { - this.tension += yuu.random.uniform(0.2); - }, - - reverse: function () { - this.tension -= yuu.random.uniform(0.1); - this.reversed = Math.max(this.reversed, 0) + 1; - } - -});