前排提醒
1- 本系列文章的所有技术仅支持合法的技术讨论 ,不参与任何形式的盈利与不当行动。
2- 游戏数值图片音乐等爬取于《魔法纪录中文Wiki》与《Magic Record English Wiki》。
3- 爬虫会对服务器造成很大伤害 ,不要模仿、照抄。
4- 本文仅在此账号(DaisyMosuki)上发表 ,可以转载 ,可以学习 ,不允许私用。
5- 有想要源码的 ,请私聊(无偿);有具体不理解的地方 ,请发到评论区。
不喜勿喷 ,手下留情。
理由
有款游戏呢 ,叫《魔法纪录》 ,之前我很喜欢玩 ,但是呢 ,国服关了 ,我“心有余而力不足” ;那 ,不妨让我们实现一下游戏的逻辑。
游戏介绍
该游戏是由日本公司制作的回合制游戏 ,具体请见百度百科:魔法纪录(Aniplex发行的手机游戏)_百度百科
思考一下
那么 ,让我们思考一下 ,如何开始?
一个屏幕是需要的、人物是需要的、游戏图片音乐与数据是需要的...
“战斗逻辑”也必不可少:
随机discs -> 选定discs -> 根据顺序攻击 -> 造成对方伤害 -> 对方反击 -> 造成我方伤害...
作者认为呢 ,硬要说个 “最复杂的部分” ,可能也就是 “让计算机知道谁是谁” ,其他的部分就 “迎刃而解”了。
初步实现
所以说 ,我们的目标很明确 :
1- 实现一个屏幕类 ,提供基本的屏幕操作。
2- 实现一个人物抽象类 ,抽象类生人物类 ,在其中实现基本逻辑。
3- 当然还有主函数 ,与各种技能什么的 ,以及爬取各种数据、位置。
之后直接上代码 ,代码的粗略解释在注释里
(不是完整的代码 ,是精简出来的)
军备展示
# -*- coding: utf-8 -*-
# @author: DaisyMosuki - only in 优快云
#进行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(基本完成)
还在更新中...