קורס Front End למתכנתים שיעור תרגול SVG


זהו נושא דיון מלווה לערך המקורי שב־https://www.tocode.co.il/bundles/frontend/lessons/svg-lab

איך אפשר לישר טקסט בsvg למרכז? (מרכז הטקסט צריך להיות במרכז העיגול.)

הי
כזה?

כן, זה מה שחיפשתי, אבל לא הבנתי איך עושים את זה?
בנוסף רציתי למרכז טקסט גם בשאלה א’ מתחת לעמודים של הגרף
תודה

בקשר לשאלות 3 ומעלה.
הכדור אמור לרוץ ע"י שינוי של cx/cy ב transition?
אם לא, איך כן אמורים לעשות את זה?
אם כן, איך בודקים מפגש אלמנטים?
תודה

אז כיוון אחד יכול להיות לקבוע את ה cx/cy להיות נקודה על המסגרת ואז לתת לערכים להשתנות ב transition. כשה transition נגמר אפשר לבחור יעד אחר. הנה דוגמא קצרה לקונספט:

בצורה של transition איך בודקים מפגש בין אלמנטים?

לא צריך - אנחנו מראש שולחים את האלמנט ל X,Y שם נמצא כבר אלמנט אחר. כשה Transition תסתיים זה אומר שהם התנגשו ואפשר לתת יעד חדש.

מימוש של בדיקת מפגש בין אלמנטים יהיה קצת יותר מורכב ובשביל זה נצטרך לבנות מנגנון שיזיז את הצורות בכל פריים לנקודה הבאה וגם יבדוק התנגשות (כחישוב מתמטי - למשל להוסיף לנקודת מרכז הכדור את הרדיוס ולראות אם התוצאה היא קואורדינטה של צורה אחרת)

בקשר לשאלה 5.
איזה אירוע קורה כשלוחצים על חץ למעלה ולמטה שלפיו אני יכול להתאים את תזוזת המלבן?
תודה

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

  1. אשמח להצעות יעול לקוד
  2. הארוע
    transitionend
    קורא לפנקציה move פעימים לכן השתמשתי בדגל flagMove כדי לבצע את הפעולה פעם אחת
    כיצד ניתן להימנע מקריאה כפולה בארוע transitionend כאשר משנים שני ערכי מיקום למעגל.

הי,
כן זה בגלל שיש שני transitions - שים לב שבאוביקט ev שאתה מקבל לפונקציה move יש שדה בשם propertyName. בפעם הראשונה הוא עם הערך cx ובפעם השניה עם הערך cy. אתה יכול להחליט לשנות את הכיוון רק כשמקבל שם ערך מסוים (אחד מהשניים לבחירתך), או לארגן את הקוד כך שכל סיום טרנזאקציה יטופל בנפרד

מבחינת הצעות אני חושב שה flag קצת מסרבל את הקוד, אבל אחרי שתוותר עליו זה יראה אחלה

תודה
תוקן
השורה הרלוונטית
if (ev.propertyName === ‘cy’ || ev.type === ‘load’)

לייק 1

מה קורה ינון אשמח להערות על פתרון לתרגיל 1
HTML

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
    </head>
    <body>
    <div id = 'searchDiv'>
        <!-- <input id="searchInput" type="text"> -->
        <textarea id="teArSearch"></textarea>
    </div>

    <div id="divShowFather">

        <div id = 'divShowTable'>
        </div>

        <div id='divShowGraf'>
            <div id ='divShowGrafIn' >
                <svg class="svgElement">
                </svg>
            </div>

        </div>
    </div>

    <script src="graph.js"></script>
    <script src="tableFunction.js"></script>
    <script src="getTheDataFunctions.js"></script>
    <script src="main.js"></script>
    </body>
    </html>

CSS

    body{
    margin: 0;
    padding: 0;

    width: 100vw;
    height: 100vh;
    }

    #searchDiv{
    display: flex;
    align-items: center;
    justify-content: center;
    height: 30%;
    width: 100%;
    background-color: antiquewhite;
    }

    #teArSearch{
    resize: none;
    width: 400px;
    height: 200px;
    }

    #divShowFather{
    height: 70%;
    width: 100%;
    background-color: rgb(0, 0, 0);
    display: flex;
    flex-direction: row;
    }

    #divShowTable{
    background-color: rgb(99, 84, 67);
    width: 50%;
    overflow: scroll;
    height: 100%;
    }

    #divShowTable table{
    border-collapse: collapse; /* מזרק את הקווים בין התאים */
    width: 100%;
    }

    #divShowTable th,td{
    border: 1px solid black; /* קווים שחורים */
    padding: 8px; /* מרווח בין התוכן לקווים */
    text-align: center; /* יישור טקסט לשמאל */
    }

    #divShowGraf{
    background-color: rgb(238, 165, 74);
    width: 50%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    }

    #divShowGrafIn{
    width: 80%;
    height: 80%;
    }

JS

    function returnTheSmallOnAray(result5words, countWords){
    if(result5words.length>0){
        let small = result5words[0];
        for(let i = 1 ; i < result5words.length ; i++){
            if(countWords[result5words[i]]<countWords[small]){
                small = result5words[i];
            }
        }
       return small; // small have the key with the smaller value
    }
    }

    function objWithWordsAndCount(){
        let countWords = {};
        if(allWords.length>0){
            for(let i = 0;i<allWords.length;i++){
                if(allWords[i].trim() !== ''){ // מניעת רווחים
                    if(!countWords[allWords[i]]){
                        countWords[allWords[i]] = 0;
                    }
                    countWords[allWords[i]]++;
                }
            }
        }
    return countWords;
    }

    function cheakTheArayWorldsUpdateTable(){

    //Counting how many of each word exists
    let countWords = objWithWordsAndCount();
    
    //Extracting the 5 words that appear the most
    const allKeys = Object.keys(countWords);
    let result5words = [];

    for(let i = 0 ; i<allKeys.length;i++){
        if(result5words.length<5){
            result5words.push(allKeys[i]);
        }
        else{
            let smallerInArrayKey = returnTheSmallOnAray(result5words , countWords);
            if(countWords[allKeys[i]]>countWords[smallerInArrayKey]){
                result5words = result5words.filter(item => item !== smallerInArrayKey); //Create a new array without smallerInArrayKey
                result5words.push(allKeys[i]);
            }
        }
    }

    return result5words;
    }

    const divShowTable = document.querySelector('#divShowTable');
    function setupUiOnTheTable(){
    clearTable();
     const arr =  objWithWordsAndCount();
    //Create table Element
    const tableJS = document.createElement('table');
    tableJS.classList.add('tableFromJS');

    //Create tr table Element
    const trTopTable = document.createElement('tr');

    //Create th table Element
    const thWord = document.createElement('th');
    thWord.textContent= 'Word';

    //Create th table Element
    const thCount = document.createElement('th');
    thCount.textContent= 'Count';

    //Apeend them on tr
    trTopTable.appendChild(thWord);
    trTopTable.appendChild(thCount);

    //Apeend tr on tableJS
    tableJS.appendChild(trTopTable);

    for(key in arr){
        const tr = document.createElement('tr');
        tr.classList.add('trFromJS');
        const tdWord = document.createElement('td');
        const tdCount = document.createElement('td');

        tdWord.textContent = key;
        tdCount.textContent = arr[key];
        tr.appendChild(tdWord);
        tr.appendChild(tdCount);

        tableJS.appendChild(tr);
    }

    //Apeend tableJS on divShowTable
    divShowTable.appendChild(tableJS);
    }

    function clearTable(){
    // while(document.querySelector('.trFromJS')){
    //     document.querySelector('.trFromJS').remove();
    // }
    if(document.querySelector('.tableFromJS')){
        document.querySelector('.tableFromJS').remove();
    }
    }
    function updateGraph(){
    const result5word = cheakTheArayWorldsUpdateTable();
    if(result5word.length>0){
        let hundredPercent = 0;
        const countWords =  objWithWordsAndCount();

        // חישוב השלם שמייצג 100 אחוז
        for(key in countWords){
            hundredPercent+= countWords[key]
        }

        //הזנת כל אחד מה5 מילים עם האחוזים המייצגים אותו 
        let allWordPercent ={}; // have problem here cheak first

        for(key of result5word){
            allWordPercent[key] = (countWords[key]/hundredPercent)*100;
        }
        
        const svg = document.querySelector('svg');
        const svgns = "http://www.w3.org/2000/svg";
        
        const svgParent = document.querySelector('#divShowGrafIn'); // אלמנט האב של ה־SVG
        const divShowGrafInHeight = svgParent.clientHeight; // גובה הדhc

        svg.setAttribute( 'viewBox' , `0 0 ${svgParent.clientWidth} ${svgParent.clientHeight}`);
        let rectHeight = divShowGrafInHeight ; // 30% מגובה האלמנט האב
        let currentY = divShowGrafInHeight - rectHeight; // ה־Y הנוכחי של הריבוע


        const allElementsOnSvg = document.querySelectorAll('.clickable');
        //
        //delete all svg childern 
        for(child of allElementsOnSvg){
                child.remove();
        }
        // for(child of svg.children){
        //     if(child.classList.contains('clickable')){
        //         child.remove();
        //     }
        // }
        // const svgHeight = svg.clientHeight;

        svg.style.width='100%';
        svg.style.height='100%';
        svg.style.backgroundColor='green';
        
        const colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF"];

         let location = 0 ;

        for(key in allWordPercent){
            const color  = colors[location];

            location+=1;
            const rect =  document.createElementNS(svgns,'rect');

            rectHeight = divShowGrafInHeight * (allWordPercent[key]/100); // גובה הריבוע מחושב ע"י גובה הדיב האבא פחות האחוזים
            currentY = divShowGrafInHeight - rectHeight; // התחלת הריבוע מחושבת ע"י חישוב גובה הדיב האבא פחות גובה הריבוע

            rect.setAttribute("height", rectHeight); // גובה הריבוע
            rect.setAttribute("width", "50"); // רוחב הריבוע
            
            rect.setAttribute("x", location * 80); // נקודת ההתחלה בציר ה-x
            rect.setAttribute("y", currentY +20); // משנה את ה-y למטה של ה־SVG

            rect.setAttribute("fill", color); // צבע המילוי

            const textX = document.createElementNS(svgns, 'text');
            textX.setAttribute("x", location * 80); // נקודת ההתחלה בציר ה-X
            textX.setAttribute("y", divShowGrafInHeight - rectHeight +15); // נקודת ההתחלה בציר ה-Y
            textX.setAttribute('stroke',"red"); // נקודת ההתחלה בציר ה-Y
            textX.setAttribute('fill',"black"); // נקודת ההתחלה בציר ה-Y
            
            textX.textContent = `${key} - ${Math.floor(allWordPercent[key])}%`; // הטקסט שיוצג
            textX.style.color='black';
            textX.style.border='1px soild black'
            textX.classList.add('clickable');
            svg.appendChild(textX);
            
            rect.style.backgroundColor = 'black';
            rect.classList.add('clickable');
            svg.appendChild(rect);

        }
    }
    }
    const inputTextArea = document.querySelector('#teArSearch');

    let allWords = [];

    inputTextArea.addEventListener('input',()=>{
    allWords = inputTextArea.value.split(" ");
    setupUiOnTheTable();
    updateGraph();
    });
לייק 1

נראה ממש טוב! התוכנית עובדת, מראה גם את הטבלה וגם את הגרף וכיף לשחק עם זה.

מבחינת המימוש זה מאוד נוח שכל לחיצה אתה מנקה לגמרי את הטבלה ומצייר הכל מחדש, אבל זה עלול ליצור בעיות במקרים יותר מסובכים מהעולם האמיתי, למשל אפשר לדמיין שתהיה עמודה של Checkbox ליד כל מילה בטבלה ואפשר יהיה לסמן איזה מילים אתה רוצה לראות בגרף. במצב כזה כל פעם שתוסיף מילה הסימונים יתאפסו כי אתה יוצר מחדש את האלמנטים ב HTML. אם יש לך זמן ורצון לבונוס, אפשר לנסות לחשוב על מנגנון שיעדכן את הטבלה הקיימת במקום למחוק ולצייר הכל מחדש.

רעיון נוסף לשיפור מבחינת איכות הקוד זה אורך הפונקציות. כרגע יש מעט פונקציות יחסית ארוכות ואולי אפשר לארגן את הקוד מחדש בכתיב Object Oriented או בפונקציות אחרות יותר קצרות. דוגמה קטנה זה הפונקציה של updateGraph שכרגע עושה המון דברים, ואפשר לשבור אותה לכמה חלקים יותר קטנים ויותר גנריים, למשל שתהיה פונקציה אחת שמחשבת את allWordPercent ופונקציה אחרת שמציירת מלבן מכל מפתח בו.

בכל מקרה כל הכבוד על ההשקעה עד לכאן יצא ממש יפה.

תודה רבה על הפידבק !!

מה קורה ינון אשמח לפידבק והערות על תרגיל 4 מצרף את הקוד :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=`, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Single Pong</title>
</head>
<body>
<script src="SvgComponnet.js"></script>
<script src="main.js"></script>
</body>
</html>

CSS

 body{
margin: 0;
padding: 0;
overflow: hidden;
}
svg{
background: black;
width: 100vw;
height: 100vh;
}
circle{
transition: all 0.7s ease;
}
rect{
transition: all 0.4s ease;
}

JAVASCRIPT

class SvgComponnet{
constructor(BodyEle){
    this.bodyElement = BodyEle;
    this.nowCircleX = 0;
    this.nowCircleY = 600;
    this.setupUI();
}
setupUI=()=>{
    const svgns = "http://www.w3.org/2000/svg";
    
    this.svg = document.createElementNS(svgns, "svg");
    this.circle = document.createElementNS(svgns,'circle');

    this.circle.setAttribute("r", 30);
    this.circle.setAttribute("cx", this.nowCircleX);
    this.circle.setAttribute("cy", this.nowCircleY);
    this.circle.setAttribute("fill", 'blue'); 

    this.svg.appendChild(this.circle);

    this.bodyElement.appendChild(this.svg);

    this.createTwoRectangle();

     setInterval(this.moveCircle, 1000);
}
createTwoRectangle=()=>{
    const svgns = "http://www.w3.org/2000/svg";
    this.widthRectangle = 30;
    this.heightRectangle = 120;

    this.rectLeft = document.createElementNS(svgns, "rect");
    this.rectLeft.setAttribute("x", "0"); 
    this.rectLeft.setAttribute("y", "0"); 
    this.rectLeft.setAttribute("width", this.widthRectangle);
    this.rectLeft.setAttribute("height", this.heightRectangle);
    this.rectLeft.setAttribute("fill", "white");

    this.rectRight = document.createElementNS(svgns, "rect");
    this.rectRight.setAttribute("x", this.svg.clientWidth-this.widthRectangle); 
    this.rectRight.setAttribute("y", "0"); 
    this.rectRight.setAttribute("width", this.widthRectangle);
    this.rectRight.setAttribute("height", this.heightRectangle);
    this.rectRight.setAttribute("fill", "green");

    this.svg.appendChild(this.rectLeft);
    this.svg.appendChild(this.rectRight);
}
moveRectLeft=(locationOnYcircle)=>{
    if((locationOnYcircle+this.heightRectangle)>=this.svg.clientHeight){

        const locationTemp = (locationOnYcircle+this.heightRectangle) - this.svg.clientHeight;
        this.rectLeft.setAttribute('y',locationTemp);        }
    else{
        this.rectLeft.setAttribute('y',(locationOnYcircle-(this.heightRectangle/2)));
    }
}
moveRectRight=(locationOnYcircle)=>{
    if((locationOnYcircle+this.heightRectangle)>=this.svg.clientHeight){

        const locationTemp = (locationOnYcircle+this.heightRectangle) - this.svg.clientHeight;
        this.rectRight.setAttribute('y',locationTemp);
    }
    else{
        this.rectRight.setAttribute('y',(locationOnYcircle-(this.heightRectangle/2)));
    }
}
moveCircle=()=>{
    if(this.nowCircleX == 0 || this.nowCircleX == this.svg.clientWidth){
        
        this.nowCircleX = (this.svg.clientWidth*this.randomPrecent())/100; // Create new Location for circle on X
        this.circle.setAttribute('cx',this.nowCircleX); 

        if(this.randomZeroOrOne()==0){

            this.nowCircleY = 0;
            
            this.circle.setAttribute('cy', this.nowCircleY);

        }
        else{

            this.nowCircleY = this.svg.clientHeight;
            this.circle.setAttribute('cy', this.nowCircleY);

        }
    }
    else if(this.nowCircleY == 0 || this.nowCircleY == this.svg.clientHeight){
        
        this.nowCircleY = (this.svg.clientHeight*this.randomPrecent())/100; // Create new Location for circle on Y
        this.circle.setAttribute('cy',this.nowCircleY); 

        if(this.randomZeroOrOne()==0){

            this.nowCircleX = 0;
            this.moveRectLeft(this.nowCircleY);
            this.circle.setAttribute('cx', this.nowCircleX+this.widthRectangle*2);

        }
        else{

            this.nowCircleX = this.svg.clientWidth;
            this.moveRectRight(this.nowCircleY);
            this.circle.setAttribute('cx', this.nowCircleX-this.widthRectangle*2);

        }
    }
}
randomPrecent=()=>{
    return Math.floor(Math.random() * 101);
}
randomZeroOrOne=()=>{
    return Math.floor(Math.random() * 2);
}

}
const start = new SvgComponnet(document.body);

מה קורה ינון מצרף גם את תרגיל 5 אשמח לפידבק והערות גם עליו :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=`, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Pong Game To Single Player</title>
</head>
<body>
<div id="messageContainer">
<p>Game Over !</p>
</div>
<script src="SvgComponnet.js"></script>
<script src="main.js"></script>
</body>
</html>

CSS

body{
margin: 0;
padding: 0;
overflow: hidden;
}
svg{
background: black;
width: 100vw;
height: 100vh;
z-index: 0;
}
circle{
transition: all 0.7s ease;
}
rect{
transition: all 0.4s ease;
}
#messageContainer {
position: absolute;
top: 50%; 
left: 50%;
transform: translate(-50%, -50%);
background-color: #f0f0f0;
border: 1px solid #ccc;
padding: 10px;
z-index: 1; 
width: 200px;
text-align: center;
display: none;
}

JS

class SvgComponnet{
constructor(BodyEle){
    this.bodyElement = BodyEle;
    this.nowCircleX = 0;
    this.nowCircleY = 600;
    this.setupUI();
    this.bodyElement.addEventListener('keydown',this.keyboradClick);
    this.bodyElement.addEventListener('keydown',this.keyboradClick);
    this.timeoutID =  null;
}
setupUI=()=>{
    const svgns = "http://www.w3.org/2000/svg";
    
    this.svg = document.createElementNS(svgns, "svg");
    this.circle = document.createElementNS(svgns,'circle');

    this.circle.setAttribute("r", 30);
    this.circle.setAttribute("cx", this.nowCircleX);
    this.circle.setAttribute("cy", this.nowCircleY);
    this.circle.setAttribute("fill", 'blue'); 

    this.svg.appendChild(this.circle);

    this.bodyElement.appendChild(this.svg);

    this.createTwoRectangle();

    this.intervalID = setInterval(this.moveCircle, 1000);
 }
createTwoRectangle=()=>{
    const svgns = "http://www.w3.org/2000/svg";
    this.widthRectangle = 30;
    this.heightRectangle = 120;

    this.rectLeft = document.createElementNS(svgns, "rect");
    this.rectLeft.setAttribute("x", "0"); 
    this.rectLeft.setAttribute("y", "0"); 
    this.rectLeft.setAttribute("width", this.widthRectangle);
    this.rectLeft.setAttribute("height", this.heightRectangle);
    this.rectLeft.setAttribute("fill", "white");

    this.rectRight = document.createElementNS(svgns, "rect");
    this.rectRight.setAttribute("x", this.svg.clientWidth-this.widthRectangle); 
    this.rectRight.setAttribute("y", "0"); 
    this.rectRight.setAttribute("width", this.widthRectangle);
    this.rectRight.setAttribute("height", this.heightRectangle);
    this.rectRight.setAttribute("fill", "green");

    this.svg.appendChild(this.rectLeft);
    this.svg.appendChild(this.rectRight);
}
moveRectLeft=(locationOnYcircle)=>{
    if((locationOnYcircle+this.heightRectangle)>=this.svg.clientHeight){

        const locationTemp = (locationOnYcircle+this.heightRectangle) - this.svg.clientHeight;
        this.rectLeft.setAttribute('y',locationTemp);//(locationOnYcircle-this.heightRectangle)
    }
    else{
        this.rectLeft.setAttribute('y',(locationOnYcircle-(this.heightRectangle/2)));
    }
}
moveRectRight=(locationOnYcircle)=>{
    if((locationOnYcircle+this.heightRectangle)>=this.svg.clientHeight){

        const locationTemp = (locationOnYcircle+this.heightRectangle) - this.svg.clientHeight;
        this.rectRight.setAttribute('y',locationTemp);//(locationOnYcircle-this.heightRectangle)
    }
    else{
        this.rectRight.setAttribute('y',(locationOnYcircle-(this.heightRectangle/2)));
    }
}
keyboradClick=(event)=>{
    if (event.key === 'ArrowDown') {
      clearTimeout(this.timeoutID);
      this.moveRectangleDown();
    }
    else if (event.key === 'ArrowUp') {
        clearTimeout(this.timeoutID);
        this.moveRectangleUp();
    }
    else if(event.key === ' '){
        clearTimeout(this.timeoutID);
    }
  }

moveRectangleDown=()=>{
    const step = 5;
    const currentY = parseInt(this.rectLeft.getAttribute('y'));
    const maxY = this.svg.clientHeight - this.heightRectangle;
    if (currentY + step <= maxY) {
      this.rectLeft.setAttribute('y', currentY + step);
      this.timeoutID = setTimeout(this.moveRectangleDown, 25); 
    }
}
moveRectangleUp=()=>{
    const step = 5;
    const currentY = parseInt(this.rectLeft.getAttribute('y'));
    const minY = 0;
    if (currentY - step >= minY) {
      this.rectLeft.setAttribute('y', currentY - step);
      this.timeoutID = setTimeout(this.moveRectangleUp, 25); 
    }
}
moveCircle=()=>{
    if(this.nowCircleX == 0 || this.nowCircleX == this.svg.clientWidth){
        
        this.nowCircleX = (this.svg.clientWidth*this.randomPrecent())/100; // Create new Location for circle on X
        this.circle.setAttribute('cx',this.nowCircleX); 

        if(this.randomZeroOrOne()==0){

            this.nowCircleY = 0;
            
            this.circle.setAttribute('cy', this.nowCircleY);

        }
        else{

            this.nowCircleY = this.svg.clientHeight;
            this.circle.setAttribute('cy', this.nowCircleY);

        }
    }
    else if(this.nowCircleY == 0 || this.nowCircleY == this.svg.clientHeight){
        
        this.nowCircleY = (this.svg.clientHeight*this.randomPrecent())/100; // Create new Location for circle on Y
        this.circle.setAttribute('cy',this.nowCircleY); 

        if(this.randomZeroOrOne()==0){

            this.nowCircleX = 0;

            if (
                parseFloat(this.rectLeft.getAttribute('y')) <= parseFloat(this.circle.getAttribute('cy')) &&
                parseFloat(this.rectLeft.getAttribute('y')) + this.heightRectangle >= parseFloat(this.circle.getAttribute('cy'))
              ) 
            {
                console.log('win');
                this.circle.setAttribute('cx', this.nowCircleX+this.widthRectangle*2);
            }

            else{

                this.circle.setAttribute('cx', this.nowCircleX);
                document.querySelector('#messageContainer').style.display = 'block';
                console.log('lose');
                clearInterval(this.intervalID);
            }

        }
        else{

            this.nowCircleX = this.svg.clientWidth;
            this.moveRectRight(this.nowCircleY);
            this.circle.setAttribute('cx', this.nowCircleX-this.widthRectangle*2);

        }
    }
}
randomPrecent=()=>{
    return Math.floor(Math.random() * 101);
}
randomZeroOrOne=()=>{
    return Math.floor(Math.random() * 2);
}

}

הי,

תרגיל 4 - נראה מעולה. שים לב שיש הרבה קוד שחוזר על עצמו ב JavaScript. עדיף לכתוב class בשביל מלבן ו class בשביל הכדור, ואז תהיה פחות חזרה בבלוק האיתחול ובבלוקים של התנועה.

לגבי שני התרגילים - אחרי שתעביר את זה לקלאס נפרד המבנה שיותר מקובל למשחקים נקרא game loop. הרעיון לכתוב לולאה שרצה כל הזמן שכל איטרציה מבינה מה צריך לקרות במשחק באותה שניה ומזיזה את הדברים בהתאם. במקרה של הפינג פונג זה יהיה משהו כמו:

function tick() {
  rect1.move()
  rect2.move()
  ball.move()
  
  if (ball.direction == "right") {
    if (ballHits(RightWall)) { /* lose points */ }
    if (ballHits(rect1)) { /* change direction */ }
  }
  if (ball.direction == "left") {
    // check the same for the left wall and rectangle
  }

  // check if ball hits top or bottom walls, then change its y direction
}

ולמה זה נקרא לולאה? כי בעבר היינו קוראים ל tick כזה בתוך setInterval שיריץ אותו נגיד 20 פעמים בשניה. היום מקובל להשתמש בפונקציה requestAnimationFrame שאוטומטית מפעילה את tick לפי המהירות האופטימלית של הדפדפן:

requestAnimationFrame(tick)

אפשר לקרוא עליה כאן: