Skip to content

Commit

Permalink
More features
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Ferentz committed Jan 22, 2024
1 parent 8a6bb1b commit 27e34a0
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 29 deletions.
31 changes: 24 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import type { Component } from 'solid-js';
import styles from './App.module.css';
import { GameContainer } from './GameContainer';
import { createSignal, type Component, Show, onMount } from 'solid-js'
import styles from './App.module.css'
import { GameContainer } from './GameContainer'
import { GameMenu } from './GameMenu'
import { gameController } from './stores/GameStore'

const App: Component = () => {
const [hasPreviousGame, setHasPreviousGame] = createSignal(false)
onMount(() => {
setHasPreviousGame(gameController.loadGame())
})
const [showGame, setShowGame] = createSignal(false)

return (
<div class={styles.App}>
<GameContainer />
<Show when={!showGame()}>
<GameMenu
canResume={hasPreviousGame()}
onNewGame={() => setShowGame(true)}
onResumeGame={() => setShowGame(true)}
/>
</Show>
<Show when={showGame()}>
<GameContainer />
</Show>
</div>
);
};
)
}

export default App;
export default App
27 changes: 10 additions & 17 deletions src/GameContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import { For, Show, createComputed, createEffect, createSignal, onMount } from 'solid-js'
import { For, Show, createEffect, createSignal, onMount } from 'solid-js'
import { NumberSquare } from './NumberSquare'
import styles from './GameContainer.module.css'
import { boardStore, solutionStore } from './stores/GameStore'
import { generateSudoku } from './game/generate'
import { gameController } from './stores/GameStore'

const { solution, setSolution } = solutionStore
const { board, setBoard } = boardStore
const [selected, setSelected] = createSignal<{ x: number; y: number } | null>(null)
export const GameContainer = () => {
onMount(() => {
const game = generateSudoku('Easy')
setSolution(game.solution)
setBoard(game.puzzle)
})

const [selected, setSelected] = createSignal<{ x: number; y: number } | null>(null)
const validateGuess = (row: number, col: number) => {
return (guess: number) => {
const solutionValue = solution[row][col]
const solutionValue = gameController.solutionBoard[row][col]
const isCorrect = guess === solutionValue
if (isCorrect) {
setBoard(row, col, guess)
gameController.setCurrentBoardValue(row, col, guess)
}
gameController.saveGame()
return isCorrect
}
}

const isSolved = () => board.flatMap((m) => m).every((m) => !!m)
const isSolved = () => gameController.currentBoard.flatMap((m) => m).every((m) => !!m)
createEffect(() => {
if (isSolved()) {
setTimeout(() => {
Expand All @@ -35,16 +27,17 @@ export const GameContainer = () => {
})

const handleSelect = ({ x, y }: { x: number; y: number }) => {
const board = gameController.currentBoard
if (x < 0) x = board[0].length - 1
if (y < 0) y = board.length - 1
if (x >= board[0].length) x = 0
if (y >= board.length) y = 0
setSelected({ x, y })
}
return (
<Show when={board.length} fallback="Loading...">
<Show when={gameController.currentBoard.length} fallback="Loading...">
<div class={styles.container}>
<For each={board}>
<For each={gameController.currentBoard}>
{(row, rowIndex) => (
<For each={row}>
{(item, colIndex) => (
Expand Down
50 changes: 50 additions & 0 deletions src/GameMenu.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

@property --fromColor {
syntax: '<color>';
inherits: false;
initial-value: #ff105f;
}

@property --toColor {
syntax: '<color>';
inherits: false;
initial-value: #ffad06;
}

.buttonContainer {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
margin-top: 20px;

button {
margin-top: 1vh;
padding: 1vh 2vh;
border-radius: 1rem;
position: relative;
border: none;
background: linear-gradient(to right,var(--fromColor),var(--toColor));
background-repeat: repeat-x;
background-position: 0 0;
color: #fff;
font-weight: bold;
font-size: 1.2rem;
cursor: pointer;
transition: --fromColor 1s, --toColor 1s;
&:focus-visible {
outline: white dashed 1px;
outline-offset: -2px;
}
&:hover {
--fromColor: #ffad06;
--toColor: #ff105f;
}
}
}
42 changes: 42 additions & 0 deletions src/GameMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Show } from 'solid-js'
import { Difficulty } from './game/generate'
import styles from './GameMenu.module.css'
import { gameController } from './stores/GameStore'

type GameMenuProps = {
canResume: boolean,
onNewGame: () => void,
onResumeGame: () => void,
}
export const GameMenu = (props: GameMenuProps) => {
const handleNewGame = (difficulty: Difficulty) => {
gameController.createGame(difficulty)
props.onNewGame()
}

const handleResumeGame = () => {
props.onResumeGame()
}

return (
<div class={styles.container}>
<h1>Game Menu</h1>
<div class={styles.buttonContainer}>
<Show when={props.canResume}>
<button type="button" onClick={handleResumeGame}>
Resume Game
</button>
</Show>
<button type="button" onClick={() => handleNewGame('Easy')}>
New Game - Easy
</button>
<button type="button" onClick={() => handleNewGame('Medium')}>
New Game - Medium
</button>
<button type="button" onClick={() => handleNewGame('Hard')}>
New Game - Hard
</button>
</div>
</div>
)
}
4 changes: 3 additions & 1 deletion src/game/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ function getNumberToRemove(difficulty: Difficulty): number {
export type Game = {
solution: number[][]
puzzle: number[][]
current: number[][]
mistakes: number
difficulty: Difficulty
}
function shuffle(input: number[]) {
Expand All @@ -46,7 +48,7 @@ export function generateSudoku(difficulty: Difficulty): Game {
// Remove numbers to create a puzzle
removeNumbers(board, getNumberToRemove(difficulty)) // Adjust the number of numbers removed to control difficulty

return { solution, puzzle: board, difficulty }
return { solution, puzzle: board, difficulty, mistakes: 0, current: structuredClone(board) }
}

function solveSudoku(board: number[][]): boolean {
Expand Down
83 changes: 79 additions & 4 deletions src/stores/GameStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
import { createStore } from 'solid-js/store'
import { createStore, reconcile } from 'solid-js/store'
import { Difficulty, Game, generateSudoku } from '../game/generate'
import { createSignal } from 'solid-js'

const [board, setBoard] = createStore<number[][]>([])
const [puzzle, setPuzzle] = createStore<number[][]>([])
const [solution, setSolution] = createStore<number[][]>([])
const [current, setCurrent] = createStore<number[][]>([])
const [difficulty, setDifficulty] = createSignal<Difficulty>('Easy')
const [mistakes, setMistakes] = createSignal<number>(0)

export const boardStore = { board, setBoard }
export const solutionStore = { solution, setSolution }
// export const boardStore = { board, setBoard }
// export const solutionStore = { solution, setSolution }

class GameController {
private storeGame(game: Game) {
setSolution(reconcile(game.solution))
setPuzzle(reconcile(game.puzzle))
setCurrent(reconcile(game.current))
setDifficulty(game.difficulty)
setMistakes(game.mistakes)
}
createGame(difficulty: Difficulty): void {
const game = generateSudoku(difficulty)
this.storeGame(game)
this.saveGame()
}

get currentBoard(): number[][] {
return current
}

get solutionBoard(): number[][] {
return solution
}

get puzzleBoard(): number[][] {
return puzzle
}

get difficulty(): Difficulty {
return difficulty()
}

get mistakes(): number {
return mistakes()
}

setCurrentBoardValue(row: number, col: number, value: number): void {
setCurrent(row, col, value)
}


saveGame(): void {
if (!this.currentBoard.length) return
localStorage.setItem(
'game',
btoa(JSON.stringify({ solution, puzzle, current, difficulty: difficulty(), mistakes: mistakes() }))
)
}

loadGame(): boolean {
const game = localStorage.getItem('game')
if (game) {
this.storeGame(JSON.parse(atob(game)))
return true
}
return false
}

deleteGame(): void {
localStorage.removeItem('game')
this.storeGame({
solution: [],
puzzle: [],
current: [],
difficulty: 'Easy',
mistakes: 0,
})
}
}

export const gameController = new GameController()

0 comments on commit 27e34a0

Please sign in to comment.