<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Projects | 정진영의 포트폴리오</title><link>https://wjdwlsdud.github.io/projects/</link><atom:link href="https://wjdwlsdud.github.io/projects/index.xml" rel="self" type="application/rss+xml"/><description>Projects</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>ko</language><lastBuildDate>Sun, 19 May 2024 00:00:00 +0000</lastBuildDate><image><url>https://wjdwlsdud.github.io/media/sharing.jpg</url><title>Projects</title><link>https://wjdwlsdud.github.io/projects/</link></image><item><title>팩맨</title><link>https://wjdwlsdud.github.io/projects/pacman/</link><pubDate>Sat, 01 Mar 2025 00:00:00 +0000</pubDate><guid>https://wjdwlsdud.github.io/projects/pacman/</guid><description>&lt;p&gt;이 프로젝트는 인공지능 수업의 일환으로 진행한 UC Berkeley Pacman AI 프로젝트입니다. 이 과제의 핵심 목표는 **그래프 탐색(Graph Search)**의 기본이 되는 여러 알고리즘을 직접 구현하고, 이를 팩맨 게임 환경에 적용하여 실제 문제를 해결하는 것이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;search.py&lt;/strong&gt; 파일에 범용 탐색 알고리즘들을 구현하고, &lt;strong&gt;searchAgents.py&lt;/strong&gt; 파일에 팩맨의 특정 문제(Problem)들을 정의하고 해결하는 방식으로 진행했습니다.&lt;/p&gt;
&lt;h2 id="1-핵심-탐색-알고리즘-구현-searchpy"&gt;1. 핵심 탐색 알고리즘 구현 (search.py)&lt;/h2&gt;
&lt;p&gt;먼저, 어떤 문제에도 적용할 수 있는 4가지 핵심 탐색 알고리즘을 구현했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DFS (깊이 우선 탐색): &lt;strong&gt;Stack(스택)&lt;/strong&gt; 자료구조를 사용하여 가능한 한 깊은 노드를 먼저 탐색하는 알고리즘입니다.
방문한 노드를 기록(explored)하여 무한 루프를 방지하는 그래프 탐색 방식으로 구현했습니다.&lt;/li&gt;
&lt;li&gt;BFS (너비 우선 탐색): &lt;strong&gt;Queue(큐)&lt;/strong&gt; 자료구조를 사용하여 현재 노드에서 가장 가까운(얕은) 노드들을 먼저 탐색합니다.
이 방식은 경로의 비용이 모두 같을 때 최단 경로를 보장합니다.&lt;/li&gt;
&lt;li&gt;UCS (균일 비용 탐색): &lt;strong&gt;PriorityQueue(우선순위 큐)&lt;/strong&gt;를 사용합니다. 각 경로의 &amp;lsquo;누적 비용(cost)&amp;lsquo;을 우선순위로 하여, 항상 비용이 가장 적게 든 경로를 먼저 탐색합니다. 이를 통해 최단 비용의 경로를 보장합니다.&lt;/li&gt;
&lt;li&gt;A* (A-star) 탐색: UCS와 마찬가지로 &lt;strong&gt;PriorityQueue&lt;/strong&gt;를 사용하지만, 우선순위를 &amp;lsquo;현재까지의 누적 비용($g(n)$)&amp;lsquo;과 &amp;lsquo;목표까지의 추정 비용(휴리스틱, $h(n)$)&amp;lsquo;의 합 ($f(n) = g(n) + h(n)$)으로 설정합니다. 정보에 기반한(informed) 탐색으로, UCS보다 훨씬 효율적으로 최적의 해를 찾아냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-팩맨-문제-정의-및-해결-searchagentspy"&gt;2. 팩맨 문제 정의 및 해결 (searchAgents.py)&lt;/h2&gt;
&lt;p&gt;단순히 알고리즘을 구현하는 것을 넘어, 팩맨이 직면한 복잡한 문제들을 &amp;lsquo;탐색 문제&amp;rsquo;로 모델링하고 해결했습니다.&lt;/p&gt;
&lt;p&gt;📍 4개의 코너 방문 문제 (CornersProblem)팩맨이 맵의 네 귀퉁이에 있는 모든 점을 방문하는 경로를 찾는 문제입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상태(State) 정의: 이 문제를 풀기 위해 상태 공간을 단순한 (x, y) 좌표가 아닌, (현재 위치, (방문한 코너 튜플))로 정의했습니다. 예를 들어, ((5, 5), (True, False, False, True))와 같이 현재 위치와 4개 코너의 방문 여부를 함께 기록했습니다.&lt;/li&gt;
&lt;li&gt;목표(Goal) 정의: state[1]에 해당하는 튜플의 모든 값이 True가 되는 순간을 목표 상태로 설정했습니다.
후계자(Successor) 함수: 팩맨이 이동할 때마다, 새로 도착한 위치가 4개의 코너 중 하나인지 확인하고, 만약 코너라면 튜플의 해당 값을 &lt;strong&gt;True&lt;/strong&gt;로 갱신한 새로운 상태를 생성하도록 구현했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 코너 문제 휴리스틱 (cornersHeuristic)&lt;/p&gt;
&lt;p&gt;위의 코너 문제를 A* 탐색으로 풀기 위한 휴리스틱 함수를 직접 설계했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설계: 휴리스틱은 항상 실제 비용보다 낮거나 같아야 합니다(Admissible).&lt;/li&gt;
&lt;li&gt;로직: &amp;lsquo;현재 위치&amp;rsquo;에서 &amp;lsquo;아직 방문하지 않은 모든 코너&amp;rsquo;까지의 맨해튼 거리(Manhattan Distance)를 각각 계산한 뒤, 그중 가장 큰(max) 값을 반환하도록 했습니다.&lt;/li&gt;
&lt;li&gt;이유: 팩맨은 적어도 남아있는 코너 중 가장 먼 곳까지는 가야 모든 코너를 방문할 수 있으므로, 이 값은 항상 실제 남은 비용의 하한선이 되어 A*의 허용 가능한(admissible) 휴리스틱 조건을 만족합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🍔 모든 음식 찾기 휴리스틱 (foodHeuristic)&lt;/p&gt;
&lt;p&gt;맵에 흩어져 있는 모든 음식을 먹는 경로를 찾는 &lt;strong&gt;FoodSearchProblem&lt;/strong&gt;을 위한 휴리스틱입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로직: 코너 휴리스틱과 유사하게, &amp;lsquo;현재 위치&amp;rsquo;에서 &amp;lsquo;아직 먹지 않은 모든 음식&amp;rsquo;까지의 실제 미로 거리(&lt;strong&gt;mazeDistance&lt;/strong&gt;)를 계산하고, 그중 가장 큰(max) 값을 반환했습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mazeDistance&lt;/strong&gt; 활용: 맨해튼 거리와 달리, &lt;strong&gt;mazeDistance&lt;/strong&gt;는 BFS를 기반으로 벽을 모두 고려한 실제 최단 거리를 반환해 주어 더 정확한 추정치를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🎯 가장 가까운 음식 찾기 (ClosestDotSearchAgent)&lt;/p&gt;
&lt;p&gt;모든 음식을 한 번에 찾는 것은 매우 복잡(NP-hard)하므로, &amp;lsquo;가장 가까운 음식을 찾아 먹는&amp;rsquo; 과정을 반복하는 탐욕(Greedy) 알고리즘을 구현했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;findPathToClosestDot&lt;/strong&gt; 구현: &lt;strong&gt;AnyFoodSearchProblem&lt;/strong&gt;(어떤 음식이든 하나만 먹으면 성공)이라는 하위 문제를 정의했습니다.&lt;/li&gt;
&lt;li&gt;UCS 활용: 이 문제에 &lt;strong&gt;UCS&lt;/strong&gt;를 적용하여, 현재 위치에서 가장 비용(거리)이 적게 드는 음식의 위치와 경로를 찾아 반환하도록 했습니다. (비용이 1로 동일하므로 &lt;strong&gt;BFS&lt;/strong&gt;도 동일한 결과를 보장합니다.)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>전화번호부 애플리케이션</title><link>https://wjdwlsdud.github.io/projects/mobile/</link><pubDate>Fri, 20 Dec 2024 00:00:00 +0000</pubDate><guid>https://wjdwlsdud.github.io/projects/mobile/</guid><description>&lt;p&gt;2024년 2학기 모바일프로그래밍 수업에서 만든 간단한 모바일 앱입니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트는 안드로이드의 기본 기능과 데이터베이스를 활용한 전화번호부 관리 앱입니다.&lt;/p&gt;
&lt;p&gt;먼저, phoneDB라는 이름의 데이터베이스와 phoneTBL 테이블을 설계했습니다. 이 테이블은 **이름(문자형 20)**과 **전화번호(문자형 20)**를 저장하며, 이름은 중복될 수 없도록 UNIQUE 제약 조건을 설정했습니다. &amp;lsquo;홍길동&amp;rsquo;과 제 연락처를 초기 데이터 2개로 포함시켰습니다.&lt;/p&gt;
&lt;p&gt;제가 구현한 주요 기능은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;메인 화면 (데이터 조회): 앱 실행 시, 데이터베이스에 저장된 모든 사용자의 이름을 리스트(ListView)로 불러와 보여줍니다. 하단에는 &amp;lsquo;추가&amp;rsquo; 버튼과 옵션 메뉴를 배치했습니다.&lt;/p&gt;
&lt;p&gt;연락처 추가 (Create): &amp;lsquo;추가&amp;rsquo; 버튼을 누르면, 별도의 Activity가 열려 이름과 전화번호를 입력받습니다. 입력 완료 후, 이 정보를 메인 Activity로 전달하여 데이터베이스에 저장하고 리스트를 즉시 갱신하도록 구현했습니다.&lt;/p&gt;
&lt;p&gt;데이터베이스 초기화 (Delete All): 옵션 메뉴에서 &amp;lsquo;초기화&amp;rsquo;를 선택하면, 대화상자를 띄워 사용자에게 정말 초기화할 것인지 확인을 받습니다. &amp;lsquo;확인&amp;rsquo;을 누르면 데이터베이스의 모든 내용을 삭제하고 리스트도 새로고침합니다.&lt;/p&gt;
&lt;p&gt;데이터 백업 (Export): 옵션 메뉴의 &amp;lsquo;백업&amp;rsquo; 기능을 통해, 데이터베이스에 저장된 모든 이름과 전화번호를 SD카드(_sdcard) 경로에 제학번.txt 파일로 저장하는 기능을 구현했습니다.&lt;/p&gt;
&lt;p&gt;전화 앱 연동 (Intent): 리스트의 특정 항목을 클릭하면, 해당 항목의 전화번호를 가져와 안드로이드의 기본 전화 앱으로 즉시 연결(Intent)되도록 했습니다.&lt;/p&gt;
&lt;p&gt;수정 및 삭제 (Update/Delete): 리스트 항목을 길게 누르면(Long Click), 선택한 항목을 수정하거나 삭제할 수 있는 별도의 Activity로 이동합니다. 각 작업 시 대화상자를 통해 사용자 확인을 받도록 했으며, 완료 시 메인 Activity의 데이터베이스와 리스트에 변경 사항이 실시간으로 반영되도록 구현했습니다.&lt;/p&gt;</description></item><item><title>리눅스 TCP 채팅 프로그램</title><link>https://wjdwlsdud.github.io/projects/linux/</link><pubDate>Wed, 26 Jun 2024 00:00:00 +0000</pubDate><guid>https://wjdwlsdud.github.io/projects/linux/</guid><description>&lt;p&gt;리눅스 시스템 프로그래밍 수업에서 배운 내용을 바탕으로 간단한 멀티-클라이언트 TCP 채팅 프로그램을 프로젝트로 구현해 보았습니다.&lt;/p&gt;
&lt;p&gt;C언어의 소켓(Socket) 통신과 멀티스레드(pthread) 개념을 직접 적용해 볼 수 있었던 의미 있는 경험이라, 프로젝트의 구조와 핵심 동작 원리, 그리고 배운 점들을 정리해 보려고 합니다.&lt;/p&gt;
&lt;h2 id="1-프로젝트-개요"&gt;1. 프로젝트 개요&lt;/h2&gt;
&lt;p&gt;이 프로그램은 1개의 서버와 다수의 클라이언트가 TCP/IP 프로토콜을 기반으로 실시간 채팅을 주고받을 수 있는 간단한 콘솔(터미널) 기반 채팅방입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 기술&lt;/strong&gt;: C언어, TCP/IP 소켓 프로그래밍, pthread (멀티스레딩)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 기능&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버: 다수의 클라이언트 접속을 동시에 관리합니다.&lt;/li&gt;
&lt;li&gt;브로드캐스팅: 한 클라이언트가 보낸 메시지를, 보낸 사람을 제외한 모든 접속자에게 전송합니다.&lt;/li&gt;
&lt;li&gt;멀티 채팅: 여러 사용자가 동시에 메시지를 보내고 받을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-핵심-아키텍처-어떻게-동시에"&gt;2. 핵심 아키텍처: 어떻게 동시에?&lt;/h2&gt;
&lt;p&gt;채팅 프로그램의 핵심은 &amp;lsquo;어떻게 여러 명의 요청을 동시에 처리하는가&amp;rsquo;입니다. accept(), recv(), fgets() 같은 함수들은 기본적으로 &amp;lsquo;블로킹(Blocking)&amp;rsquo; 방식으로 동작합니다. 즉, 자기 차례가 올 때까지 프로그램 전체를 멈추고 기다립니다.&lt;/p&gt;
&lt;p&gt;이 문제를 해결하기 위해 멀티스레딩을 사용했습니다.&lt;/p&gt;
&lt;p&gt;⚙️ &lt;strong&gt;서버: 클라이언트마다 전담 스레드 (Thread-per-Client)&lt;/strong&gt;
서버는 동시에 여러 클라이언트의 접속 요청과 메시지 수신을 처리해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;메인 스레드는 accept() 함수로 클라이언트의 새로운 접속을 기다립니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;새로운 클라이언트가 접속하면, pthread_create()를 호출해 그 클라이언트만을 전담하는 스레드(client_chat)를 하나 생성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이 전담 스레드는 recv() 함수를 통해 해당 클라이언트로부터 메시지가 올 때까지 대기합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;메인 스레드는 다시 accept()로 돌아가 다음 접속자를 기다립니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, &amp;lsquo;접속 처리&amp;rsquo; 스레드와 &amp;lsquo;클라이언트별 메시지 수신&amp;rsquo; 스레드들이 각자 독립적으로 동시에 실행됩니다.&lt;/p&gt;
&lt;p&gt;메시지를 받으면 send_msg 함수가 모든 클라이언트 리스트(clientList)를 순회하며 메시지를 전파(브로드캐스팅)합니다.&lt;/p&gt;
&lt;p&gt;💻 &lt;strong&gt;클라이언트: &amp;lsquo;보내는 스레드&amp;rsquo;와 &amp;lsquo;받는 스레드&amp;rsquo;의 분리&lt;/strong&gt;
클라이언트 역시 큰 숙제가 있습니다. 바로 내가 메시지를 입력하는 중에도 다른 사람의 메시지를 실시간으로 받아야 한다는 것입니다.&lt;/p&gt;
&lt;p&gt;만약 스레드가 하나라면, fgets()로 내 입력을 기다리는 동안 recv()가 실행될 수 없어 다른 사람의 메시지를 받지 못합니다.&lt;/p&gt;
&lt;p&gt;이 문제를 해결하기 위해 클라이언트 프로그램을 2개의 스레드로 분리했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;메인 스레드 (main 함수): 사용자의 키보드 입력(fgets)을 받아 서버로 전송(send)하는 &amp;lsquo;보내는&amp;rsquo; 역할만 전담합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;수신 스레드 (receive 함수): pthread_create()로 생성되며, 오직 서버로부터 메시지를 받는(recv) &amp;lsquo;받는&amp;rsquo; 역할만 전담합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 두 스레드가 각자의 역할을 나눠 갖기 때문에, 내가 메시지를 한창 입력하고 있는 중에도 receive 스레드는 서버가 보낸 다른 사람의 메시지를 화면에 실시간으로 출력해줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;위에 첨부된 링크로 코드를 확인하실 수 있습니다!&lt;/p&gt;</description></item></channel></rss>