GameMain.java

package chapter11.interface_part;

import java.util.Scanner;

public class GameMain {

	public static void main(String[] args) {

		/*
		 * seoul45_game
		 * 1. 프로그램이 실행되면 메뉴를 출력한다.
		 * 2. 메뉴는 (1.캐릭터 생성, 2.캐릭터 정보, 3.전직하기, 4.공격하기, 0.종료)
		 *    - (1.캐릭터 생성)을 선택한 경우, 이름과 스탯값을 초기값으로 하는 Novice(평민)클래스형 인스턴스를 생성한다.
		 *      스탯은 (힘, 민첩, 지능)이 있으며 각각 랜덤한 값을 가지고, 
		 *      형평성을 위해 스탯의 총 합이 15이상인 경우에만 저장되도록 하며,
		 *      사용자의 선택에 따라 해당 스탯을 저장 or 스탯 재부여 할 수 있도록 한다.
		 * 3. - (2.캐릭터 정보)를 선택한 경우, 생성된 캐릭터의 이름, 직업, 힘, 민첩, 지능을 출력한다.
		 * 4. - (3.전직하기)를 선택한 경우, 생성된 캐릭터의 정보를 초기값으로 가지는 
		 *      선택한 클래스에 해당하는 인스턴스를 생성하고 문구를 출력한다.
		 * 5. - (4.공격하기)를 선택한 경우, Attack()메서드를 호출하여 직업(인스턴스)에 따라 공격한다.
		 * 6. - (5.스킬사용)을 선택한 경우, Skill()메서드를 호출하여 직업(인스턴스)에 따라 스킬을 사용한다.
		 * 7. - (0.종료)를 선택한 경우, 프로그램을 종료한다.
		 * 8. - 종료를 선택하기 전까지 반복한다.
		 * */
		
		Scanner scan = new Scanner(System.in);
		GameMain_Method gm = new GameMain_Method(); //1.GameMain는 GameMain_Method에 의존하고 있음(의존관계)
		Novice character = null;
		Skill skill = null;
		int num = 0;
		
		while(true) {
			gm.printMenu(); //2.의존관계로 GameMain_Method의 매서드 호출
			num = scan.nextInt();
			
			switch (num) {
				case 1 -> character = gm.createCharacter(); //호출한 값을 Novice 타입 맴버변수에 넣음
				case 2 -> {
					if(character != null)	gm.characterInfo(character); //case 1에서 저장한 값을 담은 변수 character 를 매개변수로 메서드 호출
					else					gm.printText(3);  //맴버변수 3을 가지고 메서드를 호출해라					
				}
				case 3 -> {
					if(character != null) {
						int jobNum = gm.numberInput();
						character = gm.jobSelect(jobNum, character);						
					} else					gm.printText(3);
				}
				case 4 -> {
					if(character != null)	character.attack(); //예를들어 기사로 전직했다면 인스턴스는 기사, 타입은 Novice 이라 부모에게 먼ㅌ저 접근, 그러나 오버라이드 된 메서드가 우선권을 가지게 되어 기사 메서드가 호출됨
					else					gm.printText(3);					
				}
				case 5 -> {
					if(character != null) { 
						//character.skill(); 질문3.왜 case 4번과 다르게?
						skill = gm.skill(character);
						if(skill != null)	skill.skill();
						else 				gm.printText(4);
					}
					else gm.printText(3);					
				}
				case 0 -> { 
					gm.printText(1);
					return; //질문4. 무슨역할?
				}
				default -> gm.printText(2);
			}
		}
	}

}

GameMain_Method.java

package chapter10.interface_part;

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

public class GameMain_Method {
	public void printMenu() {
		System.out.println();
		System.out.println("""
				=== ABCDE_rpg ===
				1. 캐릭터 생성
				2. 캐릭터 정보
				3. 전직하기
				4. 기본 공격
				5. 스킬 사용
				0. 종료
				=================""");
		System.out.print("번호를 선택하세요 : ");
	}
	
	//출력 문구만 모아 놓은 메서드
	public void printText(int num) {
		switch (num) {
			case 1 -> System.out.println("게임을 종료합니다.");
			case 2 -> System.out.println("번호를 잘못 입력하셨습니다.");
			case 3 -> System.out.println("캐릭터를 생성하세요.");
			case 4 -> System.out.println("스킬을 배우지 않았습니다.");
		}
	}
	
	//캐릭터를 생성하는 메서드
	//스탯 힘, 민첩, 지능을 랜덤한 숫자로 스탯의 총 합이 15이상일 경우에만 부여한다.
	//사용자의 선택에 따라 저장 or 스탯 재부여 한다.
	public Novice createCharacter() {
		Scanner scan = new Scanner(System.in);
		Random ran = new Random(); //난수를 생성할 도구 만듬(실제로 생성 x)
		System.out.println("캐릭터를 생성합니다.");
		System.out.print("사용하실 아이디를 입력해 주세요 : ");
		String id = scan.next();
		int str = 0, dex = 0, intel = 0, sum = 0; //지역변수
		
		String text = "";
		while(true) {
			System.out.println("스탯을 부여합니다. ");
			
			while(true) {
				str = ran.nextInt(10);
				dex = ran.nextInt(10);
				intel = ran.nextInt(10);
				sum = str + dex + intel;
				//스탯의 총 합이 15이상일 때까지 각 스탯에 랜덤한 숫자 부여
				if(sum >= 15)
					break; //15이상이면 가까운 while문 탈출 //아닐경우 다시 반복문으로 돌아가 난수 발생
			}
			
			System.out.printf("부여된 스탯정보: 힘[%d], 민첩[%d], 지능[%d]\n", str, dex, intel);
			System.out.print("스탯을 다시 받으시겠습니까? (y/n) : ");
			text = scan.next(); 
			
			if(text.equals("n")) //문자와 비교하기 위해 equals 사용
				break; //대답이 n일 경우 : while문 탈출 (드래그 하면 무슨 반복문에 걸려있는지 확인 가능) 
			           //대답이 n가 아닐경우 : break가 없어서 다시 반복문으로 돌아가 난수 발생시킴 (true 무한 반복)
			
		}
		//질문1.클래스에 직접 대입x, 동일 클래스 타입에(빈깡통, 지역변수) 설정된 값을 저장함
		//질문2.인스턴스 생성후 생성자로 값 설정해주고 이 인스턴스의 참조값을 담음
		Novice novice = new Novice(id, str, dex, intel);   
		System.out.println(novice); //저장된 값을 프린트 , toString
		System.out.println("현재 정보로 저장합니다.");
		
		return novice; //호출한 곳으로 저장한 값을 보냄
	}
	
	//직업에 따라 인스턴스를 생성하는 메서드
	//선택한 직업에 따라 해당 클래스로 인스턴스를 생성하고, 문구를 출력한다.
	public Novice jobSelect(int num, Novice novice) {
		Novice character = null;
		switch (num) {
			case 1 -> {
				character = new Knight(novice.getName(), novice.getStrength(), 
						novice.getDexterity(), novice.getIntelligence());
				System.out.println("기사로 전직합니다.");				
			}
			case 2 -> {
				character = new Thief(novice.getName(), novice.getStrength(), 
						novice.getDexterity(), novice.getIntelligence());
				System.out.println("도적으로 전직합니다.");
			}
			case 3 -> {
				character = new Magician(novice.getName(), novice.getStrength(), 
						novice.getDexterity(), novice.getIntelligence());
				System.out.println("마법사로 전직합니다.");				
			}
		}
		
		return character;
	}

	//전직을 위해 번호를 입력받는 메서드
	public int numberInput() {
		Scanner scan = new Scanner(System.in);
		int num = 0;
		while(num > 3 || num < 1) {
			System.out.println("""
					=== 직업 종류 ===
					1. 기사
					2. 도적
					3. 마법사""");
			System.out.print("전직할 직업의 번호를 선택하세요 : ");
			num = scan.nextInt();
		}
		return num;
	}
	
	//생성된 캐릭터의 정보를 보여 주는 메서드
	//생성된 인스턴스에 따라 직업표시가 달라진다.
	public void characterInfo(Novice novice) { //생성된 character 정보받음 타입이 Novice 이니까 여기서도 Novice 타입으로받음
		String job = "";
		
		if(novice instanceof Knight) //만약 기사로 전직했다면 jobSelect메서드에서 기사 인스턴스를 생성하여 character에 저장했을거라  본인과 본인을 비교하게 됨
			job = "기사";
		else if(novice instanceof Thief)
			job = "도적";
		else if(novice instanceof Magician)
			job = "마법사";
		else
			job = "평민"; //위에 셋다 아니라면 job 을 평민으로 설정
		
		String str = String.format("[ID: %s(%s), stat: 힘(%d), 민첩(%d), 지능(%d)]"
				, novice.getName(), job, novice.getStrength() // privater 변수라 get사용
				, novice.getDexterity(), novice.getIntelligence());
		System.out.println(str);
	}

	public Skill skill(Novice novice) {
		Skill skill = null;
		if		(novice instanceof Knight knight)
			skill = knight;
		else if	(novice instanceof Thief thief)
			skill = thief;
		else if	(novice instanceof Magician magician)
			skill = magician;
		
		return skill;
	}
}

평민

package chapter10.interface_part;

//평민
public class Novice {
	
	//멤버 변수
	private String name;		//캐릭터 이름
	private int strength;		//힘 스탯
	private int dexterity;		//민첩 스탯
	private int intelligence;	//지능 스탯
	
	//기본생성자
	public Novice() {}
	
	//명시적 생성자
	public Novice(String name, int strength, int dexterity, int intelligence) {
		this.name = name;
		this.strength = strength;
		this.dexterity = dexterity;
		this.intelligence = intelligence;
	}
	
	//멤버 메서드
	public void attack() {
		System.err.println("기본 공격");
	}

	//getter / setter
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getStrength() {
		return strength;
	}

	public void setStrength(int strength) {
		this.strength = strength;
	}

	public int getDexterity() {
		return dexterity;
	}

	public void setDexterity(int dexterity) {
		this.dexterity = dexterity;
	}

	public int getIntelligence() {
		return intelligence;
	}

	public void setIntelligence(int intelligence) {
		this.intelligence = intelligence;
	}

	//객체 정보 출력용 Object의 멤버 메서드 재정의(오버라이드)
	@Override
	public String toString() {
		return String.format("[ID: %s, stat: 힘(%d), 민첩(%d), 지능(%d)]"
				, name, strength, dexterity, intelligence);
	}
	
	
}

나이트

package chapter10.interface_part;

//기사
public class Knight extends Novice implements Skill{
	
	//명시적 생성자
	//Knight형 인스턴스 생성시 Novice의 명시적 생성자를 호출하여 각각의 초기값을 가지는 인스턴스 생성
	public Knight(String name, int strength, int dexterity, int intelligence) {
		super(name, strength, dexterity, intelligence);
	}
	
	//상위 클래스 Novice의 attack()메서드를 재정의(오버라이드)
	@Override
	public void attack() {
		System.err.println("강력한 배쉬 공격");
	}

	//interface Skill의 메서드 skill()를 구현한다. (오버라이드) 
	@Override
	public void skill() {
		System.err.println("쇼크 스턴");
	}

}

도적

//도적
public class Thief extends Novice implements Skill{

	//명시적 생성자
	//Thief형 인스턴스 생성시 Novice의 명시적 생성자를 호출하여 각각의 초기값을 가지는 인스턴스 생성
	public Thief(String name, int strength, int dexterity, int intelligence) {
		super(name, strength, dexterity, intelligence);
	}
	
	//상위 클래스 Novice의 attack()메서드를 재정의(오버라이드)
	@Override
	public void attack() {
		System.err.println("치명적인 공격");
	}

	//interface Skill의 메서드 skill()를 구현한다. (오버라이드) 
	@Override
	public void skill() {
		System.err.println("중독");
	}
	
}

마법사

package chapter10.interface_part;

//마법사
public class Magician extends Novice implements Skill{

	//명시적 생성자
	//Magician형 인스턴스 생성시 Novice의 명시적 생성자를 호출하여 각각의 초기값을 가지는 인스턴스 생성
	public Magician(String name, int strength, int dexterity, int intelligence) {
		super(name, strength, dexterity, intelligence);
	}
	
	//상위 클래스 Novice의 attack()메서드를 재정의(오버라이드)
	@Override
	public void attack() {
		System.err.println("매직 애로우 공격");
	}

	//interface Skill의 메서드 skill()를 구현한다. (오버라이드) 
	@Override
	public void skill() {
		System.err.println("메테오");
	}
	
}

스킬

package chapter10.interface_part;

public interface Skill {

	//스킬메서드
	public void skill();
	}