How to find out that something is a Legal Move for my Chess Game?

Im currently Programming a Chess game. I want to mark all possible Moves for Piece if I click it. For a Move to be Legal it has to be following the Movement of the Piece, it has to be in Bounds of the Chessboard and it has to be safe for the own King. My Problem is with the last one. Because to determine if a Piece is attacking the King after a Move you would have to simulate it and than check if something can attack the King. but if just update all possible Moves and look if someting is attacking the King after the Simulation you get infinite Rekursion. My only way was to have an additonal variable (canAttackKing // after a move) and a Method just like the one to determine the possible Moves just without the Check if its safe. But this is just annoying and hard to take care of. Constantly there are some Bugs etc. So my Question is, is there a better way to determine if a Move is Safe?

All the Pieces have different classes which inherit the Piece class like this:

import lombok.Getter;
import javafx.scene.image.Image;
import schach.SchachScreen.Chessboard;
import lombok.Setter;

import java.util.Objects;

@Getter
public abstract class Piece {
    private final boolean white;
    protected int x,y;
    private final Image image;
    protected Chessboard c;

    @Setter
    protected int[][] possibleMoves = new int[8][8]; // 0 = no move, 1 = move, 2 = capture

    @Setter
    protected boolean canAttackKing = false; // If something else Moves

    public Piece(boolean white) {
        this.white = white;
        this.image = loadImage();
    }

    private Image loadImage() {
        String color = white ? "white" : "black";
        String className = this.getClass().getSimpleName().toLowerCase();
        String imagePath = "/Images/figuren/" + color + "_" + className + ".png";
        return new Image(Objects.requireNonNull(getClass().getResourceAsStream(imagePath)));
    }

    public void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }


    public abstract void setPossibleMoves();


    public abstract void updateCanAttackKing2();

    public abstract String getFenNotation();

    public boolean canAttackKing() {
        return canAttackKing;
    }

    protected void addMoveIfSafe(int x, int y) {
        if (c.simMovePiece(x, y, this)) {
            possibleMoves[x][y] = c.getPiece(x, y) == null ? 1 : 2;
        }
    }

    protected void checkDiagonals() {
        c = Chessboard.getInstance();
        int originalX = x;
        int originalY = y;

        checkLine(originalX, originalY, 1, 1);  // Top-right
        checkLine(originalX, originalY, -1, -1);  // Bottom-left
        checkLine(originalX, originalY, -1, 1);  // Top-left
        checkLine(originalX, originalY, 1, -1);  // Bottom-right
    }

    protected void checkStraights() {
        c = Chessboard.getInstance();
        int originalX = x;
        int originalY = y;

        checkLine(originalX, originalY, 1, 0);  // Right
        checkLine(originalX, originalY, -1, 0);  // Left
        checkLine(originalX, originalY, 0, 1);  // Up
        checkLine(originalX, originalY, 0, -1);  // Down
    }

    private void checkLine(int startX, int startY, int xIncrement, int yIncrement) {
        for (int i = 1; startX + i * xIncrement < 8 && startX + i * xIncrement >= 0
                && startY + i * yIncrement < 8 && startY + i * yIncrement >= 0; i++) {
            int newX = startX + i * xIncrement;
            int newY = startY + i * yIncrement;
            if (c.check(newX, newY, this) == 0) break;
            addMoveIfSafe(newX, newY);
            if (c.check(newX, newY, this) == 2) break;
        }
    }

    // 2cnd Step (It doesn't have to check if its safe,
    // if it attacks the King its over before the other player can move)
    protected void checkDiagonals2() {
        c = Chessboard.getInstance();
        int originalX = x;
        int originalY = y;

        checkLine2(originalX, originalY, 1, 1);  // Top-right
        checkLine2(originalX, originalY, -1, -1);  // Bottom-left
        checkLine2(originalX, originalY, -1, 1);  // Top-left
        checkLine2(originalX, originalY, 1, -1);  // Bottom-right
    }

    protected void checkStraights2() {
        c = Chessboard.getInstance();
        int originalX = x;
        int originalY = y;

        checkLine2(originalX, originalY, 1, 0);  // Right
        checkLine2(originalX, originalY, -1, 0);  // Left
        checkLine2(originalX, originalY, 0, 1);  // Up
        checkLine2(originalX, originalY, 0, -1);  // Down
    }

    private void checkLine2(int startX, int startY, int xIncrement, int yIncrement) {
        for (int i = 1; startX + i * xIncrement < 8 && startX + i * xIncrement >= 0
                && startY + i * yIncrement < 8 && startY + i * yIncrement >= 0; i++) {
            int newX = startX + i * xIncrement;
            int newY = startY + i * yIncrement;
            if (c.check(newX, newY, this) == 2) {
                if (c.getBoard()[newX][newY] instanceof King) {
                    canAttackKing = true;
                }
                break;
            }
        }
    }

}
import schach.SchachScreen.Chessboard;
import schach.SchachScreen.SchachScreenPresenter;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class Pawn extends Piece {
    private boolean moved = false;
    private int direction;

    public Pawn(boolean white) {
        super(white);
        direction = isWhite() ? -1 : 1; // White moves up (negative), Black moves down (positive)
    }

    @Override
    public String getFenNotation() {
        return isWhite() ? "P" : "p"; // Return "P" for white, "p" for black
    }

    @Override
    public void setPossibleMoves() {
        c = Chessboard.getInstance();
        possibleMoves = new int[8][8]; // Initialize the move matrix

        checkMoveForward();          // Regular move forward by one
        checkInitialDoubleMove();    // First move double-step
        checkEnPassant();            // En Passant
        checkDiagonalCaptures();    // Diagonal captures
    }

    private void checkMoveForward() {
        if (c.check(x, y + direction, this) == 1) {
            addMoveIfSafe(x, y + direction);  // Regular forward move if the square is empty
        }
    }

    private void checkInitialDoubleMove() {
        if (!moved && c.check(x, y + 2 * direction, this) == 1
                && c.check(x, y + direction, this) == 1) {
            addMoveIfSafe(x, y + 2 * direction); // Double move on first move if both squares are free
        }
    }

    private void checkDiagonalCaptures() {
        if (c.getPiece(x + 1, y + direction) != null
                && c.check(x + 1, y + direction, this) == 2) {
            addMoveIfSafe(x + 1, y + direction);  // Capture to the right
        }
        if (c.getPiece(x - 1, y + direction) != null
                && c.check(x - 1, y + direction, this) == 2) {
            addMoveIfSafe(x - 1, y + direction);  // Capture to the left
        }
    }

    private void checkEnPassant() {
        // En Passant to the right (if valid)
        if (SchachScreenPresenter.convertToChessNotation(x + 1, y + direction)
                .equals(c.getPossibleEnPassant()) && c.getPiece(x + 1, y) instanceof Pawn) {
            addEnPassantIfSafe(true);  // Right side en passant
        }
        // En Passant to the left (if valid)
        else if (SchachScreenPresenter.convertToChessNotation(x - 1, y + direction)
                .equals(c.getPossibleEnPassant()) && c.getPiece(x - 1, y) instanceof Pawn) {
            addEnPassantIfSafe(false); // Left side en passant
        }
    }

    private void addEnPassantIfSafe(boolean right) {
        if (c.simEnPassant(this, right)) {
            // En Passant capture move is represented as `5` in the move matrix
            possibleMoves[x + (right ? 1 : -1)][y + direction] = 5;
        }
    }

    @Override
    public void updateCanAttackKing2() {
        canAttackKing = false;
        c = Chessboard.getInstance();
        int x = getX();
        int y = getY();

        checkKingAttack(x + 1, y + direction); // Check for possible attack on the right diagonal
        checkKingAttack(x - 1, y + direction); // Check for possible attack on the left diagonal
    }

    private void checkKingAttack(int x, int y) {
        if (c.check(x, y, this) == 2 && c.getPiece(x, y) instanceof King) {
            canAttackKing = true; // Mark as can attack king if the square is occupied by the opposing king
        }
    }
}
import schach.figuren.*;
import javafx.scene.shape.MoveTo;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
public class Chessboard {

    private boolean gameRunning = true;

    private Piece[][] board = new Piece[8][8];
    private List<Piece> whitePieces = new ArrayList<>();
    private List<Piece> blackPieces = new ArrayList<>();


    private int moveCounter;
    private boolean whiteTurn;
    private King whiteKing, blackKing;
    private Piece selectedPiece = null;

    private static Chessboard instance;

    private String possibleEnPassant;

    //fifty move rule and threefold repetition rule
    private int fiftyMoveCounter; // 1 Move consists of both Players making a move, therefore this variable has to reach 100 for a draw
    private List<String> boardStates;

    private Chessboard() {
        fiftyMoveCounter = 0;
        possibleEnPassant = "-";
        moveCounter = 0;
        whiteTurn = true;

        boardStates = new ArrayList<>();
    }

    public static Chessboard getInstance() {
        if (instance == null) {
            instance = new Chessboard();
        }
        return instance;
    }

    public static void resetInstance() {
        instance = new Chessboard();
    }

    public void initializeBoard() {
        board = new Piece[8][8];
        whiteTurn = true;
        moveCounter = 0;
        selectedPiece = null;

        // Place white pieces at the bottom
        addPiece(new Rook(true), 0, 7);
        addPiece(new Knight(true), 1, 7);
        addPiece(new Bishop(true), 2, 7);
        addPiece(new Queen(true), 3, 7);
        addPiece(new King(true), 4, 7);
        addPiece(new Bishop(true), 5, 7);
        addPiece(new Knight(true), 6, 7);
        addPiece(new Rook(true), 7, 7);
        for (int i = 0; i < 8; i++) {
            addPiece(new Pawn(true), i, 6);
        }

        // Place black pieces at the top
        addPiece(new Rook(false), 0, 0);
        addPiece(new Knight(false), 1, 0);
        addPiece(new Bishop(false), 2, 0);
        addPiece(new Queen(false), 3, 0);
        addPiece(new King(false), 4, 0);
        addPiece(new Bishop(false), 5, 0);
        addPiece(new Knight(false), 6, 0);
        addPiece(new Rook(false), 7, 0);
        for (int i = 0; i < 8; i++) {
            addPiece(new Pawn(false), i, 1);
        }

        whiteKing = (King) board[4][7];
        blackKing = (King) board[4][0];
        updatePossibleMoves();
    }

    // Generates a valid FEN string for UCI compatibility
    public String getFen() {
        StringBuilder fen = new StringBuilder();
        fen.append(getBoardState());
        fen.append(' ').append(isWhiteTurn() ? 'w' : 'b');
        fen.append(' ').append(getCastlingRights());
        fen.append(' ').append(possibleEnPassant);
        fen.append(' ').append(fiftyMoveCounter);
        fen.append(' ').append((moveCounter / 2) + 1);
        return fen.toString();
    }

    private String getCastlingRights() {
        StringBuilder rights = new StringBuilder();
        if (!whiteKing.isMoved()) {
            if (!isRookMoved(7, 7)) rights.append('K');
            if (!isRookMoved(0, 7)) rights.append('Q');
        }
        if (!blackKing.isMoved()) {
            if (!isRookMoved(7, 0)) rights.append('k');
            if (!isRookMoved(0, 0)) rights.append('q');
        }
        return rights.length() > 0 ? rights.toString() : "-";
    }

    private boolean isRookMoved(int x, int y) {
        Piece rook = board[x][y];
        return !(rook instanceof Rook) || ((Rook) rook).isMoved();
    }


    public void updateGameState(Piece piece, Piece capturedPiece, boolean doubleMove) {
        whiteTurn = !whiteTurn;
        moveCounter++;
        selectedPiece = null;

        switch (piece) {
            case Pawn pawn -> {
                pawn.setMoved(true);
                if (doubleMove) {
                    possibleEnPassant = SchachScreenPresenter.convertToChessNotation(piece.getX(),
                            piece.getY() + (pawn.isWhite() ? 1 : -1));
                }
                else {
                    possibleEnPassant = "-";
                }
            }

            case King king -> {
                king.setMoved(true);
                possibleEnPassant = "-";
            }
            case Rook rook -> {
                rook.setMoved(true);
                possibleEnPassant = "-";
            }
            default -> { possibleEnPassant = "-";
            }
        }

        updatePossibleMoves();

        if (piece instanceof Pawn || capturedPiece != null) {
            fiftyMoveCounter = 0;
        } else {
            fiftyMoveCounter++;
        }
        boardStates.add(getBoardState());
    }



    public String getBoardState() {
        StringBuilder boardState = new StringBuilder();
        for (int y = 0; y < 8; y++) {
            int emptyCount = 0;
            for (int x = 0; x < 8; x++) {
                Piece piece = board[x][y];
                if (piece == null) {
                    emptyCount++;
                } else {
                    if (emptyCount > 0) {
                        boardState.append(emptyCount);
                        emptyCount = 0;
                    }
                    boardState.append(piece.getFenNotation());
                }
            }
            if (emptyCount > 0) {
                boardState.append(emptyCount);
            }
            if (y < 7) {
                boardState.append('/');
            }
        }
        return boardState.toString();
    }

    private boolean checkThreefoldRepetition() {
        int count = 0;
        String currentState = getBoardState();
        for (String state : boardStates) {
            if (state.equals(currentState)) {
                count++;
            }
        }
        return count >= 3;
    }


    public void addPiece(Piece piece, int x, int y) {
        board[x][y] = piece;
        piece.setPosition(x, y);
        if(piece.isWhite()){
            whitePieces.add(piece);
        }else{
            blackPieces.add(piece);
        }
    }
    public void removePiece(Piece piece) {
        int x = piece.getX();
        int y = piece.getY();
        piece.setPosition(-1, -1);
        board[x][y] = null;
        if(piece.isWhite()){
            whitePieces.remove(piece);
        }else{
            blackPieces.remove(piece);
        }
    }

    //returns 0 if the move is invalid, 1 if the move is valid, 2 if the move is a valid capture
    public int check(int x, int y, Piece piece) {
        if (x < 0 || x >= 8 || y < 0 || y >= 8) {
            return 0; // Out of bounds
        }
        Piece targetPiece = board[x][y];
        if (targetPiece == null) {
            return 1; // Empty square
        } else if (targetPiece.isWhite() != piece.isWhite()) {
            return 2; // Opponent's piece
        } else {
            return 0; // Own piece
        }
    }

    public boolean isInCheck(King king, int i) {
        if (i == 1) {
            return canAttack(king.getX(), king.getY(), !king.isWhite());
        } else {
            return canAttack2(king.getX(), king.getY(), !king.isWhite());
        }
    }


    public boolean canAttack(int targetX, int targetY, boolean isWhite) {
        List<Piece> pieces = isWhite ? whitePieces : blackPieces;
        for (Piece piece : pieces) {
            int cell = piece.getPossibleMoves()[targetX][targetY];
            if (cell == 2) {
                System.out.println("Piece: " + piece.getFenNotation() + SchachScreenPresenter.convertToChessNotation(piece.getX(), piece.getY())
                        + " can attack: " + SchachScreenPresenter.convertToChessNotation(targetX, targetY));
                return true;
            }
        }
        return false;
    }

    public boolean isSquareSafe(int targetX, int targetY, boolean isWhite){
        List<Piece> pieces = isWhite ? whitePieces : blackPieces;
        for (Piece piece : pieces) {
            int cell = piece.getPossibleMoves()[targetX][targetY];
            if (cell == 2 || (cell == 1) && !(piece instanceof Pawn)) {
                System.out.println("Piece: " + piece.getFenNotation() + SchachScreenPresenter.convertToChessNotation(piece.getX(), piece.getY())
                        + " can attack: " + SchachScreenPresenter.convertToChessNotation(targetX, targetY));
                return false;
            }
        }
        return true;
    }

    public boolean canAttack2(int x, int y, boolean isWhite) {
        List<Piece> pieces = isWhite ? whitePieces : blackPieces;
        for (Piece piece : pieces) {
            if (piece.canAttackKing()) {
                return true;
            }
        }
        return false;
    }



    public void movePiece(int x, int y, Piece piece) {
        int[] originalPosition = new int[]{piece.getX(), piece.getY()};

        Piece capturedPiece = null;

        // Clear the target position
        if (board[x][y] != null) {
            capturedPiece = board[x][y];
            removePiece(capturedPiece);
        }

        // Execute the move
        addPiece(piece, x, y);
        board[originalPosition[0]][originalPosition[1]] = null;

        updateGameState(piece, capturedPiece,
                y - originalPosition[1] == 2 || y - originalPosition[1] == -2);
    }

    public boolean simMovePiece(int x, int y, Piece piece) {
        if (moveCounter < 3) return true;
        boolean isSafe = true;

        // Save original position
        int[] originalPosition = new int[]{piece.getX(), piece.getY()};
        Piece targetedPiece = board[x][y];

        // Simulate move
        addPiece(piece, x, y);
        board[originalPosition[0]][originalPosition[1]] = null;
        if (targetedPiece != null) {
            removePiece(targetedPiece);
        }

        // Update possible King attacks for opponent pieces after the Move of the piece
        updatePossibleMoves2(piece);

        // Check if the king is in check
        if (isInCheck(getKing(piece.isWhite()), 2)) {
            isSafe = false;
        }

        // Revert move
        addPiece(piece, originalPosition[0], originalPosition[1]);
        if (targetedPiece != null) {
            addPiece(targetedPiece, x, y);

        } else {
            board[x][y] = null;
        }

        return isSafe;
    }

    public boolean simEnPassant(Pawn pawn, boolean right) {
        int startX = pawn.getX();
        int startY = pawn.getY();
        int endX = pawn.getX() + (right ? 1 : -1);
        int endY = pawn.getY() + (pawn.isWhite() ? -1 : 1);

        Pawn capturedPawn = (Pawn) board[endX][endY - (pawn.isWhite() ? -1 : 1)];

        boolean isSafe = true;

        // Simulate move
        addPiece(pawn, endX, endY);
        board[startX][startY] = null;
        removePiece(capturedPawn);

        // Update possible King attacks for opponent pieces after the move of the piece
        updatePossibleMoves2(pawn);

        // Check if the king is in check
        if (isInCheck(getKing(pawn.isWhite()), 2)) {
            isSafe = false;
        }

        // Revert move
        addPiece(pawn, startX, startY);
        addPiece(capturedPawn, endX,endY - (pawn.isWhite() ? -1 : 1)); // Restore the en passant captured pawn
        board[endX][endY] = null;

        return isSafe;
    }

    public String checkForEnd(){
        //Check for fifty move rule
        if(fiftyMoveCounter >= 100){
            return "Fifty move rule";
        }
        if (checkThreefoldRepetition()) {
            return "Threefold repetition rule";
        }

        //Check for checkmate or stalemate
        boolean check = isInCheck(getKing(whiteTurn),1);
        boolean possibleMoveExists = false;

        for(int i = 0; i < 8; i++){
            for(int j = 0; j < 8; j++){
                Piece piece = board[i][j];
                if(piece != null && piece.isWhite() == whiteTurn){
                    for(int k = 0; k < 8; k++){
                        for(int l = 0; l < 8; l++){
                            if(piece.getPossibleMoves()[k][l] != 0){
                                possibleMoveExists = true;
                            }
                        }
                    }
                }
            }
        }
        if(!possibleMoveExists){
            if(check){
                return "Checkmate";
            }else{
                return "Stalemate";
            }
        }
        return "";
    }

    public void castle(King king, boolean kingside) {
        int y = king.getY();
        int x = king.getX();
        int rookX = kingside ? 7 : 0;
        int newKingX = kingside ? 6 : 2;
        int newRookX = kingside ? 5 : 3;

        // Move the king and rook to their new positions
        addPiece(king, newKingX, y);
        addPiece(board[rookX][y], newRookX, y);

        // Clear the old positions
        board[x][y] = null;
        board[rookX][y] = null;

        updateGameState(king, null, false);
    }

    public void enPassant(Pawn pawn, int x, int y) {
        int direction = pawn.isWhite() ? -1 : 1;
        board[x][y - direction] = null;
        movePiece(x, y, pawn);
    }

    public King getKing(boolean isWhite) {
        return isWhite ? whiteKing : blackKing;
    }

    public void updatePossibleMoves() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                Piece piece = board[i][j];
                if (piece != null) {
                    piece.setPossibleMoves();
                }
            }
        }

    }
    public void updatePossibleMoves2(Piece exception) {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                Piece piece = board[i][j];
                if (piece != null) {
                    if (piece.isWhite() != exception.isWhite()) {
                        piece.updateCanAttackKing2();
                    }
                }
            }
        }
    }


    public Piece getPiece(int x, int y) {
        if(x > 7 || x < 0 || y > 7 || y < 0) return null;
        return board[x][y];
    }
    public void setPiece(int x, int y, Piece piece) {
        board[x][y] = piece;
    }

    public Piece promotePawn(Pawn pawn, String pieceType, int newX, int newY) {
        Piece capturedPiece = board[newX][newY];

        Piece newPiece = switch (pieceType) {
            case "Queen" -> new Queen(pawn.isWhite());
            case "Rook" -> new Rook(pawn.isWhite());
            case "Bishop" -> new Bishop(pawn.isWhite());
            case "Knight" -> new Knight(pawn.isWhite());
            default -> null;
        };
        if (newPiece != null) {
            removePiece(pawn);
            addPiece(newPiece, newX, newY);
        }
        updateGameState(pawn, capturedPiece, false);

        return  newPiece;
    }
}

Like i have said i’ve tried to check if the Move is safe with an additional variable and Methods but the way its programmed right now is very unclear/confusing/hard to work with.
It did work at some point but the minute i started to add or change something it always completly broke.

New contributor

katsuro is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật