
An AI that sets puzzles
The Go, Gomoku and Othello AIs so far were opponents that play against people. The crossword is different. The AI neither solves puzzles nor competes with people; it sets puzzles for people to solve. The puzzles are Korean crosswords, so each grid cell holds one Korean syllable.
The web app was built on the same frame as the other games.
- It lives at crossword.mariago.kr. It runs on port 3009 of the Mac at home, and nginx at the IDC forwards to it.
- Choosing a difficulty in the lobby and entering a game room creates a new grid.
- The grid and every single input are saved to SQLite as a game record.
The first order: words → clues → grid
At first, it picked words from a word bank, attached a clue to each word and then joined those words together on the grid. If a word shared a syllable with a word already placed, it was laid across at that spot.
The resulting grids were not crosswords.
- A 12×12 grid had only 7–8 clues. Most of the grid was black squares.
- Most answers were two syllables.
- Words were joined only by their first and last syllables, lining up like a slanted staircase. There were almost no crossings through middle syllables, such as 크리스마스 (Christmas) and 알프스 (the Alps) crossing at the shared syllable 스.
- Words clustered only in the middle of the grid.
- When I played two hard grids in a row, every word but one had appeared in the previous grid.
The 24 grids saved on the first day
The game record DB still holds 24 grids made that day. All are 12×12 hard grids.
- They had 6–8 words, 6.8 on average.
- White squares made up 6.5% of the grid on average.
- 76% of the answers were two syllables.
- Not a single grid had 180° symmetry.
For 22 of the 24 grids, I pressed new grid again without filling in a single square. I regenerated them the moment I saw them.
Things we tried to fix
- We required 15 or more across and down clues. It passed the tests, but the screen still showed grids with 8 clues. The lobby kept reopening a grid made earlier.
- We built a text agent that runs the generator directly and prints the grid and the clue list as text. It also checks the grids in the game record DB and flags islands of two-syllable words.
- An approach that filled the grid by repeating three-syllable cross-shaped clusters also came up. It is the picture at the top right. The number of clues went up, but every answer was three syllables and the clusters did not connect to each other. It was a very AI-like idea, and no crossword like this exists in the world.
Deciding to change the order
If you place words first, the shape of the grid simply follows as a result, and you cannot control it. So we decided to build the grid first, then pick words that fit the slot lengths, and write the clues last.
The actual code for this step
This is the part where the text agent checks saved grids. If the average length is short or there are no middle crossings, it prints that a new game is needed.
scripts/text-agent.ts
for (const row of rows) {
let across = 0;
let down = 0;
let mid = 0;
let avg = 0;
try {
const puzzle = JSON.parse(row.puzzle_json) as Puzzle;
across = puzzle.across?.length ?? 0;
down = puzzle.down?.length ?? 0;
mid = countMiddleCrosses(puzzle);
avg = avgWordLength(puzzle);
} catch {
/* ignore */
}
const mark = avg >= 2.7 && mid >= 1 ? "교차있음" : "2글자섬 ← 새 게임 필요";
console.log(
`${row.code} ${row.difficulty} ${row.size}×${row.size} ${row.status} 가로 ${across} 세로 ${down} 교차 ${mid} 평균 ${avg.toFixed(2)} ${mark} ${row.created_at}`,
);These are the rules of the placer that joins words together. A new word must overlap an already placed word in exactly one square, and no other syllable may sit in an adjacent square. This placer still remains as a fallback path, and it comes up again in post 3.
lib/gridFill.ts
for (const [nr, nc] of neighbors) {
if (inBoard(size, nr, nc) && grid[idx(size, nr, nc)] != null) return null;
}
}
if (intersections !== 1 || newCells < 1) return null;The next post is "2. Grid first, words next, clues last".
COMMENTS · CHAPTER 01
Placing words first did not give a crossword 0 comments
No comments yet. Leave a question or anything you would like to add.
To post a comment, continue with Google and register a nickname and email address.
Continue with Google