Commit 0b6eeba7 authored by Chunchi Che's avatar Chunchi Che

Merge branch 'feat/cards/service' into 'main'

Feat/cards/service

See merge request mycard/Neos!21
parents 8afb47b7 8723b7d8
...@@ -5,7 +5,7 @@ export interface CardMeta { ...@@ -5,7 +5,7 @@ export interface CardMeta {
data: { data: {
ot?: number; ot?: number;
setcode?: number; setcode?: number;
type?: number; type_?: number;
atk?: number; atk?: number;
def?: number; def?: number;
level?: number; level?: number;
...@@ -27,7 +27,7 @@ export interface CardMeta { ...@@ -27,7 +27,7 @@ export interface CardMeta {
* *
* */ * */
export async function fetchCard(id: number): Promise<CardMeta> { export async function fetchCard(id: number): Promise<CardMeta> {
const res = await axios.get<CardMeta>("https://ygocdb.com/api/v0/card/" + id); const res = await axios.get<CardMeta>("http://localhost:3030/cards/" + id);
return res.data; return res.data;
} }
/*
* 卡牌数据存储
*
* */
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "../api/cards";
import { RootState } from "../store";
export const fetchCardMetaById = createAsyncThunk(
"cards/fetchByIdStatus",
async (cardId: number) => {
return await fetchCard(cardId);
}
);
export interface Card {
meta?: CardMeta;
state: string;
}
export interface CardMetaState {
metas: Map<number, Card>;
}
const initialState: CardMetaState = {
metas: new Map(),
};
const cardsSlice = createSlice({
name: "cards",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchCardMetaById.fulfilled, (state, action) => {
const id = action.payload.id;
const card = {
meta: action.payload,
state: "filled",
};
state.metas.set(id, card);
}); // TODO: handle pending and rejected
},
});
export const selectCards = (state: RootState) => state.cards.metas;
export default cardsSlice.reducer;
import { PayloadAction, CaseReducer } from "@reduxjs/toolkit"; import {
PayloadAction,
CaseReducer,
createAsyncThunk,
ActionReducerMapBuilder,
} from "@reduxjs/toolkit";
import { DuelState } from "./mod"; import { DuelState } from "./mod";
import { RootState } from "../../store"; import { RootState } from "../../store";
import { CardMeta, fetchCard } from "../../api/cards";
export interface Hands { export interface Hands {
cards: number[]; // TODO: use Card struct Unitly cards: CardMeta[];
} }
// 自己增加手牌 // 自己增加手牌
...@@ -11,10 +17,13 @@ export const meAddHandsImpl: CaseReducer<DuelState, PayloadAction<number[]>> = ( ...@@ -11,10 +17,13 @@ export const meAddHandsImpl: CaseReducer<DuelState, PayloadAction<number[]>> = (
state, state,
action action
) => { ) => {
const cards = action.payload.map((id) => {
return { id, data: {}, text: {} };
});
if (state.meHands) { if (state.meHands) {
state.meHands.cards = state.meHands.cards.concat(action.payload); state.meHands.cards = state.meHands.cards.concat(cards);
} else { } else {
state.meHands = { cards: action.payload }; state.meHands = { cards };
} }
}; };
...@@ -23,13 +32,39 @@ export const opAddHandsImpl: CaseReducer<DuelState, PayloadAction<number[]>> = ( ...@@ -23,13 +32,39 @@ export const opAddHandsImpl: CaseReducer<DuelState, PayloadAction<number[]>> = (
state, state,
action action
) => { ) => {
const cards = action.payload.map((id) => {
return { id, data: {}, text: {} };
});
if (state.opHands) { if (state.opHands) {
state.opHands.cards = state.opHands.cards.concat(action.payload); state.opHands.cards = state.opHands.cards.concat(cards);
} else { } else {
state.opHands = { cards: action.payload }; state.opHands = { cards };
} }
}; };
export const fetchMeHandsMeta = createAsyncThunk(
"duel/fetchMeHandsMeta",
async (Ids: number[]) => {
return await Promise.all(
Ids.map(async (id) => {
return await fetchCard(id);
})
);
}
);
export const meHandsCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchMeHandsMeta.fulfilled, (state, action) => {
// TODO: 合法性校验
const cards = action.payload;
if (state.meHands) {
state.meHands.cards = cards;
} else {
state.meHands = { cards };
}
});
};
export const selectMeHands = (state: RootState) => export const selectMeHands = (state: RootState) =>
state.duel.meHands || { cards: [] }; state.duel.meHands || { cards: [] };
export const selectOpHands = (state: RootState) => export const selectOpHands = (state: RootState) =>
......
...@@ -5,7 +5,12 @@ ...@@ -5,7 +5,12 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
import { InitInfo, meInfoInitImpl, opInfoInitImpl } from "./initInfoSlice"; import { InitInfo, meInfoInitImpl, opInfoInitImpl } from "./initInfoSlice";
import { Hands, meAddHandsImpl, opAddHandsImpl } from "./handsSlice"; import {
Hands,
meAddHandsImpl,
opAddHandsImpl,
meHandsCase,
} from "./handsSlice";
import { RootState } from "../../store"; import { RootState } from "../../store";
export interface DuelState { export interface DuelState {
...@@ -26,6 +31,9 @@ const duelSlice = createSlice({ ...@@ -26,6 +31,9 @@ const duelSlice = createSlice({
meAddHands: meAddHandsImpl, meAddHands: meAddHandsImpl,
opAddHands: opAddHandsImpl, opAddHands: opAddHandsImpl,
}, },
extraReducers(builder) {
meHandsCase(builder);
},
}); });
export const { meInfoInit, opInfoInit, meAddHands, opAddHands } = export const { meInfoInit, opInfoInit, meAddHands, opAddHands } =
......
import { ygopro } from "../../api/ocgcore/idl/ocgcore"; import { ygopro } from "../../api/ocgcore/idl/ocgcore";
import { AppDispatch } from "../../store"; import { AppDispatch } from "../../store";
import { meAddHands, opAddHands } from "../../reducers/duel/mod"; import { meAddHands, opAddHands } from "../../reducers/duel/mod";
import { fetchMeHandsMeta } from "../../reducers/duel/handsSlice";
export default ( export default (
draw: ygopro.StocGameMessage.MsgDraw, draw: ygopro.StocGameMessage.MsgDraw,
...@@ -9,6 +10,7 @@ export default ( ...@@ -9,6 +10,7 @@ export default (
// FIXME: draw.player 和先后攻有关系 // FIXME: draw.player 和先后攻有关系
if (draw.player === 0) { if (draw.player === 0) {
dispatch(meAddHands(draw.cards)); dispatch(meAddHands(draw.cards));
dispatch(fetchMeHandsMeta(draw.cards));
} else if (draw.player === 1) { } else if (draw.player === 1) {
dispatch(opAddHands(draw.cards)); dispatch(opAddHands(draw.cards));
} else { } else {
......
...@@ -7,7 +7,6 @@ import chatReducer from "./reducers/chatSlice"; ...@@ -7,7 +7,6 @@ import chatReducer from "./reducers/chatSlice";
import playerReducer from "./reducers/playerSlice"; import playerReducer from "./reducers/playerSlice";
import moraReducer from "./reducers/moraSlice"; import moraReducer from "./reducers/moraSlice";
import duelReducer from "./reducers/duel/mod"; import duelReducer from "./reducers/duel/mod";
import cardsReducer from "./reducers/cardsSlice";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
...@@ -16,7 +15,6 @@ export const store = configureStore({ ...@@ -16,7 +15,6 @@ export const store = configureStore({
player: playerReducer, player: playerReducer,
mora: moraReducer, mora: moraReducer,
duel: duelReducer, duel: duelReducer,
cards: cardsReducer,
}, },
}); });
......
/*
* 决斗界面渲染需要的数据结构
*
* */
export interface Hand {
code: number; // Currently only code
}
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
* *
* */ * */
import * as DuelData from "./data";
import React from "react"; import React from "react";
import type { RootState } from "../../store"; import type { RootState } from "../../store";
import { CardMeta } from "../../api/cards";
/* /*
* 通用的决斗界面抽象接口 * 通用的决斗界面抽象接口
...@@ -26,7 +26,7 @@ export interface IDuelPlate { ...@@ -26,7 +26,7 @@ export interface IDuelPlate {
// 渲染接口,返回一个React组件 // 渲染接口,返回一个React组件
render(): React.ReactElement; render(): React.ReactElement;
// 注册手牌selector // 注册手牌selector
registerHands(selector: TypeSelector<DuelData.Hand[]>): void; registerHands(selector: TypeSelector<CardMeta[]>): void;
} }
export interface TypeSelector<T> { export interface TypeSelector<T> {
......
...@@ -12,11 +12,7 @@ export default function Duel() { ...@@ -12,11 +12,7 @@ export default function Duel() {
// TODO: opHands // TODO: opHands
const handsSelector = (state: RootState) => { const handsSelector = (state: RootState) => {
const cards = selectMeHands(state).cards; return selectMeHands(state).cards;
return cards.map((item) => {
return { code: item };
});
}; };
simpleDuelPlate.registerHands(handsSelector); simpleDuelPlate.registerHands(handsSelector);
......
import { Hand } from "../data";
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "./config"; import * as CONFIG from "./config";
import { Card } from "../../../reducers/cardsSlice"; import { CardMeta } from "../../../api/cards";
export default ( export default (hands: CardMeta[], scene: BABYLON.Scene) => {
hands: Hand[],
cardMetas: Map<number, Card>,
scene: BABYLON.Scene
) => {
const groundShape = CONFIG.GroundShape(); const groundShape = CONFIG.GroundShape();
const handShape = CONFIG.HandShape(); const handShape = CONFIG.HandShape();
const gap = groundShape.width / hands.length; const gap = groundShape.width / hands.length;
...@@ -36,9 +31,7 @@ export default ( ...@@ -36,9 +31,7 @@ export default (
new BABYLON.ExecuteCodeAction( new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnPickTrigger, BABYLON.ActionManager.OnPickTrigger,
(event) => { (event) => {
const meta = cardMetas.get(item.code); console.log(`<Click>hand: ${idx}`, "card:", item, "event:", event);
console.log(`<Click>hand: ${idx}`, "card:", meta, "event:", event);
} }
) )
); );
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
* */ * */
import { IDuelPlate, TypeSelector } from "../duel"; import { IDuelPlate, TypeSelector } from "../duel";
import * as DuelData from "../data";
import { useAppSelector } from "../../../hook"; import { useAppSelector } from "../../../hook";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import type { RootState } from "../../../store"; import type { RootState } from "../../../store";
...@@ -14,34 +13,24 @@ import renderMonsters from "./monsters"; ...@@ -14,34 +13,24 @@ import renderMonsters from "./monsters";
import renderExtraMonsters from "./extraMonsters"; import renderExtraMonsters from "./extraMonsters";
import renderMagics from "./magics"; import renderMagics from "./magics";
import * as CONFIG from "./config"; import * as CONFIG from "./config";
import { fetchCardMetaById } from "../../../reducers/cardsSlice"; import { CardMeta } from "../../../api/cards";
import { store } from "../../../store";
import { selectCards } from "../../../reducers/cardsSlice";
// CONFIG // CONFIG
export default class SimpleDuelPlateImpl implements IDuelPlate { export default class SimpleDuelPlateImpl implements IDuelPlate {
handsSelector?: TypeSelector<DuelData.Hand[]>; handsSelector?: TypeSelector<CardMeta[]>;
constructor() {} constructor() {}
render(): React.ReactElement { render(): React.ReactElement {
const dispatch = store.dispatch;
// ----- 数据获取 ----- // ----- 数据获取 -----
// 默认的手牌Selector,返回五个code为-1的Card。 // 默认的手牌Selector,返回五个code为-1的Card。
const defaultHandsSelector = (_: RootState) => { const defaultHandsSelector = (_: RootState) => {
return new Array(5).fill({ code: -1 }); return [];
}; };
const hands = useAppSelector(this.handsSelector || defaultHandsSelector); const hands = useAppSelector(this.handsSelector || defaultHandsSelector);
// TODO: 这里应该思考更合理的处理方式
hands.forEach((item) => {
dispatch(fetchCardMetaById(item.code));
});
const cardMetas = useAppSelector(selectCards);
// ----- WebGL渲染 ----- // ----- WebGL渲染 -----
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
...@@ -77,7 +66,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate { ...@@ -77,7 +66,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate {
renderExtraMonsters(scene); renderExtraMonsters(scene);
// 创建手牌 // 创建手牌
renderHands(hands, cardMetas, scene); renderHands(hands, scene);
// 创建地板 // 创建地板
const ground = BABYLON.MeshBuilder.CreateGround( const ground = BABYLON.MeshBuilder.CreateGround(
...@@ -90,7 +79,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate { ...@@ -90,7 +79,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate {
engine.runRenderLoop(() => { engine.runRenderLoop(() => {
scene.render(); scene.render();
}); });
}, [canvasRef, hands, cardMetas]); }, [canvasRef, hands]);
return ( return (
<canvas <canvas
...@@ -101,7 +90,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate { ...@@ -101,7 +90,7 @@ export default class SimpleDuelPlateImpl implements IDuelPlate {
); );
} }
registerHands(selector: TypeSelector<DuelData.Hand[]>): void { registerHands(selector: TypeSelector<CardMeta[]>): void {
this.handsSelector = selector; this.handsSelector = selector;
} }
} }
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