From 3e0fdc2fe738388a5b3dd6917200001e6b88b2f8 Mon Sep 17 00:00:00 2001 From: Taiizor <41683699+Taiizor@users.noreply.github.com> Date: Fri, 3 Feb 2023 15:00:53 +0300 Subject: [PATCH] Coloron Preparation 8 --- src/GamePortal/Pages/Game/GameColoron.razor | 2 +- .../coloron/{script.js => scriptDesktop.js} | 3 - .../wwwroot/js/game/coloron/scriptMobile.js | 788 ++++++++++++++++++ 3 files changed, 789 insertions(+), 4 deletions(-) rename src/GamePortal/wwwroot/js/game/coloron/{script.js => scriptDesktop.js} (99%) create mode 100644 src/GamePortal/wwwroot/js/game/coloron/scriptMobile.js diff --git a/src/GamePortal/Pages/Game/GameColoron.razor b/src/GamePortal/Pages/Game/GameColoron.razor index b2c62e9..a5eca2a 100644 --- a/src/GamePortal/Pages/Game/GameColoron.razor +++ b/src/GamePortal/Pages/Game/GameColoron.razor @@ -29,7 +29,7 @@ { await Task.Delay(DelayValue.Render); - await Include.ScriptProtected("js/game/coloron/script.js", HelperPath.Path); + await Include.ScriptProtected($"js/game/coloron/script{Device.KindTypeString}.js", HelperPath.Path); await Task.Delay(DelayValue.Localization); diff --git a/src/GamePortal/wwwroot/js/game/coloron/script.js b/src/GamePortal/wwwroot/js/game/coloron/scriptDesktop.js similarity index 99% rename from src/GamePortal/wwwroot/js/game/coloron/script.js rename to src/GamePortal/wwwroot/js/game/coloron/scriptDesktop.js index ff0fed7..cf5b870 100644 --- a/src/GamePortal/wwwroot/js/game/coloron/script.js +++ b/src/GamePortal/wwwroot/js/game/coloron/scriptDesktop.js @@ -453,10 +453,8 @@ class Game { ease: Elastic.easeOut.config(1.5, 0.5) }); } else { - // you loose game.stop(); - } } @@ -738,7 +736,6 @@ class Animation { } - var game = new Game(); var animation = new Animation(); var color = new Color(); diff --git a/src/GamePortal/wwwroot/js/game/coloron/scriptMobile.js b/src/GamePortal/wwwroot/js/game/coloron/scriptMobile.js new file mode 100644 index 0000000..7034d3b --- /dev/null +++ b/src/GamePortal/wwwroot/js/game/coloron/scriptMobile.js @@ -0,0 +1,788 @@ +class Game { + + constructor() { + this.score = 0; + this.isRunning = 0; // game is not running + + this.calculateScale(); + + this.timeline = new TimelineMax({ + smoothChildTiming: true + }); + this.time = 1.6; // initial speed + this.colors = ["#FF4571", "#FFD145", "#8260F6"]; // the 3 colors used in the game + this.colorsRGBA = ["rgba(255, 69, 113, 1)", "rgba(255, 69, 113, 1)", "rgba(255, 69, 113, 1)"]; + this.color = this.colors[0]; // the intial color of the ball + this.prevColor = null; // used as a holder to prevent ball colors from repeating + + this.tweettext = null; + + this.point0 = null; + this.point5 = null; + this.point10 = null; + this.point13 = null; + this.point15 = null; + this.point20 = null; + this.point25 = null; + this.point30 = null; + } + + SetLocalization(TweetText, Point0, Point5, Point10, Point13, Point15, Point20, Point25, Point30) { + this.tweettext = TweetText; + + this.point0 = Point0; + this.point5 = Point5; + this.point10 = Point10; + this.point13 = Point13; + this.point15 = Point15; + this.point20 = Point20; + this.point25 = Point25; + this.point30 = Point30; + } + + /** + * The game screen is scalable. I took 1200x800px as the initial scale. + * In order to display the game an many screen sizes properly + * I have to compare the player's sreen size to the initial scale, + * then scale the game using CSS Transform to fit the screen properly + * The function is called in the controller and anywhere where I need + * to recalculate the scale on screen resize or device rotation + */ + calculateScale() { + this.screen = $(window).width(); // screen width + this.screenHeight = $(window).height(); + this.scale = this.screen > this.screenHeight ? this.screenHeight / 800 : this.screen / 1200; + this.stickWidth = 180 * this.scale; + this.steps = this.screen / this.stickWidth; // how many steps (stick width + margin) it takes from one end to another + } + + /** + * Creating as many sticks we need to fill the screen + * from start to end of the screen. The steps property is used for that + */ + generateSticks() { + let numberOfSticks = Math.ceil(this.steps); + for (let i = 0; i <= numberOfSticks; i++) + new Stick(); + } + + generateBall() { + this.balltween = new TimelineMax({ + repeat: -1, + paused: 1 + }); + $('.scene .ball-holder').append('
'); + this.bounce(); + } + + generateTweet() { + let top = $(window).height() / 2 - 150; + let left = $(window).width() / 2 - 300; + let uri = new URL("https://twitter.com/intent/tweet?url=https://github.com/Soferity/GamePortal&text=" + String.format(this.tweettext, this.score) + "&hashtags=soferity,gameportal,coloron&via=ReaLTaiizor") + /*window.open(uri, "TweetWindow", "width=600px,height=300px,top=" + top + ",left=" + left);*/ + Taiizor.Invoke.MethodAsync("GamePortal", "BrowserOpen", uri); + } + + /** + * The greeting when the game begins + */ + intro() { + + TweenMax.killAll(); + + $('.stop-game').css('display', 'none'); + $('.start-game').css('display', 'flex'); + + let introTl = new TimelineMax(); + let ball = new TimelineMax({ + repeat: -1, + delay: 3 + }); + introTl. + fromTo('.start-game .logo-holder', 0.9, { + opacity: 0 + }, { + opacity: 1 + }). + staggerFromTo('.start-game .logo span', 0.5, { + opacity: 0 + }, { + opacity: 1 + }, 0.08). + staggerFromTo('.start-game .bar', 1.6, { + y: '+100%' + }, { + y: '0%', + ease: Elastic.easeOut.config(1, 0.3) + }, 0.08). + staggerFromTo('.start-game .ball-demo', 1, { + scale: 0 + }, { + scale: 1, + ease: Elastic.easeOut.config(1, 0.3) + }, 0.8, 2); + + + ball.fromTo('.start-game .section-1 .ball-demo', 0.5, { + y: "0px" + }, { + y: "100px", + scaleY: 1.1, + transformOrigin: "bottom", + ease: Power2.easeIn + }). + to('.start-game .section-1 .ball-demo', 0.5, { + y: "0px", + scaleY: 1, + transformOrigin: "bottom", + ease: Power2.easeOut, + onStart: () => { + while (this.prevColor == this.color) { + this.color = new Color().getRandomColor(); + } + this.prevColor = this.color; + TweenMax.to('.start-game .section-1 .ball-demo', 0.5, { + backgroundColor: this.color + }); + } + }); + + } + + /** + * Display score + */ + showResult() { + let score = this.score; + $('.stop-game').css('display', 'flex'); + $('.stop-game .final-score').text(score + '!'); + $('.stop-game .result').text(this.showGrade(score)); + $('.nominee').show(); + + let resultTimeline = new TimelineMax(); + resultTimeline. + fromTo('.stop-game .score-container', 0.7, { + opacity: 0, + scale: 0.3 + }, { + opacity: 1, + scale: 1, + ease: Elastic.easeOut.config(1.25, 0.5) + }). + fromTo('.stop-game .final-score', 2, { + scale: 0.5 + }, { + scale: 1, + ease: Elastic.easeOut.config(2, 0.5) + }, 0). + fromTo('.stop-game .result', 1, { + scale: 0.5 + }, { + scale: 1, + ease: Elastic.easeOut.config(1.5, 0.5) + }, 0.3); + + + } + + /** + * Takes players score and generates the cheering copy + * @param {int} score + * @return {string} grade + */ + showGrade(score) { + if (score > 30) return this.point30; + else if (score > 25) return this.point25; + else if (score > 20) return this.point20; + else if (score > 15) return this.point15; + else if (score > 13) return this.point13; + else if (score > 10) return this.point10; + else if (score > 5) return this.point5; + else + return this.point0; + } + + start() { + + this.stop(); // stop the game + + Taiizor.Storage.Local.Set('coloron', 'play'); + + $('.start-game, .stop-game').css('display', 'none'); // hide all the popups + $('.nominee').hide(); + + new Game(); + this.score = 0; // reset + + this.isRunning = 1; + + // Clean up the stick and ball holders + // and generate new ones + $('#sticks, .scene .ball-holder').html(''); + $('#score').text(this.score); + this.generateSticks(); + this.generateBall(); + + // disables scene animations for Phones + if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent)) { + Animation.sceneAnimation(); + } + this.moveToStart(); + this.moveScene(); + + // reset timescale to normal as the game speeds up + this.timeline.timeScale(1); + this.balltween.timeScale(1); + } + + stop() { + + this.isRunning = 0; + + $('.start-game, .stop-game').css('display', 'none'); + $('#sticks, .scene .ball-holder, #score').html(''); + TweenMax.killAll(); + + this.showResult(); + } + + scaleScreen() { + + TweenMax.killAll(); // prevent multiple calls on resize + + let height = $(window).height(); + let width = $(window).width(); + + this.calculateScale(); + + $('.container'). + css('transform', 'scale(' + this.scale + ')'). + css('height', height / this.scale). + css('width', width / this.scale). + css('transformOrigin', 'left top'); + + $('#sticks').width(this.screen / this.scale + 3 * this.stickWidth / this.scale); + + } + + /** + * Calls the above function + * If the game is running it stops and shows the score + * If the game has stops it takes player to the main menu + */ + scaleScreenAndRun() { + + this.scaleScreen(); + + if (this.isRunning) { + this.stop(); + } else { + this.intro(); + } + + } + + /** + * This is the initial animation + * where the sticks come to the starting position + * and the ball appears and falls down + */ + moveToStart() { + + let tip = new TimelineMax({ + delay: 2 + }); + + tip. + fromTo('.learn-to-play', 1, { + scale: 0 + }, { + scale: 1, + opacity: 1, + ease: Elastic.easeOut.config(1.25, 0.5) + }). + to('.learn-to-play', 1, { + scale: 0, + opacity: 0, + ease: Elastic.easeOut.config(1.25, 0.5) + }, 3); + + TweenMax.fromTo('#ball', this.time, { + scale: 0 + }, + + { + scale: 1, + delay: this.time * (this.steps - 3 - 1.5), + onComplete: () => { + this.balltween.play(); + } + }); + + + this.timeline.add( + TweenMax.fromTo('#sticks', this.time * this.steps, { + x: this.screen / this.scale + }, { + x: 0, + ease: Power0.easeNone + })); + + } + + /** + * The animation that moves sticks + */ + moveScene() { + + this.timeline.add( + TweenMax.to('#sticks', this.time, { + x: '-=180px', + ease: Power0.easeNone, + repeat: -1, + onRepeat: () => { + this.rearrange(); + } + })); + + + } + + /** + * removes the first stick and adds one the the end + * this gives the sticks an infinite movement + */ + rearrange() { + + let scale = this.speedUp(); + + this.timeline.timeScale(scale); + this.balltween.timeScale(scale); + + $('#sticks .stick').first().remove(); + new Stick(); + + } + + /** + * The game speeds up based on score + * The GSAP timeScale() function is called on the timeline to speed up the game + * This calculates how much shall the game speed up + */ + speedUp() { + if (this.score > 30) { + return 1.8; + } + if (this.score > 20) { + return 1.7; + } + if (this.score > 15) { + return 1.5; + } else + if (this.score > 12) { + return 1.4; + } else + if (this.score > 10) { + return 1.3; + } else + if (this.score > 8) { + return 1.2; + } else + if (this.score > 5) { + return 1.1; + } + return 1; + } + + /** + * Ball bouncing animation + * It checks if the ball and stick colors match + * And changes the ball color + */ + bounce() { + + this.balltween. + to('#ball', this.time / 2, { + y: '+=250px', + scaleY: 0.7, + transformOrigin: "bottom", + ease: Power2.easeIn, + onComplete: () => { + this.checkColor(); + } + }). + to('#ball', this.time / 2, { + y: '-=250px', + scaleY: 1.1, + transformOrigin: "bottom", + ease: Power2.easeOut, + onStart: () => { + while (this.prevColor == this.color) { + this.color = new Color().getRandomColor(); + } + this.prevColor = this.color; + TweenMax.to('#ball', 0.5, { + backgroundColor: this.color + }); + $('#ball').removeClass('red'). + removeClass('yellow'). + removeClass('purple'). + addClass(new Color().colorcodeToName(this.color)); + } + }); + + } + + checkColor() { + + let ballPos = $('#ball').offset().left + $('#ball').width() / 2; + let stickWidth = $('.stick').width(); + let score = this.score; + + $('#sticks .stick').each(function () { + if ($(this).offset().left < ballPos && $(this).offset().left > ballPos - stickWidth) { + + if (Color.getColorFromClass($(this)) == Color.getColorFromClass('#ball')) { + // if matches increase the score + score++; + $('#score').text(score); + TweenMax.fromTo('#score', 0.5, { + scale: 1.5 + }, { + scale: 1, + ease: Elastic.easeOut.config(1.5, 0.5) + }); + } else { + // you loose + game.stop(); + } + + } + }); + + this.score = score; + } +} + + + +class Stick { + + constructor() { + this.stick = this.addStick(); + } + + addStick() { + this.stick = $('#sticks').append(''); + return this.stick; + } +} + + + +class Color { + + constructor() { + this.colors = ["#FF4571", "#FFD145", "#8260F6"]; + this.effects = ["bubble", "triangle", "block"]; + this.prevEffect = null; + } + + getRandomColor() { + let colorIndex = Math.random() * 3; + let color = this.colors[Math.floor(colorIndex)]; + return color; + } + + colorcodeToName(color) { + let colors = ["#FF4571", "#FFD145", "#8260F6"]; + let names = ["red", "yellow", "purple"]; + let index = colors.indexOf(color); + if (index == -1) return false; + return names[index]; + } + + /** + * Changes the color of an element + * As we as adds verbal name of the color + */ + changeColor(el) { + let index = el.data("index"); + if (index === undefined) { + index = 0; + } else { + index += 1; + } + if (index == 3) index = 0; + el. + css('background-color', this.colors[index]). + data('index', index); + + el.removeClass('red'). + removeClass('yellow'). + removeClass('purple'). + addClass(this.colorcodeToName(this.colors[index])); + + if (el.hasClass('inactive')) { + this.setEffect(el); + el.addClass('no-effect'); + } + + el.removeClass('inactive'); + } + + getRandomEffect() { + let effectIndex = null; + + effectIndex = Math.floor(Math.random() * 3); + while (effectIndex == this.prevEffect) { + effectIndex = Math.floor(Math.random() * 3); + } + + this.prevEffect = effectIndex; + return this.effects[effectIndex]; + } + + /** + * Adds the effect specific particles to the stick + */ + setEffect(el) { + let effect = this.getRandomEffect(); + el.addClass(effect + '-stick'); + for (let i = 1; i <= 14; i++) { + if (effect == 'block') { + el.append(`