프로젝트/사과게임 매크로 만들기

[python][사과게임 매크로 만들기] 2. 사과게임 매크로 코드 작성, 실행

레까 2025. 4. 5. 02:28

- 개요

사과게임 시작화면의 숫자를 인식해서 numpy 배열로 저장했으니 이제 사과게임 매크로를 만듭시다.

 

- 숫자 합 10 확인 및 제거

사과게임에서는 마우스로 드래그한 영역의 사과들의 숫자합이 10일 때 그 사과들이 지워집니다.

우린 숫자를 numpy 배열에 저장했으니 numpy 배열에서 특정 범위의 합이 10인 경우를 찾으면 됩니다.

그 다음 이 특정 범위의 위치를 내 모니터의 좌표로 변환하여 마우스를 드래그하면 됩니다.

 

합이 10이되는 영역을 찾는 알고리즘은 여러가지가 있지만

저는 왼쪽 위부터 오른쪽으로 한 줄씩 합이 10인 영역을 찾겠습니다.

"""
합이 10인 위치를 제거하는 기능을 10번 시행하기 위해 _로 열번 돌립니다.
특정 영역이 0이 되면서 없어지면 합이 0이 될 수 있는 영역이 새로 생길 수 있으므로
여러 번 돌려야 합니다.
"""
for _ in range(10):
	"""
    num2d의 크기는 10x17로
    yi, xi를 각각 for문으로 돌려 num2d의 모든 요소에 접근합니다.
    """
    for yi in range(10):
        for xi in range(17):
        	"""
            w, h는 width, height를 의미합니다.
            xi가 10이라면 최대값이 16이므로 마우스는 좌우폭으로 +6까지 움직이면 됩니다.
            (이렇게 제한을 안하면 numpy 배열의 크기를 넘어버려서 오류가 남)
            """
            w_max, h_max = 17 - xi, 10 - yi
            for h in range(h_max):
                for w in range(w_max):
                	"""
                    yi부터 yi+h까지, xi부터 xi+w까지의 범위의 합이 10이면
                    사과를 없앴다고 치고 해당 영역의 위치를 0으로 만듬
                    """
                    if np.sum(num2d[yi: yi + h+1, xi: xi + w+1]) == 10:
                        num2d[yi: yi + h+1, xi: xi + w+1] = 0

 

numpy 배열의 합이 10인 영역을 0으로 만드는 코드를 짯고

이제 마우스를 드래그해서 사과게임 화면의 사과를 지워봅시다.

 

- 마우스로 숫자 합 10 확인 및 제거

numpy 배열에서 합이 10되는 위치를 찾았고, 실제 내 모니터에서 이 좌표값을 알아야 합니다.

그래야 파이썬으로 마우스를 제어해서 해당 영역 드래그를 할 수 있습니다.

import pyautogui
import keyboard

"""
xloc, yloc의 평균 간격으로 사과 간 좌표의 간격을 구합니다.
dx는 x방향의 사과좌표 간격
dy는 y방향의 사과좌표 간격
"""
xloc = df['left'].unique()
yloc = df['top'].unique()

dx = np.mean(np.diff(xloc))
dy = np.mean(np.diff(yloc))

"""
좌표를 가져오는 이미지는 사과 안의 숫자 이미지입니다.
그래서 이 좌표의 영역으로 마우스 드래그를 하면 
드래그 영역에 사과가 완전히 속하지 않습니다.
그래서 사과가 드래그 영역에 들어올 수 있도록
-0.25 * dy, -0.25 * dy만큼 좌표 이동을 해야합니다.
"""
xloc_mouse = xloc - 0.25 * dx
yloc_mouse = yloc - 0.25 * dy

def mouse_move(xi, yi, w, h, duration):
	# 드래그 영역의 왼쪽, 위 좌표로 마우스 이동
    pyautogui.moveTo( xloc_mouse[xi], yloc_mouse[yi], duration=duration)
    # 마우스 누르는 상태
    pyautogui.mouseDown()
    # 드래그 영역의 오른쪽, 아래 좌표로 마우스 이동
    pyautogui.moveTo(xloc_mouse[xi] + (w+1)*dx, yloc_mouse[yi] + (h+1)*dy,  duration=duration)
    # 위의 좌표 이동과 똑같지만 이렇게 반복을 안하면 드래그가 제대로 안됩니다.
    # 어떻게 되는지 궁금하면 지우고 테스트해보세요.
    pyautogui.moveTo(xloc_mouse[xi] + (w+1)*dx, yloc_mouse[yi] + (h+1)*dy,  duration=duration)
    # 마우스 누르는 상태 해제
    pyautogui.mouseUp()

duration = 0.01
for _ in range(10):
    for yi in range(10):
        for xi in range(17):
        	# q를 꾹 누르고 있으면 취소되게 만듬
            if keyboard.is_pressed("q"):  
                break
            w_max, h_max = 17 - xi, 10 - yi
            for h in range(h_max):
                for w in range(w_max):
                    if np.sum(num2d[yi: yi + h+1, xi: xi + w+1]) == 10:
                        num2d[yi: yi + h+1, xi: xi + w+1] = 0
                        mouse_move(xi, yi, w, h, duration)

 

코드의 로직은 간단합니다.

아까 numpy 배열에서 합이 10될 때 xi, yi, w, h를 mouse_move() 함수에 넣습니다.

duration은 단어 의미 그대로 마우스 행동의 지속시간을 설정합니다.

 

- 실행하기

저번 코드도 같이 첨부합니다.

사과게임을 시작하고 이 코드를 실행하면 매크로가 동작합니다.

q를 꾹 누르고 있으면 꺼집니다.

#전체코드
import pyautogui
import pandas as pd
df = pd.DataFrame(columns=['num', 'top', 'left', 'width', 'height'])
img_path = './imgs/'

ll = 0
for i in range(1, 9+1):
    positions = pyautogui.locateAllOnScreen(f'{img_path}{i}.png', confidence=0.90)
    for pos in positions:
        new_row = {'num':i, 'top':pos.top, 'left':pos.left, 
                   'width':pos.width, 'height':pos.height}
        df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)
        ll += 1
        
import os
os.environ["LOKY_MAX_CPU_COUNT"] = "4"
os.environ["OMP_NUM_THREADS"] = "2"

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=170)
kmeans.fit(df[['top', 'left']])
df['cluster'] = kmeans.labels_
df = df.groupby(by='cluster').mean()

kmeans = KMeans(n_clusters=17)
kmeans.fit(df[['left']])
df['x_cls'] = kmeans.labels_

kmeans = KMeans(n_clusters=10)
kmeans.fit(df[['top']])
df['y_cls'] = kmeans.labels_

for xi in range(17):
    mean_value = df.loc[df['x_cls'] == xi, 'left'].mean()  # 먼저 평균값 계산
    df.loc[df['x_cls'] == xi, 'left'] = mean_value  # loc를 사용하여 값 업데이트
for xi in range(10):
    mean_value = df.loc[df['y_cls'] == xi, 'top'].mean()  # 먼저 평균값 계산
    df.loc[df['y_cls'] == xi, 'top'] = mean_value  # loc를 사용하여 값 업데이트

import numpy as np
df = df.sort_values(by=['top', 'left']).reset_index(drop=True)
num1d = np.array(df['num'].values.tolist(), dtype=int)
num2d = num1d.reshape(10, 17)

import pyautogui
import keyboard
xloc = df['left'].unique()
yloc = df['top'].unique()

dx = np.mean(np.diff(xloc))
dy = np.mean(np.diff(yloc))
xloc_mouse = xloc - 0.25 * dx
yloc_mouse = yloc - 0.25 * dy

def mouse_move(xi, yi, w, h, duration):
    pyautogui.moveTo( xloc_mouse[xi], yloc_mouse[yi], duration=duration)
    pyautogui.mouseDown()
    pyautogui.moveTo(xloc_mouse[xi] + (w+1)*dx, yloc_mouse[yi] + (h+1)*dy,  duration=duration)
    pyautogui.moveTo(xloc_mouse[xi] + (w+1)*dx, yloc_mouse[yi] + (h+1)*dy,  duration=duration)
    pyautogui.mouseUp()

duration = 0.01
for _ in range(10):
    for yi in range(10):
        for xi in range(17):
            if keyboard.is_pressed("q"):  
                break
            w_max, h_max = 17 - xi, 10 - yi
            for h in range(h_max):
                for w in range(w_max):
                    if np.sum(num2d[yi: yi + h+1, xi: xi + w+1]) == 10:
                        num2d[yi: yi + h+1, xi: xi + w+1] = 0
                        mouse_move(xi, yi, w, h, duration)