https://www.acmicpc.net/problem/1012

 

1012번: 유기농 배추

차세대 영농인 한나는 강원도 고랭지에서 유기농 배추를 재배하기로 하였다. 농약을 쓰지 않고 배추를 재배하려면 배추를 해충으로부터 보호하는 것이 중요하기 때문에, 한나는 해충 방지에 

www.acmicpc.net

문제

차세대 영농인 한나는 강원도 고랭지에서 유기농 배추를 재배하기로 하였다. 농약을 쓰지 않고 배추를 재배하려면 배추를 해충으로부터 보호하는 것이 중요하기 때문에, 한나는 해충 방지에 효과적인 배추흰지렁이를 구입하기로 결심한다. 이 지렁이는 배추근처에 서식하며 해충을 잡아 먹음으로써 배추를 보호한다. 특히, 어떤 배추에 배추흰지렁이가 한 마리라도 살고 있으면 이 지렁이는 인접한 다른 배추로 이동할 수 있어, 그 배추들 역시 해충으로부터 보호받을 수 있다. 한 배추의 상하좌우 네 방향에 다른 배추가 위치한 경우에 서로 인접해있는 것이다.

한나가 배추를 재배하는 땅은 고르지 못해서 배추를 군데군데 심어 놓았다. 배추들이 모여있는 곳에는 배추흰지렁이가 한 마리만 있으면 되므로 서로 인접해있는 배추들이 몇 군데에 퍼져있는지 조사하면 총 몇 마리의 지렁이가 필요한지 알 수 있다. 예를 들어 배추밭이 아래와 같이 구성되어 있으면 최소 5마리의 배추흰지렁이가 필요하다. 0은 배추가 심어져 있지 않은 땅이고, 1은 배추가 심어져 있는 땅을 나타낸다.

1 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 1 1 0 0 0 1 1 1
0 0 0 0 1 0 0 1 1 1

입력

입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트 케이스에 대해 첫째 줄에는 배추를 심은 배추밭의 가로길이 M(1 ≤ M ≤ 50)과 세로길이 N(1 ≤ N ≤ 50), 그리고 배추가 심어져 있는 위치의 개수 K(1 ≤ K ≤ 2500)이 주어진다. 그 다음 K줄에는 배추의 위치 X(0 ≤ X ≤ M-1), Y(0 ≤ Y ≤ N-1)가 주어진다. 두 배추의 위치가 같은 경우는 없다.

출력

각 테스트 케이스에 대해 필요한 최소의 배추흰지렁이 마리 수를 출력한다.

 
2

 


풀이

  • 배추가 심어져 있는 땅: 1, 배추가 심어져 있지 않은 땅: 0
  • 상하좌우 인접한 배추가 존재한다면, 지렁이 이동 가능 -> 1인 좌표의 상하좌우를 탐색하여 값이 1인지 확인
  • 방문하지 않은 그래프 값이 1일 때 너비 우선 탐색(BFS) 수행 후 지렁이 개수 증가
  • deque.popleft(): 데크의 가장 앞쪽에 있는 값을 빼내는 기능 수행

코드

from collections import deque

# 상하좌우
dx = [0, 0, 1, -1]
dy = [1, -1 , 0, 0]

# 테스트케이스 개수
t = int(input())

def bfs(x, y):
    queue = deque()
    queue.append((x, y))    # 해당 좌표 deque에 저장
    visited[x][y] = True     # 중복 방문 방지를 위해 방문 시 True 부여

    # 데크에 제일 왼쪽에 있는 좌표를 꺼내 상하좌우 탐색
    # 배추가 있고, 방문하지 않은 곳이면 데크에 저장하고 방문 처리
    while queue:
        x, y = queue.popleft()  
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if nx < 0 or nx >= n or ny < 0 or ny >= m:
                continue
            if board[nx][ny] and not visited[nx][ny]:   
                queue.append((nx, ny))
                visited[nx][ny] = True

def dfs(x, y):
    queue = deque()
    queue.append((x, y))    # 해당 좌표 deque에 저장
    visited[x][y] = True     # 중복 방문 방지를 위해 방문 시 True 부여

    # 데크에 제일 마지막에 있는 좌표를 꺼내 상하좌우 탐색
    # 배추가 있고, 방문하지 않은 곳이면 데크에 저장하고 방문 처리
    while queue:
        x, y = queue.pop()  
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if nx < 0 or nx >= m or ny < 0 or ny >= n:
                continue
            if board[nx][ny] and not visited[nx][ny]:   
                queue.append((nx, ny))
                visited[nx][ny] = True

if __name__ == "__main__":
    for _ in range(t):
        m, n, k = map(int, input().split())
        board = [[0] * m for _ in range(n)]         # 배추밭 배열
        visited = [[False] * m for _ in range(n)]   # 방문 처리 배열
        # 배추 위치 받아오기
        for _ in range(k):
            x, y = map(int, input().split())
            board[y][x] = 1

        cnt = 0     # 지렁이 수
        # 배열 탐색하여 배추 존재하고, 방문하지 않았으면 BFS 실행 후 지렁이 수 증가
        for i in range(n):
            for j in range(m):
                if board[i][j] == 1 and not visited[i][j]:
                    bfs(i, j)
                    # dfs(i, j)
                    cnt += 1
        print(cnt)

 

CarController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarController : MonoBehaviour
{
    [SerializeField]
    float speed = 0;
    Vector2 startPos;

    private void Awake()
    {
        Debug.Log("Awake");
    }

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("start");
    }

    // Update is called once per frame
    void Update()
    {
        // 버튼을 누르면
        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log("down");
            this.startPos = Input.mousePosition;
            Debug.LogFormat("startPos: {0}", this.startPos);
            this.speed = 0.2f;
        }
        else if (Input.GetMouseButtonUp(0))
        {
            Debug.Log("Up");
            Vector2 endPos = Input.mousePosition;
            Debug.LogFormat("endPos: {0}", endPos);
            float length = endPos.x - this.startPos.x;
            Debug.LogFormat("length: {0}", length);

            this.speed = length / 500f;
            Debug.LogFormat("speed: {0}", this.speed);

            this.GetComponent<AudioSource>().Play();

            //AudioSource audio = this.GetComponent<AudioSource>();
            //audio.Play();

        }
        // 이동
        this.transform.Translate(this.speed, 0, 0);

        // 감속
        this.speed *= 0.96f;
    }
}

GameDirector.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameDirector : MonoBehaviour
{
    public GameObject car;
    [SerializeField]
    private GameObject flag;
    [SerializeField]
    private GameObject distance;

    // Start is called before the first frame update
    void Start()
    {
        //this.car = GameObject.Find("car");
        //this.flag = GameObject.Find("flag");
        //this.distance = GameObject.Find("Distance");

        Debug.LogFormat("car: {0}", this.car);
        Debug.LogFormat("flag: {0}", this.flag);
        Debug.LogFormat("distance: {0}", this.distance);
    }

    // Update is called once per frame
    void Update()
    {
        float length = this.flag.transform.position.x - this.car.transform.position.x;
        Text textDistance = this.distance.GetComponent<Text>();
        textDistance.text = string.Format("목표 지점까지 {0}m", length.ToString("F2"));
    }
}

car Component
GameDirector Component

 

실행 결과

GameDirector2.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameDirector2 : MonoBehaviour
{
    public GameObject shuriken;
    [SerializeField]
    private GameObject ground;
    [SerializeField]
    private GameObject dt;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float len = this.ground.transform.position.y - this.shuriken.transform.position.y;
        Text textDt = this.dt.GetComponent<Text>();
        textDt.text = string.Format("{0}m", len.ToString("F2"));
        if (len < 0)
        {
            textDt.text = "성공";
        }
    }
}

ShuridenController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShurikenController : MonoBehaviour
{
    [SerializeField]
    private float speed;
    [SerializeField]
    private float rotSpeed = 0;
    [SerializeField]
    private Vector2 startPos;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 마우스가 클릭한 곳의 좌표를 얻어옴
            this.startPos = Input.mousePosition;
            this.speed = 0.1f;
            this.rotSpeed = 15;
        }
        else if (Input.GetMouseButtonUp(0))
        {
            Vector2 endPos = Input.mousePosition;
            var length = endPos.y - this.startPos.y;

            // 감속
            this.speed = length / 500f;
        }

        this.transform.Rotate(0, 0, this.rotSpeed);
        this.transform.position += new Vector3(0, this.speed, 0);
        this.rotSpeed *= 0.96f;

    }
}

shuriken Component
GameDirector Component

App.cs

exam.zip
0.06MB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace exam
{
    public class App
    {
        // 생성자
        public App()
        {
            //this.Solution1();
            //this.Solution2();
            //this.Solution3();
            //this.Solution4();
            //this.Solution5();
            //this.Solution6();
            //this.Solution7();
            //this.Solution8();
            //this.Solution9();
        }

        public void Solution1()
        {
            int[] arr = new int[] { 20, 10, 35, 30, 7 };
            int min = arr[0];
            int max = arr[0];


            for (int i = 0; i < arr.Length; i++)
            {
                if (arr[i] > max)
                    max = arr[i];

                if (arr[i] < min)
                    min = arr[i];
            }

            Console.WriteLine("{0}, {1}", max, min);
            Console.WriteLine();
        }

        public void Solution2()
        {
            int h = 23;
            int m = 40;

            m -= 45;
            if (m < 0)
            {
                m += 60;
                h -= 1;
                if (h == 0) h = 24;
            }

            Console.WriteLine("{0}시 {1}분", h, m);
            Console.WriteLine();
        }

        public void Solution3()
        {
            Queue<int> queue = new Queue<int>();
            for (int i = 1; i <= 8; i++)
            {
                queue.Enqueue(i);
            }

            while(queue.Count > 1)
            {
                queue.Dequeue();
                int peek = queue.Peek();
                queue.Enqueue(peek);
                queue.Dequeue();
            }

            Console.WriteLine(queue.Peek());
            Console.WriteLine();
        }

        public void Solution4()
        {
            string s1 = "So when I die(the (first) I will see in (heaven) is a score list)";
            string s2 = "A rope may form )( a trail in a maze";
            string s3 = "Help( I(m being held prisoner) in a fortune cookie factory)";

            Stack<string> stack = new Stack<string>();
        }

        public void Solution5()
        {
            Console.Write("입력: ");
            int num = Convert.ToInt32(Console.ReadLine());
            if (num > 10)
            {
                Console.WriteLine("다시 입력하세요.");
            }
            else
            {
                Console.Write("출력: {0}", this.Factorial(num));
                Console.WriteLine();
            }

        }

        public void Solution6()
        {
            var graph = new Graph<int>();
            var one = graph.AddVertex(1);
            var two = graph.AddVertex(2);
            var three = graph.AddVertex(3);
            var four = graph.AddVertex(4);
            var five = graph.AddVertex(5);

            graph.AddEdge(one, two);
            graph.AddEdge(one, four);
            graph.AddEdge(two, three);
            graph.AddEdge(two, four);
            graph.AddEdge(four, five);

            graph.BFS();
            //graph.DebugPrintGraph();
        }

        public void Solution7()
        {
            var bt = new BinaryTree<string>("A");

            bt.Root.left = new BinaryTreeNode<string>("B");
            bt.Root.left.left = new BinaryTreeNode<string>("D");
            bt.Root.right = new BinaryTreeNode<string>("C");
            bt.Root.right.left = new BinaryTreeNode<string>("E");
            bt.Root.right.right = new BinaryTreeNode<string>("F");
            bt.Root.right.right.right = new BinaryTreeNode<string>("G");

            Console.Write("전위 순회한 결과: ");
            bt.PreorderTraversal();
            Console.WriteLine();
            Console.Write("중위 순회한 결과: ");
            bt.InorderTraversal();
            Console.WriteLine();
            Console.Write("후위 순회한 결과: ");
            bt.PostorderTraversal();
            Console.WriteLine();
        }

        public void Solution8()
        {
            string p = "I am happy today";
            string[] words = p.Split(' ');
            foreach(var word in words)
            {
                char[] reverse = word.ToCharArray();
                Array.Reverse(reverse);
                Console.Write(reverse);
                Console.Write(" ");
            }
            Console.WriteLine();
        }

        public void Solution9()
        {
            string p = "It over 2000 years old";
            string str = p.Replace(" ", String.Empty);
            char[] result = str.ToCharArray();
            int count = 0;
            foreach(var c in result)
            {
                count++;
            }
            Console.WriteLine(count);
        }

        private int Factorial(int n)
        {
            if (n == 0 || n == 1) return 1;
            return n * Factorial(n - 1);
        }
    }
}

BinaryTree.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace exam
{
    public class BinaryTree<T>
    {
        public BinaryTreeNode<T> Root { get; private set; }

        public BinaryTree(T data)
        {
            this.Root = new BinaryTreeNode<T>(data);
        }

        public void PreorderTraversal()
        {
            this.PreorderTraversal(this.Root);
        }

        private void PreorderTraversal(BinaryTreeNode<T> node)
        {
            if (node == null) return;
            Console.Write(node.data);
            this.PreorderTraversal(node.left);
            this.PreorderTraversal(node.right);
        }

        public void InorderTraversal()
        {
            this.InorderTraversal(this.Root);
        }

        private void InorderTraversal(BinaryTreeNode<T> node)
        {
            if (node == null) return;
            this.InorderTraversal(node.left);
            Console.Write(node.data);
            this.InorderTraversal(node.right);
        }

        public void PostorderTraversal()
        {
            this.PostorderTraversal(this.Root);
        }

        private void PostorderTraversal(BinaryTreeNode<T> node)
        {
            if (node == null) return;
            this.PostorderTraversal(node.left);
            this.PostorderTraversal(node.right);
            Console.Write(node.data);
        }

        public void PreorderIterative()
        {
            if (this.Root == null) return;
            var stack = new Stack<BinaryTreeNode<T>>();

            // 루트를 스택에 저장
            stack.Push(this.Root);

            while (stack.Count > 0)
            {
                // 스택에 저장된 노드 가져오기
                var node = stack.Pop();

                // 방문
                Console.WriteLine("{0} ", node.data);

                // 오른쪽 노드를 스택에 저장
                if (node.right != null)
                {
                    stack.Push(node.right);
                }
                if (node.left != null)
                {
                    stack.Push(node.left);
                }
            }
        }

        public void InorderIterative()
        {
            var stack = new Stack<BinaryTreeNode<T>>();
            var node = this.Root;

            // 왼쪽 노드를 스택에 저장
            while (node != null)
            {
                stack.Push(node);
                node = node.left;
            }

            while (stack.Count > 0)
            {
                // 스택에 저장된 노드 가져오기
                node = stack.Pop();
                // 방문
                Console.WriteLine("{0} ", node.data);

                // 오른쪽 노드 존재 -> 루프 돌아서 오른쪽 노드의 왼쪽 노드도 저장
                if (node.right != null)
                {
                    node = node.right;
                    while (node != null)
                    {
                        stack.Push(node);
                        node = node.left;
                    }
                }
            }
        }

        public void PostorderIterative()
        {
            var stack = new Stack<BinaryTreeNode<T>>();
            var node = this.Root;

            while (node != null)
            {
                if (node.right != null)
                {
                    stack.Push(node.right);
                }
                stack.Push(node);
                node = node.left;
            }

            while (stack.Count > 0)
            {
                node = stack.Pop();
                if (node.right != null && stack.Count > 0 && node.right == stack.Peek())
                {
                    var right = stack.Pop();
                    stack.Push(node);
                    node = right;

                    while (node != null)
                    {
                        if (node.right != null)
                        {
                            stack.Push(node.right);
                        }
                        stack.Push(node);
                        node = node.left;
                    }
                }
                else
                {
                    Console.WriteLine("{0} ", node.data);
                }
            }
        }
    }
}

BinaryTreeNode.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace exam
{
    public class BinaryTreeNode<T>
    {
        public T data;
        public BinaryTreeNode<T> left;
        public BinaryTreeNode<T> right;

        public BinaryTreeNode(T data)
        {
            this.data = data;
        }
    }
}

Graph.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace exam
{
    public class Graph<T>
    {
        private List<Node<T>> nodes;
        private bool directedGraph;     // 방향 그래프 판단 변수

        public Graph(bool directedGraph = false)
        {
            this.nodes = new List<Node<T>>();
            this.directedGraph = directedGraph;
        }

        public Node<T> AddVertex(T data)
        {
            return this.AddVertex(new Node<T>(data));
        }

        public Node<T> AddVertex(Node<T> node)
        {
            this.nodes.Add(node);
            return node;
        }

        public void AddEdge(Node<T> from, Node<T> to, int weight = 1)
        {
            from.Neighbors.Add(to);
            from.Weights.Add(weight);
            if (!directedGraph)
            {
                to.Neighbors.Add(from);
                to.Weights.Add(weight);
            }
        }

        public void DebugPrintGraph()
        {
            foreach (var vertex in nodes)
            {
                int cnt = vertex.Neighbors.Count;
                for (int i = 0; i < cnt; i++)
                {
                    Console.WriteLine("{0}----({1})----{2}",
                        vertex.Data,
                        vertex.Weights[i],
                        vertex.Neighbors[i].Data);
                }
            }
        }

        // 깊이 우선 방식
        public void DFS()
        {
            // 방문 여부를 표시하는 방문테이블
            var visited = new HashSet<Node<T>>();

            // Disconnected Graph를 위해
            // 방문하지 않은 노드들 모두 체크
            foreach (var node in nodes)
            {
                if (!visited.Contains(node))
                {
                    this.DFSRecursive(node, visited);
                    Console.WriteLine();
                }
            }
        }

        private void DFSRecursive(Node<T> node, HashSet<Node<T>> visited)
        {
            // 노드 방문
            Console.Write("{0}", node.Data);
            visited.Add(node);

            // 인접 노드가 있을 때
            foreach (var adjNode in node.Neighbors)
            {
                // 이미 방문하지 않은 인접 노드에 대해서만
                if (!visited.Contains(adjNode))
                {
                    // 재귀 호출
                    DFSRecursive(adjNode, visited);
                }
            }
        }

        public void DFSIterative()
        {
            // 방문 테이블 생성
            var visited = new HashSet<Node<T>>();

            foreach (var node in nodes)
            {
                if (!visited.Contains(node))
                {
                    DFSUsingStack(node, visited);
                }
            }
        }

        private void DFSUsingStack(Node<T> node, HashSet<Node<T>> visited)
        {
            var stack = new Stack<Node<T>>();
            stack.Push(node);

            while (stack.Count > 0)
            {
                var vertex = stack.Pop();
                if (!visited.Contains(vertex))
                {
                    Console.WriteLine("{0}", vertex.Data);
                    visited.Add(vertex);
                }
                foreach (var adjNode in vertex.Neighbors)
                {
                    if (!visited.Contains(adjNode))
                    {
                        stack.Push(adjNode);
                    }
                }
            }
        }

        public void BFS()
        {
            var visited = new HashSet<Node<T>>();

            // 방문하지 않은 노드 체크
            foreach (var node in nodes)
            {
                if (!visited.Contains(node))
                {
                    BFS(node, visited);
                }
            }
        }

        private void BFS(Node<T> node, HashSet<Node<T>> visited)
        {
            var q = new Queue<Node<T>>();
            int count = 0;
            q.Enqueue(node);

            while (q.Count > 0)
            {
                var vertex = q.Dequeue();

                // 노드 방문
                if (!visited.Contains(vertex))
                {
                    //Console.Write("{0}", vertex.Data);
                    count++;
                    visited.Add(vertex);
                }

                foreach (var adjNode in vertex.Neighbors)
                {
                    // 이미 방문하지 않은 인접 노드에 대해
                    if (!visited.Contains(adjNode))
                    {
                        q.Enqueue(adjNode);
                    }
                }
            }

            Console.WriteLine(count);
        }
    }
}

Node.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace exam
{
    public class Node<T>
    {
        public T Data { get; set; }
        public List<Node<T>> Neighbors { get; set; }
        public List<int> Weights { get; set; }

        public Node()
        {
            this.Neighbors = new List<Node<T>>();
            this.Weights = new List<int>();
        }

        public Node(T data) : this()
        {
            this.Data = data;
        }
    }
}

 

1. App.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    public class App
    {
        // 생성자
        public App()
        {
            Game game = new Game();
            game.Start();
        }
    }
}

2. Game.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;


namespace Study09
{
    public class Game
    {
        private Character character;

        // 생성자
        public Game()
        {
            this.LoadDatas();
        }

        public void Start()
        {
            if (this.IsNewbie())
            {
                Console.WriteLine("<메이플 스토리>");
                Console.WriteLine("신규 유저입니다.");
                Console.Write("캐릭터 이름을 정하세요: ");
                string inputName = Console.ReadLine();
                Console.Write("직업을 선택하세요(0. 전사, 1. 마법사, 2. 도적): ");
                int job = Convert.ToInt32(Console.ReadLine());

                CharacterData data = DataManager.instance.GetLoadCharacterData(job);
                this.character = new Character(inputName, data);
            }
            else
            {
                Console.WriteLine("기존 유저입니다.");
                CharacterInfo info = this.LoadCharacterInfo();
                this.character = new Character(info);
                this.character.ShowStatus();
                this.character.Hit(2);
                this.character.ShowStatus();
                this.character.GetExp(100);
                this.character.ShowStatus();
            }

            // 이벤트 구독
            this.character.onUpdateStatus += (sender, args) =>
            {
                CharacterInfo info = args.Info;
                this.Save(info);
            };

            if (this.IsNewbie())
            {
                this.character.Hit(3);
                this.character.GetExp(10);
            }
        }

        // 데이터 정보를 저장할 json 파일 생성
        // 역직렬화: 객체 -> 문자열
        private CharacterInfo LoadCharacterInfo()
        {
            string json = File.ReadAllText("./character_info.json");
            return JsonConvert.DeserializeObject<CharacterInfo>(json);
        }

        private bool IsNewbie()
        {
            var exists = File.Exists("./character_info.json");
            return !exists;
        }

        private void LoadDatas()
        {
            DataManager.instance.LoadCharacterDatas();
            DataManager.instance.LoadExpDatas();
        }

        private void Save(CharacterInfo info)
        {
            Console.WriteLine("Save Game");
            // 저장될 때 현재 변화한 데이터가 담긴 info를 저장
            //Console.WriteLine(info);
            // 직렬화 사용 ( .json 파일에 담겨야하기 때문에)
            string json = JsonConvert.SerializeObject(info);
            File.WriteAllText("./character_info.json", json);
            Console.WriteLine("Save Complete");
        }
    }
}

3. DataManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
using System.Linq;

namespace Study09
{
    // 싱글톤 클래스: 생성자를 private로 사용
    // sealed: 다른 클래스에서 상속받지 못하게 함
    // DataManager 클래스: 데이터시트를 딕셔너리로 관리
    public sealed class DataManager
    {
        // readonly 키워드 사용하여 읽기 전용으로 필드 생성
        public static readonly DataManager instance = new DataManager();

        // 데이터를 읽어올 딕셔너리 생성
        private Dictionary<int, CharacterData> dicCharacters;
        private Dictionary<int, ExpData> dicExp;

        //생성자
        private DataManager()
        {

        }

        public void LoadCharacterDatas()
        {
            string json = File.ReadAllText("./character_data.json");
            var arr = JsonConvert.DeserializeObject<CharacterData[]>(json);
            // 배열 요소를 사전에 넣기
            this.dicCharacters = arr.ToDictionary(x => x.id);
            
        }

        public void LoadExpDatas()
        {
            string json = File.ReadAllText("./exp_data.json");
            var arr = JsonConvert.DeserializeObject<ExpData[]>(json);
            // 배열 요소를 사전에 넣기
            this.dicExp = arr.ToDictionary(x => x.id);
        }

        public CharacterData GetLoadCharacterData(int job)
        {
            CharacterData data = this.dicCharacters.Values.Where(x => x.job == job).First();
            return data;
        }

        public ExpData GetRequireExpData(int level)
        {
            ExpData data = this.dicExp.Values.Where(x => x.level == level).First();
            return data;
        }
    }
}

4. CharacterData.cs

*  데이터시트를 기반하여 제작, 변수 정의 후 수정X

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    // 바인딩 클래스
    public class CharacterData
    {
        public int id;
        public int job;
        public float max_hp;
        public float damage;
    }
}

5. CharacterInfo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    public class CharacterInfo
    {
        public string Name { get; set; }
        public int Id { get; set; }
        public int Level { get; set; }
        public float Hp { get; set; }
        public float MaxHp { get; set; }
        public float Damage { get; set; }
        public int CurrExp { get; set; }

        // 생성자
        public CharacterInfo()
        {

        }

        // 생성자
        public CharacterInfo(string name, int id, int level, float hp, float maxHp, float damage, int currExp)
        {
            Name = name;
            Id = id;
            Level = level;
            Hp = hp;
            MaxHp = maxHp;
            Damage = damage;
            CurrExp = currExp;
        }
    }
}

6. Character.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Study09
{
    public class Character
    {
        public class GetExpEventArgs : EventArgs
        {
            public float per;
        }

        // 캐릭터 정보를 업데이트하는 이벤트 작성
        public class UpdateStatusEventArgs: EventArgs
        {
            public CharacterInfo Info { get; set; }
        }

        public event EventHandler<UpdateStatusEventArgs> onUpdateStatus;
        public event EventHandler<GetExpEventArgs> onGetExp;

        public enum eJob
        {
            Warrior,
            Wizard,
            Assassin
        }

        private eJob job;
        private int reqExp;

        private CharacterInfo info;

        public Character(CharacterInfo info)
        {
            this.info = info;
            // 저장된 레벨, 경험치 가져옴
            ExpData expData = DataManager.instance.GetRequireExpData(this.info.Level);
            this.reqExp = expData.require_exp;
        }

        // 생성자
        // 신규 유저
        public Character(string name, CharacterData data)
        {
            this.info = new CharacterInfo();
            this.info.Id = data.id;
            this.info.Name = name;
            this.job = (eJob)data.job;
            this.info.Level = 1;
            this.info.MaxHp = data.max_hp;
            this.info.Hp = this.info.MaxHp;
            this.info.Damage = data.damage;
            this.info.CurrExp = 0;
            ExpData expData = DataManager.instance.GetRequireExpData(this.info.Level);
            this.reqExp = expData.require_exp;
        }

        public void Hit(int damage)
        {
            this.info.Hp -= damage;
            

            this.UpdateStatus();

            if (this.info.Hp <= 0)
                this.info.Hp = 0;

            if (this.info.Hp <= 0)
            {
                this.ShowDie();
                this.info.Hp = this.info.MaxHp;
                this.info.CurrExp -= this.reqExp / 10;
            }
        }

        public void UpdateStatus()
        {
            if (this.onUpdateStatus != null)
            {
                this.onUpdateStatus(this, new UpdateStatusEventArgs() { Info = this.info});
            }
        }

        public void ShowDie()
        {
            Console.WriteLine("***************************");
            Console.WriteLine("죽었습니다.");
            Console.WriteLine("마을에서 다시 태어났습니다.");
            Console.WriteLine("경험치를 -{0} 잃었습니다.", this.reqExp / 10);
            Console.WriteLine("***************************");
        }

        public void GetExp(int exp)
        {
            this.info.CurrExp += exp;
            if (this.info.CurrExp >= 100)
            {
                this.ShowLevelUp();
                this.LevelUp();
                //this.LevelUp();
            }
                

            this.UpdateStatus();
        }

        public void LevelUp()
        {
            this.info.Level++;
            this.info.Damage++;
            this.info.MaxHp = this.info.MaxHp + 10;
            this.info.Hp = this.info.MaxHp;
            int restExp = this.info.CurrExp - this.reqExp;
            this.info.CurrExp = restExp;
            ExpData expData = DataManager.instance.GetRequireExpData(this.info.Level);
            this.reqExp = expData.require_exp;
            this.UpdateStatus();
        }

        public void ShowLevelUp()
        {
            Console.WriteLine("***************************");
            Console.WriteLine("LEVEL UP !");
            Console.WriteLine("레벨: {0} -> {1}", this.info.Level, this.info.Level + 1);
            Console.WriteLine("체력: {0} -> {1}", this.info.MaxHp, this.info.MaxHp + 10);
            Console.WriteLine("공격력: {0} -> {1}", this.info.Damage, this.info.Damage + 1);
            Console.WriteLine("***************************");
        }

        public string GetStringJob()
        {
            switch (this.job)
            {
                case eJob.Warrior: return "전사";
                case eJob.Wizard: return "마법사";
                case eJob.Assassin: return "도적";
                default: return "";
            }
        }

        public void ShowStatus()
        {
            Console.WriteLine("--------------------");
            Console.WriteLine("이름: {0}", this.info.Name);
            Console.WriteLine("직업: {0}", this.GetStringJob());
            Console.WriteLine("체력: {0} / {1}", this.info.Hp, this.info.MaxHp);
            Console.WriteLine("공격력: {0}", this.info.Damage);
            Console.WriteLine("레벨: {0}", this.info.Level);
            Console.WriteLine("경험치: {0} / {1} ({2}%)", this.info.CurrExp, this.reqExp, (float)this.info.CurrExp / this.reqExp * 100f);
            Console.WriteLine("--------------------");
        }
    }
}

7. ExpData.cs

*  데이터시트를 기반하여 제작, 변수 정의 후 수정X

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    // 바인딩 클래스
    public class ExpData
    {
        public int id;
        public int level;
        public int require_exp;   // 레벨업 후 필요한 경험치
    }
}

8. ExpInfo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    public class ExpInfo
    {
        public int level;
        public float reqExp;

        // 생성자
        public ExpInfo(int level, float reqExp)
        {
            this.level = level;
            this.reqExp = reqExp;
        }
    }
}

9. Exp.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study09
{
    public class Exp
    {
        private int id;
        private int level;
        private float requireExp;

        private ExpInfo info;

        // 생성자
        public Exp(ExpInfo info)
        {
            this.info = info;
        }
    }
}

 

'C# > 수업 내용' 카테고리의 다른 글

[Unity2D] Swipe 이용하여 표창 날리기  (0) 2022.07.07
[C#] 알고리즘 문제  (0) 2022.07.06
Json 연습 - 같은 아이템의 개수(amount) 증가  (0) 2022.06.20
대리자 연습  (0) 2022.06.16
2차원 배열 맵타일  (0) 2022.06.15

 

* amount 숫자 증가 X - 수업 이후 해결


실행 결과

1. App.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class App
    {
        // 생성자
        public App()
        {
            Game game = new Game();
            game.Start();
        }
    }
}

2. Game.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;

namespace Study08
{
    public class Game
    {
        // 필드
        public List<Monster> monsters;
        private Inventory inven;

        // 생성자
        public Game()
        {
            this.inven = new Inventory();
            this.monsters = new List<Monster>();
            this.LoadDatas();
            if (!this.IsNewbie())
            {
                this.LoadInfos();
            }
        }

        public void Start()
        {
            Console.WriteLine("Start");
            this.CreateMonster(100);
            this.CreateMonster(101);
            this.CreateMonster(102);

            foreach (var monster in this.monsters)
            {
                //MonsterData data = DataManager.instance.GetMonsterData(monster.Id);
                //Console.WriteLine(data.name);
                Console.WriteLine("{0}, {1}", monster.Id, monster.Name);
            }

            Monster target = this.monsters[0];
            // Action 대리자
            target.dieAction = () =>
            {
                this.monsters.Remove(target);   // List에서 제거
                Console.WriteLine(this.monsters.Count);     // 2
                MonsterData data = DataManager.instance.GetMonsterData(target.Id);
                Item item = this.DropItem(data.drop_item0, data.drop_item1, data.drop_item2);
                this.inven.Add(item);
                Console.WriteLine("아이템 수: {0}", this.inven.Count);

                this.Save();
            };

            target.onHit += (sender, args) =>
            {
                Console.WriteLine("{0}%", args.per); // 남은 체력 %
            };

            Console.WriteLine(target);
            target.Hit(14);
            target.Hit(16);
        }

        public Item DropItem(params int[] arr)
        {
            IEnumerable<int> ids = arr.Where(x => x != 0);
            Random rand = new Random();
            var idx = rand.Next(0, ids.Count());    // 0, 1
            foreach (var id in arr)
            {
                Console.WriteLine("=>{0}", id);
            }
            int randItemIdx = arr[idx];
            Console.WriteLine(randItemIdx);
            ItemData data = DataManager.instance.GetItemData(randItemIdx);

            return this.CreateItem(data.id);
        }

        private Item CreateItem(int id)
        {
            ItemInfo info = new ItemInfo(id);
            return new Item(info);
        }

        private void LoadInfos()
        {
            var itemInfosJson = File.ReadAllText("./item_infos.json");
            // 역직렬화
            var itemInfos = JsonConvert.DeserializeObject<ItemInfo[]>(itemInfosJson);
            Console.WriteLine(itemInfos);

            foreach (var info in itemInfos)
            {
                var item = new Item(info);
                this.inven.Add(item);
            }
            Console.WriteLine("인벤토리 아이템 갯수 : {0}", this.inven.Count);
        }

        private bool IsNewbie()
        {
            var exists = File.Exists("./item_infos.json");
            if (exists)
                Console.WriteLine("기존 유저");
            else
                Console.WriteLine("신규 유저");
            return !exists;
        }

        private void LoadDatas()
        {
            DataManager.instance.LoadMonsterDatas();
            DataManager.instance.LoadItemDatas();
        }

        private void CreateMonster(int id)
        {
            Monster mon = new Monster(id);
            this.monsters.Add(mon);
        }

        public void Save()
        {
            // 직렬화: 객체 -> 문자열
            // List<Item> -> [{"id": 100 }]
            var json = JsonConvert.SerializeObject(this.inven.GetItems());
            Console.WriteLine( json);

            // 파일로 저장
            File.WriteAllText("./item_infos.json", json);
            Console.WriteLine("인벤토리 아이템들 저장 완료");
        }
    }
}

// 저장할 때는 시점이 저장됨

3. DataManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
using System.Linq;

namespace Study08
{
    // 싱글톤 클래스
    // sealed: 봉인 키워드 -> 상속 불가
    public sealed class DataManager
    {
        // static: 정적 키워드, 프로그램 시작해서 끝날 때까지 값 유지 -> 형식으로 접근해야 함
        // readonly: 읽기 전용 필드 생성 
        public static readonly DataManager instance = new DataManager();
        private Dictionary<int, MonsterData> dicMonsters;
        private Dictionary<int, ItemData> dicItems;

        // private 생성자
        private DataManager()
        {

        }

        // 인스턴스 메서드
        public void LoadItemDatas()
        {
            string json = File.ReadAllText("./item_data.json");
            // 역직렬화
            var arr = JsonConvert.DeserializeObject<ItemData[]>(json);
            this.dicItems = arr.ToDictionary(x => x.id);
            Console.WriteLine("item_data가 로드되었습니다. {0}", this.dicItems.Count);  
        }


        public void LoadMonsterDatas()
        {
            Console.WriteLine("LoadData");
            string json = File.ReadAllText("./monster_data.json");
            Console.WriteLine(json);

            // 역직렬화 : 문자열 -> 객체
            // 몬스터 데이터 배열을 반환
            MonsterData[] arrMonsterDatas = JsonConvert.DeserializeObject<MonsterData[]>(json);

            // Linq 사용
            // 내부 람다의 의미는 배열에 담긴 객체의 속성 중 key를 어떤 것으로 할 것인가?
            this.dicMonsters = arrMonsterDatas.ToDictionary(x => x.id);

            // Linq 사용 X
            ////this.dicMonsters = new Dictionary<int, MonsterData>();

            //// 배열에 담긴 MonsterData 객체 사전에 옮긴다
            //foreach (MonsterData data in arrMonsterDatas)
            //{
            //    this.dicMonsters.Add(data.id, data);
            //}

            // 확인
            foreach (KeyValuePair<int, MonsterData> pair in this.dicMonsters)
            {
                // id, monsterdata 인스턴스 출력됨
                Console.WriteLine("key: {0}, value: {1}", pair.Key, pair.Value);
            }
        }

        public MonsterData GetMonsterData(int id)
        {
            return this.dicMonsters[id];
        }

        public ItemData GetItemData(int id)
        {
            return this.dicItems[id];
        }
    }
}

4. MonsterData.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class MonsterData
    {
        public int id;
        public string name;
        public float max_hp;
        public int drop_item0;  // item_data 테이블의 id
        public int drop_item1;
        public int drop_item2;
    }
}

5. Monster.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class Monster
    {
        public class HitEventArgs : EventArgs
        {
            public float per;
        }

        public int Id
        {
            get;
            private set;
        }
        public string Name
        {
            get
            {
                MonsterData data = DataManager.instance.GetMonsterData(this.Id);
                return data.name;
            }
        }

        private float hp;       // 현재 체력
        private float maxHp;    // 최대 체력
        public Action dieAction;
        public EventHandler<HitEventArgs> onHit;


        // 생성자
        public Monster(int id)
        {
            this.Id = id;
            MonsterData data = DataManager.instance.GetMonsterData(this.Id);
            this.maxHp = data.max_hp;
            this.hp = this.maxHp;
            Console.WriteLine("몬스터 (id: {0})가 생성되었습니다.", this.Id);
            Console.WriteLine("체력 {0}/{1}", this.hp, this.maxHp);
        }

        public bool IsDie()
        {
            return this.hp <= 0;
        }

        public void Hit(int damage)
        {
            if (this.IsDie()) return;

            this.hp -= damage;
            if (this.hp <= 0)
                this.hp = 0;

            var args = new HitEventArgs();
            args.per = this.hp / this.maxHp * 100.0f;
            this.onHit(this, args);
            //this.onHit(this, new HitEventArgs() { per = this.hp / this.maxHp });
            Console.WriteLine("{0}이(가) {1} 피해를 입었습니다. (체력 {2}/{3})", this.Name, damage, this.hp, this.maxHp);

            if(this.hp <= 0)
            {
                this.Die();
            }
        }

        private void Die()
        {
            Console.WriteLine("id: {0}, name: {1}이(가) 죽었습니다.", this.Id, this.Name);
            this.DieAction();
        }

        public void DieAction()
        {
            this.dieAction();
        }
    }
}

6. ItemData.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class ItemData
    {
        public int id;
        public string name;
        public float damage;
    }
}

7. Item.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class Item
    {
        private ItemInfo info;  // 저장 객체

        public int Id
        {
            get
            {
                return this.info.id;
            }
        }

        public int Amount
        {
            get
            {
                return this.info.amount;
            }
            set
            {
                this.info.amount = value;
            }
        }

        // 생성자
        public Item(ItemInfo info)
        {
            this.info = info;
            this.info.amount = Amount;
        }
    }
}

8. ItemInfo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class ItemInfo
    {
        // 저장 객체를 public으로 정의
        public int id;
        public int amount;

        // 생성자
        public ItemInfo(int id, int amount = 1)
        {
            this.id = id;
            this.amount = amount;
        }
    }
}

9. Inventory.cs 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study08
{
    public class Inventory
    {
        private List<Item> items;
        public int Count
        {
            get
            {
                return this.items.Count;
            }
        }
        // 생성자
        public Inventory()
        {
            this.items = new List<Item>();
        }

        public void Add(Item item)
        {
            // 아래와 같은 기능
            //Item sameItem = null;
            //for (int i = 0; i < this.items.Count; i++)
            //{
            //    if (this.items[i].Id == item.Id)
            //    {
            //        sameItem = this.items[i];
            //        break;
            //    }
            //}

            var sameItem = this.items.Find(x => x.Id == item.Id);

            if (sameItem != null)
            {
                sameItem.Amount++;
            }
            else {
                this.items.Add(item);
            }
            
        }

        public void Remove(Item item)
        {
            this.items.Remove(item);
        }

        public List<Item> GetItems()
        {
            return this.items;
        }
    }
}

'C# > 수업 내용' 카테고리의 다른 글

[C#] 알고리즘 문제  (0) 2022.07.06
Json 연습 2 - 캐릭터, 경험치 데이터  (1) 2022.06.21
대리자 연습  (0) 2022.06.16
2차원 배열 맵타일  (0) 2022.06.15
2048 왼쪽 이동  (0) 2022.06.15

* 문제점: Key값이 "info"로 들어감

1. WeaponInfo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LoadDataExample
{
    // 저장될(변화하는) 데이터 저장
    public class WeaponInfo
    {
        public int id;  // 아이디
        public int enforce; // 강화 수치

        // 생성자
        public WeaponInfo(int id, int enforce = 0)
        {
            this.id = id;
            this.enforce = enforce;
        }
    }
}

2. Item.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LoadDataExample
{
    public class Item
    {
        public WeaponInfo Info
        {
            get;
            private set;
        }

        // 생성자
        public Item(WeaponInfo info)
        {
            this.Info = info;
        }
    }
}

3. Inventory.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LoadDataExample
{
    public class Inventory
    {
        Random rand = new Random();
        public List<Item> ItemList { get; set; }

        // 생성자
        public Inventory()
        {
            this.ItemList = new List<Item>();
        }
        public void CreateItem(int id)
        {
            WeaponInfo info = new WeaponInfo(id);
            Item item = new Item(info);
            this.ItemList.Add(item);
        }

        public void AddItem()
        {
            int id = this.rand.Next(100, 104);
            WeaponInfo info = new WeaponInfo(id);
            Item item = new Item(info);
            this.ItemList.Add(item);
        }
    }
}

4. App.cs

using System;
using System.IO;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;

namespace LoadDataExample
{
    public class App
    {
        //생성자 
        public App()
        {
            Inventory inventory = new Inventory();

            bool exists = File.Exists("./weapon_info.json");
            if (exists)
            {
                Console.WriteLine("기존유저");
                //  메모장에 있는 걸 읽어옴
                string weaponInfoJson = File.ReadAllText("./weapon_info.json");
                //Console.WriteLine(weaponInfoJson);

                //역직렬화 : 문자열 -> 오브젝트
                inventory.ItemList = JsonConvert.DeserializeObject<List<Item>>(weaponInfoJson);

                // 아이템 추가
                inventory.AddItem();

                // 직렬화: 오브젝트 -> 문자열
                string serializedJson = JsonConvert.SerializeObject(inventory.ItemList);

                //string serializedJson = JsonConvert.SerializeObject(item.Info);
                Console.WriteLine(serializedJson);
                //저장 
                File.WriteAllText("./weapon_info.json", serializedJson);

            }
            else
            {
                Console.WriteLine("신규유저");
                //초기 아이템 지급 
                inventory.CreateItem(100);

                //직렬화 : 오브젝트 -> 문자열 
                string serializedJson = JsonConvert.SerializeObject(inventory.ItemList);
                Console.WriteLine(serializedJson);
                //저장 
                File.WriteAllText("./weapon_info.json", serializedJson);
            }
        }
    }
}

 

'C# > 수업 과제' 카테고리의 다른 글

for문 연습 문제  (0) 2022.06.10
디아블로 아이템 출력하기  (0) 2022.06.09

1. App.cs

Hero hero2 = new Hero(3, 3);
            hero2.Move((x, y) =>
            {
                Console.WriteLine("({0}, {1})로 이동 완료", x, y);
            });

            Hero hero3 = new Hero(2);
            Monster mon = new Monster("용", 10);
            hero3.SetTarget(mon);
            hero3.Attack((target) =>
            {
                target.hitAction = (id) =>
                {
                    Console.WriteLine("{0}이 피해를 받았습니다.", id);
                };

                target.Hit(2);
            });

2. Hero.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study06
{
    public class Hero
    {
        public Action attackCompleteAction;
        
        private int damage;

        private int x;
        private int y;
        private Monster target;

        // 생성자
        public Hero(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public Hero(int damage)
        {
            this.damage = damage;
        }

        public void Move(Action<int, int> callback)
        {
            callback(this.x, this.y);
        }

        public void Attack(Action<Monster> callback)
        {
            this.target.hp -= this.damage;
            callback(this.target);
        }

        public void SetTarget(Monster target)
        {
            this.target = target;
        }
    }
}

3. Monster.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Study06
{
    public class Monster
    {
        public Action<string> hitAction;
        public Action<string> dieAction;

        private string id;
        public int hp;
        private int maxHp;

        // 생성자
        public Monster(string id, int maxHp)
        {
            this.id = id;
            this.maxHp = maxHp;
            this.hp = this.maxHp;
        }


        public void DieAction(string id)
        {
            this.dieAction(id);
        }

        public void Hit(int damage)
        {
            this.hp -= damage;
            this.hitAction(this.id);
        }
    }
}

실행 결과

 

'C# > 수업 내용' 카테고리의 다른 글

Json 연습 2 - 캐릭터, 경험치 데이터  (1) 2022.06.21
Json 연습 - 같은 아이템의 개수(amount) 증가  (0) 2022.06.20
2차원 배열 맵타일  (0) 2022.06.15
2048 왼쪽 이동  (0) 2022.06.15
인벤토리 예제  (0) 2022.06.14

+ Recent posts