@beta-gamer/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +368 -0
- package/dist/index.mjs +324 -0
- package/package.json +36 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import * as socket_io_client from 'socket.io-client';
|
|
4
|
+
import { Socket } from 'socket.io-client';
|
|
5
|
+
import * as _socket_io_component_emitter from '@socket.io/component-emitter';
|
|
6
|
+
|
|
7
|
+
type GameType = 'chess' | 'checkers' | 'connect4' | 'tictactoe' | 'subway-runner';
|
|
8
|
+
type MatchType = 'matchmaking' | 'private' | 'bot';
|
|
9
|
+
type SessionMode = 'live' | 'test' | 'training';
|
|
10
|
+
type SessionStatus = 'pending' | 'active' | 'ended';
|
|
11
|
+
interface SessionPlayer {
|
|
12
|
+
id: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
}
|
|
15
|
+
interface SessionTheme {
|
|
16
|
+
primaryColor?: string;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
boardLight?: string;
|
|
19
|
+
boardDark?: string;
|
|
20
|
+
fontFamily?: string;
|
|
21
|
+
[key: string]: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
interface SessionTokenPayload {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
game: GameType;
|
|
26
|
+
mode: SessionMode;
|
|
27
|
+
matchType: MatchType;
|
|
28
|
+
tenantId: string;
|
|
29
|
+
players: SessionPlayer[];
|
|
30
|
+
roomCode: string | null;
|
|
31
|
+
theme: SessionTheme;
|
|
32
|
+
exp: number;
|
|
33
|
+
}
|
|
34
|
+
interface GameState {
|
|
35
|
+
status: SessionStatus;
|
|
36
|
+
players: SessionPlayer[];
|
|
37
|
+
currentTurn?: string;
|
|
38
|
+
winner?: string | null;
|
|
39
|
+
reason?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface BetaGamerContextValue {
|
|
43
|
+
token: string;
|
|
44
|
+
session: SessionTokenPayload;
|
|
45
|
+
socket: Socket | null;
|
|
46
|
+
gameState: GameState;
|
|
47
|
+
theme: SessionTheme;
|
|
48
|
+
}
|
|
49
|
+
interface BetaGamerProviderProps {
|
|
50
|
+
token: string;
|
|
51
|
+
serverUrl?: string;
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
}
|
|
54
|
+
declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
|
|
55
|
+
declare function useBetaGamer(): BetaGamerContextValue;
|
|
56
|
+
|
|
57
|
+
/** Returns the current game state (status, players, winner, etc.) */
|
|
58
|
+
declare function useGameState(): GameState;
|
|
59
|
+
/** Returns the decoded session payload (game, mode, matchType, players, theme) */
|
|
60
|
+
declare function useSession(): SessionTokenPayload;
|
|
61
|
+
/** Returns the raw Socket.IO socket — for advanced custom event handling */
|
|
62
|
+
declare function useSocket(): socket_io_client.Socket<_socket_io_component_emitter.DefaultEventsMap, _socket_io_component_emitter.DefaultEventsMap> | null;
|
|
63
|
+
/** Returns the active theme tokens */
|
|
64
|
+
declare function useTheme(): SessionTheme;
|
|
65
|
+
|
|
66
|
+
interface PlayerCardProps {
|
|
67
|
+
/** 'self' = players[0], 'opponent' = players[1] */
|
|
68
|
+
player: 'self' | 'opponent';
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Displays a player's name and active-turn indicator. Style it however you want. */
|
|
72
|
+
declare function PlayerCard({ player, className }: PlayerCardProps): react_jsx_runtime.JSX.Element | null;
|
|
73
|
+
|
|
74
|
+
interface TimerProps {
|
|
75
|
+
/** Which player's clock to show */
|
|
76
|
+
player: 'self' | 'opponent';
|
|
77
|
+
/** Initial seconds on the clock (e.g. 600 for 10 min) */
|
|
78
|
+
initialSeconds?: number;
|
|
79
|
+
className?: string;
|
|
80
|
+
}
|
|
81
|
+
/** A live countdown clock that syncs with the server via socket events. */
|
|
82
|
+
declare function Timer({ player, initialSeconds, className }: TimerProps): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
interface ChessBoardProps {
|
|
85
|
+
className?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The chess board — handles all game rendering and move input internally.
|
|
89
|
+
* Place this anywhere in your layout. Style the container via className.
|
|
90
|
+
*/
|
|
91
|
+
declare function ChessBoard({ className }: ChessBoardProps): react_jsx_runtime.JSX.Element | null;
|
|
92
|
+
|
|
93
|
+
interface ChessMoveHistoryProps {
|
|
94
|
+
className?: string;
|
|
95
|
+
}
|
|
96
|
+
/** Scrollable move history in algebraic notation. Syncs via socket. */
|
|
97
|
+
declare function ChessMoveHistory({ className }: ChessMoveHistoryProps): react_jsx_runtime.JSX.Element;
|
|
98
|
+
|
|
99
|
+
interface ChessCapturedPiecesProps {
|
|
100
|
+
player: 'self' | 'opponent';
|
|
101
|
+
className?: string;
|
|
102
|
+
}
|
|
103
|
+
/** Shows pieces captured by the given player. */
|
|
104
|
+
declare function ChessCapturedPieces({ player, className }: ChessCapturedPiecesProps): react_jsx_runtime.JSX.Element;
|
|
105
|
+
|
|
106
|
+
declare function CheckersBoard({ className }: {
|
|
107
|
+
className?: string;
|
|
108
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
109
|
+
|
|
110
|
+
declare function Connect4Board({ className }: {
|
|
111
|
+
className?: string;
|
|
112
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
113
|
+
declare function Connect4Score({ className }: {
|
|
114
|
+
className?: string;
|
|
115
|
+
}): react_jsx_runtime.JSX.Element;
|
|
116
|
+
|
|
117
|
+
declare function TictactoeBoard({ className }: {
|
|
118
|
+
className?: string;
|
|
119
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
120
|
+
|
|
121
|
+
declare function SubwayRunnerScore({ className }: {
|
|
122
|
+
className?: string;
|
|
123
|
+
}): react_jsx_runtime.JSX.Element;
|
|
124
|
+
|
|
125
|
+
declare function SubwayRunnerLives({ className }: {
|
|
126
|
+
className?: string;
|
|
127
|
+
}): react_jsx_runtime.JSX.Element;
|
|
128
|
+
|
|
129
|
+
declare function SubwayRunnerGame({ className }: {
|
|
130
|
+
className?: string;
|
|
131
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
132
|
+
|
|
133
|
+
export { BetaGamerProvider, CheckersBoard, ChessBoard, ChessCapturedPieces, ChessMoveHistory, Connect4Board, Connect4Score, type GameState, type GameType, type MatchType, PlayerCard, type SessionMode, type SessionPlayer, type SessionStatus, type SessionTheme, type SessionTokenPayload, SubwayRunnerGame, SubwayRunnerLives, SubwayRunnerScore, TictactoeBoard, Timer, useBetaGamer, useGameState, useSession, useSocket, useTheme };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import * as socket_io_client from 'socket.io-client';
|
|
4
|
+
import { Socket } from 'socket.io-client';
|
|
5
|
+
import * as _socket_io_component_emitter from '@socket.io/component-emitter';
|
|
6
|
+
|
|
7
|
+
type GameType = 'chess' | 'checkers' | 'connect4' | 'tictactoe' | 'subway-runner';
|
|
8
|
+
type MatchType = 'matchmaking' | 'private' | 'bot';
|
|
9
|
+
type SessionMode = 'live' | 'test' | 'training';
|
|
10
|
+
type SessionStatus = 'pending' | 'active' | 'ended';
|
|
11
|
+
interface SessionPlayer {
|
|
12
|
+
id: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
}
|
|
15
|
+
interface SessionTheme {
|
|
16
|
+
primaryColor?: string;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
boardLight?: string;
|
|
19
|
+
boardDark?: string;
|
|
20
|
+
fontFamily?: string;
|
|
21
|
+
[key: string]: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
interface SessionTokenPayload {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
game: GameType;
|
|
26
|
+
mode: SessionMode;
|
|
27
|
+
matchType: MatchType;
|
|
28
|
+
tenantId: string;
|
|
29
|
+
players: SessionPlayer[];
|
|
30
|
+
roomCode: string | null;
|
|
31
|
+
theme: SessionTheme;
|
|
32
|
+
exp: number;
|
|
33
|
+
}
|
|
34
|
+
interface GameState {
|
|
35
|
+
status: SessionStatus;
|
|
36
|
+
players: SessionPlayer[];
|
|
37
|
+
currentTurn?: string;
|
|
38
|
+
winner?: string | null;
|
|
39
|
+
reason?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface BetaGamerContextValue {
|
|
43
|
+
token: string;
|
|
44
|
+
session: SessionTokenPayload;
|
|
45
|
+
socket: Socket | null;
|
|
46
|
+
gameState: GameState;
|
|
47
|
+
theme: SessionTheme;
|
|
48
|
+
}
|
|
49
|
+
interface BetaGamerProviderProps {
|
|
50
|
+
token: string;
|
|
51
|
+
serverUrl?: string;
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
}
|
|
54
|
+
declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
|
|
55
|
+
declare function useBetaGamer(): BetaGamerContextValue;
|
|
56
|
+
|
|
57
|
+
/** Returns the current game state (status, players, winner, etc.) */
|
|
58
|
+
declare function useGameState(): GameState;
|
|
59
|
+
/** Returns the decoded session payload (game, mode, matchType, players, theme) */
|
|
60
|
+
declare function useSession(): SessionTokenPayload;
|
|
61
|
+
/** Returns the raw Socket.IO socket — for advanced custom event handling */
|
|
62
|
+
declare function useSocket(): socket_io_client.Socket<_socket_io_component_emitter.DefaultEventsMap, _socket_io_component_emitter.DefaultEventsMap> | null;
|
|
63
|
+
/** Returns the active theme tokens */
|
|
64
|
+
declare function useTheme(): SessionTheme;
|
|
65
|
+
|
|
66
|
+
interface PlayerCardProps {
|
|
67
|
+
/** 'self' = players[0], 'opponent' = players[1] */
|
|
68
|
+
player: 'self' | 'opponent';
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Displays a player's name and active-turn indicator. Style it however you want. */
|
|
72
|
+
declare function PlayerCard({ player, className }: PlayerCardProps): react_jsx_runtime.JSX.Element | null;
|
|
73
|
+
|
|
74
|
+
interface TimerProps {
|
|
75
|
+
/** Which player's clock to show */
|
|
76
|
+
player: 'self' | 'opponent';
|
|
77
|
+
/** Initial seconds on the clock (e.g. 600 for 10 min) */
|
|
78
|
+
initialSeconds?: number;
|
|
79
|
+
className?: string;
|
|
80
|
+
}
|
|
81
|
+
/** A live countdown clock that syncs with the server via socket events. */
|
|
82
|
+
declare function Timer({ player, initialSeconds, className }: TimerProps): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
interface ChessBoardProps {
|
|
85
|
+
className?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The chess board — handles all game rendering and move input internally.
|
|
89
|
+
* Place this anywhere in your layout. Style the container via className.
|
|
90
|
+
*/
|
|
91
|
+
declare function ChessBoard({ className }: ChessBoardProps): react_jsx_runtime.JSX.Element | null;
|
|
92
|
+
|
|
93
|
+
interface ChessMoveHistoryProps {
|
|
94
|
+
className?: string;
|
|
95
|
+
}
|
|
96
|
+
/** Scrollable move history in algebraic notation. Syncs via socket. */
|
|
97
|
+
declare function ChessMoveHistory({ className }: ChessMoveHistoryProps): react_jsx_runtime.JSX.Element;
|
|
98
|
+
|
|
99
|
+
interface ChessCapturedPiecesProps {
|
|
100
|
+
player: 'self' | 'opponent';
|
|
101
|
+
className?: string;
|
|
102
|
+
}
|
|
103
|
+
/** Shows pieces captured by the given player. */
|
|
104
|
+
declare function ChessCapturedPieces({ player, className }: ChessCapturedPiecesProps): react_jsx_runtime.JSX.Element;
|
|
105
|
+
|
|
106
|
+
declare function CheckersBoard({ className }: {
|
|
107
|
+
className?: string;
|
|
108
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
109
|
+
|
|
110
|
+
declare function Connect4Board({ className }: {
|
|
111
|
+
className?: string;
|
|
112
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
113
|
+
declare function Connect4Score({ className }: {
|
|
114
|
+
className?: string;
|
|
115
|
+
}): react_jsx_runtime.JSX.Element;
|
|
116
|
+
|
|
117
|
+
declare function TictactoeBoard({ className }: {
|
|
118
|
+
className?: string;
|
|
119
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
120
|
+
|
|
121
|
+
declare function SubwayRunnerScore({ className }: {
|
|
122
|
+
className?: string;
|
|
123
|
+
}): react_jsx_runtime.JSX.Element;
|
|
124
|
+
|
|
125
|
+
declare function SubwayRunnerLives({ className }: {
|
|
126
|
+
className?: string;
|
|
127
|
+
}): react_jsx_runtime.JSX.Element;
|
|
128
|
+
|
|
129
|
+
declare function SubwayRunnerGame({ className }: {
|
|
130
|
+
className?: string;
|
|
131
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
132
|
+
|
|
133
|
+
export { BetaGamerProvider, CheckersBoard, ChessBoard, ChessCapturedPieces, ChessMoveHistory, Connect4Board, Connect4Score, type GameState, type GameType, type MatchType, PlayerCard, type SessionMode, type SessionPlayer, type SessionStatus, type SessionTheme, type SessionTokenPayload, SubwayRunnerGame, SubwayRunnerLives, SubwayRunnerScore, TictactoeBoard, Timer, useBetaGamer, useGameState, useSession, useSocket, useTheme };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BetaGamerProvider: () => BetaGamerProvider,
|
|
24
|
+
CheckersBoard: () => CheckersBoard,
|
|
25
|
+
ChessBoard: () => ChessBoard,
|
|
26
|
+
ChessCapturedPieces: () => ChessCapturedPieces,
|
|
27
|
+
ChessMoveHistory: () => ChessMoveHistory,
|
|
28
|
+
Connect4Board: () => Connect4Board,
|
|
29
|
+
Connect4Score: () => Connect4Score,
|
|
30
|
+
PlayerCard: () => PlayerCard,
|
|
31
|
+
SubwayRunnerGame: () => SubwayRunnerGame,
|
|
32
|
+
SubwayRunnerLives: () => SubwayRunnerLives,
|
|
33
|
+
SubwayRunnerScore: () => SubwayRunnerScore,
|
|
34
|
+
TictactoeBoard: () => TictactoeBoard,
|
|
35
|
+
Timer: () => Timer,
|
|
36
|
+
useBetaGamer: () => useBetaGamer,
|
|
37
|
+
useGameState: () => useGameState,
|
|
38
|
+
useSession: () => useSession,
|
|
39
|
+
useSocket: () => useSocket,
|
|
40
|
+
useTheme: () => useTheme
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/context/BetaGamerProvider.tsx
|
|
45
|
+
var import_react = require("react");
|
|
46
|
+
var import_socket = require("socket.io-client");
|
|
47
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
48
|
+
function decodeToken(token) {
|
|
49
|
+
const payload = token.split(".")[1];
|
|
50
|
+
return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
51
|
+
}
|
|
52
|
+
var BetaGamerContext = (0, import_react.createContext)(null);
|
|
53
|
+
function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
|
|
54
|
+
const session = decodeToken(token);
|
|
55
|
+
const socketRef = (0, import_react.useRef)(null);
|
|
56
|
+
const [gameState, setGameState] = (0, import_react.useState)({
|
|
57
|
+
status: "pending",
|
|
58
|
+
players: session.players
|
|
59
|
+
});
|
|
60
|
+
(0, import_react.useEffect)(() => {
|
|
61
|
+
const socket = (0, import_socket.io)(`${serverUrl}/${session.game}`, {
|
|
62
|
+
auth: { token },
|
|
63
|
+
transports: ["websocket"]
|
|
64
|
+
});
|
|
65
|
+
socketRef.current = socket;
|
|
66
|
+
socket.on("game:state", (state) => {
|
|
67
|
+
setGameState((prev) => ({ ...prev, ...state }));
|
|
68
|
+
});
|
|
69
|
+
socket.on("game:ended", ({ winner, reason }) => {
|
|
70
|
+
setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
|
|
71
|
+
});
|
|
72
|
+
const root = document.documentElement;
|
|
73
|
+
const t = session.theme ?? {};
|
|
74
|
+
if (t.primaryColor) root.style.setProperty("--bg-primary", t.primaryColor);
|
|
75
|
+
if (t.backgroundColor) root.style.setProperty("--bg-surface", t.backgroundColor);
|
|
76
|
+
if (t.boardLight) root.style.setProperty("--board-light", t.boardLight);
|
|
77
|
+
if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
|
|
78
|
+
if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
|
|
79
|
+
return () => {
|
|
80
|
+
socket.disconnect();
|
|
81
|
+
};
|
|
82
|
+
}, [token, serverUrl, session.game]);
|
|
83
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
|
|
84
|
+
}
|
|
85
|
+
function useBetaGamer() {
|
|
86
|
+
const ctx = (0, import_react.useContext)(BetaGamerContext);
|
|
87
|
+
if (!ctx) throw new Error("useBetaGamer must be used inside <BetaGamerProvider>");
|
|
88
|
+
return ctx;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/hooks/index.ts
|
|
92
|
+
function useGameState() {
|
|
93
|
+
return useBetaGamer().gameState;
|
|
94
|
+
}
|
|
95
|
+
function useSession() {
|
|
96
|
+
return useBetaGamer().session;
|
|
97
|
+
}
|
|
98
|
+
function useSocket() {
|
|
99
|
+
return useBetaGamer().socket;
|
|
100
|
+
}
|
|
101
|
+
function useTheme() {
|
|
102
|
+
return useBetaGamer().theme;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/components/PlayerCard.tsx
|
|
106
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
107
|
+
function PlayerCard({ player, className }) {
|
|
108
|
+
const { players } = useSession();
|
|
109
|
+
const { currentTurn } = useGameState();
|
|
110
|
+
const p = player === "self" ? players[0] : players[1];
|
|
111
|
+
if (!p) return null;
|
|
112
|
+
const isActive = currentTurn === p.id;
|
|
113
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, "data-active": isActive, "data-player": player, children: [
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-role": "display-name", children: p.displayName }),
|
|
115
|
+
isActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-role": "turn-indicator", "aria-label": "Your turn" })
|
|
116
|
+
] });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/components/Timer.tsx
|
|
120
|
+
var import_react2 = require("react");
|
|
121
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
122
|
+
function Timer({ player, initialSeconds = 600, className }) {
|
|
123
|
+
const socket = useSocket();
|
|
124
|
+
const { status } = useGameState();
|
|
125
|
+
const [seconds, setSeconds] = (0, import_react2.useState)(initialSeconds);
|
|
126
|
+
(0, import_react2.useEffect)(() => {
|
|
127
|
+
if (!socket) return;
|
|
128
|
+
const handler = (clocks) => {
|
|
129
|
+
if (clocks[player] !== void 0) setSeconds(clocks[player]);
|
|
130
|
+
};
|
|
131
|
+
socket.on("game:clock", handler);
|
|
132
|
+
return () => {
|
|
133
|
+
socket.off("game:clock", handler);
|
|
134
|
+
};
|
|
135
|
+
}, [socket, player]);
|
|
136
|
+
(0, import_react2.useEffect)(() => {
|
|
137
|
+
if (status !== "active") return;
|
|
138
|
+
const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
|
|
139
|
+
return () => clearInterval(id);
|
|
140
|
+
}, [status]);
|
|
141
|
+
const m = Math.floor(seconds / 60).toString().padStart(2, "0");
|
|
142
|
+
const s = (seconds % 60).toString().padStart(2, "0");
|
|
143
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, "data-player": player, "data-low": seconds < 30, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "data-role": "clock", children: [
|
|
144
|
+
m,
|
|
145
|
+
":",
|
|
146
|
+
s
|
|
147
|
+
] }) });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/components/chess/ChessBoard.tsx
|
|
151
|
+
var import_react3 = require("react");
|
|
152
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
153
|
+
function ChessBoard({ className }) {
|
|
154
|
+
const { token, session } = useBetaGamer();
|
|
155
|
+
const iframeRef = (0, import_react3.useRef)(null);
|
|
156
|
+
(0, import_react3.useEffect)(() => {
|
|
157
|
+
const iframe = iframeRef.current;
|
|
158
|
+
if (!iframe) return;
|
|
159
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
160
|
+
iframe.addEventListener("load", onLoad);
|
|
161
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
162
|
+
}, [token]);
|
|
163
|
+
if (session.game !== "chess") return null;
|
|
164
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
165
|
+
"iframe",
|
|
166
|
+
{
|
|
167
|
+
ref: iframeRef,
|
|
168
|
+
src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/chess`,
|
|
169
|
+
className,
|
|
170
|
+
style: { border: "none", width: "100%", height: "100%" },
|
|
171
|
+
allow: "autoplay",
|
|
172
|
+
title: "Chess"
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/components/chess/ChessMoveHistory.tsx
|
|
178
|
+
var import_react4 = require("react");
|
|
179
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
180
|
+
function ChessMoveHistory({ className }) {
|
|
181
|
+
const socket = useSocket();
|
|
182
|
+
const [moves, setMoves] = (0, import_react4.useState)([]);
|
|
183
|
+
(0, import_react4.useEffect)(() => {
|
|
184
|
+
if (!socket) return;
|
|
185
|
+
const handler = ({ san, moveIndex }) => {
|
|
186
|
+
const moveNumber = Math.floor(moveIndex / 2) + 1;
|
|
187
|
+
const isWhite = moveIndex % 2 === 0;
|
|
188
|
+
setMoves((prev) => {
|
|
189
|
+
const next = [...prev];
|
|
190
|
+
if (isWhite) {
|
|
191
|
+
next.push({ moveNumber, white: san });
|
|
192
|
+
} else {
|
|
193
|
+
const last = next[next.length - 1];
|
|
194
|
+
if (last && last.moveNumber === moveNumber) last.black = san;
|
|
195
|
+
}
|
|
196
|
+
return next;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
socket.on("chess:move", handler);
|
|
200
|
+
return () => {
|
|
201
|
+
socket.off("chess:move", handler);
|
|
202
|
+
};
|
|
203
|
+
}, [socket]);
|
|
204
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, "data-role": "move-history", children: moves.map(({ moveNumber, white, black }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { "data-role": "move-row", children: [
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { "data-role": "move-number", children: [
|
|
206
|
+
moveNumber,
|
|
207
|
+
"."
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-role": "move-white", children: white }),
|
|
210
|
+
black && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-role": "move-black", children: black })
|
|
211
|
+
] }, moveNumber)) });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/components/chess/ChessCapturedPieces.tsx
|
|
215
|
+
var import_react5 = require("react");
|
|
216
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
217
|
+
var PIECE_SYMBOLS = {
|
|
218
|
+
p: "\u265F",
|
|
219
|
+
n: "\u265E",
|
|
220
|
+
b: "\u265D",
|
|
221
|
+
r: "\u265C",
|
|
222
|
+
q: "\u265B"
|
|
223
|
+
};
|
|
224
|
+
function ChessCapturedPieces({ player, className }) {
|
|
225
|
+
const socket = useSocket();
|
|
226
|
+
const { players } = useGameState();
|
|
227
|
+
const [captured, setCaptured] = (0, import_react5.useState)([]);
|
|
228
|
+
(0, import_react5.useEffect)(() => {
|
|
229
|
+
if (!socket) return;
|
|
230
|
+
const handler = ({ capturedBy, piece }) => {
|
|
231
|
+
const p = player === "self" ? players[0] : players[1];
|
|
232
|
+
if (p && capturedBy === p.id) setCaptured((prev) => [...prev, piece]);
|
|
233
|
+
};
|
|
234
|
+
socket.on("chess:capture", handler);
|
|
235
|
+
return () => {
|
|
236
|
+
socket.off("chess:capture", handler);
|
|
237
|
+
};
|
|
238
|
+
}, [socket, player, players]);
|
|
239
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, "data-role": "captured-pieces", "data-player": player, children: captured.map((piece, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "data-role": "piece", "data-type": piece, children: PIECE_SYMBOLS[piece.toLowerCase()] ?? piece }, i)) });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/components/checkers/index.tsx
|
|
243
|
+
var import_react6 = require("react");
|
|
244
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
245
|
+
function CheckersBoard({ className }) {
|
|
246
|
+
const { token, session } = useBetaGamer();
|
|
247
|
+
const iframeRef = (0, import_react6.useRef)(null);
|
|
248
|
+
(0, import_react6.useEffect)(() => {
|
|
249
|
+
const iframe = iframeRef.current;
|
|
250
|
+
if (!iframe) return;
|
|
251
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
252
|
+
iframe.addEventListener("load", onLoad);
|
|
253
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
254
|
+
}, [token]);
|
|
255
|
+
if (session.game !== "checkers") return null;
|
|
256
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/checkers`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Checkers" });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/components/connect4/index.tsx
|
|
260
|
+
var import_react7 = require("react");
|
|
261
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
262
|
+
function Connect4Board({ className }) {
|
|
263
|
+
const { token, session } = useBetaGamer();
|
|
264
|
+
const iframeRef = (0, import_react7.useRef)(null);
|
|
265
|
+
(0, import_react7.useEffect)(() => {
|
|
266
|
+
const iframe = iframeRef.current;
|
|
267
|
+
if (!iframe) return;
|
|
268
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
269
|
+
iframe.addEventListener("load", onLoad);
|
|
270
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
271
|
+
}, [token]);
|
|
272
|
+
if (session.game !== "connect4") return null;
|
|
273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/connect4`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Connect 4" });
|
|
274
|
+
}
|
|
275
|
+
function Connect4Score({ className }) {
|
|
276
|
+
const { gameState } = useBetaGamer();
|
|
277
|
+
const scores = gameState?.scores ?? {};
|
|
278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className, "data-role": "score", children: Object.entries(scores).map(([id, score]) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "data-player": id, "data-role": "score-value", children: score }, id)) });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/components/tictactoe/index.tsx
|
|
282
|
+
var import_react8 = require("react");
|
|
283
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
284
|
+
function TictactoeBoard({ className }) {
|
|
285
|
+
const { token, session } = useBetaGamer();
|
|
286
|
+
const iframeRef = (0, import_react8.useRef)(null);
|
|
287
|
+
(0, import_react8.useEffect)(() => {
|
|
288
|
+
const iframe = iframeRef.current;
|
|
289
|
+
if (!iframe) return;
|
|
290
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
291
|
+
iframe.addEventListener("load", onLoad);
|
|
292
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
293
|
+
}, [token]);
|
|
294
|
+
if (session.game !== "tictactoe") return null;
|
|
295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/tictactoe`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Tic-tac-toe" });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/components/subway-runner/index.tsx
|
|
299
|
+
var import_react11 = require("react");
|
|
300
|
+
|
|
301
|
+
// src/components/subway-runner/SubwayRunnerScore.tsx
|
|
302
|
+
var import_react9 = require("react");
|
|
303
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
304
|
+
function SubwayRunnerScore({ className }) {
|
|
305
|
+
const socket = useSocket();
|
|
306
|
+
const [score, setScore] = (0, import_react9.useState)(0);
|
|
307
|
+
(0, import_react9.useEffect)(() => {
|
|
308
|
+
if (!socket) return;
|
|
309
|
+
socket.on("runner:score", (s) => setScore(s));
|
|
310
|
+
return () => {
|
|
311
|
+
socket.off("runner:score");
|
|
312
|
+
};
|
|
313
|
+
}, [socket]);
|
|
314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, "data-role": "score", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "data-role": "score-value", children: score }) });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/components/subway-runner/SubwayRunnerLives.tsx
|
|
318
|
+
var import_react10 = require("react");
|
|
319
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
320
|
+
function SubwayRunnerLives({ className }) {
|
|
321
|
+
const socket = useSocket();
|
|
322
|
+
const [lives, setLives] = (0, import_react10.useState)(3);
|
|
323
|
+
(0, import_react10.useEffect)(() => {
|
|
324
|
+
if (!socket) return;
|
|
325
|
+
socket.on("runner:lives", (l) => setLives(l));
|
|
326
|
+
return () => {
|
|
327
|
+
socket.off("runner:lives");
|
|
328
|
+
};
|
|
329
|
+
}, [socket]);
|
|
330
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className, "data-role": "lives", children: Array.from({ length: lives }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "data-role": "life", "aria-label": "life" }, i)) });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/components/subway-runner/index.tsx
|
|
334
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
335
|
+
function SubwayRunnerGame({ className }) {
|
|
336
|
+
const { token, session } = useBetaGamer();
|
|
337
|
+
const iframeRef = (0, import_react11.useRef)(null);
|
|
338
|
+
(0, import_react11.useEffect)(() => {
|
|
339
|
+
const iframe = iframeRef.current;
|
|
340
|
+
if (!iframe) return;
|
|
341
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
342
|
+
iframe.addEventListener("load", onLoad);
|
|
343
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
344
|
+
}, [token]);
|
|
345
|
+
if (session.game !== "subway-runner") return null;
|
|
346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/subway-runner`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Subway Runner" });
|
|
347
|
+
}
|
|
348
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
349
|
+
0 && (module.exports = {
|
|
350
|
+
BetaGamerProvider,
|
|
351
|
+
CheckersBoard,
|
|
352
|
+
ChessBoard,
|
|
353
|
+
ChessCapturedPieces,
|
|
354
|
+
ChessMoveHistory,
|
|
355
|
+
Connect4Board,
|
|
356
|
+
Connect4Score,
|
|
357
|
+
PlayerCard,
|
|
358
|
+
SubwayRunnerGame,
|
|
359
|
+
SubwayRunnerLives,
|
|
360
|
+
SubwayRunnerScore,
|
|
361
|
+
TictactoeBoard,
|
|
362
|
+
Timer,
|
|
363
|
+
useBetaGamer,
|
|
364
|
+
useGameState,
|
|
365
|
+
useSession,
|
|
366
|
+
useSocket,
|
|
367
|
+
useTheme
|
|
368
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// src/context/BetaGamerProvider.tsx
|
|
2
|
+
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { io } from "socket.io-client";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
function decodeToken(token) {
|
|
6
|
+
const payload = token.split(".")[1];
|
|
7
|
+
return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
|
8
|
+
}
|
|
9
|
+
var BetaGamerContext = createContext(null);
|
|
10
|
+
function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
|
|
11
|
+
const session = decodeToken(token);
|
|
12
|
+
const socketRef = useRef(null);
|
|
13
|
+
const [gameState, setGameState] = useState({
|
|
14
|
+
status: "pending",
|
|
15
|
+
players: session.players
|
|
16
|
+
});
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const socket = io(`${serverUrl}/${session.game}`, {
|
|
19
|
+
auth: { token },
|
|
20
|
+
transports: ["websocket"]
|
|
21
|
+
});
|
|
22
|
+
socketRef.current = socket;
|
|
23
|
+
socket.on("game:state", (state) => {
|
|
24
|
+
setGameState((prev) => ({ ...prev, ...state }));
|
|
25
|
+
});
|
|
26
|
+
socket.on("game:ended", ({ winner, reason }) => {
|
|
27
|
+
setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
|
|
28
|
+
});
|
|
29
|
+
const root = document.documentElement;
|
|
30
|
+
const t = session.theme ?? {};
|
|
31
|
+
if (t.primaryColor) root.style.setProperty("--bg-primary", t.primaryColor);
|
|
32
|
+
if (t.backgroundColor) root.style.setProperty("--bg-surface", t.backgroundColor);
|
|
33
|
+
if (t.boardLight) root.style.setProperty("--board-light", t.boardLight);
|
|
34
|
+
if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
|
|
35
|
+
if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
|
|
36
|
+
return () => {
|
|
37
|
+
socket.disconnect();
|
|
38
|
+
};
|
|
39
|
+
}, [token, serverUrl, session.game]);
|
|
40
|
+
return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
|
|
41
|
+
}
|
|
42
|
+
function useBetaGamer() {
|
|
43
|
+
const ctx = useContext(BetaGamerContext);
|
|
44
|
+
if (!ctx) throw new Error("useBetaGamer must be used inside <BetaGamerProvider>");
|
|
45
|
+
return ctx;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/hooks/index.ts
|
|
49
|
+
function useGameState() {
|
|
50
|
+
return useBetaGamer().gameState;
|
|
51
|
+
}
|
|
52
|
+
function useSession() {
|
|
53
|
+
return useBetaGamer().session;
|
|
54
|
+
}
|
|
55
|
+
function useSocket() {
|
|
56
|
+
return useBetaGamer().socket;
|
|
57
|
+
}
|
|
58
|
+
function useTheme() {
|
|
59
|
+
return useBetaGamer().theme;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/components/PlayerCard.tsx
|
|
63
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
64
|
+
function PlayerCard({ player, className }) {
|
|
65
|
+
const { players } = useSession();
|
|
66
|
+
const { currentTurn } = useGameState();
|
|
67
|
+
const p = player === "self" ? players[0] : players[1];
|
|
68
|
+
if (!p) return null;
|
|
69
|
+
const isActive = currentTurn === p.id;
|
|
70
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-active": isActive, "data-player": player, children: [
|
|
71
|
+
/* @__PURE__ */ jsx2("span", { "data-role": "display-name", children: p.displayName }),
|
|
72
|
+
isActive && /* @__PURE__ */ jsx2("span", { "data-role": "turn-indicator", "aria-label": "Your turn" })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/components/Timer.tsx
|
|
77
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
78
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
79
|
+
function Timer({ player, initialSeconds = 600, className }) {
|
|
80
|
+
const socket = useSocket();
|
|
81
|
+
const { status } = useGameState();
|
|
82
|
+
const [seconds, setSeconds] = useState2(initialSeconds);
|
|
83
|
+
useEffect2(() => {
|
|
84
|
+
if (!socket) return;
|
|
85
|
+
const handler = (clocks) => {
|
|
86
|
+
if (clocks[player] !== void 0) setSeconds(clocks[player]);
|
|
87
|
+
};
|
|
88
|
+
socket.on("game:clock", handler);
|
|
89
|
+
return () => {
|
|
90
|
+
socket.off("game:clock", handler);
|
|
91
|
+
};
|
|
92
|
+
}, [socket, player]);
|
|
93
|
+
useEffect2(() => {
|
|
94
|
+
if (status !== "active") return;
|
|
95
|
+
const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
|
|
96
|
+
return () => clearInterval(id);
|
|
97
|
+
}, [status]);
|
|
98
|
+
const m = Math.floor(seconds / 60).toString().padStart(2, "0");
|
|
99
|
+
const s = (seconds % 60).toString().padStart(2, "0");
|
|
100
|
+
return /* @__PURE__ */ jsx3("div", { className, "data-player": player, "data-low": seconds < 30, children: /* @__PURE__ */ jsxs2("span", { "data-role": "clock", children: [
|
|
101
|
+
m,
|
|
102
|
+
":",
|
|
103
|
+
s
|
|
104
|
+
] }) });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/components/chess/ChessBoard.tsx
|
|
108
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
109
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
110
|
+
function ChessBoard({ className }) {
|
|
111
|
+
const { token, session } = useBetaGamer();
|
|
112
|
+
const iframeRef = useRef2(null);
|
|
113
|
+
useEffect3(() => {
|
|
114
|
+
const iframe = iframeRef.current;
|
|
115
|
+
if (!iframe) return;
|
|
116
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
117
|
+
iframe.addEventListener("load", onLoad);
|
|
118
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
119
|
+
}, [token]);
|
|
120
|
+
if (session.game !== "chess") return null;
|
|
121
|
+
return /* @__PURE__ */ jsx4(
|
|
122
|
+
"iframe",
|
|
123
|
+
{
|
|
124
|
+
ref: iframeRef,
|
|
125
|
+
src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/chess`,
|
|
126
|
+
className,
|
|
127
|
+
style: { border: "none", width: "100%", height: "100%" },
|
|
128
|
+
allow: "autoplay",
|
|
129
|
+
title: "Chess"
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/components/chess/ChessMoveHistory.tsx
|
|
135
|
+
import { useEffect as useEffect4, useState as useState3 } from "react";
|
|
136
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
137
|
+
function ChessMoveHistory({ className }) {
|
|
138
|
+
const socket = useSocket();
|
|
139
|
+
const [moves, setMoves] = useState3([]);
|
|
140
|
+
useEffect4(() => {
|
|
141
|
+
if (!socket) return;
|
|
142
|
+
const handler = ({ san, moveIndex }) => {
|
|
143
|
+
const moveNumber = Math.floor(moveIndex / 2) + 1;
|
|
144
|
+
const isWhite = moveIndex % 2 === 0;
|
|
145
|
+
setMoves((prev) => {
|
|
146
|
+
const next = [...prev];
|
|
147
|
+
if (isWhite) {
|
|
148
|
+
next.push({ moveNumber, white: san });
|
|
149
|
+
} else {
|
|
150
|
+
const last = next[next.length - 1];
|
|
151
|
+
if (last && last.moveNumber === moveNumber) last.black = san;
|
|
152
|
+
}
|
|
153
|
+
return next;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
socket.on("chess:move", handler);
|
|
157
|
+
return () => {
|
|
158
|
+
socket.off("chess:move", handler);
|
|
159
|
+
};
|
|
160
|
+
}, [socket]);
|
|
161
|
+
return /* @__PURE__ */ jsx5("div", { className, "data-role": "move-history", children: moves.map(({ moveNumber, white, black }) => /* @__PURE__ */ jsxs3("div", { "data-role": "move-row", children: [
|
|
162
|
+
/* @__PURE__ */ jsxs3("span", { "data-role": "move-number", children: [
|
|
163
|
+
moveNumber,
|
|
164
|
+
"."
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ jsx5("span", { "data-role": "move-white", children: white }),
|
|
167
|
+
black && /* @__PURE__ */ jsx5("span", { "data-role": "move-black", children: black })
|
|
168
|
+
] }, moveNumber)) });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/components/chess/ChessCapturedPieces.tsx
|
|
172
|
+
import { useEffect as useEffect5, useState as useState4 } from "react";
|
|
173
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
174
|
+
var PIECE_SYMBOLS = {
|
|
175
|
+
p: "\u265F",
|
|
176
|
+
n: "\u265E",
|
|
177
|
+
b: "\u265D",
|
|
178
|
+
r: "\u265C",
|
|
179
|
+
q: "\u265B"
|
|
180
|
+
};
|
|
181
|
+
function ChessCapturedPieces({ player, className }) {
|
|
182
|
+
const socket = useSocket();
|
|
183
|
+
const { players } = useGameState();
|
|
184
|
+
const [captured, setCaptured] = useState4([]);
|
|
185
|
+
useEffect5(() => {
|
|
186
|
+
if (!socket) return;
|
|
187
|
+
const handler = ({ capturedBy, piece }) => {
|
|
188
|
+
const p = player === "self" ? players[0] : players[1];
|
|
189
|
+
if (p && capturedBy === p.id) setCaptured((prev) => [...prev, piece]);
|
|
190
|
+
};
|
|
191
|
+
socket.on("chess:capture", handler);
|
|
192
|
+
return () => {
|
|
193
|
+
socket.off("chess:capture", handler);
|
|
194
|
+
};
|
|
195
|
+
}, [socket, player, players]);
|
|
196
|
+
return /* @__PURE__ */ jsx6("div", { className, "data-role": "captured-pieces", "data-player": player, children: captured.map((piece, i) => /* @__PURE__ */ jsx6("span", { "data-role": "piece", "data-type": piece, children: PIECE_SYMBOLS[piece.toLowerCase()] ?? piece }, i)) });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/components/checkers/index.tsx
|
|
200
|
+
import { useEffect as useEffect6, useRef as useRef3 } from "react";
|
|
201
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
202
|
+
function CheckersBoard({ className }) {
|
|
203
|
+
const { token, session } = useBetaGamer();
|
|
204
|
+
const iframeRef = useRef3(null);
|
|
205
|
+
useEffect6(() => {
|
|
206
|
+
const iframe = iframeRef.current;
|
|
207
|
+
if (!iframe) return;
|
|
208
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
209
|
+
iframe.addEventListener("load", onLoad);
|
|
210
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
211
|
+
}, [token]);
|
|
212
|
+
if (session.game !== "checkers") return null;
|
|
213
|
+
return /* @__PURE__ */ jsx7("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/checkers`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Checkers" });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/components/connect4/index.tsx
|
|
217
|
+
import { useEffect as useEffect7, useRef as useRef4 } from "react";
|
|
218
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
219
|
+
function Connect4Board({ className }) {
|
|
220
|
+
const { token, session } = useBetaGamer();
|
|
221
|
+
const iframeRef = useRef4(null);
|
|
222
|
+
useEffect7(() => {
|
|
223
|
+
const iframe = iframeRef.current;
|
|
224
|
+
if (!iframe) return;
|
|
225
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
226
|
+
iframe.addEventListener("load", onLoad);
|
|
227
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
228
|
+
}, [token]);
|
|
229
|
+
if (session.game !== "connect4") return null;
|
|
230
|
+
return /* @__PURE__ */ jsx8("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/connect4`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Connect 4" });
|
|
231
|
+
}
|
|
232
|
+
function Connect4Score({ className }) {
|
|
233
|
+
const { gameState } = useBetaGamer();
|
|
234
|
+
const scores = gameState?.scores ?? {};
|
|
235
|
+
return /* @__PURE__ */ jsx8("div", { className, "data-role": "score", children: Object.entries(scores).map(([id, score]) => /* @__PURE__ */ jsx8("span", { "data-player": id, "data-role": "score-value", children: score }, id)) });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/components/tictactoe/index.tsx
|
|
239
|
+
import { useEffect as useEffect8, useRef as useRef5 } from "react";
|
|
240
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
241
|
+
function TictactoeBoard({ className }) {
|
|
242
|
+
const { token, session } = useBetaGamer();
|
|
243
|
+
const iframeRef = useRef5(null);
|
|
244
|
+
useEffect8(() => {
|
|
245
|
+
const iframe = iframeRef.current;
|
|
246
|
+
if (!iframe) return;
|
|
247
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
248
|
+
iframe.addEventListener("load", onLoad);
|
|
249
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
250
|
+
}, [token]);
|
|
251
|
+
if (session.game !== "tictactoe") return null;
|
|
252
|
+
return /* @__PURE__ */ jsx9("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/tictactoe`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Tic-tac-toe" });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/components/subway-runner/index.tsx
|
|
256
|
+
import { useEffect as useEffect11, useRef as useRef6 } from "react";
|
|
257
|
+
|
|
258
|
+
// src/components/subway-runner/SubwayRunnerScore.tsx
|
|
259
|
+
import { useEffect as useEffect9, useState as useState5 } from "react";
|
|
260
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
261
|
+
function SubwayRunnerScore({ className }) {
|
|
262
|
+
const socket = useSocket();
|
|
263
|
+
const [score, setScore] = useState5(0);
|
|
264
|
+
useEffect9(() => {
|
|
265
|
+
if (!socket) return;
|
|
266
|
+
socket.on("runner:score", (s) => setScore(s));
|
|
267
|
+
return () => {
|
|
268
|
+
socket.off("runner:score");
|
|
269
|
+
};
|
|
270
|
+
}, [socket]);
|
|
271
|
+
return /* @__PURE__ */ jsx10("div", { className, "data-role": "score", children: /* @__PURE__ */ jsx10("span", { "data-role": "score-value", children: score }) });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/components/subway-runner/SubwayRunnerLives.tsx
|
|
275
|
+
import { useEffect as useEffect10, useState as useState6 } from "react";
|
|
276
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
277
|
+
function SubwayRunnerLives({ className }) {
|
|
278
|
+
const socket = useSocket();
|
|
279
|
+
const [lives, setLives] = useState6(3);
|
|
280
|
+
useEffect10(() => {
|
|
281
|
+
if (!socket) return;
|
|
282
|
+
socket.on("runner:lives", (l) => setLives(l));
|
|
283
|
+
return () => {
|
|
284
|
+
socket.off("runner:lives");
|
|
285
|
+
};
|
|
286
|
+
}, [socket]);
|
|
287
|
+
return /* @__PURE__ */ jsx11("div", { className, "data-role": "lives", children: Array.from({ length: lives }).map((_, i) => /* @__PURE__ */ jsx11("span", { "data-role": "life", "aria-label": "life" }, i)) });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/components/subway-runner/index.tsx
|
|
291
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
292
|
+
function SubwayRunnerGame({ className }) {
|
|
293
|
+
const { token, session } = useBetaGamer();
|
|
294
|
+
const iframeRef = useRef6(null);
|
|
295
|
+
useEffect11(() => {
|
|
296
|
+
const iframe = iframeRef.current;
|
|
297
|
+
if (!iframe) return;
|
|
298
|
+
const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
|
|
299
|
+
iframe.addEventListener("load", onLoad);
|
|
300
|
+
return () => iframe.removeEventListener("load", onLoad);
|
|
301
|
+
}, [token]);
|
|
302
|
+
if (session.game !== "subway-runner") return null;
|
|
303
|
+
return /* @__PURE__ */ jsx12("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/subway-runner`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Subway Runner" });
|
|
304
|
+
}
|
|
305
|
+
export {
|
|
306
|
+
BetaGamerProvider,
|
|
307
|
+
CheckersBoard,
|
|
308
|
+
ChessBoard,
|
|
309
|
+
ChessCapturedPieces,
|
|
310
|
+
ChessMoveHistory,
|
|
311
|
+
Connect4Board,
|
|
312
|
+
Connect4Score,
|
|
313
|
+
PlayerCard,
|
|
314
|
+
SubwayRunnerGame,
|
|
315
|
+
SubwayRunnerLives,
|
|
316
|
+
SubwayRunnerScore,
|
|
317
|
+
TictactoeBoard,
|
|
318
|
+
Timer,
|
|
319
|
+
useBetaGamer,
|
|
320
|
+
useGameState,
|
|
321
|
+
useSession,
|
|
322
|
+
useSocket,
|
|
323
|
+
useTheme
|
|
324
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beta-gamer/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React SDK for Beta Gamer GaaS — composable game components",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
|
18
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"socket.io-client": "^4.8.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/react": "^19",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/acetennyson/beta-gamer"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|