Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add Logger as Event Emitter #210

Merged
merged 9 commits into from
Apr 17, 2024
104 changes: 104 additions & 0 deletions apps/movex-demo/pages/local/rps-v2/Game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import { MovexBoundResource } from 'movex-react';
import movexConfig from './movex.config';
import { initialState } from './movex';
import { useState } from 'react';
import { ResourceIdentifier, toResourceIdentifierStr } from 'movex-core-util';
import { GameUI } from './GameUI';
// import { MovexStoreItem } from "movex";
import { MovexLocalInstance } from 'movex-react-local-master';
import { MovexStoreItem } from 'movex-store';

type Props = {
masterStore?: MovexStoreItem<any>;
};

export function Game(props: Props) {
const [rpsRid, setRpsRid] = useState<ResourceIdentifier<'rps'>>();
const [masterStateUpdated, setMasterStateUpdated] = useState(false);

return (
<div className="h-screen flex flex-1 flex-col md:flex-row bg-gradient-to-bl from-blue-400 via-indigo-500 to-purple-500">
<MovexLocalInstance
clientId="playerA"
movexDefinition={movexConfig}
onConnected={(movex) => {
const reg = movex.register('rps');

reg.create(initialState).map(({ rid }) => {
setRpsRid(rid);

setTimeout(() => {
// HACK. Fake the Maser State Update in order to fix a current issue with working with
// master state values, instead of local ones.
// See https://github.com/movesthatmatter/movex/issues/9 for more info.
// This feature witll be added in the close future
setMasterStateUpdated(true);
}, 10);
});
}}
>
{rpsRid && (
<MovexBoundResource
rid={rpsRid}
movexDefinition={movexConfig}
render={({ boundResource, clientId }) => (
<div className="w-full flex-1">
<GameUI boundResource={boundResource} userId={clientId} />
</div>
)}
/>
)}
</MovexLocalInstance>
<div className="flex-1 flex flex-col h-full">
<div className="flex-1 flex justify-center items-center">
<span role="img" aria-label="versus" className="text-5xl">
🆚
</span>
</div>
{props.masterStore && (
<div
className="hidden md:block bg-red-100 bg-opacity-10 text-center pb-2 pt-2 pl-2 pr-2 overflow-scroll flex flex-col justify-center"
style={{
flex: 0.55,
}}
>
<div className="flex flex-col flex-1 nbg-green-100 h-full">
<p className="font-bold pb-4 capitalize text-white">
Master Movex State
</p>
<pre
className="text-xs text-left text-white flex flex-1 justify-center items-center nbg-red-100"
lang="json"
>
<code>
{JSON.stringify(
{
submissions: props.masterStore.state[0].submissions,
winner: props.masterStore.state[0].winner,
},
null,
1
)}
</code>
</pre>
</div>
</div>
)}
</div>
{rpsRid && masterStateUpdated && (
<MovexLocalInstance clientId="playerB" movexDefinition={movexConfig}>
<MovexBoundResource
rid={rpsRid}
movexDefinition={movexConfig}
render={({ boundResource, clientId }) => (
<div className="w-full flex-1">
<GameUI boundResource={boundResource} userId={clientId} />
</div>
)}
/>
</MovexLocalInstance>
)}
</div>
);
}
263 changes: 263 additions & 0 deletions apps/movex-demo/pages/local/rps-v2/GameUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import React from "react";
import { MovexBoundResourceFromConfig } from "movex-react";
import { useCallback, useEffect, useMemo } from "react";
import { globalLogsy } from "movex-core-util";
import movexConfig from "./movex.config";
import {
PlayerLabel,
toOppositeLabel,
RPS,
selectAvailableLabels
} from "./movex";

type Props = {
boundResource: MovexBoundResourceFromConfig<
typeof movexConfig["resources"],
"rps"
>;
userId: string;
buttonClassName?: string;
containerClassName?: string;
backgroundColor?: string;
};

export const GameUI: React.FC<Props> = ({
boundResource,
userId,
...props
}) => {
const { state, dispatch, dispatchPrivate } = boundResource;
const { players } = state;

const myPlayerLabel = useMemo((): PlayerLabel | undefined => {
if (!(players.playerA && players.playerB)) {
return undefined;
}

if (players.playerA.id === userId) {
return "playerA";
}

if (players.playerB.id === userId) {
return "playerB";
}

return undefined;
}, [players, userId]);

const oppnentPlayerLabel = useMemo(() => {
return myPlayerLabel ? toOppositeLabel(myPlayerLabel) : undefined;
}, [myPlayerLabel]);

// Add Player
useEffect(() => {
// TODO: here there is a major issue, as selectAvailableLables works with local state
// but it needs to check on the actual (master) state. How to solve this?
// add an api to be able to read master state seperately?

// Or in this case change the strategy altogether, and work with master generated values, in which case
// the local optimistic state udate gets turned off by default, so that means it will wait for the real state to update.
// Kinda like a dispatchAndWait
const availableLabels = selectAvailableLabels(state);

if (
state.players.playerA?.id === userId ||
state.players.playerB?.id === userId
) {
return;
}

if (availableLabels.length === 0) {
globalLogsy.warn("Player Slots taken");

return;
}

dispatch({
type: "addPlayer",
payload: {
id: userId,
playerLabel: availableLabels[0],
atTimestamp: new Date().getTime()
}
});
}, [userId, state, dispatch]);

const submit = useCallback(
(play: RPS) => {
if (!myPlayerLabel) {
console.warn("Not A Player");
return;
}

dispatchPrivate(
{
type: "submit",
payload: {
playerLabel: myPlayerLabel,
rps: play
},
isPrivate: true
},
{
type: "setReadySubmission",
payload: {
playerLabel: myPlayerLabel
}
}
);
},
[dispatchPrivate, myPlayerLabel]
);

const winner = useMemo(() => {
if (!state.winner) {
return undefined;
}

if (state.winner === "1/2") {
return "1/2";
}

const {
submissions: { playerA }
} = state;

if (playerA.play === state.winner) {
return state.players.playerA.label;
}

return state.players.playerB.label;
}, [state.winner]);

return (
<div
className="w-full flex h-full flex-col text-white"
style={{
...(props.backgroundColor
? {
background: props.backgroundColor
}
: {
// background: "#afd8fa"
})
}}
>
<div className="flex flex-1 w-full h-full flex-col justify-center items-center content-center">
<div className="p-10 pb-20 text-xl font-bold capitalize">{userId}</div>
{state.winner ? (
<>
<h3 className="text-7xl text-center">
{winner === "1/2"
? "Draw 😫"
: winner === myPlayerLabel
? "You Won 🎉"
: "You Lost 😢"}
</h3>
<button
className="text-2xl font-bold mt-10 hover:text-3xl"
onClick={() => dispatch({ type: "playAgain" })}
>
Play Again
</button>
</>
) : (
<>
<div className="flex">
<div>
<button
style={{
animationDelay: "200ms",
animationDuration: "3s"
}}
className={`animate-bounce text-7xl hover:bg-red-500 p-6 rounded-xl ${
myPlayerLabel &&
state.submissions[myPlayerLabel]?.play === "rock" &&
"bg-red-500"
}`}
onClick={() => submit("rock")}
>
<span role="img" aria-label="rock">
👊
</span>
</button>
</div>
<div>
<button
style={{
animationDelay: "500ms",
animationDuration: "3s"
}}
className={`animate-bounce text-7xl hover:bg-red-500 p-6 rounded-xl ${
myPlayerLabel &&
state.submissions[myPlayerLabel]?.play === "paper" &&
"bg-red-500"
}`}
onClick={() => submit("paper")}
>
<span role="img" aria-label="paper">
</span>
</button>
</div>
<div>
<button
style={{
animationDelay: "0ms",
animationDuration: "3s"
}}
className={`animate-bounce text-7xl hover:bg-red-500 p-6 rounded-xl ${
myPlayerLabel &&
state.submissions[myPlayerLabel]?.play === "scissors" &&
"bg-red-500"
}`}
onClick={() => submit("scissors")}
>
<span role="img" aria-label="scrissors">
✌️
</span>
</button>
</div>
</div>
<div className="text-center">
{oppnentPlayerLabel &&
state.submissions[oppnentPlayerLabel]?.play && (
<h5 className="text-lg italic">
Opponent Submitted{" "}
<span role="img" aria-label="time ticking">
⌛️⏰
</span>
</h5>
)}
</div>
</>
)}
</div>
<div
className="hidden md:block bg-red-100 bg-opacity-10 text-center pb-2 pt-2 pl-2 pr-2 overflow-scroll flex flex-col justify-center"
style={{
flex: 0.55
}}
>
<div className="flex flex-col flex-1 nbg-green-100 h-full">
<p className="font-bold pb-4 capitalize">{userId} Movex State</p>
<pre
className="text-xs text-left text-white flex flex-1 justify-center items-center nbg-red-100"
lang="json"
>
<code>
{JSON.stringify(
{
submissions: state.submissions,
winner: state.winner
},
null,
1
)}
</code>
</pre>
</div>
</div>
</div>
);
};
28 changes: 28 additions & 0 deletions apps/movex-demo/pages/local/rps-v2/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useState } from 'react';
import movexConfig from './movex.config';
import { Game } from './Game';
import { MovexLocalMasterProvider } from 'movex-react-local-master';
import { MovexStoreItem } from 'movex-store';

export default function App() {
const [masterStore, setMasterStore] = useState<MovexStoreItem<any>>();

return (
/**
* This is using the Local Provider in order to simulate
* multiple players in the same browser instance
*/
<MovexLocalMasterProvider
movexDefinition={movexConfig}
onMasterResourceUpdated={setMasterStore}
logger={{
onLog: ({ method, prefix, message, payload }) => {
// console.log('event', method, prefix, message, payload)
console[method](prefix + ' ' + message, payload);
},
}}
>
<Game masterStore={masterStore} />
</MovexLocalMasterProvider>
);
}
Loading