用Python3基本实现游戏《魔法纪录》

前排提醒

        1- 本系列文章的所有技术仅支持合法的技术讨论 ,不参与任何形式的盈利与不当行动。

        2- 游戏数值图片音乐等爬取于《魔法纪录中文Wiki》与《Magic Record English Wiki》。

        3- 爬虫会对服务器造成很大伤害 ,不要模仿、照抄。

        4- 本文仅在此账号(DaisyMosuki)上发表 ,可以转载 ,可以学习 ,不允许私用。

        5- 有想要源码的 ,请私聊(无偿);有具体不理解的地方 ,请发到评论区。

        不喜勿喷 ,手下留情。

理由

        有款游戏呢 ,叫《魔法纪录》 ,之前我很喜欢玩 ,但是呢 ,国服关了 ,我“心有余而力不足” ;那 ,不妨让我们实现一下游戏的逻辑

游戏介绍

        该游戏是由日本公司制作的回合制游戏 ,具体请见百度百科魔法纪录(Aniplex发行的手机游戏)_百度百科

思考一下

        那么 ,让我们思考一下 ,如何开始?

        一个屏幕是需要的、人物是需要的、游戏图片音乐与数据是需要的...

        战斗逻辑也必不可少:

        随机discs -> 选定discs -> 根据顺序攻击 -> 造成对方伤害 -> 对方反击 -> 造成我方伤害...

        作者认为呢 ,硬要说个 “最复杂的部分” ,可能也就是 “让计算机知道谁是谁” ,其他的部分就 “迎刃而解”了。

初步实现

        所以说 ,我们的目标很明确 :

        1- 实现一个屏幕类 ,提供基本的屏幕操作。

        2- 实现一个人物抽象类 ,抽象类生人物类 ,在其中实现基本逻辑。

        3- 当然还有主函数 ,与各种技能什么的 ,以及爬取各种数据、位置。

之后直接上代码 ,代码的粗略解释在注释里

(不是完整的代码 ,是精简出来的)

军备展示

# -*- coding: utf-8 -*-
# @author: DaisyMosuki - only in CSDN

#进行Type Hint ,是一个良好的习惯
from typing import NoReturn ,Any ,Callable ,Self ,Dict ,List

#正则表达式 ,用来匹配
from re import findall as re_findall

#查找文件
from glob import iglob ,glob

#偏函数与装饰器
from functools import partial ,wraps

#控制程序
from time import sleep ,time

#线程与进程
from threading import Thread ,Event
from multiprocessing import Process

#随机函数
from random import randint ,shuffle ,choice

#游戏的屏幕与操作
import pygame
from pygame.locals import *

#处理文件与文件名
from os import path ,mkdir

#简单的提示(绝不是因为懒!)
from tkinter.messagebox import showerror ,showinfo

#爬取数据与图片
import requests as rqs
from lxml import etree

#其它
from abc import ABC ,abstractmethod
from pprint import pp ,pformat

屏幕类

#pygame本来就是所谓的单例对象 ,就不需要实现了
class PyScreen(object):


    #实现screen对象的共享 ,在外也可以对screen操作
    @property
    def root(self) -> Self:
        return self.screen


    #实现screen对象的改变
    @root.setter
    def root(self ,new_root:Self) -> NoReturn:
        self.screen:Self = new_root


    #初始化一下
    def __init__(self ,
                 * ,
                 size:Callable[[str] ,tuple[int ,int]] ,
                 name:str
                 ) -> NoReturn:

        self.__width ,self.__heigth = size
        pygame.init()
        self.screen = pygame.display.set_mode(size ,vsync=True)
        pygame.display.set_caption(name)
        self.AutoKeyDowner:bool = False


    #打印图片到屏幕
    def pic(self ,
            path:str ,
            x:int ,
            y:int ,
            **mode:dict
            ) -> NoReturn:

        photo = pygame.image.load(path).convert_alpha() if mode["pixel"] else pygame.image.load(path).convert()
        photo = pygame.transform.rotozoom(photo ,0 ,mode["ratio"]) if mode["resize"] else photo
        photo.set_colorkey(mode["colorkey"]) if mode["transparent"] else None

        if not mode["animated"]:
            self.screen.blit(photo ,(x ,y))
            return

        try:
            start ,final ,steps = mode["start"] ,mode["final"] ,mode["steps"]
            if not self.ispicable(start ,final ,steps):
                raise KeyError
        except KeyError as KE:
            start ,final ,steps = 0 ,14**2 ,16
    
        for index ,pixel in enumerate(range(start ,final ,steps)):
            if pixel >= 200:
                break
            photo.set_alpha(pixel)
            self.screen.blit(photo ,(x ,y))
            self.flash()
            sleep(0.06)


    #简单的打印图片 ,功能相当于偏函数
    def simple_pic(self ,
                   address:str ,
                   x:int ,
                   y:int ,
                   * ,
                   alpha:int = 255 ,
                   ratio:float = 1.0
                   ) -> NoReturn:
        picture = pygame.image.load(address).convert_alpha()
        photo = pygame.transform.rotozoom(picture ,0 ,ratio)
        photo.set_alpha(alpha)
        self.screen.blit(photo ,(x ,y))


    #给屏幕取个 “好听的名字”
    def __repr__(self) -> repr:
        return "<class 'PyScreen'>"


    #在屏幕上一下子打印很多字
    def say(self ,
            text:str ,
            x:int ,
            y:int ,
            size:int ,
            color:tuple ,
            auto:bool=False ,
            font:str = r"magic record\font\SIMYOU.TTF"
            ) -> NoReturn:
        self.screen.blit(pygame.font.Font(font ,size).render(text ,True ,color) ,(x ,y))
        if auto: PyScreen.flash()


    #类似于simple_pic
    def simple_say(self ,
                   text:str ,
                   x:int ,
                   y:int ,
                   color:tuple ,
                   size:int = 28 ,
                   font:str = r"magic record\font\SIMYOU.TTF"
                   ) -> NoReturn:

        self.say(text=text ,x=x ,y=y ,size=size ,color=color ,font=font)


    #动画递进式打印文字
    def said(self ,text:str ,x:int ,y:int ,size:int ,color:tuple ,auto:bool=False): #animation say -> said
        _x ,_y = x ,y
        for index ,char in enumerate(text):
            _x ,_y = (x ,_y+46) if _x >= self.width-x else (_x ,_y)
            self.say(char ,_x ,_y ,size ,color ,auto)
            PyScreen.flash()
            _x += 33
            sleep(0.04)
        else:
            print(f"Say Animation Show Done!")


    #刷新
    def flash(self ,isnew:bool=True) -> NoReturn:
        pygame.display.flip() if isnew else pygame.display.update()


    #根据血量显示血条
    def show_HP_bar(self ,
                    HP_or_MP:int ,
                    x:int ,
                    y:int ,
                    * ,
                    color:tuple = (255 ,0 ,0) ,
                    target:str = "HP"
                    ) -> NoReturn:

        inter_color:tuple = (255 ,0 ,0) if target == "HP" else (45 ,92 ,255)
        #random_color:tuple = tuple([randint(0 ,255) for _ in range(3)]) if target == "HP" else (100 ,200 ,255)
        pygame.draw.rect(self.screen ,color=inter_color ,rect=(x+42+34 ,y+72 ,HP_or_MP ,10))
        pygame.draw.rect(self.screen ,color=tuple([255 for _ in range(3)]) ,rect=(x+42+34 ,y+72 ,HP_or_MP ,10) ,width=3)



    #在被选定的敌人周围生成“框” ,以此显示被选定的敌人是哪个
    def show_rival(self ,
                    * ,
                    x:int ,
                    y:int ,
                    color:tuple[int ,int ,int] ,
                    size:int = 512
                    ) -> NoReturn:

        pygame.draw.rect(self.screen ,color=color ,rect=(x ,y ,size ,size) ,width=2)


    #功能类同show_rival ,只不过是针对我方的框
    def show_chara(self ,
                   * ,
                   x:int ,
                   y:int ,
                   color:tuple[int ,int ,int] ,
                   size:int = 512
                   ) -> NoReturn:

        pygame.draw.rect(self.screen ,color=color ,rect=(x ,y ,size ,size) ,width=2)


    #显示人物与血条 ,集合于一体
    def character(self ,
                  name:str ,
                  * ,
                  root:Self ,
                  pos:tuple[int] ,
                  element:str ,
                  info:dict = {},
                  color:tuple[tuple[int]] = ((255 ,255 ,255) ,(255 ,255 ,255)) ,
                  magic_pic:str = "magic.jpg" ,
                  ch_dict:dict ,
                  ch_list:list ,
                  ri_dict:dict ,
                  ri_list:list
                  ) -> NoReturn:

        """
        function PyScreen.character
            draw character and basic info

        :param info -> dict
            HP(now) && __HP__
            MP(now)
            :param HP
            :param __HP__
            :param MP
            
        :param element -> str
            element name ,just name ,no suffix

        :param color -> tuple
            colorkey

        :param magic_pic -> str
            magic pic .png
        """

        if not info["alive"]:
            return

        if (HP := info["HP"]) < 0b0 :
            info["alive"] = False

            basename:str = path.basename(name)
            removed_name:str = basename[:basename.index('.')]
            print(f"!!!{name} DIED!!!")

            try:
                # start with ri #
                ri_list.remove(ri_dict[removed_name])
            except:
                ch_list.remove(ch_dict[removed_name])
            finally:
                self.AutoKeyDowner:bool = True
                return

        x ,y ,_x ,_y = (*pos ,*pos)

        _y:int = _y+2 if root.Animate else _y-2
        root.Animate:bool = not root.Animate
       
        __HP__ ,MP = info["__HP__"] ,info["MP"]
        HP:int = self.convert_HP(new=HP ,old=__HP__)

        element_name:str = show_abspath_single("element//"+element+".png")
        magic_pic:str = show_abspath_single(magic_pic)
        self.show_effects(x=x+50 ,y=y+35 ,info=info)
        self.pic(path=magic_pic ,x=x-36 ,y=y+50 ,animated=False ,resize=True ,pixel=False ,transparent=True ,colorkey=color[0] ,ratio=0.16)
        self.pic(path=show_abspath_single(name) ,x=_x ,y=_y ,animated=False ,resize=True ,pixel=False ,transparent=True ,colorkey=color[1] ,ratio=0.45)
        self.pic(path=element_name ,x=x+42 ,y=y+60 ,animated=False ,resize=False ,pixel=False ,transparent=True ,colorkey=color[1])
        self.show_HP_bar(HP ,x=x ,y=y)
        self.show_HP_bar(MP ,x=x ,y=y-12 ,target="MP")

    
    #因为满血量不是100 ,所以说将现在的血量转化成100进制下的血量
    def convert_HP(self ,new:int ,old:int) -> int:
        return new*100//old
    

    #对战的实现
    @staticmethod
    def battle(* ,
               ALL_CHARACTER:dict[str ,Any] ,
               ViceCharacter:list[Any] ,
               charge:int ,
               discs_getlist:list[tuple[Any]]
               ) -> int:
        charge:int = 0 if not charge else charge
        all_hurt:int = 0
                                    
        for index ,each in enumerate(discs_getlist ,start=1):           
            match each[2]:
                case "Accele":
                    print(f"{index} is Accele")
                    ALL_CHARACTER[each[3]].data["MP"] += randint(10 ,15)
                    
                case "Blast1" | "Blast2":
                    all_hurt += ALL_CHARACTER[each[3]].blast(ViceCharacter ,index=index ,charge=charge)
                    charge:int = 0
                    print(f"{index} is Blast")
                    continue
                
                case "Charge":
                    charge += 0b1
                    print(f"{index} is Charge")
                    
            all_hurt += ALL_CHARACTER[each[3]] >> ViceCharacter
            print(f"Disc{index} Fire!")
        return charge ,int(all_hurt)


    #检测是否游戏结束
    def isOver(self ,
               * ,
               main_list:list ,
               vice_list:list
               ) -> bool:

        if not len(vice_list):
            showinfo("☺" ,"You Did It!")
            return False
        if not len(main_list):
            showinfo("ㄒoㄒ" ,"You Are Lost!")
            return False
        return True


    #点击角色 ,可以显示具体状态(HP ATK DEF MP...)与记忆结晶图片与效果
    def _SHOW_EFFECT_PIC(self ,character:Any) -> NoReturn:
        x_init ,y_init = 630 ,0
        x ,y = x_init ,y_init
        self.simple_pic(show_abspath_single("alpha_back.png") ,x=0 ,y=0 ,alpha=200 ,ratio=1.2)
        self.simple_pic(choice(character.self) ,x=0 ,y=0 ,ratio=0.81 ,alpha=210)
        self.simple_say(f"HP:{character.data['HP']}/{character.data['__HP__']}" ,x=50 ,y=H//2 ,color=(255 ,0 ,0) ,size=36)
        self.simple_say(f"MP:{character.data['MP']}/100" ,x=50 ,y=H//2+50 ,color=(0 ,190 ,255) ,size=36)
        self.simple_say(f"ATK:{character.data['ATK']}/{character.data['__ATK__']}" ,x=50 ,y=H//2+120 ,color=(255 ,160 ,30) ,size=36)
        self.simple_say(f"DEF:{character.data['DEF']}/{character.data['__DEF__']}" ,x=50 ,y=H//2+190 ,color=(100 ,180 ,55) ,size=36)
        ratio:float = 0.68 if len(re_findall(r".*?Memoria.*?" ,character.style[0])) else 0.45
        for index ,each in enumerate(character.style ,start=6):
            if index == 8:
                x ,y = x_init ,y_init+500
            self.simple_say(f"<{index}>" ,x=x-100 ,y=y ,color=(0 ,0 ,0) ,size=40)
            self.simple_pic(each ,x=x ,y=y ,alpha=245 ,ratio=ratio)
            x += 350
        else:
            pass



    #菜单装饰器 ,MainFnuc其实就是主函数入口(部分功能之后更新)
    def SHOW_MAIN_MENU(self ,MainFunc:Callable) -> Callable:
        @wraps(MainFunc)
        def MagicRecord(*args:tuple ,**kwargs:dict) -> NoReturn:
            clock ,run ,pointer ,animate ,FPS = pygame.time.Clock() ,True ,0b0 ,True ,10
            back:str = show_abspath_single(r"Menu.png")

            menu_mixer = SimpleMixer([0])
            #menu_mixer.air()
            menu_say:Callable = partial(self.simple_say ,font=r"magic record\font\SNAP____.TTF")
            menu_txt:tuple[dict] = (
                {"text":"Start" ,"color":(255 ,121 ,233)} ,
                {"text":"Load" ,"color":(97 ,255 ,255)} ,
                {"text":"Gallery" ,"color":(248 ,255 ,31)} ,
                {"text":"Config" ,"color":(255 ,0 ,0)} ,
                {"text":"Quit" ,"color":(0 ,0 ,0)}
                )
    
            while run:
                clock.tick(FPS)
                
                self.pic(path=back ,x=0 ,y=0 ,animated=animate ,resize=True ,pixel=True ,transparent=False ,ratio=1.6)
                if animate:
                    sleep(0.2)
                    animate:bool = False

                for index ,each in enumerate(menu_txt ,start=1):
                    menu_say(each["text"] ,W//2-100 ,H//3+80*index ,color=each["color"] ,size=52)

                pygame.draw.circle(self.screen ,menu_txt[pointer]["color"] ,(W//2-130 ,H//3+80*(pointer+1)+34) ,16 ,width=0)

                self.flash()
                
                pygame.event.set_allowed([KEYDOWN ,QUIT])
                for event in pygame.event.get():
                    match event.type:
                        case pygame.QUIT:
                            run:bool = not run
                            break
                        case pygame.KEYDOWN:
                            try:
                                match event.key:
                                    case pygame.K_w|pygame.K_UP|pygame.K_a|pygame.K_LSHIFT|pygame.K_LEFT:
                                        pointer:int = abs((pointer - 1) % 5)
                                    case pygame.K_s|pygame.K_DOWN|pygame.K_d|pygame.K_RSHIFT|pygame.K_RIGHT:
                                        pointer:int = (pointer + 1) % 5
                                    case pygame.K_RETURN|pygame.K_SPACE:
                                        match pointer:
                                            case 0:
                                                MainFunc(FPS ,**kwargs)
                                                print("\n*Game Over*")
                                                return
                                            case 1:
                                                print(1)
                                            case 2:
                                                print(2)
                                            case 3:
                                                pointer3:int = 0
                                                """
                                                while True:
                                                    break
                                                    EVENT = pygame.event.wait()
                                                    if EVENT.type == pygame.QUIT:
                                                        pygame.quit()
                                                        return
                                                    elif EVENT.type == pygame.KEYDOWN:
                                                        match EVENT.key:
                                                            case pygame.K_LEFT|pygame.K_LSHIFT:
                                                                pass
                                                """
                                                
                                            case 4:
                                                pygame.quit()
                                                return
                                            case _:
                                                pass
                                    case _:
                                        pass
                            except ValueError:
                                print("*** ***")
        return MagicRecord

背景音乐类 与 音效类

#背景音乐类
class SimpleMixer(object):


    def __init__(self ,
                 fromlist:list[str] ,
                 * ,
                 vol:float=0.6
                 ) -> NoReturn:
        """
        function SimpleMixer.__init__
            first initialize mixer
        """
        super(SimpleMixer ,self).__init__()
        pygame.mixer.init()
        self.fromlist:list[str] = fromlist
        self.running:bool = True
        self.length:int = len(self.fromlist)
        self.pointer:int = randint(0 ,self.length-1)
        pygame.mixer.music.set_volume(vol)


    def air(self ,music:str) -> NoReturn:
        pygame.mixer.music.load(music)
        pygame.mixer.music.play(1)


    #使用线程
    def run(self) -> NoReturn:
        while self.running:
            try:
                if not pygame.mixer.music.get_busy():
                    self.air(self.fromlist[self.pointer])
                    self.pointer:int = (self.pointer + 0b1) % self.length
                sleep(3.8)
            except:
                return


#音效类
class SimpleEffect(object):


    def __init__(self ,
                 fromlist:str ,
                 * ,
                 init:bool=False
                 ) -> NoReturn:
        """
        function SimpleEffect.__init__
            init in SimpleMixer
        """
        if init:
            pygame.mixer.init()
        _list:iter = iglob(r"magic record/items/style/" + fromlist + r"/*.mp3")
        self.choose:list[str] = [each for each in _list if len(re_findall(r".*?(choose).\.mp3" ,each))]
        self.ok:list[str] = [each for each in _list if len(re_findall(r".*?(ok).\.mp3" ,each))]
        self.start:str = r"magic record/items/style/" + fromlist + r"/start.mp3"
        self.die:str = r"magic record/items/style/" + fromlist + r"/die.mp3"


    def effect(self ,
               act:str ,
               * ,
               vol:int = 1.2) -> NoReturn:

        effect = pygame.mixer.Sound(choice(self.ok) if act=='ok' else choice(self.choose) if act=='choose' else self.start if act=='start' else self.die)
        effect.set_volume(vol)
        effect.play()

人物抽象类

#人物抽象类 ,也就是模板
class Character(ABC):


    #根据我方人物 ,打乱所有的discs ,随机抽出五个
    @staticmethod
    def shuffle_disc(*discs:tuple[dict[str ,tuple[Any ,Any]]]) -> dict[int ,tuple[Any ,Any]]:

        before_shuffle:list[tuple[Any ,Any]] = [item for each in discs for index ,(key ,item) in enumerate(each.items())]
        shuffle(before_shuffle)
        chosen:list[tuple[Any ,Any]] = [choice(before_shuffle) for _ in range(5)]
        after_shuffle:list[tuple[Any ,Any]] = [each+(time()+index ,) for index ,each in enumerate(chosen ,start=1)]
        return dict(zip(range(1 ,6) ,after_shuffle))


    def __init__(self ,
                 name:str ,
                 pos:tuple[int ,int] ,
                 * ,
                 root:Self ,
                 skill:dict[str ,Callable] ,
                 element:str ,
                 data:dict[str ,int] ,
                 character:Callable
                 ) -> NoReturn:
        """
        function Character.__init__

        :param data
            HP __HP__ MP
            ATK DEF
            :Status
                :hurt
                :any effect...

        :arg character
            #name :str
            info :dict[HP ,__HP__ ,MP]

        :super
            :param
                pos:tuple[int ,int]
                character:Callable

        :note
            data != info
            info ∈ data
            data -> info

            name is abs_name
            _name is real name
        """

        self.skill:dict[str ,Callable] = skill
        self.name:str = r"{}/{}.png".format(name ,name)
        self.character:Callable = partial(character ,name=self.name ,element=element ,pos=pos ,root=root)
        self.data ,self.element ,self.x ,self.y = (data ,element ,*pos)
        self.info:dict[str ,int] = {key:item for index ,(key ,item) in enumerate(data.items()) if key in {"HP" ,"__HP__" ,"MP"}}
        self._name:str = name
        self.effect = SimpleEffect(self._name)
        self.style:list[str] = glob(r"magic record\items\style\{}\*.png".format(name))
        self.self:list[str] = glob(r"magic record\items\style\{}\*.jpg".format(name))
        print("Create Character OK!")

        #self.name:str = name


    #简单的动画开关
    def __init_subclass__(self) -> NoReturn:
        self.Animate:bool = True


    #有四个盘 ,每个盘上面都印有人物 ,所以说用tuple来绑定它们 ,以及盘信息 ,人物信息
    @property
    def disc(self) -> dict[str ,tuple[Any ,Any]]:
        """
        res:
            {
                "" : (pic ,pic ,"")
            }
        """
        another:dict[str ,Any] = {}
        for index ,each in enumerate(iglob(show_abspath_single("disc/*.png")) ,start=1):
            basename:str = path.basename(each)
            character ,disc = pygame.image.load(show_abspath_single(self.name)).convert_alpha() ,pygame.image.load(each).convert_alpha()
            character ,disc = pygame.transform.rotozoom(character ,0 ,0.3) ,pygame.transform.rotozoom(disc ,0 ,0.68)
            character.set_colorkey((255 ,255 ,255))
            disc.set_colorkey((255 ,255 ,255))
            character.set_alpha(220)
            name:str = basename[:basename.index('.')] #discs name
            another[f"{name}"]:dict = (disc ,character ,name ,self._name)
        else:
            return another


    #将抽出的五个盘整齐的放到屏幕上
    @staticmethod
    def pic_disc(screen:Any ,
                 disc:dict[str ,tuple[Any ,Any]] ,
                 x:int ,
                 y:int ,
                 gap:int = 160 ,
                 *args:tuple
                 ) -> NoReturn:
        
        if not len(disc):
            return
        
        if isinstance(disc ,dict):
            if not len(args):
                for index ,(key ,item) in enumerate(disc.items()):
                    screen.blit(item[0] ,(x ,y))
                    screen.blit(item[1] ,(x+10 ,y-40))
                    x:int = x + gap
        else:
            for index ,each in enumerate(disc):
                screen.blit(each[0] ,(x ,y))
                screen.blit(each[1] ,(x+10 ,y-40))
                x:int = x + gap


    #模拟discs在屏幕上的位置
    @staticmethod
    def disc_pos(* ,
                 x:int ,
                 y:int ,
                 gap:int = 160
                 ) -> list[tuple[int ,int]]:
        
        return [(x:=x+gap ,y) for _ in range(5)]


    #blast攻击需要考虑的参数多一点(我简化了原公式)
    def blast(self ,
              target:Callable ,
              * ,
              charge:int ,
              index:int
              ) -> int:
        """
        index 1-3
        """
        all_hurt:int = (self.data["ATK"] - target.data["DEF"]//2)*self.data["Status"]["hurt"]*(1.0+0.08*(index-1))*(charge+1) + randint(0 ,100)
        target.data["HP"] -= all_hurt
        return all_hurt
        

    #左移<<代表buff ,因为自己实例化的对象在左移的左边
    @abstractmethod
    def __lshift__(self ,keyboard:str) -> NoReturn:
        """
        function Character.__lshift__
            buff / debuff

        example:
            main << keyboard
        """
        pass


    #相对的 ,右移代表攻击 ,因为敌人实例化对象在右移右边
    @abstractmethod
    def __rshift__(self ,target) -> NoReturn:
        """
        function Character.__rshift__
            target.HP --
        """
        #target.data["HP"] -= (self.data["ATK"] - target.data["DEF"]//3)*self.data["Status"]
        all_hurt:int = (self.data["ATK"] - target.data["DEF"]//3)*self.data["Status"]["hurt"] + randint(0 ,100)
        target.data["HP"] -= all_hurt
        return all_hurt


    #绑定每个角色的记忆结晶
    @staticmethod
    def skill(namelist:list[str ,str ,str ,str] ,
                  * ,
                  file:str = r"magic record\items\memoriaDATA2.dat" ,
                  keys:list[str] = ["type" ,"name" ,"ATK" ,"DEF" ,"HP"]
                  ) -> dict[str:dict[str:Any]]|None:

        if len(namelist) != 4:
            print("Every Character Have Four Skill!")
            return None

        final:dict = {}
        with open(file ,'r' ,encoding='utf-8') as f:
            data:dict[str:dict[str:Any]] = eval(eval(f.read()))
            
        for index ,each in enumerate(namelist ,start=6):
            now ,gap = randint(1 ,3) ,randint(8 ,12)
            init:dict = {
                "init":{"now":now ,"gap":gap} ,"now":now ,"gap":gap ,"open":False ,"type":'' ,"name":'' ,
                "ATK":0 ,"DEF":0 ,"HP":0
            }

            another:dict = data[each]
            for i ,e in enumerate(keys):
                init[e] = eval(another[e].rstrip('\n')) if e not in {"type" ,"name"} else another[e]
            else:
                final[str(index)]:dict[str:dict] = init
        else:
            return final

人物模板类

#模板类 ,一行流生成一个人物
class Template(Character):


    def __init__(self ,
                 name:str ,
                 * ,
                 HP:int ,
                 ATK:int ,
                 DEF:int ,
                 pos:tuple[int ,int] ,
                 character:Callable ,
                 skill:dict[str ,Any],
                 toward:str = ''  ,
                 MP:int = 0 ,
                 __lshift__:Callable = None ,
                 __rshift__:Callable = None
                 ) -> NoReturn:
        """
        function self.__init__

        :param character
            constantly eq screen.character

        :param skill
            {
                '6':{
                    "init":{"now":1 ,"gap":8} ,"now":1 ,"gap":8 ,"open":False ,"type":"skill"/"ability" ,"name":"渺小的真正的希望" ,ATK/HP/DEF
                }
                
                '7':{
                }
                ...
            }

        :note
            _skill -> dict
            skill  -> list[str]
        """

        name:str = "re_"+name if toward == "re" else name
        _skill:dict[str:Any] = Character.skill(skill)
        
        for index ,(key ,item) in enumerate(_skill.items()):
            ATK ,DEF ,HP = ATK+item["ATK"] ,DEF+item["DEF"] ,HP+item["HP"]
        
        data:dict[str ,int] = {
            "HP":HP ,"__HP__":HP ,"ATK":ATK ,"__ATK__":ATK ,"DEF":DEF ,"__DEF__":DEF ,"MP":MP ,"magic":None ,"alive":True ,
            "Status":{
                "hurt":1.0 ,"effect":{}
                }
            }
        
        super(Template ,self).__init__(name ,pos ,skill=_skill ,element="Light" ,data=data ,character=character ,root=self)
        self.skill_dict:dict[str ,dict[Any]] = _skill
        self.style:list[str] = [r"magic record\items\Memoria\{}.png".format(each) for each in skill]
        self.__lshift__: Callable = __lshift__
        self.__rshift__: Callable = __rshift__


    def __rper__(self) -> repr:
        return self._name

主函数

screen:Callable = PyScreen(size=(W ,H) ,name="MAIN")

@screen.SHOW_MAIN_MENU
def main(*args:tuple ,**kwargs:dict) -> NoReturn:

    mixer = SimpleMixer(fromlist=glob(r"magic record/items/music/*"))
    MyMixer = Thread(target=mixer.run ,name="music")
    MyMixer.start()

    charge:int = 0b0
    all_hurt:int = None
    TIMESTAPM:float = time()
    
    clock ,run ,disc_ok ,pointer ,_pointer ,rounds = pygame.time.Clock() ,True ,False ,0b0 ,0b0 ,0b1
    madoka_skill:dict[str ,Callable] = {'q':Skill.Effect.HP_plus ,'a':Skill.Buff.MP}
    
    #screen:PyScreen = PyScreen(size=(W ,H) ,name="MAIN")
    root:Callable = screen.character

    #新类
    test:Character = Template("Iroha_Miko" ,
                              HP=30003 ,ATK=8547 ,DEF=8606 ,MP=50 ,
                              pos=Grid.c2l2 ,
                              character=root ,
                              skill=["Begin a Hunt" ,"少女的决心" ,"盛装出行" ,"三位天才"] ,
                              )
    
    #这里 ,文章我刻意没写 ,因为写的比较烂(明明一行的事 ,却写了上百行)
    #老类
    madoka:Character = Ultimate_Madoka_Sprite(pos=Grid.c1l1 ,character=root ,skill=madoka_skill)
    re_madoka:Character = Ultimate_Madoka_Sprite(pos=Grid.re_c3l1 ,character=root ,skill=madoka_skill ,toward='re')
    iroha_miko:Character = Iroha_Miko(pos=Grid.re_c2l2 ,character=root ,skill=madoka_skill ,toward="re")#
    nanami_yachiyo:Character = Nanami_Yachiyo(pos=Grid.c3l3 ,character=root ,skill=madoka_skill)

    discs:dict[str ,tuple[Any ,Any]] = Character.shuffle_disc(madoka.disc ,nanami_yachiyo.disc ,test.disc) #@TEST@#
    memoria:dict[str ,tuple[Any ,Any]] = {**discs}
    #vice_discs:dict[str ,tuple[Any ,Any]] = Character.shuffle_disc(iroha_miko.disc ,re_madoka.disc)
    discs_getlist:list[tuple] = []
    discs_getdict:dict[tuple ,Character] = {}
    

    character_list:list = [madoka ,nanami_yachiyo ,test] #@TEST@#
    
    rival_name:list = [iroha_miko._name ,re_madoka._name]
    rival_list:list = [iroha_miko ,re_madoka]
    rival:dict[str ,Character] = dict(zip(rival_name ,rival_list))
    ALL_VICE_CHARACTER = rival

    InitCharLen:int = len(character_list)
    InitViceLen:int = len(rival_list)

    MainCharacter:Callable = character_list[pointer]
    ViceCharacter:Callable = rival_list[_pointer]
    
    pointer:int = (pointer + 1) % len(character_list)
    disc_pos:list[tuple[int ,int]] = Character.disc_pos(x=190 ,y=H-130) #gap=160

    our_name:list[str] = [madoka._name ,nanami_yachiyo._name ,test._name] #@TEST@#
    ALL_CHARACTER:dict[str ,Character] = dict(zip(our_name ,character_list))
    #print(ALL_CHARACTER)

    choice(character_list).effect.effect("start")

    # *Partial Function* #
    re_all:list = [each.disc for each in character_list]
    re:Callable = partial(Skill.re ,discs_getlist=discs_getlist ,discs_getdict=discs_getdict ,Character=Character)

    #所有人物
    def SHOWTIME() -> NoReturn: #@TEST@#
        """
        function SHOWTIME
            show all-character in screen

        """
        #screen.SHOW_EFFECT_PIC(pos=() ,character=MainCharacter)
        screen.simple_pic(show_abspath_single("back1.jpg") ,x=0 ,y=0)
        screen.simple_say(text=f"Round{rounds}" ,x=20 ,y=H-120 ,color=(255 ,0 ,0) ,size=40)
        #screen.simple_say(text=f"Time{abs(int(TIMESTAPM-time()))}s" ,x=20 ,y=H-40 ,color=(238 ,138 ,248))
        screen.simple_say(text=f"{MainCharacter}" ,x=W//2-150 ,y=10 ,color=(145 ,255 ,214))
        screen.show_rival(x=ViceCharacter.x+20 ,y=ViceCharacter.y+36 ,color=(214 ,255 ,233) ,size=220)
        screen.show_chara(x=MainCharacter.x+20 ,y=MainCharacter.y+36 ,color=(255 ,20 ,240) ,size=220)

        #screen.simple_pic(r"C:\Users\sfuch\Desktop\Download\117791959_p1_master1200.jpg" ,x=0 ,y=0)
        
        madoka.character(info=madoka.data ,
                         ch_dict=ALL_CHARACTER ,
                         ch_list=character_list ,
                         ri_list=rival_list ,
                         ri_dict=rival)
        
        iroha_miko.character(info=iroha_miko.data ,
                             ch_dict=ALL_CHARACTER ,
                             ch_list=character_list ,
                             ri_list=rival_list ,
                             ri_dict=rival)
        
        nanami_yachiyo.character(info=nanami_yachiyo.data ,
                                 ch_dict=ALL_CHARACTER ,
                                 ch_list=character_list ,
                                 ri_list=rival_list ,
                                 ri_dict=rival)
        
        re_madoka.character(info=re_madoka.data ,
                            ch_dict=ALL_CHARACTER ,
                            ch_list=character_list ,
                            ri_list=rival_list ,
                            ri_dict=rival)

        test.character(info=test.data ,
                       ch_dict=ALL_CHARACTER ,
                       ch_list=character_list ,
                       ri_list=rival_list ,
                       ri_dict=rival)

    while run:

        clock.tick(args[0])
        pygame.event.set_allowed([MOUSEBUTTONDOWN ,MOUSEMOTION ,KEYDOWN ,QUIT])

        if screen.AutoKeyDowner:
            InitCharLen ,InitViceLen = len(character_list) ,len(rival_list)
            _pointer ,pointer = (_pointer + 1) % InitViceLen ,(pointer + 1) % InitCharLen
            MainCharacter ,ViceCharacter = character_list[pointer] ,rival_list[_pointer]
            screen.AutoKeyDowner:bool = False
            re_all:list = [each.disc for each in character_list]
            re:Callable = partial(Skill.re ,discs_getlist=discs_getlist ,discs_getdict=discs_getdict ,Character=Character)
            if not (run := screen.isOver(main_list=character_list ,vice_list=rival_list)):
                break

        SHOWTIME()

        # * three-discs * #
        Character.pic_disc(screen.root ,discs_getlist ,x=20 ,y=10 ,gap=136)
        
        #if not disc_ok:
        Character.pic_disc(screen.root ,discs ,x=190 ,y=H-130)

        if charge:
            #screen.simple_pic(show_abspath_single(r"disc/Charge.png") ,x= ,y=)
            screen.simple_say(f"Charge{charge}" ,x=20 ,y=H-80 ,color=(240 ,255 ,16) ,size=38)
            
        screen.flash()

        for event in pygame.event.get():
            match event.type:

                case pygame.KEYDOWN:
                    KEY = pygame.key.get_pressed()

                    if KEY[pygame.K_0]:
                        print(111)
                        break

                    if KEY[pygame.K_RIGHT]:
                        MainCharacter:Callable = character_list[pointer]
                        pointer:int = (pointer + 1) % InitCharLen
                        break

                    if KEY[pygame.K_LEFT]:
                        _pointer:int = (_pointer + 1) % InitViceLen
                        ViceCharacter:Callable = rival_list[_pointer]
                        break

                    if KEY[pygame.K_TAB]:
                        #discs:dict[str ,tuple[Any ,Any]] = re()
                        discs_getlist[:]:list = []
                        discs_getdict:dict = {}
                        discs = {**memoria}
                        #Character.pic_disc(screen.root ,memoria ,x=190 ,y=H-130)
                        break

                    if KEY[pygame.K_p]:
                        print(f"\ndiscs_dict:{discs_getdict}\n")
                        print(f"\ndiscs_list:{discs_getlist}\n")

                    try:
                        match (key := chr(event.key)):
                            case '1'|'2'|'3'|'4'|'5':
                                if len(discs_getlist) >= 3:
                                    break
                                for index ,(keys ,items) in enumerate(discs.items() ,start=1):
                                    #print(index ,key ,item)
                                    if keys == eval(key):
                                        ALL_CHARACTER[items[3]].effect.effect("choose")
                                        discs_getlist.append(items) #item is dict
                                        discs_getdict[items] = ViceCharacter
                                        try:
                                            discs.pop(keys)
                                        except KeyError as KE:
                                            pass
                                        print(f"append disc {index} ok!")
                                        break

                            case '\r':
                                if len(discs_getlist) == 3:

                                    for index ,(DISC ,TARGET) in enumerate(discs_getdict.items()):
                                        charge ,all_hurt = PyScreen.battle(ALL_CHARACTER=ALL_CHARACTER ,
                                                                 ViceCharacter=TARGET ,
                                                                 charge=charge ,
                                                                 discs_getlist=[DISC])
                                        
                                        screen.show_all_hurt(all_hurt ,target=TARGET)
                                        SHOWTIME()
                                        screen.flash()
                                    else:
                                        discs_getdict:dict = {}
                                        
                                    #print(f"charge : {charge}\nall_hurt : {all_hurt}")

                                    #re_all:list = [each.disc for each in character_list]

                                    ### check1 ! ###
                                    if not (run := screen.isOver(main_list=character_list ,vice_list=rival_list)):
                                        break

                                    # start to vice-battle #
                                    #print("START VICE ATTACK!!!")

                                    __discs:list[tuple[Any]] = [e for i ,e in Character.shuffle_disc(iroha_miko.disc ,re_madoka.disc).items()]
                                    for index ,each in enumerate([choice(__discs) for _ in range(3)] ,start=1):
                                        chosen_vice:Character = choice(character_list)
                                        _ ,_hurt = PyScreen.battle(ALL_CHARACTER=ALL_VICE_CHARACTER ,
                                                        ViceCharacter=chosen_vice ,
                                                        charge=0 ,
                                                        discs_getlist=[each])
                                        
                                        screen.show_all_hurt(_hurt ,target=chosen_vice)
                                        SHOWTIME()
                                        screen.flash()
                                        
                                    else:
                                        #del _ ,_hurt

                                        ### check2 ! ###
                                        if not (run := screen.isOver(main_list=character_list ,vice_list=rival_list)):
                                            break
                                        
                                        re_all:list = [each.disc for each in character_list]
                                        discs:dict[str ,tuple[Any ,Any]] = re(re_all=re_all)
                                        memoria:dict[str ,tuple[Any ,Any]] = {**discs}
                                        
                                        pygame.event.clear()
                                        rounds:int = rounds + 0b1
                                        MainCharacter.countdown()
                                else:
                                    showerror("X" ,"需要选满3个盘!")
                                    
                                break
                                
                                    
                            case '6'|'7'|'8'|'9'|'0':
                                MainCharacter << key
                                #MainCharacter.countdown()

                            case _:
                                pass
                            
                    except ValueError as VE:
                        print(f"**\nCommon Error :\n{VE}\n**")

                case pygame.MOUSEBUTTONDOWN:
                    x ,y = event.pos
                    for index ,each in enumerate(character_list):
                        if each.x <= x <= each.x+512*Grid.ratio and each.y <=y <= each.y+512*Grid.ratio:
                            screen._SHOW_EFFECT_PIC(each)
                            screen.flash()
                            while True:
                                event = pygame.event.wait()
                                if event.type == pygame.KEYDOWN:
                                    if event.key == pygame.K_SPACE:
                                        break
                                    
                            #sleep(5)
                            break
                    pass

                case pygame.MOUSEMOTION:
                    pass

                case pygame.QUIT:
                    run:bool = not run
                    # #
                    break

                case _: pass # match #

        else: pass # for #
    else: pass # while #

爬取记忆结晶图片与数值

#所有的XPATH我都删除了 ,请尊重《魔法纪录中文Wiki》!

class Memoria(object):

    #请求头我就隐藏了#

    keys:list[str] = [
            "img" ,
            "name" ,
            "star" ,
            "type" ,
            "HP" ,"ATK" ,"DEF" ,
            "effect" 
        ]

    #headers=Memoria.headers
    def __init__(self ,
                 filename:str = "memoriaHTML.dat" ,
                 * ,
                 url:str
                 save:bool = False ,
                 load:bool = False
                 ) -> NoReturn:

        if save and load:
            showerror("X" ,"SAVE and LOAD!")
            return

        if load:
            with open(filename ,'r' ,encoding='utf-8') as file:
                self.html:str = file.read()
        else:
            if not (respond:=rqs.get(url=url ,headers=Memoria.headers)).ok:
                return showerror("X" ,"Respond Error!")
            self.respond:Any = respond
            print("GET OK!")

            if save:
                with open(filename ,'w' ,encoding="utf-8") as file:
                    file.write(self.respond.text)
                print("SAVE OK!")
            self.html:str = self.respond.text

        self.memoria:dict[str:dict[str:Any]] = {}
        print("INIT OK!")


    def get(self ,target:Any) -> list:
        return [each for index ,each in enumerate(target)]


    def set(self ,xpath:str) -> Any:
        return etree.HTML(self.html).xpath(xpath)


    def parser(self ,
               filename:str = r"memoriaDATA.dat",
               * ,
               img_xpath:str ,
               name_xpath:str ,
               star_xpath:str ,
               type_xpath:str ,
               HP_xpath:str ,
               ATK_xpath:str ,
               DEF_xpath:str ,
               effect_xpath:str
               ) -> NoReturn:

        
        img:list = self.get(self.set(img_xpath))
        name:list = self.get(self.set(name_xpath))
        star:list = self.get(self.set(star_xpath))
        tp:list = self.get(self.set(type_xpath))
        HP:list = self.get(self.set(HP_xpath))
        ATK:list = self.get(self.set(ATK_xpath))
        DEF:list = self.get(self.set(DEF_xpath))
        effect:list = self.get(self.set(effect_xpath))

        print(f"img:{len(img)}\nname:{len(name)}\nstar:{len(star)}\ntp:{len(tp)}\nHP:{len(HP)}\nATK:{len(ATK)}\nDEF:{len(DEF)}\neffect:{len(effect)}")

        lexicon:dict[str:Any] = dict(zip(Memoria.keys ,[img ,name ,star ,tp ,HP ,ATK ,DEF]))

        for index ,title in enumerate(name ,start=0):
            self.memoria[title]:dict = {}
            for i ,(key ,each) in enumerate(lexicon.items() ,start=0):
                self.memoria[title][key]:dict[str:Any] = each[index]
            else:
                if not index % 10:
                    print(f"{index}OK!")
        else:
            print(f"AllOver!")

            with open(filename ,'w' ,encoding='utf-8') as file:
                file.write(repr(pformat(self.memoria)))
            print("Parser OK!")


    @staticmethod
    def check(filename:str ,
              * ,
              mode:str = "-check" ,
              stop:int|None = None
              ) -> NoReturn:
        with open(filename ,'r' ,encoding='utf-8') as file:
            data:dict[str:dict[str:Any]] = file.read()

        match mode:
            case "-check":
                pp(data)
            case "-len":
                print(f"length:{len(data)}")
            case _:
                print("Mode Args Error!")


class ImgDownloader(object):

    def __init__(self ,
                 filename:str
                 ) -> NoReturn:

        with open(filename ,'r' ,encoding='utf-8') as file:
            self.data:dict[str:dict[str:Any]] = eval(eval(file.read()))

        if isinstance(self.data ,dict):
            print(f"LOAD TEXT OKK!")
        else:
            print(f"ERROR!")
            raise


    def run(self ,
            file:str ,
            * ,
            xpath:str = r'//img[@class="thumbimage"]/@src' ,
            start:int = 0
            ) -> NoReturn:

        if not path.exists(file):
            mkdir(file)
        sleep(1.0)

        for index ,(name ,each) in enumerate(self.data.items() ,start=1):
            if index < start:
                continue
            try:
                if (respond:=rqs.get(r"https://magireco.moe/"+each["img"] ,headers=Memoria.headers)).ok:
                    image_url:str = etree.HTML(respond.text).xpath(xpath)[0]
                    with open(f"{file}//{name}.png" ,'wb') as file_ptr:
                        file_ptr.write(rqs.get(image_url ,headers=Memoria.headers).content)
                    print(f"{index} OKK!")
                else:
                    print(f"{index} FAILED!")

                sleep(0.6)
            except:
                pass


    def seek(self ,name:str) -> int:
        for index ,(key ,each) in enumerate(self.data.items() ,start=1):
            if name in key:
                print(f"{key} in {index}.")
                return index

结束

        我这个小项目里百分之八十的东西都在上面 ,请大家理性观看、应用、学习。

        支持大家评判!

PS:

项目写自2024/4/17(启航) - 2024/4/25(基本完成)

还在更新中...

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值