실습_zombiebattle
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
using System;
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json.Serialization;
class ZombieGame
{
// ══════════════════════════════════════════
// Step 1. 상수 + 열거형
// ══════════════════════════════════════════
// TODO: 게임에서 절대 바뀌지 않는 수치들을 상수로 선언하세요
// 플레이어의 최대 HP, 공격력, 방어력
const int PLAYER_MAX_HP = 100;
const int PLAYER_ATTACK = 25;
const int PLAYER_DEFENCE = 5;
// 스테이지당 좀비 수, 회복량, 최소 데미지
const int ZOMBIE_COUNT = 5;
const int HEAL_AMOUNT = 20;
const int MIN_DAMAGE = 1;
// TODO: 플레이어의 상태를 열거형으로 선언하세요
// 생존 / 위험(HP 30 이하) / 사망
enum PlayerState
{
Alived,
Danger,
Dead
}
// TODO: 좀비의 등급을 열거형으로 선언하세요
// 일반 / 달리기(공격력 높음) / 탱커(HP 높음)
enum ZombieType
{
Normal,
Runner,
Tanker
}
// ══════════════════════════════════════════
// Step 2. 인터페이스 (추상화)
// ══════════════════════════════════════════
// TODO: 데미지를 받을 수 있는 대상의 약속을 인터페이스로 선언하세요
// HP 조회, 생존 여부 확인, 데미지를 받는 기능이 필요합니다
public interface IDamageable
{
public int GetHp();
public bool IsAlive { get; }
public void TakeDamage(int damage);
}
// TODO: 회복할 수 있는 대상의 약속을 인터페이스로 선언하세요
public interface IHealable
{
public void Heal();
}
// TODO: 상태를 출력할 수 있는 대상의 약속을 인터페이스로 선언하세요
public interface IPrintable
{
public void PrintStatus();
}
// ══════════════════════════════════════════
// Step 3. Zombie 클래스
// IDamageable, IPrintable을 구현합니다
// ══════════════════════════════════════════
class Zombie : IDamageable, IPrintable
{
// TODO: 이름, HP, 최대HP, 공격력, 등급 프로퍼티를 선언하세요
// 외부에서 읽을 수는 있지만 직접 바꿀 수는 없어야 합니다
public string Name { get; private set; }
public int MaxHp { get; private set; }
public int Hp { get; private set; }
public int Attack { get; private set; }
public ZombieType Type { get; private set; }
// TODO: 생존 여부를 반환하는 읽기 전용 프로퍼티를 선언하세요
public bool IsAlive => Hp > 0;
// TODO: 생성자를 작성하세요
// 등급에 따라 HP와 공격력이 달라집니다
// 탱커: HP 80, 공격력 8 / 달리기: HP 30, 공격력 20 / 일반: HP 50, 공격력 12
public Zombie(ZombieType type, string name)
{
Type = type;
Name = name;
switch (type)
{
case ZombieType.Normal:
initialize(50, 12);
break;
case ZombieType.Runner:
initialize(30, 20);
break;
case ZombieType.Tanker:
initialize(80, 8);
break;
default: break;
}
void initialize(int hp, int atk)
{
MaxHp = hp;
Hp = MaxHp;
Attack = atk;
}
}
// TODO: 데미지를 받는 기능을 구현하세요
// HP가 0 아래로 내려가지 않도록 처리하세요
public void TakeDamage(int damage)
{
Hp -= damage;
if (Hp <= 0)
{
Hp = 0;
OnZombieDied?.Invoke(Name);
OnZombieDied = null;
}
}
public event Action<string> OnZombieDied;
// TODO: 상태를 출력하는 기능을 구현하세요
// 예시: [탱커] 썩은손 | HP: 80/80 | 공격력: 8
public void PrintStatus()
{
string label = Type ==ZombieType.Tanker ? "탱커" : Type== ZombieType.Runner ? "러너" : "일반";
Console.WriteLine($" [{label}] {Name} | HP: {Hp}/{MaxHp} | 공격력: {Attack}");
}
// 인터페이스 미구현 함수들 구현
public int GetHp()
{
return Hp;
}
}
// ══════════════════════════════════════════
// Step 4. Player 클래스 (핵심 — 이벤트 발행자)
//
// 델리게이트/이벤트 구조:
// Player 안에 event를 선언합니다
// HP가 바뀌거나 좀비를 처치할 때 event를 발행합니다
// 외부(Main)에서 event에 람다식으로 구독합니다
//
// event를 쓰는 이유:
// 외부에서 직접 Invoke할 수 없어야 하고
// += / -= 로만 구독/해지할 수 있어야 하기 때문입니다
// ══════════════════════════════════════════
class Player : IDamageable, IHealable, IPrintable
{
// TODO: 이름, HP, 최대HP, 공격력, 방어력 프로퍼티를 선언하세요
public string Name { get; private set; }
public int Hp { get; private set; }
public int MaxHp { get; private set; }
public int Attack { get; private set; }
public int Defence { get; private set; }
// TODO: 생존 여부 프로퍼티를 선언하세요
public bool IsAlive => Hp > 0;
// TODO: 현재 상태를 반환하는 프로퍼티를 선언하세요
// HP 조건에 따라 Dead / Danger / Alive 중 하나를 반환합니다
public PlayerState State
{
get
{
if (Hp <= 0)
{
return PlayerState.Dead;
}
else if (Hp < 30)
{
return PlayerState.Danger;
}
else
return PlayerState.Alived;
}
}
// TODO: 이벤트 두 개를 선언하세요
//
// 첫 번째: HP가 바뀔 때 발행하는 이벤트
// 구독자에게 이전 HP와 현재 HP를 전달해야 합니다
// Action<int, int> 형태로 선언하세요 (이전HP, 현재HP)
public event Action<int, int> OnHpChanged;
//
// 두 번째: 좀비를 처치했을 때 발행하는 이벤트
// 구독자에게 처치한 좀비 이름을 전달해야 합니다
// Action<string> 형태로 선언하세요
public event Action<string> OnZombieKilled;
// TODO: 생성자를 작성하세요
public Player(string name)
{
Name = name;
MaxHp = PLAYER_MAX_HP;
Hp = MaxHp;
Attack = PLAYER_ATTACK;
Defence = PLAYER_DEFENCE;
}
// TODO: 데미지를 받는 기능을 구현하세요
// 방어력만큼 데미지를 줄이되 최소 MIN_DAMAGE는 보장합니다
// HP가 바뀐 뒤 OnHpChanged 이벤트를 발행하세요
// 발행 방법: OnHpChanged?.Invoke(이전HP, 현재HP)
// ?.Invoke 를 쓰는 이유: 구독자가 없으면(null) 오류 없이 무시됩니다
// 예시: 준헌이(가) 3 피해를 받음 (HP: 97/100)
public void TakeDamage(int damage)
{
int prevHp = Hp;
int finalDamage = Math.Max((damage - Defence), MIN_DAMAGE);
Hp = Math.Max(Hp - finalDamage, 0);
Console.WriteLine($"{Name}이(가) {finalDamage} 피해를 받음 (HP: {Hp}/{MaxHp}");
if (prevHp != Hp)
{
OnHpChanged?.Invoke(prevHp, Hp);
}
}
// TODO: 회복 기능을 구현하세요
// 최대 HP를 초과하지 않도록 처리하세요
// 회복 후에도 OnHpChanged 이벤트를 발행하세요
// 예시: 회복! 준헌 HP: 70/100
public void Heal()
{
int prevHp = Hp;
Hp = Math.Min(Hp + HEAL_AMOUNT, PLAYER_MAX_HP);
Console.WriteLine($"회복! {Name} HP: {Hp}/{MaxHp}");
if (prevHp != Hp)
{
OnHpChanged?.Invoke(prevHp, Hp);
}
}
// TODO: 좀비를 처치했을 때 호출할 메서드를 작성하세요
// OnZombieKilled 이벤트를 발행합니다
// 전투 함수에서 좀비가 죽었을 때 이 메서드를 호출하세요
public void NotifyZombieKilled(string zombieName)
{
OnZombieKilled?.Invoke(zombieName);
}
// TODO: 상태 출력 기능을 구현하세요
// 예시: [플레이어] 준헌 | HP: 100/100
// Danger 상태면 끝에 위험 표시를 추가하세요
public void PrintStatus()
{
if (State == PlayerState.Danger)
{
Console.Write($"[플레이어] {Name} | HP: {Hp}/{MaxHp} !!DANGER!!");
}
else
Console.Write($"[플레이어] {Name} | HP: {Hp}/{MaxHp}");
}
//나머지 인터페이스 미구현 함수 구현
public int GetHp() => Hp;
}
// ══════════════════════════════════════════
// Step 5. 전투 함수들
// ══════════════════════════════════════════
// TODO: 플레이어가 좀비를 공격하는 함수를 작성하세요
// 탱커 좀비는 데미지가 절반입니다
// 좀비가 처치되면 player.NotifyZombieKilled(좀비이름)를 호출하세요
// 예시: 준헌 → 썩은손 공격! 12 피해
static void PlayerAttackZombie(Player player, Zombie target)
{
int damage = player.Attack;
if (target.Type == ZombieType.Tanker) damage /= 2;
damage = Math.Max(damage, MIN_DAMAGE);
target.TakeDamage(damage);
Console.WriteLine($"{[player.Name](http://player.Name)} 공격! {[target.Name](http://target.Name)}이 {damage} 피해를 입음!");
}
// TODO: 좀비가 플레이어를 공격하는 함수를 작성하세요
// 죽은 좀비는 공격하지 않습니다
// 예시: 썩은손 → 준헌 공격!
static void ZombieAttackPlayer(Zombie Zombie, Player player)
{
if (!Zombie.IsAlive)
return;
player.TakeDamage(Zombie.Attack);
Console.WriteLine($"{[Zombie.Name](http://Zombie.Name)} 공격! {[player.Name](http://player.Name)}이 {Zombie.Attack} 피해를 입음!");
}
// TODO: 인덱스를 받아서 좀비를 생성하는 함수를 작성하세요
// 인덱스 나머지로 등급을 결정하세요 (0:탱커 / 1:달리기 / 2:일반)
// 이름 목록: 썩은손, 피투성이, 절름발이, 괴물, 검은눈
static Zombie CreateZombie(int index)
{
ZombieType type = ZombieType.Normal;
string[] names = { "썩은손", "피투성이", "절름발이", "괴물", "검은눈" };
switch (index)
{
case 0:
type = ZombieType.Tanker;
break;
case 1:
type = ZombieType.Runner;
break;
case 2:
type = ZombieType.Normal;
break;
default:
type = ZombieType.Normal;
break;
}
return new Zombie(type, names[index % names.Length]);
}
// TODO: 전투 결과를 출력하는 함수를 작성하세요
// 승리면 클리어 메시지와 생존 HP를 출력하세요
// 패배면 게임 오버 메시지를 출력하세요
static void PrintBattleResult(Player player, bool isWon)
{
if (isWon)
{
Console.WriteLine("플레이어 승리!");
Console.WriteLine($"생존 Hp: {player.Hp}/{player.MaxHp}");
}
else
{
Console.WriteLine("패배하였습니다!");
Console.WriteLine("게임 오버");
}
}
// ══════════════════════════════════════════
// Step 6. 전투 루프
// ══════════════════════════════════════════
// TODO: 전투 전체 흐름을 담당하는 함수를 작성하세요
// 반환값은 승리 여부(bool)입니다
//
// 흐름:
// 좀비 배열을 만들고 채운 뒤 전체 상태를 출력합니다
// 플레이어가 살아있는 동안 반복합니다
// 살아있는 좀비가 없으면 승리를 반환합니다
// 살아있는 첫 번째 좀비와 1:1 전투를 진행합니다
// 플레이어가 위험 상태면 자동으로 회복합니다
// 반복이 끝나면 패배를 반환합니다
static bool BattleLoop(Player player)
{
//좀비생성
Zombie[] zombies = new Zombie[ZOMBIE_COUNT];
for (int i = 0; i < ZOMBIE_COUNT; i++)
{
zombies[i] = CreateZombie(i);
zombies[i].PrintStatus();
zombies[i].OnZombieDied += player.NotifyZombieKilled;
}
while (player.IsAlive)
{
int aliveZombie = 0;
for (int i = 0; i < ZOMBIE_COUNT; i++)
{
if (zombies[i].IsAlive)
{
aliveZombie++;
}
}
if (aliveZombie == 0) return true;
for (int i = 0; i < ZOMBIE_COUNT; i++)
{
if (!zombies[i].IsAlive)
continue;
PlayerAttackZombie(player, zombies[i]);
if (zombies[i].IsAlive)
{
ZombieAttackPlayer(zombies[i], player);
Thread.Sleep(2000);
}
else
{
Console.WriteLine("승리하였습니다!");
}
break;
}
if (player.State == PlayerState.Danger)
{
player.Heal();
}
}
return false;
}
// ══════════════════════════════════════════
// Step 7. 통합 실행
//
// 이벤트 구독은 전부 여기서 합니다
// Player 안에 UI/로그 코드를 넣지 않습니다
// Player는 "이벤트를 발행"하고
// Main이 "어떻게 반응할지"를 결정합니다
// ══════════════════════════════════════════
static void Main()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
// TODO: 게임 타이틀을 출력하세요
Console.WriteLine("좀비배틀");
// TODO: 플레이어 이름을 입력받으세요
string name = Console.ReadLine();
// 비어있으면 "생존자"로 설정하세요
if (string.IsNullOrEmpty(name))
{
name = "생존자";
}
// TODO: Player 객체를 생성하세요
Player player = new Player(name);
// TODO: OnHpChanged 이벤트에 람다식으로 두 가지 반응을 구독하세요
//
// 첫 번째 구독 — HP 막대 그래프 출력
// HP 비율을 계산해서 10칸짜리 막대로 출력합니다
// 예시: [UI] [██████░░░░] 60/100
// 힌트: new string('█', 채워진칸) + new string('░', 빈칸)
// player.OnHpChanged += (oldHp, newHp) => { ... }
Action<int, int> hpBarHandler = (prevHp, newHp) =>
{
//현재 hp비율 계산
float ratio = (float)newHp / player.MaxHp;
int filled = (int)(ratio * 10);
string bar = new string('█', filled) + new string('░', (10 - filled));
Console.WriteLine($"[UI] [{bar}] {newHp}/{player.Hp}");
};
player.OnHpChanged += hpBarHandler;
//
// 두 번째 구독 — 전투 로그 출력
// HP 변화 내역을 출력합니다
// 예시: [LOG] 준헌 HP: 100→70
// player.OnHpChanged += (oldHp, newHp) => { ... }
player.OnHpChanged += (prevHp, newHp) =>
{
Console.WriteLine($"[LOG] {[player.Name](http://player.Name)} HP: {prevHp}->{newHp}");
}; // 람다식은 해제가 불가능함.
// TODO: OnZombieKilled 이벤트에 람다식으로 구독하세요
// 처치 내역을 출력합니다
// 예시: [킬로그] 썩은손 처치!
// player.OnZombieKilled += (name) => { ... }
player.OnZombieKilled += (name) =>
{
Console.WriteLine($"[킬로그] {name} 처치!");
};
// TODO: 시작 메시지를 출력하세요
// 예시: 준헌, 좀비 5마리를 처치하라!
Console.WriteLine($"{[player.Name](http://player.Name)}, 좀비 {ZOMBIE_COUNT}마리를 처치하라!");
// TODO: 전투를 실행하고 결과를 받으세요
bool result = BattleLoop(player);
// TODO: 전투 결과를 출력하세요
PrintBattleResult(player, result);
// TODO: HP 막대 그래프 구독을 해지하세요
//
// 주의: 람다식은 변수에 저장하지 않으면 해지할 수 없습니다
// 해지가 필요한 구독은 람다식을 미리 변수에 저장한 뒤 등록하세요
// 예시:
// Action<int, int> hpBarHandler = (oldHp, newHp) => { ... }
// player.OnHpChanged += hpBarHandler
// ... 나중에 ...
// player.OnHpChanged -= hpBarHandler
}
}
XR을 활용한 게임 개발 3기(유니티) 수강생입니다. 곧 수료 하지만 앞으로 이곳에 가끔 저의 개발 경험이 나 지식 기록할까 합니다. 더 나아가 이 사이트가 제 개인위키의 역할을 할 수 있으면 좋겠습니다. 한국 게임 시장을 흔들겠습니다

게임 광고 수익은 단순히 광고를 붙이는 것이 아니라, 여러 광고 네트워크를 경쟁시켜 가장 높은 수익을 만드는 구조입니다.

안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.