Sokak Efsaneleri
A slide-and-merge puzzle about the street animals of Istanbul. Swipe and every creature slides at once; two of a kind merge, until a stray kitten becomes a legend.
The code is private, so this page explains how the game is built instead of linking to it.



Summary
- Problem
- Hundreds of puzzle levels had to be fair and get harder on purpose, without a crowd of testers, and the game had to reach iOS from a Windows machine.
- My part
- All of it, as the only developer: game design, the C# code and its tests, the level generator and solver, the release pipeline. The art is AI-generated from briefs I wrote.
- Outcome and status
- All 500 levels ship already solved, the generated ones by breadth-first search, and 803 tests check the rules without Unity. Built on Windows and signed on a cloud Mac, version 1.0 is in App Store review; an Android closed test is next.
From the street to a legend
The real game, as it appears in its App Store listing, plus one capture from a real iPhone.
Screen 1 of 7
The street
1Your street
The creatures live on a postcard street in Balat. Feed them and their love grows.
2Two districts
Kadıköy and Balat, each with its own creature types and things to tap.
3On a real iPhone
Kadıköy's home scene, captured on a device.
Playing
4A level
Swipe and every creature slides at once. Reach the goal before the moves run out.
5Efsane!
Eight of a kind become a legend: Yavru, Afacan, Patron, then Efsane.
Collecting
6The album
Five species with four stages each: twenty characters to collect.
7A legend's card
Every Efsane has a nickname, a line of story and three costumes.
Try the rule
Swipe the board or use the arrow keys. Two of the same kind merge and their counts add up. Make a cat of 8 to raise an Efsane.
- Yavru1+
- Afacan2+
- Patron4+
- Efsane8+
A small re-telling of the rule for this page, not the game's code. The shortest solution shown here is found with the same idea the game uses: breadth-first search.
While the board has focus, the arrow keys swipe it. The buttons under it do the same, and the board can be read as a table further down.
Moves 0 of 8. The shortest win takes 5.
The arrow keys work once the board has focus.Swipe sideways on the board. To swipe up and down too, press Play on the board first.
Show the board as a table
| Column 1 | Column 2 | Column 3 | Column 4 | |
|---|---|---|---|---|
| Row 1 | Cat 1 | empty | Gull 1 | Cat 1 |
| Row 2 | Cat 1 | Cat 1 | empty | Cat 1 |
| Row 3 | Cat 1 | Gull 1 | Cat 1 | empty |
| Row 4 | empty | empty | Gull 1 | Cat 1 |
How it works
Generating levels that are fair, measuring difficulty without a crowd of testers, shipping to iOS from Windows and keeping AI-made art readable.
Every level is solved before you see it
Levels 1 to 60 were tuned by hand in a level editor inside Unity. Levels 61 to 500 come from a seeded generator: each candidate board is solved with breadth-first search, thrown away if a creature can never meet its own kind, and kept only if its depth (the length of its shortest win) fits its slot, the level's place in a block of ten. The levels that pass go into a pre-built level table that ships with the game.
- SeedMulberry32: same seed, same board
- Generatorboard recipes for each slot
- Dead-end checkcan every creature meet its kind?
- BFS solvershortest win is the level's depth
- Move budgetdepth plus a slack
- Level tablepre-built: levels 61 to 500, all solved
Difficulty measured with a simulated player
A player model looks a few swipes ahead and picks moves with a softmax; its parameters were fitted to my own star history. It plays each candidate level up to two thousand times, and the share of lost games has to land inside the range set for the level's slot. Every tenth level is a hard one, so the curve is a saw.
- Regular level
- Hard level, every tenth
Show as a table
| Level | Target loss |
|---|---|
| 61 | 15–28% |
| 62 | 15–28% |
| 63 | 15–28% |
| 64 | 22–35% |
| 65 | 22–35% |
| 66 | 22–35% |
| 67 | 28–40% |
| 68 | 28–40% |
| 69 | 28–40% |
| 70 | 40–55% |
| 71 | 15–28% |
| 72 | 15–28% |
| 73 | 15–28% |
| 74 | 22–35% |
| 75 | 22–35% |
| 76 | 22–35% |
| 77 | 28–40% |
| 78 | 28–40% |
| 79 | 28–40% |
| 80 | 40–55% |
Show as a table
| Depth | Measured loss |
|---|---|
| 4 | about 10% |
| 5 | 20–30% |
| 6 | 45–55% |
| 7 | 60–70% |
Six layers, one direction
The rules, the solver and the economy live in a plain C# core with no Unity in it, so 803 tests run in seconds on .NET. Each layer is its own assembly, and dependencies only point down.
- Editorscene builder, level editor27 filesGameboard, HUD, scenes107 files
- depends onServicesinterfaces + Firebase, AdMob, IAP adapters55 filesDataScriptableObject catalogs and tuning8 files
- depends onCorepure C#: rules, solver, economy48 files
Balance changes without a release
Every setting resolves as code default, then a tuning asset, then Remote Config. Analytics only start after the player accepts the consent card, and a daily BigQuery export feeds a level funnel that shows where people get stuck.
- Unity clientlevel_start, level_end
- Consent cardKVKK, before any event
- Firebase Analyticswith Crashlytics
- BigQuerydaily export, SQL funnel
- Remote Config121 parameters back to the game
Shipping to iOS without a Mac
I develop on Windows. Unity exports the Xcode project, a GitHub release carries it to Codemagic, and a cloud Mac signs and uploads the build. App Store Connect itself is scripted through its API: builds, in-app purchases and review notes.
- Unity on WindowsIL2CPP, ARM64
- Xcode exportzipped project
- GitHub releasea small private CI repo
- CodemagicMac mini M2: pods, sign, build
- TestFlightthen App Store review
AI-made art with hard checks
I wrote the art briefs and Codex generated the images. Deterministic Python scripts then assemble the sprites and check every creature at 64 pixels, in greyscale, as a bare silhouette and under three kinds of colour blindness, so a species stays recognisable on a small, busy board.




Five prototypes, one game
Before any Unity code I built five quick HTML5 prototypes and played them. The slide-and-merge one stayed.
Prototype 1 of 5

Blast

Sort

Block puzzle

Çini kaydır

Sokak Efsanelerikept
In numbers
- 500
- levels, each solved before it ships
- 803
- core tests that run on plain .NET, without Unity
- 1,042
- EditMode tests inside Unity
- 474
- PlayMode tests inside Unity
- 121
- Remote Config parameters the game reads
- 49k
- lines of C#
Decisions I would make again
Solve every level before it ships
A level that cannot be won, or is won by accident, never reaches a player, and stars are scored against the real shortest solution. At first the phone picked each next level during play, but with the player model a candidate needs a full search and up to two thousand simulated games, so levels 61 to 500 now ship as a table. The cost: any change to the generator changes every board from level 61 on, which after release needs a migration plan.
Keep the rules out of the engine
The core knows nothing about Unity, so the rules, the solver and the economy are tested in seconds and reused by the level tools.
Measure before tuning
Difficulty targets come from a calibrated model and are checked against the BigQuery funnel, not from my own feeling after the hundredth playthrough.

