jengine-kotoba
Word tiles: kana placed one at a time where they make a word, read across or down - a timed puzzle, a real-time word battle with combos, a bot, and a Japanese dictionary with meanings.
| Kind | Project package: linked into the builds of the projects that list it |
| Source | packages/jengine-kotoba |
| Version | 0.1.0 |
| Entry point | jengine_kotoba::KotobaPackage |
| Script namespace | Kotoba |
| Depends on | jengine-online |
| Listed by | projects/kotodama |
Using it
Section titled “Using it”List it in the top-level "packages" array of project.jengine, together with every package it depends on: a crate dependency compiles a package in but does not register it, and packages_a_project_lists_cover_their_dependencies fails a project that lists only the outer one:
{ "packages": [ { "name": "jengine-kotoba", "source": { "type": "path", "path": "../../packages/jengine-kotoba" } }, { "name": "jengine-online", "source": { "type": "path", "path": "../../packages/jengine-online" } }, { "name": "jengine-net", "source": { "type": "path", "path": "../../packages/jengine-net" } } ]}Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the
editor’s Rebuild viewport banner). Native and web builds link it on their own.
A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field
for a package component and keeps it as inert custom data.
A script can check that the package is linked with if Kotoba == nil then return end.
Components
Section titled “Components”| Scene key | Rust type | |
|---|---|---|
kotoba_table | Table | package only: needs a v2 scene |
kotoba_waves | Waves | package only: needs a v2 scene |
Script commands
Section titled “Script commands”Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.
| Call | Flat name |
|---|---|
Kotoba.puzzle(e, stage) | kotoba_puzzle |
Kotoba.battle(e, rival) | kotoba_battle |
Kotoba.demo(e) | kotoba_demo |
Kotoba.stop(e) | kotoba_stop |
Kotoba.pause(e, paused) | kotoba_pause |
Kotoba.press(e, x, y) | kotoba_press |
Kotoba.drag(e, x, y) | kotoba_drag |
Kotoba.release(e, x, y) | kotoba_release |
Kotoba.select(e, slot) | kotoba_select |
Kotoba.turn(e) | kotoba_turn |
Kotoba.cursor(e, dx, dy) | kotoba_cursor |
Kotoba.place(e) | kotoba_place |
Kotoba.swap(e) | kotoba_swap |
Kotoba.hint(e) | kotoba_hint |
Kotoba.online(e, conn) | kotoba_online |
Kotoba.ripple(e, x, y, strength, r, g, b) | kotoba_ripple |
Kotoba.surge(e, strength, r, g, b) | kotoba_surge |
Kotoba.swell(e, amount) | kotoba_swell |
Scenes that use it
Section titled “Scenes that use it”projects/kotodama/assets/scenes/battle.sceneprojects/kotodama/assets/scenes/howto.sceneprojects/kotodama/assets/scenes/online.sceneprojects/kotodama/assets/scenes/puzzle.sceneprojects/kotodama/assets/scenes/results.sceneprojects/kotodama/assets/scenes/rivals.sceneprojects/kotodama/assets/scenes/stages.sceneprojects/kotodama/assets/scenes/title.scene
From packages/jengine-kotoba/README.md.
Word tiles: kana go on a board one at a time, and a tile only stays where
it makes a word - read left to right, or top to bottom. The game of
Kotoba no Puzzle: Mojipittan, with a real-time two-player battle on top.
It is what Kotodama (projects/kotodama) is made of.
The rule
Section titled “The rule”A tile may go on an empty cell next to a kana, if a word then reads through it. The word need not be the whole run of kana it sits in: a board fills up with runs that are not words themselves (さかなつ), and placing the な still counts because さかな is one. Each direction keeps its longest words, so かな inside さかな is not scored twice. A word is two kana or more.
A tile in hand can be turned through its forms - か → が, は → ば → ぱ, つ → づ → っ, や → ゃ - and when the face it shows makes no word, placing it tries its other forms by itself.
Points are the kana’s values times one less than the word’s length, so
length multiplies; a tile that reads both ways at once scores half again.
What a kana is worth and how often the bag deals it are read off the
dictionary (kana::TileStats): a kana the words use often is common and
cheap, a rare one is rare and dear (い 1, ぬ 4), and a kana no word uses (を)
is never dealt.
Modules
Section titled “Modules”| Module | What it is |
|---|---|
kana | the tiles, their forms, and the bag weights and values |
dict | the dictionary - words/ja.tsv, compiled in: reading, written form, meaning |
board | the grid and the rule: Board::words_at |
play | what both modes share: hands, stocks, placing, the events |
puzzle | one player, a stage, a stock to empty against the clock |
battle | two players at once on one board, every word a hit |
bot | every legal move, and one picked by skill |
draw | the board on screen, in ordinary components |
content | stages and rivals: the project’s data tables, or a built-in one of each |
waves | the sea behind the board: seigaiha built from its pieces, swelling and rippling |
net | a battle over the network: the wire format a server and a client share, prediction and reconciliation |
None of the rules knows about the ECS; draw and lib.rs are the seam.
Puzzle
Section titled “Puzzle”A stage (data/kotoba_stages) is a board drawn in text - . a cell, # a
wall, a kana a kana already there - a stock of tiles dealt from seed, a
hand size, a time and the stars scores. Clear it by placing every
tile. A swap trades the chosen tile for another (5 s; once the stock is
dry, for a fresh tile from the seed), so a hand is never a dead end - only
the clock is. A hint chooses a tile and lights its cell (10 s). Time
left is a bonus. puzzle::autoplay plays a stage with the bot; a test plays
every shipped stage through it and fails one the bot cannot clear.
Battle
Section titled “Battle”Two Fighters on one board, with no turns. A word hits the other player
for its points; words within COMBO_WINDOW (4 s) chain, each step adding a
quarter, up to double. A swap stuns for two seconds. A knockout, the clock
(more life wins) or a full board ends it.
Battle::apply(dict, who, Move) takes a move, Battle::update(dict, dt)
moves time and the bots on. A Move is plain data (Place, Turn,
Swap), applied in the order it arrives - which is what an online match
needs.
Online
Section titled “Online”net is a battle over the network, written once for both ends. A server
room (projects/kotodama/server) owns the Battle, applies moves in the
order they reach it - two players racing for one cell are settled by whose
move it heard first - and sends match (a net::Start), state (a
net::State, ten times a second and after every move), ev (the battle’s
events), again and left. A client keeps a mirror built from the same
Start and overwritten by each state; its own placements are predicted -
down on the mirror the moment they are played (net::predict_place) and
played again on top of every state until one acknowledges their number
(net::apply_state), so a tile never blinks while its move is in flight and
one the server refused (the other player took the cell first) is simply gone
in the next state. Turning a tile is the player’s own business: it is not
sent, and a placement carries the form it was played as.
The package does the client side itself: Kotoba.online(e, conn) names the
connection entity (an online component, jengine-online), after which the
package reads the server’s frames from its online bag, builds the mirror on
match, and sends moves through its queue. So a project using it lists
jengine-online and jengine-net beside this package. The lobby - connect,
wait, rematch - is the scene’s script, talking to the room with
Online.send.
One kotoba_table on a manager entity says where things are drawn:
board_centre and board_max (the box the board fits; cell is the
largest a cell may be), hand_centre / hand_tile for the player’s hand,
rival_hand_centre / rival_tile / show_rival_hand for a battle
rival’s, font (a TTF / OTF with the kana), glyph_centre and
kana_size (where a kana sits on its tile), and the textures - tile,
cell_tex, frame, panel, white - with their nine-slice borders. The
textures are named only at runtime, so preload them (Kotodama puts them in
a sprite_library on the same entity).
The sea
Section titled “The sea”kotoba_waves is the background: seigaiha (青海波), the old pattern of
overlapping waves, built from its pieces - every scale a sprite of its
own (texture, drawn light so the tint colours it), laid in the pattern’s
rows over area about centre - so it can move. It is never still:
- at rest two slow waves cross it (
swell,swell_speed,swell_length), each scale lifting and brightening betweendeepandlightas a crest passes; scales glint like sun on water (glintsa second); a gust rolls in from one side every so often (gusts, the least and most seconds between); and withbpmset it breathes on the music’s beat, read from the music’s own playhead, and holds still when the music does; - under a hand (
hover) scales rise round the pointer, a quick sweep leaves a wake of small rings, and a press on the open sea splashes; a pointer at rest is forgotten after a moment, so a finger lifted from a phone leaves nothing behind; - the game sends rings and jolts. Every word sends a ring out from its tile (gold for player 1, coral for player 2; stronger for more points and a longer combo, an extra ring for each kana past three, a surge for a combo of three). A ring lights, lifts, tints and pushes outward the scales it passes, and fades as it spreads. A hit jolts the sea under the hurt player’s hand, harder for more damage, and your own hurt shows there in red; a refused tile sends a dark ring and a shudder; the countdown beats once a second and “go” lands like a drum; a clear sends three golden rings and a surge, a loss a dark surge, a knockout rings and a surge in the winner’s colour and a blow to the loser’s side.
lib.rs turns the board’s events into rings and jolts (waves_for), so a
scene only needs the component; scripts add their own with the commands
below. Lay the area wider than the screen - a wider screen shows more world.
Tiles in hand float on it, too: each bobs and rocks a little on a beat of its own, and the chosen one’s glow pulses.
Script commands
Section titled “Script commands”| Call | Does |
|---|---|
Kotoba.puzzle(e, stage) | play a stage by id |
Kotoba.battle(e, rival) | fight a rival by id: you against its bot |
Kotoba.demo(e) | two bots fighting, restarted when it ends (a title screen) |
Kotoba.stop(e) | clear the board |
Kotoba.pause(e, paused) | stop the clock and hide the kana |
Kotoba.press(e, x, y) / drag(e, x, y) / release(e, x, y) | a pointer at a world point: a hand tile is chosen (a second tap turns it), a cell takes the chosen tile, a drag carries a tile to a cell |
Kotoba.select(e, slot) | choose hand tile slot (from 1) |
Kotoba.turn(e) / Kotoba.swap(e) | turn or swap the chosen tile |
Kotoba.cursor(e, dx, dy) / Kotoba.place(e) | the keyboard cursor (the first call only shows it), and put the chosen tile under it |
Kotoba.hint(e) | a puzzle’s hint |
Kotoba.online(e, conn) | play the battles the server behind this connection entity runs (-1 to stop) |
Kotoba.ripple(e, x, y, strength, r, g, b) | on a kotoba_waves: a ring from a world point (sRGB colour, gold without one; negative strength darkens) |
Kotoba.surge(e, strength, r, g, b) | a wave across the whole sea |
Kotoba.swell(e, amount) | how rough the resting sea is (0 still, 1 gentle, up to 4) |
What scripts read
Section titled “What scripts read”The kotoba bag: active, mode (puzzle, battle, none), demo,
paused, phase, selected (from 1; -1 none), hand (the faces), made
(every word: reading, written, meaning, points, player), words
(the dictionary’s size), stages and rivals (the tables, for menus).
A puzzle adds stage, stage_name, stage_title, time, time_left,
score, bonus, tiles_left, stock, swaps, stars, star_scores; a
battle countdown, time_left, winner (from 1; -1 a draw), you,
rival and fighters (name, hp, max_hp, score, combo,
multiplier, combo_left, stun, words, best, hand); online, also
online, seat (from 1), board (its name) and pending (placements not
yet answered).
events, this frame’s: started, countdown (n), placed (player,
x, y, kana, points, damage, combo, words - each with its
cells), refused (why: no word, not next to a kana, taken),
turned, swapped, hit (to, damage, hp), cleared (score,
bonus, time_left), failed (why: time, stuck), over (winner,
why: ko, time, full).
The dictionary
Section titled “The dictionary”words/ja.tsv is written for this package (no third-party list, so no
licence of its own): about two thousand everyday words - animals, food,
the body, nature, home, school, town, people, time, verbs, adjectives,
onomatopoeia and borrowed words in hiragana with ー. A test fails a line
whose reading is not tile kana, or a reading that appears twice.