Skip to content
This repository was archived by the owner on Dec 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
921e1f0
Delete everything
adzialocha Aug 23, 2022
00c4176
Set up Rollup project
adzialocha Aug 23, 2022
93f0626
Correct .nvmrc format
adzialocha Aug 23, 2022
cfb55ab
Add a README.md
adzialocha Aug 23, 2022
aef56a0
Update dependencies
adzialocha Aug 23, 2022
411c619
Rename too ZooAdventures component
adzialocha Aug 23, 2022
30e9aff
Add nodemon to watch for changes
adzialocha Aug 23, 2022
5346e77
Install graphql-request dependencies
adzialocha Aug 23, 2022
891f522
Add CommonJS to build library
adzialocha Aug 23, 2022
41f9806
Initial script to build schema and first board
adzialocha Aug 23, 2022
a1dc4f5
Initialise key pair and pass over configuration
adzialocha Aug 23, 2022
0913969
Display game board
adzialocha Aug 23, 2022
6fc83c5
Update field when clicking
adzialocha Aug 23, 2022
f9b3531
Add linter for react hooks
adzialocha Aug 23, 2022
79f3509
Define animal emoji based on public key
adzialocha Aug 23, 2022
400a53e
A little bit of styling
adzialocha Aug 23, 2022
50b4568
Do not show hover when already set
adzialocha Aug 23, 2022
0050b46
Do not send request when field is already set
adzialocha Aug 23, 2022
5e1f28e
Frequently update game board
adzialocha Aug 23, 2022
ca7dd33
Block player if they just made a move
adzialocha Aug 23, 2022
363e576
Split up in multiple files
adzialocha Aug 23, 2022
b0395d9
Move React components into separate files
adzialocha Aug 23, 2022
003ac79
Add some comments, improve logic for blocking player
adzialocha Aug 23, 2022
e426504
Add some doc strings
adzialocha Aug 23, 2022
09c3d39
WIP detect winner
adzialocha Aug 23, 2022
97cac6a
Add some doc strings
adzialocha Aug 24, 2022
9af8c7d
Calculate win combinations once, represent them as strings
adzialocha Aug 24, 2022
da8a28b
Get current winners of board
adzialocha Aug 24, 2022
605fe60
Show winner on board
adzialocha Aug 24, 2022
dc4260f
Fix checking winning positions
adzialocha Aug 24, 2022
ddd8961
Update comment
adzialocha Aug 24, 2022
e86ae85
Show messages to the user
adzialocha Aug 24, 2022
caf50bd
Style Message component, move animal info to the bottom
adzialocha Aug 24, 2022
67d593b
Remove emojis which are not animals
adzialocha Aug 24, 2022
92caf0e
Change message on the bottom
adzialocha Aug 24, 2022
cee7e0b
Change winSize to 3, check for sane configs
adzialocha Aug 24, 2022
925924a
Upload screenshot
adzialocha Aug 24, 2022
93be9f9
Update README.md
adzialocha Aug 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move React components into separate files
  • Loading branch information
adzialocha committed Aug 23, 2022
commit b0395d99b498ee8607410cf19e92feb923c0ad62
120 changes: 120 additions & 0 deletions src/Game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { GraphQLClient } from 'graphql-request';

import { GameBoard } from './GameBoard';
import { fetchBoard, updateBoardField } from './board';
import { loadKeyPair, loadLastMove, storeLastMove } from './storage';
import { publicKeyToAnimal } from './animals';

import type { Configuration } from './types';

type Props = {
config: Configuration;
};

export const Game: React.FC<Props> = ({ config }) => {
const keyPair = useMemo(() => {
return loadKeyPair();
}, []);

const client = useMemo(() => {
return new GraphQLClient(config.endpoint);
}, [config.endpoint]);

const animal = useMemo(() => {
return publicKeyToAnimal(keyPair.publicKey());
}, [keyPair]);

const [viewId, setViewId] = useState<string>();
const [fields, setFields] = useState<string[]>();
const [lastMove, setLastMove] = useState<string | null>(() => {
return loadLastMove();
});

const onSetField = useCallback(
async (fieldIndex: number) => {
if (!viewId) {
return;
}

// Do not allow making a move when player already did it one round ago
if (lastMove === viewId) {
return;
}

// Apply update locally first
setFields((value) => {
if (!value) {
return;
}

value[fieldIndex - 1] = animal;
return [...value];
});

// Send update to node.
//
// The method gives us back the "latest" view id, that is, we assume that
// our last write is now the latest edge of the operation graph. But who
// knows, maybe some concurrent write by someone decided something else!
const latestViewId = await updateBoardField(
client,
keyPair,
config.schemaId,
viewId,
fieldIndex,
animal,
);

// Set and persist last move
setLastMove(latestViewId);
storeLastMove(latestViewId);

// Temporarily guess that this might be the latest viewId from the
// perspective of the node as well. The next update will proof us right
// or wrong ..
//
// At least it helps us to block the player until the next update!
setViewId(latestViewId);
},
[viewId, client, lastMove, keyPair, config, animal],
);

useEffect(() => {
const updateBoard = async () => {
const board = await fetchBoard(
client,
config.schemaId,
config.documentId,
config.boardSize,
);

setViewId(board.viewId);
setFields(board.fields);
};

const interval = window.setInterval(() => {
updateBoard();
}, config.updateIntervalMs);

updateBoard();

return () => {
window.clearInterval(interval);
};
}, [
client,
config.boardSize,
config.documentId,
config.schemaId,
config.updateIntervalMs,
]);

return (
<>
{fields && (
<GameBoard fields={fields} animal={animal} onSetField={onSetField} />
)}
</>
);
};
73 changes: 73 additions & 0 deletions src/GameBoard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import styled from 'styled-components';

const FIELD_SIZE = 60;
const GAP_SIZE = 17;
const ICON_SIZE = 28;

type Props = {
animal: string;
fields: string[];
onSetField: (index: number) => void;
};

export const GameBoard: React.FC<Props> = ({ fields, onSetField, animal }) => {
return (
<StyledGameBoard boardSize={Math.sqrt(fields.length)}>
{fields.map((field, index) => {
const fieldIndex = index + 1;

// Was this field already set by this player?
const alreadySet = field === animal;

return (
<GameBoardField
key={`field-${fieldIndex}`}
alreadySet={alreadySet}
onClick={() => {
if (alreadySet) {
return;
}

onSetField(fieldIndex);
}}
>
{field}
</GameBoardField>
);
})}
</StyledGameBoard>
);
};

const StyledGameBoard = styled.div<{ boardSize: number }>`
display: grid;
font-size: ${ICON_SIZE}px;
gap: ${GAP_SIZE}px;
grid-auto-rows: ${FIELD_SIZE}px;
grid-template-columns: ${(props) =>
`repeat(${props.boardSize}, ${FIELD_SIZE}px)`};
`;

const GameBoardField = styled.div<{ alreadySet: boolean }>`
align-content: center;
background-color: #efefef;
border-radius: 50%;
cursor: ${(props) => (props.alreadySet ? 'normal' : 'pointer')};
display: inline-grid;
text-align: center;
transition: background-color linear 20ms;
user-select: none;

${(props) => {
if (props.alreadySet) {
return;
}

return `
&:hover {
background-color: #ddd;
}
`;
}}
`;
21 changes: 21 additions & 0 deletions src/InitWasm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useEffect, useState } from 'react';
import { initWebAssembly } from 'p2panda-js';

type Props = {
children: JSX.Element;
};

export const InitWasm: React.FC<Props> = ({ children }) => {
const [ready, setReady] = useState(false);

useEffect(() => {
const init = async () => {
await initWebAssembly();
setReady(true);
};

init();
}, []);

return ready ? children : null;
};
Loading