{"id":27349600,"url":"https://github.com/azruine/cpp_game_project","last_synced_at":"2025-06-29T05:39:08.362Z","repository":{"id":287409215,"uuid":"964539485","full_name":"Azruine/cpp_game_project","owner":"Azruine","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-11T14:38:06.000Z","size":14314,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T15:50:36.795Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Azruine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-11T11:25:16.000Z","updated_at":"2025-04-11T14:38:10.000Z","dependencies_parsed_at":"2025-04-11T16:01:45.169Z","dependency_job_id":null,"html_url":"https://github.com/Azruine/cpp_game_project","commit_stats":null,"previous_names":["azruine/cpp_game_project"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azruine%2Fcpp_game_project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azruine%2Fcpp_game_project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azruine%2Fcpp_game_project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azruine%2Fcpp_game_project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Azruine","download_url":"https://codeload.github.com/Azruine/cpp_game_project/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248620230,"owners_count":21134605,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-12T19:26:36.533Z","updated_at":"2025-04-12T19:26:38.150Z","avatar_url":"https://github.com/Azruine.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C++ 게임 프로젝트\n\n개구리 게임과 똥 피하기 게임 두 개를 객체 지항형 프로그래밍 패러다임을 적용하여 만들었다.\n\n객체 지향 패러다임을 잘 지키기 위해 노력했지만, 정작 완성하고 나니 아쉬운 부분이 많이 보인다.\n\n## 계층도\n\n클래스 계층도는 아래와 같다.\n\n```mermaid\nflowchart TD\n\n    subgraph Game \u0026 Manager\n        direction TB\n        \n        subgraph Manager\n            GameManager\n        end\n        \n        subgraph Manager\n            Game --\u003e FrogGame\n            Game --\u003e PoopGame\n        end\n    end\n    \n    subgraph Object\n        direction LR\n        IObject --\u003e Player\n        IObject --\u003e Obstacle\n\n        Player --\u003e Frog\n        Player --\u003e Man\n\n        Obstacle --\u003e Car\n        Obstacle --\u003e Poop\n    end\n    \n    Frog -.-\u003e FrogGame\n    Car -.-\u003e FrogGame\n    Man -.-\u003e PoopGame\n    Poop -.-\u003e PoopGame\n    FrogGame -.-\u003e GameManager\n    PoopGame -.-\u003e GameManager\n    \n    classDef abstract fill:#f96,stroke:#333,stroke-width:2px\n    class IObject,Player,Obstacle,Game abstract\n```\n\n## 분석\n\n객체 지향 프로그래밍을 본격적으로 활용해본 것은 처음이라 아무래도 아쉬운 부분이 조금 많았다.\n\n내가 만든 코드를 SOLID 원칙에 입각하여 분석해보자.\n\n### 간단한 정리\n\nSRP 측면에서, 단일 클래스가 다양한 책임을 맡고 있는 경우가 있다.\n\nOCP/DIP 관점에서, GameManager가 FrogGame, PoopGame 등의 구체 인스턴스에 직접 의존한다.\n\nISP 관점에서, IObject가 무겁게 설계된 점이 걸린다.\n\nLSP는 setDir() 메서드를 제외하면 괜찮게 지켜진 것 같다.\n\n### 상세 내용\n\n#### IObject 및 하위 클래스\n\n1. Single Responsibility Principle (SRP)\n    - IObject는 모든 객체의 공통 인터페이스를 정의하고, Player와 Obstacle은 각각 플레이어와 장애물의 속성과 동작을 정의한다.\n    - 하나의 클래스 안에서 그리기와 이동 등의 여러 액션을 처리하고 있다. 그리기와 이동, 충돌 판단 등은 전부 Object 객체 내에서 이루어지는 것이 합리적이라고 생각했지만 다시 보니 예상보다 많은 책임을 가지고 있는 것 같다.\n\n2. Open/Closed Principle (OCP)\n    - IObject는 인터페이스로, 새로운 객체를 추가할 때 기존 코드를 수정하지 않고도 새로운 클래스를 추가할 수 있다.\n    - 다만 Draw 함수 포인터가 도형별 함수 포인터를 저장하고 있는데, 새로운 도형을 추가한다면 함수 포인터 배열을 수정해야 하기 때문에 완전히 OCP를 준수한다고 볼 수는 없다.\n\n3. Liskov Substitution Principle (LSP)\n    - Frog 와 Man 클래스의 setDir() 메서드가 서로 다른 동작을 하여 상위 클래스로 일관되게 치환할 수 없다.\n    - 이외에는 리스코프 치환 원칙이 지켜진 것으로 보인다.\n\n4. Interface Segregation Principle (ISP)\n    - 단순히 '그리기'만 필요한 객체를 만들어도 다른 메서드를 추가로 구현해야 한다.\n    - STM32라는 제한을 생각해보면 문제가 발생할 여지는 없지만, 규모가 커진다면 움직일 수 있는 인터페이스, 충돌 인터페이스 등을 나누어 필요한 기능만을 구현해야 할 것 같다.\n\n5. Dependency Inversion Principle (DIP)\n    - Car, Frog 등의 구체 클래스에 의존하지 않고 추상화된 IObject에 의존하도록 설계되어 있다.\n    - 하지만 그리기 메서드는 함수 포인터 배열을 갖고 있으므로, 새로운 도형 추가 시 배열 수정이 필요하다. 이는 렌더링 방식을 외부에서 주입받게끔 한다면 해결할 수 있을 것으로 보인다.\n\n#### GameManager\n\n1. Single Responsibility Principle (SRP)\n    - 하나의 클래스에 지나치게 많은 책임이 부여되어 있다. 게임 선택, 초기화, 타이틀 표시 등의 기능을 모두 포함하고 있다. 선택창과 타이틀을 별도의 클래스로 분리하여 책임을 나누는 것이 좋다고 판단된다.\n\n2. Open/Closed Principle (OCP)\n    - 게임 매니저는 새로운 게임을 추가할 때 기존 코드를 수정해야 한다. 게임을 동적으로 관리하거나 팩토리 패턴 등을 적용해볼 수 있을 것 같다.\n\n3. Liskov Substitution Principle (LSP)\n    - 추상 클래스를 따로 만들지 않고 게임 관리 객체를 바로 만들었으므로, LSP를 적용할 상위 타입이 존재하지 않는다.\n\n4. Interface Segregation Principle (ISP)\n    - 추상 클래스를 따로 만들지 않고 게임 관리 객체를 바로 만들었으므로, ISP를 적용할 상위 타입이 존재하지 않는다.\n\n5. Dependency Inversion Principle (DIP)\n    - GameManager는 Game 클래스를 상속받은 구체 클래스에 직접 의존하여 DIP를 위배한다.\n    - Game 추상 클래스에 의존하는 식으로 변경하고, 구체 게임 인스턴스는 의존성 주입 컨테이너 등의 방식으로 구성해야 할 것 같다.\n\n#### Game 및 하위 클래스\n\n1. Single Responsibility Principle (SRP)\n    - 게임 로직과 충돌 감지, 점수 처리 등의 다양한 책임을 지고 있다. 게임 로직만을 여기서 담당하고 충돌 감지 및 점수 처리는 별도의 클래스로 분리해야 할 것 같다.\n\n2. Open/Closed Principle (OCP)\n    - 게임 클래스는 추상 클래스로, 새로운 게임을 추가할 때 기존 코드를 수정하지 않고도 새로운 게임을 추가할 수 있다.\n    - 하지만 각 게임에 대한 구체적인 구현은 GameManager와 Game 클래스에 포함되어 있어, 새로운 게임을 추가할 때 기존 코드를 수정해야 한다. 게임 로직을 별도의 클래스로 분리하여 확장성을 높이는 것이 좋을 것으로 판단된다.\n\n3. Liskov Substitution Principle (LSP)\n    - 상속받은 메서드들을 적절히 오버라이드하여 사용하고 있다. LSP가 잘 지켜진 것으로 판단된다.\n\n4. Interface Segregation Principle (ISP)\n    - IObject와 마찬가지로, Game 클래스가 무겁게 설계되어 있어 구체 클래스에 필요하지 않은 메서드가 포함될 수 있다.\n\n5. Dependency Inversion Principle (DIP)\n    - FrogGame과 PoopGame은 IObject 기반 객체들을 직접 생성하여 사용한다.\n    - 추상화된 IObject 인터페이스에 의존하긴 하지만, 객체 생성 로직이 각 클래스로 분산되어 있어서 객체 생성 방식을 변경한다면 Game 관련 클래스들의 코드를 수정해야 한다.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fazruine%2Fcpp_game_project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fazruine%2Fcpp_game_project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fazruine%2Fcpp_game_project/lists"}