Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrobangs-patch-1 #23

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)

9 changes: 5 additions & 4 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
overflow: hidden;
}

.undo {
margin-top: 1em;
}

.app-wrapper {
height: 100vh;
width: 100vw;
Expand All @@ -25,6 +21,11 @@
justify-items: center;
}

.undo {
grid-column-start: 1;
grid-column-end: 3;
}

.header {
text-align: center;
margin: 0;
Expand Down
63 changes: 47 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IState>(getInitialState)
const [prevState, setPrevState] = useState<IState>(state)
const [undosLeft, setUndosLeft] = useState<number>(INITIAL_UNDOSLEFT)
const [undosLeft, setUndosLeft] = useState<number>(undos)
const [queue, setQueue] = useState<(IPiece | null)[] | null>(null)
const undoRef = useRef<HTMLButtonElement>(null!)
const boardRef = useRef<HTMLDivElement>(null)
const [pointer, setPointer] = useState<{
offset: [number, number],
Expand Down Expand Up @@ -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])

Expand All @@ -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)
Expand All @@ -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()
}
}

Expand All @@ -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)

Expand All @@ -148,10 +170,11 @@ export default function App() {
<div className="options">
<div className="button">Score: {state.score}</div>
<div className="button">High Score: {state.highscore}</div>
<button onClick={resetGame}>Reset game</button>
<button ref={undoRef} disabled={undosLeft <= 0} className='undo' onClick={undo}>
Undo ({undosLeft})
</button>
</div>
{/* <button disabled={undosLeft <= 0} className='undo' onClick={undo}>
Undo ({undosLeft})
</button> */}
</header>
<main className="game">
<div className="board-wrapper">
Expand Down Expand Up @@ -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)
}
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ export const hues = [
]

export const highscoreLocalStorageKey = "KLODS_HIGHSCORE"

export const undos = 3