Integrate draft/network stack via Vuex #26

Merged
hamcha merged 5 commits from feature/vuex into master 2019-09-17 13:22:43 +00:00
14 changed files with 299 additions and 8 deletions

View file

@ -25,6 +25,12 @@ export class PeerClient extends Client {
this.connection.on("open", () => { this.connection.on("open", () => {
this.emit("connected"); this.emit("connected");
}); });
this.connection.on("close", () => {
this.emit("disconnected");
});
this.connection.on("error", err => {
this.emit("error", err);
});
this.connection.on("data", data => { this.connection.on("data", data => {
this._received(data); this._received(data);
}); });
@ -34,6 +40,10 @@ export class PeerClient extends Client {
return this.metadata.name; return this.metadata.name;
} }
public get id(): string {
return this.peer.id;
}
public send<T extends NetworkMessage>(data: T) { public send<T extends NetworkMessage>(data: T) {
if (!this.connection) { if (!this.connection) {
throw new Error("Client is not connected to a server"); throw new Error("Client is not connected to a server");

View file

@ -234,6 +234,10 @@ export class PeerServer extends EventEmitter {
this.send<T>(player, message); this.send<T>(player, message);
} }
} }
public get id(): string {
return this.peer.id;
}
} }
export default PeerServer; export default PeerServer;

View file

@ -0,0 +1,12 @@
import { ActionTree } from "vuex";
import { AppState } from "../types";
import { DraftState } from "./types";
import { Card } from "@/mlpccg";
const actions: ActionTree<DraftState, AppState> = {
pickCard({ commit }, card: Card) {
//TODO
}
};
export default actions;

View file

@ -0,0 +1,12 @@
import { GetterTree } from "vuex";
import { AppState } from "../types";
import { DraftState } from "./types";
import { createPonyheadURL } from "@/mlpccg";
const getters: GetterTree<DraftState, AppState> = {
ponyheadURL(state): string {
return createPonyheadURL(state.picks);
}
};
export default getters;

27
src/store/draft/index.ts Normal file
View file

@ -0,0 +1,27 @@
import { DraftState } from "./types";
import { AppState } from "../types";
import { Module } from "vuex";
const namespaced = true;
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
export const state: DraftState = {
cards: [],
picks: [],
pod: [],
packCount: 0,
currentPack: 0
};
export const draft: Module<DraftState, AppState> = {
namespaced,
state,
actions,
mutations,
getters
};
export default draft;

View file

@ -0,0 +1,37 @@
import { MutationTree } from "vuex";
import { DraftState, PlayerStatus } from "./types";
import { Card } from "@/mlpccg";
const mutations: MutationTree<DraftState> = {
playerPicked(state, payload: { name: string; picked: boolean }) {
const idx = state.pod.findIndex(p => p.name == payload.name);
state.pod[idx].picked = payload.picked;
},
resetPickStatus(state) {
state.pod = state.pod.map(p => ({ ...p, picked: false }));
},
setCardPool(state, pool: Card[]) {
state.cards = pool;
},
setPackInfo(state, payload: { current: number; total: number }) {
state.currentPack = payload.current;
state.packCount = payload.total;
},
addPicks(state, pick: Card) {
state.picks.push(pick);
},
setPod(state, pod: PlayerStatus[]) {
state.pod = pod;
},
resetPicks(state) {
state.picks = [];
}
};
export default mutations;

21
src/store/draft/types.ts Normal file
View file

@ -0,0 +1,21 @@
import { Session } from "@/mlpccg/draft";
import { Card } from "@/mlpccg";
export interface DraftState {
session?: Session;
pod: PlayerStatus[];
cards: Card[];
picks: Card[];
// Multiple pack draft
packCount: number;
currentPack: number;
}
export interface PlayerStatus {
name: string;
isBot: boolean;
isMe: boolean;
picked: boolean;
}

View file

@ -8,6 +8,9 @@ import actions from "./actions";
import mutations from "./mutations"; import mutations from "./mutations";
import getters from "./getters"; import getters from "./getters";
import network from "./network";
import draft from "./draft";
const store: StoreOptions<AppState> = { const store: StoreOptions<AppState> = {
state: { state: {
loaded: false, loaded: false,
@ -17,7 +20,10 @@ const store: StoreOptions<AppState> = {
actions, actions,
mutations, mutations,
getters, getters,
modules: {} modules: {
network,
draft
}
}; };
export default new Vuex.Store<AppState>(store); export default new Vuex.Store<AppState>(store);

View file

@ -0,0 +1,29 @@
import { ActionTree } from "vuex";
import { AppState } from "../types";
import { NetworkState, StartServerOptions, ConnectOptions } from "./types";
import { PeerServer, LocalClient, PeerClient } from "@/network";
const actions: ActionTree<NetworkState, AppState> = {
startServer({ commit }, options: StartServerOptions) {
const local = new LocalClient(options.playerInfo);
const server = new PeerServer(options.roomInfo, local, options._customPeer);
commit("becomeServer", { local, server });
},
connect({ commit }, options: ConnectOptions) {
const client = new PeerClient(options.playerInfo, options._customPeer);
commit("becomeClient", { peer: client });
client.on("connected", () => {
commit("connected");
});
client.on("disconnected", () => {
commit("disconnected");
});
client.on("error", err => {
commit("connectionError", err);
});
client.connect(options.serverID);
}
};
export default actions;

View file

@ -0,0 +1,21 @@
import { GetterTree } from "vuex";
import { AppState } from "../types";
import { NetworkState } from "./types";
const getters: GetterTree<NetworkState, AppState> = {
peerID(state): string | null {
switch (state.peerType) {
case "server":
return state.server.id;
case "client":
return state.peer.id;
}
return null;
},
connectionType(state): "client" | "server" | "none" {
return state.peerType;
}
};
export default getters;

View file

@ -0,0 +1,24 @@
import { NetworkState } from "./types";
import { AppState } from "../types";
import { Module } from "vuex";
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
const namespaced = true;
export const state: NetworkState = {
peerType: "none",
chatLog: []
};
export const network: Module<NetworkState, AppState> = {
namespaced,
state,
actions,
mutations,
getters
};
export default network;

View file

@ -0,0 +1,38 @@
import { MutationTree } from "vuex";
import { NetworkState, ServerNetworkState, ClientNetworkState } from "./types";
import { LocalClient, PeerServer, PeerClient } from "@/network";
const mutations: MutationTree<NetworkState> = {
becomeServer(state, payload: { local: LocalClient; server: PeerServer }) {
state = {
...state,
peerType: "server",
local: payload.local,
server: payload.server
};
},
becomeClient(state, payload: { peer: PeerClient }) {
state = {
...state,
connectionStatus: "connecting",
peerType: "client",
peer: payload.peer
};
},
connected(state) {
(state as ClientNetworkState).connectionStatus = "connected";
},
disconnected(state) {
(state as ClientNetworkState).connectionStatus = "disconnected";
},
connectionError(state, error) {
(state as ClientNetworkState).connectionStatus = "error";
(state as ClientNetworkState).connectionError = error;
}
};
export default mutations;

View file

@ -0,0 +1,56 @@
import {
PeerClient,
PeerServer,
LocalClient,
RoomInfo,
PeerMetadata
} from "@/network";
import Peer from "peerjs";
export interface ChatMessage {
who: string;
to: string;
message: string;
}
export interface SharedNetworkState {
chatLog: ChatMessage[];
}
export interface NoNetworkState extends SharedNetworkState {
peerType: "none";
}
export interface ClientNetworkState extends SharedNetworkState {
peerType: "client";
connectionStatus: "connecting" | "connected" | "disconnected" | "error";
connectionError?: Error;
peer: PeerClient;
}
export interface ServerNetworkState extends SharedNetworkState {
peerType: "server";
server: PeerServer;
local: LocalClient;
}
export type NetworkState =
| NoNetworkState
| ClientNetworkState
| ServerNetworkState;
export interface StartServerOptions {
roomInfo: RoomInfo;
playerInfo: PeerMetadata;
// Testing utils
_customPeer?: Peer;
}
export interface ConnectOptions {
serverID: string;
playerInfo: PeerMetadata;
// Testing utils
_customPeer?: Peer;
}

View file

@ -152,13 +152,7 @@ import { Component, Vue } from "vue-property-decorator";
import { getCards, CardSlot, cardLimit, Card } from "@/mlpccg"; import { getCards, CardSlot, cardLimit, Card } from "@/mlpccg";
import CardPicker from "@/components/DeckBuilder/CardPicker.vue"; import CardPicker from "@/components/DeckBuilder/CardPicker.vue";
import DeckList from "@/components/DeckBuilder/DeckList.vue"; import DeckList from "@/components/DeckBuilder/DeckList.vue";
import { PlayerStatus } from "@/store/draft/types";
interface PlayerStatus {
name: string;
isBot: boolean;
isMe: boolean;
picked: boolean;
}
@Component({ @Component({
components: { components: {