Build Hokm card game: offline vs-AI + online social/gamification (mock backend)
- Pure-TS Hokm engine (deal, hakem, trump, tricks, scoring, Kot) + AI bots - Persian-luxury RTL UI (Next 16 / React 19 / Tailwind v4 / Framer Motion / Zustand) - Online platform behind OnlineService seam (mock now, .NET SignalR later): auth (phone OTP + email/Google), profiles, friends, private rooms with partner pick, ranked matchmaking, leaderboard, shop - Gamification: ranks/leagues, coins, XP/levels, daily rewards, achievements - i18n fa/en, PWA manifest, engine + gamification sims Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { create } from "zustand";
|
||||
import { CreateRoomOptions, MatchmakingOptions, getService } from "./online/service";
|
||||
import {
|
||||
Friend,
|
||||
FriendRequest,
|
||||
LeaderboardEntry,
|
||||
MatchmakingState,
|
||||
Room,
|
||||
} from "./online/types";
|
||||
|
||||
interface OnlineStore {
|
||||
friends: Friend[];
|
||||
requests: FriendRequest[];
|
||||
room: Room | null;
|
||||
matchmaking: MatchmakingState;
|
||||
leaderboard: LeaderboardEntry[];
|
||||
|
||||
loadFriends: () => Promise<void>;
|
||||
addFriend: (q: string) => Promise<{ ok: boolean; messageFa: string; messageEn: string }>;
|
||||
acceptRequest: (id: string) => Promise<void>;
|
||||
declineRequest: (id: string) => Promise<void>;
|
||||
removeFriend: (id: string) => Promise<void>;
|
||||
|
||||
createRoom: (opts: CreateRoomOptions) => Promise<void>;
|
||||
setPartner: (friendId: string | null) => Promise<void>;
|
||||
inviteToSeat: (seat: 1 | 3, friendId: string) => Promise<void>;
|
||||
addBot: (seat: 1 | 2 | 3) => Promise<void>;
|
||||
clearSeat: (seat: 1 | 2 | 3) => Promise<void>;
|
||||
startRoom: () => Promise<void>;
|
||||
leaveRoom: () => Promise<void>;
|
||||
|
||||
startMatchmaking: (opts: MatchmakingOptions) => Promise<void>;
|
||||
cancelMatchmaking: () => Promise<void>;
|
||||
|
||||
loadLeaderboard: () => Promise<void>;
|
||||
}
|
||||
|
||||
let roomUnsub: (() => void) | null = null;
|
||||
let mmUnsub: (() => void) | null = null;
|
||||
let friendUnsub: (() => void) | null = null;
|
||||
|
||||
export const useOnlineStore = create<OnlineStore>((set, get) => ({
|
||||
friends: [],
|
||||
requests: [],
|
||||
room: null,
|
||||
matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 },
|
||||
leaderboard: [],
|
||||
|
||||
loadFriends: async () => {
|
||||
const svc = getService();
|
||||
const [friends, requests] = await Promise.all([svc.listFriends(), svc.listRequests()]);
|
||||
set({ friends, requests });
|
||||
if (!friendUnsub) friendUnsub = svc.onFriends((f) => set({ friends: f }));
|
||||
},
|
||||
|
||||
addFriend: async (q) => {
|
||||
const res = await getService().addFriend(q);
|
||||
if (res.ok) await get().loadFriends();
|
||||
return res;
|
||||
},
|
||||
acceptRequest: async (id) => {
|
||||
await getService().acceptRequest(id);
|
||||
const requests = await getService().listRequests();
|
||||
set({ requests });
|
||||
},
|
||||
declineRequest: async (id) => {
|
||||
await getService().declineRequest(id);
|
||||
set({ requests: get().requests.filter((r) => r.id !== id) });
|
||||
},
|
||||
removeFriend: async (id) => {
|
||||
await getService().removeFriend(id);
|
||||
},
|
||||
|
||||
createRoom: async (opts) => {
|
||||
const svc = getService();
|
||||
const room = await svc.createRoom(opts);
|
||||
set({ room });
|
||||
if (roomUnsub) roomUnsub();
|
||||
roomUnsub = svc.onRoom((r) => set({ room: { ...r } }));
|
||||
},
|
||||
setPartner: async (friendId) => {
|
||||
const r = await getService().setPartner(get().room!.id, friendId);
|
||||
set({ room: { ...r } });
|
||||
},
|
||||
inviteToSeat: async (seat, friendId) => {
|
||||
const r = await getService().inviteToSeat(get().room!.id, seat, friendId);
|
||||
set({ room: { ...r } });
|
||||
},
|
||||
addBot: async (seat) => {
|
||||
const r = await getService().addBot(get().room!.id, seat);
|
||||
set({ room: { ...r } });
|
||||
},
|
||||
clearSeat: async (seat) => {
|
||||
const r = await getService().clearSeat(get().room!.id, seat);
|
||||
set({ room: { ...r } });
|
||||
},
|
||||
startRoom: async () => {
|
||||
const r = await getService().startRoom(get().room!.id);
|
||||
set({ room: { ...r } });
|
||||
},
|
||||
leaveRoom: async () => {
|
||||
if (get().room) await getService().leaveRoom(get().room!.id);
|
||||
if (roomUnsub) {
|
||||
roomUnsub();
|
||||
roomUnsub = null;
|
||||
}
|
||||
set({ room: null });
|
||||
},
|
||||
|
||||
startMatchmaking: async (opts) => {
|
||||
const svc = getService();
|
||||
if (mmUnsub) mmUnsub();
|
||||
mmUnsub = svc.onMatchmaking((s) => set({ matchmaking: s }));
|
||||
await svc.startMatchmaking(opts);
|
||||
},
|
||||
cancelMatchmaking: async () => {
|
||||
await getService().cancelMatchmaking();
|
||||
if (mmUnsub) {
|
||||
mmUnsub();
|
||||
mmUnsub = null;
|
||||
}
|
||||
set({ matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 } });
|
||||
},
|
||||
|
||||
loadLeaderboard: async () => {
|
||||
const leaderboard = await getService().getLeaderboard();
|
||||
set({ leaderboard });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user