diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6c5aa7 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ + +# Klods + +Klods is a tetris-like game where you have to think ahead. Random puzzle pieces appear, and it is your job to fit them together, to clear rows and columns. Each tile cleared gives a point! + +![App Screenshot](https://screenshot.launchbrightly.com/klods/klods/desktop/dark/game.png) + diff --git a/src/App.css b/src/App.css index 060ec42..8afcc54 100644 --- a/src/App.css +++ b/src/App.css @@ -10,10 +10,6 @@ overflow: hidden; } -.undo { - margin-top: 1em; -} - .app-wrapper { height: 100vh; width: 100vw; @@ -25,6 +21,11 @@ justify-items: center; } +.undo { + grid-column-start: 1; + grid-column-end: 3; +} + .header { text-align: center; margin: 0; diff --git a/src/App.tsx b/src/App.tsx index 2c3bf90..1a70859 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,16 +4,15 @@ import { pieces } from './pieces' import type { IBoard, IPiece, IState } from './model' import { Square } from './components/Square' import { checkIfPieceFitsAndUpdateBoard, clearFullRows, createEmptyBoard, generateFitTest, getPieceHeight, getPieceWidth, snapPositionToBoard, drawN, calculateLocationFromIndex, createRainbowBoard, checkIfPieceCanBePlaced } from './util' -import { boardSize, getSquareSizePixels, highscoreLocalStorageKey } from './constants' +import { boardSize, getSquareSizePixels, highscoreLocalStorageKey, undos } from './constants' import { usePointerExit } from './hooks' -const INITIAL_UNDOSLEFT = 3 - export default function App() { const [state, setState] = useState(getInitialState) const [prevState, setPrevState] = useState(state) - const [undosLeft, setUndosLeft] = useState(INITIAL_UNDOSLEFT) + const [undosLeft, setUndosLeft] = useState(undos) const [queue, setQueue] = useState<(IPiece | null)[] | null>(null) + const undoRef = useRef(null!) const boardRef = useRef(null) const [pointer, setPointer] = useState<{ offset: [number, number], @@ -52,7 +51,7 @@ export default function App() { const { width, height, x, y } = boardRef.current.getBoundingClientRect() const { colIndex, rowIndex } = snapPositionToBoard({ boardSize, width, height, x, y, pageX: mousePosWithOffset[0], pageY: mousePosWithOffset[1] }) const [fit, board] = checkIfPieceFitsAndUpdateBoard( - { board: state.board, piece: selectedPiece, squareLocation: [colIndex, rowIndex], boardSize } ) + { board: state.board, piece: selectedPiece, squareLocation: [colIndex, rowIndex], boardSize }) return [fit, fit ? board : state.board] }, [gameOver, mousePosWithOffset, state]) @@ -67,6 +66,9 @@ export default function App() { return { boardWithClearedRows, rowsToClear, colsToClear} }, [boardWithPreview, fit, state.board]) + // set this initial to disabled. So we don't mess with the 'undosLeft <= 0' on the button tag + useEffect(() => {undoRef.current.disabled = true}, [undoRef]) + useEffect(() => { const highscore = Math.max(state.highscore, state.score).toString() localStorage.setItem(highscoreLocalStorageKey, highscore) @@ -77,16 +79,32 @@ export default function App() { ...getInitialState(), highscore: Math.max(prevState.highscore, prevState.score) })) - setUndosLeft(INITIAL_UNDOSLEFT) - setPrevState(getInitialState) + setUndosLeft(undos) + setPrevState(getInitialState()) + // disable on reset + undoRef.current.disabled = true } const undo = () => { + // console.log(Object.is(prevState, state)) // This gives false.. - if(JSON.stringify(prevState) !== JSON.stringify(state)){ + // this will just exclude userPieces from both objects, because they (almost) never match. + // this wil prevent undo when reset has been done + + const state_ = JSON.stringify(state, (k, v) => k === "userPieces" ? null : v) + const getInitialState_ = JSON.stringify(getInitialState(), (k, v) => k === "userPieces" ? null : v) + + if( + JSON.stringify(prevState.userPieces) !== JSON.stringify(state.userPieces) && + state_ !== getInitialState_ + ){ setState(prevState) setQueue(state.userPieces) setUndosLeft(undosLeft - 1) + // disable after undo + undoRef.current.disabled = true + // Cancel that the brick was picked up, because we set the prevState on the pick-up + pointerUpHandler() } } @@ -106,23 +124,27 @@ export default function App() { const { newBoard, rowsAndColsCleared } = clearFullRows(boardWithPreview, boardSize) setState(prevState => { const userPieces = prevState.userPieces.map((piece, i) => i === selectedPieceIndex ? null : piece) + // only enable if user has any undos left + return ({ board: newBoard, userPieces: userPieces.every(p => p == null) - ? getNewPieces(queue) + ? getNewPieces(queue, undoRef) : userPieces, selectedPieceIndex: null, score: prevState.score + rowsAndColsCleared * boardSize, highscore: prevState.highscore }) }) - + undoRef.current.disabled = undosLeft <= 0 + setPrevState(state) setQueue(null) // if (shouldSucceed !== fit) { // prompt("red test", newTest) // } - }, [boardWithPreview, fit, queue, state]) + + }, [boardWithPreview, fit, queue, state, undoRef, undosLeft]) usePointerExit(pointerUpHandler) @@ -148,10 +170,11 @@ export default function App() {
Score: {state.score}
High Score: {state.highscore}
+ +
- {/* */}
@@ -269,9 +292,17 @@ const getOffsetFromPieceInPixels: (p: IPiece, pointerType: string) => [number, n const getInitialState: () => IState = () => ({ highscore: Number(window.localStorage.getItem(highscoreLocalStorageKey)) ?? 0, board: createEmptyBoard(boardSize), - userPieces: getNewPieces(null), + userPieces: getNewPieces(null, null), selectedPieceIndex: null, score: 0 }) -const getNewPieces = (queue: (IPiece | null)[] | null) => (queue ? queue : drawN(pieces, 3)) +const getNewPieces = (queue: (IPiece | null)[] | null, undoRef: any) => { + if(queue !== null){ + return queue + } else{ + // disable undo when new round has been seen + undoRef && (undoRef.current.disabled = true) + return drawN(pieces, 3) + } +} diff --git a/src/constants.ts b/src/constants.ts index a89549a..256ab13 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -29,3 +29,5 @@ export const hues = [ ] export const highscoreLocalStorageKey = "KLODS_HIGHSCORE" + +export const undos = 3