presque.cool
Back to projects
seedoku icon

seedoku

Avr 2026 Game
Visit project

A placement puzzle on a 3×3 grid. Crops, boosters, and synergies between the two. A new puzzle every day.


Le pitch

Seedoku est un puzzle de placement sur une grille 3×3. Chaque jour, une main de 6 tuiles — cultures et boosters — à répartir sur 8 cases disponibles (une est bloquée par un rocher). L’objectif : maximiser le score de récolte en tirant parti des synergies entre les tuiles. Un puzzle par jour, le même pour tout le monde.

Jouer : presque.cool/seedoku/

Durée 7-10 jours
Période Avr 2026
Stack
React TypeScript Vite PWA
Statut En production

Pourquoi ce projet

Seedoku est une rétro-ingénierie de seedle.io, un daily puzzle farming que j’avais trouvé satisfaisant. La démarche est la même que pour Grid4 : comprendre ce qui rend un jeu simple agréable en essayant de le reproduire aussi fidèlement que possible.

Le game design étant largement emprunté, j’ai concentré l’effort ailleurs : l’exécution technique, et surtout le juice — la couche de son, d’animations et de feedback qui transforme un puzzle fonctionnel en quelque chose de plaisant à toucher.


Le puzzle du jour

Le puzzle est généré de façon déterministe à partir de la date. Un hash FNV1a sur la chaîne seedoku-daily-v1:YYYY-MM-DD, passé dans un PRNG Mulberry32, produit la position du rocher et les 6 tuiles de la main. Pas de serveur, tout le monde a le même puzzle.

La navigation par date (/date/YYYYMMDD) permet de rejouer les jours précédents. Un lien partageable encode le score et la note obtenus.


Le moteur de scoring

Chaque culture a ses propres règles de placement. La patate marque davantage en coin (+8) ou en bord (+4). La citrouille veut le centre (+18). Le riz a besoin d’un arroseur adjacent (+8). Le champignon perd son bonus si un booster est à portée. Le berry-bush prend une pénalité de -4 s’il n’est pas à côté d’un booster.

Les boosters ne scorent pas eux-mêmes : ils appliquent des effets sur leurs voisins. L’arroseur ajoute +5 par culture adjacente, le soleil multiplie sa rangée entière par 2, le chilli double chaque culture qu’il touche directement.

Le tout est modélisé via des discriminated unions TypeScript (IntrinsicRule, BoosterEffect). Pas de switch sur les identifiants de tuile — chaque règle est une donnée, le moteur la lit. Ça rend l’ajout d’une nouvelle tuile trivial.

Pour le calcul des notes (D → S), le moteur calcule par brute-force DFS le score théorique maximum pour la grille et la main du jour. La note est le ratio score obtenu / score maximum possible.


Le juice

C’est là où j’ai passé le plus de temps — et c’était délibéré.

Les tuiles se posent avec une animation de rebond. La récolte se déroule pas à pas : d’abord les scores de base s’affichent sur chaque case, puis les bonus additifs apparaissent un par un avec un delta visible, puis les multiplicateurs s’appliquent. Chaque étape a un son. Le total monte progressivement dans un ticker dont la vitesse s’accélère.

Pendant le drag, chaque case cible affiche en temps réel le score que la tuile obtiendrait si elle était posée là. Les synergies deviennent lisibles sans avoir à mémoriser les règles.


Stack technique

  • React 19 + TypeScript (strict, zero any)
  • Vite 8 comme bundler (avec plugin-react)
  • Tailwind CSS v4 en mode CSS-first (OKLCH)
  • Zustand pour le state : game state, historique (undo), settings, toasts
  • Web Audio API pour les SFX
  • PWA via vite-plugin-pwa (Workbox, installable, offline)
  • i18n maison (français/anglais)
  • CSP avec hashes auto-générés
  • Compression Brotli + Gzip

Architecture en couches : logique de jeu pure dans engine/ (scoring, puzzle, types), données dans data/, hooks d’orchestration, composants UI. Le moteur de scoring est découplé du rendu — les breakdowns de score servent à la fois aux tooltips en jeu et à l’animation de récolte.


Évolutions

DateVersionDescription
Avr 20261.0Version complète

Bilan

Durée : 7-10 jours.

Ce que j’ai appris :

  • Le juice, c’est la moitié du travail. Un puzzle de placement correct prend quelques jours. Rendre chaque interaction satisfaisante en prend autant. L’animation de récolte séquentielle, le score en temps réel au drag, la progression sonore — aucun de ces détails n’est nécessaire, tous changent le ressenti.
  • La modélisation par données plutôt que par code conditionnel (les IntrinsicRule et BoosterEffect typés) a rendu l’ajout et le débogage des règles de tuile très simple. Une leçon que je retiens pour les prochains jeux.

Ce que je referais pareil :

  • Le brute-force DFS pour le score max. Sur une grille 3×3 avec 6 tuiles et 8 cases, l’espace est petit — pas besoin d’optimisation. La note est juste, le calcul se fait en quelques millisecondes.
  • Emprunter un game design existant et créditer. Ça permet de se concentrer sur l’exécution plutôt que sur l’équilibrage.

Ce que je changerais :

  • J’ajouterais un mode Série sur 7 jours : même puzzle quotidien, mais avec une progression visible (streak et petits objectifs optionnels comme “faire A ou plus trois jours d’affilée”). Ça renforcerait la rétention sans alourdir le cœur du jeu.

The pitch

Seedoku is a placement puzzle on a 3×3 grid. Each day, a hand of 6 tiles — crops and boosters — to arrange across 8 available cells (one is blocked by a rock). The goal: maximize your harvest score by making the most of the synergies between tiles. One puzzle per day, the same for everyone.

Play: presque.cool/seedoku/

Duration 7-10 days
Period Apr 2026
Stack
React TypeScript Vite PWA
Status In production

Why this project

Seedoku is a reverse-engineering of seedle.io, a daily farming puzzle game I found satisfying to play. The approach is the same as Grid4: understand what makes a simple game feel good by trying to reproduce it as faithfully as possible.

With the game design largely borrowed, I focused effort elsewhere: the technical execution, and especially the juice — the layer of sound, animation, and feedback that turns a functional puzzle into something pleasant to touch.


The daily puzzle

Each puzzle is generated deterministically from the date. An FNV1a hash of the string seedoku-daily-v1:YYYY-MM-DD, passed through a Mulberry32 PRNG, produces the rock position and the 6-tile hand. No server needed — everyone gets the same puzzle.

Date navigation (/date/YYYYMMDD) lets players replay past puzzles. A shareable link encodes the day’s score and grade.


The scoring engine

Each crop has its own placement rules. Potato scores more in a corner (+8) or on an edge (+4). Pumpkin wants the center (+18). Rice needs an adjacent sprinkler (+8). Mushroom loses its bonus if any booster is nearby. Berry-bush takes a -4 penalty if no adjacent booster is present.

Boosters don’t score themselves: they apply effects to their neighbors. Sprinkler adds +5 per adjacent crop, Sun multiplies its entire row by 2, Chilli doubles each crop it directly touches.

Everything is modeled with TypeScript discriminated unions (IntrinsicRule, BoosterEffect). No switch statements on tile IDs — each rule is data, the engine reads it. Adding a new tile is trivial.

To compute grades (D → S), the engine runs a brute-force DFS to find the theoretical maximum score for the day’s grid and hand. The grade is the ratio of your score to the maximum possible.


The juice

This is where most of the time went — deliberately.

Tiles land with a bounce animation. The harvest plays out step by step: base scores appear on each cell, then additive bonuses show up one by one with a visible delta, then multipliers apply. Each step has a sound. The total climbs progressively in a ticker that accelerates as it goes.

While dragging, each target cell shows in real time the score the tile would get if placed there. Synergies become readable without having to memorize the rules.


Tech stack

  • React 19 + TypeScript (strict, zero any)
  • Vite 8 as bundler (with plugin-react)
  • Tailwind CSS v4 in CSS-first mode (OKLCH)
  • Zustand for state: game state, move history (undo), settings, toasts
  • Web Audio API for SFX
  • PWA via vite-plugin-pwa (Workbox, installable, offline)
  • i18n custom (French/English)
  • CSP with auto-generated hashes
  • Compression Brotli + Gzip

Layered architecture: pure game logic in engine/ (scoring, puzzle, types), data in data/, orchestration hooks, UI components. The scoring engine is decoupled from rendering — score breakdowns power both the in-game tooltips and the harvest animation.


Timeline

DateVersionDescription
Apr 20261.0Full release

Looking back

Duration: 7-10 days.

What I learned:

  • Juice is half the work. A functional placement puzzle takes a few days. Making every interaction satisfying takes just as long. The sequential harvest animation, the real-time score preview during drag, the layered sound — none of these are necessary, all of them change how the game feels.
  • Modeling rules as data rather than conditional code (the typed IntrinsicRule and BoosterEffect) made adding and debugging tile rules straightforward. Worth keeping for future games.

What I’d do the same:

  • The brute-force DFS for the max score. On a 3×3 grid with 6 tiles and 8 cells, the search space is small — no optimization needed. The grade is accurate, the calculation takes a few milliseconds.
  • Borrowing an existing game design and crediting it. Lets you focus on execution rather than balancing.

What I’d change:

  • I’d add a 7-day Streak mode: same daily puzzle, but with visible progression (streak tracking and optional mini-goals like “hit A or higher three days in a row”). It would improve retention without making the core game heavier.

Settings

Language
Theme
Privacy Policy