> Java > java지도 시간 > 본문

Java에서 온라인 주사위 놀이를 구현하는 방법

WBOY
풀어 주다: 2023-04-19 23:40:12
앞으로
876명이 탐색했습니다.

클라이언트 실행 효과는 다음과 같습니다.

Java에서 온라인 주사위 놀이를 구현하는 방법

코드 패키지의 구조는 다음과 같습니다.

Java에서 온라인 주사위 놀이를 구현하는 방법

그럼 이 클래스들이 차례로 완성하는 기능에 대해 이야기하겠습니다.

미디어 패키지

미디어 패키지: 메인 주사위 놀이의 배경 이미지와 음악 연주 수업 및 음악 콘텐츠가 포함되어 있습니다

룸메이트에게 음악 연주 수업을 받았기 때문에 잘 이해가 되지 않아서 살펴보았습니다. .wav 접미사만 처리할 수 있는 애플릿을 사용하여 수행되었습니다.

Net 패키지

Net 패키지: 두 개의 클래스가 포함되어 있습니다. 주의 깊게 보면 클라이언트에 기본 메서드가 없다는 것을 알 수 있습니다. 클라이언트에는 실제로 서버와 통신하기 위한 소켓이 포함되어 있으며 여기에는 몇 가지 읽기 및 쓰기 방법이 포함되어 있습니다. 서버 측에서는 스레드 풀 방식을 사용하여 클라이언트 요청을 처리합니다(스레드 풀에 대해 잘 모르고 멀티스레딩과 비슷한 느낌입니다).

View 패키지

View 패키지: ChessBoard, ChessPanel, Pieces 및 WhoWin 클래스의 4개 클래스를 포함합니다.

ChessBoard는 Main 메소드를 포함하는 JFrame입니다. 이 JFrame에 추가됩니다.

ChessPanel은 19*19 체스판 선 그리기, 배경 이미지 로드, 서버로부터 지속적으로 메시지 수신 및 처리(패널에 체스 말 추가), 클릭할 때마다 체스 말 추가 처리 등의 작업을 완료하는 체스판 패널입니다. . 패널로 이동하여 체스 말을 서버로 보내고 결과를 결정한 후 프롬프트 메시지를 보냅니다.

Pieces는 색상, 조각 반경, 체스 조각 위치 및 명령과 같은 속성을 포함하는 체스 조각입니다(이전 명령 패널과 함께 사용되며 기본값은 전송입니다).

WhoWin은 누가 패하고 누가 승리하는지를 결정하는 주사위 놀이의 일부입니다. 모든 다음 단계에는 판단이 필요합니다.

음악 연주 수업:

package Media.Music;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
public class PlayMusic {
    private Clip clip;
    public PlayMusic(String filePath) throws LineUnavailableException, UnsupportedAudioFileException, IOException {
        File file = new File(filePath);
        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
        clip = AudioSystem.getClip();
        clip.open(audioInputStream);
    }
 
    public void play() {
        clip.setFramePosition(1);
        clip.start();
    }
 
    public void loop() {
        clip.loop(Clip.LOOP_CONTINUOUSLY);
    }
    public void stop() {
        clip.stop();
    }
}
로그인 후 복사

TcpClient:

package net;
import view.Pieces;
import java.io.*;
import java.net.Socket;
public class TcpClient{
    private Socket socket;
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    public TcpClient(Socket socket,ObjectInputStream ois,ObjectOutputStream oos){
        this.socket= socket;
        this.ois = ois;
        this.oos = oos;
    }
    public Socket getSocket() {
        return socket;
    }
 
    public void setSocket(Socket socket) {
        this.socket = socket;
    }
 
    public ObjectInputStream getOis() {
        return ois;
    }
 
    public void setOis(ObjectInputStream ois) {
        this.ois = ois;
    }
 
    public ObjectOutputStream getOos() {
        return oos;
    }
 
    public void setOos(ObjectOutputStream oos) {
        this.oos = oos;
    }
 
    public void send(Pieces pieces) throws IOException {
        oos.writeObject(pieces);
        System.out.println(socket+"向服务器发送消息");
    }
    public Pieces accept() throws IOException, ClassNotFoundException {
        Pieces pieces = (Pieces) ois.readObject();
        System.out.println(socket+"从服务器读取消息");
        return pieces;
    }
    public void close(){
        ;
    }
}
로그인 후 복사

TcpServer:

package net;
import view.Pieces;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpServer {
    public static void main(String[] args) {
        // 保存客户端处理的线程
        ArrayList<UserThread> userList = new ArrayList<>();
        // 固定大小的线程池只支持两个线程,用来处理客户端
        ExecutorService es = Executors.newFixedThreadPool(2);
        try {
            ServerSocket server = new ServerSocket(10086);
            System.out.println("服务器已经启动,正在等待客户端连接......");
            while (true) {
                //接收客户端的Socket,如果没有客户端连接就一直卡在这里
                Socket socket = server.accept();
                // 每来一个用户就创建一个线程
                UserThread user = new UserThread(socket, userList);
                // 开启线程
                es.execute(user);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
class UserThread implements Runnable {
    private Socket socket = null;
    private static ArrayList<UserThread> list; // 客户端线程集合
    private ObjectOutputStream oos;
    private ObjectInputStream ois;
    private boolean flag = true;// 标记
 
    public UserThread(Socket socket, ArrayList<UserThread> list) {
        this.socket = socket;
        this.list = list;
        list.add(this); // 把当前线程加入list中
    }
 
    @Override
    public void run() {
        UserThread user = null;
        try {
            System.out.println("客户端:" + socket.getInetAddress().getHostAddress() + "已经连接");
            ois = new ObjectInputStream(socket.getInputStream());
            oos = new ObjectOutputStream(socket.getOutputStream());
            while(true){
                Pieces pieces = (Pieces) ois.readObject(); // 客户端发给服务端的消息,把他写到其它套接字中去
                int size = list.size();
                for (int i = 0; i < size; i++) {
                    user = list.get(i);
                    if (user.socket != socket) {
                        user.oos.writeObject(pieces);
                        System.out.println("从"+socket+"向"+user.socket+"发送消息");
                    }
                }
            }
        } catch(SocketException e){ //  todo 客户端掉线后,移除客户端。没想好{1.从客户端列表移除当前元素,关闭当前:socket,通知另一方:这一方已经掉线,然后关闭这一方的socket}
            try {
                int i = list.size();
                if (i ==2){
                    list.remove(user);
                    System.out.println("已经删除了一个客户端");
                    list.get(0).oos.writeObject(new Pieces("对方掉线"));
                }else if (i==1){
                    list.remove(0);
                    System.out.println("又移除了另一个客户端");
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
로그인 후 복사

ChessBoard:

/*
* 1.变量值不变的问题
* 2.输入输出流先后顺序的问题(socket阻塞)
* 3.socket 短连接不关闭输入输出流,为何看起来就像长连接一样(长短连接的区别是什么)
* */
// todo 一个提示框
package view;
import Media.Music.PlayMusic;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
public class ChessBoard extends JFrame implements ActionListener {
    private JButton PlayMusic = new JButton("播放音乐");
    private ChessPanel chessPanel;
    private Panel CommandPanel = new Panel();
    private JButton reStart = new JButton("重新开始");
    private JButton fail = new JButton("认输");
    private JButton Regret = new JButton("悔棋");
    private String command=null; // 触发按钮后发送的命令
    private PlayMusic music = null;
    private int count = 1;
    //  todo 静态语句块
    {
        try {
            music = new PlayMusic("./src/Media/Music/bg3.wav");
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public ChessBoard() {
        this.setTitle("欢乐五子棋");
        chessPanel = new ChessPanel();
        this.add(chessPanel,BorderLayout.CENTER);
        reStart.addActionListener(this);
        fail .addActionListener(this);
        Regret.addActionListener(this);
        PlayMusic.addActionListener(this);
        CommandPanel.add(reStart);
        CommandPanel.add(fail);
        CommandPanel.add(Regret);
        CommandPanel.add(PlayMusic);
        this.add(CommandPanel,BorderLayout.SOUTH);
        this.setBounds(10, 10, 800, 800);
        this.setVisible(true);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        ChessBoard Board = new ChessBoard();
 
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getSource()==reStart){
            command ="重新开始";
            chessPanel.canPlay = true;
        }else if(e.getSource()==fail){
            command ="认输";
            JOptionPane.showMessageDialog(chessPanel,"It&#39;s a pity,you have fail the game!");
            chessPanel.canPlay = false;
        }else if (e.getSource()==Regret){
            command ="悔棋";
        }else if (e.getSource()==PlayMusic){
            // todo 播放音乐,单数次播放;
            if (count%2==1){
                music.play();
            }else {
                music.stop();
            }
            count++;
            command = null;
        }
        if(command!=null){
            Pieces pieces = new Pieces(command);
            try {
                this.chessPanel.client.send(pieces);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
 
    }
}
로그인 후 복사

ChessPanel:

package view;
// 五子棋面板,就是在这里面画图。
// todo 背景图片 ,也许一个背景音乐
import net.TcpClient;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
public class ChessPanel extends JPanel implements MouseListener{
    // TODO 从服务器接收来的棋子 ,值不变有问题
//   Pieces accept_pieces = new Pieces();
//   Pieces send_pieces = new Pieces();
    whoWin ifWin =new whoWin() ; // 是否胜利
    TcpClient client = null; // 客户端
    boolean canPlay = true; // 是否能继续玩
    boolean isBlack = true; // 是否黑子,黑1,白2
    ArrayList<Pieces> allPieces = new ArrayList<>(); // 存储棋子对象,就是通过这个画图的
    int [][] allChess = new int[19][19];
    int PanelWidth;
    int PanelHeight;
    int width = 600;
    int height = 600;
    int temp = width / 18;
    int xbase,ybase;
    Image image = Toolkit.getDefaultToolkit().getImage("./src/Media/bg.jpeg"); // "./"表示当前项目下
    public ChessPanel(){
        this.addMouseListener(this);
        try {
            Socket socket = new Socket("172.27.29.190", 10086);
            //TODO 构建输出输入流,输入输出流问题,先输出后输入
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            client = new TcpClient(socket,ois,oos);
            new Thread(new getMessage()).start(); // 开启读取的线程
        } catch (ConnectException e){
            System.out.println("服务器拒绝连接!");
        } catch (IOException e) {
            e.printStackTrace();
        }catch(Exception e ){
            e.printStackTrace();
        }
    }
    // 画图部分
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        PanelWidth = this.getSize().width; // 这两步骤
        PanelHeight = this.getSize().height;
        xbase = (PanelWidth - width) / 2;
        ybase = (PanelHeight - height) / 2;
        Graphics2D g2d = (Graphics2D) g;
//        this.setBackground(new Color(246, 186, 114));
        g2d.drawImage(image,0,0,this.getWidth(),this.getHeight(),this);
        int x1, y1, x2, y2;
        // 画线
        for (int i = 0; i < 19; i++) {
            if (i == 0 || i == 18) {
                g2d.setStroke(new BasicStroke(3.0f));
            } else g2d.setStroke(new BasicStroke(1.0f));
            x1 = xbase + temp * i;
            y1 = ybase;
            y2 = ybase + 18 * temp;
            g2d.drawLine(x1, y1, x1, y2);
            x1 = xbase;
            y1 = ybase + temp * i;
            x2 = xbase + temp * 18;
            g2d.drawLine(x1, y1, x2, y1);
        }
        // 开始画棋子
        int radius ;
        int xPos,yPos;
        Iterator it = allPieces.iterator(); // 迭代器遍历arraylist
        while(it.hasNext()){
            Pieces pieces = (Pieces) it.next();
            radius  = pieces.getRadius();
            xPos = pieces.getxPos();
            yPos = pieces.getyPos();
            System.out.println(pieces.getColor()+","+pieces.getxPos()+","+pieces.getyPos());
            if (pieces.getColor() == 1){
                g2d.setColor(Color.black);
                g2d.fillOval(xPos*temp+xbase-radius/2,yPos*temp+ybase-radius/2,radius,radius);
            }
            else if (pieces.getColor() == 2) {
                g2d.setColor(Color.white);
                g2d.fillOval(xPos * temp + xbase - radius / 2, yPos * temp + ybase - radius / 2, radius, radius);
            }
        }
 
    }
 
    @Override
    public void mousePressed(MouseEvent e) {
        int x ,y ;
        if (canPlay) {
            x = e.getX();
            y = e.getY();
            //  判断鼠标点击位置
            if (x >= xbase & x <= (xbase + 18 * temp) & y >= ybase & y < (ybase + 18 * temp)) {
                // 判断是不是下在空的位置
                int tempX = (x - xbase) / temp, tempY = (y - ybase) / temp;
                // todo 这里是关键判断这点坐标的数组下标是什么
                if ((x - xbase) % temp > temp / 2) {
                    x = tempX + 1;
                } else
                    x = tempX;
                if ((y - ybase) % temp > temp / 2)
                    y = tempY + 1;
                else
                    y = tempY;
                // 先判断有没有棋子,处理没有棋子的情况
                if (allChess[x][y] != 0) {
                       JOptionPane.showMessageDialog(this, "这里有棋子了");
                } else {
                    Pieces send_pieces = new Pieces();
                    send_pieces.setxPos(x);
                    send_pieces.setyPos(y);
                    if (isBlack){
                        send_pieces.setColor(1);
                        allChess[x][y] = 1;
                        isBlack = false;
                    }
                    else{
                        send_pieces.setColor(2);
                        allChess[x][y]=2;
                        isBlack = true;
                    }
                    allPieces.add(send_pieces); // 向棋子队列加入当前棋子
                    canPlay = false;// canPlay在true情况下, 点击一次后不能点击了
                    this.repaint();
                    ifWin = new whoWin(allChess,x,y);
                    // 如果赢了,就不能玩了,并且给出提示消息,你赢了;
                    if(ifWin.isWin()){
                        canPlay = false;
                        JOptionPane.showMessageDialog(this,"Congratulations you have won tha game !");
                    }
                    try {
                        if (client!=null){
                            client.send(send_pieces);
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
 
        }
    }
 
    // 读取来自服务器端的信息
    class getMessage implements Runnable{
        private boolean flag = true;
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
 
        @Override
        public void run() {
            // 循环读
            while(flag){
                if(client!=null){
                    try {
                        Pieces accept_pieces = client.accept();
                        String command = accept_pieces.getCommand();
                        int color = accept_pieces.getColor();
                        switch (command){
                            case "发送":{
                                canPlay = true;
                                if (color == 1){
                                    isBlack = false;//对方为黑我为白
                                }else{
                                    isBlack = true;
                                }
                                allPieces.add(accept_pieces);
                                allChess[accept_pieces.getxPos()][accept_pieces.getyPos()]= accept_pieces.getColor();
                                ChessPanel.this.repaint();
                                ifWin.setY(accept_pieces.getyPos());
                                ifWin.setX(accept_pieces.getxPos());
                                ifWin.setAllChess(allChess);
                                if(ifWin.isWin()){
                                    canPlay = false;
                                    JOptionPane.showMessageDialog(ChessPanel.this,"It&#39;s a pity you have fail the game!");
                                }
                                break;
                            }
                            case "悔棋":{
                                int i = JOptionPane.showConfirmDialog(ChessPanel.this,"对方请求悔棋,是否同意!");
                                // yes 0,no 1,cancel 2,closed -1
                                Pieces pieces = new Pieces();
                                if (i == 0){
                                    // 同意悔棋:1.同意对方悔棋
                                    pieces.setCommand("同意悔棋");
                                    // arraylist 去除末尾的两个值,对应allChess置0
                                    int size = allPieces.size();
                                    for (int j = 1;j<=2;j++){
                                        allChess[allPieces.get(size-j).getxPos()][allPieces.get(size-j).getyPos()]=0;
                                        allPieces.remove(size-j);
                                    }
                                    ChessPanel.this.repaint();
                                }else if(i==1){
                                    pieces.setCommand("不同意悔棋");
                                }
                                client.send(pieces);
                                break;
                            }
                            case "认输":{ // 不能继续玩下去,你已经胜利
                                JOptionPane.showMessageDialog(ChessPanel.this,"对方认输");
                                canPlay = false;
                                JOptionPane.showMessageDialog(ChessPanel.this,"Congratulations you have won tha game !");
                                break;
                            }
                            case "重新开始":{ // 是否同意重新开始
                               int i = JOptionPane.showConfirmDialog(ChessPanel.this,"对方请求重新开始,是否同意");
                                Pieces pieces = new Pieces();
                                if(i == 0){// allChess 和 allPieces全部置0;
                                    pieces.setCommand("同意重新开始");
                                    int size = allPieces.size();
                                    System.out.println("arraylist 长度:"+size);
                                    for (int j = 0;j<size;j++){// 移除队首元素
                                        allChess[allPieces.get(0).getxPos()][allPieces.get(0).getyPos()] = 0;
                                        allPieces.remove(0);
                                    }
                                    canPlay = true;
                                    ChessPanel.this.repaint();
                                }else if (i ==1){
                                    pieces.setCommand("不同意重新开始");
                                }
                                client.send(pieces);
                                break;
                            }
                            case "同意悔棋":{// allpieces 和 allchess 回退
                                JOptionPane.showMessageDialog(ChessPanel.this,"对方同意悔棋");
                                int size = allPieces.size();
                                for (int j = 1;j<=2;j++){
                                    allChess[allPieces.get(size-j).getxPos()][allPieces.get(size-j).getyPos()]=0;
                                    allPieces.remove(size-j);
                                }
                                ChessPanel.this.repaint();
                                break;
                            }
                            case "不同意悔棋":{
                                JOptionPane.showMessageDialog(ChessPanel.this,"对方不同意悔棋");
                                break;
                            }
 
                            case "同意重新开始":{ // 全部置0,调用repaint 方法
                                JOptionPane.showMessageDialog(ChessPanel.this,"对方同意重新开始");
                                int size = allPieces.size();
                                for (int j = 0;j<size;j++){ //  todo 移除队首元素arraylist 长度改变;序列也发生改变
                                    allChess[allPieces.get(0).getxPos()][allPieces.get(0).getyPos()] = 0;
                                    allPieces.remove(0);
                                }
                                canPlay = true;
                                ChessPanel.this.repaint();
                                break;
                            }
                            case "不同意重新开始":{
                                JOptionPane.showMessageDialog(ChessPanel.this,"对方不同意重新开始");
                                break;
                            }
                            case "对方掉线":{ // 对方已经掉线
                                JOptionPane.showMessageDialog(ChessPanel.this,"不好意思,对方已经掉线!");
                                canPlay = false;
                                break;
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
 
    @Override
    public void mouseClicked(MouseEvent e) {
 
    }
    @Override
    public void mouseReleased(MouseEvent e) {
 
    }
 
    @Override
    public void mouseEntered(MouseEvent e) {
 
    }
 
    @Override
    public void mouseExited(MouseEvent e) {
 
    }
 
}
로그인 후 복사

Pieces:

package view;
 
import java.io.Serializable;
// 存储棋子的相关信息
public class Pieces implements Serializable {
    private int radius = 16;
    private int color = 0;
    private int xPos ;
    private int yPos;
    private String command = "发送";
    public String getCommand() {
        return command;
    }
 
    public void setCommand(String command) {
        this.command = command;
    }
 
    public Pieces(int color, int xPos, int yPos){
        this.color = color;
        this.xPos = xPos;
        this.yPos = yPos;
    }
    public Pieces(){
 
    }
    public Pieces(String command){
        this.command = command;
    }
    public int getRadius() {
        return radius;
    }
 
    public void setRadius(int radius) {
        this.radius = radius;
    }
 
    public int getColor() {
        return color;
    }
 
    public void setColor(int color) {
        this.color = color;
    }
 
    public int getxPos() {
        return xPos;
    }
 
    public void setxPos(int xPos) {
        this.xPos = xPos;
    }
 
    public int getyPos() {
        return yPos;
    }
 
    public void setyPos(int yPos) {
        this.yPos = yPos;
    }
}
로그인 후 복사

WhoWin:

package view;
public class whoWin { // 判断是谁赢了
    private int allChess[][] = new int[19][19];
    private int x = 0;
    private int y = 0;
    public whoWin(){
 
    }
    public whoWin(int allChess[][],int x,int y){
        this.allChess = allChess;
        this.x = x;
        this.y = y;
    }
 
    public void setAllChess(int allChess[][]){
        this.allChess = allChess;
    }
    public void setX(int x){
        this.x = x;
    }
    public void setY(int y ){
        this.y = y;
    }
    public boolean isWin() {
        int color = allChess[x][y];
        int count;
        count = this.Count(1, 0, color); // 对横方向的判断
        if (count >= 5) {
            return true;
        } else {
            count = this.Count(0, 1, color); // 对竖方向的判断
            if (count >= 5) {
                return true;
            } else {
                count = this.Count(1, 1, color); // 对左上方向的判断
                if (count >= 5)
                    return true;
                else {
                    count = this.Count(1, -1, color); // 对右上方向的判断
                    if (count >= 5)
                        return true;
                }
            }
        }
        return false;
    }
    private int Count(int xChange, int yChange, int color) {
        int count = 1;
        int tempX = xChange, tempY = yChange;
        while (color == allChess[x + xChange][y + yChange]) {
            count++;
            if (xChange != 0) {
                xChange++;
            }
            if (yChange != 0) {
                if (yChange > 0)
                    yChange++;
                else
                    yChange--;
            }
        }
        xChange = tempX;
        yChange = tempY;
        while (color == allChess[x - xChange][y - yChange]) {
            count++;
            if (xChange != 0)
                xChange++;
            if (yChange != 0) {
                if (yChange > 0)
                    yChange++;
                else
                    yChange--;
            }
        }
        return count;
    }
}
로그인 후 복사

위 내용은 Java에서 온라인 주사위 놀이를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿