Skip to content

Commit

Permalink
Add cheat mode to crossword (#1837)
Browse files Browse the repository at this point in the history
  • Loading branch information
sndrs authored Dec 6, 2024
2 parents 6302bae + d51be09 commit de3d764
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 21 deletions.
62 changes: 41 additions & 21 deletions libs/@guardian/react-crossword/src/components/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { css } from '@emotion/react';
import { isUndefined } from '@guardian/libs';
import { memo, useCallback, useEffect, useRef } from 'react';
import type { Coords, Separator, Theme } from '../@types/crossword';
Expand All @@ -7,12 +8,11 @@ import { useCurrentClue } from '../context/CurrentClue';
import { useData } from '../context/Data';
import { useProgress } from '../context/Progress';
import { useTheme } from '../context/Theme';
import { useCheatMode } from '../hooks/useCheatMode';
import { useUpdateCell } from '../hooks/useUpdateCell';
import { keyDownRegex } from '../utils/keydownRegex';
import { Cell } from './Cell';

// define and cache the regex for valid keydown events

const getCellPosition = (index: number, { cellSize, gutter }: Theme) =>
index * (cellSize + gutter) + gutter;

Expand Down Expand Up @@ -101,6 +101,8 @@ export const Grid = () => {
const gridRef = useRef<SVGSVGElement>(null);
const workingDirectionRef = useRef<Direction>('across');

const [cheatMode, cheatStyles] = useCheatMode(gridRef);

// keep workingDirectionRef.current up to date with the current entry
useEffect(() => {
if (currentEntryId) {
Expand Down Expand Up @@ -208,20 +210,27 @@ export const Grid = () => {
break;
}
default: {
if (currentEntryId && keyDownRegex.test(key)) {
updateCell({
x: currentCell.x,
y: currentCell.y,
value: key.toUpperCase(),
});
if (direction === 'across') {
moveFocus({ delta: { x: 1, y: 0 }, isTyping: true });
}
if (direction === 'down') {
moveFocus({ delta: { x: 0, y: 1 }, isTyping: true });
if (currentEntryId) {
const value = cheatMode
? cells.getByCoords({ x: currentCell.x, y: currentCell.y })
?.solution
: keyDownRegex.test(key) && key.toUpperCase();

if (value) {
updateCell({
x: currentCell.x,
y: currentCell.y,
value,
});
if (direction === 'across') {
moveFocus({ delta: { x: 1, y: 0 }, isTyping: true });
}
if (direction === 'down') {
moveFocus({ delta: { x: 0, y: 1 }, isTyping: true });
}
} else {
preventDefault = false;
}
} else {
preventDefault = false;
}
break;
}
Expand All @@ -231,7 +240,15 @@ export const Grid = () => {
event.preventDefault();
}
},
[currentCell, currentEntryId, moveFocus, handleTab, updateCell],
[
currentCell,
currentEntryId,
moveFocus,
handleTab,
updateCell,
cheatMode,
cells,
],
);

const selectClickedCell = useCallback(
Expand Down Expand Up @@ -353,11 +370,14 @@ export const Grid = () => {

return (
<svg
style={{
background: theme.background,
width: '100%',
maxWidth: width,
}}
css={[
css`
background: ${theme.background};
width: 100%;
max-width: ${width}px;
`,
cheatStyles,
]}
ref={gridRef}
viewBox={`0 0 ${width} ${height}`}
tabIndex={-1}
Expand Down
93 changes: 93 additions & 0 deletions libs/@guardian/react-crossword/src/hooks/useCheatMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { css } from '@emotion/react';
import type { RefObject } from 'react';
import { useCallback, useEffect, useState } from 'react';

const konamiCode = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];

const cheatStyles = css`
@keyframes violent-shake {
0% {
transform: translate(0, 0) rotate(0deg);
}
10% {
transform: translate(-5px, -5px) rotate(-2deg);
}
20% {
transform: translate(5px, -5px) rotate(2deg);
}
30% {
transform: translate(-5px, 5px) rotate(-2deg);
}
40% {
transform: translate(5px, 5px) rotate(2deg);
}
50% {
transform: translate(-5px, -5px) rotate(-2deg);
}
60% {
transform: translate(5px, -5px) rotate(2deg);
}
70% {
transform: translate(-5px, 5px) rotate(-2deg);
}
80% {
transform: translate(5px, 5px) rotate(2deg);
}
90% {
transform: translate(-5px, -5px) rotate(-2deg);
}
100% {
transform: translate(0, 0) rotate(0deg);
}
}
&.cheat-mode {
animation: violent-shake 0.15s ease-out;
}
`;

export const useCheatMode = (ref: RefObject<SVGSVGElement>) => {
const [konamiProgress, setKonamiProgress] = useState<string[]>([]);
const [cheatMode, setCheatMode] = useState(false);

const onKeyDown = useCallback(
(event: KeyboardEvent) => {
if (cheatMode) {
return;
}

if (konamiCode[konamiProgress.length] === event.key) {
setKonamiProgress([...konamiProgress, event.key]);
} else {
setKonamiProgress([]);
}
},
[cheatMode, konamiProgress],
);

useEffect(() => {
if (konamiProgress.length === konamiCode.length) {
document.removeEventListener('keydown', onKeyDown);
setCheatMode(true);
ref.current?.classList.add('cheat-mode');
}
}, [konamiProgress.length, onKeyDown, ref]);

useEffect(() => {
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [onKeyDown]);

return [cheatMode, cheatStyles];
};

0 comments on commit de3d764

Please sign in to comment.