مینی پروژه فصل - سوالات ریاضی

Chapter Mini Project - Math Questions

14 آبان 1399
Vue.JS 2: مینی پروژه ی فصل، سوالات ریاضی - قسمت 68

در جلسه قبل با انواع و اقسام انیمیشن ها در Vue آشنا شدیم و حالا نوبت به ساخت یک مینی پروژه برای تمرین تمام موارد یاد گرفته شده است. این پروژه یک پروژه پرسش و پاسخ ریاضی (فقط عملیات های جمع و تفریق) است که از کاربر می خواهد اعداد مختلفی را جمع یا تفریق کند و اگر پاسخ کاربر درست بود با یک انیمیشن flip (چرخیدن صفحه) به کاربر اعلام کنیم پاسخ او درست بوده است. من باز هم مثل همیشه با یک پروژه جدید و خالی شروع می کنم. فایل این پروژه را بار ها در فصل های قبل برایتان قرار داده ام بنابراین می توانید برگشته و آن را دانلود کنید. در واقع باید یک پروژه جدید Vue ایجاد کنید که فقط فایل هایی ابتدایی را داشته باشد سپس در App.vue کدهای زیر را اضافه می کنیم:

<template>
    <div class="container">
        <div class="row">
            <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
                <h1>Animations</h1>

            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {

            }
        }
    }
</script>

<style>

</style>

در واقع کد بالا چیزی به جز چند کلاس ساده بوت استرپ نیست. یادتان نرود که بوت استرپ را در فایل index.html قرار بدهید:

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"></head>

این پروژه خالی و ابتدایی ما است که در تمام پروژه های خود از آن استفاده می کنیم. در مرحله بعد کمی HTML می نویسیم که ساختار اولیه پروژه را پیاده سازی کرده باشیم:

<template>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
        <h1 class="text-center">The Super Quiz</h1>
      </div>
    </div>
    <hr />
    <div class="row">
      <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
// محل کامپوننت ما //
      </div>
    </div>
  </div>
</template>

کدهای بالا چیزی به جز چند تگ div و h1 و hr نیستند و فقط اسکلت طرح ما را مشخص می کنند. همانطور که در این کدها مشخص است، قسمتی را خالی باقی گذاشته ام که باید در آن کامپوننت پویای خود را قرار دهیم. مشکل اینجاست که هنوز کامپوننتی را نداریم که بخواهیم در این قسمت قرار دهیم بنابراین یک پوشه به نام components در src می سازیم که درون خود دو فایل به نام های Answer.vue و Question.vue را داشته باشد. من می خواهم بین این دو کامپوننت به صورت پویا بچرخم بنابراین باید ابتدا این کامپوننت ها را وارد App.vue کنیم (هنوز فایل ها خالی هستند):

<script>
import Question from "./components/Question.vue";
import Answer from "./components/Answer.vue";

export default {
  data() {
    return {};
  }
};
</script>

سپس آن ها را به عنوان کامپوننت محلی ثبت می کنیم:

<script>
import Question from "./components/Question.vue";
import Answer from "./components/Answer.vue";

export default {
  data() {
    return {};
  },
  components: {
    appQuestion: Question,
    appAnswer: Answer
  }
};
</script>

در مرحله بعد باید خصوصیتی به نام mode را داشته باشیم که مشخص کند در ابتدا کدام یک از این کامپوننت ها نمایش داده شوند. قطعا در ابتدا باید Question را نمایش بدهیم:

  data() {
    return {
      mode: "app-question"
    };
  },

در نهایت می توانیم کامپوننت را نیز بنویسیم:

<div class="row">
    <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
    <component :is="mode"></component>
    </div>
</div>

تعیین کامپوننت اصلی برای کامپوننت های پویا با is است (همانطور که در کد بالا مشاهده می کنید).

حالا باید کدهای درون کامپوننت Question.vue را بنویسیم. کدهای درون این کامپوننت طولانی هستند چرا که باید چهار گزینه را تولید کنیم که فقط یکی از آن ها صحیح است اما چیز پیچیده ای ندارند. به عبارت دیگر بیشتر ممکن است خسته کننده باشند تا اینکه بخواهند شما را گیج کنند. از طرفی بحث این فصل روی انیمیشن ها است و من نمی خواهم چند جلسه را به نوشتن منطق جاوا اسکریپتی و محاسباتی اختصاص بدهم بنابراین در این جلسه فقط نگاهی کلی به Question.vue خواهیم داشت. من تمام کدهای فایل Question.vue را برایتان قرار می دهم و سپس توضیحات خود را به آن اضافه می کنم:

 <template>
  <div class="panel panel-default">
    <div class="panel-heading">
      <h3 class="panel-title text-center">{{ question }}</h3>
    </div>
    <div class="panel-body">
      <div class="col-xs-12 col-sm-6 text-center">
        <button
          class="btn btn-primary btn-lg"
          style="margin: 10px"
          @click="onAnswer(btnData[0].correct)"
        >{{ btnData[0].answer }}</button>
      </div>
      <div class="col-xs-12 col-sm-6 text-center">
        <button
          class="btn btn-primary btn-lg"
          style="margin: 10px"
          @click="onAnswer(btnData[1].correct)"
        >{{ btnData[1].answer }}</button>
      </div>
      <div class="col-xs-12 col-sm-6 text-center">
        <button
          class="btn btn-primary btn-lg"
          style="margin: 10px"
          @click="onAnswer(btnData[2].correct)"
        >{{ btnData[2].answer }}</button>
      </div>
      <div class="col-xs-12 col-sm-6 text-center">
        <button
          class="btn btn-primary btn-lg"
          style="margin: 10px"
          @click="onAnswer(btnData[3].correct)"
        >{{ btnData[3].answer }}</button>
      </div>
    </div>
  </div>
</template>
<style>
</style>
<script>
const MODE_ADDITION = 1;
const MODE_SUBTRACTION = 2;
export default {
  data() {
    return {
      question: "Oops, an error ocurred :/",
      btnData: [
        { correct: true, answer: 0 },
        { correct: false, answer: 0 },
        { correct: false, answer: 0 },
        { correct: false, answer: 0 }
      ]
    };
  },
  methods: {
    generateQuestion() {
      const firstNumber = this.generateRandomNumber(1, 100);
      const secondNumber = this.generateRandomNumber(1, 100);
      const modeNumber = this.generateRandomNumber(1, 2);

      let correctAnswer = 0;

      switch (modeNumber) {
        case MODE_ADDITION:
          correctAnswer = firstNumber + secondNumber;
          this.question = `What's ${firstNumber} + ${secondNumber}?`;
          break;
        case MODE_SUBTRACTION:
          correctAnswer = firstNumber - secondNumber;
          this.question = `What's ${firstNumber} - ${secondNumber}?`;
          break;
        default:
          correctAnswer = 0;
          this.question = "Oops, an Error occurred :/";
      }

      this.btnData[0].answer = this.generateRandomNumber(
        correctAnswer - 10,
        correctAnswer + 10,
        correctAnswer
      );
      this.btnData[0].correct = false;
      this.btnData[1].answer = this.generateRandomNumber(
        correctAnswer - 10,
        correctAnswer + 10,
        correctAnswer
      );
      this.btnData[1].correct = false;
      this.btnData[2].answer = this.generateRandomNumber(
        correctAnswer - 10,
        correctAnswer + 10,
        correctAnswer
      );
      this.btnData[2].correct = false;
      this.btnData[3].answer = this.generateRandomNumber(
        correctAnswer - 10,
        correctAnswer + 10,
        correctAnswer
      );
      this.btnData[3].correct = false;

      const correctButton = this.generateRandomNumber(0, 3);
      this.btnData[correctButton].correct = true;
      this.btnData[correctButton].answer = correctAnswer;
    },
    generateRandomNumber(min, max, except) {
      const rndNumber = Math.round(Math.random() * (max - min)) + min;
      console.log(min, max, rndNumber);
      if (rndNumber == except) {
        return this.generateRandomNumber(min, max, except);
      }
      return rndNumber;
    },
    onAnswer(isCorrect) {
      this.$emit("answered", isCorrect);
    }
  },
  created() {
    this.generateQuestion();
  }
};
</script>

ابتدا با data شروع می کنیم. اگر به Data نگاه کنید، دو خصوصیت question و btnData می بینید. question در واقع سوال ما را تعیین می کند که در حالت پیش فرض روی Oops, an error occurred قرار گرفته است (یعنی «متاسفانه اشتباهی رخ داده است»). btnData نیز آرایه ای از اشیاء است که در واقع همان دکمه ها یا چهار گزینه موجود برای پاسخ دادن هستند و هر کدام دو خصوصیت زیر را دارند:

  • correct که مشخص کننده درست یا غلط بودن پاسخ (دکمه) است.
  • answer که مشخص کننده پاسخ اصلی است.

من مقادیر اولیه ای برای آن ها تعیین کرده ام که سلیقه ای است و شما می توانید ساختار خودتان را برای آن بسازید. در مرحله بعد متدی به نام generateRandomNumber (به معنی «یک عدد تصادفی تولید کن») را داریم که طبیعتا یک عدد تصادفی را تولید می کند و سه آرگومان می گیرد: min یا عدد حداقل، max یا عدد حداکثر و except یا عددی که نباید تولید شود. بعضی از اعداد نباید توسط این متد تولید شوند. آیا می دانید کدام اعداد؟ بله، جواب! اگر پاسخ ما توسط این متد به صورت تصادفی تولید شود، آنگاه دو دکمه صحیح خواهیم داشت (یکی جواب مد نظر ما و دیگری که به صورت تصادفی دقیقا برابر پاسخ ما تولید شده است). برای این کار گفته ایم که اگر rndNumber برابر except شد، دوباره generateRandomNumber را درون خودش صدا زده ایم تا یک عدد دیگر به ما بدهد (در جاوا اسکریپت می توانیم این کار را انجام بدهیم).

در مرحله بعد متدی به نام generateQuestion (به معنی «سوال بساز») را داریم که بسیار طولانی است. در این متد در ابتدا firstNumber (عدد اول برای عملیات جمع یا تفریق) و سپس secondNumber (عدد دوم برای عملیات جمع یا تفریق) و در آخر modeNumber (یا عدد 1 یا 2 را انتخاب می کند) را تعریف کرده ایم. اگر به بالای کدها نگاه کرده باشید دو خط زیر را می بینید:

const MODE_ADDITION = 1;
const MODE_SUBTRACTION = 2;

MODE_ADDITION یعنی «حالت جمع» و MODE_SUBTRACTION یعنی «حالت تفریق» چرا که برنامه ما در نهایت یکی از این دو عملیات را از کاربر می خواهد. حالا متوجه می شویم که متغیر modeNumber چه متغیری است. در واقع modeNumber به صورت تصادفی انتخاب می کند که سوال پرسیده شده از کاربر از نوع جمع یا تفریق باشد.

در ادامه متد generateQuestion خصوصیت correctAnswer را تعریف کرده ایم که قرار است پاسخ صحیح را در خود نگه دارد و به صورت پیش فرض 0 است. سپس یک دستور switch داریم که می گوید اگر حالت برنامه تفریق بود برای ساخت سوال اعداد ساخته شده (firstNumber و secondNumber) را از هم کم کن و در صورتی که جمع بود آن ها را با هم جمع کن و در غیر این صورت correctAnswer را روی صفر بگذار و پیام Oops, an Error occurred (خطایی پیش آمده است) را نمایش بده چرا که سوال ما یا باید تفریق یا جمع باشد و در غیر این صورت حتما خطا است. در همین مرحله است که می توانیم پاسخ صحیح را محاسبه کنیم (در کد مشخص است).

در مرحله بعد از متد generateQuestion تمام دکمه ها را پر از پاسخ های غلط می کنیم تا تمام آن ها در ابتدا غلط باشند. مثلا برای دکمه اول گفته ایم:

this.btnData[0].answer = this.generateRandomNumber(
correctAnswer - 10,
correctAnswer + 10,
correctAnswer
);
this.btnData[0].correct = false;

یعنی با generateRandomNumber عددی بین دو مورد زیر ایجاد کن:

  • پاسخ صحیح منهای 10
  • پاسخ صحیح به علاوه 10

برای پارامتر سوم که except بود نیز می گوییم هیچ وقت پاسخ صحیح (correctAnswer) را تولید نکن. دلیل آن را هم گفتم که نباید دو پاسخ صحیح برای سوالات داشته باشیم. در مرحله بعد ثابتی به نام correctButton (به معنی «دکمه صحیح») را داریم که عددی بین 0 تا 3 را می گیرد و این عدد قرار است ایندکس یکی از دکمه ها باشد (ایندکس از صفر شروع می شود بنابراین 0 تا 3 یعنی چهار دکمه مجزا). سپس بر اساس این ایندکس دکمه مورد نظر را پیدا کرده و مقدار آن را روی صحیح می گذاریم:

this.btnData[correctButton].correct = true;
this.btnData[correctButton].answer = correctAnswer;

سپس در lifecycle ای به نام created متد generateQuestion را صدا می زنیم تا هر بار که این کامپوننت ساخته شد، یک سوال تولید شود (یادتان باشد که این یک متد نیست، بلکه یک lifecycle است):

  created() {
    this.generateQuestion();
  }

حالا اگر به template نگاه کنید چنین چیزی را می بینید:

    <div class="panel-heading">
      <h3 class="panel-title text-center">{{ question }}</h3>

یعنی سوال در همان ابتدای کار و در بالای صفحه نمایش داده می شود. سپس دکمه های ما ساختار زیر را دارند:

<div class="col-xs-12 col-sm-6 text-center">
<button
    class="btn btn-primary btn-lg"
    style="margin: 10px"
    @click="onAnswer(btnData[0].correct)"
>{{ btnData[0].answer }}</button>
</div>

این یکی از دکمه های ما است که درون خود پاسخ های تولید شده را نشان می دهد (هر دکمه یک پاسخ). همانطور که می بینید قسمت مهم، متد onAnswer است که هنگام کلیک شدن، اجرا خواهد شد. توجه کنید که با کلیک روی این دکمه، مقدار این دکمه به متد onAnswer پاس داده می شود. این متد را می توانیم در قسمت Script پیدا کنیم:

onAnswer(isCorrect) {
    this.$emit("answered", isCorrect);
}

یعنی این متد عبارت answered را به همراه مقدار دکمه کلیک شده emit می کنیم. این تمام کاری است که در کامپوننت Question.vue انجام می دهیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش رایگان Vue js از صفر تا صد توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.