קורס Front-End Web Development שיעור תרגיל סיכום: דף עזרה למשחק תפוס תאדום

אתה משתמש ב divs כמו מערך - באחת השורות פונה לאינדקס מסוים ממנו עם סוגריים מרובעים. אם התכוונת לקחת רק div יחיד הפקודה שמתיחסת אליו כמו מערך צריכה להשתנות

צודק סליחה עכשיו עובד

שלום,
מצרפת את התרגיל שלי, קצת שיכללתי אותו מעבר לדרישות. אשמח להערות.
כמו כן יש לי כמה שאלות:

1.) ישנו באג שכאשר נמצאים בעמוד של העזרה ועושים ריענון אז ה-hash בכתובת נשאר ‘help’ למרות שהריענון מחזיר אותנו למצב ראשוני שבו העמוד הוא ‘game’ ולכן כאשר לוחצים שוב על הלינק שאמור להעביר אותי לעזרה הוא לא עובר כיון שלא נוצר אירוע של שינוי ב-hash.

2.) כאשר נמצאים במסך עזרה ולוחצים על לינק חזרה למשחק, הוספתי שימוש ב-promise שמציג לי הודעת ברוכים הבאים למשך 2 שניות ואז מחזיר אותי למשחק באותו מצב שהיה קודם.

היתה לי בעיה שה-this של האוביקט promise הוא window ולכן בתוך ה-then כאשר כתבתי this הוא לא הכיר את האוביקט כ-application ולא זיהה בו את השדה הרלוונטי.
פתרתי את הבעיה עם משתנה גלובלי שמקבל את ה-this ושולח אותו ל-resolve.
יש פתרון יותר טוב לדבר?
תוכל לראות את הקוד במחלקה Application:
הפונקציה: showPage שמשתמשת ב-promise שמוחזר מהפונקציה welcomeGame.

3.) הרעיון של הפתרון שלי הוא לעשות בתחילת המשחק new לכל אחד מהאוביקטים ולשמור אותם במערך -
כך שבכל שינוי hash אני משנה את המצביע שהוא currentPage לעמוד הנוכחי.
ואז במחלקה gamePage הפונקציה leave שומרת את ה-innerHTML הנוכחי באוביקט ובפונקציה enter מזריקים את ה-innerHtml השמור באוביקט מהיציאה הקודמת.
למשתנים של המשחק עשיתי שם אתחול רק בפעם הראשונה.

השאלה שלי היא: חשבתי גם את האירועים וה-QuerySelectors לעשות רק בפעם הראשונה אך ראיתי שאירועים לא נשמרים. מדוע? האם יש קשר לכך שאת המסך הפנימי של המשחק (הריבועים) - אני יוצרת דרך הקוד בהתאם לבחירת המשתמש?.
תודה רבה מראש.

//////HTML page//////

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>JS Bin</title>
    <link rel="stylesheet" href="tfosGame.css">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">    
</head>
<body>
    <span class="main">
    </span>


<script type="template" id="game">
        <div class="alert alert-success">
            <strong dir="rtl">כל הכבוד!!!</strong> 
        </div>
        
        <div class="box">
            <h1>תפוס ת'אדום</h1>
            <p class="linkForHelp">
                <a href="#help" class="linkForHelp"2>לעזרה</a>
            </p>
        </div>
        
        <div class="game">
                <div class="grid">
                </div>
        
                <div class="sidebar">
                    
                    <h2>הגדרות המשחק</h2>
                    
                    <div class="dropdown">
                        <button dir="rtl" class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> בחר רמה 
                        </button>
                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                                <a class="dropdown-item" href="#">2X2</a>
                                <a class="dropdown-item" href="#">3X3</a>
                                <a class="dropdown-item" href="#">4X4</a>
                        </div> 
                    </div>
                    
                    <div dir="rtl" class="score">  ניקוד:<num>0</num></div>
                    
                    <div class="color" dir="rtl">
                        בחר צבע: 
                        <input type="color" id="bgcolor" value="#9abde2"/>
                    </div>
                    
                    
                    <div class="out">
                        <img src="b.JPG">
                    </div>
                    
                </div>
        </div>
</script>

<script type="template" id="help">
            <h1 class="titleHelp">Help </h1>
            <h4>
                <p dir="rtl">
                   ברוכים הבאים למשחק ת'פוס ת'אדום!!
מטרת המשחק היא לצבור כמה שיותר נקודות. 
וכיצד צוברים נקודות?
לוח המשחק מחולק למספר ריבועים לפי בחירתך.
עליך ללחוץ על הריבוע האדום אשר משנה את מקומו בכל שניה.
כל לחיצה על הריבוע האדום תזכה אותך ב-5 נקודות וכל שניה ללא לחיצה תגרע ממך 2 נקודות.
לעומת זאת כל לחיצה על ריבוע שאינו אדום תגרע ממך 5 נקודות .
(צבע הריבועים האחרים תלוי בבחירתך)
כאשר תגיע ל--50 נקודות תופיע תמונה כצ'ופר :)
בהנאה!
                </p>
                <p>
                    <a href="#game">Return to my game</a>
                </p>
            </h4>
            
</script>
    
<script type="template" id="welcome">
<div class="welcome">
    <p>
        Welcome back to your game!!!
    </p>
</div>
</script>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

    <script src="Page.js".js></script>
    <script src="GamePage.js"></script>
    <script src="HelpPage.js"></script>
    <script src="Application.js"></script>
    <script src="Main.js"></script>
</body>
</html>

////////////CSS//////////////

body,html {width: 100%; height: 95%;}

.box {
    display: flex;
    background-color: cadetblue;
}

h1 {
    flex: 5;
    text-align: center;
}

.game {
    display: flex;
    height: 95%;
    width: 99%;
    margin: 10px;
}

.sidebar {
    flex: 1;
    background-color: #ecccdb;
    display: flex;
    flex-direction: column;
    margin: 10px;

}

.grid {
    flex: 3;
    box-sizing: border-box;
    display: grid;
}

.squre {
    border: solid;
    margin: 10px;
}

h2 {
    box-sizing: border-box;
    padding-top: 40px;
    text-align: center;
    flex: 1;
}

.dropdown {
    text-align: center;
    flex: 1;
}

.score {
    text-align: center;
    font-size: 30px;
    flex: 1;
}

.out {
    flex: 2;
    display: grid;
    grid-template-columns: repeat(4,1fr);
}

img {
    padding-bottom: 25px;
    grid-column: 2/span 2;
    width: 250px;
    display: none;
}

.alert-success {
    text-align: center;
    font-size: 70px;
    height: 150px;
    display: none;
}

.display-yes {
    display: block;
}

.color {
    text-align: center;
    font-size: 30px;
    flex: 1;
}

.titleHelp {
    padding-top: 30px;
    color: red;
    font-size: 70px;
    text-align: center;
}

h4 p {
    padding: 30px;
    font-size: 50px;
    text-align: center;
}

.linkForHelp {
    padding-top: 6px;
    margin: 5px;
    color: black;
    flex: 1;
}

.linkForHelp2 {
    color: black;
}

.welcome {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    width: 100%;
}

.welcome p {
    font-size: 100px;
    color: orange;
}

//////////////Main////////////////

let main = document.querySelector('.main');
AllGame = new Application(main);

///////////////Page////////////

function Page(templateId) {
    this.templateId = templateId;
    this.main = main;
}

Page.prototype.injectTemplte = function(el, templateId) {
    el.innerHTML = document.querySelector(`#${templateId}`).innerHTML;
}

Page.prototype.enter = function() {
    
}

Page.prototype.leave = function() {
    
}

////////////////////////GamePage////////////////////////

function GamePage(main) {
    Page.call(this,'game',main);
    this.constructor = 'GamePage';
    this.myInnerHTML = '';
}

GamePage.prototype = Object.create(Page.prototype);

GamePage.prototype.enter = function() {
    if(this.myInnerHTML) {
        this.main.innerHTML = this.myInnerHTML;
    }
    else {
        this.size = 0;
        this.score = 0;
        this.flag_time = 0;
        this.flag_alert = 0;
        this.countTime = 0;
        this.colorChange = '#9abde2';
        this.root = document.createDocumentFragment();
        this.squre = 0;
        this.num = 0;
    }
        this.buttn_drop = document.querySelector('.dropdown');
        this.grid = document.querySelector('.grid');
        this.image_score = document.querySelector('img');
        this.alert = document.querySelector('.alert-success');
        this.time = document.querySelector('.time');
        this.colorElement = document.querySelector('#bgcolor');
        this.colorElement.addEventListener('change',this.colorChangeF.bind(this));
        this.colorElement.value=this.colorChange;
        this.buttn_drop.addEventListener('click',this.chooseSize.bind(this));
        this.grid.addEventListener('click',this.game.bind(this));  
        this.timer();
}

GamePage.prototype.leave = function() {
     this.myInnerHTML = this.main.innerHTML;
     this.stopTimer();
}

GamePage.prototype.stopTimer = function() {
    clearInterval(this.toCancelTimer);
}

GamePage.prototype.chooseSize = function() {
    if(event.target.tagName!=='BUTTON') {
        this.grid.innerHTML='';
        document.querySelector('num').textContent =0;
        this.score = 0;
        this.image_score.style.display='none';
        this.flag_alert=0;
        this.countTime=0;
        if(event.target.textContent=='2X2') {
            this.grid.style.gridTemplateColumns="repeate(2,1fr)";
            this.grid.style.gridTemplateRows="repeate(2,1fr)";
            this.size=2;
        }
        
        if(event.target.textContent=='3X3') {
            this.grid.style.gridTemplateColumns="repeate(3,1fr)";
            this.grid.style.gridTemplateRows="repeate(3,1fr)";
            this.size=3;
        }
        if(event.target.textContent=='4X4') {
            this.grid.style.gridTemplateColumns="repeate(4,1fr)";
            this.grid.style.gridTemplateRows="repeate(4,1fr)";
            this.size=4;
        }
        
  for(let i=1;i<=this.size;i++) {
                for(let j=1;j<=this.size;j++) {
                    this.squre = document.createElement('div');
                    this.squre.classList.add('squre');
                    this.squre.style.gridRow = i + "/span 1";
                    this.squre.style.gridColumn = j + "/span 1";
                    if(this.colorChange) { 
                        this.squre.style.backgroundColor = this.colorChange;
                    }
                    this.root.appendChild(this.squre);
                }
            }
    
    this.grid.appendChild(this.root);
    this.mix();
}
}

GamePage.prototype.mix = function () {
    if(this.size) {
        let tmp = 0;
        do {tmp = Math.floor(Math.random()*this.size*this.size);} while(tmp == this.num);
        this.num = tmp;
        
        for (let i=0;i<this.grid.childNodes.length;i++) {
            if(this.grid.childNodes[i].style.backgroundColor=="rgb(255, 0, 0)") {
                this.grid.childNodes[i].style.backgroundColor = this.colorChange;
            }
        }
        
        this.grid.childNodes[this.num].style.backgroundColor="rgb(255, 0, 0)";

    }
}

GamePage.prototype.game = function(event) {
    if(event.target.classList.contains('squre')) {
        this.flag_time = 1;
        if (event.target.style.backgroundColor=="rgb(255, 0, 0)") {
            this.score +=5;
        }
        else {            
            this.score-=5;
        }
        if (this.score>=50) {
            this.image_score.style.display='inline-block';
            if(!this.flag_alert) {
                this.alert.classList.add('display-yes');
                this.flag_alert = 1;
            }
        }
        
        document.querySelector('num').textContent=this.score;
        this.mix();
    }
}

GamePage.prototype.count = function () {
    if(this.squre) {
          if(!this.flag_time) {
              this.score-=2;
              document.querySelector('num').textContent = this.score;
              this.mix();
          }
          else {
            this.flag_time = 0;
          }
    
            if(this.alert.classList.contains('display-yes')) {
                this.countTime++;
            if (this.countTime==2) {
                this.alert.classList.remove('display-yes');
            }
        }
    }
}

GamePage.prototype.timer = function() {
    this.toCancelTimer = setInterval(this.count.bind(this), 1000);
}

GamePage.prototype.changeNotRedSquers = function () {
    let index_red = 0;
    if(this.squre) {
        for (let i=0;i<this.grid.childNodes.length;i++) {
            if(this.grid.childNodes[i].style.backgroundColor!=="rgb(255, 0, 0)") {
                this.grid.childNodes[i].style.backgroundColor = this.colorChange;
            }
        }
    }
}

GamePage.prototype.colorChangeF = function() {
      if(this.colorElement.value!='#ff0000') {
        this.colorChange = this.colorElement.value;
    }
    this.changeNotRedSquers();
}

/////////////////////////////HelpPage////////////////////////////////

function HelpPage(main) {
    Page.call(this,'help',main);
    this.constructor = 'HelpPage';
}

HelpPage.prototype = Object.create(Page.prototype);

HelpPage.prototype.enter = function() {
    this.injectTemplte(this.main , this.templateId);
}

HelpPage.prototype.leave = function() {
    
}

/////////////////////Application/////////////////////

function Application(main) {
    this.pages = [new GamePage(main), new HelpPage(main)];
    this.currentPage = this.pages[0];
    window.addEventListener('hashchange',this.hashChange.bind(this));
    this.firstBoot();
}

Application.prototype.welcomeGame = function() {
    this.currentPage.injectTemplte(this.currentPage.main , 'welcome');
    myThis = this;
    //promise returns this = window and not Application 
    return new Promise(function(resolve,reject) {
        setTimeout(resolve,2000);
    })
}

Application.prototype.firstBoot = function() {
    this.currentPage.injectTemplte(this.currentPage.main , this.currentPage.templateId);
    this.currentPage.enter();
}

Application.prototype.showPage = function (numPageInArray) {
    this.currentPage.leave();
    this.currentPage = this.pages[numPageInArray];
    if(this.currentPage.constructor === 'GamePage') {
        this.welcomeGame().then(function() {
            myThis.currentPage.enter();
        });
    }
    else {
        this.currentPage.enter();
    }  
}

Application.prototype.hashChange = function() {
    debugger;
    hashName = window.location.hash.substr(1);
    switch (hashName) {
        case 'game':
            this.showPage(0);
            break;
        case 'help':
            this.showPage(1);
    }
}

הי,
לא עברתי עוד על הקוד - אבל אתחיל עם השאלות

  1. צריך בעליית המערכת לבדוק מה ה hash הנוכחי ולפי זה להראות את הדף המתאים. נדמה לי שאת תמיד מראה את אותו עמוד ולכן הבעיה (אפשר להסתכל על המשתנה window.location.hash)

  2. הפיתרון נקרא bind. דיברתי עליו בקורס בשיעור כאן:
    https://www.tocode.co.il/bundles/html5-web-development/lessons/bind
    ויש גם גירסא חדשה יותר של השיעור הזה כאן:
    https://www.tocode.co.il/bundles/frontend/lessons/this-and-events

  3. נשמע מעולה. נכון אירועים מחוברים ל DOM Elements. ברגע שאת מוחקת DOM Element מסוים כל האירועים עליו יימחקו גם. דרך אחרת לבנות מנגנון כמו שרצית היא לשים Event Handler על האלמנט החיצוני ביותר (על ה body לדוגמא), אלמנט שלא נמחק במעברי דפים ואז שם צריך לבדוק כל פעם שמגיע אירוע מה היה האירוע ולפי זה לשלוח לקוד המתאים. אבל זה מימוש קצת מורכב.
    יותר קל יהיה פשוט לשים מחדש את ה Event Handlers כל פעם שעוברים דף.

בקורס החדש כתבתי שיעור ספציפי על טיפול באירועים עבור הילדים כאן:
https://www.tocode.co.il/bundles/frontend/lessons/events-delegation