Back to Home
Gistify Snippet
kyiov
Sep 20ยท1 file(s)
Public

3D Tower Blocks (susun balok) inline WhatsApp

#github-gist#javascript#game#gist:d2cbbe38e3a50b2c289f49ce1807823f
12
Raw
towergame.jsjavascript
825 lines
1import crypto from 'crypto'
2import { AIRichBuilder } from '#system/lib/mbuilder.js'
3import { wrapWithVendors } from '#system/lib/game_vendors.js'
4
5const towerHtml = `<style>
6@import url("https://fonts.googleapis.com/css?family=Comfortaa");
7* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
8body {
9 margin: 0;
10 background: #D0CBC7;
11 color: #334;
12 font-family: "Comfortaa", cursive, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
13 min-height: 100dvh;
14 display: flex;
15 flex-direction: column;
16 align-items: center;
17 justify-content: flex-start;
18 padding: 10px 8px;
19 touch-action: manipulation;
20 cursor: pointer;
21 overflow: hidden;
22}
23.stage {
24 width: 100%;
25 max-width: 280px;
26 margin: 0 auto;
27 display: flex;
28 justify-content: center;
29}
30.stage-wrap {
31 position: relative;
32 width: 280px;
33 height: 560px;
34 max-width: 100%;
35 aspect-ratio: 1 / 2;
36 border-radius: 14px;
37 overflow: hidden;
38 border: 1px solid rgba(51, 51, 68, 0.25);
39 box-shadow: 0 10px 30px rgba(51, 51, 68, 0.25);
40 background: #D0CBC7;
41}
42#game {
43 position: absolute;
44 inset: 0;
45 width: 100%;
46 height: 100%;
47}
48#game canvas {
49 display: block;
50 width: 100%;
51 height: 100%;
52}
53#score {
54 position: absolute;
55 top: 20px;
56 width: 100%;
57 text-align: center;
58 font-size: 54px;
59 font-weight: 700;
60 color: #334;
61 pointer-events: none;
62 transition: transform 0.5s ease;
63 z-index: 10;
64}
65.stage-wrap.ended #score {
66 transform: translateY(56px) scale(1.5);
67}
68.chips {
69 position: absolute;
70 top: 14px;
71 right: 12px;
72 display: flex;
73 gap: 6px;
74 align-items: center;
75 z-index: 12;
76}
77.chips span {
78 font-size: 11px;
79 font-weight: 700;
80 color: #334;
81 background: rgba(51, 51, 68, 0.08);
82 border: 1px solid rgba(51, 51, 68, 0.2);
83 border-radius: 12px;
84 padding: 4px 9px;
85}
86.chips button {
87 background: rgba(51, 51, 68, 0.08);
88 border: 1px solid rgba(51, 51, 68, 0.2);
89 border-radius: 12px;
90 padding: 3px 7px;
91 font-size: 11px;
92 cursor: pointer;
93}
94#instructions {
95 position: absolute;
96 top: 90px;
97 width: 100%;
98 text-align: center;
99 font-size: 12px;
100 color: #334;
101 font-weight: 700;
102 pointer-events: none;
103 transition: opacity 0.4s ease;
104 z-index: 10;
105 padding: 0 20px;
106 opacity: 0;
107}
108#instructions.hide { opacity: 0 !important; }
109.stage-wrap.playing #instructions { opacity: 1; }
110.game-over {
111 position: absolute;
112 inset: 0;
113 display: flex;
114 flex-direction: column;
115 align-items: center;
116 justify-content: center;
117 background: rgba(208, 203, 199, 0.9);
118 z-index: 20;
119 opacity: 0;
120 pointer-events: none;
121 transition: opacity 0.4s ease;
122 padding: 24px;
123 text-align: center;
124}
125.game-over h2 {
126 font-size: 30px;
127 font-weight: 700;
128 color: #334;
129 margin-bottom: 8px;
130}
131.game-over .final-score {
132 font-size: 34px;
133 font-weight: 700;
134 color: #334;
135 margin: 6px 0;
136}
137.game-over p {
138 font-size: 13px;
139 color: #445;
140 margin: 4px 0;
141}
142.game-over .retry-hint {
143 margin-top: 18px;
144 font-size: 14px;
145 color: #334;
146 font-weight: 700;
147 animation: pulse 1.5s infinite;
148}
149.game-ready {
150 position: absolute;
151 inset: 0;
152 display: flex;
153 flex-direction: column;
154 align-items: center;
155 justify-content: center;
156 background: transparent;
157 z-index: 20;
158 transition: opacity 0.4s ease;
159}
160.game-ready #start-button {
161 border: 3px solid #334;
162 padding: 12px 32px;
163 background: transparent;
164 color: #334;
165 font-size: 22px;
166 font-weight: 700;
167 letter-spacing: 1px;
168 cursor: pointer;
169 transition: transform 0.15s ease;
170}
171.game-ready #start-button:active { transform: scale(0.95); }
172.stage-wrap.ready .game-ready { opacity: 1; pointer-events: auto; }
173.stage-wrap.playing .game-ready,
174.stage-wrap.ended .game-ready { opacity: 0; pointer-events: none; }
175.stage-wrap.ended .game-over { opacity: 1; pointer-events: auto; }
176.foot {
177 font-size: 11px;
178 color: #556;
179 margin-top: 8px;
180 text-align: center;
181 letter-spacing: 0.3px;
182}
183.foot b { color: #334; }
184@keyframes pulse {
185 0%, 100% { transform: scale(1); opacity: 1; }
186 50% { transform: scale(1.05); opacity: 0.85; }
187}
188</style>
189
190<div class="stage">
191 <div class="stage-wrap ready" id="container">
192 <div id="game"></div>
193 <div id="score">0</div>
194 <div class="chips">
195 <span id="hdrBest">๐Ÿ† 0</span>
196 <button id="audioToggle" title="Toggle Sound">๐Ÿ”Š</button>
197 </div>
198 <div id="instructions">Ketuk layar untuk menaruh balok tepat di atasnya</div>
199 <div class="game-over">
200 <h2>GAME OVER</h2>
201 <p>Menara Anda runtuh!</p>
202 <div class="final-score" id="finalScore">0</div>
203 <p id="bestScoreText">Rekor Terbaik: 0</p>
204 <div class="retry-hint">๐Ÿ”„ KETUK UNTUK MAIN LAGI</div>
205 </div>
206 <div class="game-ready">
207 <div id="start-button">MULAI</div>
208 </div>
209 </div>
210</div>
211
212<div class="foot">
213 ๐Ÿ‘† <b>TAP DI MANA SAJA</b> untuk menaruh balok ยท adaptasi game by TANAY PADAR
214</div>
215<script>
216(function() {
217 if (window.AndroidBridge && window.AndroidBridge.updateSize) {
218 try { window.AndroidBridge.updateSize(650); } catch(e) {}
219 }
220 var container = document.getElementById("container");
221 var hdrBest = document.getElementById("hdrBest");
222 var audioToggle = document.getElementById("audioToggle");
223 var finalScore = document.getElementById("finalScore");
224 var bestScoreText = document.getElementById("bestScoreText");
225
226 var bestScore = 0;
227 try {
228 bestScore = parseInt(localStorage.getItem('mithra_tower_best') || '0', 10) || 0;
229 } catch(e) {}
230 hdrBest.textContent = "๐Ÿ† " + bestScore;
231
232 // WebAudio Synth
233 var audioCtx = null;
234 var muted = false;
235 function initAudio() {
236 if (!audioCtx) {
237 var AC = window.AudioContext || window.webkitAudioContext;
238 if (AC) {
239 try { audioCtx = new AC(); } catch (e) {}
240 }
241 }
242 if (audioCtx && audioCtx.state === 'suspended') {
243 audioCtx.resume().catch(function() {});
244 }
245 }
246
247 function playTone(freq, dur, type, vol) {
248 if (muted) return;
249 initAudio();
250 if (!audioCtx) return;
251 try {
252 var osc = audioCtx.createOscillator();
253 var gain = audioCtx.createGain();
254 osc.type = type || 'sine';
255 osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
256 gain.gain.setValueAtTime(vol || 0.12, audioCtx.currentTime);
257 gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + dur);
258 osc.connect(gain);
259 gain.connect(audioCtx.destination);
260 osc.start();
261 osc.stop(audioCtx.currentTime + dur);
262 } catch(e) {}
263 }
264
265 var PENTATONIC = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25, 587.33, 659.25, 783.99, 880.00];
266 function playPlace(index, isBonus) {
267 var note = PENTATONIC[index % PENTATONIC.length];
268 var octave = Math.floor(index / PENTATONIC.length) * 0.25;
269 var freq = note * Math.pow(2, octave);
270 playTone(freq, isBonus ? 0.3 : 0.12, isBonus ? 'triangle' : 'sine', isBonus ? 0.16 : 0.1);
271 if (isBonus) {
272 setTimeout(function() { playTone(freq * 1.5, 0.25, 'triangle', 0.14); }, 70);
273 }
274 }
275 function playHitMiss() {
276 playTone(160, 0.25, 'sawtooth', 0.15);
277 setTimeout(function() { playTone(110, 0.35, 'sawtooth', 0.12); }, 90);
278 }
279
280 audioToggle.addEventListener('click', function(e) {
281 e.stopPropagation();
282 initAudio();
283 muted = !muted;
284 audioToggle.textContent = muted ? '๐Ÿ”‡' : '๐Ÿ”Š';
285 });
286
287 var Stage = (function () {
288 function Stage() {
289 var _this = this;
290 this.render = function () {
291 this.renderer.render(this.scene, this.camera);
292 };
293 this.add = function (elem) {
294 this.scene.add(elem);
295 };
296 this.remove = function (elem) {
297 this.scene.remove(elem);
298 };
299 this.container = document.getElementById("game");
300
301 this.renderer = new THREE.WebGLRenderer({
302 antialias: true,
303 alpha: false,
304 });
305
306 var w = 280;
307 var h = 560;
308 this.renderer.setSize(w, h);
309 this.renderer.setClearColor("#D0CBC7", 1);
310 this.container.appendChild(this.renderer.domElement);
311
312 this.scene = new THREE.Scene();
313
314 var aspect = w / h; // 0.5
315 var d = 20;
316 this.camera = new THREE.OrthographicCamera(
317 -d * aspect,
318 d * aspect,
319 d,
320 -d,
321 -100,
322 1000
323 );
324 this.camera.position.x = 2;
325 this.camera.position.y = 2;
326 this.camera.position.z = 2;
327 this.camera.lookAt(new THREE.Vector3(0, 0, 0));
328
329 this.light = new THREE.DirectionalLight(0xffffff, 0.5);
330 this.light.position.set(0, 499, 0);
331 this.scene.add(this.light);
332 this.softLight = new THREE.AmbientLight(0xffffff, 0.4);
333 this.scene.add(this.softLight);
334
335 window.addEventListener("resize", function () {
336 return _this.onResize();
337 });
338 this.onResize();
339 }
340
341 Stage.prototype.setCamera = function (y, speed) {
342 if (speed === void 0) speed = 0.3;
343 TweenLite.to(this.camera.position, speed, {
344 y: y + 4,
345 ease: Power1.easeInOut,
346 });
347 TweenLite.to(this.camera.lookAt, speed, { y: y, ease: Power1.easeInOut });
348 };
349
350 Stage.prototype.onResize = function () {
351 var w = this.container.clientWidth || 280;
352 var h = this.container.clientHeight || (w * 2);
353 this.renderer.setSize(w, h);
354 var aspect = w / h;
355 var d = 20;
356 this.camera.left = -d * aspect;
357 this.camera.right = d * aspect;
358 this.camera.top = d;
359 this.camera.bottom = -d;
360 this.camera.updateProjectionMatrix();
361 };
362 return Stage;
363 })();
364
365 var Block = (function () {
366 function Block(block) {
367 this.STATES = { ACTIVE: "active", STOPPED: "stopped", MISSED: "missed" };
368 this.MOVE_AMOUNT = 12;
369 this.dimension = { width: 0, height: 0, depth: 0 };
370 this.position = { x: 0, y: 0, z: 0 };
371 this.targetBlock = block;
372 this.index = (this.targetBlock ? this.targetBlock.index : 0) + 1;
373 this.workingPlane = this.index % 2 ? "x" : "z";
374 this.workingDimension = this.index % 2 ? "width" : "depth";
375
376 this.dimension.width = this.targetBlock ? this.targetBlock.dimension.width : 10;
377 this.dimension.height = this.targetBlock ? this.targetBlock.dimension.height : 2;
378 this.dimension.depth = this.targetBlock ? this.targetBlock.dimension.depth : 10;
379
380 this.position.x = this.targetBlock ? this.targetBlock.position.x : 0;
381 this.position.y = this.dimension.height * this.index;
382 this.position.z = this.targetBlock ? this.targetBlock.position.z : 0;
383 this.colorOffset = this.targetBlock ? this.targetBlock.colorOffset : Math.round(Math.random() * 100);
384
385 if (!this.targetBlock) {
386 this.color = 0x333344;
387 } else {
388 var offset = this.index + this.colorOffset;
389 var r = Math.sin(0.3 * offset) * 55 + 200;
390 var g = Math.sin(0.3 * offset + 2) * 55 + 200;
391 var b = Math.sin(0.3 * offset + 4) * 55 + 200;
392 this.color = new THREE.Color(r / 255, g / 255, b / 255);
393 }
394
395 this.state = this.index > 1 ? this.STATES.ACTIVE : this.STATES.STOPPED;
396 this.speed = -0.1 - this.index * 0.005;
397 if (this.speed < -4) this.speed = -4;
398 this.direction = this.speed;
399
400 var geometry = new THREE.BoxGeometry(
401 this.dimension.width,
402 this.dimension.height,
403 this.dimension.depth
404 );
405 geometry.applyMatrix(
406 new THREE.Matrix4().makeTranslation(
407 this.dimension.width / 2,
408 this.dimension.height / 2,
409 this.dimension.depth / 2
410 )
411 );
412 this.material = new THREE.MeshToonMaterial({
413 color: this.color,
414 shading: THREE.FlatShading,
415 });
416 this.mesh = new THREE.Mesh(geometry, this.material);
417 this.mesh.position.set(
418 this.position.x,
419 this.position.y,
420 this.position.z
421 );
422 if (this.state == this.STATES.ACTIVE) {
423 this.position[this.workingPlane] = Math.random() > 0.5 ? -this.MOVE_AMOUNT : this.MOVE_AMOUNT;
424 }
425 }
426
427 Block.prototype.reverseDirection = function () {
428 this.direction = this.direction > 0 ? this.speed : Math.abs(this.speed);
429 };
430
431 Block.prototype.place = function () {
432 this.state = this.STATES.STOPPED;
433 var overlap =
434 this.targetBlock.dimension[this.workingDimension] -
435 Math.abs(
436 this.position[this.workingPlane] -
437 this.targetBlock.position[this.workingPlane]
438 );
439 var blocksToReturn = {
440 plane: this.workingPlane,
441 direction: this.direction,
442 };
443
444 if (this.dimension[this.workingDimension] - overlap < 0.3) {
445 overlap = this.dimension[this.workingDimension];
446 blocksToReturn.bonus = true;
447 this.position.x = this.targetBlock.position.x;
448 this.position.z = this.targetBlock.position.z;
449 this.dimension.width = this.targetBlock.dimension.width;
450 this.dimension.depth = this.targetBlock.dimension.depth;
451 }
452
453 if (overlap > 0) {
454 var choppedDimensions = {
455 width: this.dimension.width,
456 height: this.dimension.height,
457 depth: this.dimension.depth,
458 };
459 choppedDimensions[this.workingDimension] -= overlap;
460 this.dimension[this.workingDimension] = overlap;
461
462 var placedGeometry = new THREE.BoxGeometry(
463 this.dimension.width,
464 this.dimension.height,
465 this.dimension.depth
466 );
467 placedGeometry.applyMatrix(
468 new THREE.Matrix4().makeTranslation(
469 this.dimension.width / 2,
470 this.dimension.height / 2,
471 this.dimension.depth / 2
472 )
473 );
474 var placedMesh = new THREE.Mesh(placedGeometry, this.material);
475
476 var choppedGeometry = new THREE.BoxGeometry(
477 choppedDimensions.width,
478 choppedDimensions.height,
479 choppedDimensions.depth
480 );
481 choppedGeometry.applyMatrix(
482 new THREE.Matrix4().makeTranslation(
483 choppedDimensions.width / 2,
484 choppedDimensions.height / 2,
485 choppedDimensions.depth / 2
486 )
487 );
488 var choppedMesh = new THREE.Mesh(choppedGeometry, this.material);
489
490 var choppedPosition = {
491 x: this.position.x,
492 y: this.position.y,
493 z: this.position.z,
494 };
495 if (
496 this.position[this.workingPlane] <
497 this.targetBlock.position[this.workingPlane]
498 ) {
499 this.position[this.workingPlane] = this.targetBlock.position[
500 this.workingPlane
501 ];
502 } else {
503 choppedPosition[this.workingPlane] += overlap;
504 }
505
506 placedMesh.position.set(
507 this.position.x,
508 this.position.y,
509 this.position.z
510 );
511 choppedMesh.position.set(
512 choppedPosition.x,
513 choppedPosition.y,
514 choppedPosition.z
515 );
516 blocksToReturn.placed = placedMesh;
517 if (!blocksToReturn.bonus) blocksToReturn.chopped = choppedMesh;
518 } else {
519 this.state = this.STATES.MISSED;
520 }
521 this.dimension[this.workingDimension] = overlap;
522 return blocksToReturn;
523 };
524
525 Block.prototype.tick = function () {
526 if (this.state == this.STATES.ACTIVE) {
527 var value = this.position[this.workingPlane];
528 if (value > this.MOVE_AMOUNT || value < -this.MOVE_AMOUNT)
529 this.reverseDirection();
530 this.position[this.workingPlane] += this.direction;
531 this.mesh.position[this.workingPlane] = this.position[this.workingPlane];
532 }
533 };
534 return Block;
535 })();
536
537 var Game = (function () {
538 function Game() {
539 var _this = this;
540 this.STATES = {
541 LOADING: "loading",
542 PLAYING: "playing",
543 READY: "ready",
544 ENDED: "ended",
545 RESETTING: "resetting",
546 };
547 this.blocks = [];
548 this.state = this.STATES.LOADING;
549 this.stage = new Stage();
550 this.mainContainer = document.getElementById("container");
551 this.scoreContainer = document.getElementById("score");
552 this.startButton = document.getElementById("start-button");
553 this.instructions = document.getElementById("instructions");
554 this.scoreContainer.innerHTML = "0";
555
556 this.newBlocks = new THREE.Group();
557 this.placedBlocks = new THREE.Group();
558 this.choppedBlocks = new THREE.Group();
559
560 this.stage.add(this.newBlocks);
561 this.stage.add(this.placedBlocks);
562 this.stage.add(this.choppedBlocks);
563
564 this.addBlock();
565 this.tick();
566 this.updateState(this.STATES.READY);
567
568 var lastActionTime = 0;
569 function onActionSafe(e) {
570 var now = Date.now();
571 if (now - lastActionTime < 70) return;
572 lastActionTime = now;
573 if (e && e.target && e.target.id === 'audioToggle') return;
574 if (e && e.cancelable && e.type !== 'click') e.preventDefault();
575 _this.onAction();
576 }
577
578 window.addEventListener("pointerdown", onActionSafe, { passive: false });
579 window.addEventListener("keydown", function (e) {
580 if (e.keyCode == 32 || e.code === 'Space' || e.code === 'ArrowUp' || e.key === 'w' || e.key === 'W') {
581 e.preventDefault();
582 onActionSafe(e);
583 }
584 });
585 }
586
587 Game.prototype.updateState = function (newState) {
588 for (var key in this.STATES)
589 this.mainContainer.classList.remove(this.STATES[key]);
590 this.mainContainer.classList.add(newState);
591 this.state = newState;
592 };
593
594 Game.prototype.onAction = function () {
595 initAudio();
596 switch (this.state) {
597 case this.STATES.READY:
598 this.startGame();
599 break;
600 case this.STATES.PLAYING:
601 this.placeBlock();
602 break;
603 case this.STATES.ENDED:
604 this.restartGame();
605 break;
606 }
607 };
608
609 Game.prototype.startGame = function () {
610 if (this.state != this.STATES.PLAYING) {
611 this.scoreContainer.innerHTML = "0";
612 this.updateState(this.STATES.PLAYING);
613 this.addBlock();
614 }
615 };
616
617 Game.prototype.restartGame = function () {
618 var _this = this;
619 this.updateState(this.STATES.RESETTING);
620 var oldBlocks = this.placedBlocks.children;
621 var removeSpeed = 0.2;
622 var delayAmount = 0.02;
623 var _loop_1 = function (i) {
624 TweenLite.to(oldBlocks[i].scale, removeSpeed, {
625 x: 0,
626 y: 0,
627 z: 0,
628 delay: (oldBlocks.length - i) * delayAmount,
629 ease: Power1.easeIn,
630 onComplete: function () {
631 return _this.placedBlocks.remove(oldBlocks[i]);
632 },
633 });
634 TweenLite.to(oldBlocks[i].rotation, removeSpeed, {
635 y: 0.5,
636 delay: (oldBlocks.length - i) * delayAmount,
637 ease: Power1.easeIn,
638 });
639 };
640 for (var i = 0; i < oldBlocks.length; i++) {
641 _loop_1(i);
642 }
643 var cameraMoveSpeed = removeSpeed * 2 + oldBlocks.length * delayAmount;
644 this.stage.setCamera(2, cameraMoveSpeed);
645 var countdown = { value: this.blocks.length - 1 };
646 TweenLite.to(countdown, cameraMoveSpeed, {
647 value: 0,
648 onUpdate: function () {
649 var val = String(Math.round(countdown.value));
650 _this.scoreContainer.innerHTML = val;
651 },
652 });
653 this.blocks = this.blocks.slice(0, 1);
654 setTimeout(function () {
655 _this.startGame();
656 }, cameraMoveSpeed * 1000);
657 };
658
659 Game.prototype.placeBlock = function () {
660 var _this = this;
661 var currentBlock = this.blocks[this.blocks.length - 1];
662 var newBlocks = currentBlock.place();
663 this.newBlocks.remove(currentBlock.mesh);
664
665 if (newBlocks.placed) this.placedBlocks.add(newBlocks.placed);
666 if (newBlocks.chopped) {
667 this.choppedBlocks.add(newBlocks.chopped);
668 var positionParams = {
669 y: "-=30",
670 ease: Power1.easeIn,
671 onComplete: function () {
672 return _this.choppedBlocks.remove(newBlocks.chopped);
673 },
674 };
675 var rotateRandomness = 10;
676 var rotationParams = {
677 delay: 0.05,
678 x:
679 newBlocks.plane == "z"
680 ? Math.random() * rotateRandomness - rotateRandomness / 2
681 : 0.1,
682 z:
683 newBlocks.plane == "x"
684 ? Math.random() * rotateRandomness - rotateRandomness / 2
685 : 0.1,
686 y: Math.random() * 0.1,
687 };
688 if (
689 newBlocks.chopped.position[newBlocks.plane] >
690 newBlocks.placed.position[newBlocks.plane]
691 ) {
692 positionParams[newBlocks.plane] =
693 "+=" + 40 * Math.abs(newBlocks.direction);
694 } else {
695 positionParams[newBlocks.plane] =
696 "-=" + 40 * Math.abs(newBlocks.direction);
697 }
698 TweenLite.to(newBlocks.chopped.position, 1, positionParams);
699 TweenLite.to(newBlocks.chopped.rotation, 1, rotationParams);
700 }
701
702 if (currentBlock.state !== currentBlock.STATES.MISSED) {
703 playPlace(this.blocks.length, newBlocks.bonus);
704 }
705
706 this.addBlock();
707 };
708
709 Game.prototype.addBlock = function () {
710 var lastBlock = this.blocks[this.blocks.length - 1];
711 if (lastBlock && lastBlock.state == lastBlock.STATES.MISSED) {
712 return this.endGame();
713 }
714 var sc = String(this.blocks.length - 1);
715 this.scoreContainer.innerHTML = sc;
716
717 var newKidOnTheBlock = new Block(lastBlock);
718 this.newBlocks.add(newKidOnTheBlock.mesh);
719 this.blocks.push(newKidOnTheBlock);
720 this.stage.setCamera(this.blocks.length * 2);
721 if (this.blocks.length >= 5) this.instructions.classList.add("hide");
722 };
723
724 Game.prototype.endGame = function () {
725 this.updateState(this.STATES.ENDED);
726 playHitMiss();
727 var currentScore = Math.max(0, this.blocks.length - 2);
728 finalScore.textContent = currentScore;
729 if (currentScore > bestScore) {
730 bestScore = currentScore;
731 hdrBest.textContent = "๐Ÿ† " + bestScore;
732 try { localStorage.setItem('mithra_tower_best', String(bestScore)); } catch(e) {}
733 }
734 bestScoreText.textContent = "Rekor Terbaik: " + bestScore;
735 };
736
737 Game.prototype.tick = function () {
738 var _this = this;
739 this.blocks[this.blocks.length - 1].tick();
740 this.stage.render();
741 requestAnimationFrame(function () {
742 _this.tick();
743 });
744 };
745 return Game;
746 })();
747
748 // Mulai game ketika THREE & TweenLite siap
749 function checkReady() {
750 if (window.THREE && window.TweenLite) {
751 new Game();
752 } else {
753 setTimeout(checkReady, 50);
754 }
755 }
756 checkReady();
757})();
758<\/script>`
759
760export default function towerGame(ev) {
761 ev.on({
762 name: 'towergame',
763 cmd: ['towergame', 'towerblocks', 'tower', 'stackgame', 'susunbalok'],
764 tags: 'Game',
765 desc: 'Mainkan game 3D Tower Blocks (Stacking Balok) interaktif di WhatsApp',
766 run: async (xp, m) => {
767 const loadingMsg = await xp.sendThinkingStatus(m.chat, '๐Ÿ—๏ธ', { quoted: m }).catch(() => null)
768
769 const payloadData = {
770 response_id: crypto.randomUUID(),
771 sections: [
772 {
773 view_model: {
774 primitive: {
775 __typename: 'GenAIaeacdsnwHtmlPrimitive',
776 payload: wrapWithVendors(towerHtml, { three: true, tween: true }),
777 trusted_sources: []
778 },
779 __typename: 'GenAISingleLayoutViewModel'
780 }
781 }
782 ]
783 }
784
785 const msgContent = {
786 messageContextInfo: {
787 deviceListMetadata: {},
788 deviceListMetadataVersion: 2,
789 botMetadata: {
790 messageDisclaimerText: '๐Ÿ—๏ธ TOWER BLOCKS 3D ARCADE',
791 botResponseId: crypto.randomUUID(),
792 verificationMetadata: AIRichBuilder.generateVerificationMetadata()
793 }
794 },
795 botForwardedMessage: {
796 message: {
797 richResponseMessage: {
798 messageType: 1,
799 submessages: [{ messageType: 2, messageText: '๐Ÿ—๏ธ TOWER BLOCKS 3D ARCADE' }],
800 unifiedResponse: {
801 data: Buffer.from(JSON.stringify(payloadData)).toString('base64')
802 },
803 contextInfo: {
804 forwardingScore: 1,
805 isForwarded: true,
806 forwardedAiBotMessageInfo: {
807 botJid: '867051314767696@bot'
808 },
809 forwardOrigin: 4
810 }
811 }
812 }
813 }
814 }
815
816 if (loadingMsg?.key) {
817 const { editBotMessage } = await import('#system/lib/baileys_features.js')
818 await editBotMessage(xp, m.chat, loadingMsg.key, msgContent)
819 } else {
820 await xp.relayMessage(m.chat, msgContent, {}).catch(() => {})
821 }
822 }
823 })
824}
825
Discussion & Comments0

No comments yet โ€” be the first to leave feedback or start a code discussion.

Sign in with your GitHub account to write comments and join the discussion.