티스토리 뷰

반응형

1. 계획

끝말잇기를 파이썬을 활용해 프로그램으로 구현한다. 규칙은 다음과 같다.

 
1. 기본적으로 단어는 대한민국 표준국어대사전에 등재된 단어들 중 2글자 이상의 동사, 형용사, 조사를 제외한 명사, 대명사, 부사(의성어, 의태어 포함), 관형사, 감탄사(: 아이고, 에그머니나), 수사이며, 추가로 접미사(: 는답니까, 는다오)를 사용한다.
 
2. 참여자는 2명으로, 컴퓨터와 플레이어이다. 먼저 컴퓨터가 단어를 제시하고, 플레이어는 컴퓨터가 처음으로 제시한 단어의 마지막 글자로 시작하는 단어를 입력한다. 이후 컴퓨터가 플레이어가 제시한 단어의 마지막 글자로 시작하는 단어를 찾아 제시하고, 이 과정을 플레이어와 컴퓨터가 반복한다.
 
2-1. 만약 마지막 글자에 두음법칙이 적용 가능하다면, 그 다음 차례에 그 글자에 두음법칙을 적용한 글자가 첫 글자로 오는 단어를 사용할 수 있다. 이 내용은 플레이어와 컴퓨터 모두에게 적용된다.
 
2-1-1. 두음법칙은 우리말 첫머리에 발음하기 어려운 '' 또는 ''이 오는 것을 꺼리는 현상이다. 주로 한자어에서 나타난다. 따라서 한자어에 존재하지 않는 글자 중 첫소리에 '' 또는 ''이 오는 단어에는 두음법칙이 적용되지 않는다.
 
3. 플레이어 또는 컴퓨터가 제시한 단어의 마지막 글자로 시작하는 단어가 사전에 존재하지 않을 경우, 반복을 종료하며, 마지막으로 단어를 제시한 참여자가 승리한다.
 
4. 플레이어가 입력한 단어가 사전에 등재되어 있지 않은 단어라면, 게임이 종료되고, 컴퓨터가 승리한다.
 
5. 플레이어가 입력한 단어가 이미 사용된 단어라면, 게임이 종료되고 컴퓨터가 승리한다.
 

위 규칙에 따라 작동하는 끝말잇기 프로그램을 제작한다. 한국어 단어 목록 중 규칙에 맞는 단어들만 얻기 위해 표준국어대사전 웹사이트에서 제공하는 한국어 단어 JSON 파일을 적절히 파싱해서 사용하고, 두음법칙을 적용하기 위해서는 한글의 유니코드 값을 이용한다.

위 내용을 토대로 표준국어대사전 JSON 파일을 파싱하는 코드, 해당 단어의 끝 글자로 시작하는 단어가 존재하지 않는 '한방 단어', 두음법칙이 적용되는 글자, 전체 단어 목록 등 특수한 목록을 생성하는 코드, 끝말잇기가 작동하는 코드를 작성할 것이다.

2. 배경이론

 

1. JSON

JSONJavaScript Object Notation의 약자로, 자바스크립트 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반 파일 형식이다. XML과 같이 애플리케이션에서 데이터를 주고받을 때 자주 사용되는 파일 형식이다. 표준국어대사전에서 사전 내려받기로 제공하는 파일 형식은 xml, JSON, 그리고 엑셀이 있는데, 나는 가독성과 코드에서의 사용의 용이성을 위해 JSON을 선택하였다. JSON 내에서는 기본 데이터형인 문자열, 숫자, 불리언, 배열, 그리고 다른 객체를 포함할 수 있다. 예를 들어 아래처럼 

:값 쌍 형태로 각 데이터가 존재하며, 하나의 객체 내에 다른 객체가 포함되는 형태도 가능하며, 배열, 숫자, 그리고 불리언(true, false) 형태의 데이터도 가능하다. 위 예시에서 살펴볼 때, 하위 내용에 접근하기 위해서는 이름과 배열의 인덱스를 이용한다. 예를 들어 해당 객체 내 "friends" 내의 첫 번째 객체의 "gender"에 접근하고 싶으면 이 JSON파일을 k라고 하였을 때 k["friends"][0]["gender"]라고 하면 된다. 이 프로젝트를 통해 파싱할 JSON 파일은 이것보다 훨씬 거대하기에 정확한 키를 입력하는 데 집중해야 한다. 이 프로젝트에서는 파이썬을 이용할 것이기 때문에 파이썬에서 활용할 수 있는 모듈 중 하나인 json 모듈을 사용한다.

 

2. 한글 유니코드

한글 유니코드는 끝말잇기의 핵심 규칙 중 하나인 두음법칙을 적용하기 위해 필수적이다. 한글 유니코드 표를 보면, 한글의 각 유니코드 값은 ""부터 ""까지 사전순으로 배열되어 있다. 사전순이라 함은 먼저 하나의 초성-중성에 대해 종성으로 올 수 있는 모든 경우, 즉 ㅇ(없음),,,,,,,,,,,,,,,,,,,,,(),,,,,,ㅎ이 모두 온 후 다음 중성, ,,,,,,,,,,,,,,,,,,,,ㅣ이 차례로 온 후, 첫소리가 가능한 모든 각 자음, ,,,,,,,,,,,,,,,,,,ㅎ이 오는 형태이다. 따라서 종성이 28, 중성이 21개이므로 이 구조를 28초가 지나면 1분이 올라가고, 21분이 지나가면 1시간이 지나가는 시계의 형태로 생각할 수 있다. 따라서 각 자음 사이에 존재하는 유니코드의 수가 28*21588만큼의 차이가 존재한다. 이를 이용해 두음법칙이 적용되는 글자에 한해 자유롭게 글자를 변환할 수 있다. 예를 들어 ""에서 ""으로 변환한다면 그 사이 자음이 6개 차이이므로 ""의 유니코드 값에 588*6을 더한 값에 해당하는 글자를 반환하면 된다.

3. 알고리즘 설계

프로그램이 작동하기 위한 단어의 목록이 모두 준비되어 있다고 가정할 때 프로그램 동작의 순서도는 아래와 같다.

4. 관련 이론 학습

. JSON

1) 정의

JSON은 파싱 또는 직렬화 없이도 사용할 수 있는 텍스트 기반의 데이터 표현 방식이다. JSON은 그 특성상 상대적으로 쉽게 작성할 수 있고, 소프트웨어에서 파싱하거나 생성하기도 쉽다. 종종 구조화된 데이터를 직렬화해 네트워크에서 교환할 때, 특히 서버와 웹 애플리케이션 간에서 자주 사용된다. JSON에는 데이터 유형이 들어갈 수 있고, 그 예로 배열, 불린, 숫자, 객체, 문자열, 널이 있다. JSON은 임시 데이터의 저장에 적합하고, 시스템 간 데이터 전송에 자주 사용되며, 복잡한 데이터 모델을 간소화시킬 수 있다는 장점이 있다.

2) 사용

JSONAPI 코드 및 웹 서비스에서 더욱 두각을 드러낸다. 이는 텍스트 기반의 JSON이 빠른 데이터 처리 능력을 보여주기 때문이다. JSON은 앞서 서술했듯 텍스트 기반의 언어로 추가적인 코드 작업 없이도 손쉽게 파싱이 가능하고, 많은 데이터를 다뤄야 하는 웹 서비스의 경우 JSON이 이상적인 선택이다. 이렇듯 JSON은 본래는 웹에서 작동하는 기능을 만들 때 자주 사용한다.

3) 프로젝트에서의 활용

파이썬에서는 JSON 파일을 json 모듈을 불러와 쉽게 처리할 수 있다. 파이썬에는 JSON과 비슷한 형태의 자료형인 딕셔너리가 있다. 딕셔너리는 JSON처럼 키와 값 쌍으로 이루어져 있고, 따라서 JSON 데이터를 딕셔너리로 불러와 사용하는 것이 가능하다. json.load() 함수는 json 데이터를 불러와 딕셔너리 형태로 변환한다. 이후 내부 데이터를 딕셔너리와 동일한 방식으로 다루면 된다.

나. 딕셔너리

파이썬에 존재하는 JSON과 같은 형태의 자료형이다. 딕셔너리는 해시 구조로 이루어져 있다.

 

1) 해시 테이블

해시 테이블은 딕셔너리와 같이 키-값 쌍으로 이루어진 데이터를 저장하는 데 사용되는 자료 구조이다. 키를 입력한다면 키는 해시함수를 통해 해시 코드로 변환되고, 이를 인덱스로 사용해 버킷(Bucket)에는 값이 저장된다. 반대로 필요한 값을 찾을 때에는 입력받은 키를 변환, 인덱스를 계산해 값을 찾아 반환한다. 이때 인덱스를 사용하기 때문에 시간 복잡도는 O(1)로 일정하다.

 

2) 해시 함수

앞서 서술했듯 해시함수는 키를 해시 코드로 변환하는 함수이다. 해시 코드는 인덱스가 된다. 이때 해시 함수의 성능에 따라 해시 충돌이 발생할 수 있다. 버킷의 길이 중 인덱스의 중복이 일어나지 않기 위해 적재율이라는 개념이 있다. 적재율은 버킷의 길이에 대한 사용된 인덱스의 수의 비율이다. 일반적으로 적재율이 0.7을 넘어가면 적재율이 높다고 판단되며 충돌이 일어나기 쉬워진다. (적재율은 로드 팩터라고도 부른다. )

 

3) 해시 충돌

해시 충돌은 해시 함수에 의해 같은 값이 반환되게 되면 같은 인덱스를 사용하게 되는데, 이때 해시 충돌이 발생한다. 해시 충돌이 발생하면 값이 덮어씌워진다던가 하여 기존의 값을 찾을 수 없다는 문제가 발생한다. 이를 해결하기 위해서는 몇 가지 알고리즘을 활용할 수 있다.

 

) 더블 해싱

더블 해싱은 2개의 해시 함수를 이용한다. 하나의 해시 함수를 활용하여 해시 충돌이 발생한 경우 2번째 해시 함수를 이용하여 새로운 키 값을 반환한다. 이때 두 함수의 키의 합이 임의의 소수가 되게 하면 충돌의 가능성을 현저히 줄일 수 있다. 적재율이 낮은 상황에서는 유용하게 활용할 수 있지만, 적재율이 높을 때는 효과가 떨어진다는 단점이 있다.

) 선형 탐사법

선형 탐사법은 해시 충돌이 발생했을 때 다음 빈 곳에 저장하는 것이다. 방법은 단순하지만 빈 곳을 빠르게 소모하므로 빈 곳이 뒤로 미뤄지는 현상이 발생할 수 있으며, 이를 군집화 현상이라고 한다.

) 이차 탐사법

비슷한 방법으로 이차 탐사법이 있다. 이차 탐사법은 선형 탐사법과 달리 인덱스를 제곱한다. 군집화 현상은 선형 탐사법에 비해 적게 일어난다.

) 체이닝

이 방법은 버킷 뒤에 연결 리스트를 삽입하는 방법이다. 이 방법에서는 점유된 슬롯 뒤에 억지로 리스트 형태로 값을 추가한다. 체이닝은 해시 충돌이 발생해도 빠른 시간 안에 해결할 수 있고 메모리 낭비가 없다는 장점이 있지만, 군집화 현상이 심하게 일어날 수 있다는 단점이 있다.

 

4) 딕셔너리에서의 구조

파이썬의 딕셔너리에서는 더블 해싱을 이용하며, MurmurHash라는 해시 함수를 사용한다. 해당 해시 함수에서는 적재율이 0.66을 초과하면 resizing으로 해시 테이블의 크기를 2배로 늘린다.

5. 데이터 처리 계획

표준국어대사전에서는 아래와 같은 형태로 JSON을 제공한다.

표준국어대사전에서 제공하는 JSON 데이터는 위와 같은 형태의 단어 데이터가 총 434361개 존재하며 5000단어씩 총 87개의 파일로 나뉘어 있다. 각 파일의 이름은 1200922_5000.json, 1200922_10000.json, 1200922_15000.json과 같은 형식으로 1200922_434361.json까지 되어 있다. 위 내용을 활용하여 반복문을 통해 모든 단어의 목록을 txt 파일 형태로 저장할 수 있을 것이다. 이때 단어는 동사, 형용사, 조사, 구를 제외한 나머지가 되어야 하고, 단어의 품사에 대한 정보는 ["pos_info"] 내의 ["pos"]에 존재한다. JSON 파일을 쭉 살펴보면 가감^소거법, 가감승-합제, 또 다의어의 경우에는 가01,02,03과같은 형태로 word에 존재한다. 이 내용이 그대로 들어가는 것을 막기 위해 해당 문자가 문자열 내에 존재하면 제거하고 저장해야 한다. 또 단어 사이에 공백 문자가 포함되어 있는 경우 공백 문자를 제거해야 한다.

6. 단어 데이터 생성

표준국어대사전에서 다운로드한 JSON 파일을 파싱해 txt 파일로 저장하고, 이를 바탕으로 단어의 목록을 생성하였다. 먼저 위 방법으로 단어의 목록을 생성하고, 두음법칙이 적용되는 마지막 글자의 목록, 두음법칙을 적용시키기 위한 한자어의 목록을 생성하였다.

1) 단어 목록

import json
 
f = open("wordlist.txt", 'w',encoding="UTF-8")
 
def write(data, i): # 메모장에 데이터 작성
wordlist = []
for i in range(i):
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "동사":
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "형용사":
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "":
global json_string
json_string = data["channel"]["item"][i]["word_info"]["word"]
else:
pass
if "-" in json_string: #문자열 내의 특정 문자 제거
json_string = json_string.replace('-', '')
else:
pass
if "^" in json_string:
json_string = json_string.replace('^', '')
else:
pass
if " " in json_string:
json_string = json_string.replace(' ', '')
else:
pass
 
for k in range(11): # 문자열 안에 숫자가 포함되어있으면 숫자 제거
if str(k) in json_string:
json_string = json_string.replace(str(k), '')

if json_string in wordlist:
pass
else:
if len(json_string) >= 2:
wordlist.append(json_string)
f.write(json_string + "\n")
print(json_string)
 
for i in range(1, 87):
with open('파일이름{0}.json'.format(str(i*5000)), 'rt', encoding="UTF-8") as json_file:
json_data = json.load(json_file)
write(json_data, 5000)
 
with open('파일이름{0}.json', 'rt', encoding="UTF-8") as json_file:
json_data = json.load(json_file)
 
write(json_data, 4340)
 
f.close()

단어를 추가하기 전 끝말잇기에 사용할 수 없는 단어를 제거하고, 단어 내 띄어쓰기나 불필요한 문자를 제거하고, 단어가 겹치지 않게끔 파일 내에 이미 적혀 있지 않은 단어만 추가한다. 이 과정을 모든 파일에 대해 반복하고, 마지막 파일의 경우 단어가 총 4340개이기 때문에 반복문 밖에서 실행한다.

ㅎ종성체언같이 자음으로 시작하는 단어도 삿ㅎ과 같은 단어 뒤에 올 수 있으므로 똑같이 추가한다.

2) 한자어 목록

두음법칙이 적용되는 단어의 목록을 생성하기 위해 먼저 한자어의 목록을 생성하였다. 글자들 중 과 같은 단어에는 두음법칙이 적용되지 않고, 두음법칙이 적용되는 단어들은 대부분 한자어 단어에서 발생되기 때문에 먼저 한자어 목록을 생성한다. 이후 글자들을 생성한 후 예외의 경우를 추가하고 두음법칙이 적용되지 않는 글자를 제거해 최종적으로 두음법칙이 적용되는 글자의 목록을 만들 수 있다.

import json
 
f = open("한자어.txt", 'w',encoding="UTF-8")
def write(data, i): # 메모장에 데이터 작성
global json_string
json_string = ''
wordlist = []
for i in range(i):
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "동사":
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "형용사":
if data["channel"]["item"][i]["word_info"]["pos_info"][0]["pos"] != "":
if data["channel"]["item"][i]["word_info"]["word_type"] == "한자어":
json_string = data["channel"]["item"][i]["word_info"]["word"]
else:
pass
if "-" in json_string: #문자열 내의 특정 문자 제거
json_string = json_string.replace('-', '')
else:
pass
if "^" in json_string:
json_string = json_string.replace('^', '')
else:
pass
if " " in json_string:
json_string = json_string.replace(' ', '')
else:
pass
for k in range(11): # 문자열 안에 숫자가 포함되어있으면 숫자 제거
if str(k) in json_string:
json_string = json_string.replace(str(k), '')
if json_string in wordlist:
pass
else:
if len(json_string) >= 2:
wordlist.append(json_string)
f.write(json_string + "\n")
print(json_string)
 
for i in range(1, 87):
with open('파일{0}.json'.format(str(i*5000)),'rt',encoding="UTF-8") as json_file:
json_data = json.load(json_file)
 
write(json_data, 5000)
 
with open('파일{0}.json', 'rt', encoding="UTF-8") as json_file:
json_data = json.load(json_file)
 
write(json_data, 4340)
 
f.close()

큰 틀은 단어의 목록을 생성하는 코드와 같고, “word_type”가 한자어인 경우만을 따로 목록으로 만들었다. 이후 한자어 마지막 글자 목록을 만들었다.

word_list = []
with open("kktmal\한자어.txt", "r",encoding="utf-8") as f:
example = f.read()
 
allstr = example
word_list = allstr.split('\n')
word_list.remove(word_list[-1])
f.close()
lastwordlist = []
k = open("kktmal\chinese_lastword_list.txt", 'w',encoding="UTF-8")
for i in word_list:
print(i[-1])
word = i[-1]
if word not in lastwordlist:
lastwordlist.append(word)
k.write(word + "\n")
k.close()

3) 두음법칙이 적용되는 글자 추출

한글 유니코드에서 45208, ㄴ으로 시작하는 글자 중 마지막인 45795, “46972, “47559번이다. 앞서 한자어 마지막 글자 목록에서 유니코드의 범위가 45208~45795 또는 46972~47559번인 경우 두음법칙이 적용되는 것이다. 아래는 그 코드이다.

word_list = []
with open("kktmal\\chinese_lastword_list.txt", "r",encoding="utf-8") as f:
example = f.read()
allstr = example
word_list = allstr.split('\n')
word_list.remove(word_list[-1])
f.close()
lastwordlist = []
k = open("kktmal\\two_sound_lastword_list.txt", 'w',encoding="UTF-8")
for i in word_list:
word = i[0]
if 45208 <= ord(word) <= 45795 or 46972 <= ord(word) <= 47559:
lastwordlist.append(word)
k.write(word + "\n")
k.close()

위 코드로 만들어진 목록은 례,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,논이고, 이중 두음법칙이 적용되는 것을 추출하고 한방 단어들 중 예외적으로 두음법칙으로 한방 단어가 아니게 되는 경우(“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”(이 경우는 두음법칙을 적용해도 그대로 한방단어이므로 제외),“”->“”,“”->“”,“/”->“”(이 경우 또한 한방단어이므로 제외),“”->“”(욜로(요리로의 준말)이 있어 제외X),“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”(마찬가지로 제외),“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->“”,“”->““,”“->”“,”“->”“,”“->”“,”릿“->”“)를 추가하면 두음법칙이 적용되는 글자는 ㄴ에서 ㅇ이 되는 '','','','','','','','','','',’‘,’‘,’‘,’‘, ㄹ에서 ㄴ이 되는 '','','','','','','','','','','','','','','','','','','',’‘,’‘,’‘,’‘,’‘,’‘,’‘, ㄹ에서 ㅇ이 되는 '','','','','','','','','','','','','','','','','','','','',’릿‘,’‘,’가 된다.

 

7. 끝말잇기 코드 작성

지금까지 준비한 단어 목록과 알고리즘 순서도를 바탕으로 코드를 작성하고, 여러 번 직접 실행해보면 발생하는 버그를 수정하였다. 발생하는 모든 경우에 대해 예상을 벗어나는 경우에도 거기에 맞추어 에러 메시지가 출력되도록 하였다. 두음법칙을 적용시키는 코드는 함수로 나타내 구현을 쉽게 하였다.

import random
import os
 
# 특정 글자로 시작하는 리스트 추출
def filter_words_starting_with(lst, prefix):
return [word for word in lst if word.startswith(prefix)]
 
#특정 글자로 끝나는 리스트 추출
def filter_words_end_with(lst, prefix):
return [word for word in lst if word.endswith(prefix)]
 
#에러메시지
def errMessage(err_num):
errList = {1 : "게임을 종료합니다",
2 : "컴퓨터 승리\n게임을 종료합니다. \n이 게임에서 이미 사용된 단어입니다. ",
3 : "컴퓨터 승리\n게임을 종료합니다. \n표준국어대사전에 등재된 단어 중 동사, 형용사, 속담, 관용구를 제외한 단어를 입력해주세요.",
4 : "컴퓨터 승리 \n게임을 종료합니다. \n플레이어의 입력 값은 컴퓨터 입력 단어의 마지막 글자로 시작해야 합니다.",}
return errList[err_num]
 
#두음법칙
def two_sound_law(last_lett, is_player_turn):
if last_lett[0] in n_to_o:
last_lett.append(chr(ord(last_lett[0])+5292))
if is_player_turn == 1: print("컴퓨터: {0}({1})\n".format(computer_choice,last_lett[1]))
elif last_lett[0] in r_to_n:
last_lett.append(chr(ord(last_lett[0])-1764))
if is_player_turn == 1: print("컴퓨터: {0}({1})\n".format(computer_choice,last_lett[1]))
elif last_lett[0] in r_to_o:
last_lett.append(chr(ord(last_lett[0])+3528))
if is_player_turn == 1: print("컴퓨터: {0}({1})\n".format(computer_choice,last_lett[1]))
else:
if is_player_turn == 1: print("컴퓨터 : {0}\n".format(computer_choice))
this_file_path = os.path.abspath(__file__)
rel_path = this_file_path[:-8]
 
#단어 목록 추출
word_list = []
word_list_file = rel_path + "\\wordlist.txt"
with open(word_list_file, "r",encoding="utf-8") as f:
allstr = f.read()
f.close()
 
word_list = allstr.split('\n')
word_list.remove(word_list[-1])
used_word_list = []
#한방단어 목록 추출
hanbang_file = rel_path + "\\hanbang.txt"
with open(hanbang_file, "r", encoding="utf-8") as f:
hanbang_list = f.read().split("\n")
hanbang_list.remove(hanbang_list[-1])
 
#두음법칙
n_to_o=['','','','','','','','','','','','','','']
r_to_n=['','','','','','','','','','','','','','','','','','','','','','','','','','']
r_to_o=['','','','','','','','','','','','','','','','','','','','','릿','','']
 
a = input("게임을 시작하려면 \"시작\"을 입력하세요\n")
if a == "시작":
print("─────────────────────────────────────────")
print("게임 시작")
gamestate = 1
#컴퓨터 첫단어 고르기
computer_choice = random.choice(word_list)
while computer_choice[-1] in hanbang_list:
computer_choice = random.choice(word_list)
used_word_list.append(computer_choice)
word_list.remove(computer_choice)
last_lett = [computer_choice[-1]]
two_sound_law(last_lett, 1)
#플레이어 단어 입력
player_choice = input("플레이어 : ")
if player_choice[0] in last_lett: # 플레이어 단어가 컴퓨터 마지막단어와 같을때
if player_choice in word_list: # 단어가 사전에 있을 때
while gamestate == 1: #게임 반복 부분
last_lett = [player_choice[-1]]
used_word_list.append(player_choice)
word_list.remove(player_choice)
two_sound_law(last_lett,0)
prefix_to_search = last_lett[0]
result_list = filter_words_starting_with(word_list, prefix_to_search)
if len(last_lett) != 1:
prefix_to_search = last_lett[1]
result_list=result_list+filter_words_starting_with(word_list, prefix_to_search)
if result_list != []:
computer_choice = random.choice(result_list) #컴퓨터 단어 선택
used_word_list.append(computer_choice)
word_list.remove(computer_choice)
last_lett = [computer_choice[-1]]
else:
print("플레이어 승리!")
gamestate = 2
break
two_sound_law(last_lett, 1)
player_choice = input("플레이어 : ")

if player_choice[0] in last_lett:
if player_choice in word_list:
continue
else:
gamestate = 0
if player_choice in used_word_list:
print(errMessage(2))
else:
print(errMessage(3))
else:
gamestate = 0
print(errMessage(4))
else:
gaemstate = 0
print(errMessage(3))
else:
gamestate = 0
print(errMessage(4))
else:
print(errMessage(1))

게임을 시작하면 가장 먼저 시작을 입력받는다. 정상적으로 시작을 입력할 경우 게임이 시작되고, 아니면 게임을 종료합니다를 출력한다. 여기서 컴퓨터는 기본적으로 사용 가능한 단어가 존재하면 그 중 무작위 단어를 출력하게 된다. 사용 가능한 단어의 기준은 게임 시작 후 지금까지 사용되지 않았는가이고, 첫 단어 선택의 경우 한방 단어가 아닌가도 기준이 된다.

그렇게 첫 단어를 컴퓨터가 선택하면 해당 단어에 두음법칙이 적용되는지에 따라 두음법칙을 적용하고, 플레이어 입력을 받는다.

플레이어 입력을 받으면 단어가 사전에 있는지, 이미 사용된 단어가 아닌지를 판단한 후 컴퓨터 차례로 넘어간다. 컴퓨터는 다시 플레이어 단어에 두음법칙이 적용된다면 적용하고, 적용되지 않는다면 바로 단어를 선택해 출력한다. 이 과정을 반복한다.

컴퓨터가 선택할 수 있는 단어가 없는 경우 플레이어가 승리하고, 플레이어가 사전에 없는 단어를 사용하거나 이미 사용한 단어를 사용하면 컴퓨터가 승리한다.

8. 변수, 함수 설명

가. 함수명

filter_words_starting_with : 특정 글자로 시작하는 단어 목록을 추출한다.

filter_words_end_with : 특정 글자로 끝나는 단어 목록을 추출한다.

errMessage : 상황에 맞는 에러 메시지를 출력한다.

two_sound_law : 단어에 두음법칙이 적용된다면 적용한다. 다음이 플레이어 차례라면 컴퓨터가 입력한 단어 끝에 괄호를 붙여 첫 글자로 사용할 수 있는 단어를 알려 준다.

ex) 달무리 -> 달무리()

만약 다음이 컴퓨터 차례라면 이를 출력하지 않는다. 두음법칙을 적용하는 것에는 한글 유니코드의 번호 차이를 이용한다. ㄴ부터 ㅇ까지 5292, ㄹ부터 ㄴ까지 1764, ㄹ부터 ㅇ까지 3528 간격이다.

. 주요 변수 설명

word_list : 사용 가능 여부에 관계없는 전체 단어를 저장한다.

word_list_file : 단어 목록이 저장된 txt 파일의 경로를 저장한다.

used_word_list : 사용된 단어를 저장한다.

hanbang_file : 한방 단어 목록이 저장된 txt 파일의 경로를 저장한다.

hanbang_list : 한방 단어의 목록을 저장한다.

n_to_o, r_to_n, r_to_o : 각각 ㄴ->, ->, ->ㅇ으로 바뀌는 두음법칙이 적용되는 글자를 저장한다.

gamestate : 현재 게임 상황을 저장한다. 0은 게임 종료, 1은 진행 중, 2는 플레이어 승리이다.

computer_choice : 컴퓨터가 선택한 단어를 저장한다.

last_lett : 컴퓨터 또는 플레이어가 선택한 단어의 마지막 글자를 저장한다. 리스트 형태로 플레이어의 단어 마지막 글자에 두음법칙이 적용된다면 적용한 글자도 여기에 추가된다.

player_choice : 플레이어가 선택한 단어를 저장한다.

result_list : 플레이어가 선택한 단어의 마지막 글자로 시작하는 단어의 목록을 저장한다.

9. 오류 개선

처음부터 체계적으로 순서도를 그리고 알고리즘을 충분히 생각한 후 코드를 짜서 오류가 거의 발생하지 않았다. 대신 코드를 다 짠 후 반복적으로 나타나는 두음법칙에 대한 부분을 따로 함수로 표현하였고, 플레이어에 의한 에러 상황을 에러 메시지를 튜플에 저장하여 사용했다. 다만 가끔 두음법칙이 적용되는 글자에도 괄호 안에 두음 예시가 출력되지 않는 버그가 있어 이를 해결하였다.

10. 느낀 점, 시행착오

스스로 내가 만들어 보고 싶은 프로그램을 만들어 볼 수 있어서 좋았다. 두음법칙을 적용하기 위해 한글 유니코드에서 글자별로 초성이 같다면 유니코드 간격이 일정하다는 사실을 이용한 것이 인상 깊었다. 코드를 짠 후 시간이 지나 다시 볼 때 내가 짜 놓고도 잘 이해가 안 됐었는데, 그때 VSC의 파이썬 디버거의 유용성을 깨닫게 되었다. 이번에는 JSON 데이터로 잘 구조화되어 있는 데이터를 사용했는데, 다음에는 훨씬 어려운 데이터도 사용해 보고 싶다. 처음 계획을 세울 때 두음법칙을 어떻게 적용할 수 있을지 고민했었는데, 유튜브에서 개발자들이 한글을 처리하는 법을 다룬 영상을 보고 영감을 얻을 수 있었다. 처음에는 JSON 데이터를 처리하는 것조차도 어려워했었는데, 이 프로젝트를 진행하면서 코딩 실력이나 코드에 대한 이해도가 전에 비해 많이 높아진 것 같다.

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함