사과게임 메크로를 만들게 된 계기
평소에 가끔 심심할 때 사과게임을 하곤 했습니다. 하다 보니 반복적으로 같은 패턴으로 사과를 찾고 드래그하는 과정이 많았고,
문득 "이거 메크로로 만들면 되겠는데?"라는 생각이 들었습니다.
딱히 거창한 이유는 없고, 그냥 심심해서 재미삼아 직접 만들어보기로 했습니다.
사과게임이 뭔데?
사과게임(Fruit Box)은 17 X 10 격자에 무작위로 있는 사과(1~9 숫자)
를 마우스로 드래그해서 합이 10이 되도록 조합 하는 퍼즐 게임으로 최대한 많은 사과를 없애
높은 점수를 기록하는것이 목표입니다.


구현
Python을 선택한 이유
사과게임 메크로를 만들면서 가장 핵심이라고 생각했던 부분은 게임 화면에 있는 숫자들을 정확하게 추출하는 것이었습니다.
이를위해 OCR(Optical Character Recognition) 기술이 필요했고, 여러 가지 라이브러리를 찾아보던 중 Tesseract OCR을 선택하게 되었습니다.
Tesseract를 사용하는 데 있어 예제나 참고할 수 있는 코드가 가장 많은 언어가 Python이었고 러닝커브가 높지않아 Python으로 프로젝트를 진행하게 되었습니다.
구현 순서
1. 스크린샷 캡처 -> 2. OCR로 숫자 추출 -> 3. 숫자 조합 탐색 -> 4.마우스 드래그 순서
1. 스크린샷 캡처
- 게임 화면 내 숫자가 표시되는 영역만 캡처하기 위해, 컴퓨터 스크린내의 영역을 미리 설정해두고 해당 영역만 스크린샷을 찍도록 했습니다.
- 스크린샷은 프로젝트 폴더에 저장되며, 파일명은 현재 시스템의 날짜, 시간으로 만들었습니다. ex)2025_04_27_17_27_09.png
- 저장이 완료되면 파일 이름과 함께 "스크린샷 저장 완료" 메세지를 출력해 줍니다.
# 스크린샷 찍기
def take_screenshot(fileName):
screenshot = pyautogui.screenshot(region=(SCREENSHOT_OFFSET_X, SCREENSHOT_OFFSET_Y, SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT))
screenshot.save(IMAGE_SCREENSHOT_FOLDER + fileName + ".png")
print("스크린샷 저장 완료! 파일 이름:", fileName, "확장자: png")
2. OCR로 숫자 추출
게임 화면을 캡처한 다음에는, 각 칸에 적힌 숫자를 인식해내야 했습니다.
이를 위해 Tesseract OCR 라이브러리를 사용해 이미지에서 숫자를 추출하는 과정을 구현했습니다.
OCR 처리 과정은 다음과 같습니다.
이미지 전처리 -> 셀 단위 분할 -> OCR 수행 -> 수행한 데이터 저장
2-1. 이미지 전처리
캡처한 스크린샷 원본을 그대로 OCR 처리를 하게되면 숫자에 대한 인식률이 높지 않기에 OCR 인식률을 높이기 위해 이미지 전처리를 진행해 주었습니다.
적용한 이미지 전처리 과정
그레이스케일 변환 -> 색 반전 -> 이진화 과정
이미지 전처리 코드
# 캡처한 이미지 로드
image = cv2.imread(IMAGE_SCREENSHOT_FOLDER + image_name + ".png")
# 그레이 스케일
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 색상 반전
inverted = cv2.bitwise_not(gray)
# 이진화 처리
_, binaryFullImage = cv2.threshold(inverted, 100, 255, cv2.THRESH_BINARY)



2-2, 2-3. 셀 단위 분할, OCR 처리
이진화된 이미지를 셀 단위로 잘라냅니다.
OCR 은 이미지가 클 수록 정확도가 높아지기에 크기를 키워 숫자 윤곽을 뚜렷하게 만드는 처리 진행
확대인 이미지를 대상으로 pytesseract OCR 진행
for row in range(ROWS):
for col in range(COLS):
cell_img = binaryFullImage[y1:y1 + cell_h, x1:x1 + cell_w]
resized = cv2.resize(cell_img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)
# OCR (숫자만 허용)
config = "--psm 10 -c tessedit_char_whitelist=123456789"
text = pytesseract.image_to_string(resized, config=config).strip()
--psm 10 : Page Segmentation Mode를 10 으로 설정 -> 하나의 문자가 있는 작은 이미지 라고 가정하는 인식 모드
-c tessedit_char_whitelist=123456789 : 오직 숫자 (1~9)만 인식하도록 제한 (0 or 영어 알파벳 오탐지를 막기위함)
📌 문제
이렇게 첫 OCR 시도를 통해 셀의 숫자를 읽어 오지만,
아무리 이미지를 전처리하고 크기를 키워도 100% 정확도를 기대할 수는 없었다.
라이브러리의 여러 값들을 바꿔봐도 100%의 성공은 힘들기에 가장 높은 성공률을 보이는 위 방법을 첫번째로 시도하고 실패할 경우 해당 셀에 대해 추가 전처리 후 재시도하는 전략을 세웠다.
✏️ Retry 방식 요약
실패한 셀에대에 아래 방식 적용
- 이미지 2배 -> 3배 로 확대
- 팽창(Dilate) 적용
- Morphology Close 적용
- OCR 재시도
그래도 실패할 경우 해당 위치를 장애물(-1) 처리 해주었다.
# 실패할 경우 Retry
reResized = cv2.resize(cell_img, None, fx=3, fy=3, interpolation=cv2.INTER_LINEAR)
kernel = np.ones((2, 2), np.uint8)
dilated = cv2.dilate(reResized, kernel, iterations=1)
cleaned = cv2.morphologyEx(dilated, cv2.MORPH_CLOSE, kernel, iterations=2)
config_alt = "--psm 8 -c tessedit_char_whitelist=123456789"
text_retry = pytesseract.image_to_string(cleaned, config=config_alt).strip()
2-4. 수행한 결과 데이터 저장
# 수행한 결과 데이터를 차원 배열에 저장
grid[row][col] = int(text)
3. 숫자 조합 탐색
ocr을 통해 숫자 2차원 배열(Grid)을 읽어온 후,
합이 10이 되는 직사각형 영역을 찾아 하나씩 제거하는 과정을 수행해서 목표는 최대한 많은 셀을 0으로 만들어 게임을 풀어나가는 것이다.
✏️ 전체 흐름 요약
- 현재 Grid 에서 합이 10인 직사각형을 탐색
- 가장 넓은(많은 셀을 포함한) 직사각형 선택
- 선택한 직사각형 영역을 0으로 변경
- 위 과정을 반복 (더이상 합이 10인 직사각형이 없을 때 종료)
✅ 숫자 변환에 실패한 셀(-1)이 포함되있을 경우에는 해당 영역을 건너뛴다
4. 마우스 드래그
탐색한 경로를 자동으로 마우스 드래그를 해주는 부분으로 게임해서 사과를 자동으로 드래그해주는 부분입니다.
우선 마우스를 자동으로 움직이게 하기위해 pyautogui 라이브러리를 사용했고, 중간에 종료하는 기능을 넣기 위해
키보드 입력 감지 라이브러리 keyboard 를 사용했습니다.
숫자 조합 탐색 처리 결과로 나온 사과의 셀 위치를 바탕으로 드래그 로직을 수행
# 드래그를 수행하는 핵심 부분
def drag_one(x1, y1, x2, y2):
start_x, start_y = get_top_left(x1, y1)
end_x, end_y = get_bottom_right(x2, y2)
pyautogui.moveTo(start_x, start_y)
pyautogui.mouseDown()
time.sleep(DRAG_MOUSE_DOWN_DELAY)
pyautogui.moveTo(end_x, end_y, duration=DRAG_MOUSE_MOVE_TO_DURATION)
pyautogui.mouseUp()
사과게임 메크로 영상
느낀점
이번 프로젝트를 진행하면서 파이썬의 편리함과 특히 라이브러리 적용이 쉬워 마치 블록 조립처럼 빨리 기능구현을 할 수 있었습니다.
OCR 적용 과정에서 100% 인식의 어려움을 체감했고 이 프로젝트에서 가장 많은 시간을 할애했습니다.
다양한 시도를 하면서 완벽한 인식률은 쉽지 않았지만, 그 과정에서 OCR 기술에 대한 이해를 높였고 밤새 코딩에 몰두할 만큼 문제 해결에 재미있는 경험이였습니다.
아직 개선해야할 점이 많다 OCR 인식률을 100%는 힘들겠지만 99%까지 올리고, 경로 탐색 알고리즘 개선, 그리고 병렬처리를 통해 프로젝트를 좀더 고도화 시켜보고 싶다.
'개발' 카테고리의 다른 글
| 반복되는 흐름을 정리하는 디자인 패턴: 템플릿 메서드 패턴 (0) | 2025.05.17 |
|---|---|
| Fixed length format과 getByte()의 예상치 못한 함정 (1) | 2025.03.11 |
| URL Encode 왜 필요해? (1) | 2025.03.08 |