Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielCTroia committed Jul 11, 2024
2 parents 938bc5a + 3cfc405 commit 396706f
Show file tree
Hide file tree
Showing 64 changed files with 2,514 additions and 1,304 deletions.
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

0 comments on commit 396706f

Please sign in to comment.