Skip to content

Commit

Permalink
Merge pull request #21 from alexander-zibert/implement-manual-card-se…
Browse files Browse the repository at this point in the history
…lection

Manual Game Setup
  • Loading branch information
alexander-zibert authored Dec 7, 2023
2 parents 761f7c5 + 511808e commit 28e6bf0
Show file tree
Hide file tree
Showing 11 changed files with 1,020 additions and 163 deletions.
169 changes: 169 additions & 0 deletions frontend/src/components/HashCodeRegistration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import LoadIcon from "@mui/icons-material/HourglassTopRounded";
import HashIcon from "@mui/icons-material/NumbersRounded";
import OkIcon from "@mui/icons-material/ThumbUpAltRounded";
import SearchIcon from "@mui/icons-material/TravelExploreRounded";
import Alert from "@mui/material/Alert";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Collapse from "@mui/material/Collapse";
import Snackbar from "@mui/material/Snackbar";
import { alpha, useTheme } from "@mui/material/styles";
import axios from "axios";
import TextField from "components/TextField";
import { useAppDispatch } from "hooks/useAppDispatch";
import { useAppSelector } from "hooks/useAppSelector";
import { FC, useState } from "react";
import { commentsActions } from "store/slices/commentsSlice";
import { digitCodeActions } from "store/slices/digitCodeSlice";
import { registrationActions } from "store/slices/registrationSlice";
import { roundsActions } from "store/slices/roundsSlice";

const HashCodeRegistration: FC = () => {
const dispatch = useAppDispatch();
const registration = useAppSelector((state) => state.registration);
const [showNotFound, setShowNotFound] = useState(false);
const theme = useTheme();

const onSubmit = () => {
dispatch(registrationActions.fetch());

axios
.get(process.env.REACT_APP_API_END_POINT, {
params: {
h: registration.hash,
},
})
.then((response) => response.data)
.then((data: any) => {
dispatch(roundsActions.reset());
dispatch(commentsActions.reset());
dispatch(digitCodeActions.reset());

switch (data.status) {
case "ok":
const {
fake,
ind,
crypt,
color,
m,
}: {
ind: number[];
fake?: number[];
crypt: number[];
color: number;
m: number;
} = data;
dispatch(registrationActions.fetchDone());
dispatch(commentsActions.setCards({ ind, fake, crypt, color, m }));
break;
default:
setShowNotFound(true);
dispatch(registrationActions.fetchBad());
break;
}
})
.catch(() => {
setShowNotFound(true);
dispatch(registrationActions.fetchBad());
});
};

return (
<>
<Snackbar
anchorOrigin={{ horizontal: "center", vertical: "bottom" }}
open={showNotFound}
onClose={() => {
setShowNotFound(false);
}}
>
<Alert
onClose={() => {
setShowNotFound(false);
}}
severity="error"
sx={{ width: "100%" }}
variant="filled"
>
{registration.hash} Game ID not found!
</Alert>
</Snackbar>
{registration.status === "new" && (
<Box pt={0.5} pb={0.5}>
<Alert severity="warning">
Starting a game by hashcode might be broken at the moment. If you
encounter an error, please use the manual method.
</Alert>
</Box>
)}
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}
>
<TextField
prefixId="registration__hash"
disabled={registration.status !== "new"}
iconRender={<HashIcon />}
value={registration.hash}
maxChars={10}
onChange={(value) =>
dispatch(registrationActions.updateHash(value.toUpperCase()))
}
withReset={registration.status === "new"}
onReset={() => dispatch(registrationActions.updateHash(""))}
customRadius={
registration.status === "ready"
? theme.spacing(0, 0, 2, 2)
: undefined
}
/>
<Box pt={0.5}>
<Collapse in={registration.status !== "ready"}>
<Button
aria-label="search"
disabled={!registration.hash || registration.status !== "new"}
fullWidth
size="large"
type="submit"
sx={(theme) => ({
background: alpha(theme.palette.primary.main, 0.1),
borderRadius: theme.spacing(0, 0, 2, 2),
fontFamily: "Kalam",
fontSize: 24,
height: theme.spacing(6),
"&:hover": {
background: alpha(theme.palette.primary.main, 0.2),
},
})}
>
{registration.status === "ready" ? (
<OkIcon />
) : registration.status === "fetch" ? (
<LoadIcon
sx={{
"@keyframes rotation": {
from: {
transform: "rotate(0deg)",
},
to: {
transform: "rotate(359deg)",
},
},
animation: "rotation 2s infinite linear",
}}
/>
) : (
<SearchIcon />
)}
</Button>
</Collapse>
</Box>
</form>
</>
);
};

export default HashCodeRegistration;
178 changes: 178 additions & 0 deletions frontend/src/components/ManualRegistration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Typography } from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import { alpha, useTheme } from "@mui/material/styles";
import TextField from "components/TextField";
import { useAppDispatch } from "hooks/useAppDispatch";
import { useAppSelector } from "hooks/useAppSelector";
import { FC, useState } from "react";
import { commentsActions } from "store/slices/commentsSlice";
import { digitCodeActions } from "store/slices/digitCodeSlice";
import { registrationActions } from "store/slices/registrationSlice";
import { roundsActions } from "store/slices/roundsSlice";

function correctCards(cards: number[]) {
return (
cards.length === cards.filter((card) => card >= 1 && card <= 48).length
);
}

function cardsAreUnique(cards: number[]) {
const uniqueCards = new Set(cards);
if (uniqueCards.size !== cards.length) {
return false;
}
return true;
}

function cardsAreSorted(cards: number[]) {
const sortedCards = [...cards];
sortedCards.sort((a, b) => a - b);
for (let i = 0; i < cards.length; i += 1) {
if (sortedCards[i] !== cards[i]) {
return false;
}
}
return true;
}

function parseCards(cardText: string) {
return cardText
.replaceAll(/[^0-9]+/g, " ")
.trim()
.split(" ")
.filter((s) => s.length > 0)
.map((s) => Number(s));
}

function validateCards(cards: number[]) {
if (cards.length === 0) {
return [];
}
const errors = [];
if (!cardsAreSorted(cards)) {
errors.push("The cards are not sorted ascending.");
}
if (!cardsAreUnique(cards)) {
errors.push("There are duplicate cards.");
}
if (cards.length < 4) {
errors.push("There are less than four cards.");
}
if (cards.length > 6) {
errors.push("There are more than six cards.");
}
if (!correctCards(cards)) {
errors.push("There are invalid cards.");
}
return errors;
}

const ManualRegistration: FC = () => {
const dispatch = useAppDispatch();
const registration = useAppSelector((state) => state.registration);
const [mode, setMode] = useState(0);
const [cardText, setCardText] = useState("");
const [errors, setErrors] = useState<string[]>([]);
const theme = useTheme();

function validateCardText(cardText: string) {
const cards = parseCards(cardText);
const errors = validateCards(cards);
setErrors(errors);
}

function onSubmit() {
const cards = parseCards(cardText);

console.log(cards);
dispatch(roundsActions.reset());
dispatch(commentsActions.reset());
dispatch(digitCodeActions.reset());
dispatch(registrationActions.fetchDone());
dispatch(
commentsActions.setCards({
ind: cards,
crypt: [1, 2, 3, 4, 5, 6],
color: 0,
m: mode,
})
);
}

if (registration.status !== "new") {
return <></>;
}

return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}
>
<FormControl>
<FormLabel id="demo-controlled-radio-buttons-group">
Choose Mode
</FormLabel>
<RadioGroup
row
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={mode}
onChange={(e) => setMode(Number(e.target.value))}
>
<FormControlLabel value={0} control={<Radio />} label="Classic" />
<FormControlLabel value={2} control={<Radio />} label="Nightmare" />
</RadioGroup>
</FormControl>
<Typography>Enter Cards</Typography>
<TextField
iconRender={<div />}
value={cardText}
onChange={(value) => {
setCardText(value);
validateCardText(value);
}}
withReset={true}
onReset={() => {
setCardText("");
setErrors([]);
}}
/>
{errors.length > 0 &&
errors.map((error) => <div key={error}>{error}</div>)}

<Box pt={0.5}>
<Button
aria-label="search"
disabled={cardText === "" || errors.length > 0}
fullWidth
size="large"
type="submit"
sx={(theme) => ({
background: alpha(theme.palette.primary.main, 0.1),
borderRadius: theme.spacing(0, 0, 2, 2),
fontFamily: "Kalam",
fontSize: 24,
height: theme.spacing(6),
"&:hover": {
background: alpha(theme.palette.primary.main, 0.2),
},
})}
>
Start Game
</Button>
</Box>
</form>
</>
);
};

export default ManualRegistration;
Loading

0 comments on commit 28e6bf0

Please sign in to comment.