Commit 699cf360 by 최애림

INNODEV-2996 Seminar- 야구 게임 시뮬레이션 돌리기(최종)

*시뮬레이션에 필요한 객체 코드 수정
- Pitcher, Batter
	- match_n 파라미터 삽입
- Base, Game
	- Game클래스로 합치기
	- 아웃 카운트 셀 수 있는 메소드인 out_state 추가
	- 이닝 확인하는 메소드인 inning_state 추가
- TeamSelected
	- 불필요한 Enum 클래스 삭제
* 시뮬레이션 코드(main문) 추가
parent d84cb4d9
import pandas as pd
import pandas as pd
import json
import random
from programming_simulation_v2_5_최종 import *
# 이름
kor_name = pd.read_csv('kor_name.csv', header=None)
kor_name.columns = ['name', 'counts']
# 성
kor_surname_json = open('kor_surname.json', encoding='UTF8')
kor_surname_dict = json.load(kor_surname_json)['surnames']
kor_surname = pd.DataFrame(kor_surname_dict)
# 이름, 성 리스트화
last_name = list(kor_name['name'])
first_name = list(kor_surname['surname'])
def player_maker(n):
player_dict = {
'name': [random.choice(first_name) + random.choice(last_name) for _ in range(n)],
'num': random.sample(range(1, 200), n),
'age': [random.randint(20, 40) for _ in range(n)],
'height': [random.randint(170, 190) for _ in range(n)],
'weight': [random.randint(70, 100) for _ in range(n)],
'position': random.choices(['pitcher', 'batter'], weights=[1, 9], k=n)
}
return player_dict
def player_to_team(team_obj, player_dict, n):
for idx in range(n):
player_inform = [value[idx] for value in player_dict.values()]
if 'pitcher' in player_inform:
player = Pitcher(*player_inform[:-1])
else:
player = Batter(*player_inform[:-1])
team_obj.add_player(player)
print(Team.player_id)
print('\n선수 추가가 완료되었습니다.\n')
# 팀
kiwoom_heros = Team('키움', '서울')
ssg_landers = Team('SSG', '인천')
lg_twins = Team('LG', '서울')
kt_wiz = Team('KT', '수원')
kia_tigers = Team('기아', '광주')
nc_dinos = Team('NC', '창원')
samsung_lions = Team('삼성', '대구')
lotte_giants = Team('롯데', '부산')
doosan_bears = Team('두산', '서울')
hanwha_eagles = Team('한화', '대전')
Team_li = [kiwoom_heros, ssg_landers, lg_twins, kt_wiz, kia_tigers,
nc_dinos, samsung_lions, lotte_giants, doosan_bears, hanwha_eagles]
# 선수 인원
n = 100
for team in Team_li:
# 선수 생성
team_player_li = player_maker(n)
# 선수 추가
player_to_team(team, team_player_li, n)
# 선수 명단 확인
team.check_player()
# 선수 삭제
# hanwha_eagles.remove_player(901)
####### 시즌 시작 #######
season = Season(Team_li)
# 대전 정보 생성
match_iter = season.game_schedule()
####### 시즌 경기 시작 #######
for match_team in match_iter:
# 경기 팀 불러오기
team1, team2 = match_team[0], match_team[1]
print('*' * 10, f'"{team1}" vs "{team2}"', '*' * 60)
# 출전 선수 군단 선택
team1_players = team1.select_player()
team2_players = team2.select_player()
# 경기 시작 회초/말 공격 선택, 경기장 셋
game = Game(team1, team2)
# 선수군 선택
game.choice_players(team1_players, team2_players)
while game.inning_state():
print(f'\n\n{"=" * 10} {game.innings}이닝 {"=" * 80}')
print('-' * 20, match_team[game.team_idx].name, '-' * 70)
# 투수(수비), 타자(공격) 선수 셋팅
pitcher, batters = game.set_player()
for batter in game.batter_generator(batters):
# 3아웃 될 때 까지 경기 진행
output = game.batting_output(pitcher, batter)
if output == PlayerBatting.OUTS:
pitcher.update_records(output)
pitcher.out_avg
# 아웃 횟수 확인하고 3아웃인 경우, 타순 재배치
if game.out_state():
# 마지막 타자 기억 후 타순 재배치
game.save_order(batters.index(batter))
break
else:
game.move_base(batter, output)
# game.now_base()
batter.update_records(output)
batter.batting_avg
get_score = game.result_score(all = False)
print(f'\n\t점수: {get_score}점')
# 점수 정보 저장
game.save_score(get_score)
# 공수 교대
game.shift_attack_defense()
# 경기 결과 저장
result = game.result_score()
season.update_record(result)
# 시즌 전체 경기 순위 확인
season.rank()
import random
import random
from abc import ABC, abstractmethod
from enum import Enum
import numpy as np
from itertools import combinations
import pandas as pd
class PlayerType(Enum):
"""
선수의 타자/투수 여부를 열거형으로 나타냅니다.
"""
PITCHER = 0
BATTER = 1
class PlayerBatting(Enum):
"""
타석에 오른 선수가 취할 수 있는 액션을 열거형으로 나타냅니다.
"""
SINGLES = 1
DOUBLES = 2
TRIPLES = 3
HOMERUNS = 4
OUTS = 5
class PlayerSelected(Enum):
"""
1군, 2군, 랜덤 선수 여부를 열거형으로 나타냅니다.
"""
FIRST_PLAYERS = 0
SECOND_PLAYERS = 1
RANDOM_PLAYER = 2
class Player(ABC):
"""
선수 정보를 관리하는 클래스입니다.
"""
def __init__(self, name, num, age, height, weight, match_n):
"""
객체를 초기화하고 선수들의 정보를 입력하는 메소드입니다.
:param name: 선수 이름
:param num: 등번호
:param age: 나이
:param height: 키
:param weight: 몸무게
:param match_n: 대전 횟수
"""
self.name = name
self.num = num
self.age = age
self.height = height
self.weight = weight
self.match_n = match_n
def __str__(self):
"""
객체를 문자열로 반환합니다.
:return: '선수 이름(선수 등번호)' 형태로 반환
"""
return f'{self.name}({self.num})'
@abstractmethod
def update_records(self, batting_output):
"""
선수의 전적을 업데이트할 추상함수를 구현합니다.
:return: None
"""
pass
class Batter(Player):
"""
Player 클래스를 상속 받아 타자 정보를 관리합니다.
"""
def __init__(self, name, num, age, height, weight, match_n=100, n_singles=25, n_doubles=10, n_triples=5, n_homeruns=5):
"""
객체를 초기화하고 타자 정보를 생성합니다.
:param name: 타자 이름
:param num: 등번호
:param age: 나이
:param height: 키
:param weight: 몸무게
:param match_n: 대전 횟수 (default = 100)
:param n_singles: 1루타 횟수 (default = 25)
:param n_doubles: 2루타 횟수 (default = 10)
:param n_triples: 3루타 횟수 (default = 5)
:param n_homeruns: 홈런 횟수 (default = 5)
"""
super(Batter, self).__init__(name, num, age, height, weight, match_n)
self.position = PlayerType.BATTER
self.n_singles = n_singles
self.n_doubles = n_doubles
self.n_triples = n_triples
self.n_homeruns = n_homeruns
def update_records(self, batting_output):
"""
선수 정보를 업데이트합니다.
:param batting_output: 경기 결과 객체(PlayerBatting Enum class)
:return: None
"""
self.match_n += 1
if batting_output == PlayerBatting.SINGLES:
self.n_singles += 1
elif batting_output == PlayerBatting.DOUBLES:
self.n_doubles += 1
elif batting_output == PlayerBatting.TRIPLES:
self.n_triples += 1
elif batting_output == PlayerBatting.HOMERUNS:
self.n_homeruns += 1
else:
pass
@property
def batting_avg(self):
"""
타자의 안타율을 계산해줍니다.
:return: 안타율
"""
return sum([self.n_singles, self.n_doubles, self.n_triples, self.n_homeruns]) / self.match_n
def __str__(self):
"""
객체를 문자열로 반환합니다.
:return: '선수 이름(등번호, 안타율)' 형태로 반환
"""
return f'{self.name}({self.num}, {self.batting_avg:.3f})'
class Pitcher(Player):
"""
Player를 상속 받아 투수 정보를 관리합니다.
"""
def __init__(self, name, num, age, height, weight, match_n=100, n_outs=30):
"""
객체를 초기화하고 타자 정보를 생성합니다.
:param name: 타자 이름
:param num: 등번호
:param age: 나이
:param height: 키
:param weight: 몸무게
:param match_n: 대전 횟수 (default = 100)
:param n_outs: 아웃 횟수 (default = 30)
"""
super(Pitcher, self).__init__(name, num, age, height, weight, match_n)
self.position = PlayerType.PITCHER
self.n_outs = n_outs
def update_records(self, batting_output):
"""
선수 정보를 업데이트합니다.
:param batting_output: 경기 결과 객체(PlayerBatting Enum class)
:return: None
"""
self.match_n += 1
if batting_output == PlayerBatting.OUTS:
self.n_outs += 1
else:
pass
@property
def out_avg(self):
"""
타자의 아웃율을 계산해줍니다.
:return: 아웃률
"""
return self.n_outs / self.match_n
def __str__(self):
"""
객체를 문자열로 반환합니다.
:return: '선수 이름(등번호, 안타율)' 형태로 반환
"""
return f'{self.name}({self.num}, {self.out_avg:.3f})'
class Team:
"""
구단 정보를 관리하는 클래스입니다.
"""
player_id = 0
def __init__(self, name, location):
"""
객체를 초기화하고 구단 이름, 연고지를 정보를 입력하는 메소드입니다.
:param name: 구단 이름
:param location: 구단 연고지
"""
self.name = name
self.location = location
self.player_pitcher = {}
self.player_batter = {}
def __str__(self):
"""
객체를 문자열로 반환합니다.
:return: '구단명(연고지)' 형태로 반환
"""
return f'{self.name}({self.location})'
def check_player(self):
"""
구단 내 선수 번호와 선수 이름 목록을 반환합니다.
:return: None
"""
pitcher = [f'{id_num}:{player.name}' for id_num, player in self.player_pitcher.items()]
batter = [f'{id_num}:{player.name}' for id_num, player in self.player_batter.items()]
print(f'투수: {pitcher}\n타자: {batter}')
def add_player(self, player):
"""
구단 내 선수 객체 정보를 추가합니다.
:param player: 추가할 선수 객체
:return: None
"""
Team.player_id += 1
if player.position == PlayerType.PITCHER:
self.player_pitcher[Team.player_id] = player
else:
self.player_batter[Team.player_id] = player
print(f'"{player}" 선수가 선수 명단에서 추가 되었습니다.')
def remove_player(self, remove_id):
"""
구단 내 선수 이름을 파라미터로 받아 투수/타자 선수 목록에 해당 이름이 존재하는 경우 삭제합니다.
:param remove_player_id: 삭제할 선수 id
:return: None
"""
if remove_id in self.player_pitcher:
remove_player = self.player_pitcher[remove_id]
del self.player_pitcher[remove_id]
print(f'{remove_player}이/가 선수 명단에서 제거 되었습니다.')
elif remove_id in self.player_batter:
remove_player = self.player_batter[remove_id]
del self.player_batter[remove_id]
print(f'{remove_player}이/가 선수 명단에서 제거 되었습니다.')
else:
print('선수가 존재하지 않습니다.')
def select_player(self):
"""
연습 게임 횟수에 따른 1군, 2군, 랜덤 투수/타자 선수들을 선발하여 출력합니다.
:return: ((1군 투수/타자), (2군 투수/타자), (랜덤 투수/타자))
"""
sorted_pitchers = sorted(self.player_pitcher.values(), key=lambda x: x.out_avg, reverse=True)
selected_pitcher_1 = sorted_pitchers[0]
selected_pitcher_2 = sorted_pitchers[1]
selected_pitcher_random = random.choice(sorted_pitchers)
sorted_batters = sorted(self.player_batter.values(), key=lambda x: x.batting_avg, reverse=True)
selected_batters_1 = sorted_batters[:9]
selected_batters_2 = sorted_batters[9:18]
selected_batters_random = random.sample(sorted_batters, 9)
return ((selected_pitcher_1, selected_batters_1), (selected_pitcher_2, selected_batters_2),
(selected_pitcher_random, selected_batters_random))
class Game:
"""
두 팀의 경기 정보를 관리하는 클래스입니다.
"""
def __init__(self, team1, team2):
"""
객체를 초기화하고 대전할 두 팀에 관한 정보, 이닝 정보, 이닝별 득점 점수 기록 속성, 팀 인덱스를 생성하고 회초의 선공을 정합니다.
:param team1: 대전할 팀1 객체
:param team2: 대전할 팀2 객체
:return: None
"""
self.match_team = [team1, team2]
self.innings = 1
self.half_of_innings = 0
self.n_out = 0
self.base_board = {}
self.score = 0
self.score_board = {team: [] for team in self.match_team}
self.team_idx = self.match_team.index(random.choice(self.match_team))
print(f'\t{self.match_team[self.team_idx]} 선공!')
def choice_players(self, team_1_players, team_2_players):
"""
두 팀에서 선발된 선수군들(1군, 2군, 랜덤군) 중 랜덤하게 하나의 선수군을 뽑습니다.
:param team_1_players: 팀1에서 선발된 선수군(1군, 2군, 랜덤군)
:param team_2_players: 팀2에서 선발된 선수군(1군, 2군, 랜덤군)
:return: None
"""
selected_group = random.choice(
[PlayerSelected.FIRST_PLAYERS, PlayerSelected.SECOND_PLAYERS, PlayerSelected.RANDOM_PLAYER])
self.selected_team1 = team_1_players[selected_group.value]
self.selected_team2 = team_2_players[selected_group.value]
self.selected_pitchers = [self.selected_team1[PlayerType.PITCHER.value],
self.selected_team2[PlayerType.PITCHER.value]]
self.selected_batters = [self.selected_team1[PlayerType.BATTER.value],
self.selected_team2[PlayerType.BATTER.value]]
def set_player(self):
"""
두 팀에서 선발된 선수군에 대해 회초 수비 팀-> 투수, 회초 공격 팀-> 타자를 경기장에 셋팅합니다.
:return: 이번 경기 투수, 타자들
"""
self.temp_pitcher = self.selected_pitchers[(self.team_idx + 1) % 2]
self.temp_batters = self.selected_batters[self.team_idx]
print(f'\t투수: {self.temp_pitcher.name}')
print(f'\t타자: {[batter.name for batter in self.temp_batters]}')
return self.temp_pitcher, self.temp_batters
def out_state(self):
"""
아웃 카운트를 세고, 3-아웃 여부를 반환합니다.
:return: boolean
"""
self.n_out += 1
if self.n_out == 3:
return True
else:
return False
def inning_state(self):
"""
이닝 수를 확인하고 9이닝 이하 여부를 반환합니다
:return: boolean
"""
if self.innings > 9:
return False
else:
return True
def shift_attack_defense(self):
"""
두 팀의 공격/수비를 변환하고, 게임 정보들(팀 인덱스, 베이스 상황, 아웃 횟수, 점수, 회초/말 정보, 이닝 정보 등)을 초기화합니다.
:return: None
"""
print('공수 교대')
self.team_idx = (self.team_idx + 1) % 2
self.base_board = {}
self.n_out = 0
self.score = 0
self.half_of_innings += 1
if self.half_of_innings == 2:
self.half_of_innings %= 2
self.innings += 1
def batting_output(self, temp_pitcher, temp_batter):
"""
타자, 투수 각각 하나의 객체를 받아 선수 전적을 가중치로 두어 랜덤으로 결과(안타(1/2/3/홈런타), 아웃)를 산출합니다.
:param temp_pitcher: 공격팀 타자 객체
:param temp_batter: 수비팀 투수 객체
:return: 타자의 배팅 결과
"""
win_player = random.choices([PlayerType.PITCHER, PlayerType.BATTER],
weights=[temp_pitcher.out_avg, temp_batter.batting_avg], k=1)[0]
if win_player == PlayerType.BATTER:
batting_li = [PlayerBatting.SINGLES, PlayerBatting.DOUBLES, PlayerBatting.TRIPLES, PlayerBatting.HOMERUNS]
batting_n = [temp_batter.n_singles, temp_batter.n_doubles, temp_batter.n_triples,
temp_batter.n_homeruns]
output = random.choices(batting_li, weights=batting_n, k=1)[0]
if output.value < 4:
print(f'\t\t"{temp_batter.name}" {output.value}루타!')
else:
print(f'\t\t"{temp_batter.name}" 홈런!!!!')
else:
output = PlayerBatting.OUTS
print(f'\t\t"{temp_pitcher.name}" 수비 성공! "{temp_batter.name}" 아웃!')
return output
def save_order(self, batter_idx):
"""
타자들의 타순 인덱스를 받아와 현재 이닝의 타순을 기억하고, 다음 이닝의 타순을 바꿔줍니다.
:param batter_idx: 타자들의 타순 인덱스
:return: None
"""
batter_li = self.selected_batters[self.team_idx]
self.selected_batters[self.team_idx] = batter_li[batter_idx + 1:] + batter_li[:batter_idx + 1]
def save_score(self, score):
"""
이닝 내 회초/회말에 해당 팀의 득점 수를 기록합니다.
:param score: 팀의 득점 수
:return: None
"""
self.score_board[self.match_team[self.team_idx]].append(score)
print('\n\t득정 정보가 저장 되었습니다.\n')
def result_score(self, all = True):
"""
회초/회말의 최종 최종 득점 수 또는 9이닝 이후 팀당 득점 결과를 합산하여 최종 득점 수를 반환합니다.
:return: 최종 득점 수
"""
if all:
self.result = {team: sum(scores) for team, scores in self.score_board.items()}
print('-' * 100)
print('최종 스코어: \n\t', [f'{team.name}: {sum_score}' for team, sum_score in self.result.items()])
print('-' * 100)
return self.result
else:
return self.score
@staticmethod
def batter_generator(batters):
"""
타자 리스트를 받아서 제너레이터로 반환합니다.
"""
idx = 0
for _ in range(50):
yield batters[idx % 9]
idx += 1
def move_base(self, batter, output):
"""
타자 객체와 타자 배팅 결과 객체를 인자로 받아 타자의 베이스 위치 정보를 업데이트(이동)합니다.
:param batter: 타자
:param output: 타자의 배팅 결과
:return: None
"""
for player, base_n in list(self.base_board.items()):
point, moved = divmod(base_n + output.value, 4)
if point == 0:
self.base_board[player] = moved
else:
del (self.base_board[player])
self.score += point
self.base_board[batter] = output.value
def now_base(self):
"""
현재 베이스 내 타자 위치 정보를 보여줍니다.
:return: None
"""
base_board_visual = np.full((3, 3), '-')
base_board_visual[[0, 0, 2, 2], [0, 2, 2, 0]] = '○'
for value in self.base_board.values():
if value == 1:
base_board_visual[2, 2] = '●'
elif value == 2:
base_board_visual[0, 2] = '●'
elif value == 3:
base_board_visual[0, 0] = '●'
print(base_board_visual, '\n')
class Season:
"""
시즌 정보를 관리하는 클래스입니다.
"""
def __init__(self, team_list):
"""
객체를 초기화하고 시즌 경기 참전 팀 리스트와 팀들의 경기 결과를 기록을 생성합니다.
:param: 시즌 경기 참전 팀 리스트
"""
self.team_list = team_list
self.record_dict = {team: {'승': 0, '패': 0, '무승부': 0} for team in self.team_list}
def game_schedule(self):
"""
참전 팀들의 경기 일정 정보를 생성합니다.
각 팀들은 나머지 9개 팀들에 대하여 16번씩 총 144번씩 경기를 치루게 됩니다.
:return: 대전 정보 객체
"""
print('대전 정보를 생성합니다.')
self.one_game = list(combinations(self.team_list, 2))
self.all_game = iter(game for _ in range(16) for game in self.one_game)
return self.all_game
def update_record(self, game_result):
"""
한 경기 내 팀별 최종 득점 수 결과를 받아 최종 승/패/무승부 여부를 가리고 팀별 시즌 정보를 업데이트 합니다.
"""
(team1, team1_score), (team2, team2_score) = game_result.items()
if team1_score > team2_score:
self.record_dict[team1]['승'] += 1
self.record_dict[team2]['패'] += 1
elif team1_score < team2_score:
self.record_dict[team1]['패'] += 1
self.record_dict[team2]['승'] += 1
else:
self.record_dict[team1]['무승부'] += 1
self.record_dict[team2]['무승부'] += 1
def rank(self):
"""
시즌 내 순위대로 팀별 전적을 반환합니다.
return: 팀별 전적
"""
self.sorted_record_dict = dict(
sorted(self.record_dict.items(), key=lambda x: (x[1]['승'], x[1]['무승부'], x[1]['패']), reverse=True))
self.rank_df = pd.DataFrame(self.sorted_record_dict.values(), index=self.sorted_record_dict.keys())
print('*' * 10, '최종 시즌 결과', '*' * 80)
print(self.rank_df)
return self.rank_df
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment