pip install pygame SpeechRecognition pyaudio pyttsx3. pygame.Rect 선언/그리기와 마우스 클릭 이벤트는 이미 할 수 있다고 가정합니다.
각 챕터의 코드는 바로 이전 챕터의 전체 코드 + 이번 챕터에서 새로 추가되는 부분입니다.
파란색으로 밑칠된 줄 이 이번 챕터에서 새로 생긴 부분입니다. 챕터 4~7은 아직 전체 앱에 합치기 전에, 그 기능 하나만 따로 테스트해보는 작은 프로그램입니다.
1메뉴 화면 만들기
버거(2종) · 사이드(3종) · 음료(3종), 세 줄로 메뉴 버튼을 그립니다. seat-demo의 책상처럼, 메뉴도 이름 + 위치를 담은 딕셔너리를 리스트에 모아서 관리합니다.
import pygame
pygame.init()
screen = pygame.display.set_mode((1000, 650)) # 키오스크 화면 크기
font = pygame.font.SysFont("malgungothic", 26)
clock = pygame.time.Clock()
BG, INK, CARD, LINE = (253, 248, 240), (26, 16, 8), (255, 255, 255), (176, 96, 16)
BURGER_MENU = ["치킨버거", "불고기버거"]
SIDE_MENU = ["감자튀김", "어니언링", "치즈스틱"]
DRINK_MENU = ["콜라", "사이다", "환타"]
# 버거 버튼들을 한 줄로 만든다 (100+220칸씩 옆으로)
burger_buttons = []
for i, name in enumerate(BURGER_MENU):
burger_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 140, 200, 100)})
# 사이드도 똑같은 방식으로, y좌표만 다르게
side_buttons = []
for i, name in enumerate(SIDE_MENU):
side_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 280, 200, 100)})
# 음료도 똑같이
drink_buttons = []
for i, name in enumerate(DRINK_MENU):
drink_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 420, 200, 100)})
def draw_buttons(button_list):
for b in button_list:
pygame.draw.rect(screen, CARD, b["rect"], border_radius=12)
pygame.draw.rect(screen, LINE, b["rect"], 3, border_radius=12)
label = font.render(b["name"], True, INK)
screen.blit(label, label.get_rect(center=b["rect"].center))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BG)
screen.blit(font.render("버거", True, INK), (60, 100))
draw_buttons(burger_buttons)
screen.blit(font.render("사이드", True, INK), (60, 240))
draw_buttons(side_buttons)
screen.blit(font.render("음료", True, INK), (60, 380))
draw_buttons(drink_buttons)
pygame.display.flip()
clock.tick(60)
pygame.quit()
버거/사이드/음료를 하나로 합친 함수 대신, 거의 똑같은 코드를 세 번 반복했습니다. 나중에 각 줄마다 하는 일이 조금씩 달라질 수 있어서, 처음부터 억지로 합치기보다 이렇게 풀어두는 게 이해하기 쉽습니다.
2터치로 메뉴 고르기
클릭한 메뉴를 cart 딕셔너리에 저장하고, 고른 메뉴는 버튼 색을 노란색으로 바꿔서 보여줍니다.
import pygame
pygame.init()
screen = pygame.display.set_mode((1000, 650))
font = pygame.font.SysFont("malgungothic", 26)
clock = pygame.time.Clock()
BG, INK, CARD, LINE = (253, 248, 240), (26, 16, 8), (255, 255, 255), (176, 96, 16)
SELECTED = (255, 224, 150) # 선택된 메뉴 버튼 색
BURGER_MENU = ["치킨버거", "불고기버거"]
SIDE_MENU = ["감자튀김", "어니언링", "치즈스틱"]
DRINK_MENU = ["콜라", "사이다", "환타"]
cart = {"버거": None, "사이드": None, "음료": None} # 아직 아무것도 안 고르면 전부 None
burger_buttons = []
for i, name in enumerate(BURGER_MENU):
burger_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 140, 200, 100)})
side_buttons = []
for i, name in enumerate(SIDE_MENU):
side_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 280, 200, 100)})
drink_buttons = []
for i, name in enumerate(DRINK_MENU):
drink_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 420, 200, 100)})
# cart_key: 이 버튼 줄이 "버거"인지 "사이드"인지 "음료"인지
def draw_buttons(button_list, cart_key):
for b in button_list:
color = SELECTED if cart[cart_key] == b["name"] else CARD
pygame.draw.rect(screen, color, b["rect"], border_radius=12)
pygame.draw.rect(screen, LINE, b["rect"], 3, border_radius=12)
label = font.render(b["name"], True, INK)
screen.blit(label, label.get_rect(center=b["rect"].center))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
for b in burger_buttons:
if b["rect"].collidepoint(event.pos):
cart["버거"] = b["name"]
for b in side_buttons:
if b["rect"].collidepoint(event.pos):
cart["사이드"] = b["name"]
for b in drink_buttons:
if b["rect"].collidepoint(event.pos):
cart["음료"] = b["name"]
screen.fill(BG)
screen.blit(font.render("버거", True, INK), (60, 100))
draw_buttons(burger_buttons, "버거")
screen.blit(font.render("사이드", True, INK), (60, 240))
draw_buttons(side_buttons, "사이드")
screen.blit(font.render("음료", True, INK), (60, 380))
draw_buttons(drink_buttons, "음료")
pygame.display.flip()
clock.tick(60)
pygame.quit()
3장바구니 → 결제까지 완성하기
세 가지를 다 고르면 자동으로 장바구니 화면이 뜨고, "카드를 넣어주세요" 버튼을 누르면 완료 화면으로 넘어갑니다. 화면이 여러 개가 됐으니 지금이 어느 화면인지 기억해둘 state 변수가 필요합니다.
import pygame
pygame.init()
screen = pygame.display.set_mode((1000, 650))
font = pygame.font.SysFont("malgungothic", 26)
big_font = pygame.font.SysFont("malgungothic", 34) # 화면 제목용 큰 글꼴
clock = pygame.time.Clock()
BG, INK, CARD, LINE = (253, 248, 240), (26, 16, 8), (255, 255, 255), (176, 96, 16)
SELECTED = (255, 224, 150)
DONE_GREEN = (10, 122, 48)
BURGER_MENU = ["치킨버거", "불고기버거"]
SIDE_MENU = ["감자튀김", "어니언링", "치즈스틱"]
DRINK_MENU = ["콜라", "사이다", "환타"]
cart = {"버거": None, "사이드": None, "음료": None}
state = "menu" # "menu"(고르기) -> "cart"(장바구니) -> "done"(완료)
burger_buttons = []
for i, name in enumerate(BURGER_MENU):
burger_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 140, 200, 100)})
side_buttons = []
for i, name in enumerate(SIDE_MENU):
side_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 280, 200, 100)})
drink_buttons = []
for i, name in enumerate(DRINK_MENU):
drink_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 420, 200, 100)})
pay_btn = pygame.Rect(1000 // 2 - 110, 650 - 100, 220, 60) # 장바구니 화면의 버튼
restart_btn = pygame.Rect(1000 // 2 - 110, 650 - 100, 220, 60) # 완료 화면의 버튼
def draw_buttons(button_list, cart_key):
for b in button_list:
color = SELECTED if cart[cart_key] == b["name"] else CARD
pygame.draw.rect(screen, color, b["rect"], border_radius=12)
pygame.draw.rect(screen, LINE, b["rect"], 3, border_radius=12)
label = font.render(b["name"], True, INK)
screen.blit(label, label.get_rect(center=b["rect"].center))
def draw_menu_screen():
screen.blit(font.render("버거", True, INK), (60, 100))
draw_buttons(burger_buttons, "버거")
screen.blit(font.render("사이드", True, INK), (60, 240))
draw_buttons(side_buttons, "사이드")
screen.blit(font.render("음료", True, INK), (60, 380))
draw_buttons(drink_buttons, "음료")
def draw_cart_screen():
title = big_font.render("장바구니", True, INK)
screen.blit(title, (60, 60))
screen.blit(font.render(f"버거: {cart['버거']}", True, INK), (100, 160))
screen.blit(font.render(f"사이드: {cart['사이드']}", True, INK), (100, 220))
screen.blit(font.render(f"음료: {cart['음료']}", True, INK), (100, 280))
pygame.draw.rect(screen, DONE_GREEN, pay_btn, border_radius=12)
label = font.render("카드를 넣어주세요", True, (255, 255, 255))
screen.blit(label, label.get_rect(center=pay_btn.center))
def draw_done_screen():
title = big_font.render("주문이 완료됐습니다. 감사합니다!", True, DONE_GREEN)
screen.blit(title, title.get_rect(center=(1000 // 2, 650 // 2 - 40)))
pygame.draw.rect(screen, LINE, restart_btn, border_radius=12)
label = font.render("처음으로", True, (255, 255, 255))
screen.blit(label, label.get_rect(center=restart_btn.center))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif state == "menu" and event.type == pygame.MOUSEBUTTONDOWN:
for b in burger_buttons:
if b["rect"].collidepoint(event.pos):
cart["버거"] = b["name"]
for b in side_buttons:
if b["rect"].collidepoint(event.pos):
cart["사이드"] = b["name"]
for b in drink_buttons:
if b["rect"].collidepoint(event.pos):
cart["음료"] = b["name"]
if cart["버거"] and cart["사이드"] and cart["음료"]: # 세 가지 다 골랐으면
state = "cart"
elif state == "cart" and event.type == pygame.MOUSEBUTTONDOWN:
if pay_btn.collidepoint(event.pos):
state = "done"
elif state == "done" and event.type == pygame.MOUSEBUTTONDOWN:
if restart_btn.collidepoint(event.pos):
cart = {"버거": None, "사이드": None, "음료": None}
state = "menu"
screen.fill(BG)
if state == "menu":
draw_menu_screen()
elif state == "cart":
draw_cart_screen()
elif state == "done":
draw_done_screen()
pygame.display.flip()
clock.tick(60)
pygame.quit()
여기까지가 터치로만 주문하는 키오스크입니다. 이 상태로도 완전히 동작합니다 — 다음 챕터부터는 "시니어 어르신을 위한 음성 안내"를 추가해나갑니다.
4마이크로 듣고 텍스트로 바꾸기
여기서부터 4개 챕터(4~7)는 잠깐 키오스크 화면에서 벗어나, 음성 기능 하나씩만 따로 테스트해봅니다. 다 만들어지면 8번 챕터에서 지금까지 만든 키오스크에 합칩니다.
import pygame
import speech_recognition as sr
pygame.init()
screen = pygame.display.set_mode((1000, 650))
font = pygame.font.SysFont("malgungothic", 26)
small_font = pygame.font.SysFont("malgungothic", 20)
clock = pygame.time.Clock()
BG, INK, LINE = (253, 248, 240), (26, 16, 8), (176, 96, 16)
mic_btn = pygame.Rect(400, 300, 200, 70)
recognizer = sr.Recognizer()
status_text = "마이크 버튼을 눌러 말해보세요"
recognized_text = ""
# 마이크로 소리를 듣고, 텍스트로 바꿔서 recognized_text에 저장한다
def listen_and_recognize():
global status_text, recognized_text
status_text = "듣고 있어요..."
pygame.display.flip()
with sr.Microphone() as source:
try:
audio = recognizer.listen(source, timeout=5, phrase_time_limit=6)
except sr.WaitTimeoutError:
status_text = "음성이 감지되지 않았어요"
return
try:
recognized_text = recognizer.recognize_google(audio, language="ko-KR")
status_text = "인식 완료!"
except sr.UnknownValueError:
status_text = "못 알아들었어요, 다시 시도해주세요"
except sr.RequestError:
status_text = "네트워크 오류"
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if mic_btn.collidepoint(event.pos):
listen_and_recognize()
screen.fill(BG)
pygame.draw.rect(screen, LINE, mic_btn, border_radius=10)
mic_label = font.render("눌러서 말하기", True, (255, 255, 255))
screen.blit(mic_label, mic_label.get_rect(center=mic_btn.center))
screen.blit(small_font.render(status_text, True, INK), (60, 100))
screen.blit(small_font.render(f"인식된 문장: {recognized_text}", True, INK), (60, 140))
pygame.display.flip()
clock.tick(60)
pygame.quit()
timeout=5는 "5초 안에 말을 시작하지 않으면 포기", phrase_time_limit=6은 "한 문장은 최대 6초까지만 듣는다"는 뜻입니다.
recognized_text에 그대로 찍히는지 확인하세요.
5주변 소음 줄이기
키오스크는 매장 소음(음악, 다른 손님 대화) 속에 놓입니다. adjust_for_ambient_noise()가 말하기 직전 잠깐 주변 소음을 측정해서 "이 정도부터는 말소리"라는 기준을 자동으로 맞춰줍니다.
import pygame
import speech_recognition as sr
pygame.init()
screen = pygame.display.set_mode((1000, 650))
font = pygame.font.SysFont("malgungothic", 26)
small_font = pygame.font.SysFont("malgungothic", 20)
clock = pygame.time.Clock()
BG, INK, LINE = (253, 248, 240), (26, 16, 8), (176, 96, 16)
mic_btn = pygame.Rect(400, 300, 200, 70)
recognizer = sr.Recognizer()
status_text = "마이크 버튼을 눌러 말해보세요"
recognized_text = ""
def listen_and_recognize():
global status_text, recognized_text
status_text = "듣고 있어요..."
pygame.display.flip()
with sr.Microphone() as source:
status_text = "주변 소리를 측정하는 중..."
pygame.display.flip()
recognizer.adjust_for_ambient_noise(source, duration=0.6) # 0.6초 동안 소음 측정
status_text = "듣고 있어요..."
pygame.display.flip()
try:
audio = recognizer.listen(source, timeout=5, phrase_time_limit=6)
except sr.WaitTimeoutError:
status_text = "음성이 감지되지 않았어요"
return
try:
recognized_text = recognizer.recognize_google(audio, language="ko-KR")
status_text = "인식 완료!"
except sr.UnknownValueError:
status_text = "못 알아들었어요, 다시 시도해주세요"
except sr.RequestError:
status_text = "네트워크 오류"
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if mic_btn.collidepoint(event.pos):
listen_and_recognize()
screen.fill(BG)
pygame.draw.rect(screen, LINE, mic_btn, border_radius=10)
mic_label = font.render("눌러서 말하기", True, (255, 255, 255))
screen.blit(mic_label, mic_label.get_rect(center=mic_btn.center))
screen.blit(small_font.render(status_text, True, INK), (60, 100))
screen.blit(small_font.render(f"인식된 문장: {recognized_text}", True, INK), (60, 140))
pygame.display.flip()
clock.tick(60)
pygame.quit()
duration을 너무 길게 잡으면(예: 3초) 어르신이 말을 걸기도 전에 시간이 다 지나가버릴 수 있습니다. 0.5~1초가 적당합니다.6레벤슈타인 거리로 메뉴 이름 맞추기
STT가 "치킨버거"를 "치킨버그"로 잘못 알아듣는 경우가 많습니다. seat-demo에서도 쓴 레벤슈타인 거리(편집 거리)로 가장 비슷한 메뉴를 찾습니다. 이번엔 화면도 필요 없이, 로직만 테스트합니다.
# 두 문자열이 같아지려면 몇 번 고쳐야 하는지 계산 (삽입/삭제/변경 각 1점)
def levenshtein(a, b):
n, m = len(a), len(b)
table = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n + 1):
table[i][0] = i
for j in range(m + 1):
table[0][j] = j
for i in range(1, n + 1):
for j in range(1, m + 1):
cost = 0 if a[i - 1] == b[j - 1] else 1
table[i][j] = min(
table[i - 1][j] + 1,
table[i][j - 1] + 1,
table[i - 1][j - 1] + cost
)
return table[n][m]
# text와 가장 비슷한 메뉴 이름과, 그때의 편집 거리를 돌려준다
def find_best_menu(text, menu_list):
best_name = None
best_dist = None
for name in menu_list:
d = levenshtein(text, name)
if best_dist is None or d < best_dist:
best_dist = d
best_name = name
return best_name, best_dist
BURGER_MENU = ["치킨버거", "불고기버거"]
# STT가 "치킨버그"로 잘못 알아들었다고 가정하고 테스트
name, dist = find_best_menu("치킨버그", BURGER_MENU)
print(name, dist) # -> 치킨버거 1
이 챕터는 pygame도 speech_recognition도 필요 없습니다 — 순수하게 문자열 비교 로직이기 때문입니다. 그냥 파이썬 파일로 저장해서 실행해도 됩니다.
find_best_menu("불고기버거요", BURGER_MENU)와 find_best_menu("콜라", BURGER_MENU)를 각각 실행해보세요. 메뉴에 아예 없는 말을 넣으면 거리가 얼마나 크게 나오나요?
7글자를 소리로 읽어주기 (TTS)
pyttsx3로 글자를 소리 내어 읽게 만듭니다. STT(듣기)의 반대인 TTS(말하기)입니다.
import pyttsx3
def speak(text):
engine = pyttsx3.init() # 매번 새로 만든다 (아래 설명 참고)
engine.say(text)
engine.runAndWait()
engine.stop()
speak("안녕하세요. 주문을 시작하겠습니다.")
speak("치킨버거를 골라주세요.")
pyttsx3는 엔진 하나를 계속 재사용해서 runAndWait()을 여러 번 부르면, 두 번째 호출부터 소리가 안 나는 경우가 있습니다(특히 윈도우에서). 그래서 speak()를 부를 때마다 pyttsx3.init()으로 엔진을 새로 만듭니다.
speak()를 5번 연달아 호출해서, 전부 소리가 잘 나오는지 확인하세요. (엔진을 한 번만 만들어서 재사용하도록 바꿔보고, 두 번째 호출부터 소리가 안 나는지도 비교해보면 왜 이 방식을 쓰는지 확실히 알 수 있습니다.)
8음성으로 버거 하나 주문받기
3번 챕터(완성된 키오스크)에 4~7번에서 따로 테스트한 기능(STT·소음 줄이기·레벤슈타인·TTS)을 전부 합쳐서, "버거" 한 카테고리만 음성으로 주문받아 봅니다. 메뉴 화면에 임시 버튼을 하나 추가해서, 누르면 음성 주문이 시작되도록 만듭니다.
import pygame
import speech_recognition as sr
import pyttsx3
pygame.init()
screen = pygame.display.set_mode((1000, 650))
font = pygame.font.SysFont("malgungothic", 26)
small_font = pygame.font.SysFont("malgungothic", 18)
big_font = pygame.font.SysFont("malgungothic", 34)
clock = pygame.time.Clock()
BG, INK, CARD, LINE = (253, 248, 240), (26, 16, 8), (255, 255, 255), (176, 96, 16)
SELECTED = (255, 224, 150)
DONE_GREEN = (10, 122, 48)
REC_RED = (200, 40, 40) # "듣고 있어요" 표시 색
BURGER_MENU = ["치킨버거", "불고기버거"]
SIDE_MENU = ["감자튀김", "어니언링", "치즈스틱"]
DRINK_MENU = ["콜라", "사이다", "환타"]
cart = {"버거": None, "사이드": None, "음료": None}
state = "menu"
burger_buttons = []
for i, name in enumerate(BURGER_MENU):
burger_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 140, 200, 100)})
side_buttons = []
for i, name in enumerate(SIDE_MENU):
side_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 280, 200, 100)})
drink_buttons = []
for i, name in enumerate(DRINK_MENU):
drink_buttons.append({"name": name, "rect": pygame.Rect(100 + i * 220, 420, 200, 100)})
pay_btn = pygame.Rect(1000 // 2 - 110, 650 - 100, 220, 60)
restart_btn = pygame.Rect(1000 // 2 - 110, 650 - 100, 220, 60)
voice_burger_btn = pygame.Rect(60, 560, 280, 50) # 테스트용 임시 버튼 (9번 챕터에서 없어짐)
def draw_buttons(button_list, cart_key):
for b in button_list:
color = SELECTED if cart[cart_key] == b["name"] else CARD
pygame.draw.rect(screen, color, b["rect"], border_radius=12)
pygame.draw.rect(screen, LINE, b["rect"], 3, border_radius=12)
label = font.render(b["name"], True, INK)
screen.blit(label, label.get_rect(center=b["rect"].center))
def draw_menu_screen():
screen.blit(font.render("버거", True, INK), (60, 100))
draw_buttons(burger_buttons, "버거")
screen.blit(font.render("사이드", True, INK), (60, 240))
draw_buttons(side_buttons, "사이드")
screen.blit(font.render("음료", True, INK), (60, 380))
draw_buttons(drink_buttons, "음료")
pygame.draw.rect(screen, LINE, voice_burger_btn, border_radius=10)
vlabel = small_font.render("🎤 음성으로 버거 주문 (테스트용)", True, (255, 255, 255))
screen.blit(vlabel, vlabel.get_rect(center=voice_burger_btn.center))
def draw_cart_screen():
title = big_font.render("장바구니", True, INK)
screen.blit(title, (60, 60))
screen.blit(font.render(f"버거: {cart['버거']}", True, INK), (100, 160))
screen.blit(font.render(f"사이드: {cart['사이드']}", True, INK), (100, 220))
screen.blit(font.render(f"음료: {cart['음료']}", True, INK), (100, 280))
pygame.draw.rect(screen, DONE_GREEN, pay_btn, border_radius=12)
label = font.render("카드를 넣어주세요", True, (255, 255, 255))
screen.blit(label, label.get_rect(center=pay_btn.center))
def draw_done_screen():
title = big_font.render("주문이 완료됐습니다. 감사합니다!", True, DONE_GREEN)
screen.blit(title, title.get_rect(center=(1000 // 2, 650 // 2 - 40)))
pygame.draw.rect(screen, LINE, restart_btn, border_radius=12)
label = font.render("처음으로", True, (255, 255, 255))
screen.blit(label, label.get_rect(center=restart_btn.center))
# ---- 5·6·7번 챕터에서 테스트한 함수들을 그대로 가져옴 ----
recognizer = sr.Recognizer()
def listen_and_recognize():
with sr.Microphone() as source:
recognizer.adjust_for_ambient_noise(source, duration=0.6)
try:
audio = recognizer.listen(source, timeout=5, phrase_time_limit=6)
except sr.WaitTimeoutError:
return ""
try:
return recognizer.recognize_google(audio, language="ko-KR")
except sr.UnknownValueError:
return ""
except sr.RequestError:
return ""
def levenshtein(a, b):
n, m = len(a), len(b)
table = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n + 1):
table[i][0] = i
for j in range(m + 1):
table[0][j] = j
for i in range(1, n + 1):
for j in range(1, m + 1):
cost = 0 if a[i - 1] == b[j - 1] else 1
table[i][j] = min(table[i - 1][j] + 1, table[i][j - 1] + 1, table[i - 1][j - 1] + cost)
return table[n][m]
def find_best_menu(text, menu_list):
best_name, best_dist = None, None
for name in menu_list:
d = levenshtein(text, name)
if best_dist is None or d < best_dist:
best_dist = d
best_name = name
return best_name, best_dist
def speak(text):
engine = pyttsx3.init()
engine.say(text)
engine.runAndWait()
engine.stop()
# ---- 버거 하나를 음성으로 주문받는 함수 ----
def order_burger_by_voice():
global cart
screen.fill(BG)
screen.blit(small_font.render("버거를 골라주세요", True, INK), (60, 30))
draw_buttons(burger_buttons, "버거")
pygame.display.flip()
speak("드시고 싶은 버거를 말씀해주세요.")
screen.fill(BG)
screen.blit(small_font.render("듣고 있어요...", True, REC_RED), (60, 30))
draw_buttons(burger_buttons, "버거")
pygame.display.flip()
heard = listen_and_recognize()
name, dist = find_best_menu(heard, BURGER_MENU) if heard else (None, None)
if not name:
speak("다시 한 번 말씀해주세요")
heard = listen_and_recognize()
name, dist = find_best_menu(heard, BURGER_MENU) if heard else (None, None)
cart["버거"] = name if name else BURGER_MENU[0]
screen.fill(BG)
screen.blit(small_font.render(f"선택하신 메뉴: {cart['버거']}", True, DONE_GREEN), (60, 30))
draw_buttons(burger_buttons, "버거")
pygame.display.flip()
pygame.time.delay(1200)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif state == "menu" and event.type == pygame.MOUSEBUTTONDOWN:
if voice_burger_btn.collidepoint(event.pos):
order_burger_by_voice()
for b in burger_buttons:
if b["rect"].collidepoint(event.pos):
cart["버거"] = b["name"]
for b in side_buttons:
if b["rect"].collidepoint(event.pos):
cart["사이드"] = b["name"]
for b in drink_buttons:
if b["rect"].collidepoint(event.pos):
cart["음료"] = b["name"]
if cart["버거"] and cart["사이드"] and cart["음료"]:
state = "cart"
elif state == "cart" and event.type == pygame.MOUSEBUTTONDOWN:
if pay_btn.collidepoint(event.pos):
state = "done"
elif state == "done" and event.type == pygame.MOUSEBUTTONDOWN:
if restart_btn.collidepoint(event.pos):
cart = {"버거": None, "사이드": None, "음료": None}
state = "menu"
screen.fill(BG)
if state == "menu":
draw_menu_screen()
elif state == "cart":
draw_cart_screen()
elif state == "done":
draw_done_screen()
pygame.display.flip()
clock.tick(60)
pygame.quit()
버튼을 눌러서 직접 테스트할 수 있으니, 9번 챕터로 넘어가기 전에 이 버튼으로 "버거" 음성 주문이 잘 되는지 충분히 확인해보세요.
BURGER_MENU[0](치킨버거)로 자동 선택되는지 확인하세요.
9마무리 — 사이드·음료까지 잇고 자동 시작
8번 챕터의 order_burger_by_voice()를 사이드·음료용으로 두 번 더 복사해서 이어 붙이고, 임시 버튼 대신 10초 유휴 타이머로 자동 시작하도록 바꾸면 완성입니다. 새로운 개념은 없고, 지금까지 배운 것을 그대로 이어 붙이는 마지막 단계입니다.
바뀌는 것 3가지
order_burger_by_voice()뒤에 사이드용, 음료용을 문구만 바꿔서 그대로 이어 붙입니다 (안내 멘트: "먼저 버거를 골라주세요" → "이번엔 사이드를 골라주세요" → "마지막으로 음료를 골라주세요").- 세 단계가 다 끝나면
state = "cart"로 바꾸고 "카드를 넣어주세요"를speak()합니다. voice_burger_btn버튼을 지우고, 대신last_interaction(마지막으로 화면을 건드린 시각)을 기록해뒀다가 10초가 지나면 자동으로 이 함수를 실행합니다 (voice-guide 이전 6번 챕터에서 쓴AUTO_LISTEN_MS방식과 동일).
전체 코드는 이 사이트의 완성된 데모 → "코드 보기" 버튼에서 그대로 확인할 수 있습니다. 함수 이름과 구조가 지금까지와 완전히 똑같아서, order_burger_by_voice() 옆에 order_side_by_voice(), order_drink_by_voice()가 나란히 있는 걸 바로 알아볼 수 있을 겁니다.
여기까지 오셨다면
메뉴 화면 → 터치 주문 → 장바구니/결제 → 음성 인식 → 소음 보정 → 레벤슈타인 매칭 → TTS 안내 → 한 카테고리 음성 주문 → 전체 3단계 자동 음성 주문까지, 시니어 음성 키오스크의 모든 기능을 직접 만든 것입니다.