├── cards ├── __init__.py ├── kind.py ├── attack.py ├── arts_mode.py ├── quick_mode.py ├── positioning.py ├── Cards.py ├── Combat.py └── default_mode.py ├── extra ├── __init__.py ├── Servant.py ├── matching.py ├── extra.py └── find_servant.py ├── util ├── __init__.py ├── log.py ├── clean.py ├── anti.py ├── ats.py ├── filter.py ├── cvs.py └── split.py ├── interface ├── __init__.py ├── Support.py ├── Battle.py ├── scene.py ├── Basic.py ├── Finish.py ├── Loading.py └── Major.py ├── phantasms ├── __init__.py ├── Phantasms.py ├── turns.py └── detection.py ├── recovery ├── __init__.py ├── panel.py ├── recover.py └── Recovery.py ├── sh.png ├── assets ├── scene │ ├── bond.png │ ├── win.png │ ├── win2.png │ ├── apple.png │ ├── apple2.png │ ├── attack.png │ ├── bond2.png │ ├── decide.png │ ├── loading.png │ ├── master.png │ ├── master2.png │ ├── mistake.png │ ├── standby.png │ ├── support.png │ ├── fighting.png │ ├── checkpoint.png │ ├── checkpoint1.png │ ├── checkpoint2.png │ └── checkpoint3.png └── battle │ ├── arts.png │ ├── buster.png │ ├── quick.png │ ├── resistance.png │ └── restraint.png ├── .gitignore ├── opencvtest.py ├── test.py ├── tesseracttest.py ├── config.py ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── README.md ├── alogrithms ├── card.h └── sort1.cpp ├── main.py └── log.txt /cards/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /interface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /phantasms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /recovery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/sh.png -------------------------------------------------------------------------------- /assets/scene/bond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/bond.png -------------------------------------------------------------------------------- /assets/scene/win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/win.png -------------------------------------------------------------------------------- /assets/scene/win2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/win2.png -------------------------------------------------------------------------------- /assets/battle/arts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/battle/arts.png -------------------------------------------------------------------------------- /assets/battle/buster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/battle/buster.png -------------------------------------------------------------------------------- /assets/battle/quick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/battle/quick.png -------------------------------------------------------------------------------- /assets/scene/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/apple.png -------------------------------------------------------------------------------- /assets/scene/apple2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/apple2.png -------------------------------------------------------------------------------- /assets/scene/attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/attack.png -------------------------------------------------------------------------------- /assets/scene/bond2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/bond2.png -------------------------------------------------------------------------------- /assets/scene/decide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/decide.png -------------------------------------------------------------------------------- /assets/scene/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/loading.png -------------------------------------------------------------------------------- /assets/scene/master.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/master.png -------------------------------------------------------------------------------- /assets/scene/master2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/master2.png -------------------------------------------------------------------------------- /assets/scene/mistake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/mistake.png -------------------------------------------------------------------------------- /assets/scene/standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/standby.png -------------------------------------------------------------------------------- /assets/scene/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/support.png -------------------------------------------------------------------------------- /assets/scene/fighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/fighting.png -------------------------------------------------------------------------------- /assets/battle/resistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/battle/resistance.png -------------------------------------------------------------------------------- /assets/battle/restraint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/battle/restraint.png -------------------------------------------------------------------------------- /assets/scene/checkpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/checkpoint.png -------------------------------------------------------------------------------- /assets/scene/checkpoint1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/checkpoint1.png -------------------------------------------------------------------------------- /assets/scene/checkpoint2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/checkpoint2.png -------------------------------------------------------------------------------- /assets/scene/checkpoint3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meowcolm024/FGO-One/HEAD/assets/scene/checkpoint3.png -------------------------------------------------------------------------------- /util/log.py: -------------------------------------------------------------------------------- 1 | def output_log(out): 2 | f = open('log.txt', 'a') 3 | st = out + ' \r' 4 | f.write(st) 5 | f.close() 6 | -------------------------------------------------------------------------------- /recovery/panel.py: -------------------------------------------------------------------------------- 1 | """initialize parameters for recovery interface""" 2 | # the recovery interface 3 | apple = "apple" 4 | decide = "decide" 5 | -------------------------------------------------------------------------------- /extra/Servant.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | 4 | class Servant: 5 | def __init__(self): 6 | self.order = [] 7 | self.count = [] 8 | -------------------------------------------------------------------------------- /phantasms/Phantasms.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | 4 | class Phantasms: 5 | def __init__(self): 6 | self.servant = [] 7 | self.crd = [] 8 | self.ready = 0 9 | -------------------------------------------------------------------------------- /util/clean.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def clean_tmp(): 5 | for i in range(5): 6 | filename = f"./temp/servant{i}.png" 7 | if os.path.exists(filename): 8 | os.remove(filename) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /__pycache__ 4 | /a.out.dSYM 5 | /util/__pycache__ 6 | /recovery/__pycache__ 7 | /phantasms/__pycache__ 8 | /interface/__pycache__ 9 | /extra/__pycache__ 10 | /cards/__pycache__ -------------------------------------------------------------------------------- /cards/kind.py: -------------------------------------------------------------------------------- 1 | """initialize type of cards and marks""" 2 | 3 | # type of the cards 4 | quick_card = "quick" 5 | arts_card = "arts" 6 | buster_card = "buster" 7 | 8 | # type of marks 9 | restraint_mark = "restraint" 10 | resistance_mark = "resistance" 11 | -------------------------------------------------------------------------------- /opencvtest.py: -------------------------------------------------------------------------------- 1 | from util.cvs import check 2 | 3 | # template path 4 | tmpl = "assets/battle/arts.png" 5 | 6 | # tested image path 7 | img = "assets/test/t2.jpeg" 8 | 9 | # the threshold 10 | threshold = 0.9 11 | 12 | # match template 13 | result = check(img, tmpl, threshold) 14 | print(result) 15 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from cards.Combat import Combat 2 | from util.clean import clean_tmp 3 | from util.split import split 4 | from interface.Major import Major 5 | 6 | clean_tmp() 7 | 8 | s = Major() 9 | out = s.recognize() 10 | print(out) 11 | """ 12 | split() 13 | a = Combat() 14 | for crd in a.card_crd: 15 | print(crd) 16 | """ 17 | -------------------------------------------------------------------------------- /cards/attack.py: -------------------------------------------------------------------------------- 1 | """initialize attack index""" 2 | 3 | # mark index 4 | restraint = 2.0 5 | resistance = 0.5 6 | 7 | # order index 8 | zero = 1.0 9 | first = 1.2 10 | second = 1.4 11 | 12 | # type index 13 | quick = 0.8 14 | arts = 1.0 15 | buster = 1.5 16 | ex_card = 2.0 17 | 18 | # special index 19 | exbst = 0.5 20 | same_color = 1.5 21 | red_ex = 1.0 22 | all_red = 2.2 23 | -------------------------------------------------------------------------------- /tesseracttest.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from pytesseract import image_to_string 3 | from config import screenshot_path 4 | 5 | 6 | img = Image.open(screenshot_path) 7 | img_size = img.size 8 | 9 | gap = (img_size[0] - 1920) / 2 10 | left = gap + 1280 11 | right = left + 100 12 | bottom = img_size[1] / 20 13 | 14 | region = img.crop((left, 0, right, bottom)) 15 | text = image_to_string(region, config='--psm 7 -c tessedit_char_whitelist=/1234') 16 | print(text) 17 | -------------------------------------------------------------------------------- /extra/matching.py: -------------------------------------------------------------------------------- 1 | from cards.positioning import positioning 2 | from extra.find_servant import get_servant 3 | 4 | 5 | def matching(): 6 | 7 | cards = positioning() 8 | servants = get_servant() 9 | 10 | counts = len(servants) 11 | 12 | for i in range(counts): 13 | sv_cards = servants[i].count 14 | for j in sv_cards: 15 | cards[j].servant = servants[i].order 16 | if len(sv_cards) >= 3: 17 | cards[j].chain = 1 18 | 19 | return cards 20 | -------------------------------------------------------------------------------- /phantasms/turns.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from pytesseract import image_to_string 3 | from config import screenshot_path 4 | 5 | 6 | def get_turns(): 7 | img = Image.open(screenshot_path) 8 | img_size = img.size 9 | 10 | gap = (img_size[0] - 1920) / 2 11 | left = gap + 1280 12 | right = left + 100 13 | bottom = img_size[1] / 20 14 | 15 | region = img.crop((left, 0, right, bottom)) 16 | text = image_to_string(region, config='--psm 7 -c tessedit_char_whitelist=/1234') 17 | print(text) 18 | 19 | return text 20 | -------------------------------------------------------------------------------- /interface/Support.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | import random 4 | from PIL import Image 5 | from util.anti import basic_tap 6 | 7 | 8 | class Support: 9 | def __init__(self): 10 | self.scene = [] 11 | self.crd = [] 12 | 13 | def select_support(self): 14 | im = Image.open(f"./{self.scene}") 15 | im_size = im.size 16 | 17 | x = random.randrange(-300, 300) + (im_size[0] / 2) 18 | y = random.randrange(-200, 200) + (im_size[1] / 2) 19 | 20 | self.crd = [x, y] 21 | basic_tap(self.crd[0], self.crd[1]) 22 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | """initialize paths, checkpoints etc""" 2 | 3 | # initialize screenshot path (default: "sh.png", for testing e.g. "assets/test/t1.jpeg") 4 | screenshot_path = "sh.png" 5 | 6 | # initialize checkpoint template file, located in "./assets/scene/" (default: "checkpoint") 7 | checkpoint = "checkpoint3" 8 | 9 | # initialize battle mode ("default_mode", "quick_mode" or "arts_mode") 10 | script_mode = "arts_mode" 11 | 12 | # initialize how many "Golden Apples" would be used (default: 0) 13 | recovery_times = 0 14 | 15 | # initialize how many times will be played ("inf" for infinity) 16 | run_time = 1 17 | -------------------------------------------------------------------------------- /interface/Battle.py: -------------------------------------------------------------------------------- 1 | from cards.Combat import Combat 2 | import time 3 | from util.anti import tap_card 4 | import random 5 | from util.log import output_log 6 | 7 | 8 | class Battle(Combat): 9 | def get_cards(self): 10 | rank = [self.card_crd[0], self.card_crd[1], self.card_crd[2]] 11 | print(rank) 12 | 13 | out = "[BATTLE] Cards: " + str(self.card_crd[0]) + " " + str(self.card_crd[1]) + " " + str(self.card_crd[2]) 14 | output_log(out) 15 | 16 | for i in range(3): 17 | tap_card(rank[i][0], rank[i][1]) 18 | time.sleep(random.uniform(0.1, 0.2)) 19 | time.sleep(3.5) 20 | -------------------------------------------------------------------------------- /cards/arts_mode.py: -------------------------------------------------------------------------------- 1 | from cards.kind import arts_card 2 | from cards.default_mode import arrange 3 | 4 | 5 | def get_arts(cards): 6 | 7 | arts = [] 8 | counts = [] 9 | 10 | for i in range(5): 11 | if cards[i].type == arts_card: 12 | arts.append(cards[i]) 13 | counts.append(i) 14 | 15 | if len(arts) <= 2 or len(arts) == 5: 16 | rank = arrange(cards) 17 | return rank 18 | elif len(arts) == 3: 19 | rank = [arts[0].crd, arts[1].crd, arts[2].crd] 20 | print("Order of the cards: ", counts) 21 | return rank 22 | else: 23 | rank = arrange(arts) 24 | return rank 25 | 26 | -------------------------------------------------------------------------------- /recovery/recover.py: -------------------------------------------------------------------------------- 1 | from config import screenshot_path 2 | from recovery.Recovery import Recovery 3 | from recovery.panel import * 4 | from util.cvs import check 5 | 6 | 7 | def recover_ap(): 8 | # initialize parameters 9 | sh = screenshot_path 10 | threshold = 0.9 11 | recover_scenes = [apple, decide] 12 | for recover_scene in recover_scenes: 13 | recover_path = f"./assets/scene/{recover_scene}.png" 14 | if check(sh, recover_path, threshold) == 1: 15 | recovery_interface = Recovery() 16 | recovery_interface.scene = sh 17 | recovery_interface.recover() 18 | return recovery_interface.done 19 | -------------------------------------------------------------------------------- /interface/scene.py: -------------------------------------------------------------------------------- 1 | """initialize different scenes of interfaces""" 2 | from config import checkpoint 3 | 4 | # basic interface, which requires tapping ONE button 5 | basic_scenes = [checkpoint, "standby", "attack", "win2", "mistake"] 6 | 7 | # the support interface, which requires random tapping 8 | support_scene = "support" 9 | 10 | # the battle interface, which needs to select cards 11 | battle_scenes = ["quick", "arts", "buster"] 12 | 13 | # the post-battle interface, which needs to tap on special location 14 | finish_scenes = ["bond2", "master2"] 15 | 16 | # the loading/fighting interface, random tap and swipe to avoid being ban 17 | loading_scenes = ["loading", "fighting"] 18 | -------------------------------------------------------------------------------- /cards/quick_mode.py: -------------------------------------------------------------------------------- 1 | from cards.kind import quick_card 2 | from cards.default_mode import arrange 3 | 4 | 5 | def get_quick(cards): 6 | 7 | quicks = [] 8 | counts = [] 9 | 10 | for i in range(5): 11 | if cards[i].type == quick_card: 12 | quicks.append(cards[i]) 13 | counts.append(i) 14 | 15 | if len(quicks) <= 2 or len(quicks) == 5: 16 | rank = arrange(cards) 17 | return rank 18 | elif len(quicks) == 3: 19 | rank = [quicks[0].crd, quicks[1].crd, quicks[2].crd] 20 | print("Order of the cards: ", counts) 21 | return rank 22 | else: 23 | rank = arrange(quicks) 24 | return rank 25 | 26 | -------------------------------------------------------------------------------- /interface/Basic.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | from util.filter import get_filtered 4 | from util.anti import basic_tap 5 | from util.cvs import analyze 6 | import cv2 7 | 8 | 9 | class Basic: 10 | def __init__(self): 11 | self.scene = [] 12 | self.sh = [] 13 | self.btn_crd = [0, 0] 14 | self.end = [] 15 | 16 | def get_button(self): 17 | position = get_filtered(self.sh, self.scene, 0.9) 18 | self.btn_crd[0] = position[0][0] 19 | self.btn_crd[1] = position[0][1] 20 | basic_tap(self.btn_crd[0], self.btn_crd[1]) 21 | 22 | if analyze(self.sh, cv2.imread("./assets/scene/win2.png", 0), 0.9) == 1: 23 | self.end = "end" 24 | -------------------------------------------------------------------------------- /util/anti.py: -------------------------------------------------------------------------------- 1 | # avoid the ID being banned 2 | from util.ats import * 3 | import random 4 | 5 | 6 | def long_tap(x, y): # random length tap 7 | delay = random.randrange(5, 100) 8 | x0 = x + random.randrange(-10, 10) 9 | y0 = y + random.randrange(-10, 10) 10 | swipe(x, y, x0, y0, delay) 11 | 12 | 13 | def tap_card(x, y): # tap on random location of the card 14 | x0 = x + random.uniform(0, 75) 15 | y0 = y + random.uniform(-90, 0) 16 | x0 = int(x0) 17 | y0 = int(y0) 18 | tap(x0, y0) 19 | 20 | 21 | def basic_tap(x, y): # tap on random location of the button 22 | x0 = x + random.uniform(-10, 10) 23 | y0 = y + random.uniform(-10, 10) 24 | x0 = int(x0) 25 | y0 = int(y0) 26 | tap(x0, y0) 27 | -------------------------------------------------------------------------------- /util/ats.py: -------------------------------------------------------------------------------- 1 | # Android tap and swipe control 2 | 3 | import os 4 | from config import screenshot_path 5 | 6 | 7 | def tap(x0, y0): 8 | cmdTap = 'adb shell input tap {x1} {y1}'.format( 9 | x1=x0, 10 | y1=y0 11 | ) 12 | print(cmdTap) 13 | os.system(cmdTap) 14 | 15 | 16 | def swipe(x0, y0, x1, y1, delay0): 17 | cmdSwipe = 'adb shell input swipe {x2} {y2} {x3} {y3} {delay1}'.format( 18 | x2=x0, 19 | y2=y0, 20 | x3=x1, 21 | y3=y1, 22 | delay1=delay0 23 | ) 24 | print(cmdSwipe) 25 | os.system(cmdSwipe) 26 | 27 | 28 | def screenshot(): 29 | os.system('adb shell screencap -p /sdcard/sh.png') 30 | os.system('adb pull /sdcard/sh.png .') 31 | return screenshot_path 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /util/filter.py: -------------------------------------------------------------------------------- 1 | from util.cvs import location, position 2 | 3 | 4 | def filter_crd(sh, tmp, thd): # get the coordinates of cards/marks 5 | ary = location(sh, tmp, thd) 6 | ary.sort() 7 | for i in range(len(ary)): 8 | for p in range(1, len(ary) - i): 9 | for k in range(-5, 5): 10 | for l in range(-5, 5): 11 | if ((ary[i][0] + k) == ary[i + p][0]) and ((ary[i][1] + l) == ary[i + p][1]): 12 | ary[i + p] = [0, 0] 13 | while ary.count([0, 0]) >= 1: 14 | ary.remove([0, 0]) 15 | return ary 16 | 17 | 18 | def get_filtered(sh, tmp, thd): 19 | ary = position(sh, tmp, thd) 20 | ary.sort() 21 | for i in range(len(ary)): 22 | for p in range(1, len(ary) - i): 23 | for k in range(-5, 5): 24 | for l in range(-5, 5): 25 | if ((ary[i][0] + k) == ary[i + p][0]) and ((ary[i][1] + l) == ary[i + p][1]): 26 | ary[i + p] = [0, 0] 27 | while ary.count([0, 0]) >= 1: 28 | ary.remove([0, 0]) 29 | return ary 30 | -------------------------------------------------------------------------------- /interface/Finish.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | from util.cvs import check 4 | from util.log import output_log 5 | from interface.scene import finish_scenes 6 | from PIL import Image 7 | import random 8 | from util.anti import basic_tap 9 | 10 | 11 | class Finish: 12 | def __init__(self): 13 | self.scene = [] 14 | self.crd = [] 15 | 16 | def pass_finish(self): 17 | for finish_scene in finish_scenes: 18 | scene_path = f"./assets/scene/{finish_scene}.png" 19 | if check(self.scene, scene_path, 0.9) == 1: 20 | im = Image.open(f"./{self.scene}") 21 | im_size = im.size 22 | x0 = im_size[0] / 4 23 | y0 = im_size[0] / 4 24 | x = random.randrange(-x0, x0) + (im_size[0] / 2) 25 | y = random.randrange(-y0, y0) + (im_size[1] / 2) 26 | 27 | self.crd = [x, y] 28 | out = "[FINISH] Checked: " + str(finish_scene) + " " + str(x) + " " + str(y) 29 | print(out) 30 | output_log(out) 31 | basic_tap(self.crd[0], self.crd[1]) 32 | -------------------------------------------------------------------------------- /util/cvs.py: -------------------------------------------------------------------------------- 1 | # combine some common used function from cv2 2 | 3 | import cv2 4 | import numpy as np 5 | 6 | 7 | def location(sh, tmp, thd): 8 | img = cv2.imread(sh, 0) 9 | template = cv2.imread(tmp, 0) 10 | 11 | res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) 12 | threshold = thd 13 | pos = [] 14 | 15 | loc = np.where(res >= threshold) 16 | for pt in zip(*loc[::-1]): 17 | pos.append(pt) 18 | 19 | return pos 20 | 21 | 22 | def position(sh, tmp, thd): 23 | res = cv2.matchTemplate(sh, tmp, cv2.TM_CCOEFF_NORMED) 24 | pos = [] 25 | loc = np.where(res >= thd) 26 | for pt in zip(*loc[::-1]): 27 | pos.append(pt) 28 | return pos 29 | 30 | 31 | def check(sh, tmp, thd): 32 | img = cv2.imread(sh, 0) 33 | template = cv2.imread(tmp, 0) 34 | 35 | res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) 36 | threshold = thd 37 | 38 | if (res >= threshold).any(): 39 | return 1 40 | 41 | 42 | def analyze(sh, tmp, thd): 43 | res = cv2.matchTemplate(sh, tmp, cv2.TM_CCOEFF_NORMED) 44 | if (res >= thd).any(): 45 | return 1 46 | -------------------------------------------------------------------------------- /extra/extra.py: -------------------------------------------------------------------------------- 1 | """detect servants""" 2 | from util.cvs import check 3 | from extra.Servant import * 4 | 5 | 6 | def init(): 7 | a = Servant() 8 | b = Servant() 9 | c = Servant() 10 | 11 | global servants 12 | servants = [a, b, c] 13 | 14 | 15 | def init_extra(): 16 | exists = [i for i in range(5)] 17 | # filter same servants 18 | for exist in exists: 19 | tmp = f"./temp/s{exist}.png" 20 | for exis in exists: 21 | if exist != exis: 22 | pic = f"./temp/{exis}.png" 23 | if check(pic, tmp, 0.9) == 1: 24 | exists.remove(exis) 25 | 26 | counts = [i for i in range(len(exists))] 27 | for count in counts: 28 | servants[count].order = exists[count] 29 | # mark servants 30 | for coun in counts: 31 | stmp = f"./temp/s{servants[coun].order}.png" 32 | for k in range(5): 33 | spic = f"./temp/{k}.png" 34 | if check(spic, stmp, 0.9) == 1: 35 | servants[coun].count.append(k) 36 | 37 | 38 | def get_extra(): 39 | init() 40 | init_extra() 41 | return servants 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Malcolm Law 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /recovery/Recovery.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | from util.filter import filter_crd 4 | from util.cvs import check 5 | from recovery.panel import * 6 | from util.anti import basic_tap 7 | 8 | 9 | class Recovery: 10 | def __init__(self): 11 | self.scene = [] 12 | self.crd = [0, 0] 13 | self.done = 0 14 | 15 | def recover(self): 16 | apple_path = f"./assets/scene/{apple}.png" 17 | decide_path = f"./assets/scene/{decide}.png" 18 | if check(self.scene, apple_path, 0.9) == 1: 19 | position = filter_crd(self.scene, apple_path, 0.9) 20 | self.crd[0] = position[0][0] 21 | self.crd[1] = position[0][1] 22 | print("Apple: ", self.crd[0], self.crd[1]) 23 | basic_tap(self.crd[0], self.crd[1]) 24 | self.done = 1 25 | if check(self.scene, decide_path, 0.9) == 1: 26 | position = filter_crd(self.scene, decide_path, 0.9) 27 | self.crd[0] = position[0][0] 28 | self.crd[1] = position[0][1] 29 | print("Decide: ", self.crd[0], self.crd[1]) 30 | basic_tap(self.crd[0], self.crd[1]) 31 | self.done = 2 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FGO-One Script 2 | 3 | ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Meowcolm024/FGO-One?include_prereleases) 4 | ![GitHub issues](https://img.shields.io/github/issues/Meowcolm024/FGO-One) 5 | ![GitHub pull requests](https://img.shields.io/github/issues-pr/Meowcolm024/FGO-One) 6 | ![GitHub](https://img.shields.io/github/license/Meowcolm024/FGO-One) 7 | 8 | ## Notice 9 | 10 | **由于国服已更改指令卡卡面,需要使用现在的界面的卡面重新制作template** 11 | 12 | **Besides, this project no longer maintains** 13 | 14 | 与FGO脚本有关的项目:**[FGO-Automata](https://github.com/Meowcolm024/FGO-Automata)** (脚本+API) 15 | 16 | ## About 17 | 18 | _A Redesigned FGO Script_ 19 | 20 | For usages, go to [Wiki](https://github.com/Meowcolm024/FGO-One/wiki) 21 | Showcase video on [Bilibili](https://www.bilibili.com/video/av35291507) 22 | The main thinking of this script on [Jianshu](https://www.jianshu.com/p/1b2ca5454c73) 23 | 关于如何配置脚本,请查看[Wiki](https://github.com/Meowcolm024/FGO-One/wiki) 24 | 25 | ## Features 26 | 27 | 1. Auto-rolling, including selecting _Checkpoint_, _Support Servants_ and completing the battle. 28 | 2. Multiple modes supported, and _Noble Phantasms_ is supported. 29 | 3. AP auto recovery when out of AP if is set. 30 | -------------------------------------------------------------------------------- /alogrithms/card.h: -------------------------------------------------------------------------------- 1 | class Card 2 | { 3 | public: 4 | int mark; // 0-normal 1-restraint 2-resistance 5 | int type; // 0-quick 1-arts 2-buster 6 | int priority; 7 | bool buster; 8 | float atk; 9 | 10 | void get_atk() 11 | { 12 | // setting basic card type 13 | switch(type) 14 | { 15 | case 0: atk = 0.8; 16 | break; 17 | case 1: atk = 1.0; 18 | break; 19 | case 2: atk = 1.5; 20 | break; 21 | }; 22 | // setting priority attack gain 23 | switch(priority) 24 | { 25 | case 1: atk = atk * 1.2; 26 | break; 27 | case 2: atk = atk * 1.4; 28 | break; 29 | }; 30 | // setting buster gain 31 | if(buster == 1) 32 | atk += 0.5; 33 | // setting restraint and resistance 34 | switch(mark) 35 | { 36 | case 1: atk = atk * 2; 37 | break; 38 | case 2: atk = atk * 0.5; 39 | break; 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /phantasms/detection.py: -------------------------------------------------------------------------------- 1 | import os 2 | from phantasms.Phantasms import * 3 | from util.cvs import check 4 | from PIL import Image 5 | 6 | 7 | def init(): 8 | a = Phantasms() 9 | b = Phantasms() 10 | c = Phantasms() 11 | 12 | global nobles 13 | nobles = [a, b, c] 14 | 15 | 16 | def match_servants(): 17 | servants = [] 18 | for i in range(5): 19 | if os.path.exists(f"./temp/servant{i}.png") == 1: 20 | servants.append(i) 21 | 22 | for j in range(len(servants)): 23 | nobles[j].servant = servants[j] 24 | 25 | img = Image.open("./temp/np.png") 26 | img_size = img.size 27 | gap = (img_size[0] - 1920) / 2 28 | length = 384 # 1920/5 29 | exactx = 210 30 | orgx = gap + length + exactx 31 | height = img_size[1] 32 | exacty = height * 2 / 3 33 | 34 | for servant in servants: 35 | templ = f"./temp/servant{servant}.png" 36 | for i in range(3): 37 | img = f"./temp/np{i}.png" 38 | if check(img, templ, 0.68) == 1: 39 | x = orgx + i * length 40 | y = exacty 41 | nobles[i].crd = [x, y] 42 | 43 | 44 | def match_np(): 45 | init() 46 | match_servants() 47 | 48 | for i in range(len(nobles)): 49 | print("npcrd: ", nobles[i].crd) 50 | 51 | return nobles 52 | -------------------------------------------------------------------------------- /cards/positioning.py: -------------------------------------------------------------------------------- 1 | from cards.Cards import * 2 | from cards.kind import * 3 | from config import screenshot_path 4 | from util.filter import * 5 | from util.cvs import * 6 | from PIL import Image 7 | 8 | 9 | def init(): 10 | 11 | a = Card() 12 | b = Card() 13 | c = Card() 14 | d = Card() 15 | e = Card() 16 | global cards 17 | cards = [a, b, c, d, e] 18 | 19 | 20 | def init_cards(): 21 | 22 | im = Image.open(screenshot_path) 23 | img_size = im.size 24 | gap = (img_size[0] - 1920) / 2 25 | 26 | card_types = [quick_card, arts_card, buster_card] 27 | mark_types = [resistance_mark, restraint_mark] 28 | # get the coordinates, mark and type of the card 29 | for i in range(5): 30 | for card_type in card_types: 31 | sh = f"./temp/{i}.png" 32 | tmpl = f"./assets/battle/{card_type}.png" 33 | if check(sh, tmpl, 0.9) == 1: 34 | pos = filter_crd(sh, tmpl, 0.9) 35 | x = pos[0][0] + gap + (i * 384) 36 | y = pos[0][1] + (img_size[1] / 2) - 100 37 | cards[i].crd = [x, y] 38 | cards[i].type = card_type 39 | for mark_type in mark_types: 40 | sh = f"./temp/{i}.png" 41 | tmpl = f"./assets/battle/{mark_type}.png" 42 | if check(sh, tmpl, 0.9) == 1: 43 | cards[i].mark = mark_type 44 | 45 | return cards 46 | 47 | 48 | def positioning(): 49 | init() 50 | result = init_cards() 51 | return result 52 | -------------------------------------------------------------------------------- /cards/Cards.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | from cards.attack import * 3 | from cards.kind import * 4 | 5 | 6 | class Card: 7 | def __init__(self): 8 | self.crd = [] 9 | self.mark = "" 10 | self.type = "" 11 | self.priority = [] 12 | self.atk = 1.0 13 | self.chain = 0 14 | self.servant = [] 15 | self.serial = "" 16 | 17 | def get_atk(self, ex): 18 | # attack from type of the card 19 | if self.type == quick_card: 20 | self.atk = self.atk * quick 21 | elif self.type == arts_card: 22 | self.atk = self.atk * arts 23 | elif self.type == buster_card: 24 | self.atk = self.atk * buster 25 | 26 | # attack from card order 27 | if self.priority == 0: 28 | self.atk = self.atk * zero 29 | elif self.priority == 1: 30 | self.atk = self.atk * first 31 | elif self.priority == 2: 32 | self.atk = self.atk * second 33 | 34 | # attack from buster benefit 35 | if ex == 1: 36 | self.atk = self.atk + exbst 37 | 38 | # attack from extra card 39 | if self.serial == "bc": 40 | self.atk = self.atk + 1.7 41 | if self.serial == "aqc": 42 | self.atk = self.atk + 1.2 43 | if self.serial == "bnc": 44 | self.atk = self.atk + 1 45 | if self.serial == "nc": 46 | self.atk = self.atk + 0.7 47 | 48 | # attack from mark of the card 49 | if self.mark == restraint_mark: 50 | self.atk = self.atk * restraint 51 | elif self.mark == resistance_mark: 52 | self.atk = self.atk * resistance 53 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from interface.Major import * 2 | from recovery.recover import * 3 | from config import recovery_times, run_time 4 | from util.ats import screenshot 5 | from util.clean import clean_tmp 6 | 7 | times = recovery_times 8 | play = run_time 9 | 10 | f = open('log.txt', 'w') 11 | st = '[MAIN] MAX APPLES: ' + \ 12 | str(times) + '\r' + '[MAIN] MAX TIMES: ' + str(play) + '\r' 13 | f.write(st) 14 | f.close() 15 | 16 | clean_tmp() 17 | 18 | while times >= 0: 19 | time.sleep(0.2) 20 | screenshot() 21 | s = Major() 22 | end = s.recognize() 23 | 24 | if play == "inf": 25 | 26 | ap = recover_ap() 27 | if ap == 1 and times != 0: 28 | continue 29 | elif ap == 1 and times == 0: 30 | apa = '[MAIN] Out of AP!' 31 | output_log(apa) 32 | break 33 | elif ap == 2: 34 | times = times - 1 35 | out = '[MAIN] Apples left: ' + str(times) 36 | output_log(out) 37 | time.sleep(2) 38 | 39 | elif play != "inf": 40 | 41 | if end == "end": 42 | play = play - 1 43 | pl = '[MAIN] Times left: ' + str(play) 44 | output_log(pl) 45 | 46 | ap = recover_ap() 47 | if ap == 1 and times != 0: 48 | continue 49 | elif ap == 1 and times == 0: 50 | apb = '[MAIN] Out of AP!' 51 | output_log(apb) 52 | break 53 | elif ap == 2: 54 | times = times - 1 55 | out = '[MAIN] Apples left: ' + str(times) 56 | output_log(out) 57 | time.sleep(2) 58 | 59 | if play == 0: 60 | break 61 | 62 | endnote = '[MAIN] FINISHED' 63 | output_log(endnote) 64 | -------------------------------------------------------------------------------- /cards/Combat.py: -------------------------------------------------------------------------------- 1 | import time 2 | from extra.find_servant import get_servant 3 | from extra.matching import matching 4 | from config import script_mode 5 | from cards.default_mode import arrange 6 | from cards.quick_mode import get_quick 7 | from cards.arts_mode import get_arts 8 | from phantasms.detection import match_np 9 | from phantasms.turns import get_turns 10 | 11 | __metaclass__ = type 12 | 13 | 14 | class Combat: 15 | def __init__(self): 16 | self.cards = matching() 17 | self.servants = get_servant() 18 | self.modes = script_mode 19 | self.turns = get_turns() 20 | self.card_crd = self.get_np() 21 | 22 | def get_np(self): 23 | time.sleep(0.5) 24 | if not self.turns: 25 | return self.get_arrangement() 26 | a = self.turns[0] 27 | b = self.turns[len(self.turns)-1] 28 | npcrd = [] 29 | if float(a) / float(b) == 1: 30 | nps = match_np() 31 | if len(nps) == 0: 32 | return self.get_arrangement() 33 | else: 34 | for i in range(len(nps)): 35 | if nps[i].crd: 36 | npcrd.append(nps[i].crd) 37 | npcrd = npcrd + self.get_arrangement() 38 | return npcrd 39 | else: 40 | return self.get_arrangement() 41 | 42 | def get_arrangement(self): 43 | if self.modes == "default_mode": 44 | rank = arrange(self.cards) 45 | return rank 46 | if self.modes == "quick_mode": 47 | rank = get_quick(self.cards) 48 | return rank 49 | if self.modes == "arts_mode": 50 | rank = get_arts(self.cards) 51 | return rank 52 | 53 | -------------------------------------------------------------------------------- /interface/Loading.py: -------------------------------------------------------------------------------- 1 | __metaclass__ = type 2 | 3 | import time 4 | from PIL import Image 5 | from util.anti import * 6 | from util.log import output_log 7 | 8 | 9 | class Loading: 10 | def __init__(self): 11 | self.scene = [] 12 | self.mark = [] 13 | 14 | def have_fun(self): 15 | im = Image.open(f"./{self.scene}") 16 | im_size = im.size 17 | x = im_size[0] / 4 18 | y = im_size[1] / 4 19 | # create random action list 20 | max_act = [] 21 | if self.mark == 1: 22 | max_act = 2 23 | elif self.mark == 0: 24 | max_act = 5 25 | action = [] 26 | press = "tap" 27 | swipes = "swipe" 28 | for i in range(5): 29 | action.append(press) 30 | action.append(swipes) 31 | random.shuffle(action) 32 | # do random actions 33 | for j in range(random.randrange(1, max_act)): 34 | if action[j] == press: 35 | x0 = 2 * x + random.randrange(-x, x) 36 | y0 = 2 * y + random.randrange(-y, y - 100) 37 | out = "[LOADING] Tap: [" + \ 38 | str(self.mark) + "] " + str(x0) + " " + str(y0) 39 | print(out) 40 | output_log(out) 41 | basic_tap(x0, y0) 42 | if action[j] == swipes: 43 | time.sleep(0.1) 44 | x0 = 2 * x + random.randrange(-x, x) 45 | y0 = 2 * y + random.randrange(-y, y) 46 | x1 = 2 * x + random.randrange(-x / 3, x / 3) 47 | y1 = 2 * y + random.randrange(-y / 3, y / 3) 48 | delay = random.randrange(100, 1000) 49 | out = "[LOADING] Swipe: [" + str(self.mark) + "] " + str(x0) + " " + str(y0) + " " +\ 50 | str(x1) + " " + str(y1) + " " + str(delay) 51 | print(out) 52 | output_log(out) 53 | swipe(x0, y0, x1, y1, random.randrange(50, 500)) 54 | -------------------------------------------------------------------------------- /util/split.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from config import screenshot_path 3 | 4 | 5 | def init(): # split the screenshot in half 6 | im2 = Image.open(f"./{screenshot_path}") 7 | img2_size = im2.size 8 | st_x = (img2_size[1] / 9) * 16 9 | gap = (img2_size[0] - st_x) / 2 10 | left = gap 11 | right = img2_size[0] - gap 12 | top = (img2_size[1] / 2) - 100 13 | bottom = img2_size[1] 14 | # print((left, top, right, bottom)) 15 | region = im2.crop((left, top, right, bottom)) 16 | region2 = im2.crop((left, 0, right, top + 100)) 17 | region.save("./temp/out.png") 18 | region2.save("./temp/np.png") 19 | 20 | 21 | def main(): # split the five order cards 22 | im = Image.open("./temp/out.png") 23 | img_size = im.size 24 | xx = 5 25 | yy = 1 26 | x = img_size[0] // xx 27 | y = img_size[1] // yy 28 | for j in range(yy): 29 | for i in range(xx): 30 | left = i*x 31 | up = y*j 32 | right = left + x 33 | low = up + y 34 | region = im.crop((left, up, right, low)) 35 | region.save(f"./temp/{i}.png") 36 | 37 | 38 | def extra_split(): # get templates of servants 39 | for i in range(5): 40 | im2 = Image.open(f"./temp/{i}.png") 41 | img2_size = im2.size 42 | x = img2_size[0] / 5 43 | y = img2_size[1] / 7 44 | region = im2.crop((2 * x, 2 * y, 3 * x, 3 * y)) 45 | region.save(f"./temp/s{i}.png") 46 | 47 | 48 | def np_split(): 49 | im = Image.open("./temp/np.png") 50 | img_size = im.size 51 | xx = 5 52 | yy = 1 53 | x = img_size[0] // xx 54 | y = img_size[1] // yy 55 | for i in range(1, 4): 56 | left = i * x 57 | up = y / 5 58 | right = left + x 59 | low = y 60 | region = im.crop((left, up, right, low)) 61 | region.save(f"./temp/np{i-1}.png") 62 | 63 | 64 | def split(): 65 | init() 66 | main() 67 | extra_split() 68 | np_split() 69 | -------------------------------------------------------------------------------- /log.txt: -------------------------------------------------------------------------------- 1 | [MAIN] MAX APPLES: 0 [MAIN] MAX TIMES: 1 [BASIC] Buttons: [1800, 986] [LOADING] Swipe: [0] 666.0 282.0 1168.0 608.0 117 [LOADING] Tap: [0] 1590.0 497.0 [LOADING] Swipe: [0] 1500.0 277.0 1011.0 522.0 229 [LOADING] Tap: [0] 624.0 404.0 [LOADING] Swipe: [0] 1378.0 636.0 1214.0 556.0 594 [LOADING] Tap: [0] 670.0 544.0 [LOADING] Swipe: [0] 1319.0 761.0 984.0 565.0 363 [LOADING] Tap: [0] 662.0 578.0 [LOADING] Swipe: [0] 1538.0 468.0 1238.0 581.0 402 [LOADING] Tap: [0] 638.0 544.0 [LOADING] Tap: [0] 584.0 530.0 [LOADING] Tap: [0] 976.0 666.0 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [268.0, 858.0] [1035.0, 856.0] [1812.0, 856.0] [LOADING] Swipe: [1] 583.0 526.0 1059.0 571.0 212 [LOADING] Swipe: [1] 627.0 738.0 907.0 539.0 606 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [1735.0, 852.0] [1410.0, 845.0] [652.0, 858.0] [LOADING] Swipe: [1] 1446.0 304.0 1172.0 486.0 778 [LOADING] Swipe: [1] 1126.0 466.0 922.0 480.0 727 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [652.0, 860.0] [1422.0, 858.0] [1035.0, 858.0] [LOADING] Swipe: [1] 1056.0 671.0 904.0 624.0 305 [LOADING] Swipe: [1] 1603.0 277.0 1042.0 585.0 548 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [978.0, 360.0] [1362.0, 360.0] [268.0, 859.0] [LOADING] Tap: [1] 550.0 513.0 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [268.0, 859.0] [1422.0, 859.0] [1812.0, 857.0] [LOADING] Tap: [1] 871.0 602.0 [LOADING] Swipe: [1] 800.0 693.0 1200.0 493.0 932 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [594.0, 360.0] [575.0, 851.0] [1801.0, 846.0] [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [268.0, 860.0] [1422.0, 860.0] [1812.0, 860.0] [LOADING] Tap: [1] 1381.0 318.0 [LOADING] Swipe: [1] 653.0 428.0 1047.0 622.0 942 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [978.0, 360.0] [268.0, 858.0] [652.0, 859.0] [LOADING] Tap: [1] 1360.0 321.0 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [191.0, 852.0] [652.0, 859.0] [1035.0, 859.0] [LOADING] Swipe: [1] 1560.0 298.0 1219.0 488.0 651 [LOADING] Swipe: [1] 1383.0 703.0 1236.0 581.0 867 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [1362.0, 360.0] [268.0, 859.0] [1422.0, 857.0] [LOADING] Tap: [1] 609.0 493.0 [BASIC] Buttons: [1714, 956] [BATTLE] Cards: [594.0, 360.0] [268.0, 859.0] [1035.0, 860.0] [FINISH] Checked: bond2 1209.0 504.0 [FINISH] Checked: master2 1172.0 306.0 [BASIC] Buttons: [1689, 983] [MAIN] Times left: 0 [MAIN] FINISHED [BASIC] Buttons: [1689, 983] -------------------------------------------------------------------------------- /extra/find_servant.py: -------------------------------------------------------------------------------- 1 | from util.cvs import check 2 | from extra.Servant import * 3 | import os 4 | from PIL import Image 5 | 6 | 7 | def init(): 8 | a = Servant() 9 | b = Servant() 10 | c = Servant() 11 | 12 | global servants 13 | servants = [a, b, c] 14 | 15 | 16 | def check_exists(): 17 | ses = [] 18 | for i in range(5): 19 | if os.path.exists(f"./temp/servant{i}.png") == 1: 20 | ses.append(i) 21 | print("ses:", ses) 22 | return ses 23 | 24 | 25 | def get_exists(): 26 | exists = [i for i in range(5)] 27 | # filter same servants 28 | for exist in exists: 29 | tmp = f"./temp/s{exist}.png" 30 | for exis in exists: 31 | if exist != exis: 32 | pic = f"./temp/{exis}.png" 33 | if check(pic, tmp, 0.9) == 1: 34 | exists.remove(exis) 35 | for i in exists: 36 | im = Image.open(f"./temp/s{i}.png") 37 | im.save(f"./temp/servant{i}.png") 38 | 39 | return exists 40 | 41 | 42 | def decide(): 43 | existed = check_exists() 44 | 45 | if len(existed) == 0: 46 | exists = get_exists() 47 | existed = exists 48 | elif len(existed) == 3: 49 | existed = existed 50 | elif len(existed) != 0: 51 | exists = [i for i in range(5)] 52 | existss = [i for i in range(5)] 53 | for exist in existed: 54 | for exis in exists: 55 | tmp = f"./temp/servant{exist}.png" 56 | pic = f"./temp/{exis}.png" 57 | if check(pic, tmp, 0.9) == 1: 58 | existss.remove(exis) 59 | 60 | for existe in existss: 61 | tmp = f"./temp/s{existe}.png" 62 | for exise in existss: 63 | if existe != exise: 64 | pic = f"./temp/{exise}.png" 65 | if check(pic, tmp, 0.9) == 1: 66 | existss.remove(exise) 67 | 68 | for i in existss: 69 | im = Image.open(f"./temp/s{i}.png") 70 | im.save(f"./temp/servant{i}.png") 71 | 72 | existed = existed + existss 73 | 74 | print(existed) 75 | return existed 76 | 77 | 78 | def init_extra(): 79 | exists = decide() 80 | counts = [i for i in range(len(exists))] 81 | for count in counts: 82 | servants[count].order = exists[count] 83 | # mark servants 84 | for coun in counts: 85 | stmp = f"./temp/s{servants[coun].order}.png" 86 | for k in range(5): 87 | spic = f"./temp/{k}.png" 88 | if check(spic, stmp, 0.9) == 1: 89 | servants[coun].count.append(k) 90 | 91 | 92 | def get_servant(): 93 | init() 94 | init_extra() 95 | return servants 96 | -------------------------------------------------------------------------------- /alogrithms/sort1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "card.h" 4 | using namespace std; 5 | 6 | void arrange(Card *cards, vector &orders, int orders_out[], 7 | int selected[], float max_atk[], int priority, bool buster) 8 | { 9 | for(unsigned i = 0; i < orders.size(); ++i) 10 | { 11 | cards[orders[i]].priority = priority; 12 | cards[orders[i]].buster = buster; 13 | cards[orders[i]].get_atk(); 14 | float atk = cards[orders[i]].atk; 15 | if (atk > max_atk[priority]) 16 | { 17 | max_atk[priority] = atk; 18 | orders_out[priority] = i; 19 | selected[priority] = orders[i]; 20 | } 21 | } 22 | orders.erase (orders.begin() + orders_out[priority]); 23 | } 24 | 25 | int main() 26 | { 27 | // initialize 5 order cards 28 | Card cards[5]; 29 | // set card 1 30 | cards[0].mark = 0; 31 | cards[0].type = 0; 32 | // set card 2 33 | cards[1].mark = 0; 34 | cards[1].type = 1; 35 | // set card 3 36 | cards[2].mark = 1; 37 | cards[2].type = 0; 38 | // set card 4 39 | cards[3].mark = 0; 40 | cards[3].type = 1; 41 | // set card 5 42 | cards[4].mark = 0; 43 | cards[4].type = 1; 44 | 45 | vector busters; 46 | vector orders; 47 | int orders_out[3], selected[3]; 48 | float max_atk[3] = {0, 0, 0}; 49 | float sum_atk; 50 | // initialize the array 51 | for(int i = 0; i < 5; i++) 52 | { 53 | orders.push_back(i); 54 | if(cards[i].type == 2) 55 | busters.push_back(i); 56 | } 57 | // if there is buster card 58 | if(busters.size() != 0) 59 | { 60 | max_atk[0] = 1.5; 61 | // select the red card w/ lower atk 62 | for(unsigned i = 0; i < busters.size(); ++i) 63 | { 64 | cards[busters[i]].buster = 1; 65 | cards[busters[i]].get_atk(); 66 | float atk = cards[busters[i]].atk; 67 | if (atk <= max_atk[0]) 68 | { 69 | max_atk[0] = atk; 70 | orders_out[0] = busters[i]; 71 | selected[0] = busters[i]; 72 | }; 73 | } 74 | // delete the selected card 75 | orders.erase (orders.begin() + orders_out[0]); 76 | // select the card w/ highest atk 77 | arrange(cards, orders, orders_out, selected, max_atk, 2, 1); 78 | arrange(cards, orders, orders_out, selected, max_atk, 1, 1); 79 | } 80 | else if(busters.size() == 0) 81 | { 82 | for(int i = 2; i >= 0; i--) 83 | arrange(cards, orders, orders_out, selected, max_atk, i, 0); 84 | } 85 | // get the sum 86 | for(int i = 0; i < 3; i++) 87 | sum_atk += max_atk[i]; 88 | // output the result 89 | cout <<"sum atk: " << sum_atk << endl; 90 | cout << "orders: "; 91 | for(int i = 0; i < 3; i++) 92 | cout << selected[i] << " "; 93 | cout << endl; 94 | 95 | return 0; 96 | } -------------------------------------------------------------------------------- /cards/default_mode.py: -------------------------------------------------------------------------------- 1 | from cards.kind import buster_card 2 | 3 | 4 | def arrange(cards): 5 | total = 0 6 | rank = [0, 0, 0] 7 | # calculate the attack 8 | for i in range(len(cards)): 9 | cards[i].priority = 0 10 | for j in range(len(cards)): 11 | if j == i: 12 | continue 13 | else: 14 | cards[j].priority = 1 15 | for k in range(len(cards)): 16 | if k == i or k == j: 17 | continue 18 | else: 19 | cards[k].priority = 2 20 | orders = [i, j, k] 21 | 22 | # extra card recognition 23 | if cards[i].chain == 1 and \ 24 | cards[j].chain == 1 and \ 25 | cards[k].chain == 1: 26 | # B B B 27 | if cards[i].type == buster_card and \ 28 | cards[j].type == buster_card and \ 29 | cards[k].type == buster_card: 30 | for ordera in orders: 31 | cards[ordera].serial = "bc" 32 | # A A A / Q Q Q 33 | elif cards[i].type == cards[j].type and \ 34 | cards[j].type == cards[k].type: 35 | for orderb in orders: 36 | cards[orderb].serial = "aqc" 37 | # B A/Q A/Q 38 | elif cards[i].type == buster_card and \ 39 | cards[j].type != buster_card and \ 40 | cards[k].type != buster_card: 41 | for orderc in orders: 42 | cards[orderc].serial = "bnc" 43 | # A/Q B/A/Q B/A/Q 44 | else: 45 | for orderd in orders: 46 | cards[orderd].serial = "nc" 47 | 48 | # normal recognition 49 | for order in orders: 50 | if cards[i].type == buster_card: 51 | cards[order].get_atk(1) 52 | else: 53 | cards[order].get_atk(0) 54 | 55 | atk = cards[i].atk + cards[j].atk + cards[k].atk 56 | # get the highest attack 57 | if atk > total: 58 | total = atk 59 | rank[0] = i 60 | rank[1] = j 61 | rank[2] = k 62 | # reset the attack index of the cards 63 | for l in range(len(cards)): 64 | cards[l].atk = 1.0 65 | cards[l].serial = "" 66 | 67 | print("Order of the cards: ", rank) 68 | decision = [cards[rank[0]].crd, cards[rank[1]].crd, cards[rank[2]].crd] 69 | return decision 70 | -------------------------------------------------------------------------------- /interface/Major.py: -------------------------------------------------------------------------------- 1 | from interface.Battle import * 2 | from interface.scene import * 3 | from interface.Basic import * 4 | from interface.Support import * 5 | from interface.Finish import * 6 | from interface.Loading import * 7 | from util.cvs import analyze 8 | from util.log import output_log 9 | from config import screenshot_path 10 | from util.split import split 11 | 12 | __metaclass__ = type 13 | 14 | 15 | class Major: 16 | def __init__(self): 17 | self.screenshot = cv2.imread(screenshot_path, 0) 18 | self.threshold = 0.85 19 | self.basic_scenes = [cv2.imread(f"./assets/scene/{basic_scene}.png", 0) for basic_scene in basic_scenes] 20 | self.battle_scenes = [cv2.imread(f"./assets/battle/{battle_scene}.png", 0) for battle_scene in battle_scenes] 21 | self.support_scene = cv2.imread(f"./assets/scene/{support_scene}.png", 0) 22 | self.finish_scenes = [cv2.imread(f"./assets/scene/{finish_scene}.png", 0) for finish_scene in finish_scenes] 23 | self.loading_scenes = [cv2.imread(f"./assets/scene/{loading_scene}.png", 0) for loading_scene in loading_scenes] 24 | 25 | def get_basic(self): 26 | for basic_scene in self.basic_scenes: 27 | x, y = basic_scene.shape[0:2] 28 | xx, yy = self.screenshot.shape[0:2] 29 | factor = min(1., 1. * xx / x, 1. * yy / y) 30 | resized_scene = cv2.resize(basic_scene, int(x * factor), int(y * factor)) 31 | if analyze(self.screenshot, resized_scene, self.threshold) == 1: 32 | basic_interface = Basic() 33 | basic_interface.scene = basic_scene 34 | basic_interface.sh = self.screenshot 35 | basic_interface.get_button() 36 | out = "[BASIC] Buttons: " + str(basic_interface.btn_crd) 37 | print(out) 38 | output_log(out) 39 | return basic_interface.end 40 | 41 | def get_support(self): 42 | if analyze(self.screenshot, self.support_scene, self.threshold) == 1: 43 | support_interface = Support() 44 | support_interface.scene = screenshot_path 45 | support_interface.select_support() 46 | out = "[SUPPORT] Position: " + str(support_interface.crd) 47 | print(out) 48 | output_log(out) 49 | 50 | def get_finish(self): 51 | for finish_scene in self.finish_scenes: 52 | if analyze(self.screenshot, finish_scene, self.threshold) == 1: 53 | finish_interface = Finish() 54 | finish_interface.scene = screenshot_path 55 | finish_interface.pass_finish() 56 | 57 | def get_loading(self): 58 | for loading_scene in self.loading_scenes: 59 | if analyze(self.screenshot, loading_scene, self.threshold) == 1: 60 | loading_interface = Loading() 61 | loading_interface.scene = screenshot_path 62 | loading_interface.mark = self.loading_scenes.index(loading_scene) 63 | loading_interface.have_fun() 64 | 65 | def get_battle(self): 66 | for battle_scene in self.battle_scenes: 67 | if analyze(self.screenshot, battle_scene, self.threshold) == 1: 68 | split() 69 | combat = Battle() 70 | combat.get_cards() 71 | break 72 | 73 | def recognize(self): 74 | back = self.get_basic() 75 | self.get_support() 76 | self.get_battle() 77 | self.get_finish() 78 | self.get_loading() 79 | return back 80 | --------------------------------------------------------------------------------