├── .gitignore
├── 3d-cube.js
├── 3d-walk.js
├── README.md
├── block.wav
├── breakout.js
├── ded.wav
├── dk-bacmusic.wav
├── dk-death.wav
├── dk-hammertime.wav
├── dk-howhigh.wav
├── dk-levelend.wav
├── dk-smash3.wav
├── dk-walking.wav
├── donkey-kong.js
├── face.png
├── hit1.wav
├── hit2.wav
├── index.html
├── over.wav
├── palette.json
├── popup.html
├── renderer.js
├── three.js
├── utils.js
├── wavy.js
├── webcam.js
└── win.wav
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.swp
3 |
--------------------------------------------------------------------------------
/3d-cube.js:
--------------------------------------------------------------------------------
1 | var popups = [],
2 | palette,
3 | toggleRenderButton,
4 | container,
5 | renderer,
6 | cube;
7 |
8 | function loop() {
9 | var data = new Uint8Array(renderer.width/10 * renderer.height/10 * 4),
10 | x, r, g, b, s;
11 |
12 | cube.rotation.x += .02*6;
13 | cube.rotation.y += .03*6;
14 |
15 | renderer.render();
16 |
17 | renderer.tRenderer.readRenderTargetPixels(renderer.renderTarget, 0, 0, renderer.width/10, renderer.height/10, data);
18 |
19 | popups.forEach(function (popup, y) {
20 | s = '';
21 | y *= 2;
22 |
23 | for (x = 0; x < renderer.width/10; x ++) {
24 | r = Math.floor(data[((y*renderer.width/10)+x)*4+0] / 16);
25 | g = Math.floor(data[((y*renderer.width/10)+x)*4+1] / 16);
26 | b = Math.floor(data[((y*renderer.width/10)+x)*4+2] / 16);
27 |
28 | s += palette[r][g][b].s;
29 | }
30 |
31 | popup.document.location.hash = s;
32 | });
33 |
34 | setTimeout(loop, 1000/10);
35 | }
36 |
37 | function addPlane(x, y, z, xd, yd, zd, color) {
38 | material = new THREE.MeshStandardMaterial({
39 | //color: 0xffffff * Math.random(),
40 | color: color,
41 | metalness: 0,
42 | });
43 |
44 | mesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 4, 1, 1), material);
45 |
46 | mesh.position.x = x;
47 | mesh.position.y = y;
48 | mesh.position.z = z;
49 |
50 | mesh.rotation.x = degToRad(xd);
51 | mesh.rotation.y = degToRad(yd);
52 | mesh.rotation.z = degToRad(zd);
53 |
54 | cube.add(mesh);
55 | }
56 |
57 | function loadPalette() {
58 | var xhr = new XMLHttpRequest();
59 |
60 | xhr.open('GET', 'palette.json');
61 | xhr.addEventListener('readystatechange', function () {
62 | if (xhr.readyState == 4) {
63 | palette = JSON.parse(xhr.responseText);
64 |
65 | loop();
66 | }
67 | });
68 | xhr.send();
69 | }
70 |
71 | function toggleRender() {
72 | if (container.style.display == 'none') {
73 | container.style.display = 'block';
74 | toggleRenderButton.innerHTML = 'hide render';
75 | }
76 | else {
77 | container.style.display = 'none';
78 | toggleRenderButton.innerHTML = 'show render';
79 | }
80 | }
81 |
82 | function closeWindows() {
83 | var popup;
84 |
85 | running = false;
86 |
87 | while (popups.length) {
88 | popup = popups.shift();
89 | popup.close();
90 | }
91 | }
92 |
93 | function setup() {
94 | var light, x, z,
95 | closeButton;
96 |
97 | toggleRenderButton = document.createElement('button');
98 | toggleRenderButton.innerHTML = 'show render';
99 | toggleRenderButton.addEventListener('click', toggleRender);
100 | document.body.appendChild(toggleRenderButton);
101 |
102 | closeButton = document.createElement('button');
103 | closeButton.innerHTML = 'close';
104 | closeButton.addEventListener('click', closeWindows);
105 | document.body.appendChild(closeButton);
106 |
107 | container = document.createElement('div');
108 | container.id = 'container';
109 | container.style.display = 'none';
110 | document.body.appendChild(container);
111 |
112 | renderer = new Renderer();
113 | renderer.setSize(200, 140);
114 |
115 | camera = renderer.camera;
116 |
117 | light = new THREE.AmbientLight(0xdddddd);
118 | renderer.scene.add(light);
119 |
120 | light = new THREE.PointLight(0xffffff, .05);
121 | light.castShadow = true;
122 | light.position.set(0, 20, 0);
123 | renderer.scene.add(light);
124 |
125 | renderer.camera.position.z = 7;
126 |
127 | cube = new THREE.Object3D();
128 |
129 | addPlane( 0, 0, 2, 0, 0, 0, 0xFF0000); // front
130 | addPlane( 0, 0, -2, 180, 0, 0, 0x00FF00); // back
131 | addPlane( 0, 2, 0, -90, 0, 0, 0x0000FF); // top
132 | addPlane( 0, -2, 0, 90, 0, 0, 0xFFFF00); // bottom
133 | addPlane( 2, 0, 0, 0, 90, 0, 0xFF00FF); // right
134 | addPlane(-2, 0, 0, 0, -90, 0, 0x00FFFF); // left
135 |
136 | renderer.scene.add(cube);
137 |
138 | loadPalette();
139 |
140 | popups.forEach(function (popup) {
141 | popup.history.replaceState({}, '', '/');
142 | });
143 | }
144 |
--------------------------------------------------------------------------------
/3d-walk.js:
--------------------------------------------------------------------------------
1 | var popups = [],
2 | palette,
3 | toggleRenderButton,
4 | container,
5 | renderer,
6 | camera,
7 | player,
8 | face,
9 | keys = {};
10 |
11 | var map = [
12 | 'xxxxxxxxxxxxxxxx',
13 | 'xxxxxxxx x',
14 | 'x f x',
15 | 'x x',
16 | 'x xxxxxx',
17 | 'x xxxxx x',
18 | 'x x x x',
19 | 'x x x',
20 | 'x x x x',
21 | 'x xxxxx x',
22 | 'x x',
23 | 'x x',
24 | 'xxxxxxxxxxx',
25 | ];
26 |
27 | function loop() {
28 | var data = new Uint8Array(renderer.width/10 * renderer.height/10 * 4),
29 | x, y, r, g, b, s;
30 |
31 | var movement = {x: 0, y: 0},
32 | speed = 2;
33 |
34 | if (keys[16]) speed *= 3;
35 |
36 | if (keys[87] || keys[38]) {
37 | movement.y = -speed;
38 | }
39 | else if (keys[83] || keys[40]) {
40 | movement.y = speed;
41 | }
42 |
43 | if (keys[68] || keys[39]) {
44 | player.rotation.y -= .2;
45 | }
46 | else if (keys[65] || keys[37]) {
47 | player.rotation.y += .2;
48 | }
49 |
50 | movement = rotateCoord(movement, radToDeg(player.rotation.y));
51 | player.position.x += movement.x;
52 | player.position.z += movement.y;
53 |
54 | face.lookAt(player.position);
55 |
56 | renderer.render();
57 |
58 | renderer.tRenderer.readRenderTargetPixels(renderer.renderTarget, 0, 0, renderer.width/10, renderer.height/10, data);
59 |
60 | popups.forEach(function (popup, y) {
61 | s = '';
62 | y *= 2;
63 |
64 | for (x = 0; x < renderer.width/10; x ++) {
65 | r = Math.floor(data[((y*renderer.width/10)+x)*4+0] / 16);
66 | g = Math.floor(data[((y*renderer.width/10)+x)*4+1] / 16);
67 | b = Math.floor(data[((y*renderer.width/10)+x)*4+2] / 16);
68 |
69 | s += palette[r][g][b].s;
70 | }
71 |
72 | popup.document.location.hash = s;
73 | });
74 |
75 | setTimeout(loop, 1000/10);
76 | }
77 |
78 | function addPlane(x, y, z, xd, yd, zd, color) {
79 | material = new THREE.MeshStandardMaterial({
80 | //color: 0xffffff * Math.random(),
81 | color: color,
82 | metalness: 0,
83 | });
84 |
85 | mesh = new THREE.Mesh(new THREE.PlaneGeometry(10, 20, 1, 1), material);
86 |
87 | mesh.position.x = x;
88 | mesh.position.y = y;
89 | mesh.position.z = z;
90 |
91 | mesh.rotation.x = degToRad(xd);
92 | mesh.rotation.y = degToRad(yd);
93 | mesh.rotation.z = degToRad(zd);
94 |
95 | renderer.scene.add(mesh);
96 | }
97 |
98 | function loadPalette() {
99 | var xhr = new XMLHttpRequest();
100 |
101 | xhr.open('GET', 'palette.json');
102 | xhr.addEventListener('readystatechange', function () {
103 | if (xhr.readyState == 4) {
104 | palette = JSON.parse(xhr.responseText);
105 |
106 | loop();
107 | }
108 | });
109 | xhr.send();
110 | }
111 |
112 | window.addEventListener('keydown', function (event) {
113 | console.log(event.which);
114 | keys[event.which] = true;
115 | });
116 |
117 | window.addEventListener('keyup', function (event) {
118 | keys[event.which] = false;
119 | });
120 |
121 | function toggleRender() {
122 | if (container.style.display == 'none') {
123 | container.style.display = 'block';
124 | toggleRenderButton.innerHTML = 'hide render';
125 | }
126 | else {
127 | container.style.display = 'none';
128 | toggleRenderButton.innerHTML = 'show render';
129 | }
130 | }
131 |
132 | function closeWindows() {
133 | var popup;
134 |
135 | running = false;
136 |
137 | while (popups.length) {
138 | popup = popups.shift();
139 | popup.close();
140 | }
141 | }
142 |
143 | function setup() {
144 | var light, x, z,
145 | closeButton;
146 |
147 | toggleRenderButton = document.createElement('button');
148 | toggleRenderButton.innerHTML = 'show render';
149 | toggleRenderButton.addEventListener('click', toggleRender);
150 | document.body.appendChild(toggleRenderButton);
151 |
152 | closeButton = document.createElement('button');
153 | closeButton.innerHTML = 'close';
154 | closeButton.addEventListener('click', closeWindows);
155 | document.body.appendChild(closeButton);
156 |
157 | container = document.createElement('div');
158 | container.id = 'container';
159 | container.style.display = 'none';
160 | document.body.appendChild(container);
161 |
162 | renderer = new Renderer();
163 |
164 | camera = renderer.camera;
165 |
166 | player = new THREE.Object3D();
167 | player.position.set(85, 0, 95);
168 | player.rotation.y = degToRad(45);
169 |
170 | player.add(camera);
171 |
172 | renderer.scene.add(player);
173 |
174 | light = new THREE.AmbientLight(0xdddddd);
175 | renderer.scene.add(light);
176 |
177 | light = new THREE.PointLight(0xffffff, .05);
178 | light.castShadow = true;
179 | light.position.set(0, 20, 0);
180 | renderer.scene.add(light);
181 | var lightHelper = new THREE.PointLightHelper(light, 1);
182 |
183 | for (z = 0; z < map.length; z ++) {
184 | for (x = 0; x < map[z].length; x ++) {
185 | if (map[z][x] != 'x') {
186 | if (map[z][x] == 'f') {
187 | material = new THREE.MeshBasicMaterial({
188 | map: THREE.ImageUtils.loadTexture('face.png'),
189 | transparent: true
190 | });
191 |
192 | mesh = new THREE.Mesh(new THREE.PlaneGeometry(15, 15, 1, 1), material);
193 | mesh.position.x = x * 10;
194 | mesh.position.z = z * 10;
195 | face = mesh;
196 |
197 | renderer.scene.add(mesh);
198 | }
199 |
200 | if (map[z-1][x] == 'x') { // south facing
201 | addPlane(x*10, 0, z*10-5, 0, 0, 0, 0xFF0000);
202 | }
203 | if (map[z+1][x] == 'x') { // north facing
204 | addPlane(x*10, 0, z*10+5, 180, 0, 0, 0x00FF00);
205 | }
206 | if (map[z][x+1] == 'x') { // west facing
207 | addPlane(x*10+5, 0, z*10, 0, -90, 0, 0x00FFFF);
208 | }
209 | if (map[z][x-1] == 'x') { // east facing
210 | addPlane(x*10-5, 0, z*10, 0, 90, 0, 0xFFFF00);
211 | }
212 | }
213 | }
214 | }
215 |
216 | window.addEventListener('resize', function () {
217 | renderer.resize();
218 | });
219 |
220 | renderer.camera.position.z = 7;
221 |
222 | loadPalette();
223 |
224 | popups.forEach(function (popup) {
225 | popup.history.replaceState({}, '', '/');
226 | });
227 | }
228 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | some games and graphical things that display in the URL bars of popup windows...
2 |
3 | ## more info here: http://matthewrayfield.com/articles/games-and-graphics-in-popup-url-bars/
4 |
5 | #### sound sources:
6 |
7 | dk-howhigh.wav, dk-death.wav, dk-walking.wav and dk-bacmusic.wav taken from http://www.classicgaming.cc/classics/donkey-kong/sounds
8 |
9 | dk-levelend.wav and dk-hammertime.wav ripped from https://www.youtube.com/watch?v=Pp2aMs38ERY
10 |
11 | dk-smash3.wav badly recreated by me :]]]
12 |
--------------------------------------------------------------------------------
/block.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/block.wav
--------------------------------------------------------------------------------
/breakout.js:
--------------------------------------------------------------------------------
1 | var popups = [],
2 | keys = {},
3 |
4 | map = {},
5 | statusLine = '',
6 |
7 | levelWidth = 9,
8 |
9 | paddleSize = 3,
10 | paddleX,
11 |
12 | score = 0,
13 | lives = 2,
14 | level = 1,
15 | running = false,
16 | deathTime = 0,
17 | gameoverTime = 0,
18 |
19 | paddleSpeed = .3,
20 | ballSpeed = .03,
21 |
22 | blockLines = 2,
23 | blockCount = 0,
24 | positions,
25 |
26 | ballX, ballY, ballXV, ballYV,
27 |
28 | baseURL;
29 |
30 | var emojis = [
31 | '🤠', '👽', '🤷', '👏', '👀', '💩', '🐸', '💯', '💊', '💸', '🔥', '🍆', '🍕', '💦', '💎', '💰', '🔫', '🚬', '🐓', '🗿'
32 | ];
33 |
34 | function render() {
35 | popups.forEach(function (popup, y) {
36 | var s = '', x;
37 |
38 | if (!popup.document.location) return;
39 |
40 | if (y == 0) {
41 | popup.document.location.hash = statusLine;
42 | }
43 | else {
44 | for (x = 0; x < levelWidth; x ++) {
45 | if (x == Math.floor(ballX) && y == Math.floor(ballY)) {
46 | s += '🎱';
47 | }
48 | else if (y == popups.length - 1 && x >= Math.floor(paddleX) && x < Math.floor(paddleX) + paddleSize) {
49 | if (deathTime && !(Math.floor((Date.now()-deathTime)/500) % 2)) {
50 | s += '💀';
51 | }
52 | else {
53 | s += String.fromCharCode(0x2B1B);
54 | }
55 | }
56 | else if (map[x+','+y]) {
57 | if (gameoverTime && !(Math.floor((Date.now()-gameoverTime)/500) % 2)) {
58 | s += '💀';
59 | }
60 | else {
61 | s += map[x+','+y];
62 | }
63 | }
64 | else s += '⬜';
65 | }
66 |
67 | popup.document.location.hash = '🔷' + s + '🔷';
68 | }
69 | });
70 | }
71 |
72 | function addBlock() {
73 | var p = positions.pop();
74 |
75 | if (!p) {
76 | running = true;
77 | return;
78 | }
79 |
80 | map[p[0] +','+ p[1]] = emojis[Math.floor(Math.random()*emojis.length)];
81 | blockCount ++;
82 | playSound(baseURL + 'block');
83 |
84 | setTimeout(addBlock, 100);
85 | }
86 |
87 | function setupLevel() {
88 | var x, y;
89 |
90 | if (level == 1) {
91 | blockLines = 2;
92 | paddleSize = 3;
93 | }
94 | if (level == 2) {
95 | blockLines = 3;
96 | paddleSize = 3;
97 | }
98 | else if (level == 3) {
99 | blockLines = 3;
100 | paddleSize = 2;
101 | }
102 | else if (level == 4) {
103 | blockLines = 4;
104 | paddleSize = 2;
105 | }
106 | else if (level == 5) {
107 | blockLines = 4;
108 | paddleSize = 1;
109 | }
110 | else {
111 | blockLines = 2;
112 | paddleSize = 3;
113 | ballSpeed += .1;
114 | paddleSpeed += .1;
115 | }
116 |
117 | blockCount = 0;
118 | positions = [];
119 |
120 | for (x = 0; x < levelWidth; x ++) {
121 | for (y = 1; y <= blockLines; y ++) {
122 | positions.push([x,y]);
123 | }
124 | }
125 |
126 | positions.sort(function () {return Math.random()*2-1});
127 |
128 | addBlock();
129 | }
130 |
131 | function startBall() {
132 | paddleX = Math.floor((levelWidth - paddleSize)/2);
133 |
134 | ballX = paddleX;
135 | ballY = popups.length - 2;
136 | ballXV = Math.round(Math.random()) ? -.1 : .1;
137 | ballYV = -ballSpeed;
138 | }
139 |
140 | function movePaddle() {
141 | if (keys[37]) {
142 | paddleX -= paddleSpeed; // left
143 | console.log('left');
144 | }
145 | if (keys[39]) {
146 | paddleX += paddleSpeed; // right
147 | console.log('right');
148 | }
149 | }
150 |
151 | function addSomeRandom() {
152 | var r = (Math.random()*.15) + .05;
153 |
154 | if (ballXV > 0) ballXV = r;
155 | else ballXV = -r;
156 | }
157 |
158 | function loseLife() {
159 | lives --;
160 |
161 | if (lives < 0) {
162 | playSound(baseURL + 'over');
163 | gameoverTime = Date.now();
164 | }
165 | else {
166 | playSound(baseURL + 'ded');
167 | setTimeout(function () {
168 | deathTime = 0;
169 | running = true;
170 | startBall();
171 | }, 2000);
172 | }
173 |
174 | deathTime = Date.now();
175 |
176 | running = false;
177 | }
178 |
179 | function moveBall() {
180 | var x, y;
181 |
182 | ballX += ballXV;
183 | ballY += ballYV;
184 |
185 | x = Math.floor(ballX);
186 | y = Math.floor(ballY);
187 |
188 | if (map[x+','+y]) {
189 | if (ballYV < 0) {
190 | ballYV = ballSpeed;
191 | }
192 | else {
193 | ballXV *= -1;
194 | }
195 |
196 | score += 1;
197 | map[x+','+y] = null;
198 |
199 | blockCount --;
200 | if (blockCount == 0) {
201 | playSound(baseURL + 'win');
202 | running = false;
203 | level ++;
204 | setTimeout(function () {
205 | setupLevel();
206 | startBall();
207 | }, 1000);
208 | }
209 | else {
210 | playSound(baseURL + 'hit1');
211 | addSomeRandom();
212 | }
213 | }
214 |
215 | if (ballY >= popups.length) {
216 | loseLife();
217 | }
218 | else if (ballY < 1) {
219 | ballYV = ballSpeed;
220 | addSomeRandom();
221 | }
222 | else if (y == popups.length - 1 && Math.floor(ballX) >= Math.floor(paddleX) && Math.floor(ballX) < Math.floor(paddleX) + paddleSize) {
223 | ballYV = -ballSpeed;
224 | playSound(baseURL + 'hit2');
225 | addSomeRandom();
226 | }
227 | else if (ballX < 0 || ballX >= levelWidth) {
228 | ballXV *= -1;
229 | addSomeRandom();
230 | }
231 |
232 | ballX = Math.max(0, Math.min(levelWidth - .01, ballX));
233 | }
234 |
235 | function loop() {
236 | var now = performance.now();
237 |
238 | if (running) {
239 | movePaddle();
240 | moveBall();
241 | }
242 |
243 | if (gameoverTime) {
244 | statusLine = toFullWidth('⠀Score:' + score + '⠀GAME⠀OVER');
245 | }
246 | else if (!positions.length) {
247 | statusLine = toFullWidth('⠀Score:' + score + '⠀Lives:' + lives);
248 | }
249 | else {
250 | statusLine = toFullWidth('⠀⠀---Level:' + level + '---');
251 | }
252 |
253 | render();
254 |
255 | setTimeout(loop, 1000/30);
256 | }
257 |
258 | function closeWindows() {
259 | var popup;
260 |
261 | running = false;
262 |
263 | while (popups.length) {
264 | popup = popups.shift();
265 | popup.close();
266 | }
267 | }
268 |
269 | function setup() {
270 | var split = location.href.split('/');
271 | split.pop();
272 | baseURL = split.join('/') + '/';
273 |
274 | document.addEventListener('keydown', function (event) {
275 | keys[event.which] = true;
276 | });
277 |
278 | document.addEventListener('keyup', function (event) {
279 | keys[event.which] = false;
280 | });
281 |
282 | document.title = '-';
283 |
284 | closeButton = document.createElement('button');
285 | closeButton.innerHTML = 'close';
286 | closeButton.addEventListener('click', closeWindows);
287 | document.body.appendChild(closeButton);
288 |
289 | setupLevel();
290 | startBall();
291 | loop();
292 |
293 | popups.forEach(function (popup) {
294 | popup.history.replaceState({}, '', '/');
295 | });
296 | }
297 |
--------------------------------------------------------------------------------
/ded.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/ded.wav
--------------------------------------------------------------------------------
/dk-bacmusic.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-bacmusic.wav
--------------------------------------------------------------------------------
/dk-death.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-death.wav
--------------------------------------------------------------------------------
/dk-hammertime.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-hammertime.wav
--------------------------------------------------------------------------------
/dk-howhigh.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-howhigh.wav
--------------------------------------------------------------------------------
/dk-levelend.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-levelend.wav
--------------------------------------------------------------------------------
/dk-smash3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-smash3.wav
--------------------------------------------------------------------------------
/dk-walking.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/dk-walking.wav
--------------------------------------------------------------------------------
/donkey-kong.js:
--------------------------------------------------------------------------------
1 | var popups = [],
2 | keys = {},
3 |
4 | pauline = ['💃', '🕺', '🍔'][Math.floor(Math.random()*3)],
5 |
6 | level = 1,
7 | lives = 3,
8 |
9 | levelHeight = 7,
10 | levelWidth = 15,
11 |
12 | popupWidth = window.screen.width,
13 | popupHeight = 50,
14 | popupGap = 50,
15 |
16 | didMove = false,
17 |
18 | playerX = 0,
19 | playerY = levelHeight - 1,
20 | death = false,
21 |
22 | barrels = [],
23 | barrelMap = {},
24 | barrelSpeed = 150,
25 | barrelRate = 5000,
26 | barrelBaseRate = 3000,
27 |
28 | nextBarrelAdd = 0,
29 | lastBarrelMove = 0,
30 | lastPlayerMove = 0,
31 |
32 | levelWon = false,
33 | levelEndTime,
34 |
35 | showingLevel = false,
36 | gameover = false,
37 | running = false,
38 |
39 | hammering = false,
40 | hammerX,
41 |
42 | closeButton,
43 |
44 | backgroundAudio,
45 | walkingAudio,
46 | hammeringAudio,
47 |
48 | baseURL;
49 |
50 | var map = [
51 | ' '.split(''),
52 | ' 1 '.split(''),
53 | ' 2 1 '.split(''),
54 | '1 1 '.split(''),
55 | ' 1 1 '.split(''),
56 | '1 1 '.split(''),
57 | ' 1 '.split(''),
58 | ];
59 |
60 |
61 | function render() {
62 | var timeTo,
63 | barrelShown = false,
64 | paulineShown = true,
65 | gorillaX = 2, gorillaY = 1;
66 |
67 | if (levelWon) {
68 | timeTo = levelEndTime - performance.now();
69 |
70 | if (Math.floor(timeTo/800) >= 4) {
71 | gorillaX = 3;
72 | }
73 | else if (Math.floor(timeTo/800) == 3) {
74 | gorillaX = 4;
75 | }
76 | else if (Math.floor(timeTo/800) == 2) {
77 | gorillaX = 5;
78 | }
79 | else if (Math.floor(timeTo/800) == 1) {
80 | gorillaX = 5;
81 | gorillaY = 0;
82 | paulineShown = false;
83 | }
84 | else if (Math.floor(timeTo/800) <= 0) {
85 | gorillaX = 5;
86 | gorillaY = -1;
87 | paulineShown = false;
88 | }
89 | }
90 | else {
91 | timeTo = nextBarrelAdd - performance.now();
92 |
93 | if (timeTo < 5000 && timeTo > 4000) {
94 | barrelShown = true;
95 | }
96 | if (timeTo < 4000 && timeTo > 3000) {
97 | barrelShown = true;
98 | }
99 | if (timeTo < 3000 && timeTo > 2000) {
100 | barrelShown = true;
101 | gorillaX = 1;
102 | }
103 | if (timeTo < 2000 && timeTo > 1000) {
104 | gorillaX = 1;
105 | }
106 | }
107 |
108 | popups.forEach(function (popup, y) {
109 | var s = '', x;
110 |
111 | if (!popup.document.location) return;
112 |
113 | if (showingLevel) {
114 | if (y == 1) {
115 | for (x = 0; x < levelWidth; x ++) {
116 | if ((Math.floor(performance.now()/400) + x) % 2) {
117 | s += '🔺';
118 | }
119 | else {
120 | s += '🔻';
121 | }
122 | }
123 | }
124 | else if (y == 2) {
125 | s = toFullWidth('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀LEVEL:' + level);
126 | }
127 | else if (y == 3) {
128 | s = toFullWidth('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀LIVES:' + lives);
129 | }
130 | else if (y == 4) {
131 | for (x = 0; x < levelWidth; x ++) {
132 | if ((Math.floor(performance.now()/400) + x) % 2) {
133 | s += '🔻';
134 | }
135 | else {
136 | s += '🔺';
137 | }
138 | }
139 | }
140 | else {
141 | s = '';
142 | }
143 |
144 | popup.document.location.hash = s;
145 | }
146 | else if (gameover) {
147 | if (y == 1) {
148 | for (x = 0; x < levelWidth; x ++) {
149 | if ((Math.floor(performance.now()/200) % levelWidth) == levelWidth - x) {
150 | s += '🦍';
151 | }
152 | else {
153 | s += '💀';
154 | }
155 | }
156 | }
157 | else if (y == 2) {
158 | s = toFullWidth('⠀⠀⠀⠀⠀⠀⠀⠀⠀GAME⠀OVER');
159 | }
160 | else if (y == 3) {
161 | s = toFullWidth('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀LEVEL:' + level);
162 | }
163 | else if (y == 4) {
164 | for (x = 0; x < levelWidth; x ++) {
165 | if ((Math.floor(performance.now()/200) % levelWidth) == x) {
166 | s += '🦍';
167 | }
168 | else {
169 | s += '💀';
170 | }
171 | }
172 | }
173 | else {
174 | s = '';
175 | }
176 |
177 | popup.document.location.hash = s;
178 | }
179 | else {
180 | for (x = 0; x < levelWidth; x ++) {
181 | if (y == Math.floor(playerY) && x == Math.floor(playerX)) {
182 | if (death) {
183 | if (Math.floor(performance.now()/400) % 2) {
184 | s += '💀';
185 | }
186 | else {
187 | s += '🚶';
188 | }
189 | }
190 | else {
191 | if (didMove) {
192 | if (Math.floor(performance.now()/200) % 2) {
193 | s += '🚶';
194 | }
195 | else {
196 | s += '🏃';
197 | }
198 | }
199 | else {
200 | s += '🚶';
201 | }
202 | }
203 | }
204 | else if (hammering && y == playerY && x == hammerX /*&& Math.floor(performance.now()/100) % 2*/) {
205 | s += '🔨';
206 | }
207 | else if (y == gorillaY && x == gorillaX) {
208 | s += '🦍';
209 | }
210 | else if (y == 1 && x == 0 && barrelShown) {
211 | s += '🛢';
212 | }
213 | else if (barrelMap[x+','+y]) {
214 | s += '🔴';
215 | }
216 | else if (y == 0 && (x < 5 || x > 9)) {
217 | s += '🔶';
218 | }
219 | else if (paulineShown && y == 0 && x == 6) {
220 | s += pauline;
221 | }
222 | else if (levelWon && y == 0 && x == 7) {
223 | if (Math.floor(performance.now()/400) % 2) {
224 | s += paulineShown ? '💖' : '💔';
225 | }
226 | else {
227 | s += paulineShown ? '💕' : '⬜';
228 | }
229 | }
230 | else {
231 | if (map[y][x] == '1') s += '⬆';
232 | else if (map[y][x] == '2') s += '🔨';
233 | else s += '⬜';
234 | }
235 | }
236 |
237 | popup.document.location.hash = '🔶' + s + '🔶';
238 | }
239 |
240 | });
241 | }
242 |
243 | function movePlayer() {
244 | var xMove = 0, yMove = 0;
245 |
246 | didMove = false;
247 |
248 | if (keys[37]) {
249 | didMove = true;
250 | playerX --; // left
251 | hammerX = playerX - 1;
252 | }
253 | if (keys[39]) {
254 | didMove = true;
255 | playerX ++; // right
256 | hammerX = playerX + 1;
257 | }
258 |
259 | if (keys[38]) {
260 | if (!hammering && map[Math.floor(playerY)][Math.floor(playerX)] == '1') {
261 | didMove = true;
262 | playerY --; // up
263 | }
264 | }
265 | if (keys[40]) {
266 | if (Math.floor(playerY) < levelHeight - 1) {
267 | if (!hammering && map[Math.floor(playerY)+1][Math.floor(playerX)] == '1') {
268 | didMove = true;
269 | playerY ++; // down
270 | }
271 | }
272 | }
273 |
274 | if (playerX < 0) playerX = 0;
275 | else if (playerX >= levelWidth) playerX = levelWidth - 1;
276 |
277 | if (playerY < 0) playerY = 0;
278 | else if (playerY >= levelHeight) playerY = levelHeight - 1;
279 |
280 | if (didMove) {
281 | walkingAudio.volume = 1;
282 | lastPlayerMove = performance.now();
283 | }
284 | else {
285 | walkingAudio.volume = 0;
286 | }
287 |
288 | if (map[Math.floor(playerY)][Math.floor(playerX)] == '2') {
289 | map[Math.floor(playerY)][Math.floor(playerX)] = '3';
290 |
291 | hammering = true;
292 |
293 | hammeringAudio.currentTime = 0;
294 | hammeringAudio.volume = 1;
295 |
296 | setTimeout(function () {
297 | hammering = false;
298 | }, 9000);
299 | }
300 |
301 | if (playerY == 0) {
302 | playSound(baseURL + 'dk-levelend');
303 | levelWon = true;
304 | running = false;
305 | levelEndTime = performance.now() + 4000;
306 | setTimeout(function () {
307 | levelWon = false;
308 | nextLevel();
309 | setupLevel();
310 | showLevel();
311 | }, 4000);
312 | }
313 | }
314 |
315 | function moveBarrels() {
316 | var freshBarrels = [];
317 |
318 | barrelMap = {};
319 |
320 | barrels.forEach(function (barrel) {
321 | if (barrel.d == 1) {
322 | if (barrel.x >= levelWidth - 1) {
323 | barrel.y ++;
324 | barrel.d = 0;
325 | }
326 | else {
327 | barrel.x ++;
328 | }
329 | }
330 | else {
331 | if (barrel.x <= 0) {
332 | barrel.y ++;
333 | barrel.d = 1;
334 | }
335 | else {
336 | barrel.x --;
337 | }
338 | }
339 |
340 | if (barrel.y < levelHeight) {
341 | barrelMap[barrel.x+','+barrel.y] = true;
342 | freshBarrels.push(barrel);
343 | }
344 | });
345 |
346 | barrels = freshBarrels;
347 |
348 | lastBarrelMove = performance.now();
349 | }
350 |
351 | function addBarrel() {
352 | barrels.push({x:2, y:1, d:1});
353 |
354 | nextBarrelAdd = performance.now() + barrelBaseRate + (Math.random() * barrelRate);
355 | }
356 |
357 | function checkDeath() {
358 | if (barrelMap[Math.floor(playerX) +','+ Math.floor(playerY)]) {
359 | hammering = false;
360 | running = false;
361 | lives --;
362 | death = true;
363 | playSound(baseURL + 'dk-death');
364 |
365 | setTimeout(function () {
366 | if (lives <= 0) {
367 | gameover = true;
368 | }
369 | else {
370 | death = false;
371 | setupLevel();
372 | showLevel();
373 | }
374 | }, 5000);
375 | }
376 | }
377 |
378 | function checkSmash() {
379 | var freshBarrels;
380 |
381 | if (hammering) {
382 | console.log(barrelMap[Math.floor(hammerX) +','+ Math.floor(playerY)]);
383 | if (barrelMap[Math.floor(hammerX) +','+ Math.floor(playerY)]) {
384 | playSound(baseURL + 'dk-smash3', .3);
385 |
386 | barrelMap = {};
387 | freshBarrels = [];
388 |
389 | barrels.forEach(function (barrel) {
390 | if (barrel.x != Math.floor(hammerX) || barrel.y != Math.floor(playerY)) {
391 | barrelMap[barrel.x+','+barrel.y] = true;
392 | freshBarrels.push(barrel);
393 | }
394 | });
395 |
396 | barrels = freshBarrels;
397 | }
398 | }
399 | }
400 |
401 | function loop() {
402 | var now = performance.now();
403 |
404 | if (running) {
405 | if (hammering) {
406 | hammeringAudio.volume = 1;
407 | backgroundAudio.volume = 0;
408 | }
409 | else {
410 | hammeringAudio.volume = 0;
411 | backgroundAudio.volume = 1;
412 | }
413 |
414 | if (now - lastPlayerMove >= 200) {
415 | movePlayer();
416 | checkSmash();
417 | checkDeath();
418 | }
419 | if (now - lastBarrelMove >= barrelSpeed) {
420 | moveBarrels();
421 | checkSmash();
422 | checkDeath();
423 | }
424 | if (now > nextBarrelAdd) addBarrel();
425 | }
426 | else {
427 | hammeringAudio.volume = 0;
428 | backgroundAudio.volume = 0;
429 | walkingAudio.volume = 0;
430 | }
431 |
432 | render();
433 |
434 | setTimeout(loop, 1000/30);
435 | }
436 |
437 | function nextLevel() {
438 | var i, l, x, y;
439 |
440 | level ++;
441 |
442 | barrelRate -= 500;
443 | if (barrelBaseRate > 1000) {
444 | barrelBaseRate -= 250;
445 | }
446 |
447 | map.forEach(function (line, y) {
448 | var r, x;
449 |
450 | if (y == 0 || y == 1) return;
451 |
452 | if (y == 2) {
453 | r = 4 + Math.floor(Math.random() * (levelWidth-4));
454 | }
455 | else {
456 | r = Math.floor(Math.random() * levelWidth);
457 | }
458 |
459 | for (x = 0; x < levelWidth; x ++) {
460 | line[x] = x == r ? '1' : ' ';
461 | }
462 | });
463 |
464 | if (level <= 3) l = 2;
465 | else if (level <= 5) l = 1;
466 |
467 | for (i = 0; i < l; i ++) {
468 | map[Math.floor(Math.random()*(levelHeight - 2))+2][Math.floor(Math.random()*levelWidth)] = '1';
469 | }
470 |
471 | if (level <= 5) {
472 | do {
473 | y = Math.floor(Math.random()*(levelHeight - 2))+2;
474 | x = Math.floor(Math.random()*levelWidth);
475 | }
476 | while (map[y][x] == '1');
477 |
478 | map[y][x] = '2';
479 | }
480 | }
481 |
482 | function setupLevel() {
483 | map.forEach(function (line) {
484 | var i;
485 |
486 | for (i = 0; i < line.length; i ++) {
487 | if (line[i] == '3') line[i] = '2';
488 | }
489 | });
490 |
491 | playerX = 0;
492 | playerY = levelHeight - 1;
493 | barrels = [];
494 | barrelMap = {};
495 | lastBarrelMove = 0;
496 | lastPlayerMove = 0;
497 | running = true;
498 | }
499 |
500 | function showLevel() {
501 | playSound(baseURL + 'dk-howhigh');
502 | showingLevel = true;
503 | running = false;
504 | setTimeout(function () {
505 | showingLevel = false;
506 | running = true;
507 | }, 2500);
508 | }
509 |
510 | function closeWindows() {
511 | var popup;
512 |
513 | running = false;
514 |
515 | while (popups.length) {
516 | popup = popups.shift();
517 | popup.close();
518 | }
519 | }
520 |
521 | function setup() {
522 | var split = location.href.split('/');
523 | split.pop();
524 | baseURL = split.join('/') + '/';
525 |
526 | document.addEventListener('keydown', function (event) {
527 | keys[event.which] = true;
528 | });
529 |
530 | document.addEventListener('keyup', function (event) {
531 | keys[event.which] = false;
532 | });
533 |
534 | document.title = '-';
535 |
536 | closeButton = document.createElement('button');
537 | closeButton.innerHTML = 'close';
538 | closeButton.addEventListener('click', closeWindows);
539 | document.body.appendChild(closeButton);
540 |
541 | walkingAudio = playSound(baseURL + 'dk-walking', 0);
542 | walkingAudio.loop = true;
543 | backgroundAudio = playSound(baseURL + 'dk-bacmusic', 0);
544 | backgroundAudio.loop = true;
545 | hammeringAudio = playSound(baseURL + 'dk-hammertime', 0);
546 | hammeringAudio.loop = true;
547 |
548 | setupLevel();
549 | showLevel();
550 | loop();
551 |
552 | popups.forEach(function (popup) {
553 | popup.history.replaceState({}, '', '/');
554 | });
555 | }
556 |
--------------------------------------------------------------------------------
/face.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/face.png
--------------------------------------------------------------------------------
/hit1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/hit1.wav
--------------------------------------------------------------------------------
/hit2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/hit2.wav
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
77 |
--------------------------------------------------------------------------------
/over.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/over.wav
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
18 |
--------------------------------------------------------------------------------
/renderer.js:
--------------------------------------------------------------------------------
1 | function Renderer() {
2 | var self = this,
3 | container = document.getElementById('container');
4 |
5 | self.tRenderer = new THREE.WebGLRenderer({alpha: false, antialias: true});
6 | container.appendChild(self.tRenderer.domElement);
7 |
8 | self.scene = new THREE.Scene();
9 |
10 | self.setSize = function (width, height) {
11 | self.width = width;
12 | self.height = height;
13 |
14 | self.tRenderer.setSize(self.width, self.height);
15 |
16 | self.renderTarget = new THREE.WebGLRenderTarget(self.width/10, self.height/10, {minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter});
17 | self.camera = new THREE.PerspectiveCamera(70, self.width / self.height, 1, 1000);
18 | };
19 |
20 | self.setSize(500, 200);
21 |
22 | self.render = function render() {
23 | self.tRenderer.render(self.scene, self.camera);
24 | self.tRenderer.render(self.scene, self.camera, self.renderTarget);
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | function toFullWidth(input) {
2 | var lookup = {"`" : "`","1" : "1","2" : "2","3" : "3","4" : "4","5" : "5","6" : "6","7" : "7","8" : "8","9" : "9","0" : "0","-" : "-","=" : "=","~" : "~","!" : "!","@" : "@","#" : "#","$" : "$","%" : "%","^" : "^","&" : "&","*" : "*","(" : "(",")" : ")","_" : "_","+" : "+","q" : "q","w" : "w","e" : "e","r" : "r","t" : "t","y" : "y","u" : "u","i" : "i","o" : "o","p" : "p","[" : "[","]" : "]","\\" : "\\","Q" : "Q","W" : "W","E" : "E","R" : "R","T" : "T","Y" : "Y","U" : "U","I" : "I","O" : "O","P" : "P","{" : "{","}" : "}","|" : "|","a" : "a","s" : "s","d" : "d","f" : "f","g" : "g","h" : "h","j" : "j","k" : "k","l" : "l",";" : ";","'" : "'","A" : "A","S" : "S","D" : "D","F" : "F","G" : "G","H" : "H","J" : "J","K" : "K","L" : "L",":" : ":","\"" : "\"","z" : "z","x" : "x","c" : "c","v" : "v","b" : "b","n" : "n","m" : "m","," : ",","." : ".","/" : "/","Z" : "Z","X" : "X","C" : "C","V" : "V","B" : "B","N" : "N","M" : "M","<" : "<",">" : ">","?" : "?", " ": " "},
3 | output = '';
4 |
5 | input.split('').forEach(function (c) {
6 | if (lookup[c]) output += lookup[c];
7 | else output += c;
8 | });
9 |
10 | return output;
11 | }
12 |
13 | function playSound(fileName, volume) {
14 | var audio;
15 |
16 | if (volume == undefined) volume = 1;
17 | if (volume > 1) volume = 1;
18 |
19 | audio = new (Audio)(fileName + '.wav');
20 | audio.volume = volume;
21 |
22 | audio.play();
23 |
24 | return audio;
25 | }
26 |
27 | function degToRad(degrees) {
28 | return degrees * (Math.PI / 180);
29 | }
30 | function radToDeg(radians) {
31 | return radians * (180 / Math.PI);
32 | }
33 |
34 | function rotateCoord(coords, angle) {
35 | var x = coords.x, y = coords.y;
36 | var radians = degToRad(angle),
37 | cos = Math.cos(radians),
38 | sin = Math.sin(radians),
39 | nx = (cos * x) + (sin * y),
40 | ny = (cos * y) - (sin * x);
41 |
42 | return {x: nx, y: ny};
43 | }
44 |
--------------------------------------------------------------------------------
/wavy.js:
--------------------------------------------------------------------------------
1 | var popups = [];
2 |
3 | function loop() {
4 | popups.forEach(function (popup, y) {
5 | var i,
6 | s = '#',
7 | l = Math.round(Math.sin(performance.now()/200 + (y/2)) * 5) + 5;
8 |
9 | s += String.fromCharCode(0x2588);
10 |
11 | for (i = 0; i < l; i ++) {
12 | //s += String.fromCharCode(0x2581);
13 | s += String.fromCharCode(0x2588);
14 | }
15 |
16 | //s += 'x';
17 |
18 | popup.document.location.hash = s;
19 | });
20 |
21 | setTimeout(loop, 1000/20);
22 | }
23 |
24 | function closeWindows() {
25 | var popup;
26 |
27 | running = false;
28 |
29 | while (popups.length) {
30 | popup = popups.shift();
31 | popup.close();
32 | }
33 | }
34 |
35 | function setup() {
36 | var closeButton;
37 |
38 | closeButton = document.createElement('button');
39 | closeButton.innerHTML = 'close';
40 | closeButton.addEventListener('click', closeWindows);
41 | document.body.appendChild(closeButton);
42 |
43 | loop();
44 |
45 | /*popups.forEach(function (popup) {
46 | popup.history.replaceState({}, '', '/');
47 | });*/
48 | }
49 |
--------------------------------------------------------------------------------
/webcam.js:
--------------------------------------------------------------------------------
1 | var popups = [],
2 | video = document.createElement('video'),
3 | canvas = document.createElement('canvas'),
4 | context = canvas.getContext('2d'),
5 | vWidth, vHeight,
6 | oWidth, oHeight,
7 | palette;
8 |
9 | function loop() {
10 | var x, y, i, r, g, b;
11 |
12 | context.drawImage(video, 0, 0, oWidth, oHeight);
13 | data = context.getImageData(0, 0, oWidth, oHeight).data;
14 |
15 | popups.forEach(function (popup, y) {
16 | var s = '';
17 |
18 | y *= 2;
19 |
20 | for (x = 0; x < oWidth; x ++) {
21 | i = 4 * ((y * oWidth) + x);
22 |
23 | r = Math.floor(data[i + 0] / 16);
24 | g = Math.floor(data[i + 1] / 16);
25 | b = Math.floor(data[i + 2] / 16);
26 |
27 | s += palette[r][g][b].s;
28 | }
29 |
30 | popup.document.location.hash = s;
31 | });
32 |
33 | setTimeout(loop, 1000/10);
34 | }
35 |
36 | function closeWindows() {
37 | var popup;
38 |
39 | running = false;
40 |
41 | while (popups.length) {
42 | popup = popups.shift();
43 | popup.close();
44 | }
45 | }
46 |
47 | function setup() {
48 | var xhr = new XMLHttpRequest();
49 |
50 | closeButton = document.createElement('button');
51 | closeButton.innerHTML = 'close';
52 | closeButton.addEventListener('click', closeWindows);
53 | document.body.appendChild(closeButton);
54 |
55 | xhr.open('GET', 'palette.json');
56 | xhr.addEventListener('readystatechange', function () {
57 | if (xhr.readyState == 4) {
58 | palette = JSON.parse(xhr.responseText);
59 |
60 | if (navigator.mediaDevices.getUserMedia) {
61 | navigator.mediaDevices.getUserMedia({video: true})
62 | .then(function (stream) {
63 | video.srcObject = stream;
64 | video.play();
65 |
66 | setTimeout(function () {
67 | vWidth = video.videoWidth || video.naturalWidth || video.width;
68 | vHeight = video.videoHeight || video.naturalHeight || video.height;
69 |
70 | oHeight = popups.length * 2;
71 | oWidth = Math.floor((vWidth/vHeight) * oHeight);
72 |
73 | canvas.width = oWidth;
74 | canvas.height = oHeight;
75 |
76 | loop();
77 | }, 1000);
78 | })
79 | .catch(function (error) {
80 | console.log("Something went wrong!");
81 | });
82 | }
83 | }
84 | });
85 | xhr.send();
86 |
87 | popups.forEach(function (popup) {
88 | popup.history.replaceState({}, '', '/');
89 | });
90 | }
91 |
--------------------------------------------------------------------------------
/win.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatthewRayfield/url-bar-games/a268775abec4b6a0278eae0e7b37d0b5b334858e/win.wav
--------------------------------------------------------------------------------