-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from alexander-zibert/implement-manual-card-se…
…lection Manual Game Setup
- Loading branch information
Showing
11 changed files
with
1,020 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.