Commit c2161582 authored by timel's avatar timel

refactor: store

parent 75fdc88a
Pipeline #21353 passed with stages
in 18 minutes and 10 seconds
......@@ -15,5 +15,5 @@ export default (
dispatch(fetchHandsMeta({ controler: draw.player, codes: draw.cards }));
FIXME_fetchEsHintMeta({ originMsg: "玩家抽卡时" });
matStore.hands.add(draw.player, draw.cards);
matStore.hands.of(draw.player).add(draw.cards);
};
......@@ -20,8 +20,8 @@ import {
} from "@/reducers/duel/monstersSlice";
import { AppDispatch } from "@/store";
import {
valtioStore,
fetchOverlayMeta as FIXME_fetchOverlayMeta,
valtioStore,
} from "@/valtioStores";
import { REASON_MATERIAL } from "../../common";
......@@ -55,7 +55,7 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
case ygopro.CardZone.HAND:
case ygopro.CardZone.EXTRA: {
// 其余区域就是在list删掉这张卡
matStore.in(from.location).remove(from.controler, from.sequence);
matStore.in(from.location).of(from.controler).remove(from.sequence);
break;
}
// 仅仅去除超量素材
......@@ -86,14 +86,15 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
case ygopro.CardZone.SZONE: {
matStore
.in(to.location)
.setOccupant(to.controler, to.sequence, code, to.position);
.of(to.controler)
.setOccupant(to.sequence, code, to.position);
break;
}
case ygopro.CardZone.REMOVED:
case ygopro.CardZone.GRAVE:
case ygopro.CardZone.EXTRA:
case ygopro.CardZone.HAND: {
matStore.hands.insert(to.controler, to.sequence, code);
matStore.hands.of(to.controler).insert(to.sequence, code);
break;
}
case ygopro.CardZone.OVERLAY: {
......
......@@ -4,8 +4,8 @@ import { setMagicPosition, setMonsterPosition } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgPosChange = ygopro.StocGameMessage.MsgPosChange;
import {
matStore,
fetchEsHintMeta as FIXME_fetchEsHintMeta,
matStore,
} from "@/valtioStores";
export default (posChange: MsgPosChange, dispatch: AppDispatch) => {
const { location, controler, sequence } = posChange.card_info;
......
import { ygopro } from "@/api";
import { reloadField } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import { type DuelFieldState, matStore } from "@/valtioStores";
import { matStore } from "@/valtioStores";
type MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
......@@ -14,12 +14,12 @@ export default (field: MsgReloadField, dispatch: AppDispatch) => {
const gamers = ["me", "op"] as const;
gamers.forEach((gamer) => {
matStore.banishedZones[gamer] = [];
matStore.extraDecks[gamer] = [];
matStore.graveyards[gamer] = [];
matStore.hands[gamer] = [];
matStore.monsters[gamer] = [];
matStore.magics[gamer] = [];
matStore.banishedZones[gamer].length = 0;
matStore.extraDecks[gamer].length = 0;
matStore.graveyards[gamer].length = 0;
matStore.hands[gamer].length = 0;
matStore.monsters[gamer].length = 0;
matStore.magics[gamer].length = 0;
});
const { MZONE, SZONE, HAND, DECK, GRAVE, REMOVED, EXTRA } = ygopro.CardZone;
......
......@@ -11,7 +11,6 @@ import {
setEnableM2,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import {
clearAllIdleInteractivities as FIXME_clearAllIdleInteractivities,
matStore,
......@@ -88,7 +87,8 @@ export default (selectBattleCmd: MsgSelectBattleCmd, dispatch: AppDispatch) => {
if (tmp) {
matStore
.in(cardInfo.location)
.addIdleInteractivity(player, cardInfo.sequence, {
.of(player)
.addIdleInteractivity(cardInfo.sequence, {
...tmp,
interactType,
response: data.response,
......
import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
import { ygopro } from "@/api";
import { Interactivity, InteractType } from "@/reducers/duel/generic";
import {
......@@ -12,8 +14,6 @@ import {
setEnableEp,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
import {
clearAllIdleInteractivities as FIXME_clearAllIdleInteractivities,
matStore,
......@@ -85,7 +85,8 @@ export default (selectIdleCmd: MsgSelectIdleCmd, dispatch: AppDispatch) => {
if (tmp) {
matStore
.in(cardInfo.location)
.addIdleInteractivity(player, cardInfo.sequence, {
.of(player)
.addIdleInteractivity(cardInfo.sequence, {
...tmp,
interactType,
response: data.response,
......
......@@ -22,8 +22,9 @@ export default (selectPlace: MsgSelectPlace, dispatch: AppDispatch) => {
dispatch(
addMonsterPlaceInteractivities([place.controler, place.sequence])
);
matStore.monsters.setPlaceInteractivityType(
place.controler,
matStore.monsters
.of(place.controler)
.setPlaceInteractivityType(
place.sequence,
InteractType.PLACE_SELECTABLE
);
......@@ -33,8 +34,9 @@ export default (selectPlace: MsgSelectPlace, dispatch: AppDispatch) => {
dispatch(
addMagicPlaceInteractivities([place.controler, place.sequence])
);
matStore.magics.setPlaceInteractivityType(
place.controler,
matStore.magics
.of(place.controler)
.setPlaceInteractivityType(
place.sequence,
InteractType.PLACE_SELECTABLE
);
......
......@@ -76,7 +76,7 @@ export default (
matStore.magics.of(0).forEach((x) => (x.location.controler = 0));
matStore.magics.of(1).forEach((x) => (x.location.controler = 1));
matStore.decks.add(0, Array(start.deckSize1).fill(0));
matStore.decks.add(1, Array(start.deckSize2).fill(0));
matStore.decks.of(0).add(Array(start.deckSize1).fill(0));
matStore.decks.of(0).add(Array(start.deckSize2).fill(0));
dispatch(initHint()); // 直接删除
};
import type { ygopro } from "@/api";
import { fetchCard } from "@/api/cards";
import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api/strings";
import { matStore } from "../store";
const { hint } = matStore;
......
export * from "./fetchCheckCardMeta";
export * from "./fetchHint";
export * from "./getCardByLocation";
export * from "./fetchOverlayMeta";
export * from "./getCardByLocation";
......@@ -6,7 +6,6 @@ import { fetchCard } from "@/api/cards";
import type {
BothSide,
CardsBothSide,
CardState,
DuelFieldState,
InitInfo,
......@@ -23,76 +22,42 @@ const getWhom = (controller: number): "me" | "op" =>
/**
* 根据自己的先后手判断是否是自己
* 原本名字叫judgeSelf
*/
const isMe = (player: number): boolean => {
const isMe = (controller: number): boolean => {
switch (matStore.selfType) {
case 1:
// 自己是先攻
return player === 0;
return controller === 0;
case 2:
// 自己是后攻
return player === 1;
return controller === 1;
default:
// 目前不可能出现这种情况
console.error("judgeSelf error", player, matStore.selfType);
console.error("judgeSelf error", controller, matStore.selfType);
return false;
}
};
const genDuel = <T>(obj: T): BothSide<T> => {
const genDuel = <T extends {}>(meObj: T, opObj?: T): BothSide<T> => {
// 提供opObj是为了让meObj和opObj的类型可以不同,避免深拷贝的坑...
const res = proxy({
me: cloneDeep(obj),
op: cloneDeep(obj),
me: Object.assign(meObj, {
getController: () => (matStore.selfType == 1 ? 0 : 1),
}),
op: Object.assign(opObj ?? meObj, {
getController: () => (matStore.selfType == 1 ? 0 : 1),
}),
of: (controller: number) => res[getWhom(controller)],
});
return res;
};
/**
* 生成一个指定长度的卡片数组
*/
const genBlock = (location: ygopro.CardZone, n: number): DuelFieldState =>
Array(n)
.fill(null)
.map((_) => ({
location: {
location,
},
idleInteractivities: [],
counters: {},
}));
const initInfo: MatState["initInfo"] = proxy({
...genDuel({
masterRule: "UNKNOWN",
life: -1, // 特地设置一个不可能的值
deckSize: 0,
extraSize: 0,
}),
set: (controller: number, obj: Partial<InitInfo>) => {
initInfo[getWhom(controller)] = {
...initInfo[getWhom(controller)],
...obj,
};
},
});
const hint: MatState["hint"] = proxy({
code: -1,
});
/**
* 在决斗盘仓库之中,
* 给 `{me: [...], op: [...]}` 这种类型的对象添加一些方法。
* 具体的方法可以看`CardsBothSide`的类型定义
*/
const wrap = <T extends DuelFieldState>(
entity: BothSide<T>,
const addMethods = <T extends CardState[]>(
entity: T,
zone: ygopro.CardZone
): CardsBothSide<T> => {
/**
* 生成一个卡片,根据`id`获取卡片信息
*/
): DuelFieldState => {
/** 生成一个卡片,根据`id`获取卡片信息 */
const genCard = async (controller: number, id: number) => ({
occupant: await fetchCard(id, true),
location: {
......@@ -103,67 +68,94 @@ const wrap = <T extends DuelFieldState>(
idleInteractivities: [],
});
const res: CardsBothSide<T> = proxy({
...entity,
remove: (controller: number, sequence: number) => {
res.of(controller).splice(sequence, 1);
},
insert: async (controller: number, sequence: number, id: number) => {
const card = await genCard(controller, id);
res.of(controller).splice(sequence, 0, card);
},
add: async (controller: number, ids: number[]) => {
const res = proxy(entity) as unknown as DuelFieldState;
res.remove = (sequence: number) => {
res.splice(sequence, 1);
};
res.insert = async (sequence: number, id: number) => {
const card = await genCard(res.getController(), id);
res.splice(sequence, 0, card);
};
res.add = async (ids: number[]) => {
const cards = await Promise.all(
ids.map(async (id) => genCard(controller, id))
ids.map(async (id) => genCard(res.getController(), id))
);
res.of(controller).splice(res.of(controller).length, 0, ...cards);
},
setOccupant: async (
controller: number,
res.splice(res.length, 0, ...cards);
};
res.setOccupant = async (
sequence: number,
id: number,
position?: ygopro.CardPosition
) => {
const meta = await fetchCard(id);
const target = res.of(controller)[sequence];
const target = res[sequence];
target.occupant = meta;
if (position) {
target.location.position = position;
}
},
addIdleInteractivity: (
controller: number,
};
res.addIdleInteractivity = (
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) => {
res.of(controller)[sequence].idleInteractivities.push(interactivity);
},
clearIdleInteractivities: (controller: number) => {
res.of(controller).forEach((card) => (card.idleInteractivities = []));
},
setPlaceInteractivityType: (
controller: number,
res[sequence].idleInteractivities.push(interactivity);
};
res.clearIdleInteractivities = () => {
res.forEach((card) => (card.idleInteractivities = []));
};
res.setPlaceInteractivityType = (
sequence: number,
interactType: InteractType
) => {
res.of(controller)[sequence].placeInteractivity = {
res[sequence].placeInteractivity = {
interactType: interactType,
response: {
controler: controller,
controler: res.getController(),
zone,
sequence,
},
};
},
clearPlaceInteractivity: (controller: number) => {
res
.of(controller)
.forEach((card) => (card.placeInteractivity = undefined));
},
});
};
res.clearPlaceInteractivity = () => {
res.forEach((card) => (card.placeInteractivity = undefined));
};
return res;
};
/**
* 生成一个指定长度的卡片数组
*/
const genBlock = (location: ygopro.CardZone, n: number) =>
Array(n)
.fill(null)
.map((_) => ({
location: {
location,
},
idleInteractivities: [],
counters: {},
}));
const initInfo: MatState["initInfo"] = proxy({
...genDuel({
masterRule: "UNKNOWN",
life: -1, // 特地设置一个不可能的值
deckSize: 0,
extraSize: 0,
}),
set: (controller: number, obj: Partial<InitInfo>) => {
initInfo[getWhom(controller)] = {
...initInfo[getWhom(controller)],
...obj,
};
},
});
const hint: MatState["hint"] = proxy({
code: -1,
});
/**
* zone -> matStore
*/
......@@ -190,18 +182,25 @@ const getZone = (zone: ygopro.CardZone) => {
};
const { SZONE, MZONE, GRAVE, REMOVED, HAND, DECK, EXTRA } = ygopro.CardZone;
/**
* 💡 决斗盘状态仓库,本文件核心,
* 具体介绍可以点进`MatState`去看
*/
export const matStore: MatState = proxy<MatState>({
magics: wrap(genDuel(genBlock(SZONE, 6)), SZONE),
monsters: wrap(genDuel(genBlock(MZONE, 7)), MZONE),
graveyards: wrap(genDuel([]), GRAVE),
banishedZones: wrap(genDuel([]), REMOVED),
hands: wrap(genDuel([]), HAND),
decks: wrap(genDuel([]), DECK),
extraDecks: wrap(genDuel([]), EXTRA),
magics: genDuel(
addMethods(genBlock(SZONE, 6), SZONE),
addMethods(genBlock(SZONE, 6), SZONE)
),
monsters: genDuel(
addMethods(genBlock(MZONE, 7), MZONE),
addMethods(genBlock(MZONE, 7), MZONE)
),
graveyards: genDuel(addMethods([], GRAVE), addMethods([], GRAVE)),
banishedZones: genDuel(addMethods([], REMOVED), addMethods([], REMOVED)),
hands: genDuel(addMethods([], HAND), addMethods([], HAND)),
decks: genDuel(addMethods([], DECK), addMethods([], DECK)),
extraDecks: genDuel(addMethods([], EXTRA), addMethods([], EXTRA)),
timeLimits: {
// 时间限制
......@@ -229,3 +228,6 @@ export const matStore: MatState = proxy<MatState>({
in: getZone,
isMe,
});
// @ts-ignore
window.matStore = matStore;
......@@ -9,38 +9,38 @@ export interface BothSide<T> {
/** 根据controller返回对应的数组,op或者me */
of: (controller: number) => T;
}
export interface CardsBothSide<T extends DuelFieldState> extends BothSide<T> {
/**
* CardState的顺序index,被称为sequence
*/
export type DuelFieldState = CardState[] & {
getController: () => number;
/** 移除特定位置的卡片 */
remove: (player: number, sequence: number) => void;
remove: (sequence: number) => void;
/** 在末尾添加卡片 */
add: (controller: number, ids: number[]) => void;
insert: (sequence: number, id: number) => Promise<void>;
/** 在指定位置插入卡片 */
insert: (controller: number, sequence: number, id: number) => void;
add: (ids: number[]) => Promise<void>;
/** 设置占据这个位置的卡片信息 */
setOccupant: (
controller: number,
sequence: number,
id: number,
position?: ygopro.CardPosition
) => Promise<void>;
/** 添加 idle 的交互性 */
addIdleInteractivity: (
controller: number,
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) => void;
/** 移除 idle 的交互性 */
clearIdleInteractivities: (controller: number) => void;
clearIdleInteractivities: () => void;
/** 设置 place 的交互种类 */
setPlaceInteractivityType: (
controller: number,
sequence: number,
interactType: InteractType
) => void;
/** 移除 place 的交互性 */
clearPlaceInteractivity: (controller: number) => void;
}
clearPlaceInteractivity: () => void;
};
export interface MatState {
selfType: number;
......@@ -49,19 +49,19 @@ export interface MatState {
set: (controller: number, obj: Partial<InitInfo>) => void;
}; // 双方的初始化信息
hands: CardsBothSide<HandState>; // 双方的手牌
hands: BothSide<HandState>; // 双方的手牌
monsters: CardsBothSide<MonsterState>; // 双方的怪兽区状态
monsters: BothSide<MonsterState>; // 双方的怪兽区状态
magics: CardsBothSide<MagicState>; // 双方的魔法区状态
magics: BothSide<MagicState>; // 双方的魔法区状态
graveyards: CardsBothSide<GraveyardState>; // 双方的墓地状态
graveyards: BothSide<GraveyardState>; // 双方的墓地状态
banishedZones: CardsBothSide<BanishedZoneState>; // 双方的除外区状态
banishedZones: BothSide<BanishedZoneState>; // 双方的除外区状态
decks: CardsBothSide<DeckState>; // 双方的卡组状态
decks: BothSide<DeckState>; // 双方的卡组状态
extraDecks: CardsBothSide<ExtraDeckState>; // 双方的额外卡组状态
extraDecks: BothSide<ExtraDeckState>; // 双方的额外卡组状态
timeLimits: BothSide<number> & {
set: (controller: number, time: number) => void;
......@@ -81,7 +81,7 @@ export interface MatState {
// >>> methods >>>
/** 根据zone获取hands/masters/magics... */
in: (zone: ygopro.CardZone) => CardsBothSide<DuelFieldState>;
in: (zone: ygopro.CardZone) => BothSide<DuelFieldState>;
/** 根据自己的先后手判断是否是自己 */
isMe: (player: number) => boolean;
}
......@@ -115,11 +115,6 @@ export interface CardState {
reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
}
/**
* CardState的顺序index,被称为sequence
*/
export type DuelFieldState = CardState[];
export interface Interactivity<T> {
interactType: InteractType;
// 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号
......
import { matStore } from "@/valtioStores";
export const clearAllIdleInteractivities = (controller: number) => {
matStore.banishedZones.clearIdleInteractivities(controller);
matStore.decks.clearIdleInteractivities(controller);
matStore.extraDecks.clearIdleInteractivities(controller);
matStore.graveyards.clearIdleInteractivities(controller);
matStore.hands.clearIdleInteractivities(controller);
matStore.magics.clearIdleInteractivities(controller);
matStore.monsters.clearIdleInteractivities(controller);
matStore.banishedZones.of(controller).clearIdleInteractivities();
matStore.decks.of(controller).clearIdleInteractivities();
matStore.extraDecks.of(controller).clearIdleInteractivities();
matStore.graveyards.of(controller).clearIdleInteractivities();
matStore.hands.of(controller).clearIdleInteractivities();
matStore.magics.of(controller).clearIdleInteractivities();
matStore.monsters.of(controller).clearIdleInteractivities();
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment