백엔드/JAVA

가위, 바위, 보 게임 구현하기 예제 실습

두개의 문 2023. 5. 2. 17:49
가위, 바위, 보 게임 구현하기

 

package game;

import java.util.Random;
import java.util.Scanner;

public class RockPaperScissorGame1 {

	public static void p (String msg) {
		System.out.print(msg);
	}
    
	public static void main(String[] args) {
		String [] rockPaperScissor = {"주먹", "보","가위"};
		int rockPaperScissorSize = rockPaperScissor.length;
		// -> 유지보수를 위해 숫자보다는 배열의 길이를 변수 또는 상수에 저장해서 사용
		// 상수 선언 : 변수 명 앞에 final 붙이기
		
		// 가위/바위/보 인덱스 상수
		int IDX_ROCK = 0;		// 바위 인덱스 상수
		int IDX_PAPER = 1;		// 보 인덱스 상수
		int IDX_SCISSOR = 2;	// 가위 인덱스 상수
	}

1. ① 출력함수 따로 설정  : p() 메소드

    ② 문자열 배열 rockPaperScissor 생성 및 선언

    ③ 기호 상수  : 상수를 숫자로 표현하는 것보다 변수처럼 이름을 설정해서 사용하게 되면,

       직관적으로 데이터 파악 가능 → 코드의 가독성 향상ㅇ

        - Java에서 상수 선언 방법 

          : 변수 선언 시, 변수 앞에 final 표기

 

 

        // 1) 첫번째 난수 발생 방법
		int com = (int)(Math.random() * 3);		// 0 ~ 2까지 난수 발생
		int me = (int)(Math.random() * 3);
		
		// 2) 두번째 난수 발생 방법
		// Random 클래스를 이용하여 난수 구하기
		Random rand = new Random();
		// 난수를 구해서 3으로 나눈 나머지 = 배열의 인덱스를 무작위로 구하는 역할
		com = Math.abs(rand.nextInt() % rockPaperScissorSize );
		// me = Math.abs(rand.nextInt() % rockPaperScissorSize );
        // -> 사용자의 경우는 키보드로 직접 입력받아보자.

2. ① 0 ~ 2 범위의 난수 발생 방법

      - Math.random() 이용

      - Random 클래스 이용 : nextInt() 이용 → 입력된 값을 정수형으로 반환

    ② com = Math.abs(rand.nextInt() % rockPaperScissorSize );

      - rand.nextInt(); : seed값 지정하지 않을 경우, 해당 자료형의 범위 내에서 랜덤하게 출력 

      - Math.abs() : 주어진 숫자의 절대값을 반환  ( ∵ 난수에서 음수가 발생할 수 있으니까 ) 

      - 정수형으로 반환된 난수에 따로 seed값 지정하지 않은 이유 

        : rockPaperScissorSize = 3 이므로 3으로 나눴을 때 나머지 = 0, 1, 2 값 뿐이기 때문

     

 

		String comChoice = rockPaperScissor[com];	
       		String meChoice = null ; 
        
		Scanner sc = new Scanner(System.in);
		p("가위, 바위, 보 중 하나를 선택하세요. (가위=1, 바위=2, 보=3) : ");
		// meChoice = sc.nextLine(); -> 결과값 : 문자열
		me = Integer.parseInt(sc.nextLine());  // 문자열 -> 정수형으로 형변환

3. 컴퓨터와 나의 데이터 값 설정 - Model

    ① 컴퓨터가 선택한 문자열 : 배열을 이용해 변수 comChoice에 저장

    ② 사용자의 경우, Scanner 클래스를 이용해 직접 입력 받기

     - Scanner 클래스 

     - sc.nextLine() : 문자열을 입력할 때 '\n' 또는 '\r'로 끝나는 문자열까지 반환

       예시 ) "안녕하세요 반갑습니다 [엔터]" → "안녕하세요 반갑습니다" 

     - 문자열 → 숫자로 변환 

      · Integer.parseInt() : 정수로 변환

      · Double.parseDouble() : 실수로 변환

 

 

		switch ( me ) {
		case 1 : // 가위를 선택한 경우
			meChoice = rockPaperScissor[IDX_SCISSOR];
			break;

		case 2 : // 바위를 선택한 경우
			meChoice = rockPaperScissor[IDX_ROCK];
			break;
			
		case 3 : // 보를 선택한 경우
			meChoice = rockPaperScissor[IDX_PAPER];
			break;
		}

4. ① 보정/정체 : 입력한 내용을 실제 가위/바위/보 선택으로 변환 필요

    ② switch문 

      - if문과 비교 

if문 switch문
  경우의 수가 많아질수록 else-if 계속 추가   하나의 조건식
  조건식의 결과 : 참과 거짓 → 2가지   많은 경우의 수 처리 가능
  여러 개의 조건식을 계산 → 처리 시간 ↑   단, 제약조건 있음.

     - 형식

switch ( 조건식 ) {
             case 값1 :
             // 조건식의 결과가 값1과 같을 경우 수행될 문장들
                             break;
             case 값2 :
             // 조건식의 결과가 값2과 같을 경우 수행될 문장들
                             break;
              default :
             // 조건식의 결과와 일치하는 case문이 없을 때 수행될 문장들

}

      · 조건식 계산 → 조건식의 결과와 일치하는 case문으로 이동 → 이후의 문장들 수행

        → break문이나 switch문의 끝 만나면 switch문 전체를 빠져나감

      · break문 : 각 case문의 영역을 구분하는 역할 → 정보 명확히 전달 가능 

        break문 생략 시, case문 사이의 구분이 없어지므로

            다른 break문을 만나거나 switch문의 끝을 만나기 전까지 모든 문장들을 수행

   - 제약조건

    · switch문의 조건식 결과 : 정수 또는 문자열

    · case문의 값은 정수 상수(문자 포함), 문자열만 가능 / 중복 안됨

 

 

		// 판정메세지를 저장하는 변수
		String checkMsg = "여기에 판정 메세지가 들어갑니다.";	
		
		// 기준을 컴퓨터로 : comChoice
		// 컴퓨터가 가질 수 있는 경우의 수를 모두 나열함.
		// 그리고 내가 가질 수 있는 경우의 수를 그 각각에 비교함.
		
		if (comChoice.equals(rockPaperScissor[IDX_SCISSOR])) {
			// 컴퓨터가 가위를 선택한 경우
			if (meChoice.equals(rockPaperScissor[IDX_SCISSOR])) {
				// 컴 가위 = 나 가위 : 무승부
				checkMsg = "무승부 [컴퓨터:가위, 사람:가위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_ROCK])) {
				// 컴 가위 = 나 바위 : 나의 승
				checkMsg = "나의 승리 [컴퓨터:가위, 사람:바위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_PAPER])) {
				// 컴 가위 = 나 보 : 컴의 승
				checkMsg = "컴퓨터 승 [컴퓨터:가위, 사람:보]";
			}
		} else if (comChoice.equals(rockPaperScissor[IDX_ROCK])) {
			// 컴퓨터가 바위를 선택한 경우
			if (meChoice.equals(rockPaperScissor[IDX_SCISSOR])) {
				// 컴 바위 = 나 가위 : 컴의 승
				checkMsg = "컴퓨터 승 [컴퓨터:바위, 사람:가위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_ROCK])) {
				// 컴 바위 = 나 바위 : 무승부
				checkMsg = "무승부 [컴퓨터:바위, 사람:바위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_PAPER])) {
				// 컴 바위 = 나 보 : 나의 승
				checkMsg = "나의 승리 [컴퓨터:바위, 사람:보]";
			}
		} else if(comChoice.equals(rockPaperScissor[IDX_PAPER])) {
			// 컴퓨터가 보를 선택한 경우
			if (meChoice.equals(rockPaperScissor[IDX_SCISSOR])) {
				// 컴 보 = 나 가위 : 나의 승
				checkMsg = "나의 승리 [컴퓨터:보, 사람:가위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_ROCK])) {
				// 컴 보 = 나 바위 : 컴의 승
				checkMsg = "컴퓨터 승리 [컴퓨터:보, 사람:바위]";
			}
			if (meChoice.equals(rockPaperScissor[IDX_PAPER])) {
				// 컴 보 = 나 보 : 무승부
				checkMsg = "무승부 [컴퓨터:보, 사람:보]";
			}	
		}

5. 승부판정 루틴 - Controller

   ① 컴퓨터의 데이터 값 기준으로 3가지의 큰 틀 먼저 작성

   ② 그 다음 나의 경우의 수 3가지씩 작성 : 총 9가지의 경우의 수 나열

   ③ 내부 문자열 비교 : equals() 이용

      cf. 참조 타입의 변수 간의 ==, != 연산 : 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 이용

          comChoice == meChoice : 같은 객체를 가르킴 ( 가르키는 객체의 주소 및 값 동일 )

 

		p("#판정결과# : \n");
		p(checkMsg);
		
		//p("컴퓨터 선택 : " + rockPaperScissor[com]);
		//p(" 내 선택 : " + rockPaperScissor[me]);
		
	}
}

6. 판정 출력 - View

   - if문 안에 작성하기 보다는 판정루틴과 별개로 작성하는 것이 좋음

    ※ 앞으로 프로그램 개발 시, MVC 패턴에 따라 역할을 나눠서 코드를 작성해보자.

   

 

 

▶ 프로그램 작성하면서 오류났던 부분 기록

입력값으로 3을 입력한 경우, 인덱스 번호가 배열의 범위에서 벗어났다는 에러 메시지가 떴다.

그래서 내가 키보드에 입력한 값에 이상이 있는지 체크하기 위해 직접 출력해보았다.

결론은 키보드에 입력한 값에는 이상이 없었다.

코드를 살펴본 결과, 프로그램을 짜면서 실제 값이 제대로 나오는지 확인했던 부분을 주석 처리하지 않아 오류가 발생한 걸 알 수 있었다. 

 

 

 


전역변수 / 로컬변수

 

- 변수의 선언 위치에 따라 전역변수와 로컬변수로 분류됨

- 전역변수

  · 어디서든 호출하면 사용할 수 있는 변수

  · 전역변수에는 클래스 변수와 인스턴스 변수가 있음 → 곧 배울 예정

 

- 로컬변수

  · 메서드 내에서 선언 → 메서드 내에서만 사용 가능 : 변수의 범위 ( scope )

  · 메서드 종료 시 로컬변수 소멸되어 사용 불가

 

 

 


키보드로부터 학생이름, 수학점수를 입력받아 출력하는 기능

 

package score;

import java.util.Scanner;

public class InputScore {
	public static void main(String[] args) {
		String[] names;		// 가리키는 데이터 없는 상태(null)
		int[] mathScoreArray ;

		// 기호상수 : 상수 취급 
		int STUDENT_SIZE = 2;	
		
		names = new String[STUDENT_SIZE];		
		mathScoreArray = new int[STUDENT_SIZE];

1. 변수 선언 및 기호상수 선언

  - 기호 상수 : 상수 취급

   ① 학생수를 기호상수로 정의 → 나중에 학생수의 변화가 생기더라도 STUDENT_SIZE의 값만 변경해주면 됨

   ② 이름(식별자) : 대문자 / 스네이크 표기법 사용하면 기호상수임을 알기 쉽다.

 

 

		Scanner sc = new Scanner(System.in);
		
		for(int i=0; i<STUDENT_SIZE ; i++ ) {
			p("학생이름과 수학점수를 입력해주세요 :\n");
			names[i] = sc.next();	// 학생이름 입력
			//names[i] = sc.nextLine();
			// scanner의 nextLine() '\n'으로 데이터 입력의 끝으로 간주하므로
			// 만약 출력문에 '\n'이 포함되어 있으면 빈줄을 입력한 것으로 간주하는 버그가 있다.
			// 이런 경우에는 빈칸을 구분기호로 사용하는 next() 함수를 사용하는 것이 좋다.
			mathScoreArray[i] = sc.nextInt();  // 수학점수 입력
			p("\n");
		}

2. 키보드로부터 입력 준비

  - 학생수만큼 입력하기 → 반복문 필요

  - 인원 수를 알고 있을 때 : for문 사용

 

 

		int data_size = names.length; 	// 배열의 길이를 사용하여 정보 출력
		int index = 0;					// 현재 데이터(names, mathScoreArray)의 위치를 가르킨다. 
		while ( index < data_size ) {
			p("학생이름[" + index + "] = " + names[index]);
			p("\n수학점수[" + index + "] = " + mathScoreArray[index]);
			p("\n-------------------------------\n");
			index++;
		}

3. 입력된 내용 출력

 

 

		int sum = 0;	// 과목 총점
		double avg = 0.0;	// 과목 평균
		// index  = 0; 다시 현재 학생의 위치를 가릐키려고 하므로 0으로 초기화
		index = 0;
		// 1.while문을 이용한 총점 구하기
		while(index < names.length) {
			sum = sum + mathScoreArray[index];
			index++;
		}
        // 2.향상된 for문을 이용한 총점 구하기
		sum = 0;				// 이전 총합을 새로운 총합으로 구할 것이므로 0으로 초기화
		for( var score : mathScoreArray ) {
										// = 기준 오른쪽 : 이전 총합의 값
			sum = sum + score;			// score : 현재 점수
		}								// = 기준 왼쪽 : 최신 총합 저장
		
		// 평균 = 총점 / 학생의 수
		avg = (double) sum / names.length ;
		
		// 결과 출력 이전에 출력될 정보에 대한 안내문 추가
		pl("\'수학시험\' 학생 수 : " + names.length);
		pl("총점 : "+ sum +"점");
		pl("평균 : " + avg + "점");
	}

4. 학생의 총점, 평균 구하기

   - 총점

    ① while문

    ② 향상된 for문 : 배열의 데이터를 순차적으로 가져옴

    ※ 이 때, 앞서 while문으로 총합을 구한 뒤, 주석처리를 하지 않았으므로

       향상된 for문으로 다시 구할 때 sum 값을 0으로 초기화시켜줘야 함.

      · 반복 실행을 하기 위해 루프 카운터 변수와 증감식 사용하지 않음

        → 배열 및 컬렉션 항목의 개수만큼 반복한 후, 자동으로 반복문 빠져나옴

      · 형식

for ( 타입 변수 : 배열 ) {
          실행문;
}

     ❶ for문이 처음 실행될 때 배열에서 가져올 첫 번째 값이 존재하는지 평가함

     ❷ 가져올 값이 존재 : 해당 값을 변수에 저장

     ❸ 실행문 실행

     ❹ 배열에서 가져올 다음 값이 존재하는지 평가

     ❺ 배열의 항목 수만큼 반복 

   - 평균 : 실수형으로 구하기

     · 정수형 / 정수형 = 정수형

     · 실수형 / 정수형 = 실수형 

       ① avg 변수타입을 double형으로 변경

       ② 총점과 학생의 수 중 어느 한쪽을 double형으로 강제 형변환

 

 

 


객체 지향 프로그래밍

 

- 객체 지향 언어 : 기존의 프로그래밍 언어에 몇 가지 규칙을 추가하여 발전된 형태의 것

  · 코드의 재사용성 ↑

  · 코드의 유지보수 관리 용이

  · 코드의 중복을 제거하여 신뢰성 

- OOP (Object-Oriented Programming ) : 객체의 관점에서 프로그래밍하는 것을 의미

  · 특징

   : 캡슐화 / 상속 / 추상화 / 다형성 

  ( 앞으로 하나씩 배워나가자.)

 - 객체 ( Object ) : 실제로 존재하는 것 / 속성과 동작으로 구성

- 객체 모델링 ( Object modeling ) : 현실 세계의 객체를 소프트웨어 객체를 설계하는 것

  → 현실 세계 객체의 속성과 동작을 추려내 소프트웨어 객체의 필드와 메소드로 정의하는 과정

 

- 객체 모델링 ( Object modeling )

  : 현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 의미

    → 현실 세계 객체의 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메소드로 정의하는 과정

 

- 객체의 상호작용

 

  · 객체들은 각각 독립적으로 존재 / 다른 객체들과의 상호작용하여 동작

  · 객체들 사이의 상호작용 수단 = 메소드

    → 객체가 다른 객체의 기능을 이용하는 것 = 메소드 호출

  · 메소드 호출 방법

   ※ 대상의 허락이 있어야 메소드 호출이 가능

리턴값 = 메소드를 지닌 대상 . 메소드 ( 매개값1, 매개값2, ... ) ;   
int result = Calculator.add(10, 20);

   ① 객체 . 메소드 : 도트연산자 → 객체의 속성과 메소드에 접근할 때 사용

   ② 매개값 : 메소드를 실행하기 위해 필요한 데이터

   ③ 리턴값 : 메소드가 실행되고 난 후 호출한 곳으로 돌려주는 값

  ∴ 객체의 상호작용 : 객체 간의 메소드 호출을 의미 / 매개값과 리턴값을 통해서 데이터를 주고 받음

 

- 객체 간의 관계

· 객체는 개별적으로 사용 가능 / 대부분 다른 객체들과 관계를 맺고 있음

· 관계의 종류

 ① 집합 관계 : 부품 - 완성품

 ② 사용 관계 : 객체 간의 상호작용을 의미

     ex ) 사람 - 자동차의 관계 : 사람은 자동차를 사용할 때 달린다, 멈춘다 등의 메소드 호출

 ③ 상속 관계 : 보통 상위 객체는 종류, 하위 객체는 구체적인 사물에 해당

 

- 객체와 클래스

· 클래스 : 설계도 → 객체 생성을 위한 필드와 메소드가 정의되어 있음

· 인스턴스 : 클래스로부터 만들어진 객체를 의미

· 인스턴스화 : 클래스로부터 객체를 만드는 과정

※ 하나의 클래스로부터 여러 개의 인스턴스 생성 가능

  → 단, 데이터 값은 같더라도 객체의 주소는 다름

 

- 개발 과정

① 클래스 설계

② 설계된 클래스를 기반으로 사용할 객체 생성

③ 생성된 객체 이용

 

∴ 객체 지향 프로그래밍

  : 만들고자 하는 완성품인 객체를 모델링하고, 집합 관계에 있는 부품 객체와 사용관계에 있는 객체를 하나씩 설계한 후 조립하는 방식으로      프로그램을 개발하는 기법