D. 지뢰찾기 분석 설계 예제
0. 지뢰찾기 분석
분석의 목적 시스템과 사용자 사이의 인터페이스를 정의 사용자 관점에서 인식할 수 있는 것만 정의한다 시스템 내부는 언급하지 않는다 사용자는 몰라도 되고 개발자만 알면 되는 것은 언급하지 않는다 여기서 언급한 사용자는 꼭 사람만을 의미하는 것은 아님 시스템과 명령이나 데이터를 주고 받는 것 시스템 내부가 아니고 외부 환경에 속하는 것
분석 단계의 산출물 (중요도 순서로 나열됨) 1. Use-case 모델 (Use-case Diagram + Use-case Specification) 2. 보충 요구사항 명세서 (Supplementary Requirement Specification) 3. 개념 모델 (Conceptual Level Class Diagram) 4. 화면/메뉴 설계 5. 개발계획 6. 기타 : 용어사전, 사용자 메뉴얼
0-1. Use-case Model
Use-case Diagram 지뢰찾기 닫힌 칸을 열기 지뢰를 표시 플레이어 닫힌 칸들을 열기
앞의 Use-case 모델은 적절한가? 무엇을 기준으로 판단할 것인가? 좋은 모델의 기준은 무엇인가?
좋은 모델의 기준은? 좋은 모델은 정보의 전달이라는 목적에 충실해야 한다 Use-case 모델이 고객에게 전달하는 정보 : 시스템을 이렇게 만들면 되나? Use-case 모델이 개발자에게 전달하는 정보 : 이러한 시스템을 만들어라 전달해야 하는 정보를 빠짐 없이 포함해야 한다 이해하기 쉽고 간결해야 한다
빠진 Use-case 는? 새 게임 시작하기 시나리오 초급 중급 고급 선택 색깔 소리 같은 옵션 지정 최고 기록 보기 위와 같은 것도 포함해야 하나?
Use-case Diagram 의 목적 Use-case Diagram -> 조감도 (Bird’s Eye View) 시스템이 제공하는 기능을 큰 단위로 표현한 것 모든 세세한 기능을 Use-case Diagram 으로 표현하지 말라 세밀한 묘사는 다른 적당한 다이어그램에서 표현하면 된다
지뢰찾기의 Use-case ? 지뢰찾기 게임이 사용자에게 제공하는 기능을 크게 분류하면?
Use-case Diagram 지뢰찾기 닫힌 칸을 열기 지뢰를 표시 플레이어 닫힌 칸들을 열기
Use-case Specification
Use-case Specification Name : 닫힌 칸을 열기 Description: 지뢰가 없을 것으로 짐작되는 칸을 연다 Actor: 플레이어 Precondition: 칸은 닫혀있어야 한다 지뢰 표시가 없는 칸이어야 한다
닫힌 칸을 열기 Main Flow 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다 3. 주변의 지뢰의 수가 0 이면 지뢰 수 0은 표시되지 않고 주변의 닫힌 칸들이 모두 열려지며 각 칸 안에 그 주변의 지뢰의 수가 표시된다. 이 과정은 재귀적으로 반복된다
닫힌 칸을 열기 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다
Use-case Specification Name : 지뢰를 표시 Description: 지뢰가 있을 것으로 짐작되는 칸에 지뢰 표시를 한다 Actor: 플레이어 Precondition: 칸은 닫혀있어야 한다
지뢰를 표시 Main Flow: 지뢰가 있을 것으로 짐작되는 칸을 플레이어가 마우스 오른쪽 버튼으로 클릭한다 칸에 지뢰 표시가 되고, 남은 지뢰 수 표시가 1 감소한다. 지뢰 표시가 된 칸을 마우스 오른쪽 버튼으로 클릭한다 칸에서 지뢰 표시가 제거되고, 남은 지뢰 수 표시가 1 증가한다
지뢰를 표시 Notes: 지뢰 표시가 된 칸은 열 수가 없다 "닫힌 칸 열기" 의 Precondition 참고
Use-case Specification Name : 닫힌 칸들을 열기 Description: 인접한 지뢰가 모두 표시되었을 경우, 지뢰가 없는 인접한 빈 칸을 모두 동시에 연다 Actor: 플레이어 Precondition: 클릭하는 칸은 열려 있어야 한다 열려진 칸에 표시된 주변 지뢰 수 만큼 주변 칸들에 지뢰 표시들이 있어야 한다
닫힌 칸들을 열기 Main Flow 열려진 칸에서 마우스 왼쪽과 오른쪽 버튼을 동시에 클릭한다 인접한 8개의 칸들 중 지뢰 표시가 되어 있지 않은 칸들 이 열려진다. 열려진 각 칸들에는 인접한 지뢰의 수가 표시된다. 열려진 칸의 주변 지뢰의 수가 0이면 지뢰 수 0은 표시되지 않고 주변의 칸 모두 열려지며 각 칸 안에 그 주변의 지뢰의 수가 표시된다. 이 과정은 재귀적으로 반복된다
닫힌 칸들을 열기 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다
0-2. 보충 요구사항
보충 요구사항 모든 것을 Use-case 형태에 맞춰서 표현할 필요없다 기타 요구사항으로 표현하는 편이 더 단순한 것들도 있다
보충 요구사항 난이도 초급: 9 행 9 열의 칸, 10개의 지뢰 중급: 16 행 16 열의 칸, 40개의 지뢰 고급: 16 행 30 열의 칸, 99개의 지뢰 화면에 남은 지뢰수가 표시된다 남은 지뢰수 = 전체 지뢰 수 – 지뢰 표시의 수 화면에 초시계가 표시된다 첫 칸을 연 때부터 초시계가 시작하고 성공하거나 실패한 순간 초시계가 정지한다 난이도 별로 최단시간 성공 기록을 유지한다
0-3. 개념모델 (Conceptual Model)
개념모델 Use-case 모델의 이해를 돕기 위해 작성 Use-case 모델에 등장하는 개념들의 관계를 표현한다 용어 사전과 비슷한 역할을 한다
지뢰찾기의 개념모델 개념모델의 목적은 시스템 설계가 아니고 관련 개념의 이해이다 모듈 설계와 다르다 다음의 개념들을 이해하기 쉽게 표현하기 위한 모델이다 지뢰, 이웃칸, 지뢰표시, 칸의 상태 … 사용자와 개발자들이 위 개념들을 잘 알고 있다면, 개념모델은 생략 가능하다 지뢰 찾기의 경우 사실 개념 모델은 필요없다 금융 정보시스템을 개발하는 경우라면, 관련 개념들이 이해하기 매우 어려울 것이므로 개념모델이 꼭 필요할 것이다.
지뢰찾기의 개념모델
0-4. 화면/메뉴 설계
화면설계 진행화면 성공 실패
메뉴설계
0-5. 개발 계획
점진적 개발 크고 복잡한 시스템을 한 번에 완성하는 것은 매우 어렵다 작게 쪼개어 하나씩 만들어 가는 것이 좋다 Use-case 단위로 만들어 나간다 중요한 Use-case 를 먼저 선택하여 설계, 구현, 테스트하여 완성하고 그 다음 Use-case 를 선택하여 만들어 나간다
Use-case 단위로 점진적 개발 Use-case 의 우선순위 별로 점진적 개발 닫힌 칸을 열기 지뢰를 표시 닫힌 칸들을 열기 1차 개발 2차 개발
1-1. 1차개발 : 분석
1차개발 개요 가장 중요한 use-case 인 "닫힌 칸을 열기" 를 구현한다 1차개발의 완료는 빠르면 빠를 수록 좋다 실행하고 테스트 해 볼 수 있는 1차 버전은, 고객으로부터 요구사항 수집하는데, 개발자가 시스템을 이해하고 개발하는데, 아주 큰 도움이 된다 1차개발을 빠르게 완료하기 위하여, use-case 를 단순하게 수정하고 구현하는 것도 좋다. 단순하게 수정된 내용은 2차 개발에서 보충 구현한다.
1-1-1. Use-case 모델
Use Case Diagram 지뢰찾기 닫힌 칸을 열기 지뢰를 표시 플레이어 닫힌 칸들을 열기
Use Case Specification (단순화됨) Name : 닫힌 칸을 열기 Description: 지뢰가 없을 것으로 짐작되는 칸을 연다 Actor: 플레이어 Precondition: 칸은 닫혀있어야 한다
닫힌 칸을 열기 Main Flow 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다
닫힌 칸을 열기 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다
1-1-2. 보충 요구사항
보충 요구사항 난이도 초급: 9 행 9 열의 칸, 10개의 지뢰
1-1-3. 화면 설계
화면설계 진행화면 성공하거나 실패했을 경우 위와 같은 대화상자가 출력된다
메뉴설계
1-2. 1차개발 : 아키텍처 설계
아키텍처 설계의 목적 주요 클래스 사이의 인터페이스를 정의 시스템을 몇 개의 주요 클래스로 나누고 그들 사이의 인터페이스를 정의한다 주요 클래스 사이의 상호작용만 정의한다 주요 클래스 내부는 언급하지 않는다 주요 클래스 내부는 상세 설계 단계에서 설계에서 아키텍처 설계 단계가 가장 중요하다
아키텍처 설계의 산출물 (중요도 순서로 나열됨) Class Diagram 주요 클래스를 그린 클래스 다이어그램 Specification Level Class Diagram Sequence Diagram 주요 클래스 사이의 상호 작용을 설계 Specification Level Sequence Diagram Use-case specification 에서 주요 이벤트 각각에 대하여 sequence diagram 이 그려져야 한다 Activity Diagram or Statechart Diagram
작업1: 주요 클래스 식별 시스템을 주요 클래스로 나눈다 시스템을 주요 클래스로 잘 나누는 것은 설계자의 경험과 능력에 의존한다 클래스는 단순하게 요약 설명될 수 있는 분명한 역할이 있어야 한다 클래스들 사이의 관계는 상호 의존성이 낮을 수록 좋다 개념 설계의 클래스가 아키텍처 설계의 주요 클래스가 되어야 하는 것은 아니다 그럴 수도 있고 아닐 수도 있다
아키텍처 MVC 구조 문서 중심 어플리케이션의 설계에 적합함 예: 워드프레세서, 그래픽 에디터 문서 중심 어플리케이션의 설계에 적합함 예: 워드프레세서, 그래픽 에디터 Model : 어플리케이션의 주요 데이터를 담당 View : 사용자 인터페이스를 담당 Controller : 처리 절차를 담당 3계층 구조 데이터베이스 기반 정보 시스템 설계에 적합함 Presentation 계층 : 사용자 인터페이스를 담당 Business Logic 계층 : 업무 로직을 담당 Data 계층 : 데이터를 담당
주요 클래스와 모듈 주요 클래스는 모듈을 대표한다 모듈 내부에서만 접근되는 여러 클래스들이 있을 것이다 모듈 내부 클래스1 주요 클래스1 내부 클래스2 주요 클래스2 내부 클래스3 내부 클래스4
모듈간 상호의존성 모듈2 모듈1 모듈3 자료구조 여러 모듈이 공통으로 접근하는 자료구조가 있으면 안됨 주요 로직 자료구조 처리 로직 모듈1 모듈3 주요 로직 자료구조 처리 로직 주요 로직 자료구조 처리 로직 자료구조
Abstract Data Type 모듈2 모듈1 모듈3 ADT 바람직한 구조 주요 로직 주요 로직 주요 로직 자료구조 처리로직
Abstract Data Type 자료구조 + 함수 함수는 자료구조에 직접 접근해야 하는 처리 로직을 구현 한 것 외부에서는 자료구조에 직접 접근할 수 없고 이 함수만 호출할 수 있다 외부에 자료구조는 노출되지 않는다 (encapsulation) ADT 개념을 확장한 것이 객체지향 언어의 Class
주요 모듈의 크기 주요 모듈이란 일종의 서브시스템(subsystem)을 말한다 주요 모듈의 크기는 한 사람이 상세 설계 및 구현을 담당하기에 적당한 크기 너무 작고 세밀하게 나누지 말라 자세한 것은 상세설계 단계에서 설계한다
작업1: 주요 클래스 식별 지뢰찾기는 비교적 단순한 로직이므로 주요 클래스도 단순하다 Minesweeper : 게임 로직을 담당 UI : 사용자 인터페이스를 담당 Minesweeper UI
작업2: 시스템 내부 절차 식별 Use-case specification 의 절차를 수행하기 위해 시스템 내부에서 수행해야 하는 일을 순서대로 적는다 너무 자세하면 안되고 주요 아키텍처 수준의 절차이어야 한다
닫힌 칸을 열기 use-case Main Flow 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다
닫힌 칸을 열기 use-case Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다 A2. 모든 칸이 열려졌으면 게임은 성공으로 종료된다
닫힌 칸을 열기 절차 1. 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 a. 마우스 입력을 받는다 b. 눌려진 칸을 찾는다 2. 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다 c. 칸을 열린 상태로 바꾼다 d. 주변 지뢰 수를 계산한다 e. 주변 지뢰수와 칸의 상태를 그린다
닫힌 칸을 열기 절차 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다 c-1. 지뢰가 있는 칸이면 게임 실패 c-2. 게임실패 메시지를 출력하고 게임종료 A2. 모든 칸이 열려졌으면 게임은 성공으로 종료된다 e-1. 모든 칸이 열려졌으면 게임성공 e-2. 게임성공 메시지를 출력하고 게임종료
작업3: 클래스에 역할 할당 UI a. 마우스 입력을 받는다 b. 눌려진 칸을 찾는다 e. 주변 지뢰수와 칸의 상태를 그린다 c-2. 게임실패 메시지를 출력하고 게임종료 e-2. 게임성공 메시지를 출력하고 게임종료 Minesweeper c. 칸을 열린 상태로 바꾼다 d. 주변 지뢰 수를 계산한다 c-1. 지뢰가 있는 칸이면 게임 실패 e-1. 모든 칸이 열려졌으면 게임성공
작업4: 역할을 메소드에 할당 UI OnLBottonDown a. 마우스 입력을 받는다 b. 눌려진 칸을 찾는다 DrawGame e. 주변 지뢰수와 칸의 상태를 그린다 GameFailure c-2. 게임실패 메시지를 출력하고 게임종료 GameSuccess e-2. 게임성공 메시지를 출력하고 게임종료
작업4: 역할을 메소드에 할당 Minesweeper OpenCell c. 칸을 열린 상태로 바꾼다 GetAdjacentMineCount d. 주변 지뢰 수를 계산한다
작업5: 메소드 보완 필요한 메소드를 추가한다 Sequence diagram 을 그려보는 것이 바람직하다 상호작용을 가능한 단순하게 만든다
작업5: 메소드 보완 꼭 필요한 절차가 빠졌다 무엇이 빠졌는가? 구현해 볼 때까지는 알 수 없나? 시퀀스 다이어그램으로 꼼꼼히 시뮬레이션 하면서 빠진 절차를 밝혀 내야 한다
작업5: 메소드 보완 UI 가 칸의 상태를 그리려면 Minesweeper 로부터 상태값을 받아와야 한다 Minesweeper 가 UI 의 DrawGame() 을 호출할 필요가 없다 칸을 연 후 다시 그려야 하는 것은 당연하다 UI 가 Minesweeper 의 OpenCell() 을 호출한 후 스스로 DrawGame() 을 호출하면 된다 성공과 실패 여부를 UI 가 Minesweeper 로부터 얻어가도록 수정 호출이 단방향일 수 있다면 구조가 더 단순해진다
작업5: 메소드 보완 초기화/마무리 메소드를 추가한다
작업6: 클래스 다이어그램
1-3. 1차개발 : 상세 설계
1-3-1. 1차개발 : Minesweeper 상세설계
상세 설계 주요 클래스 내부 로직을 설계한다 아키텍처 설계에서 주요 클래스의 메소드 목록이 정의되었다 주요 클래스의 메소드들의 내부 로직을 설계한다 주요 클래스 내부에서 사용될 내부 클래스들을 식별한다 내부 클래스에 역할을 할당한다 주요 클래스의 메소드 각각에 대하여 시퀀스 다이어그램이 그려져야 한다
작업1: 내부 클래스 식별 개념 모델을 참고하여 내부 클래스를 만든다 개념 모델의 클래스가 주요 클래스나 내부 클래스가 아닐 수 있다
작업2: 클래스에 역할 할당 Minesweeper : 지뢰찾기 게임의 로직을 담당 게임 성공과 실패를 판단 칸을 열 수 있는 지 없는 지를 판단 주변의 모든 칸도 저절로 열려야 하는 지 판단 (이 기능은 현재 반복에서 구현하지 않는다) Minefield : 자료구조를 담당 칸, 지뢰, 깃발의 목록 자료구조 게임의 로직과 자료구조를 분리 Cell : 칸 ADT 자료구조는 가능한 노출되지 않는 편이 좋다 Mine: 지뢰 ADT
작업3: 상세로직 시퀀스 다이어그램 주요 클래스의 메소드 각각에 대하여 시퀀스 다이어그램을 그린다 Minesweeper 모듈의 주요 클래스는 Minesweeper Minesweeper 클래스의 메소드 각각에 대하여 상세로직 시퀀스 다이어그램을 그린다 상세로직 시퀀스 다이어그램을 그리면서 내부 클래스에 할당된 역할에 따라 내부 클래스에 메소드를 만든다
작업4: 클래스 다이어그램 시퀀스 다이어그램을 그리며 만든 메소드를 클래스 다이어그램으로 정리한다
작업5: 클래스 다이어그램 보완 Mine 클래스는 메소드가 없다. 부여된 역할이 없다 Cell 에 Mine 이 있는지 없는지 나타내는 bool 형의 멤버 변수 하나면 족하다 Minefield 자료구조를 2차원 배열 자료구조로 만들면 neighbor 연관을 구현할 필요 없다 GetAdjacentMineCount() 는 게임로직이라기 보다는 Minefield 자료구조에 대한 검색에 가깝다 Minefield 클래스에서 구현
작업6: 시퀀스 다이어그램 보완 변경된 클래스 다이어그램에 따라 시퀀스 다이어그램을 보완한다
1-3-2. 1차개발 : UI 상세 설계
UI 상세 설계는 생략 UI 상세 설계는 구현될 플랫폼의 UI 기술에 크게 의존한다 여기서는 Microsoft Visual C++ MFC 로 구현한다
1-4. 1차개발: 테스트 구현
UI 테스트 구현 다음과 같은 Minefield 를 구현하여 UI 를 테스트한다
UI 테스트 데이터 구현 지뢰 1
UI 테스트 데이터 구현 주변 지뢰 수 1 2 3
model.h #ifndef MINESWEEPER_H #define MINESWEEPER_H enum CellState { CLOSED = 0, OPENED = 1 }; class Minesweeper { public: void InitGame(int rowCnt, int colCnt, int mineCnt); void OpenCell(int row, int col); bool IsSuccess(); bool IsFailure(); CellState GetCellState(int row, int col); int GetAdjacentMineCount(int row, int col); #endif
model.cpp #include <stdafx.h> #include <assert.h> #include "model.h" static char mine[9][9] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }; static char adjacentMineCnt[9][9] = { 1, 2, 0, 1, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 2, 0, 3, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 1, 2, 2, 1, 1, 0, 2, 1, 0, 1, 0, 0, 1
static char opened[9][9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void Minesweeper::InitGame(int rowCnt, int colCnt, int mineCnt) { memset(opened, 0, sizeof(char) * 9 * 9); } void Minesweeper::OpenCell(int row, int col) { assert( 0 <= row && row < 9 ); assert( 0 <= row && col < 9 ); opened[row][col] = 1; bool Minesweeper::IsSuccess() { for (int r = 0; r < 9; ++r) for (int c = 0; c < 9; ++c) if (!opened[r][c] && !mine[r][c]) return false; return true;
bool Minesweeper::IsFailure() { for (int r = 0; r < 9; ++r) for (int c = 0; c < 9; ++c) if (opened[r][c] && mine[r][c]) return true; return false; } CellState Minesweeper::GetCellState(int row, int col) { assert( 0 <= row && row < 9 ); assert( 0 <= row && col < 9 ); if (opened[row][col]) return OPENED; return CLOSED; int Minesweeper::GetAdjacentMineCount(int row, int col) { return adjacentMineCnt[row][col];
Model 테스트 구현 UI 를 텍스트 모드로 구현한다
UI.c #include <stdio.h> #include "model.h" Minesweeper game; void help() { printf("o 6 3 -> 6 행 3 열의 사각형을 열기\n"); printf("h -> 도움말\n"); printf("x -> 게임 종료\n"); printf("\n\n"); } void showMinefield() { int row, col; printf(" "); for (col = 0; col < 9; ++col) printf(" %d ", col); for (row = 0; row < 9; ++row) { printf("\n %d ", row); if (game.GetCellState(row, col) == CLOSED) printf(" . "); else { int n = game.GetAdjacentMineCount(row, col); if (n == 0) printf(" "); else printf(" %d ", n); printf("\n\n>");
void main() { char s[80]; int r, c; game.InitGame(9, 9, 10); help(); for (;;) { showMinefield(); gets(s); switch(s[0]) { case 'o': case 'O': sscanf(s+1, "%d %d", &r, &c); game.OpenCell(r, c); if (game.IsSuccess()) { puts("You Win"); return; } if (game.IsFailure()) { puts("Game Over"); return; } break; case 'h': case 'H': help(); break; case 'q': case 'Q': return; }; }
2-1. 2차개발 분석
개요 "닫힌 칸을 열기" 구현 보완 "지뢰를 표시" 구현
2-1-1. Use-case Model
Use Case Diagram 지뢰찾기 닫힌 칸을 열기 지뢰를 표시 플레이어 닫힌 칸들을 열기
Use Case Specification Name : 닫힌 칸을 열기 Description: 지뢰가 없을 것으로 짐작되는 칸을 연다 Actor: 플레이어 Precondition: 칸은 닫혀있어야 한다 지뢰 표시가 없는 칸이어야 한다
닫힌 칸을 열기 Main Flow 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다 3. 주변의 지뢰의 수가 0 이면 지뢰 수 0은 표시되지 않고 주변의 닫힌 칸들이 모두 열려지며 각 칸 안에 그 주변의 지뢰의 수가 표시된다. 이 과정은 재귀적으로 반복된다
닫힌 칸을 열기 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다
Use Case Specification Name : 지뢰를 표시 Description: 지뢰가 있을 것으로 짐작되는 칸에 지뢰 표시를 한다 Actor: 플레이어 Precondition: 칸은 닫혀있어야 한다
지뢰를 표시 Main Flow: 지뢰가 있을 것으로 짐작되는 칸을 플레이어가 마우스 오른쪽 버튼으로 클릭한다 칸에 지뢰 표시가 되고, 남은 지뢰 수 표시가 1 감소한다. 지뢰 표시가 된 칸을 마우스 오른쪽 버튼으로 클릭한다 칸에서 지뢰 표시가 제거되고, 남은 지뢰 수 표시가 1 증가한다
지뢰를 표시 Notes: 지뢰 표시가 된 칸은 열 수가 없다 "닫힌 칸 열기" 의 Precondition 참고
2-1-2. 보충 요구사항
보충 요구사항 변화 없음
2-1-3. 화면/메뉴 설계
화면/메뉴 설계
2-2. 2차개발 설계
개요 2차 개발에서 추가된 기능을 설계 구현한다 1차 개발에서 설계 구현된 것을 수정한다 얼마나 수정되어야 하는가? 1차 개발의 설계가 우수했다면 변경은 적을 것이고 주로 기능 추가일 것이다 만약 크게 변경되어야 한다면 1차 개발의 설계가 무엇이 잘못되었는지 분석 반성하여야 한다 반성의 결과는 바람직한 설계 지침으로 정리되어야 함
작업1: 시스템 내부 절차 식별 추가된 Use-case specification 의 절차를 수행하기 위해 시스템 내부에서 수행해야 하는 일을 순서대로 적는다
닫힌 칸을 열기 use-case Main Flow 지뢰가 없을 것으로 짐작되는 칸을 플레이어가 마우스 왼쪽 버튼으로 클릭한다 칸은 열려진 상태로 표시되며, 주변의 지뢰의 수가 칸 안에 표시된다 3. 주변의 지뢰의 수가 0 이면 지뢰 수 0은 표시되지 않고 주변의 닫힌 칸들이 모두 열려지며 각 칸 안에 그 주변의 지뢰의 수가 표시된다. 이 과정은 재귀적으로 반복된다
주변 지뢰 수 0 일 때의 절차 주변의 닫힌 칸들을 모두 연다 이미 구현된 Minesweeper.OpenCell(int row, int col) 메소드를 호출하여 주변의 닫힌 칸들을 모두 연다 재귀적으로 구현한다
지뢰를 표시 use-case Main Flow: 지뢰가 있을 것으로 짐작되는 칸을 플레이어가 마우스 오른쪽 버튼으로 클릭한다 칸에 지뢰 표시가 되고, 남은 지뢰 수 표시가 1 감소한다. 지뢰 표시가 된 칸을 마우스 오른쪽 버튼으로 클릭한다 칸에서 지뢰 표시가 제거되고, 남은 지뢰 수 표시가 1 증가한다
지뢰를 표시 절차 a. 오른쪽 버튼의 입력을 받는다 (UI) b. 눌려진 칸을 찾는다 (UI) c. 칸에 지뢰 표시를 토글한다 (Minesweeper)
칸의 상태 Opened, Closed, Mined, Flaged Cell 의 상태 메소드 정리 bool IsOpened(); bool IsMined(); bool IsFlaged(); Minesweeper 의 상태 메소드 정리
작업2: 시퀀스 다이어그램으로 설계
작업3: 클래스 다이어그램
변경 사항 추가되는 메소드 UI::OnRButtonDown(int x, int y); Minesweeper::FlagCell(int row, int col); Minesweeper::OpenAdjacentCells(int row, int col); Cell::PutFlag(); Cell::RemoveFlag(); 수정되는 메소드 Minesweeper::OpenCell(int row, int col); 주변 지뢰 수가 0일 경우 OpenAdjacentCells() 호출을 하도록 수정
변경 사항 Cell 의 GetState() 메소드를 다음으로 대체한다 Cell::IsOpened() Cell::IsFlaged() Cell::IsMined() Minesweeper 의 GetCellState() 메소드를 다음으로 대체한다 Minesweeper::IsOpened() Minesweeper ::IsFlaged() Minesweeper ::IsMined()
2-3. 2차개발 테스트 계획
UI 테스트 구현 다음과 같은 Minefield 를 구현하여 UI 를 테스트한다
UI 테스트 데이터 구현 지뢰 1
UI 테스트 데이터 구현 주변 지뢰 수 1 2 3
model.h #ifndef MINESWEEPER_H #define MINESWEEPER_H class Minesweeper { public: void InitGame(int rowCnt, int colCnt, int mineCnt); void OpenCell(int row, int col); void FlagCell(int row, int col); bool IsSuccess(); bool IsFailure(); int GetAdjacentMineCount(int row, int col); bool IsFlaged(int row, int col); bool IsOpened(int row, int col); bool IsMined(int row, int col); }; #endif
model.cpp #include <stdafx.h> #include <assert.h> #include "model.h" static char mine[9][9] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }; static char adjacentMineCnt[9][9] = { 1, 2, 0, 1, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 2, 0, 3, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 1, 2, 2, 1, 1, 0, 2, 1, 0, 1, 0, 0, 1
static char opened[9][9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static char flaged[9][9] = {
void Minesweeper::InitGame(int rowCnt, int colCnt, int mineCnt) { memset(opened, 0, sizeof(char) * 9 * 9); memset(flaged, 0, sizeof(char) * 9 * 9); } void Minesweeper::OpenCell(int row, int col) { assert( 0 <= row && row < 9 && 0 <= row && col < 9 ); if (flaged[row][col]) return; opened[row][col] = 1; bool Minesweeper::IsSuccess() { for (int r = 0; r < 9; ++r) for (int c = 0; c < 9; ++c) if (!opened[r][c] && !mine[r][c]) return false; return true; bool Minesweeper::IsFailure() { if (opened[r][c] && mine[r][c]) return true; return false; int Minesweeper::GetAdjacentMineCount(int row, int col) { return adjacentMineCnt[row][col];
bool Minesweeper::IsFlaged(int row, int col) { assert( 0 <= row && row < 9 && 0 <= row && col < 9 ); return flaged[row][col] == 1; } bool Minesweeper::IsMined(int row, int col) { bool Minesweeper::IsOpened(int row, int col) { return opened[row][col] == 1; void Minesweeper::FlagCell(int row, int col) { flaged[row][col] = 1;
Model 테스트 Model 은 UI 와 통합하여 테스트한다
3-1. 3차개발 분석
개요 "닫힌 칸들을 열기" 구현
3-1-1. Use-case Model
Use Case Diagram 지뢰찾기 닫힌 칸을 열기 지뢰를 표시 플레이어 닫힌 칸들을 열기
Use-case Specification Name : 닫힌 칸들을 열기 Description: 인접한 지뢰가 모두 표시되었을 경우, 지뢰가 없는 인접한 빈 칸을 모두 동시에 연다 Actor: 플레이어 Precondition: 클릭하는 칸은 열려 있어야 한다 열려진 칸에 표시된 주변 지뢰 수 만큼 주변 칸들에 지뢰 표시들이 있어야 한다
닫힌 칸들을 열기 Main Flow 열려진 칸에서 마우스 왼쪽과 오른쪽 버튼을 동시에 누른다 인접한 8개의 칸들 중 지뢰 표시가 되어 있지 않은 칸들 이 열려진다. 열려진 각 칸들에는 인접한 지뢰의 수가 표시된다. 열려진 칸의 주변 지뢰의 수가 0이면 지뢰 수 0은 표시되지 않고 주변의 칸 모두 열려지며 각 칸 안에 그 주변의 지뢰의 수가 표시된다. 이 과정은 재귀적으로 반복된다
닫힌 칸들을 열기 Alternative Flow A2. 지뢰가 있는 칸이었으면 지뢰가 폭발하여 게임은 실패로 종료된다
3-1-2. 보충 요구사항
보충 요구사항 난이도 초급: 9 행 9 열의 칸, 10개의 지뢰 중급: 16 행 16 열의 칸, 40개의 지뢰 고급: 16 행 30 열의 칸, 99개의 지뢰 화면에 남은 지뢰수가 표시된다 남은 지뢰수 = 전체 지뢰 수 – 지뢰 표시의 수
3-1-3. 화면/메뉴 설계
화면설계 진행화면 성공 실패
3-2. 3차개발 설계
개요 3차 개발에서 추가된 기능을 설계 구현한다 2차 개발에서 설계 구현된 것을 수정한다
작업1: 시스템 내부 절차 식별 추가된 Use-case specification 의 절차를 수행하기 위해 시스템 내부에서 수행해야 하는 일을 순서대로 적는다
닫힌 칸들을 열기 절차 마우스 두 버튼이 동시에 눌려진 것을 감지한다 열린 칸이 아니면 무시 주변의 지뢰 수와 주변의 지뢰표시 수를 비교한다 3-1. 주변 지뢰 수 계산 3-2. 주변 지뢰표시 수 계산 두 수가 같으면 주변의 지뢰표시가 없고 닫혀진 모든 칸을 연다. 주변의 칸을 여는 절차는 이미 구현한 inesweeper.AdjacentCells() 를 이용한다 두 수가 같지 않으면 무시
메소드에 역할 할당 UI::OnRButtonDown() 1 Minesweeper::OpenCells() 2, 3, 4, 6 Minefield::GetAdjacentMineCount() 3-1 Minefield::GetAdjacentFlagCount() 3-2 Minesweeper::OpenAdjacentCells() 5
작업2: 시퀀스 다이어그램으로 설계
작업3: 클래스 다이어그램
3-3. 3차개발 테스트 계획
테스트 계획 추가되는 기능이 매우 단순한 편 두 모듈 각각의 테스트는 생략하고 통합하여 테스트한다
4. 4차개발
개요 화면 깜빡거림의 개선 마우스 왼쪽 버튼을 누를 때 누르고 있을 때는 칸이 눌린 상태로 그려짐 버튼이 놓여질 때 칸이 열리도록 두 버튼을 동시에 누를 때 어느 버튼이 먼저 눌려졌는지 상관 없도록 누르고 있을 때는 주변의 지뢰표시 없는 칸들이 눌린 상태로 그려짐 버튼이 놓여질 때 칸들이 열리도록
설계