2023년 11월 30일 목요일

Unity Team Project - 벽돌 깨기




기간 제한은 일주일, 사실상 저번엔 C# Git 다루는 협업 연습이고

이번엔 Git Unity 콜라보 merge 연습이다

주제는 벽돌게임 만들기로 선택했고 역할 구분과 각자 개발환경 조사하였음



 


프로젝트명과 추가 기능을 서로 제안하였고 채택된 요소들을
매일 팀 노션에 기록

2023년 11월 28일 화요일

C++(Algorithm) - 이상한 문자 만들기




짝수자리는 대문자로, 홀수자리는 소문자로 바꾸면 된다.
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
 
// 파라미터로 주어지는 문자열은 const로 주어집니다. 변경하려면 문자열을 복사해서 사용하세요.
char* solution(const char* s) {
    // return 값은 malloc 등 동적 할당을 사용해주세요. 할당 길이는 상황에 맞게 변경해주세요.
    char* answer = (char*)malloc(strlen(s) + 1);
    
    if (answer != NULL)//exception
    {
        strcpy_s(answer, strlen(s) + 1, s);
    }
    else
    {
        return 0;
    }
    
 
    
   
    for (int x = 0; x < strlen(answer); x++)
    {
        if (x != 0)
        {
            if (*(answer + x) != ' ')
                continue;
            x++;
        }
 
        for (int y = 0; y < strlen(answer) - x; y++)
        {
            
            if (*(answer ++ y) == ' ')break;
 
            if (y % 2 == 0)
            {
                *(answer + x + y) = toupper(*(answer + x + y));
            }
            else
            {
                *(answer + x + y) = tolower(*(answer + x + y));
            }
        }
    }
 
    return answer;
}
 
void main()
{
    char* rtn = solution("try hello world");
    printf("%s", rtn);
    free(rtn);
}
cs
malloc을 쓰라고 해둬서 char*을 쓰게 됐음
 

공백있는 자리는 건너뛰고 다시 0 으로 돌아가 남은 문자만큼
대소문자 변환을 해준다.


결과




2023년 11월 26일 일요일

C++(Algorithm) - 3진수 뒤집기

 


10진수를 3진수로 만들고 이를 반전시키고 다시 10진수로 되돌리면 된다.


먼저 코드로는 이렇게 짰다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <math.h>
 
int solution(int num1) {
    int answer = num1;
    int ary[20];
 
    int i = 0;
    while (answer > 1)
    {  
        ary[i] =answer % 3;
        answer /= 3;
 
        i++;
    }
 
    ary[i] = answer;
    answer = 0;
 
 
    for (int j = 0; j <= i; j++)
    {
        answer += ary[j] * pow(3, i - j);
    }
 
    
    return answer;
}
 
void main()
{
    int num = 100000000;
    printf("%d, %d\n", num, solution(num));
}
cs

1억을 넣었을 때 초과되지 않도록 배열 길이는 20으로 해두었다.
Vector를 사용하는 것도 좋지만 다시 반환하기 귀찮아서 그냥 배열로 했다.
뒤집는 기능도 구현 할 필요없이 배열을 큐처럼 사용하여 바로 리턴하면 된다.






예로 45를 입력했을 경우 13번줄 코드에서 배열에 이런식으로 저장되는데
이를 다시 빼와서 3^n 만큼 곱해주면 된다.

2023년 11월 23일 목요일

unity tutorial 5 - PingPong

 


핑퐁 만들기




빈 오브젝트에 GameManager 스크립트를 넣어서 전체 게임을 관리하는 메인 씬을 구성한다.
각 매개변수에 일치하는 요소를 넣어 어느 골에 공이 들어가면
어떤 플레이어 점수가 오르는지 구현



Create > 2D > Physics Material 2D를 생성하여
마찰력(Friction)은 0으로 조정하고 탄성(Bounciness)을 1로
 현재 운동량을 줄이지 않게 만든다.




스크립트에서 매개변수로 속도와 Input을 받아와 해당 키를 눌렀을 때
오브젝트를 일정 속도만큼 이동하게 한다.




골대에서 Ball과의 충돌을 확인 후, GameManager의 점수를 올리는 함수를 호출하여
점수표의 텍스트를 변경한다.




결과



2023년 11월 22일 수요일

C# - TeamRpg 3


결과







C++(Algorithm) - 콜라츠

 


문제대로 500번 이내에 계산이 되면 횟수를 출력하면 된다. (넘어가면 -1)



간단하게 C로 만들어 보았는데

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
 
int solution(int num1) {
    unsigned long long answer = num1;
 
    for (int i = 0; i < 500; i++)
    {
        if (answer == 1)
            return i;
 
        if (answer % 2 == 0)
        {
            answer /= 2;
 
        }
        else
        {
            answer = answer * 3 + 1;
        }
    }
 
    return -1;
}
 
void main()
{
    printf("%d\n", solution(626331));
}
cs

이런 결과가 나온다.


그런데 626331의 경우에 처음에 answer 변수의 타입을 int로 했었다.

그럼 488번째 for문에서 1이 되는데, 답이 달라서 해답을 찾아봤다. 

곱셈 연산쪽에서 오버플로우가 발생한다고 long 타입을 사용하라 해답에 있었지만

그냥 long으로도 안돼서 그냥 더 큰 더블 Long으로 해결하였음



2023년 11월 17일 금요일

C# - TeamRpg 2

 



서로 담당 부분에서 발생한 문제를 해결해 나가는 과정이 순조로웠으며
이번에 처음 Merge 했을 때도 오류들을 없애 main파일에 덮어 씌웠다.



중간에 오류를 수정하기 위해 롤백도 잠시 했었는데 기록이 남아버려서 좀 그렇다.
그래도 콜라보 방식도 이젠 나름 익숙해져서 나중에는 포크로도 개발해 볼 것이다.


학생 시절에 이런식으로 다같이 개발해보는 경험이 없었으나 이번 캠프에서
다른 팀원이 활용하기 쉽게 서로 소통하면서 수정하고 규칙도 정하다 보니
이젠 큰 프로젝트도 문제없이 진행할 수 있을 것 같다








2023년 11월 16일 목요일

C# - TeamRpg 1



스크럼 회의를 처음 진행했다. 

현재 진행 상황 보고와 희망하는 개선 사항 및 문제 지적을 위한 시간으로 활용하고 있다.



이번에 모여서 논의한 요소는 사소하지만 프로그램 구조를 크게 바꿀 수 도 있기에

조심하는것이 좋다고 판단하였다.


이번에 담당 역할인 플레이어 직업과 스킬 관련해서 진행 사항을 보고하였고

프로그램 머지는 내일 오후에 약속했다. 오류가 많이 생길수도 있기에 주석과

예외처리를 끝내 놓았으며 머지가 끝나는대로 다시 진행한다.


클래스 선택 씬과 json 파일을 사용하여 스킬 데이터를 폴더로 분류하여
아트와 데이터 json 파일들을 구분지었다.


링크의 깃 커밋 규칙에 따라 docs와 feat으로 코드 수정과 폴더 분류를 팀원들에게 알렸고

다른 팀원들은 어떻게 진행되었는지 살펴본 결과 마감기한까지는 순조롭게 진행 가능성이

보여 살짝 여유를 두어 하루 전에 프로젝트를 끝낼 생각이다.




2023년 11월 14일 화요일

C# - TextRpg 3


추가 기능까지는 무리더라도 필수 기능은 모두 구현완료



총 4개의 클래스로 이루어져 있으며 Program은 메인 클래스로
다른 클래스에는 이름에 맞게 기능들을 몰아서 넣어놨다.




Ui관리를 위한 InterfaceClass에 모든 기능을 때려 넣어주고
필요한 요소만 매개변수로 받아서 출력하게 해놨다.




장비 관리 Ui창에서 해당 장비 번호를 눌렀을 때 장착 및 해체 기능 구현


장착한 장비는 E표시로 장착 여부를 확인한다.
List 내부의 구조체 변수에 접근은 어려우므로 따로 임시 변수에 저장한 다음
해당 변수를 수정하고 다시 집어넣으면 실제로 반영이 된다.
(함수를 호출해서 변경하려고 했는데 복사 값만 가져와서 실제 메모리에 반영이 안됨)


2023년 11월 9일 목요일

C# - TextRpg 2

   
시작마을 맵, 대충 만들었다.

플레이어를 구분하기 쉽게 색을 입혔고 방향키로 움직일 수 있다.

방향키 입력은 이런식으로 받아서 처리하였다.


키입력은 ReadKey 함수를 사용하여 처리하며 쓰레드로 따로 빼서 돌렸다.
눌린 버튼은 전역함수인 bool 형 변수를 true로 초기화하고
다른 함수에서 이를 처리하여 다시 false로 바꾼다. 










방향키를 입력했을때, 플레이어 위치를 이동시키고
플레이어가 이동하려는 방향에 공백문자 "  "가 아닌 다른 문자가 있는지
확인함으로써 물체가 있는지 없는지 if문으로 처리하여 벽을 뚫고 이동하지
못하게 만든다.




다음은 UI로 메인 화면과 여러 UI화면을 왔다갔다 해야 하므로
현재 랜더링을 해야 하는 화면을 구분하기 쉽게 enum으로 선언하여 정리하였다.







Switch 문으로 Ui버퍼에 원하는 문자열을 char배열로 변환하여 배치한다.


결과


하지만 이때 문제가 생기는데, 보통 영어나 숫자같은 문자열은
한자리당 1바이트씩 차지하는데 특수문자와 한글은 2바이트다. 
(UTF8 기준으로는 3바이트)
그래서 자리 칸 무시하고 그냥 출력하면 이렇게 된다.

1바이트는 한칸 차지하고 3바이트는 두칸 차지하기 때문에 
이런식으로 길이 차이가 생긴다.



여기서 1바이트 문자 개수 만큼 맨 뒤에다 " "공백을 넣게되면 정상적으로 자리가 맞춰진다.



다음은 인벤토리이다.


일단은 구조체로 만든 무기, 방어구의 상세 정보를 배열로 만들어서
출력할 때 내용물 중에서 이름 정보가 null 아니면 출력하게 하였다.
(구조체 자체가 null인지 확인할 수 없음)


이런식으로 저장되있는 장비들을 불러와 출력하면 된다.
(장착 여부에 따라 앞에 [E] 를 추가하는 코드는 구현 해놨지만
아직 장착하는 부분을 안했다.)




C# - TextRpg 1

이번 프로그램은 완성 기간이 길어질것 같아서 나눠서 작성하게 됐다..

콘솔창으로 진행되는 RPG 비스무리한 게임을 만들게 되었는데
일단 요구사항은 이렇다.




저번 게임과 동일하게 특수문자로 만든 박스 안에 모든 UI 정보를 출력하게 할 계획이다. 
가로줄은 깔끔하게 딱 맞아 떨어지는 기호가 없어서 불편...


위와 같이 게임 진행에 방해가 되지않게, 또는 코딩하기 편하라고 
Ui와 게임화면을 닌텐도처럼 분리시킨다.





스네이크 게임에서는 Console.clear() 함수로 화면을 초기화 하였지만
이는 화면을 깜빡거리게 만드는 원인이 되므로 다른함수인
Console.SetCursorPosition()를 사용할 예정



랜더링은 버퍼 하나로만 사용하다가
프레임이 떨어지는 상황이 많아지면 더블 버퍼링 할 예정





추가로 프레임도 고정시킨다.

Stopwatch 클래스가 있던 것 같지만 요것도 
나름 쓸만해 보여서 실험을 좀 해보았다.


밀리초 ms기준으로 if문까지 평균 16ms초 나오는데 오차 생각해서
 적당히 29ms으로 했다. (30프레임) 

딱 맞는걸 보면 콘솔 화면은 60프레임 고정일지도
1000/60 = 16.66..




아이템이나 캐릭터 상세 데이터는 구조체로 관리하며 
아이템 같은 경우는 배열로 다룬다.


동적배열로 만들어도 될 것 같았지만 
인벤토리는 제한이 있는게 일반적이므로 냅뒀음



2023년 11월 8일 수요일

C# - SnakeGame

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
namespace SnakeGame
{
    internal class Programs
    {
 
 
        static void Main(string[] args)
        {
 
 
            GameManager gameManager = new GameManager();
            gameManager.GameStart();
 
            System.Console.WriteLine("패배");
 
            return;
        }
    }
}
 
 
 
public class GameManager
{
    char[,] gridField = new char[2020];
    List<Point> m_snake = new List<Point>();
    Point m_food;
    bool hasFood = false;
    Direction direction;
 
    public struct Point
    {
        public int x;
        public int y;
    }
 
    public GameManager()
    {
        //처음 이동방향을 오른쪽으로 초기화
        direction = Direction.RIGHT;
 
        //기본 생성자에서 게임판 테두리를 미리 그려놓기
        for (int i = 1; i < 19; i++)
        {
            gridField[0, i] = 'ㅡ';
            gridField[19, i] = 'ㅡ';
            gridField[i, 0= '│';
            gridField[i, 19= '│';
        }
 
        gridField[00= '┌';
        gridField[019= '┐';
        gridField[190= '└';
        gridField[1919= '┘';
 
 
        //뱀 처음 시작포인트 생성
        Point tmp_snake = new Point();
        tmp_snake.x = 10;
        tmp_snake.y = 10;
        m_snake.Add(tmp_snake);
 
        DrawSnake();
        DrawFood();
    }
 
    private void DrawSnake()
    {
        //게임판 내부를 초기화
        for (int i = 1; i < 19; i++)
        {
            for (int j = 1; j < 19; j++)
            {
                gridField[i, j] = 'ㅤ';
            }
        }
 
        //뱀 그리기
        for (int i = 0; i < m_snake.Count; i++)
        {
            int x = m_snake[i].x;
            int y = m_snake[i].y;
 
 
            //머리부분만 다르게 그리기
            if (i == 0)
            {
                gridField[x, y] = '□';
            }
            else
            {
                gridField[x, y] = '■';
            }
        }
    }
 
    //뱀을 이동시키는 함수
    private void MoveSnake()
    {
        //리스트 구조체안의 변수를 직접 수정할수 없으므로 임시 구조체로 덮어씌운다.
        Point tmp_point = new Point();
        tmp_point.x = m_snake[0].x;
        tmp_point.y = m_snake[0].y;
 
 
        //xy 서로 반대로 바뀌었다고 생각한다
        //현재 이동방향에 따라 뱀이동
        switch (direction)
        {
            case Direction.RIGHT:
                tmp_point.y += 1;
                break;
            case Direction.UP:
                tmp_point.x -= 1;
                break;
            case Direction.DOWN:
                tmp_point.x += 1;
                break;
            case Direction.LEFT:
                tmp_point.y -= 1;
                break;
 
        }
 
        //뱀머리와 밥이 겹쳤을때 뱀 길이 늘리기
        if (m_food.x == tmp_point.x && m_food.y == tmp_point.y)
        {
            Point tmp_point2 = new Point();
            tmp_point2.x = m_snake[m_snake.Count - 1].x;
            tmp_point2.y = m_snake[m_snake.Count - 1].y;
 
            m_snake.Add(tmp_point2);
 
            hasFood = false;
        }
 
        if (m_snake.Count > 1)
        {
            //머리빼고 리스트 순서대로 좌표값 전달
            for (int i = m_snake.Count - 1; i > 0; i--
            {
                m_snake[i] = m_snake[i - 1];
            }
        }
 
        //머리부분 이동
        m_snake[0= tmp_point;
 
    }
 
    private void DrawFood()
    {
 
        //현재 먹이가 없을때만 생성
        if (!hasFood)
        {
 
            while (true)
            {
                int x = System.Security.Cryptography.RandomNumberGenerator.GetInt32(118);
                int y = System.Security.Cryptography.RandomNumberGenerator.GetInt32(118);
 
                //먹이를 둘 자리에 무언가 있으면 다시 처음으로 돌아가서 랜덤 좌표 생성
                if (gridField[x, y] == 'ㅤ')
                {
                    m_food.x = x;
                    m_food.y = y;
                    hasFood = true;
                    break;
                }
                else
                {
                    continue;
                }
 
            }
        }
        else
        {
            //먹이는 다른 문자로 표현
            gridField[m_food.x, m_food.y] = '♥';
        }
 
    }
 
    private bool CheckCollision()
    {
        //맵밖으로 나갔을 경우
        if (m_snake[0].x > 18 || m_snake[0].x < 1 || m_snake[0].y > 18 || m_snake[0].y < 1)
        {
            return true;
        }
 
        //자기 몸이랑 충돌햇을 경우
        for (int i = 1; i < m_snake.Count; i++)
        {
            if (m_snake[0].x == m_snake[i].x && m_snake[0].y == m_snake[i].y)
            {
                return true;
            }
 
        }
 
        return false;
    }
 
    public void InputKey()
    {
        ConsoleKeyInfo m_key;
 
        while (true)
        {
            m_key = Console.ReadKey(true);
 
            switch (m_key.Key)
            {
                case ConsoleKey.LeftArrow:
                    direction = Direction.LEFT;
 
                    break;
                case ConsoleKey.RightArrow:
                    direction = Direction.RIGHT;
 
                    break;
                case ConsoleKey.UpArrow:
                    direction = Direction.UP;
 
                    break;
                case ConsoleKey.DownArrow:
                    direction = Direction.DOWN;
 
                    break;
            }
 
        }
 
    }
 
    //게임시작 함수
    public void GameStart()
    {
        bool result;
 
        while (true)
        {
            Thread keyCheck = new Thread(InputKey);
            DrawSnake();
            DrawFood();
 
            //키입력 함수는 다른 스레드로 돌린다.
            keyCheck.Start();
 
            //렌더링
            System.Console.Clear();
 
            for (int i = 0; i < 20; i++)
            {
                for (int j = 0; j < 20; j++)
                {
                    System.Console.Write(gridField[i, j]);
                }
                System.Console.WriteLine();
            }
 
            Thread.Sleep(100);
 
            //현재 이동방향에 따라 뱀이동
            MoveSnake();
 
            //충돌체크
            result = CheckCollision();
            if (result)
            {
                //뱀이 벽이나 자기 몸에 충돌했을때 메모리 반환 후 게임 종료
                GameOver();
 
                return;
            }
        }
    }
 
    //메모리 반환 함수
    public void GameOver()
    {
 
    }
}
 
public enum Direction
{
    LEFT,
    RIGHT,
    UP,
    DOWN
}
cs




빠꾸하면 죽음



c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데 이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는 함수가 없어서 조금 아쉬운 애다. std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든 start 함...