ساخت اپلیکیشن پرتاب سکه با جاوا اسکریپت، HTML و CSS | راهنمای کاربردی
در این مطلب قصد داریم یک اپلیکیشن پرتاب سکه با جاوا اسکریپت ، HTML و CSS برای شبیهسازی فرایند پرتاب سکه بسازیم. در این اپلیکیشن به یک دکمه نیاز داریم که وقتی فشرده میشود، برنامه روی یک آرایه از مقادیر «شیر» با مقدار 1 و یا «خط» با مقدار 0 میچرخد و یک خروجی تصادفی در هر تکرار ایجاد میکند. زمانی که این تکرار پایان یابد، یک آرایه از اعداد یک و صفر به دست میآید. اگر تعداد یکها بیشتر باشد، جاوا اسکریپت عبارت «شیر برنده است» را پرینت میکند و در غیر این صورت عبارت «خط برنده است» نمایش مییابد.
این مثال برای درک مبانی ساخت یک وباپلیکیشن با جاوا اسکریپت مناسب است و از این رو در این مقاله این فرایند را با هم مرور خواهیم کرد. البته طراحی اولیه ما چندان جذاب نیست. دلایل این عدم جذابیت به شرح زیر هستند:
- هیچ سوابقی از برندهها وجود ندارد.
- برنامه یکباره روی همه پرتابها میچرخد.
- تعامل بسیار کمی وجود دارد و به جز کلیک کردن دکمه و نمایش پیام چیز دیگری نیست.
- مراسم گرامیداشت برنده بسیار خلاصه است.
اینها مشکلات واقعی هستند. در طراحی اولیه ما یک دکمه را کلیک میکنیم و سپس x عدد از پرتابها به صورت آنی اجرا میشوند و یک برنده معرفی میشود. این طراحی کار میکند، اما جذابیت کمی دارد. ما میتوانیم این فرایند را طوری بازطراحی کنیم که کاربران به نوبت پرتاب داشته باشند. این قابلیت را میتوانیم با جدا کردن کارکرد پرتاب سکه و تکرار پرتاب در دو متد مختلف اجرا کنیم:
1function coinFlip() {
2 function flip(){
3 return Math.floor((Math.random() * 2) + 1)
4 }
5 var result = flip();
6 if (result === 1){
7 document.getElementById("coin").src="images/heads.png";
8 winner = `HEADS`;
9 head_win = heads_wins.push(result);
10 document.getElementById("head_win").innerText = head_win;
11 } else if (result === 2) {
12 document.getElementById("coin").src="images/tails.png";
13 winner = `TAILS`;
14 tail_win = `${tails_wins.push(result)}`;
15 document.getElementById("tail_win").innerText = tail_win;
16 }
17 document.getElementById("winner").innerText = winner;
18}
متد ()coinFlip پرتاب عملی سکه و همچنین تعداد افتادن یک سمت سکه را پرینت میکند. کاربر روی یک تصویر کلیک میکند و یک دستگیره رویداد onclick موجب میشود که تصویر بچرخد. ()Flip خروجی عددی تصادفی را کنترل میکند. اگر نتیجه ()flip برابر با 1 باشد، ()coinFlip عبارت HEADS را پرینت میکند و سمت شیر سکه را نمایش میدهد. اگر مقدار نهایی 2 باشد، ()coinFlip عبارت TAILS را پرینت میکند و سمت خط سکه را نمایش میدهد. خروجیها در دو آرایه ذخیره میَشوند. heads_wins همه خروجیها 1 را ذخیره میکند و آرایه tails_wins خروجیهای 2 را ذخیره میکند. ()coinFlip همچنین طول کنونی هر آرایه را پس از پرتاب سکه مشخص میکند. این مقادیر میتواند در کادر امتیاز مشاهده شوند و پس از هر پرتاب دیده میشوند. سپس یک متد داریم که امتیاز را ردگیری میکند:
1function score(){
2 if (heads_wins.length + tails_wins.length === 9){
3 if (heads_wins.length > tails_wins.length){
4 document.getElementById("coin").src="images/heads.png";
5 final_winner = `The winner is HEADS with ${heads_wins.length} wins!`;
6 print_winner();
7 } else if (tails_wins.length > heads_wins.length) {
8 document.getElementById("coin").src="images/tails.png";
9 final_winner = `The winner is TAILS with ${tails_wins.length} wins!`;
10 print_winner();
11 }
12 }
13}
به این ترتیب بازی پس از تعداد فردی (9) پرتاب متوقف میشود تا مطمئن شویم که حالت تساوی پدید نمیآید. اگر heads_wins.length طولانیتر از tails_wins.length باشد، متد ()score تعداد HEADS را به عنوان برنده ارزیابی میکند. اگر tails_wins.length بزرگتر باشد، در این صورت TAILS برنده خواهد بود. یک متد دیگر نیز وجود دارد که برنده را نشان میدهد و نام آن ()print_winner است.
1function print_winner(){
2 winner = ''
3 document.getElementById("final_winner").innerHTML = final_winner;
4 fallingCoins()
5}
شاید فکر کنید که اپلیکیشن ما هم اینک پایان یافته است. یک برنده داریم، یک پیام برنده بودن نمایش مییابد و همه چیز روان و مناسب است. اما این وباپلیکیشن هنوز جا برای بهتر شدن دارد. به این منظور متد ()fallingCoins را نیز اضافه میکنیم:
1function fallingCoins(){
2 var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3 if (heads_wins.length > tails_wins.length){
4 document.getElementById("damn").innerHTML += "<div id='coin_fall'><img id='falling_coin' src='images/heads.png'/></div>"
5 } else {
6 document.getElementById("damn").innerHTML += "<div id='coin_fall'><img id='falling_coin' src='images/tails.png'/></div>"
7 }
8 // true means clone all childNodes and all event handlers
9 for (var i = 0; i < arr.length; i++) {
10 var rand = Math.floor(Math.random() * 3) + 2;
11 setTimeout(addCoin, (i * rand) * 300);
12 }
13}
یک آرایه از 1 تا 10 ایجاد میکنیم که تعداد سکههایی که میخواهیم تولید کنیم را نمایش میدهد. اگر برنده شیر باشد، سکههای شیر روی صفحه بارش پیدا میکنند. اگر سمت خط برنده باشد، در این صورت سکههای خط روی صفحه بارش پیدا میکنند. زمانی که برنده مشخص شد، یک div به نام coin_fall اضافه میکنیم که دارای تصویر فرزند به نام failling_coin است. این بخش در div به نام damn تزریق میشود. امکان نوشتن این خطوط در html اصلی نیز وجود دارد، اما در این حالت یک سکه خواهیم داشت که در سمت چپ صفحه به صورت مداوم میچرخد. ما میخواهیم این انیمیشن تنها زمانی نمایش یابد که یک طرف برنده میشود.
زمانی که روی یک آرایه میچرخیم، از failling_coin استفاده میکنیم تا یک تابع را به تعداد تصادفی فراخوانی کنیم. تابع ()addCoin جایی است که کار اصلی انجام مییابد.
1function addCoin() {
2 var coin_div = document.getElementById('coin_fall')
3 var rand = Math.floor(Math.random() * 1000) + 1
4 coin_div.style.position = "fixed";
5 coin_div.style.top = '-120px';
6 coin_div.style.left = rand +'px';
7 var clone = coin_div.cloneNode(true);
8 document.body.appendChild(clone);
9}
10@keyframes coin_fall {
11 0% {
12 transform: translate(0px, -20%);
13 } 100% {
14 transform: translate(0px, 1000%);
15 }
16}
17@keyframes infinite-spinning {
18 from {
19 transform: rotateX(0deg);
20 } to {
21 transform: rotateX(720deg);
22 }
23}
شاید فکر کنید که بهتر است coin_fall را در این تابع اضافه کنیم، اما نیازی به افزودن این خط نیست چون در این حالت هر بار که روی 10 عنصر آرایه قبلی میچرخیم یک سکه جدید ایجاد میشود. بنابراین کافی است آن خط را یک بار در ()fallingCoins بنویسیم. اما این عنصر جدیداً اضافه شده را از تابع ()addCoin برداشته و آن مقدار را به صورت coin_div ذخیره میکنیم. زمانی که عناصر اضافه شدند، یک عدد تصادفی دیگر تولید میکنیم. این عدد تصادفی برای تعیین یک نقطه تصادفی در سمت چپ صفحه مورد استفاده قرار میگیرد که بین 1 و 1000 پیکسل قرار دارد.
اکنون استایلبندی را به coin_div اضافه میکنیم که شامل یک پارامتر top است. یک موقعیت ثابت 120px- مناسب است، زیرا 120px از بالای صفحه شروع به سقوط کرده و وارد نما میشوند. سپس موقعیت سمت چپ بین 0 تا 1000 تعیین میشود تا بتواند هر جایی روی صفحه ظاهر شود. پس از اضافه کردن استایلبندی coin_div با مقدار true کلون میشود. به این ترتیب عنصر اصلی و همچنین فرزندان و شنوندهها/انیمیشنهای رویداد نیز کلون میشوند.
از آنجا که نمیتوان دو انیمیشن را روی یک عنصر اعمال کرد، باید یک انیمیشن را روی img و دیگری را روی div به نام coin_fall اعمال کنیم.
1@keyframes coin_fall {
2 0% {
3 transform: translate(0px, -20%);
4 } 100% {
5 transform: translate(0px, 1000%);
6 }
7}
8@keyframes infinite-spinning {
9 from {
10 transform: rotateX(0deg);
11 } to {
12 transform: rotateX(720deg);
13 }
14}
Translate یک تبدیل CSS است که موقعیت عنصر را در پنجره تعویض میکند. از RotateX برای چرخش عنصر در مکان دسترسی به X آن استفاده میکنیم. یک چرخش کامل به معنی 270 درجه چرخش است. برای استایلبندی div و img کاری میکنیم که انیمیشن روی 4 ثانیه تمام شود و به این ترتیب آن قدر کُند حرکت میکند که در عمل از تماشای انیمیشن لذت ببریم.
1#coin_fall{
2 animation: coin_fall 4s infinite;
3 position: fixed;
4}
5#falling_coin {
6 width: 100px;
7 animation: infinite-spinning 4s infinite;
8 margin: 0;
9}
یکی از مشکلاتی که در زمان تلاش برای چرخش سکه مواجه میشویم، زمانی که است کاربر آن را فشار میدهد. مشکل این است که css رویداد onclick را به عنوان یک سلکتور از قبیل onclick تشخیص نمیدهد. به جای آن از active استفاده میکنیم که انیمیشن را تا زمانی که کاربر کلیک را نگه داشته است اجرا میکند.
1input:active{
2 transform: rotateX(1800deg) scale(4);
3}
اما نمیتوانیم انتظار داشته باشیم که کاربر کلیک خود را روی تصویر نگه دارد و منتظر انیمیشن بماند تا پایان یابد و سپس کلیک را رها کند تا خروجی را ببیند. برای حل این مشکل باید چرخش را در تعداد بالا اجرا و گذار را روی یک و نیم ثانیه تنظیم کنیم. به این ترتیب بخش اعظم انیمیشن در کسری از زمان که لازم است تا کلیک انجام یابد اجرا میشود.
1input {
2 margin-left: 42.5%;
3 margin-right: 42.5%;
4 width: 15%;
5 transition: all .5s linear;
6}
به این ترتیب این طور به نظر میرسد که گویی انیمیشن چرخش در زمان onclick رخ میدهد.
اکنون یک وباپلیکیشن داریم که انیمیشن پرتاب سکه را به زیبایی نمایش میدهد. با این حال احتمالاً متوجه شدهاید که در برخی موارد پرتاب سکه کمی با کندی مواجه میشود. در برخی موارد سکهها به صورت روان از بالای صفحه فرود میآیند، اما گاهی نیز ناگهان به بخش دیگر صفحه پرش میکنند. گاهی اوقات به نظر میرسد که سکهها در اندازه 120- پیکسل از بالای صفحه تولید نمیشوند، بلکه در میانه صفحه پدیدار میشوند. برای حل این باگ کنسول مرورگر را در زمان اجرای انیمیشن بررسی میکنیم. به وضوح میتوان دید که در چند محل مختلف coin_div به HTML اضافه شده است:
بنابراین به نظر میرسد که فرایند ایجاد و موقعیتیابی سکهها مشکلی ندارد، بخش فوقانی ثابت است و مقدار پیکسل سمت چپ نیز عددی تصادفی بین 0 و 1000 است. اما میبینیم که یک مقدار در زیر damn اضافه میشود که بقیه کدهای صفحه را بالاتر از div با نام damn مختل میکند. حل کردن این باگ وباپلیکیشن به عنوان یک تمرین به شما واگذار میشود. لطفاً آن را بررسی کرده و راهحلهایی که به ذهنتان میرسد را با ما و دیگر خوانندگان فرادرس در میان بگذارید.