Swing - طريقة إنشاء لعبة الأفعى Snake 2D

Java Swing طريقة إنشاء لعبة الأفعى Snake 2D

في هذا الدرس ستتعلم طريقة إنشاء لعبة ( Snake 2D ) إحترافية بإستخدام إطار الـ Swing.In this lesson, you will learn how to create a professional (Snake 2D) game using the Swing framework.

java swing 2d snake game source code تحميل كود لعبة الأفعى في جافا


مميزات  لعبة الأفعى Snake 2D

  • أعلى مجموع يصل إليه اللاعب يبقى مخزناً حتى إذا تم إغلاق اللعبة.

  • يمكن إيقاف و متابعة اللعبة بالنقر على زر المسافة الفارغة Space.

  • إذا لمست الأفعى الحائط لا يخسر اللاعب, لأن الأفعى ستظهر من الجهة المقابلة.

  • يمكن تعديل كود اللعبة بكل سهولة لأنه غير معقد.

The player's highest total remains stored even if the game is closed.

The game can be paused and continued by clicking on the Space button.

If the snake touches the wall, the player does not lose, because the snake will appear from the opposite side.

The game code can be modified very easily because it is not complicated.

كيفية  إنشاء لعبة الأفعى Snake 2D

في هذا المشروع قمنا بوضع ملفات الجافا بداخل مجلد إسمه snake_2d.
و قمنا بوضع الصور بداخل مجلد إسمه images كما في الصورة التالية.

⇓ تحميل اللعبة ⇓ تحميل المشروع كاملاً ⇓ تحميل مجلد الصور فقط



كود  إنشاء لعبة الأفعى Snake 2D

Position.java
//  snake_2d  موجود بداخل المجلد  Position.java   هنا ذكرنا أن الملف
package snake_2d;

// قمنا بإنشاء هذا الكلاس لجعل أي كائن منه يمثل موقع دائرة من الدوائر الموجودة في الأفعى
public class Position {
 
    // إذاً كل دائرة في الأفعى ستكون موجودة في نفس الوقت على خط طول و خط عرض محددين
    int x, y;
 
    // قمنا بتجهيز هذا الكونستركتور لتحديد مكان وجود أي دائرة في الأفعى لحظة إنشاء الكائن منه
    public Position(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
}
		

GameDrawer.java
//  snake_2d  موجود بداخل المجلد  GameDrawer.java   هنا ذكرنا أن الملف
package snake_2d;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
 
// أي أصبح هذا الكلاس يمثل حاوية ,JPanel هنا قمنا بإنشاء كلاس يرث من الكلاس
// حتى نجعل المستخدم قادر على تحريك الأفعى من الكيبورد KeyListener و جعلناه يطبق الإنترفيس
// حتى نستطيع رسم الأفعى من جديد كلما تحركت ActionListener و جعلناه يطبق الإنترفيس
public class GameDrawer extends JPanel implements KeyListener, ActionListener{
 
    // قمنا بإنشاء هاتين المصفوفتين لتخزين مكان وجود كل دائرة من الأفعى كل لحظة, أي لتحديد المكان المحجوز لعرض دوائر الأفعى
    // ( بما أن عدد الدوائر الأقصى بالطول هو 22 و بالعرض هو أيضاً 22, فهذا يعني أنه يمكن تخزين 22×22 دائرة ( أي 484
    final int[] boardX = new int[484];
    final int[] boardY = new int[484];
 
    // سنستخدم هذه المصفوفة لتخزين مكان وجود كل دائرة في الأفعى حتى نعرف الموقع الذي لا يجب أن نظهر فيه الدائرة الحمراء
    // ملاحظة: سبب إستخدام هذا المصفوفة لتخزين موقع كل دائرة في الأفعى من جديد هو فقط لجعل اللعبة لا تعلق, أي لتحسين أداء اللعبة
    LinkedList<Position> snake = new LinkedList();
 
    // سنستخدم هذه المتغيرات لتحديد الإتجاه الذي ستتجه إليه الأفعى
    boolean left = false;
    boolean right = false;
    boolean up = false;
    boolean down = false;
 
    // سنستخدم هذه الكائنات لرسم إتجاه وجه الأفعى
    ImageIcon lookToRightImage = new ImageIcon(this.getClass().getResource("/images/face-look-right.jpg"));
    ImageIcon lookToLeftImage = new ImageIcon(this.getClass().getResource("/images/face-look-left.jpg"));
    ImageIcon lookToUpImage = new ImageIcon(this.getClass().getResource("/images/face-look-up.jpg"));
    ImageIcon lookToDownImage = new ImageIcon(this.getClass().getResource("/images/face-look-down.jpg"));
 
    // سنستخدم هذا الكائن في كل مرة لرسم جسد الأفعى
    ImageIcon snakeBodyImage = new ImageIcon(this.getClass().getResource("/images/body.png"));
 
    // سنستخدم هذا الكائن في كل مرة لرسم طعام الأفعى
    ImageIcon fruitImage = new ImageIcon(this.getClass().getResource("/images/fruit.png"));
 
    // سنستخدم هذا المتغير لتخزين عدد الدوائر التي تشكل الأفعى
    int lengthOfSnake = 3;
 
    // سنستخدم هاتين المصفوفتين لتحديد الأماكن التي يمكن أن يظهر فيها الطعام
    int[] fruitXPos = {20, 40, 60, 80, 100, 120, 140, 160, 200, 220, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 460};
    int[] fruitYPos = {20, 40, 60, 80, 100, 120, 140, 160, 200, 220, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 460};
 
    // سنستخدم هذا الكائن لجعل الأفعى تستمر بالحركة .Thread و الذي يشبه الـ Timer هنا قمنا بإنشاء كائن من الكلاس
    Timer speedTimer;
 
    // سنستخدم هذا المتغير لتحديد أنه كل 0.1 ثانية سستتحرك الأفعى
    int delay = 100;
 
    // سنستخدم هذا المتغير لمعرفة إذا كانت الأفعى تتحرك أم لا
    int moves = 0;
 
    // سنستخدم هذه المتغيرات لتحديد المجموع الذي يحققه اللاعب أثناء اللعب
    int totalScore = 0;
    int fruitEaten = 0;
    int scoreReverseCounter = 99;
 
    // في حال كان اللاعب قد حقق مجموع عالي من قبل, سيتم إظهاره كأفضل مجموع وصل إليه
    int bestScore = readBestScorefromTheFile();
 
    // لتوليد أماكن ظهور طعام الأفعى بشكل عشوائي random سنستخدم الكائن
    Random random = new Random();
 
    // هنا قمنا بتحديد مكان أول طعام سيظهر في اللعبة قبل أن يبدأ المستخدم باللعب, و جعلناه يظهر تحت الأفعى
    int xPos = random.nextInt(22);
    int yPos = 5+random.nextInt(17);
 
    // سنستخدم هذا المتغير لمعرفة ما إذا كان المستخدم قد خسر أم لا
    boolean isGameOver = false;
 
    // هنا تحديد حجم اللعبة و تهيئتها لإستشعار الأزرار التي ينقر عليها المستخدم لحظة إنشاء كائن من هذا الكلاس
    // لجعل اللعبة تتحرك بسرعة 0.1 بالثانية speedTimer و قمنا تهيئة اللعبة لإستشعار الأزرار التي ينقر عليها المستخدم و تهيئة الكائن
    public GameDrawer()
    {
        setPreferredSize(new Dimension(750, 500));
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        speedTimer = new Timer(delay, this);
    }
 
    @Override
    public void setSize(int width, int height) {
        super.setSize(width, height);
    }
 
    @Override
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x, y, width, height);
    }
 
    // (لتحديد كيف سيتم رسم و تلوين كل شيء في الحاوية (أي في اللعبة paint() للدالة Override هنا فعلنا
    // ملاحظة هذه الدالة تستدعى بشكل تلقائي عند رسم أي شيء في الحاوية
    @Override
    public void paint(Graphics g)
    {
        // هنا قمنا بتحديد مكان وجود الأفعى في كل مرة يقوم المستخدم ببدأ اللعبة من جديد
        if(moves == 0)
        {
            boardX[2] = 40;
            boardX[1] = 60;
            boardX[0] = 80;
 
            boardY[2] = 100;
            boardY[1] = 100;
            boardY[0] = 100;
 
            scoreReverseCounter = 99;
            speedTimer.start();
        }
 
        // هنا قمنا بجعل المجموع الحالي الذي وصل إليه المستخدم يظهر كأعلى مجموع وصل إليه في حال تخطى المجموع القديم
        if(totalScore > bestScore)
            bestScore = totalScore;
 
        // هنا قمن بإنشاء مربع أسود يمثل لون خلفية اللعبة
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, 750, 500);
 
        // هنا قمنا برسم المربعات التي تشكل الحدود التي لا تستطيع الأفعى عبورها باللون الرمادي
        // حجم كل مربع 13 بيكسل و المسافة بينهما 5 بيكسل
        g.setColor(Color.DARK_GRAY);
        for(int i=6; i<=482; i+=17)
            for(int j=6; j<=482; j+=17)
                g.fillRect(i, j, 13, 13);
 
        // هنا فمنا بإنشاء مربع أسود كبير فوق المربعات التي تشكل حدود اللعبة لتظهر و كأنها فارغة من الداخل
        g.setColor(Color.BLACK);
        g.fillRect(20, 20, 460, 460);
 
        // هنا قمنا بكتابة إسم اللعبة و تلوينه بالأزرق
        g.setColor(Color.CYAN);
        g.setFont(new Font("Arial", Font.BOLD, 26));
        g.drawString("Snake 2D", 565, 35);
 
        // هنا قمنا بكتابة إسم اللعبة و تلوينه بالأزرق
        g.setFont(new Font("Arial", Font.PLAIN, 13));
        g.drawString("+ "+scoreReverseCounter, 510, 222);
 
        // هنا جعلنا أي شيء سنقوم بكتابته يظهر باللون الرمادي
        g.setColor(Color.LIGHT_GRAY);
 
        // هنا قمنا بطباعة أنه تم تطوير اللعبة بواسطة موقعنا
        g.setFont(new Font("Arial", Font.PLAIN, 15));
        g.drawString("Developed by harmash.com", 530, 60);
 
        // لقياس حجم الأرفام الظاهرة في المربعات الثلاث بالبيكسل حتى نستطيع عرضهم في الوسط FontMetrics هنا قمنا بإنشاء الكائن
        FontMetrics fm = g.getFontMetrics();
 
        // هنا جعلنا أي شيء سنقوم بكتابته يظهر بنوع و حجم هذا الخط
        g.setFont(new Font("Arial", Font.PLAIN, 18));
 
        //و المربع الذي تحته و الرقم الذي بداخله Best Score هنا قمنا برسم النص
        g.drawString("Best Score", 576, 110);
        g.drawRect(550, 120, 140, 30);
        g.drawString(bestScore+"", 550+(142-fm.stringWidth(bestScore+""))/2, 142);
 
        //و المربع الذي تحته و الرقم الذي بداخله Total Score هنا قمنا برسم النص
        g.drawString("Total Score", 573, 190);
        g.drawRect(550, 200, 140, 30);
        g.drawString(totalScore+"", 550+(142-fm.stringWidth(totalScore+""))/2, 222);
 
        //و المربع الذي تحته و الرقم الذي بداخله Fruit Eaten هنا قمنا برسم النص
        g.drawString("Fruit Eaten", 575, 270);
        g.drawRect(550, 280, 140, 30);
        g.drawString(fruitEaten+"", 550+(142-fm.stringWidth(fruitEaten+""))/2, 302);
 
        // Controls هنا قمنا برسم النص
        g.setFont(new Font("Arial", Font.BOLD, 16));
        g.drawString("Controls", 550, 360);
 
        // Controls هنا قمنا برسم النصوص الظاهرة تحت النص
        g.setFont(new Font("Arial", Font.PLAIN, 14));
        g.drawString("Pause / Start : Space", 550, 385);
        g.drawString("lookTo Up : Arrow Up", 550, 410);
        g.drawString("lookTo Down : Arrow Down", 550, 435);
        g.drawString("lookTo Left : Arrow Left", 550, 460);
        g.drawString("lookTo Right : Arrow Right", 550, 485);
 
        // هنا جعلنا الأفعى تنظر ناحية اليمين قبل أن يبدأ اللاعب بتحريكها
        lookToRightImage.paintIcon(this, g, boardX[0], boardY[0]);
 
        // هنا قمنا بمسح مكان وجود الأفعى السابق لأننا سنقوم بتخزين المكان الجديد كلما تحركت
        snake.clear();
 
        // هنا قمنا بإنشاء حلقة ترسم كامل الدوائر التي تشكل الأفعى كل 0.1 ثانية
        for(int i=0; i<lengthOfSnake; i++)
        {
            if(i==0 && left)
                lookToLeftImage.paintIcon(this, g, boardX[i], boardY[i]);
 
            else if(i==0 && right)
                lookToRightImage.paintIcon(this, g, boardX[i], boardY[i]);
 
            else if(i==0 && up)
                lookToUpImage.paintIcon(this, g, boardX[i], boardY[i]);
 
            else if(i==0 && down)
                lookToDownImage.paintIcon(this, g, boardX[i], boardY[i]);
 
            else if(i!=0)
                snakeBodyImage.paintIcon(this, g, boardX[i], boardY[i]);
 
            // snake هنا قمنا بتخزين الموقع الحالي لكل دائرة في الأفعى في الكائن
            snake.add(new Position(boardX[i], boardY[i]));
        }
 
        // تقل بشكل تدريجي و أدنى قيمة ممكن أن تصل إليها هي 10 scoreReverseCounter هنا جعلنا قيمة العداد
        if(scoreReverseCounter != 10)
            scoreReverseCounter--;
 
        // هنا قمنا بإنشاء هذه الحلقة للتأكد إذا كان رأس الأفعى قد لامس أي جزء من جسدها
        for(int i=1; i<lengthOfSnake; i++)
        {
            // إذاً عندما يلمس رأس الأفعى جسدها سيتم جعل أول دائرة موجودة خلف الرأس تمثل رأس الأفعى حتى لا يظهر رأسها فوق جسدها
            if(boardX[i] == boardX[0] && boardY[i] == boardY[0])
            {
                if(right)
                    lookToRightImage.paintIcon(this, g, boardX[1], boardY[1]);
 
                else if(left)
                    lookToLeftImage.paintIcon(this, g, boardX[1], boardY[1]);
 
                else if(up)
                    lookToUpImage.paintIcon(this, g, boardX[1], boardY[1]);
 
                else if(down)
                    lookToDownImage.paintIcon(this, g, boardX[1], boardY[1]);
 
                // للإشارة إلى أن اللاعب قد خسر true تساوي isGameOver بعدها سيتم جعل قيمة الـ
                // Space و بالتالي يمكنه أن يبدأ من جديد بالنقر على زر المسافة الفارغة
                isGameOver = true;
 
                // يتوقف و بالتالي ستتوقف الأفعى تماماً عن الحركة speedTimer بعدها سيتم جعل الـ
                speedTimer.stop();
 
                // Game Over بعدها سيتم إظهار النص
                g.setColor(Color.WHITE);
                g.setFont(new Font("Arial", Font.BOLD, 50));
                g.drawString("Game Over", 110, 220);
 
                // تحته Press Space To Restart و سيتم إظهار النص
                g.setFont(new Font("Arial", Font.BOLD, 20));
                g.drawString("Press Space To Restart", 130, 260);
 
                // في الأخير سيتم إستدعاء هذه الدالة لحفظ أكبر مجموع وصل إليه اللاعب
                writeBestScoreInTheFile();
            }
        }
 
        // إذا لمس رأس الأفعى الطعام سيتم إخفاء الطعام و زيادة مجموع اللاعب
        if((fruitXPos[xPos] == boardX[0]) && fruitYPos[yPos] == boardY[0])
        {
            totalScore += scoreReverseCounter;
            scoreReverseCounter = 99;
            fruitEaten++;
            lengthOfSnake++;
        }
 
        // هنا في كل مرة سيتم ضمان أن لا يظهر الطعام فوق الأفعى
        for(int i=0; i<snake.size(); i++)
        {
            // في حال ظهر الطعام فوق جسد الأفعى سيتم خلق مكان عشوائي آخر لوضعها فيه
            if(snake.get(i).x == fruitXPos[xPos] && snake.get(i).y == fruitYPos[yPos]) {
                xPos = random.nextInt(22);
                yPos = random.nextInt(22);
            }
        }
 
        // في الأخير سيتم عرض الطعام بعيداً عن جسد الأفعى
        fruitImage.paintIcon(this, g, fruitXPos[xPos], fruitYPos[yPos]);
 
        // قمنا باستدعاء هذه الدالة للتخلص من أي مصادر لا حاجة لها في البرنامج و يمكنك إزالتها لأنها لن تؤثر هنا
        g.dispose();
    }
 
 
    // لتحديد الإتجاه الذي ستتحرك فيه النافذة keyPressed() للدالة Override هنا فعلنا
    @Override
    public void keyPressed(KeyEvent e) {
 
        // Space هنا قمنا بتحديد ما سيحدث إذا قام اللاعب بالنقر على زر المسافة الفارغة
        if(e.getKeyCode() == KeyEvent.VK_SPACE)
        {
 
            // سيتم إيقاف اللعبة بشكل مؤقت إذا كانت اللعبة شغالة
            if(speedTimer.isRunning() && isGameOver == false)
                speedTimer.stop();
 
            // سيتم إعادة اللعبة للعمل إذا كان قد تم إيقافها سابقاً
            else if(!speedTimer.isRunning() && isGameOver == false)
                speedTimer.start();
 
            // سيتم بدأ اللعبة من جديد في حال كان قد تم إيقاف اللعبة لأن اللاعب قد خسر
            else if(!speedTimer.isRunning() && isGameOver == true)
            {
                isGameOver = false;
                speedTimer.start();
                moves = 0;
                totalScore = 0;
                fruitEaten = 0;
                lengthOfSnake = 3;
                right = true;
                left = false;
                xPos = random.nextInt(22);
                yPos = 5+random.nextInt(17);
            }
        }
 
        // هنا قمنا بتحديد ما سيحدث إذا قام اللاعب بالنقر على زر السهم الأيمن
        else if(e.getKeyCode() == KeyEvent.VK_RIGHT)
        {
            // إذا لم تكن الأفعى تسير في الإتجاه الأيسر سيتم توجيهها نحو الإتجاه الأيمن
            moves++;
            right = true;
 
            if(!left)
                right = true;
 
            else
            {
                right = false;
                left = true;
            }
 
            up = false;
            down = false;
        }
 
        // هنا قمنا بتحديد ما سيحدث إذا قام اللاعب بالنقر على زر السهم الأيسر
        else if(e.getKeyCode() == KeyEvent.VK_LEFT)
        {
            // إذا لم تكن الأفعى تسير في الإتجاه الأيمن سيتم توجيهها نحو الإتجاه الأيسر
            moves++;
            left = true;
 
            if(!right)
                left = true;
 
            else
            {
                left = false;
                right = true;
            }
 
            up = false;
            down = false;
        }
 
        // هنا قمنا بتحديد ما سيحدث إذا قام اللاعب بالنقر على زر السهم المتجه للأعلى
        else if(e.getKeyCode() == KeyEvent.VK_UP)
        {
            // إذا لم تكن الأفعى تسير في اتجاه الأسفل سيتم توجيهها نحو الأعلى
            moves++;
            up = true;
 
            if(!down)
                up = true;
 
            else
            {
                up = false;
                down = true;
            }
 
            left = false;
            right = false;
        }
 
        // هنا قمنا بتحديد ما سيحدث إذا قام اللاعب بالنقر على زر السهم المتجه للأسفل
        else if(e.getKeyCode() == KeyEvent.VK_DOWN)
        {
            // إذا لم تكن الأفعى تسير في اتجاه الأعلى سيتم توجيهها نحو الأسفل
            moves++;
            down = true;
 
            if(!up)
                down = true;
 
            else
            {
                up = true;
                down = false;
            }
 
            left = false;
            right = false;
        }
    }
 
 
    // هنا قمنا بتحديد المكان الذي ستظهر فيه الأفعى لحظة تحركها
    // و لاحظ أنه يوجد شرط متعلق بكل إتجاه تسلكه الأفعى حالياً
    // هذه الشروط الموضوعة تجعل الأفعى تظهر في الجهة المقابلة لها عندما تلمس الحائط
    @Override
    public void actionPerformed(ActionEvent e) {
 
        if(right)
        {
            for(int i = lengthOfSnake-1; i>=0; i--)
                boardY[i+1] = boardY[i];
 
            for(int i = lengthOfSnake; i>=0; i--)
            {
                if(i==0)
                    boardX[i] = boardX[i] + 20;
 
                else
                    boardX[i] = boardX[i-1];
 
                if(boardX[i] > 460)
                    boardX[i] = 20;
            }
        }
 
        else if(left)
        {
            for(int i = lengthOfSnake-1; i>=0; i--)
                boardY[i+1] = boardY[i];
 
            for(int i = lengthOfSnake; i>=0; i--)
            {
                if(i==0)
                    boardX[i] = boardX[i] - 20;
 
                else
                    boardX[i] = boardX[i-1];
 
                if(boardX[i] < 20)
                    boardX[i] = 460;
            }
        }
 
        else if(up)
        {
            for(int i = lengthOfSnake-1; i>=0; i--)
                boardX[i+1] = boardX[i];
 
            for(int i = lengthOfSnake; i>=0; i--)
            {
                if(i==0)
                    boardY[i] = boardY[i] - 20;
 
                else
                    boardY[i] = boardY[i-1];
 
                if(boardY[i] < 20)
                    boardY[i] = 460;
            }
        }
 
        else if(down)
        {
            for(int i = lengthOfSnake-1; i>=0; i--)
                boardX[i+1] = boardX[i];
 
            for(int i = lengthOfSnake; i>=0; i--)
            {
                if(i==0)
                    boardY[i] = boardY[i] + 20;
 
                else
                    boardY[i] = boardY[i-1];
 
                if(boardY[i] > 460)
                    boardY[i] = 20;
            }
        }
 
        repaint();
    }
 
 
    @Override
    public void keyReleased(KeyEvent e) {
 
    }
 
 
    @Override
    public void keyTyped(KeyEvent e) {
 
    }
 
 
    // هذه الدالة تحفظ أعلى مجموع وصل إليه اللاعب في ملف خارجي بجانب ملف اللعبة
    private void writeBestScoreInTheFile()
    {
        if(totalScore >= bestScore)
        {
            try {
                FileOutputStream fos = new FileOutputStream("./snake-game-best-score.txt");
                OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
                osw.write(bestScore+"");
                osw.flush();
                osw.close();
            }
            catch(IOException e) {
            }
        }
    }
 
 
    // هذه الدالة تقرأ أعلى مجموع وصل إليه اللاعب من الملف الخارجي الموجود بجانب ملف اللعبة
    // في حال كان لا يوجد ملف خارجي ستقوم بإنشائه و وضع القيمة 0 فيه كقيمة أولية و هذا ما سيحدث عندما يقوم اللاعب بتشغيل اللعبة أول مرة
    private int readBestScorefromTheFile()
    {
        try {
            InputStreamReader isr = new InputStreamReader( new FileInputStream("./snake-game-best-score.txt"), "UTF-8" );
            BufferedReader br = new BufferedReader(isr);
 
            String str = "";
            int c;
            while( (c = br.read()) != -1){
                if(Character.isDigit(c))
                    str += (char)c;
            }
            if(str.equals(""))
                str = "0";
 
            br.close();
            return Integer.parseInt(str);
        }
        catch(IOException e) {
        }
        return 0;
    }
 
}
		

Main.java
//  snake_2d  موجود بداخل المجلد  Main.java   هنا ذكرنا أن الملف
package snake_2d;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
 
// و بالتالي أصبح إنشاء كائن منه يمثل إنشاء نافذة JFrame يرث من الكلاس Main هنا جعلنا الكلاس
public class Main extends JFrame {
 
    // فقط createAndShowGUI() سيقوم الكونستركتور بإستدعاء الدالة Main عند إنشاء كائن من الكلاس
    public Main() {
        createAndShowGUI();
    }
 
    // هنا نضع كود إنشاء النافذة و محتوياتها
    private void createAndShowGUI() {
 
        // هنا قمنا بإنشاء كائن من الحاوية التي تحتوي على اللعبة
        GameDrawer gameDrawer = new GameDrawer();
 
        // هنا وضعنا الحاوية التي تحتوي على اللعبة في النافذة حتى تظهر بداخلها
        add(gameDrawer);
 
        // هنا قمنا بتحديد بعض خصائص النافذة و جعلناها مرئية
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
 
    }
 
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // التي ستنشئ النافذة createAndShowGUI() و بالتالي سيتم إستدعاء الدالة Main هنا قمنا بإنشاء كائن من الكلاس
                new Main();
            }
        });
    }
 
}
		

دورة تعلم الجافا Swing