C#의 기초 - 9 소코반 만들기

krokrai·2025년 12월 20일

소코반 만들기

개요

  • 게임 개요
  • 게임 내 함수들 소계
    • Main()
    • ScanerBool(byte)
    • Move()
    • Swaper(int, int)
    • Pointer(Position, byte)
    • Ending()
    • Loader()
    • MapGen()
    • MapPrinter()
    • ScanerPos(byte)
    • Position
  • 최종 코드
  • 개인 소감

게임 개요

  • 소코반은 모든 폭탄 또는 박스(이하 기물)를 특정 지점으로 밀어서 모든 기물이 정해진 지점에 도달하게 되면, 게임이 종료 또는 다음 스테이지로 넘어가는 형식의 게임입니다.
  • 조작법은 W : 위로, A : 왼쪽으로, S : 아래로, D : 오른쪽으로 이동할 수 있으며, R키를 눌러 재시작할 수 있습니다.
  • Console에 문자로 표현하였으며, 매우 간단하게 제작되었습니다.

○ : 골 / ● : 폭탄 / ◆ : 플레이어

게임 내 함수들 소계

Main()


// 게임 내에 사용할 변수를 전역으로 선언하였으며, 큰 크기로 사용하지 않을 예정이라 byte(1 byte)로 선언했습니다.
const byte PLAYER = 1; // 플레이어의 객체 값입니다.
const byte PLAYER_ON_GOAL = 2; // 플레이어가 골 지점에 들어가 있을때 표시할 값입니다.
const byte BOMB = 3; // 폭탄
const byte BOMB_ON_GOAL = 4; // 폭탄 위 골
const byte GOAL = 5; // 골
const byte WALL = 9; // 벽
const byte EMPTY = 0; // 빈공간

static byte[,] map = new byte[20,20]; // 맵은 고정크기로 20 * 20으로 선업했습니다.
const byte MAXBOMB = 8; // 최대 폭탄 갯수(후술할 맵 생성기에서 사용)

static byte bombs = 0; // 폭탄 갯수

static int CountFootStep = 0; // 후술할 점수 계산에 사용될 예정입니다.

static Position player = new Position(); // 후술할 좌표를 저장하기 위한 struct입니다.

static void Main(string[] args)
{
	// 커서(마우스가 아닌 Console 내에 깜빡이는 부분)를 Console 내에 보이게 할 것인지 정하는 명령어입니다.
    Console.CursorVisible = true;
    // 함수입니다.
    Loader();
    // 게임에 승리 조건을 만족하기 전까지 게임이 종료 되지 않게 무한 루프를 걸어줬습니다.
    while(true)
    {
    	// BOMB이라고 선언된 전역변수를 넘겨줘 BOMB이 있는 지 없는 지를 판별합니다.
        if (!ScanerBool(BOMB))
        {
        	// Ending이라는 함수를 만들어 게임 종료 조건을 검사하며, 만약 재시작을 원하는 경우도 추가되어 있습니다.
            if(Ending())
            {
                break; // 조건 성립시 게임 종료
            }
        }
        Move(); // 조건에 부합하지 않는 경우 움직임으로 넘겨줍니다.
    }
}

ScanerBool(byte)

  • bool 타입으로 반환하는 검사 함수입니다.

static bool ScanerBool(byte target)
{
	// 2중첩 for문을 사용하여, 모든 맵에서 검사를 진행하지만, 가장자리 부분은 벽만 존재하기 때문에 i와 j에 1을 넣고 map 크기보다 1 작게 검사를 합니다.
    for (int i = 1; i < map.GetLength(0) - 1; i++)
    {
        for (int j = 1; j < map.GetLength(1) - 1; j++)
        {
        	// 입력받은 객체가 존재하는 검사 후 있을 경우 반환하며, 종료됩니다.
            if (map[i,j] == target)
            {
                return true;
            }
        }
    }
    // 없는 경우 false로 반환됩니다.
    return false;
}

Move()


// 움직임을 제어할 함수입니다.
static void Move()
{
	// key에 키보드로 입력 받은 키 값을 저장하며 ReadKey(true)는 console에 입력된 키 값을 보이지 않게 합니다.
    ConsoleKey key = Console.ReadKey(true).Key;
    // 움직이는 것이기 때문에 움직임에 1을 더했습니다.
    CountFootStep++;
    // 키보드로 입력 받은 값을 검사할 switch(key)입니다.
    switch(key)
    {
    	// w키를 입력했을 시, 해당 좌표에 맞게 움직임을 제어합니다.
        case ConsoleKey.W:
        	// 위로 이동시 console 상에서는 반대로 가는 것이기 때문에 -1을 해줍니다.
            Swaper(0, -1);
            break;
        case ConsoleKey.A:
            Swaper(-1, 0);
            break;
        case ConsoleKey.S:
            Swaper(0, 1);
            break;
        case ConsoleKey.D:
            Swaper(1, 0);
            break;
            // R키를 누를 시 이동이 아닌 재시작을 할 것이기 때문에 Loader()라는 함수를 만들었습니다.
        case ConsoleKey.R:
            Loader();
            break;
    }
}

Swaper(int ,int)

  • 입력된 좌표로 이동시 값을 바꿔줘야 하기 때문에 함수로 분리했습니다.
static void Swaper(int dX, int dY)
{
	// 현제 위치에서 다음 위치에 있는 객체를 조사 및 교체를 위해 좌표를 저장합니다.
    Position tPos = new Position()
    {
        X = player.X + dX,
        Y = player.Y + dY
    };
    // 만약 폭탄을 미는 경우 그 다음에 있는 객체를 조사해야 하기 때문에 추가로 더 더해서 저장합니다.
    Position tOPos = new Position()
    {
        X = tPos.X + dX,
        Y = tPos.Y + dY
    };
    //처음 움직였을때 갖일 수 있는 변수는 빈 공간, 골 위에 폭탄, 폭탄, 골, 벽이 있습니다.
    // 벽은 따로 조건을 넣지 않아도 조건 검사에서 참이 될 수 없기 때문에 따로 넣지 않았습니다.
    // 다음 좌표가 공간인 경우
    if (map[tPos.Y,tPos.X] == EMPTY)
    {
    	//빈 공간인 경우 현제 좌표만 검사하면 되고 현제 좌표에 변수는 2가지 밖에 없기 때문에 2가지만 넣었습니다.
        // 현제 좌표가 골 위에 있는 경우
        if (map[player.Y,player.X] == PLAYER_ON_GOAL)
        {
        	// 골인 경우, 현제 좌표에는 goal을 넣고 다음 좌표에는 플레이어를 넣습니다.
            map[player.Y, player.X] = GOAL;
            // 다음 좌표에 플레이어 넣기
            map[tPos.Y, tPos.X] = PLAYER;
            // Printer(Position, byte)함수로 넣어줍니다.
            Printer(player, GOAL);
            Printer(tPos,PLAYER);
            // 마지막에는 플레이어의 좌표를 넣어줍니다.
            player = tPos;
        }
        // 아닌 경우에는 빈 공간 위에 서 있을 것이기 때문에 빈 공간으로 대체합니다.
        else
        {
            map[player.Y, player.X] = EMPTY;
            map[tPos.Y, tPos.X] = PLAYER;
            Printer(player, EMPTY);
            Printer(tPos, PLAYER);
            player = tPos;
        }
    }
    // 다음 좌표가 골 위에 폭탄인 경우
    else if(map[tPos.Y, tPos.X] == BOMB_ON_GOAL)
    {
    	// 현제 좌표가 골 위에 플레이어가 있는 경우
        if(map[player.Y, player.X] == PLAYER_ON_GOAL)
        {
        	// 이 다음 다음 좌표(이동 방향에 2번째 지점)이 EMPTY인 경우
            if (map[tPos.Y + dY, tPos.X + dX] == EMPTY)
            {
                map[player.Y, player.X] = GOAL;
                map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                map[tOPos.Y, tOPos.X] = BOMB;
                Printer(player, GOAL);
                Printer(tPos, PLAYER_ON_GOAL);
                Printer(tOPos, BOMB);
                player = tPos;
            }
            // 골인 경우
            else if (map[tPos.Y + dY, tPos.X + dX] == GOAL)
            {
                map[player.Y, player.X] = GOAL;
                map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                Printer(player, GOAL);
                Printer(tPos, PLAYER_ON_GOAL);
                Printer(tOPos, BOMB_ON_GOAL);
                player = tPos;
            }
        }
        // 플레이어가 골 위에 있는 경우가 아니라면, 빈 공간에 있는 경우 밖에 없기 때문에 2가지만 검사합니다.
        else
        {
        	// 다음 다음 이 빈 공간인 경우
            if (map[tOPos.Y, tOPos.X] == EMPTY)
            {
                map[player.Y, player.X] = EMPTY;
                map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                map[tOPos.Y, tOPos.X] = BOMB;
                Printer(player, EMPTY);
                Printer(tPos, PLAYER_ON_GOAL);
                Printer(tOPos, BOMB);
                player = tPos;
            }
            // 골인 경우
            else if (map[tOPos.Y, tOPos.X] == GOAL)
            {
                map[player.Y, player.X] = EMPTY;
                map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                Printer(player, EMPTY);
                Printer(tPos, PLAYER_ON_GOAL);
                Printer(tOPos, BOMB_ON_GOAL);
                player = tPos;
            }
        }
    }
    // 다음 지점이 폭탄인 경우
    else if(map[tPos.Y, tPos.X] == BOMB)
    {
    	// 위와 같습니다.
        if(map[player.Y, player.X] == PLAYER_ON_GOAL)
        {
            if (map[tOPos.Y, tPos.X + dX] == EMPTY)
            {
                map[player.Y, player.X] = GOAL;
                map[tPos.Y, tPos.X] = PLAYER;
                map[tOPos.Y, tOPos.X] = BOMB;
                Printer(player, GOAL);
                Printer(tPos, PLAYER);
                Printer(tOPos, BOMB);
                player = tPos;
            }
            else if (map[tOPos.Y, tOPos.X] == GOAL)
            {
                map[player.Y, player.X] = GOAL;
                map[tPos.Y, tPos.X] = PLAYER;
                map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                Printer(player, GOAL);
                Printer(tPos, PLAYER);
                Printer(tOPos, BOMB_ON_GOAL);
                player = tPos;
            }
        }
        else
        {
            if (map[tOPos.Y, tPos.X + dX] == EMPTY)
            {
                map[player.Y, player.X] = EMPTY;
                map[tPos.Y, tPos.X] = PLAYER;
                map[tOPos.Y, tOPos.X] = BOMB;
                Printer(player, EMPTY);
                Printer(tPos, PLAYER);
                Printer(tOPos, BOMB);
                player = tPos;
            }
            else if (map[tOPos.Y, tOPos.X] == GOAL)
            {
                map[player.Y, player.X] = EMPTY;
                map[tPos.Y, tPos.X] = PLAYER;
                map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                Printer(player, EMPTY);
                Printer(tPos, PLAYER);
                Printer(tOPos, BOMB_ON_GOAL);
                player = tPos;
            }
        }
    }
    // 다음 좌표가 골인 경우
    else if (map[tPos.Y, tPos.X] == GOAL)
    {
        if (map[player.Y, player.X] == PLAYER_ON_GOAL)
        {
            map[player.Y, player.X] = GOAL;
            map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
            Printer(player, GOAL);
            Printer(tPos, PLAYER_ON_GOAL);
            player = tPos;
        }
        else
        {
            map[player.Y, player.X] = EMPTY;
            map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
            Printer(player, EMPTY);
            Printer(tPos, PLAYER_ON_GOAL);
            player = tPos;
        }
    }
}

Printer(Position, byte)

  • 받은 좌표에 받은 문자로 교체해줍니다.
// 2중첩 For문을 사용하여, 제작하여도 되지만, 그런 경우, 이동키를 누를 경우 반짝이는 현상(해당 console을 지운 후 빠르게 다시 작성 되지만, 눈에 보이기 때문)이 있으며, 꾹 누를 경우, 매우 빨라 화면이 표시 되지 않습니다.
// 그래서 다른 방식(왔던 곳과 이동할 곳만 선택적으로 덮어쓰기)을 사용했습니다.
static void Printer(Position pos, byte target)
{
	//Console에서 지원하는 SetCursorPosition()함수를 이용하여, 해당 좌표로 이동(X만 *2를 한 이유는 ●와 같은 문자는 2 byte(2개의 공간)이 필요하기 때문에 빈공간도 2 byte를 점유하기때문에, 해당 좌표보다 2배로 더 이동해야 합니다.) 후 덮어쓰기 했습니다.
    Console.SetCursorPosition(pos.X*2, pos.Y);
    switch (target)
    {
    	// 받은 수에 부합하는 문자를 표시합니다.
        case EMPTY:
            Console.Write("  ");
                break;
        case BOMB:
            Console.Write("●");
            break;
        case BOMB_ON_GOAL:
            Console.Write("◎");
            break;
        case GOAL:
            Console.Write("○");
            break;
        case PLAYER:
            Console.Write("◆");
            break;
        case PLAYER_ON_GOAL:
            Console.Write("@");
            break;
            // default로 예외처리는 따로 하지 않았습니다. (플레이어의 직접적인 입력이 따로 없었기 때문이지만, 협업 시 반드시 넣어야합니다.)
    }
}

Ending()

  • 게임 종료인지 확인하며, 게임 재시작을 원하는 경우 재시작 할 수 있습니다.
static bool Ending()
{
	// 간단하게 넣은 점수 계산입니다.
    float score = (float) (bombs * 500) / CountFootStep;
    // 표시를 맵보다 아래쪽에 출력하기 위해 커서 위치를 강제 이동시켰습니다.
    Console.SetCursorPosition(map.GetLength(1), map.GetLength(0));
    // 축하 메세지 및 이동거리와 점수 출력
    Console.WriteLine("\n축하합니다.\n"+
        "게임을 클리어 하셨습니다.\n"+
        $"총 이동거리는 {CountFootStep}입니다.\n"+
        $"당신의 점수는 {score.ToString("n3")}입니다.",
        "재시작할려면, R 키를 눌러주세요.");
    // R키를 누를 경우, 재시작 할 수 있게 예외처리 하였습니다.
    if (Console.ReadKey().Key == ConsoleKey.R)
    {
    	// 재시작 시 맵을 재생성해야 하기 때문에 따로 함수를 제작하였습니다.
        Loader();
        return false;
    }
    return true;
}

Loader()

  • 재시작시 새로 불러오기 위한 함수입니다.

static void Loader()
{
	// Console의 모든 출력을 지운 후
    Console.Clear();
    // MapGen()이라는 함수를 통해 맵을 재생성 후
    MapGen();
    // Player의 위치를 찾은 후 저장하고
    player = ScanerPos(PLAYER);
    // MapPrint() 함수를 이용해 맵을 출력합니다.
    MapPrint();
}

MapGen()

  • 맵 무작위 생성용 함수입니다.

static void MapGen()
{
	// 무작위로 생성하기 위해 random 함수를 사용했습니다.
    Random rd = new Random();
    // 플레이어는 1개의 객체만 존재해야하기 때문에 bool 타입으로 선언했습니다.
    bool playerAlive = false;
    // 만약 운이 낮아 플레이어가 끝지점까지 생성이 안될 경우 강제 생성되게하기 위해 short 타입(map은 20*20 으로 400이지만 byte는 255까지 밖에 검사할 수 없기 때문에)으로 선언했습니다.
    short playerExepsion = 0;
    // 폭탄의 갯수를 셉니다.
    byte bombCounter = 0;
    // 골의 갯수를 셉니다. 폭탄보다 갯수가 많아지면 안되고 적으면 안되기 때문입니다.
    byte goalCounter = 0;
    for (int i = 0; i < map.GetLength(0); i++)
    {
        for (int j = 0; j < map.GetLength(1); j++)
        {
        	// 가장자리는 벽만 있을 예정이기 때문에 조건을 걸어 줍니다.
            // i와 j가 0인 경우는 가장자리이며, map의 크기(배열은 0부터 세기 때문에 -1을 해줍니다)의 끝 부분 또한 가장자리입니다.
            if ((i == 0 || i == map.GetLength(0)-1 ) || ( j == 0 || j == map.GetLength(1)-1) )
            {
            	// 해당 좌표에 WALL이라는 숫자(객체)를 넣어줍니다.
                map[i, j] = WALL;
            }
            // 가장자리가 아닌경우 랜덤함수로 0~49(1/50 확률로) 플레이어가 생성되며, 만약 특정 지점까지 갔을때까지 생성이 안된 경우 강제 생성됩니다.
            // 플레이어는 2개 이상의 객체가 존재하면 안되기 때문에 존재하는 지 검사합니다.
            else if ((rd.Next(0,50) == 0 || playerExepsion > 300) && !playerAlive )
            {
                map[i,j] = PLAYER;
                // 생성된 경우 예외처리를 위해 true로 전환합니다.
                playerAlive = true;
            }
            // 폭탄의 최대치 이상 스폰하지 않게 하면서, 1/30 확률로 생성되고, 벽과 바로 붙은 경우, 문제를 해결할 수 없는 상황이 만들어 질 수 있기 때문에 예외처리해줍니다.
            else if ((bombCounter < MAXBOMB && rd.Next(0,30) == 0) && ((i > 1 && i < map.GetLength(0) - 2) && (j > 1 && j < map.GetLength(1) - 2)))
            {
            	// 폭탄이 생성된 경우, 최대 갯수를 넘지 않게 하기 위해 1을 더해줍니다.
                bombCounter++;
                map[i, j] = BOMB;
            }
            // 폭탄보다 많은 양의 goal을 생성하면 안되기 때문에, 예외처리해주면서, 맵에 끝(벽 바로 전 지점)에 가까워진 경우, 강제로 생성하게 만듭니다.
            // 완전 무작위로 생성할 경우, 폭탄 갯수보다 낮게 생성 될 수 있기 때문입니다.
            else if ((bombCounter > goalCounter && rd.Next(0, 10) == 0) || (i == (map.GetLength(1)-2) && bombCounter > goalCounter ))
            {
                goalCounter++;
                map[i, j] = GOAL;
            }
            else
            {
            	// 그 외에 경우에는 빈 공간으로 넣어줍니다.
                map[i, j] = EMPTY;
            }
            // 변수를 사용했지만 지금 생각해보면, i와 j만을 이용해서, 예외처리를 할 수 있었던것 같습니다.
            if (!playerAlive)
                playerExepsion++;
        }
    }
    // 이후 폭탄 갯수를 전역변수에 전달합니다.
    bombs = bombCounter;
}

MapPrinter()

  • 생성된 맵은 숫자로만 이루어져 있어 사람에게는 가독성이 낮기 때문에 비교적 더 친화적인 문자의 형태로 전환해줍니다.

static void MapPrint()
{
    for (int i = 0; i < map.GetLength(0); i++)
    {
        for (int j = 0; j < map.GetLength(1); j++)
        {
        	// map의 좌표를 받아와서 맞는 문자로 전환해서 표시합니다.
            switch(map[i,j])
            {
                case WALL:
                    Console.Write("※");
                    break;
                case GOAL:
                    Console.Write("○");
                    break;
                case BOMB:
                    Console.Write("●");
                    break;
                case PLAYER:
                    Console.Write("◆");
                    break;
                case EMPTY:
                	// 위에서도 서술했 듯이 2칸으로 표시합니다.
                    Console.Write("  ");
                    break;
            }
        }
        Console.WriteLine();
    }
    // 폭탄의 갯수를 표시해줍니다.
    Console.WriteLine($"폭탄 갯수 : {bombs}");
}

ScanerPos(byte)

  • 해당 객체의 좌표를 반환합니다.

static Position ScanerPos(byte target)
{
    Position pos;
    for (int i = 1; i < map.GetLength(0) - 1; i++)
    {
        for (int j = 1; j < map.GetLength(1) - 1; j++)
        {
        	// 맵에 처음으로 식별된 객체의 좌표를 반환합니다.
            if (map[i, j] == target)
            {
                return pos = new Position()
                {
                    X = j,
                    Y = i
                };
            }
        }
    }
    // 만약 식별하지 못한 경우, -1로 반환합니다.
    // 저는 간단하게만 작성하여, 반환처리하지 않았지만, 항상 모든 경우에 대비하기 위해, 반드시 예외처리를 해야합니다.
    return pos = new Position()
    {
        X = -1,
        Y = -1
    };
}

Position


// struct로 선언된 좌표값을 저장할 구조체입니다.
public struct Position
{
    public int X;
    public int Y;
}

최종코드

  • 최종 코드입니다.

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;


namespace ConsoleProject_0
{
    internal class Program
    {
        const byte PLAYER = 1;
        const byte PLAYER_ON_GOAL = 2;
        const byte BOMB = 3;
        const byte BOMB_ON_GOAL = 4;
        const byte GOAL = 5;
        const byte WALL = 9;
        const byte EMPTY = 0;

        static byte[,] map = new byte[20,20];
        const byte MAXBOMB = 8;

        static byte bombs = 0;

        static int CountFootStep = 0;

        static Position player = new Position();

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            Loader();
            while(true)
            {
                if (!ScanerBool(BOMB))
                {
                    if(Ending())
                    {
                        break;
                    }
                }
                Move();
            }
        }

        static void Move()
        {
            ConsoleKey key = Console.ReadKey(true).Key;
            CountFootStep++;
            switch (key)
            {
                case ConsoleKey.W:
                    Swaper(0, -1);
                    break;
                case ConsoleKey.A:
                    Swaper(-1, 0);
                    break;
                case ConsoleKey.S:
                    Swaper(0, 1);
                    break;
                case ConsoleKey.D:
                    Swaper(1, 0);
                    break;
                case ConsoleKey.R:
                    Loader();
                    break;
            }
        }

        static void Swaper(int dX, int dY)
        {
            Position tPos = new Position()
            {
                X = player.X + dX,
                Y = player.Y + dY
            };
            Position tOPos = new Position()
            {
                X = tPos.X + dX,
                Y = tPos.Y + dY
            };
            if (map[tPos.Y,tPos.X] == EMPTY)
            {
                if (map[player.Y,player.X] == PLAYER_ON_GOAL)
                {
                    map[player.Y, player.X] = GOAL;
                    map[tPos.Y, tPos.X] = PLAYER;
                    Printer(player, GOAL);
                    Printer(tPos,PLAYER);
                    player = tPos;
                }
                else
                {
                    map[player.Y, player.X] = EMPTY;
                    map[tPos.Y, tPos.X] = PLAYER;
                    Printer(player, EMPTY);
                    Printer(tPos, PLAYER);
                    player = tPos;
                }
            }
            else if(map[tPos.Y, tPos.X] == BOMB_ON_GOAL)
            {
                if(map[player.Y, player.X] == PLAYER_ON_GOAL)
                {
                    if (map[tPos.Y + dY, tPos.X + dX] == EMPTY)
                    {
                        map[player.Y, player.X] = GOAL;
                        map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                        map[tOPos.Y, tOPos.X] = BOMB;
                        Printer(player, GOAL);
                        Printer(tPos, PLAYER_ON_GOAL);
                        Printer(tOPos, BOMB);
                        player = tPos;
                    }
                    else if (map[tPos.Y + dY, tPos.X + dX] == GOAL)
                    {
                        map[player.Y, player.X] = GOAL;
                        map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                        map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                        Printer(player, GOAL);
                        Printer(tPos, PLAYER_ON_GOAL);
                        Printer(tOPos, BOMB_ON_GOAL);
                        player = tPos;
                    }
                }
                else
                {
                    if (map[tOPos.Y, tOPos.X] == EMPTY)
                    {
                        map[player.Y, player.X] = EMPTY;
                        map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                        map[tOPos.Y, tOPos.X] = BOMB;
                        Printer(player, EMPTY);
                        Printer(tPos, PLAYER_ON_GOAL);
                        Printer(tOPos, BOMB);
                        player = tPos;
                    }
                    else if (map[tOPos.Y, tOPos.X] == GOAL)
                    {
                        map[player.Y, player.X] = EMPTY;
                        map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                        map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                        Printer(player, EMPTY);
                        Printer(tPos, PLAYER_ON_GOAL);
                        Printer(tOPos, BOMB_ON_GOAL);
                        player = tPos;
                    }
                }
            }
            else if(map[tPos.Y, tPos.X] == BOMB)
            {
                if(map[player.Y, player.X] == PLAYER_ON_GOAL)
                {
                    if (map[tOPos.Y, tPos.X + dX] == EMPTY)
                    {
                        map[player.Y, player.X] = GOAL;
                        map[tPos.Y, tPos.X] = PLAYER;
                        map[tOPos.Y, tOPos.X] = BOMB;
                        Printer(player, GOAL);
                        Printer(tPos, PLAYER);
                        Printer(tOPos, BOMB);
                        player = tPos;
                    }
                    else if (map[tOPos.Y, tOPos.X] == GOAL)
                    {
                        map[player.Y, player.X] = GOAL;
                        map[tPos.Y, tPos.X] = PLAYER;
                        map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                        Printer(player, GOAL);
                        Printer(tPos, PLAYER);
                        Printer(tOPos, BOMB_ON_GOAL);
                        player = tPos;
                    }
                }
                else
                {
                    if (map[tOPos.Y, tPos.X + dX] == EMPTY)
                    {
                        map[player.Y, player.X] = EMPTY;
                        map[tPos.Y, tPos.X] = PLAYER;
                        map[tOPos.Y, tOPos.X] = BOMB;
                        Printer(player, EMPTY);
                        Printer(tPos, PLAYER);
                        Printer(tOPos, BOMB);
                        player = tPos;
                    }
                    else if (map[tOPos.Y, tOPos.X] == GOAL)
                    {
                        map[player.Y, player.X] = EMPTY;
                        map[tPos.Y, tPos.X] = PLAYER;
                        map[tOPos.Y, tOPos.X] = BOMB_ON_GOAL;
                        Printer(player, EMPTY);
                        Printer(tPos, PLAYER);
                        Printer(tOPos, BOMB_ON_GOAL);
                        player = tPos;
                    }
                }
            }
            else if (map[tPos.Y, tPos.X] == GOAL)
            {
                if (map[player.Y, player.X] == PLAYER_ON_GOAL)
                {
                    map[player.Y, player.X] = GOAL;
                    map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                    Printer(player, GOAL);
                    Printer(tPos, PLAYER_ON_GOAL);
                    player = tPos;
                }
                else
                {
                    map[player.Y, player.X] = EMPTY;
                    map[tPos.Y, tPos.X] = PLAYER_ON_GOAL;
                    Printer(player, EMPTY);
                    Printer(tPos, PLAYER_ON_GOAL);
                    player = tPos;
                }
            }
        }

        static void Printer(Position pos, byte target)
        {
            Console.SetCursorPosition(pos.X*2, pos.Y);
            switch (target)
            {
                case EMPTY:
                    Console.Write("  ");
                        break;
                case BOMB:
                    Console.Write("●");
                    break;
                case BOMB_ON_GOAL:
                    Console.Write("◎");
                    break;
                case GOAL:
                    Console.Write("○");
                    break;
                case PLAYER:
                    Console.Write("◆");
                    break;
                case PLAYER_ON_GOAL:
                    Console.Write("@");
                    break;

            }
            
        }

        static bool Ending()
        {
            float score = (float) (bombs * 500) / CountFootStep;
            Console.SetCursorPosition(map.GetLength(1), map.GetLength(0));
            Console.WriteLine("\n축하합니다.\n"+
                "게임을 클리어 하셨습니다.\n"+
                $"총 이동거리는 {CountFootStep}입니다.\n"+
                $"당신의 점수는 {score.ToString("n3")}입니다.",
                "재시작할려면, R 키를 눌러주세요.");
            if (Console.ReadKey().Key == ConsoleKey.R)
            {
                Loader();
                return false;
            }
            return true;
        }

        static void Loader()
        {
            Console.Clear();
            MapGen();
            player = ScanerPos(PLAYER);
            MapPrint();
        }

        static bool ScanerBool(byte target)
        {
            for (int i = 1; i < map.GetLength(0) - 1; i++)
            {
                for (int j = 1; j < map.GetLength(1) - 1; j++)
                {
                    if (map[i,j] == target)
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        static Position ScanerPos(byte target)
        {
            Position pos;
            for (int i = 1; i < map.GetLength(0) - 1; i++)
            {
                for (int j = 1; j < map.GetLength(1) - 1; j++)
                {
                    if (map[i, j] == target)
                    {
                        return pos = new Position()
                        {
                            X = j,
                            Y = i
                        };
                    }
                }
            }
            return pos = new Position()
            {
                X = -1,
                Y = -1
            };
        }
        static void MapPrint()
        {
            for (int i = 0; i < map.GetLength(0); i++)
            {
                for (int j = 0; j < map.GetLength(1); j++)
                {
                    switch(map[i,j])
                    {
                        case WALL:
                            Console.Write("※");
                            break;
                        case GOAL:
                            Console.Write("○");
                            break;
                        case BOMB:
                            Console.Write("●");
                            break;
                        case PLAYER:
                            Console.Write("◆");
                            break;
                        case EMPTY:
                            Console.Write("  ");
                            break;
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine($"폭탄 갯수 : {bombs}");
        }
        static void MapGen()
        {
            Random rd = new Random();
            bool playerAlive = false;
            short playerExepsion = 0;
            byte bombCounter = 0;
            byte goalCounter = 0;
            for (int i = 0; i < map.GetLength(0); i++)
            {
                for (int j = 0; j < map.GetLength(1); j++)
                {
                    if ((i == 0 || i == map.GetLength(0)-1 ) || ( j == 0 || j == map.GetLength(1)-1) )
                    {
                        map[i, j] = WALL;
                    }
                    else if ((rd.Next(0,50) == 0 || playerExepsion > 300) && !playerAlive )
                    {
                        map[i,j] = PLAYER;
                        playerAlive = true;
                    }
                    else if ((bombCounter < MAXBOMB && rd.Next(0,30) == 0) && ((i > 1 && i < map.GetLength(0) - 2) && (j > 1 && j < map.GetLength(1) - 2)))
                    {
                        bombCounter++;
                        map[i, j] = BOMB;
                    }
                    else if ((bombCounter > goalCounter && rd.Next(0, 10) == 0) || (i == (map.GetLength(1)-2) && bombCounter > goalCounter ))
                    {
                        goalCounter++;
                        map[i, j] = GOAL;
                    }
                    else
                    {
                        map[i, j] = EMPTY;
                    }
                    if (!playerAlive)
                        playerExepsion++;
                }
            }
            bombs = bombCounter;
        }

    }
}

public struct Position
{
    public int X;
    public int Y;
}

개인소감

  • 기본적인 명령어만 갖고 만들 수 있는 게임이라고 생각하며, 예외처리(이동시 예외처리)부분을 최대한 간략하게 짜기 위해 만들다 보니 머리가 복잡해졌었지만, 덕분에 깔끔하게 예외처리가 진행되었습니다.
  • 개인적으로 가장 기분이 좋았던 부분은 키 입력시 맵 전체 불러오기 방식이 아닌 부분 덮어쓰기를 이용해서 가시성을 높인 것이 가장 좋았습니다.
profile
게임을 좋아하고 만들고 싶은 개발자 지망생입니다.

0개의 댓글