디버깅
프로그램 실행 및 디버깅시 중단점이 찍힌 코드 ‘줄’에서 멈추게 해준다. 멈춘 줄로부터 코드 한줄 한줄씩 실행해보는게 가능하다.
개발 단계에선 괜찮지만 현재 서비스 되고 있는 프로그램에선 브레이크 포인트를 사용하는 디버깅은 할 수 없다.
→ 자동차 출시 및 출고 후에는 실시간으로 자동차 내부를 살펴볼 수 없는 것과 비슷. 개발 단계는 디버그 모드로 실행하고 중단점은 이때만 사용 가능하다. 프로그램을 출시할 때는 Release 모드로 빌드를 해서 실행하게 된다. 이미 출시한 프로그램은 중단점을 사용하여 한 줄 한 줄 실행하면서 실시간으로 살펴볼 수는 없다.
- 중단점을 한번 더 클릭하여 없앨 수도 있지만 중단점 해제를 직접 여기서 선택하면 중단점이 노란색으로 바뀐다. 중단점으로서 역할을 더 이상 하지 않아 여기서 멈추지 않겠지만 중단점으로 체크한적이 있는 코드라는 표시를 남기는 용도로 사용할 수 있다.
- ★중요) 조건 항목 탭을 클릭하여 어떠한 조건이 참일 때만 해당 중단점에 멈추도록 할 수도 있다.
[한 단계씩 코드 실행]
- 함수를 실행 코드에선 함수 내부로 들어가서 함수 내부의 코드들에 한줄 한줄 멈춰가며 보여준다.
- 함수 내부로 내부로 들어가기 때문에 호출 스택탭을 사용한다.
- → 해당 함수를 몇 번째 코드 줄에서 호출 했었는지가 표시 됨.
- → 호출 스택에서 해당 줄들을 누르면 그 줄로 이동함.
[프로시저 단위 실행]
- 프로시저 = 메소드 = 함수
- 함수 실행 부분은 함수 내부에 들어가서 한줄 한줄 실행하는 모습은 보여 주지 않고 바로 함수 리턴 값을 받아오는 부분으로 건너 뛴다. 실행 단위가 메소드 단위라서 함수를 그냥 코드 한줄로서 치부함. 함수 내부 코드로 들어가지 않음.
실행 순서 나타내는 화살표를 드래그 해서 임의로 실행 순서를 변경할 수도 있다. 즉 과거, 미래로 돌릴 수도 있다.
디버깅 중에도 값을 개발자가 임의로 변경할 수 있다. 이러면 변경된 메모리를 가지고 다음 줄에서 실행 된다.
모두 중단은 일시정지 버튼이다.
- 모두 중단을 눌러 디버깅을 잠시 일시정지하면 실행되고 있던 곳에서 딱 멈춘다!
- 따라서 이걸 통해 알 수 없이 무한루프 뻉뺑 돌고 있는 곳이 있다면 모두 중단으로 찾을 수 있다.
직업 고르기
using System;
namespace CSharp
{
class Program
{
enum ClassType
{
None = 0,
Knight = 1,
Archer = 2,
Mage = 3
}
static ClassType ChooseClass()
{
Console.WriteLine("직업을 선택하세요");
Console.WriteLine("[1] 기사");
Console.WriteLine("[2] 궁수");
Console.WriteLine("[3] 법사");
ClassType choice = ClassType.None;
string input = Console.ReadLine();
switch (input)
{
case "1":
choice = ClassType.Knight;
break;
case "2":
choice = ClassType.Archer;
break;
case "3":
choice = ClassType.Mage;
break;
}
return choice;
}
static void Main(string[] args)
{
while (true)
{
ClassType choice; // 초기화 하지 않으면 자동으로 첫 번째 상수인 ClassType.None 으로 초기화 된다.
choice = ChooseClass(); // static함수라 클래스 이름으로 호출하는 Program.ChooseClass(); 와도 같다.
if (choice != ClassType.None) // 함수 내부에서 switch 문에 걸리지 않았다면, 즉 123 입력을 하지 않았다면 다시 반복. 제대로 123 중 하나 입력 했다면 직업 선택 그만함.
break;
}
}
}
}
[ChooseClass()가 일반 멤버 함수 였다면]
- 같은 Program 클래스 내에 있더라도 메인 함수에서 ChooseClass(); 이렇게 호출하는건 불가능하다.
- → 왜냐면 메인 함수는 static 함수이기 때문에 어디서든 Program 객체 생성 없이 바로 Program 이름으로 호출이 가능한데 ChooseClass()는 일반 멤버 함수이므로 객체 생성이 있어야하지만 가능하기 때문이다.
- → 따라서 Program 타입의 인스턴스를 만들고 인스턴스를 통해 호출해야 한다.
- 메인 함수 같은 static 함수가 아닌 같은 일반 멤버 함수 내에서 호출된거라면 같은 클래스니까 ChooseClass(); 호출 가능
[ChooseClass()가 static 함수여서]
- 같은 static 함수인 ChooseClass()도 객체 생성이 필요 없기 때문에 그냥 메인 함수 내에서 ChooseClass(); 이렇게 호출이 가능한 것이다.
- 정석대로 클래스 이름 호출로 Program.ChooseClass(); 이렇게도 가능하다. 근데 같은 클래스 내부니까 클래스 이름 생략 가능.
플레이어 생성
class Program
{
struct Player
{
public int hp;
public int attack;
public ClassType type;
}
static void CreatePlayer(ClassType choice, out Player player)
{
player.type = choice;
// 기사(100, 10) 궁수(75, 12) 법사(50, 15)
switch (choice)
{
case ClassType.Knight:
player.hp = 100;
player.attack = 10;
break;
case ClassType.Archer:
player.hp = 75;
player.attack = 12;
break;
case ClassType.Mage:
player.hp = 50;
player.attack = 15;
break;
default:
player.hp = 0;
player.attack = 0;
break;
}
}
static void Main(string[] args)
{
while (true)
{
// 직업 고르기
ClassType choice;
choice = ChooseClass(); // Program.ChooseClass();
if (choice != ClassType.None)
{
// 플레이어 캐릭터 생성
Player player;
CreatePlayer(choice, out player);
Console.WriteLine($"HP{player.hp}, Attack{player.attack}");
}
}
}
}
[플레이어 특성 구조체로 관리]
- hp, attack 만 사용했지만 mp, region 등등 많은 특성이 생길 수 있다.
- 그래서 일일이 다 변수로 선언하기 보단 구조체로 묶어서 관리하는게 좋다.
구조체 변수들도 접근 지정자가 있다. 지정 안해주면 디폴트로 private이 되서 player.hp 이렇게 호출할 수가 없다.
구조체 매개 변수를 out으로 하면 구조체의 모든 멤버 변수들을 전부 다 빠짐 없이 write 해야 한다.
- out은 엄격하기 때문에 case 에 걸리지 않아 write 되지 않을 것도 생각해서인지 모든 case에 대해 write 해주지 않으면 컴파일 오류 난다. default에도 꼭 write 해주어야 한다.
Player player;
CreatePlayer(choice, out player);
아무런 초기화 되어 있지 않던 player가 CreatePlayer 함수 호출이 끝난 후 멤버 변수들이 전부 다 설정된다! 초기화 안되 있었던 상태니, 즉 메모리 할당이 되어 있지 않은 상태라 ref는 쓸 수 없다. out은 메모리 할당 안되어 있는 변수를 참조하는 것이라도 함수 내에서 write 할 것이라는게 보장 되어 있기 때문에 문제 되지 않는다.
몬스터 생성
enum MonsterType
{
None = 0,
Slime = 1,
Orc = 2,
Skeleton = 3
}
struct Monster
{
public int hp;
public int attack;
}
static void CreateRandomMonster(out Monster monster)
{
Random rand = new Random();
int randMonster = rand.Next(1, 4); // 1 ~ 3 중 랜덤 정수 리턴
switch(randMonster)
{
case (int)MonsterType.Slime:
Console.WriteLine("슬라임이 스폰 되었습니다!");
monster.hp = 20;
monster.attack = 2;
break;
case (int)MonsterType.Orc:
Console.WriteLine("오크가 스폰 되었습니다!");
monster.hp = 40;
monster.attack = 4;
break;
case (int)MonsterType.Skeleton:
Console.WriteLine("스켈레톤이 스폰 되었습니다!");
monster.hp = 30;
monster.attack = 3;
break;
default:
monster.hp = 0;
monster.attack = 0;
break;
}
}
static void EnterField()
{
Console.WriteLine("필드에 접속했습니다.");
// 랜덤으로 1~3 몬스터 중 하나를 리스폰
Monster monster;
CreateRandomMonster(out monster);
}
static void EnterGame()
{
while(true)
{
Console.WriteLine("마을에 접속했습니다.");
Console.WriteLine("[1] 월드로 간다.");
Console.WriteLine("[2] 로비로 돌아가기.");
String input = Console.ReadLine();
switch (input)
{
case "1":
EnterField();
break;
case "2":
return;
}
}
}
static void Main(string[] args)
{
while (true)
{
// 직업 고르기
ClassType choice;
choice = ChooseClass(); // Program.ChooseClass();
if (choice != ClassType.None)
{
// 플레이어 캐릭터 생성
Player player;
CreatePlayer(choice, out player);
Console.WriteLine($"HP{player.hp}, Attack{player.attack}");
// 게임 시작! 몬스터 생성 및 전투
EnterGame();
}
}
}
}
static void CreateRandomMonster(out Monster monster)
{
Random rand = new Random();
int randMonster = rand.Next(1, 4); // 1 ~ 3 중 랜덤 정수 리턴
switch(randMonster)
{
case (int)MonsterType.Slime:
randMonster는 int형이기 때문에 case문에서 해당 변수를 사용하기 위해서는 (int)MonsterType.Slime 이렇게 형변환 해주어야 한다. monster 구조체의 모든 멤버 변수들을 전부 다 write 해야 한다. write 되지 않을 케이스가 있어서도 안된다.
static void EnterGame()
{
while(true)
{
Console.WriteLine("마을에 접속했습니다.");
Console.WriteLine("[1] 월드로 간다.");
Console.WriteLine("[2] 로비로 돌아가기.");
String input = Console.ReadLine();
switch (input)
{
case "1":
EnterField();
break;
case "2":
return;
}
}
}
2를 선택하면 로비로 돌아가게 하고 싶다. 즉, 다시 직업을 고르도록 Main 함수로 돌아가고 싶다. 따라서 case "2"의 경우엔 break가 아닌 그냥 return을 해주어 아예 EnterGame 함수를 종료시킨다.
전투
static void Fight(ref Player player, ref Monster monster)
{
while(true)
{
// 플레이어가 몬스터 공격
monster.hp -= player.attack;
if (monster.hp <= 0)
{
Console.WriteLine("승리했습니다!");
Console.WriteLine($"남은 체력 : {player.hp}");
break;
}
// 몬스터 반격
player.hp -= monster.attack;
if (player.hp <= 0)
{
Console.WriteLine("패배했습니다!");
break;
}
}
}
static void EnterField(ref Player player)
{
Console.WriteLine("필드에 접속했습니다.");
// 랜덤으로 1~3 몬스터 중 하나를 리스폰
Monster monster;
CreateRandomMonster(out monster);
Console.WriteLine("[1] 전투 모드 돌입");
Console.WriteLine("[2] 일정 확률로 마을로 도망");
string input = Console.ReadLine();
if (input == "1")
{
Fight(ref player, ref monster);
}
else if (input == "2")
{
// 33 %
Random rand = new Random();
int randValue = rand.Next(0, 101);
if (randValue <= 33)
{
Console.WriteLine("도망치는데 성공했습니다!");
}
else
{
Fight(ref player, ref monster);
}
}
}
static void EnterGame(ref Player player)
{
while(true)
{
Console.WriteLine("마을에 접속했습니다.");
Console.WriteLine("[1] 월드로 간다.");
Console.WriteLine("[2] 로비로 돌아가기.");
String input = Console.ReadLine();
switch (input)
{
case "1":
EnterField(ref player);
break;
case "2":
return;
}
}
}
static void Main(string[] args)
{
while (true)
{
// 직업 고르기
ClassType choice;
choice = ChooseClass(); // Program.ChooseClass();
if (choice == ClassType.None)
continue;
// 플레이어 캐릭터 생성
Player player;
CreatePlayer(choice, out player);
Console.WriteLine($"HP{player.hp}, Attack{player.attack}");
// 게임 시작! 몬스터 생성 및 전투
EnterGame(ref player);
}
}
}
Fight 함수에서 player 구조체가 필요하기 때문에 인수로 전달 받아야 한다. Call by Value로 해도 나쁘지 않지만, EnterGame > EnterField > Figt 이 순으로 호출되며 player를 전달해야 하기 때문에 그냥 계속 해서 사본 만드는 과정을 거치기 보다는 사본 생성 과정 없이 ref로 원본 player를 참조할 수 있도록 하였다.
Player player;
CreatePlayer(choice, out player);
위의 과정으로 player는 현재 메모리가 할당 되어 있는 상태기 때문에 ref 참조로 넘길 수 있다.
전체코드
using System;
namespace CSharp
{
class Program
{
enum ClassType
{
None = 0,
Knight = 1,
Archer = 2,
Mage = 3
}
struct Player
{
public int hp;
public int attack;
public ClassType type;
}
enum MonsterType
{
None = 0,
Slime = 1,
Orc = 2,
Skeleton = 3
}
struct Monster
{
public int hp;
public int attack;
}
static ClassType ChooseClass()
{
Console.WriteLine("직업을 선택하세요");
Console.WriteLine("[1] 기사");
Console.WriteLine("[2] 궁수");
Console.WriteLine("[3] 법사");
ClassType choice = ClassType.None;
string input = Console.ReadLine();
switch (input)
{
case "1":
choice = ClassType.Knight;
break;
case "2":
choice = ClassType.Archer;
break;
case "3":
choice = ClassType.Mage;
break;
}
return choice;
}
static void CreatePlayer(ClassType choice, out Player player)
{
player.type = choice;
// 기사(100, 10) 궁수(75, 12) 법사(50, 15)
switch (choice)
{
case ClassType.Knight:
player.hp = 100;
player.attack = 10;
break;
case ClassType.Archer:
player.hp = 75;
player.attack = 12;
break;
case ClassType.Mage:
player.hp = 50;
player.attack = 15;
break;
default:
player.hp = 0;
player.attack = 0;
break;
}
}
static void CreateRandomMonster(out Monster monster)
{
Random rand = new Random();
int randMonster = rand.Next(1, 4); // 1 ~ 3 중 랜덤 정수 리턴
switch(randMonster)
{
case (int)MonsterType.Slime:
Console.WriteLine("슬라임이 스폰 되었습니다!");
monster.hp = 20;
monster.attack = 2;
break;
case (int)MonsterType.Orc:
Console.WriteLine("오크가 스폰 되었습니다!");
monster.hp = 40;
monster.attack = 4;
break;
case (int)MonsterType.Skeleton:
Console.WriteLine("스켈레톤이 스폰 되었습니다!");
monster.hp = 30;
monster.attack = 3;
break;
default:
monster.hp = 0;
monster.attack = 0;
break;
}
}
static void Fight(ref Player player, ref Monster monster)
{
while(true)
{
// 플레이어가 몬스터 공격
monster.hp -= player.attack;
if (monster.hp <= 0)
{
Console.WriteLine("승리했습니다!");
Console.WriteLine($"남은 체력 : {player.hp}");
break;
}
// 몬스터 반격
player.hp -= monster.attack;
if (player.hp <= 0)
{
Console.WriteLine("패배했습니다!");
break;
}
}
}
static void EnterField(ref Player player)
{
Console.WriteLine("필드에 접속했습니다.");
// 랜덤으로 1~3 몬스터 중 하나를 리스폰
Monster monster;
CreateRandomMonster(out monster);
Console.WriteLine("[1] 전투 모드 돌입");
Console.WriteLine("[2] 일정 확률로 마을로 도망");
string input = Console.ReadLine();
if (input == "1")
{
Fight(ref player, ref monster);
}
else if (input == "2")
{
// 33 %
Random rand = new Random();
int randValue = rand.Next(0, 101);
if (randValue <= 33)
{
Console.WriteLine("도망치는데 성공했습니다!");
}
else
{
Fight(ref player, ref monster);
}
}
}
static void EnterGame(ref Player player)
{
while(true)
{
Console.WriteLine("마을에 접속했습니다.");
Console.WriteLine("[1] 월드로 간다.");
Console.WriteLine("[2] 로비로 돌아가기.");
String input = Console.ReadLine();
switch (input)
{
case "1":
EnterField(ref player);
break;
case "2":
return;
}
}
}
static void Main(string[] args)
{
while (true)
{
// 직업 고르기
ClassType choice;
choice = ChooseClass(); // Program.ChooseClass();
if (choice == ClassType.None)
continue;
// 플레이어 캐릭터 생성
Player player;
CreatePlayer(choice, out player);
Console.WriteLine($"HP{player.hp}, Attack{player.attack}");
// 게임 시작! 몬스터 생성 및 전투
EnterGame(ref player);
}
}
}
}
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 1-4-2. 객체지향 : 생성자, static (0) | 2023.08.02 |
---|---|
Part 1-4-1. 객체지향 : 객체 지향, 복사와 참조, 스택과 힙 (0) | 2023.08.02 |
★ Part 1-2-2. 코드의 흐름 제어 : 함수, ref, out (0) | 2023.08.02 |
Part 1-2-1. 코드의 흐름 제어 : if와 else, switch, 상수와 열거형, while, for, break, continue (0) | 2023.07.31 |
Part 1-1-2. 데이터 : 형변환, 스트링 포맷, 산술 연산, 비교 연산, 논리 연산 (0) | 2023.07.27 |