https://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations

 

C# Image/PictureBox Rotations

How to rotate any image from the center of that image

www.codeproject.com

이 곳의 소스를 참고하였습니다! ㅎㅎ

 

주요 준비물 (button, textBox, label, pictureBox, timer)

 

실행 결과

 

아래는 전체 코드입니다.

timer1_Tick 에 주요 이미지 복사, 회전 코드가 있고 흐름을 살펴보면

 

1. 새로운 비트맵을 만들어 기존 이미지 크기와 맞추어 그릴 준비를 합니다.

2. Graphics로 비트맵을 지정해줍니다.

3. 피벗을 센터로 옮겨줍니다. (처음엔 0, 0입니다.)

4. 회전 후 다시 피벗을 원 위치 시켜줍니다.

5. 새 이미지 등록 후 기존 이미지 리소스 해제합니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Image masterImage;

        private float centerX;
        private float centerY;

        private float currentAngle = 0;
        private float angle = 1;

        private bool timerOn;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            DialogResult result = openFileDialog.ShowDialog();

            if (result == DialogResult.OK)
            {
                string filePath = openFileDialog.FileName;
                masterImage = new Bitmap(filePath);

                //미리 센터 좌표를 구해둡니다.
                centerX = masterImage.Width / 2;
                centerY = masterImage.Height / 2;

                pictureBox1.Image = (Image)masterImage.Clone(); //참조가 아닌 복사를 해줍니다.
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (masterImage == null)
            {
                MessageBox.Show("이미지가 먼저 등록되어야 합니다.");
                return;
            }

            if (timerOn)
            {
                timer1.Stop();
                timerOn = false;
            }
            else
            {
                float newAngle;
                //텍스트 내용이 숫자가 아닐 수도 있으니 조심해줍니다.
                if (float.TryParse(textBox1.Text, out newAngle) == false)
                {
                    newAngle = 1;
                }
                
                angle = newAngle;
                timer1.Start();
                timerOn = true;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            currentAngle += angle;

            if (currentAngle >= 360)
            {
                currentAngle -= 360;
            }

            label4.Text = string.Format("{0:F2}", currentAngle);

            Bitmap bitmap = new Bitmap(masterImage.Width, masterImage.Height);
            bitmap.SetResolution(masterImage.HorizontalResolution, masterImage.VerticalResolution);

            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.TranslateTransform(centerX, centerY);      //센터로 피벗을 옮겨줍니다.
            graphics.RotateTransform(currentAngle);             //센터를 중심으로 돌려줍니다.
            graphics.TranslateTransform(-centerX, -centerY);    //다시 피벗을 원위치 시켜줍니다.
            graphics.DrawImage(masterImage, PointF.Empty);      //bitmap에 masterImage를 0,0 기준으로 그려줍니다.

            Image oldImage = pictureBox1.Image; //기존 이미지 메모리 해제를 위해 저장
            pictureBox1.Image = bitmap; //새로운 이미지로 대체해줍니다.
            
            if (oldImage != null) //기존 이미지 리소스 해제
            {
                oldImage.Dispose();
            }
        }
    }
}

 

 

COM : Component Object Model

 마이크로소프트 프로그램들 간에 상호작용이 가능하게 해주는 기능입니다.

 

C#과 Excel을 연결해서 사용해 봅시다. 우선 윈폼을 만들어 진행해 봅니다. (UI 진행은 대충 생략했습니당!)

button1 : 열 추가

button2 : 엑셀에 저장

dataGridView1 : 데이터 관리

참조에서 마우스 오른쪽 클릭을 해줍니다.

참조 추가를 눌러줍니다.

 

참조 관리자에서 COM 메뉴를 클릭하고 Microsoft Excel 을 찾아서 참조합니다.

(설치되어 있는 버전에 따라 16.0이 아닐 수도 있지만 사용 가능해요!)

 

오른쪽 참조에 Microsoft.Office.Interop.Excel 가 생겼습니다.

using Microsoft.Office.Interop.Excel;

이 후 Microsoft.Office.Interop.Excel.Application 등을 사용할 수 있습니다.

 

이름이 너무 길어 불편하기 때문에

using Excel = Microsoft.Office.Interop.Excel;

라고 별칭을 주어 Excel로 해당 이름을 대체해줍니다.

 

코드를 보기전에 짤막하게 설명을 해보겠습니다.

workbook = 하나의 엑셀 파일

worksheet = 엑셀의 시트 (workbook의 worksheet) , 인덱스는 1부터 시작됩니다.

초록 라인 역시 1, 1 로 시작합니다.

 

이제 간단히 동작을 봅시다. 전체 코드는 맨 마지막에 있습니다.

워크북 생성 후 워크시트에 접근하여 사용할 수 있습니다.

엑셀을 보여지게 하고, AA 작성을 한 결과입니다.

나머지는 코드를 참고하시면 되겠습니다.

인터넷을 참고하니 ReleaseExcelObject 로 엑셀 연결에 사용된 메모리를 관리하는군요. 긁어왔습니다. ㅎㅎ

 

한 가지 주의 할 점은

dataGridView는 [column, row] 순서로 읽고

excel은 [row, column] 순으로 읽습니다. 게다가 인덱스가 1부터 시작되기에 row + 1, column + 1로 해주게 됩니다.

 

 

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;

namespace ExcelForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //그리드뷰 컬럼을 증가시킵니다.
        private void button1_Click(object sender, EventArgs e)
        {
            int index = dataGridView1.ColumnCount;
            dataGridView1.ColumnCount++;

            dataGridView1.Columns[index].HeaderText = (index + 1).ToString();
        }

        //엑셀 파일에 저장합니다.
        private void button2_Click(object sender, EventArgs e)
        {
            Excel.Application app = new Excel.Application();

            //app.Visible = true; //엑셀을 보여줍니다.

            Excel.Workbook workbook = app.Workbooks.Add(); //엑셀 창 환경을 가집니다.

            Excel.Worksheet worksheet = workbook.Worksheets[1]; // Sheet1 환경을 가집니다.
            //Excel.Worksheet worksheet = workbook.Worksheets.get_Item(1);

            //엑셀은 모든 인덱스가 0이 아닌 "1" 부터 시작합니다 주의하세요!

            for (int row = 0; row < dataGridView1.RowCount; ++row)
            {
                for (int column = 0; column < dataGridView1.ColumnCount; ++column)
                {
                    if (dataGridView1[column, row].Value == null)
                    {
                        continue;
                    }

                    worksheet.Cells[row + 1, column + 1] = dataGridView1[column, row].Value.ToString();
                }
            }

            try
            {
                string path = Path.Combine(Environment.CurrentDirectory, "Test.xlsx");
                worksheet.SaveAs(path); //파일 덮어쓰기를 선택하지 않으면 오류가 납니다.
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                workbook.Close();
                app.Quit();

                ReleaseExcelObject(worksheet);
                ReleaseExcelObject(workbook);
                ReleaseExcelObject(app);
            }
        }

        //엑셀 파일을 읽어옵니다.
        private void button3_Click(object sender, EventArgs e)
        {
            Excel.Application app = new Excel.Application();
            string path = Path.Combine(Environment.CurrentDirectory, "Test.xlsx");
            Excel.Workbook workbook = app.Workbooks.Open(path);
            Excel.Worksheet worksheet = workbook.Worksheets[1];

            Excel.Range range = worksheet.UsedRange;

            object[,] data = range.Value;

            int rowCount = data.GetLength(0);
            int columnCount = data.GetLength(1);

            //엑셀 크기보다 dataGirdView가 작다면 넓혀준다.

            if (dataGridView1.RowCount < rowCount)
            {
                dataGridView1.RowCount = rowCount;
            }

            if (dataGridView1.ColumnCount < columnCount)
            {
                dataGridView1.ColumnCount = columnCount;
            }

            for (int row = 0; row < data.GetLength(0); ++row)
            {
                for (int column = 0; column < data.GetLength(1); ++column)
                {
                    dataGridView1[column, row].Value = data[row + 1, column + 1];
                }
            }

            workbook.Close();
            app.Quit();

            ReleaseExcelObject(worksheet);
            ReleaseExcelObject(workbook);
            ReleaseExcelObject(app);
        }

        private void ReleaseExcelObject(object obj)
        {
            try
            {
                if (obj != null)
                {
                    Marshal.ReleaseComObject(obj);
                    obj = null;
                }
            }
            catch (Exception ex)
            {
                obj = null;
                throw ex;
            }
            finally
            {
                GC.Collect();
            }
        }
    }
}

 

C#에서 마우스 매크로 만드는 것을 차례대로 진행 해보겠습니다. (코드는 맨 마지막에 있습니다!)

만들어본 후엔 필요한 기능을 추가하셔서 사용하시면 되겠습니다. (어렵지 않아요~)

 

먼저 Visual Studio에서 Windows Form 만들기를 해줍니다.

전 .NET Framework 를 사용하였는데 .NET Core를 선택하셔도 상관 없습니다.

.NET Framework는 Windows 에서만 사용 가능하고

.NET Core는 다른 운영체제에서도 사용 가능합니다. 점차 .NET Core를 밀고 있는 추세입니다만

전 윈도우로 하니깐 그냥 프레임워크로 가겠슴돠

 

이름은 MouseMacro 입니다. ㄱㄱ

 

다른 기능 다 필요없습니다. 일단 폼 크기를 마음대로 줄여줍니다.

그리고 오른쪽 하단에 있는 속성 창에서 딱 두가지만 세팅해줍니다. (폼 클릭하고도 속성창이 안보이면 F4 누르세요)

FormBorderStyle = FixedToolWindow

Text = Mouse Macro

로 설정해주겠습니다. 훌륭한 UI 세팅으로 여기까지 따라하셨다면 벌써 70% 완성했습니다.

 

이제 F7을 눌러서 (혹은 마우스 오른쪽 클릭-> 코드 보기) 코드 보기로 갑시다.

MouseMacro를 오른쪽 클릭하면 사진처럼 새 항목을 추가할 수 있게 됩니다. 눌러줍시다.

 

클래스를 새로 만들겁니다. 이름은 HookManager로 결정하겠습니다.

Win32 API 를 사용할 것이기 때문에 코드를 복사 붙여넣기 하면 됩니다.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MouseMacro
{
    //C:\Windows\System32\user32.dll 참조
    public class HookManager
    {
        #region WindowsAPI_Mouse Event

        [DllImport("user32.dll")]
        static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);

        //Microsoft MouseEvent Document
        //https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event

        const uint MOUSEEVENTF_MOVE = 0x0001;      // 마우스 이동
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000;   // 전역 위치
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002;    // 왼쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_LEFTUP = 0x0004;      // 왼쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;    // 오른쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_RIGHTUP = 0x00010;      // 오른쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_WHEEL = 0x0800;        // 마우스 휠 (dwData 값으로 조정)

        #endregion

        #region WindowsAPI_Key Hook

        //Microsoft Hook Document
        //https://docs.microsoft.com/en-us/windows/win32/winmsg/hooks

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, IntPtr hInstance, uint threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hInstance);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelKeyboardProc lowLevelKeyboardProc = HookProc;

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13;
        const int WM_KEYDOWN = 256;
        const int WM_KEYUP = 257;
        const int WM_SYSKEYDOWN = 260; //Alt Key Down

        private static IntPtr hookId = IntPtr.Zero;

        #endregion

        //Custom Variables
        private static Point preMousePosition;

        private static int leftControl = 1;
        private static int controlKey = 0;

        private static uint wheel = 120;
        private static uint backWheel = unchecked((uint)-120);

        public void SetHook()
        {
            IntPtr hInstance = LoadLibrary("User32");
            hookId = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0);
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
        }

        public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code >= 0)
            {
                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.NumPad0:
                            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                            return (IntPtr)1;

                        case Keys.NumPad1:
                            Cursor.Position = new Point(preMousePosition.X, preMousePosition.Y);
                            return (IntPtr)1;

                        case Keys.NumPad4:
                            preMousePosition = Cursor.Position;
                            return (IntPtr)1;

                        case Keys.LControlKey:
                            controlKey |= leftControl;
                            break;

                        case Keys.Up:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, wheel, 0);
                                return (IntPtr)1;
                            }
                            break;

                        case Keys.Down:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, backWheel, 0);
                                return (IntPtr)1;
                            }
                            break;
                    }
                }
                else if (wParam == (IntPtr)WM_KEYUP)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.LControlKey:
                            controlKey &= ~leftControl;
                            break;
                    }
                }
            }

            return CallNextHookEx(hookId, code, (int)wParam, lParam);
        }
    }
}

코드가 좀 길지만 다른 곳은 보지 말고 (관심 있다면 살펴봐도 좋습니다.)

public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)

이 곳만 보고 입 맛에 맞게 수정하면 됩니다.

 

로직을 설명해보면

if (code >= 0) // 무언가 키 이벤트가 발생했다.

  if (wParam == (IntPtr)WM_KEYDOWN) //ㅇㅇ 키가 눌렸다고!

    Keys key = (Keys)Marshal.ReadInt32(lParam)

 

      key == Keys.???

      이제 key가 무엇인지에 따라 동작을 해주면 됩니다.

      (참고로 Alt 키는 wParam == WM_SYSKEYDOWN)

      (Label 또는 MessageBox에 출력해서 값을 봐보는 것도 좋습니당)

     

위의 샘플 코드는 numpad4 : 현재 마우스 위치를 저장하고

                       numpad1 : 저장 된 마우스 위치로 마우스 커서 이동

                       numpad0 : 마우스 클릭 발생

 

                       ctrl + 마우스 위/아래 : ctrl + wheel을 구현한 것으로 확대, 축소 기능입니다.

 

자세한 것은 코드를 분석해보시면 되겠습니다. 친절하게 마우스와 키 값에 대한 링크도 해두었습니다!

 

이제 마무리로 HookManager를 인스턴스화 해서 사용하면 됩니다.

이벤트 두 개만 추가해봅시다. 다시 폼 뷰로 돌아와서 속성 창의 번개 모양을 클릭해줍니다.

그러면 이벤트를 등록할 수 있게 됩니다.

Load를 더블 클릭하면 코드가 자동으로 생성되는데 다시 폼뷰로 돌아와

FormClosing도 더블 클릭해줍니다.

 

이제 마지막 코드입니다. 간단해유

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MouseMacro
{
    public partial class Form1 : Form
    {
        private HookManager hookManager;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //화면 가장 오른쪽에 띄우도록 합니다.
            int x = Screen.PrimaryScreen.Bounds.Width - this.Size.Width;
            int y = 0;

            this.Location = new Point(x, y);

            hookManager = new HookManager();
            hookManager.SetHook();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            hookManager.UnHook();
        }
    }
}

로드 될 때 폼을 오른쪽 상단에 배치하고 훅을 시작합니다.

폼이 꺼질 때 훅을 해제 해주며 마무리 합니다.

여기까지 다 만드셨다면 간단한 마우스 매크로 성공입니다! 실행해서 확인해 보시고..

 

추가 기능은 HookManager의 HookProc 를 수정해서 사용해보세요~

1. int, float 등은 숫자 입력이지만

    ? (물음표) 키워드를 이용해서 명시적으로 null 값을 줄 수 있습니다.

 

2. ?연산자로 null값이면 실행되지 않도록 조건문 대신 사용할 수 있습니다.

 

3. ?? 두개를 통해 null 값이면 다른 값으로 설정합니다.

 

코드가 짧아지는 장점이 있습니다.

하지만 무분별하게 사용하게 되면 가독성을 헤치게 됩니다.

using System;
using System.Collections;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            int? a = null;
            Console.WriteLine("a의 값 {0}", a);
            float? b = null;
            Console.WriteLine("b의 값 {0}", b);


            ArrayList arraylist = null;
            arraylist?.Add(30); //arraylist가 null이 아니면 Add(30)
            /*
            if (arraylist == null)
            {
                arraylist.Add(30);
            }
            */

            Console.WriteLine(arraylist?[0]); //null 이지만 오류가 나지 않습니다.

            a = a ?? 20; //a가 null이면 20
            b = b ?? 30.9f; //b가 null 이면 30.9f
            Console.WriteLine("a = {0}, b = {1}", a, b);
        }
    }
}

 

[실행 결과] null은 공백으로 출력됩니다.

 

1. 바이너리 파일로 저장하고 읽어오는 코드입니다.

 여러 타입을 저장할 수 있는데 읽어올 때 반드시 저장된 순서대로 읽어줘야 됩니다.

 (문자열은 앞에 문자열의 길이 + 문자열이 저장되어 해당 길이만큼 읽어옵니다.)

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            //바이너리 파일 생성 및 저장
            FileStream fs = new FileStream("Test.bin", FileMode.Create);
            BinaryWriter bw = new BinaryWriter(fs);

            string input1 = Console.ReadLine();
            int input2 = Convert.ToInt32(Console.ReadLine());
            float input3 = Convert.ToSingle(Console.ReadLine());

            bw.Write(input1); //string
            bw.Write(input2); //int
            bw.Write(input3); //float

            bw.Close();
            fs.Close();

            //바이너리 파일 읽어오기
            FileStream fs2 = new FileStream("Test.bin", FileMode.Open);
            BinaryReader br = new BinaryReader(fs2);

            Console.WriteLine(br.ReadString()); //string
            Console.WriteLine(br.ReadInt32()); //int
            Console.WriteLine(br.ReadSingle()); //float

            br.Close();
            fs2.Close();
        }
    }
}

 

입력을 받고 읽어온 모습
저장된 데이터

 

2. StreamReader/StreamWriter 를 이용한 모습입니다. 문자열 그대로 저장이 됩니다.

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("Test.dat");
            
            for (int i = 0; i < 5; ++i) //5개의 입력
            {
                sw.WriteLine(Console.ReadLine());
            }

            sw.Close();

            StreamReader sr = new StreamReader("Test.dat");

            while (!sr.EndOfStream) //sr.ReadLine() == null인 경우입니다.
            {
                Console.WriteLine(sr.ReadLine());
            }

            sr.Close();
        }
    }
}

5개의 입력을 받고 다시 출력해준 모습
저장된 데이터

 

보안적인 측면은 바이너리 코드가 읽기가 어려워지기 때문에 더 좋습니다.

아니면 암호화나 해시를 하는 방법을!

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

 

3197번: 백조의 호수

입력의 첫째 줄에는 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1500. 다음 R개의 줄에는 각각 길이 C의 문자열이 하나씩 주어진다. '.'은 물 공간, 'X'는 빙판 공간, 'L'은 백조가 있는 공간으로 나타낸다.

www.acmicpc.net

 

물(.) 옆에 있는 빙판(X)은 하루가 지날 때 마다 사라집니다.

백조(L) 두 마리가 며칠 뒤에 만날 수 있는가를 탐색하는 문제입니다.

 

제가 한 풀이는 이렇습니다. 우선 분리 집합을 통해 그룹화 시켜주었습니다.

( 분리 집합(Disjoint Set) 을 잘 모르시는 분은 나중에 다시 글을 올려 링크해 두겠습니다. )

 

1. 모든 지형을 탐색합니다. 물이나 백조인 곳을 기점으로 번호를 매겨 DFS 탐색을 해줍니다.

2.  .이라면 계속 탐색하고, X를 만나면 탐색을 중지하고 큐에 넣어줍니다.

 

 

3. 백조 두 마리가 처음부터 같은 그룹에 있을 수 있습니다. 이를 확인하여 처리해 줍니다.

 

4.

 4-1. X 위치에서 주변을 탐색하고 분리 집합 (parent)을 사용하여 같은 그룹으로 만들어 줍니다.

bird[1] = 3 에서 1로 변경되었습니다.

 4-2. bird[0] 과 bird[1] 이 같으면 백조는 서로 만나게 된 상태입니다.

      만났다면 day를 출력

      만나지 못했다면 day를 1 증가 시키고 4-3으로 갑니다.

 

 4-3. 다시 주변 X를 queue에 넣어주며 4를 반복합니다.

 

 

 

 

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

queue<pair<int, int>> q;
vector<int> bird;
vector<int> parent;

char input[1500][1501];
int visited[1500][1500];
int dir[4][2] = 
{
	{1, 0},
	{0, 1},
	{-1, 0},
	{0, -1}
};

void DFS(int startRow, int startCol, int r, int c, int group)
{
	if (input[startRow][startCol] == 'L')
	{
		bird.push_back(group); //새가 어느 그룹에 속해있는지 저장해둡니다.
	}

	for (int i = 0; i < 4; ++i)
	{
		int nextRow = startRow + dir[i][0];
		int nextCol = startCol + dir[i][1];

		if (nextRow < 0 || r <= nextRow ||
			nextCol < 0 || c <= nextCol ||
			visited[nextRow][nextCol] > 0)
		{
			continue;
		}
		
		visited[nextRow][nextCol] = group;

		if (input[nextRow][nextCol] == 'X')
		{
			q.push(make_pair(nextRow, nextCol));
		}
		else
		{
			DFS(nextRow, nextCol, r, c, group);
		}
	}
}

int GetParent(int p)
{
	if (parent[p] == p)
	{
		return p;
	}

	return parent[p] = GetParent(parent[p]);
}

int SetParent(int from, int to)
{
	return parent[from] = parent[to];
}

//테스트 출력용
void PrintState(int r, int c, int day)
{
	cout << endl << "day : " << day << endl;

	for (int i = 0; i < r; ++i)
	{
		for (int j = 0; j < c; ++j)
		{
			cout << GetParent(visited[i][j]) << "";
		}
		cout << endl;
	}

	cout << "parent : " << endl;

	for (int i = 1; i < parent.size(); ++i)
	{
		cout << "[" << i << "] " << parent[i] << " ";
	}

	cout << endl;
}

int main()
{
	int r, c;
	int day = 1;
	int groupId = 1;
	cin >> r >> c;

	parent.push_back(0);
	for (int i = 0; i < r; ++i)
	{
		cin >> input[i];
	}

	for (int i = 0; i < r; ++i)
	{
		for (int j = 0; j < c; ++j)
		{
			if (visited[i][j] > 0)
			{
				continue;
			}

			if (input[i][j] == '.' || input[i][j] == 'L') //탐색 및 그룹화
			{
				visited[i][j] = groupId;
				DFS(i, j, r, c, groupId);
				parent.push_back(groupId);
				groupId++;
			}
		}
	}

	if (GetParent(bird[0]) == GetParent(bird[1])) //이미 만나 있는 상태
	{
		cout << 0 << endl;
		return 0;
	}

	while (!q.empty())
	{
		int size = q.size();

		//4-1
		for (int i = 0; i < size; ++i)
		{
			q.push(q.front());

			int row = q.front().first;
			int col = q.front().second;
			int group = GetParent(visited[row][col]);
			q.pop();

			for (int j = 0; j < 4; ++j)
			{
				int nextRow = row + dir[j][0];
				int nextCol = col + dir[j][1];

				if (nextRow < 0 || r <= nextRow ||
					nextCol < 0 || c <= nextCol || 
					visited[nextRow][nextCol] == 0)
				{
					continue;
				}
				int from = GetParent(visited[nextRow][nextCol]);

				SetParent(from, group);
			}
		}

		//4-2
		PrintState(r, c, day);
		if (GetParent(bird[0]) == GetParent(bird[1]))
		{
			break;
		}
		
		//4-3
		day++;
		for (int i = 0; i < size; ++i)
		{
			int row = q.front().first;
			int col = q.front().second;
			int group = visited[row][col];
			q.pop();

			for (int j = 0; j < 4; ++j)
			{
				int nextRow = row + dir[j][0];
				int nextCol = col + dir[j][1];

				if (nextRow < 0 || r <= nextRow ||
					nextCol < 0 || c <= nextCol ||
					visited[nextRow][nextCol] > 0)
				{
					continue;
				}

				visited[nextRow][nextCol] = group;
				q.push(make_pair(nextRow, nextCol));
			}
		}
	}

	cout << day << endl;
}

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

 

2805번: 나무 자르기

첫째 줄에 나무의 수 N과 상근이가 집으로 가져가려고 하는 나무의 길이 M이 주어진다. (1 ≤ N ≤ 1,000,000, 1 ≤ M ≤ 2,000,000,000) 둘째 줄에는 나무의 높이가 주어진다. 나무의 높이의 합은 항상 M보

www.acmicpc.net

 

n개의 나무 중 m개 이상을 가져갈 수 있는 최소 높이를 구하는 문제입니다.

이분 탐색을 이용한 풀이와 높이를 비교하며 푸는 방법 두 가지가 있습니다.

 

먼저 이분 탐색을 사용한 일반적인 풀이입니다.

(이분 탐색이 left <= right,  left < right 등과 같이 여러 변형이 있고 쓰이는 곳이 달라집니다.

이건 종이에 써가면서 해보는게 좋을거 같네요!)

 

#include <iostream>
#include <algorithm>

using namespace std;

int input[1000000];

int BinarySearch(int n, int m)
{
	int left = 0;
	int right = 1000000000;

	while (left <= right)
	{
		int mid = (left + right) / 2;
		long long sum = 0LL;

		for (int i = 0; i < n; ++i)
		{
			if (input[i] > mid)
			{
				sum += (long long)input[i] - (long long)mid;
			}
		}

		if (sum == m)
		{
			return mid;
		}
		else if (sum > m)
		{
			left = mid + 1;
		}
		else
		{
			right = mid - 1;
		}
	}

	return right;
}

int main()
{
	int n, m;
	int left = 0;
	int right = 0;

	cin.tie(0);
	cin >> n >> m;

	for (int i = 0; i < n; ++i)
	{
		cin >> input[i];
	}

	sort(input, input + n);

	cout << BinarySearch(n, m) << endl;
}

 

다른 방법은 높이를 큰 순서로 나열한 후 잘라가며 원하는 수량을 체크하는 것입니다.

이 방법은 제가 알아낸 것은 아니고 어떤 분의 풀이였던 것 같습니다.

1

 

 

2

 

#include <iostream>
#include <algorithm>

using namespace std;

int input[1000001];

bool compare(int a, int b)
{
	return a > b;
}

int main()
{
	int n, m;
	long long sum = 0;
	int index = 1;
	int cut = 0;

	cin >> n >> m;

	for (int i = 0; i < n; ++i)
	{
		cin >> input[i];
	}

	sort(input, input + n, compare);

	while (sum < m)
	{
		sum += ( (long long)input[index - 1] - (long long)input[index] ) * index;
		index++;
	}

	cut = (sum - m) / (index - 1);
	cout << input[index - 1] + cut << endl;
}

cut을 구하는 것이 핵심인 코드입니다. ㅎㅎ

(이때 index가 배열의 크기를 넘어서지 않기위해 n + 1개 만큼의 배열로 선언해주어야 합니당.)

파일을 통해 데이터를 저장하여 불러오거나 해도 되지만

유니티에는 스크립터블 오브젝트라는 형태로 데이터 저장 및 사용이 가능합니다.

 

using UnityEngine;

[CreateAssetMenu(fileName = "MyEditorData", menuName = "Custom ScriptableObject/MyEditorData")]
public class MyEditorData : ScriptableObject
{
    public string filePath;
}

 

우선 string형 하나만 입력받도록 설정해줍니다. 클래스는 ScriptableObject 를 상속 받기만 하면 됩니다.

생성을 위해 CreateAssetMenu 를 설정해주겠습니다.

fileName : 생성될 파일명

munuName : 메뉴에 나타날 이름입니다.

 

CreateAssetMenu를 통해 해당 이름으로 파일 생성이 가능해졌습니다.

 

 

해당 파일을 클릭하면 Inspector에서 보기 및 편집이 가능합니다.

 

유니티를 껏다켜도 해당 정보는 저장되어 있게 됩니다.

이제 이것을 통해 윈도우 에디터 값들을 저장하며 만들 예정입니다!

using UnityEngine;
using UnityEditor;

public class MyEditor : EditorWindow
{
    private static EditorWindow window;
    private static float width = 600;
    private static float height = 600;

    [MenuItem("CustomWindow/MyEditor")] //상단 메뉴에 추가됩니다.
    public static void Open ()
    {
        window = GetWindow<MyEditor>();

        Rect main = EditorGUIUtility.GetMainWindowPosition(); //전체 화면 크기를 갖고 옵니다.

        window.minSize = window.maxSize = new Vector2(width, height); //시작 사이즈를 width, height로 만들어 주고

        window.minSize = new Vector2(200, 200);
        window.maxSize = new Vector2(main.width, main.height); //제한을 풀어줍니다.

        //window.position에 x, y 좌표와 width, height가 들어있습니다.
        window.position = new Rect((main.width - width) * 0.5f, (main.height - height) * 0.5f, width, height);
    }

    private void OnGUI()
    {
        GUILayout.Label("Hello!");
    }
}

 

[MenuItem("CustomWindow/MyEditor")] 를 통해 메뉴가 상단에 추가 된 모습

 

윈도우 창이 실행 된 모습. 가운데에 위치하게 됩니다. (전체화면으로 한 경우만!)

유니티 에디터에 대해 독학 중인데 엄청난 뻘짓들을 하고 있습니다..

가끔씩 이상한데 꽂혀서 시간을 많이 쓰네요.. ㅠ_ㅠ

 

찾아보면 기능들이 다양하고 많네요 ' - '@

시간이 되면 만져 본 것들을 하나씩 정리해보기!

커스텀 에디터를 만들 때 가장 중요한 항목은 사용자의 확인, 취소에 대한 입력을 받는 것입니다.

그리고 실수로 잘 못 된 버튼을 누르는 것을 방지하거나 무언가 오류가 생길시 알림을 주는 기능도 할 수 있습니다.

 

C#으로 치면 MessageBox 기능이고

유니티에서는 UnityEditor.EditorUtility.DisplayDialog 를 통해 메시지 출력이 가능합니다.

 

에디터 모드에서도 가능하지만 실시간 게임 중에도 출력이 가능하기에 우선

플레이 버튼 시 메시지가 뜨도록 MonoBehaviour에 작성하여 실행시켜보았습니다.

 

파라미터 개수에 따라 확인만 가능하거나 취소까지 가능하며

해당 결과를 bool 값으로 받을 수 있습니다.

using UnityEngine;
using UnityEditor;

public class ShowMessage : MonoBehaviour
{
    void Start()
    {
        bool result = EditorUtility.DisplayDialog("제목", "메시지", "확인", "취소");

        if (result)
        {
            EditorUtility.DisplayDialog("클릭 결과", "확인 메시지가 클릭되었습니다.", "확인");
        }
        else
        {
            EditorUtility.DisplayDialog("클릭 결과", "취소 메시지가 클릭되었습니다.", "확인");
        }
    }
}

 

초기 메시지

 

+ Recent posts