파이썬 실습/게임 만들기

파이썬 GUI 한글 타자 게임 만들기

파기차차 2022. 11. 6. 12:04
728x90
반응형
SMALL
728x90
ㅁ 개요

 

O 프로그램 소개
 

 - 이번 프로그램은 이전글(2022.08.09 파이썬 타자 게임 만들기) 파이썬 타자 게임 만들기의 GUI 버전입니다.

(본 블로그의 내용은 유튜브 동영상(파이썬 GUI/윈도우 한글 타자 게임 만들기(making GUI lotto Game by python))에서 더욱 자세히 보실 수 있습니다.)

 

게임의 규칙은 한글로 문제가 주어지고, 이를 정확히 따라 입력한 후 엔터키를 누르면 한문장이 완료되며, 결과로 속도,정확도,오타율을 보여줍니다. 모든 문장 완료시 재시작/종료 여부를 묻고 다시 시작 또는 종료되는 타자게임입니다.

(자세한 설명은 아래 그림 참고)

 

**본 프로그램은 아래 사이트를 참고하여 작성하였으며, 세부내용은 아래 사이트를 참고하여 주시기 바랍니다.
[출처] 파이썬#51 - 한글 타자 연습게임, 유니코드 한글 초성,중성,종성 분해|작성자 남박사

https://blog.naver.com/PostView.naver?blogId=nkj2001&logNo=222693799817&redirect=Dlog&widgetTypeCall=true&directAccess=false

[출처] 파이썬#62 - 파이썬GUI, PyQt5로 한글타자연습 게임 만들기|작성자 남박사

https://blog.naver.com/PostView.naver?blogId=nkj2001&logNo=222705785591&categoryNo=95&isAfterWrite=true&isMrblogPost=false&isHappyBeanLeverage=true&Redirect=View

 

 

O 완성된 프로그램 실행 화면
 
 - 최종 완성된 프로그램의 결과화면은 아래와 같습니다.

(1) 다음문제가 흐린 회식 글자로 주어집니다.

(2) 현재문제가 파란 글자로 주어집니다.

(3) input박스에 현재 문제의 한글을 입력합니다.

(4) 레이블에 결과(속도,정확도,오타율)을 보여줍니다.

 


 

ㅁ 세부 내용
 
O 완성된 소스

 

from PyQt5 import QtWidgets
from PyQt5 import QtCore
import random
import time



CHOSUNG = [
    'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 
    'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
]
JUNGSUNG = [
    'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 
    'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ'
]
JONGSUNG = [
    '', 'ㄱ','ㄲ','ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 
    'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 
    'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
]


WL = [
    "안녕하세요.",
    "감사합니다."
]

class MyWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        ######################################################
        # 2. 윈도우 폼 구성하기
        ######################################################
        self.setWindowTitle("파이썬 GUI 타이핑게임")

        self.lb_next = QtWidgets.QLabel("다음 문제 입니다.", self)
        self.lb_next.setAlignment(QtCore.Qt.AlignLeft)
        self.lb_next.setStyleSheet("font-family:맑은 고딕;color:gray; font-size:16px;")

        self.lb_current = QtWidgets.QLabel("현재 문제 입니다.", self)
        self.lb_current.setAlignment(QtCore.Qt.AlignLeft)
        self.lb_current.setStyleSheet("font-family:맑은 고딕;color:blue; font-size:20px;")

        self.lb_info = QtWidgets.QLabel("속도: 0000.0<br>정확도: 00%<br>오타율: 00%", self)
        self.lb_info.setAlignment(QtCore.Qt.AlignCenter)
        self.lb_info.setStyleSheet("font-family:맑은 고딕;color:red; font-size:20px;")

        self.input = QtWidgets.QLineEdit(self)
        self.input.setFixedHeight(30)
        self.input.setStyleSheet("font-family:맑은 고딕;color:black; font-size:20px;")

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.lb_next)
        layout.addWidget(self.lb_current)
        layout.addWidget(self.lb_info)
        layout.addWidget(self.input)
        self.resize(650, 200)
        self.setLayout(layout) #<---------- 위의 레이아웃설정을 반영
        self.start_time = 0
        self.current_index = 0

        ######################################################
        # 3. enter_key 함수 호출 및 정의
        ######################################################
        self.input.returnPressed.connect(self.enter_key)


        ######################################################
        # 7. 최초 게임 시작 시 동일한 문제가 출제되지 않고 시작시 마다
        # 다르게 출제하게 하기 위해서 초기화(섞어주고)하고, 
        ######################################################
        self.init_game()
        self.next_sentence()
       

        self.show()

    ######################################################
    # 입력한 값(한글)을 초성, 중성, 종성으로 분리 후 정확도 비교
    ######################################################
    def break_korean(self, string):
        break_words = []
        for k in string:
            print(k,"+++")
            if ord("가") <= ord(k) <= ord("힣"):
                print(ord("가"),"munnt")
                print(ord(k))
                index = ord(k) - ord("가")
                print(index)
                c_cho = int((index / 28 ) / 21)
                print(c_cho)
                c_jung = int((index / 28) % 21)
                print(c_jung)
                c_jong = int(index % 28)
                print(c_jung)

                break_words.append(CHOSUNG[c_cho])
                print(break_words)
                break_words.append(JUNGSUNG[c_jung])
                print(break_words)
                if c_jong > 0:
                    break_words.append(JONGSUNG[c_jong])
            else:
                print("가 보다 크지 않습니다.")
                break_words.append(k)
        return break_words

    ######################################################
    # 3. enter_key 함수 호출 및 정의
    ######################################################
    def enter_key(self):

        duration = time.time() - self.start_time
        print(duration)
        current_question = self.lb_current.text()

        user_input = self.input.text()

        break_current_question = self.break_korean(current_question)
        print(break_current_question)
        break_user_input = self.break_korean(user_input)
        print(break_user_input,"++++++++++++++++++++++++")
        correct = 0
        for c, a in zip(break_current_question, break_user_input):
            print(c, "=============", a)

            if c == a:
                correct = correct + 1
            else:
                correct

        print(current_question)
        print(user_input)
        print(correct)

        ######################################################
        # 4. 타자 결과(속도, 정확도, 오타율)를 보여주기 위한 로직
        ######################################################
        src_len = len(break_current_question)
        print(src_len)
        accuracy = correct / src_len * 100
        errorRate = (src_len - correct) / src_len * 100
        print(correct)
        print(duration)
        speed = float(correct / duration) * 60

        output = f"속도:{speed:.1f}<br>정확도: {accuracy:.2f}%<br>오타율: {errorRate:.2f}%"
        self.lb_info.setText(output)
        self.input.setText("")

        ######################################################
        # 5. 다음 문제 출제 함수 호출 및 실행
        ######################################################
        self.next_sentence()

    ######################################################
    # 6. 게임 초기화 함수 호출 및 실행
    ######################################################
    def init_game(self):
        random.shuffle(WL)

        print(WL)
        self.current_index = 0


    ######################################################
    # 5. 다음 문제 출제 함수 호출 및 실행
    ######################################################
    def next_sentence(self):
        global next_question
        if self.current_index < len(WL):
            question = WL[self.current_index]
            if self.current_index + 1 < len(WL):
                next_question = WL[self.current_index + 1]
            else:
                pass

            self.lb_current.setText(question)
            self.lb_next.setText(next_question)
            self.start_time = time.time()
            self.current_index = self.current_index + 1
        else:
            reply = QtWidgets.QMessageBox.question(self, '파이썬GUI타이핑게임', '모든 문제를 출제했습니다.\n다시 하시겠습니까?',
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes)

            if reply == QtWidgets.QMessageBox.Yes:
                ######################################################
                # 6. 게임 초기화 함수 호출 및 실행
                ######################################################
                self.init_game()
                self.next_sentence()
                self.input.setFocus()
            else:
                exit(0)


######################################################
# 1. 윈도우 창 띄우기
######################################################
if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    win = MyWindow()
    app.exec_()

 

O 소스 다운로드
 - 소스파일 다운로드 후 cmd 또는 파워쉘 등에서 아래와 같이 실행하시기 바랍니다.
 > python koreanTyping.py

koreanTyping.py
0.01MB

 


 
 
O 핵심 내용

 

1.layout = QtWidgets.QVBoxLayout()<--layout객체 생성

2.layout.addWidget(self.lb_next)<--위에서 생성한 라벨객체를 레이아웃객체에 add

3.self.setLayout(layout) #<---------- 위의 레이아웃설정을 반영

4.self.input.returnPressed.connect(self.enter_key)<-- input박스에서 엔터키 입력시 enter_key()함수 호출

5.self.init_game()<--초기화 함수(super().__init__() )에서 init_game()를 호출하여 최초 게임 시작 시 동일한 문제가 출제되지 않도록 방지

6.self.next_sentence()<--위와 동일

7.index = ord(k) - ord("")<-- ord()함수는 문자의 유니코드 값을 돌려주는 함수로 입력한 값을 초성,중성,종성으로 분리하기 위해 사용

8.break_words.append(CHOSUNG[c_cho])<-- break_words리스트에 초성을 append

9.return break_words<-- break_korean()함수의 리턴값은 입력한 사용자가 타이핑한 한글을 초,,종성으로 쪼갠 후 리스트에 담아서 리턴

10.for c, a in zip(break_current_question, break_user_input):<--두개 리스트의 데이터를 비교하기위해 zip()함수 사용

11.

if c == a:<--주어진 문제와 내가 입력한 한글(//종성으로 쪼개진 한글)을 비교 후 같으면 if문 실행

  correct = correct + 1<-- correct변수를 1 증가(, 주어진 문제와 내가 입력한 한글이 동일한지 확인)

12.accuracy = correct / src_len * 100<-- 정확도는 (입력한 올바른 글자수/전체 글자수 X 100) 으로 산정함

13.errorRate = (src_len - correct) / src_len * 100<--오타율은 정확도와 반대로 (입력한 오타 글자수/전체 글자수 X 100) 으로 산정함

14.speed = float(correct / duration) * 60<--속도는 한 문장 완료 후 (올바르게 입력한 글자수/한 문장을 완료한 시간() X 60)으로 계산함

예를 들어 게임시작 후 60초 동안 60타를 쳤다면 타자속도는 분당 60타가 됩니다

15.random.shuffle(WL)<--워드리스트(문제목록)를 썩어 줍니다.

16.

if self.current_index < len(WL):<-- current_indexWL의 길이보다 작으면(, 마지막 문제가 아니면) if문 실행

  question = WL[self.current_index]<--

  if self.current_index + 1 < len(WL):<--다음 문제가 마지막 문제가 아니면

    next_question = WL[self.current_index + 1]<-- next_question변수에 다음문제를 할당

else:

  reply = QtWidgets.QMessageBox.question(self, '파이썬GUI타이핑게임', '모든 문제를 출제했습니다.\n다시 하시겠습니까?',

  QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes)<--

17.

if reply == QtWidgets.QMessageBox.Yes:<-- ‘다시시작안내창에서 ‘Yes’를 누르면

######################################################

# 6. 게임 초기화 함수 호출 및 실행

######################################################

self.init_game()<--게임을 초기화(문제를 섞고)

self.next_sentence()<--다음문제와 현재문제가 주어지고,

self.input.setFocus()<--input창에 커서를 둡니다.

 

 

 


 

O 기본 내용

 

 

1.윈도우 창 띄우는 코드로 모듈 임포트 및 클래스를 선언하고, app객체를 만들고, exec_()함수로 윈도우창을 지속적으로 띄웁니다.

 

 

 

 

 

2. 윈도우 폼을 구성합니다. 타자를 위한 다음문제(self.lb_next), 현재 문제(self.lb_current), 타자결과(속도, 정확도, 오타율) 그리고 입력폼(self.input)으로 구성되며, 레이아웃 객체 생성 후 여기 레이블과 인풋객체를 add(addWidget)합니다. 그리고 마지막으로 setLayout()함수로 위에서 만든 레이아웃설정을 반영합니다.

 

 

 

 

3.인풋박스에 사용자가 현재문제 입력 후 엔터를 치면 enter_key()함수가 실행(호출)됩니다.

여기서는 타자결과(타자속도, 정확도, 오타율)을 보여주기 위한 로직이 포함되며, zip()함수를 이용하여 리스트를 병렬처리해주고 있습니다.

사용자가 입력한 값과, 주어진 한글문제를 for루프를 돌면서 한자씩 비교하면서, 맞으면 correct1씩 증가 시켜 주고 있습니다.

 

 

 

 

 

4.break_current_question = self.break_korean(current_question) 이 구문에서 break_korean()함수를 호출하는데, 이 함수는 입력한 값(한글)을 초성,중성,종성으로 쪼개는 역할을 수행합니다.

아래에서는 최종적으로 사용자가 입력한 값인 [‘’,’’,’’] 를 리턴합니다.

 

 

 

5.다음은 타자 결과(속도, 정확도, 오타율)를 계산하기 위한 로직입니다.

타자속도는 올바르게 타이핑한 한글 개수를 현재시간에서 시작시간을 뺀 시간으로 나눈 값에 60()을 곱하여 계산합니다. 예를 들어 게임시작 후 60초 동안 60타를 쳤다면 타자속도는 분당 60타가 됩니다

그리고 주어진 현재문제와 내가 입력한 값의 올바른 글자수를 확인하여 정확도와 오타율을 계산합니다. 아래에서는 현재 문제 입니다.’ 을 비교하고, 총 글자수 20['', '', '', '', '', ' ', '', '', '', '', '', ' ', '', '', '', '', '', '', '', '.'] 와 내가 입력한 글자수 3(‘’,’’,’’)와 비교 시 3개만 맞으므로 정확도는 3/20 = 15%가 됩니다. 오타율을 그 반대가 되겠습니다.

 

 

 

 

6.다음은 현재문제를 풀고 엔터를 눌렀을 때 다음문제를 보여주거나, 현재문제가 마지막 문제일 경우 다시 시작할지를 판단하는 로직입니다.

if self.current_index < len(WL): ß 현재 인덱스가 워드리스트의 길이보다 작으면 즉, 뒤의 문제가 남아 있으면 현재문제와 다음문제를 보여주고 계속 진행하며, 그렇지 않으면(, 마지막이면) ‘다시 시작여부를 묻는 안내창을 띄워 줍니다.

 

 

 

 

7.마지막 문제를 풀고 다시 시작하는 경우 init_game()함수를 호출하여 문제를 섞고, current_index0으로 초기화 합니다.

 

 

 

 

 

8.그리고, init함수 아래에 self.init_game() self.next_sentence()를 호출하여 최초 게임 실행 시 동일한 문제가 출제되지 않도록 해줍니다.

 

 

 

 

 

 


 

ㅁ 정리
 
O 우리가 배운 내용
 
 - 오늘은 파이썬으로 한글 타자 게임을 GUI환경에서 만들어 보았습니다.
 - 오늘 우리가 배운 내용을 간단히 정리해 보면 다음과 같습니다.
 > 1. GUI/윈도우 창 띄우기
 > 2. 윈도우 폼 구성하기
 > 3. 엔터키 함수 호출 및 정의(엔터 입력시 대응 로직)
 > 4. 타자결과(속도, 정확도, 오타율)를 보여주기 위한 로직
 > 5. 다음 문제 출제 함수 호출 및 실행
 > 6. 게임 초기화 함수 호출 및 실행
 > 7. 초기화 함수 내에서 init_game() 및 next_sentence() 함수 호출(최초 게임 시작 시 동일한 문제가 출제되지 않고, 시작 시 마다 다르게 출제되게 하기 위해서 초기화해줌)
 

 

위의 내용이 유익하셨다면, 좋아요와 구독 부탁드립니다.

 

 

감사합니다.

 

728x90
반응형
LIST