scout
scout
Read a scouted opponent's screen: the loot on offer and whether it can be skipped.
The scout screen only stands for 30 seconds before the game forces the battle to
start and takes the 下一個 button away, and every skipped opponent would cost
another call. Position, font and size are fixed by the 1600x900 layout, so the
numbers go through parsers.glyphs rather than through Gemini.
What is left here after that engine moved out is this file's real subject: which of the game's screens is on show, and what the ones a battle passes through are holding — the loot panel, the army bar, the card row, the storage bars.
Functions:
| Name | Description |
|---|---|
battle_over |
Whether the battle result screen is up with its 回營 button waiting. |
welcome_back |
Whether the raid report the game opens on is covering the village. |
shield_sheet |
Whether the 魔法護盾 sheet is covering the village. |
idle_disconnected |
Whether a dropped-session dialog is covering the game. |
session_taken |
Whether another device has logged in to this account and taken the session. |
loading_screen |
Whether the game is on 正在載入, which is where it waits for the server. |
skip_offered |
Whether 下一個 is on screen, which takes none of the loot digits to answer. |
panel_peak |
How bright the brightest pixel of the loot panel is, over its three rows. |
panel_drawn |
Whether the scout screen has finished fading in, read off the loot panel's own peak. |
attack_menu_open |
Whether the 多人遊戲 menu is up with its 尋找對戰目標 button. |
night_attack_menu |
Whether the builder base's 開始進攻 dialog is up with its 立即尋找 button. |
loot_cart_open |
Whether the builder base's 聖水車 sheet is up, whatever its button says. |
loot_cart_ready |
Whether that sheet's 收集 is live, which is not the same as the sheet being up. |
loot_cart_load |
What the cart's sheet says it holds and holds at most, or None where it will not read. |
in_battle |
Whether a battle is on screen, by the red plate that leaves one. |
battle_speed |
The speed the battle's speed button says is playing, or None with no button up. |
searching_opponent |
Whether the builder base matchmaker is still looking, by its 取消 button. |
card_groups |
Card centres in the battle row, split into the groups the game lays them out in. |
counted_cards |
Which of these cards show an |
card_count |
The |
card_drained |
Which of these counted cards actually put something on the field. |
field_units |
Which of these cards have their unit alive on the field, by its health bar. |
army_strength |
Trained and total army size off the 我的軍隊 screen, or None if not on it. |
freeze_cards |
Which of these spell cards hold freeze, the one spell worth holding back. |
live_cards |
Which of these card-row positions still have something left to deploy. |
selected_cards |
Which of these cards the game is drawing selected, by the white border. |
read_scout |
The opponent on screen, or None when this screenshot shows no opponent at all. |
read_builder_stock |
The builder base's storages, which are its first two rows and nothing else. |
storage_capacity |
What this row's tooltip says the storage holds, or None where none is open. |
read_stock |
The village's own storages, or None when this screenshot is not showing them. |
battle_over
Whether the battle result screen is up with its 回營 button waiting.
Source code in src/ai_coc/parsers/scout.py
welcome_back
Whether the raid report the game opens on is covering the village.
Source code in src/ai_coc/parsers/scout.py
shield_sheet
Whether the 魔法護盾 sheet is covering the village.
Source code in src/ai_coc/parsers/scout.py
idle_disconnected
Whether a dropped-session dialog is covering the game.
Two dialogs read here and both mean the same thing to a caller: the idle
one (還在嗎, with 重新登入遊戲) and the lost-connection one (連線已中斷, with
再試一次). Either way the session is gone and restarting the game is what
gets it back. A third reads here too and must not be restarted out of: see
session_taken.
Source code in src/ai_coc/parsers/scout.py
session_taken
Whether another device has logged in to this account and taken the session.
Answered by standing down, never by a restart: logging in again here is
what logs the player out of their phone. See SESSION_TITLE_BOX.
Source code in src/ai_coc/parsers/scout.py
loading_screen
Whether the game is on 正在載入, which is where it waits for the server.
Nothing on this screen answers a tap, so a loop that reads it has nothing to press and nothing to open: the only thing to do is wait. See the constants above for what is measured and why both halves are needed.
Source code in src/ai_coc/parsers/scout.py
skip_offered
Whether 下一個 is on screen, which takes none of the loot digits to answer.
read_scout says None both for 正在搜尋對手 and for an opponent whose loot
panel this frame cannot read, and those want opposite things from a caller:
the first is worth waiting out and the second is worth leaving. This is the
only part of that screen that separates them, because it is read off one
saturated orange rather than off the digits — the same test can_skip
already uses, asked without needing a whole ScoutView to exist first.
Source code in src/ai_coc/parsers/scout.py
panel_peak
How bright the brightest pixel of the loot panel is, over its three rows.
The measurement panel_drawn decides on, handed back as the number so a
caller can put it in the log. A refusal that says only that it refused
leaves whoever reads the run unable to tell a screen caught mid-fade from
one this threshold is wrong about.
Source code in src/ai_coc/parsers/scout.py
panel_drawn
Whether the scout screen has finished fading in, read off the loot panel's own peak.
can_skip means two opposite things on a frame that has not, and only
one of them is safe to act on. The 下一個 button being absent is how the
countdown having expired is recognised, and a caller reads that as a battle
it has no choice but to play; a button that has simply not painted yet reads
exactly the same. Measured over 56 recorded scout frames, every reading of
can_skip=False came from the second kind, and two rounds of one evening
sent an army at an opponent nobody had evaluated because of it.
Only the search loop asks this, and only about a frame read_scout has
already answered on. _wait_out_battle polls the same panel through that
reader while a battle runs, and there a dim panel is an event popup over
settled numbers — which DIM_INK_RATIO is built to read and this would
refuse.
Source code in src/ai_coc/parsers/scout.py
attack_menu_open
Whether the 多人遊戲 menu is up with its 尋找對戰目標 button.
The attack loop opens with taps that only mean anything on the home village. An agent job that finished somewhere else would otherwise send them into whatever screen was left showing.
Source code in src/ai_coc/parsers/scout.py
night_attack_menu
Whether the builder base's 開始進攻 dialog is up with its 立即尋找 button.
The builder base's own attack button sits in the same corner as the home
village's, so the tap that opens this is the tap that opens the other; only
what comes up separates them, and attack_menu_open does not recognise this
one. Without this, a loop pointed at the builder base spends every attempt
tapping a dialog it cannot see and reports the game as stuck.
Two features rather than one, because a village is mostly grass and the button is green; see the constants for the frames each half lets through.
Source code in src/ai_coc/parsers/scout.py
loot_cart_open
Whether the builder base's 聖水車 sheet is up, whatever its button says.
Tapping the cart opens this rather than collecting outright, so a caller that stopped at the tap has collected nothing at all — which is what the storage bars said the first time this was tried.
Read off the sheet rather than off its 收集 button, because the game
greys that button and a grey one used to read as no sheet at all: the run
then reported that it could not find the cart and left the sheet standing
over the village. loot_cart_ready is the button's own question.
Source code in src/ai_coc/parsers/scout.py
loot_cart_ready
Whether that sheet's 收集 is live, which is not the same as the sheet being up.
Measured live, a builder base with both storages exactly at capacity draws it a flat (178, 178, 178) — no button green at all — over a cart holding 135 843 elixir.
Nothing here knows why the game locked it, and the sheet offers two
reasons at once. That frame's own body reads 暫無新的防禦獎勵, so a cart
with nothing new in it is as good a candidate as a storage with no room —
and a cart the loop emptied minutes ago is the commoner of the two. So this
answers whether pressing it would buy anything and stops there. What
separates the two is the line beside the button, which loot_cart_load
reads; this said that number needed an ink rule of its own, and it does not.
Source code in src/ai_coc/parsers/scout.py
loot_cart_load
What the cart's sheet says it holds and holds at most, or None where it will not read.
This is what tells a locked cart the loop just emptied from one filling
behind a storage with no room: the first is nothing to act on and the
second is a village banking elixir in its cart until that fills too.
loot_cart_ready reads the button and so cannot say which.
The ceiling is the second half of the same line, and it is what lets the cart count as a storage of its own: the builder base keeps elixir in it past what the storages take, so a run filling that village fills this too, and how full it is is a share of the number written here rather than of one anybody typed in. It grows with the village like every other ceiling — 1 000 000 on the first sheet this loop opened, 1 600 000 on the committed ones.
Gated on the sheet being up, and that gate is load-bearing rather than
tidy. The builder base's own card row writes its xN corners at y 742-760
where these digits sit at y 747-767, so no box can separate them: ungated,
this answers on 315 of the 4 304 frames recorded here and on four of the
committed ones — night_cards.png reads 4 — and two of those 315 read
0, which is exactly the answer a caller would believe as an empty cart.
Two numbers with the / between them, or nothing. The line is cut at the
spaces either side of the / rather than at a glyph that matches badly
(see CART_SPACE), so a digit that fails leaves its number unread instead
of splitting it, and a line the spaces cut into anything but three words is
unread too. A glyph lost off either end still leaves a number, so
135 843 can come back as 35 843, and 1 600 000 as 160 000. The second is the one that
costs now that the share is read, since a ceiling a tenth of the real one
makes a cart holding 150 000 look full. What rules most of it out is that a
cart cannot hold more than it takes: past 160 000 the pair is unread rather
than believed. Under that nothing on this line can rule it out, so it is
named here rather than guarded: no sheet recorded on this machine has lost
a glyph off either end.
Source code in src/ai_coc/parsers/scout.py
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 | |
in_battle
Whether a battle is on screen, by the red plate that leaves one.
The question card_groups was standing in for, and could not answer: a card
row says something card-shaped is along the bottom of the frame, which the
game's own panels have as readily as a battle does. The plate in the corner
— 放棄 in the home village, 結束戰鬥 in the builder base and on the scout
screen — is on none of those panels.
False does not mean the battle is over, and a caller reusing this on its
own has to know it: the game dims the whole screen behind its own popups,
which takes the plate under the red floor as readily as it takes the loot
digits (battle_dimmed_by_popup.png, a home battle at 66% with two minutes
left, reads 0.0000), and the builder base draws no plate at all until its
countdown ends. What this answers is "a battle is definitely on screen",
which is what a filter guarding a back press wants; "the battle has ended"
is battle_over's question and stays with it.
Source code in src/ai_coc/parsers/scout.py
battle_speed
The speed the battle's speed button says is playing, or None with no button up.
Source code in src/ai_coc/parsers/scout.py
searching_opponent
Whether the builder base matchmaker is still looking, by its 取消 button.
This screen has no timer on it and no other feature to read: it is a pale field, the word 正在搜尋對手, and one red button. The button is what says the search is still running, and therefore what a caller waits on — and what it taps when the wait has gone on long enough to be worth restarting.
Source code in src/ai_coc/parsers/scout.py
card_groups
Card centres in the battle row, split into the groups the game lays them out in.
How many cards fall in each group depends on the army, so callers are meant to read the first group as the main troops and the rest as one-off drops, rather than trying to name which group is which.
Only valid on a full row. A spent card greys out below the detection floor and the row fragments, at which point the battlefield visible past its ends reads as a card too.
A card whose own artwork is dark enough to break the strip is put back
together before anything is measured, because a piece narrow enough to be
dropped takes the whole card with it; CARD_SPAN is what that costs and how
it is judged.
Source code in src/ai_coc/parsers/scout.py
counted_cards
Which of these cards show an xN count, which is to say troops or spells.
Heroes and the siege machine are the ones without it, which is what lets the
attack loop drop those and leave spells alone. floor is the builder
base's own line there; see NIGHT_COUNT_WHITE_RATIO.
Source code in src/ai_coc/parsers/scout.py
card_count
The xN on one card, or None when the artwork behind it swallows the digits.
Lets a one-off drop be tapped as many times as the card actually holds
instead of a fixed guess. A pale illustration merges into the count, and
that shows up as an x glyph of the wrong width, so it reports the failure.
The x's width was the only check here, and it is not enough: what follows
it was matched against the templates with no tolerance at all, so any scrap
of card art left standing became a digit. Measured, a four-pixel sliver off
the x matched a 1 at 31 bits and turned a card of twelve into one of a
hundred and twenty-one.
This reads the home village only, and reports the builder base as
unreadable rather than wrongly. The two write the count differently — x4
there against 4x here — and the builder base draws it half as big again,
18 px against 9 to 15. Both of those were measured while trying to make one
reader serve both, and both say not to: finding the x by which end matches
no digit template breaks a home card whose own digit is as wide as its x
(cards_full's third card reads 2 and would stop reading at all), and the
builder base's digits miss every template so far that its 4 comes back as a
9, 23 bits off — inside COUNT_DIGIT_TOLERANCE, so it would be believed. A
count nobody can read costs the fallback tap count; a count read as more than
twice what the card holds costs the burst that follows it.
Source code in src/ai_coc/parsers/scout.py
card_drained
Which of these counted cards actually put something on the field.
The xN corner is repainted whenever a card loses one, which answers the
question the loop keeps asking — did that drop land — without needing to read
the number, and without believing a banner that means four different things.
Source code in src/ai_coc/parsers/scout.py
field_units
Which of these cards have their unit alive on the field, by its health bar.
This is the only thing such a card says. It stays lit and stays counted once the unit is down — a hero's has become the ability button — so nothing else on it moves.
A unit, not a hero: measured on a recorded run, the game draws the same bar over a siege machine, so the caller cannot use this to tell the two apart.
Source code in src/ai_coc/parsers/scout.py
army_strength
Trained and total army size off the 我的軍隊 screen, or None if not on it.
Checked before the attack is confirmed, because the search fee is charged after that and an army still being trained is not worth paying it for.
Source code in src/ai_coc/parsers/scout.py
freeze_cards
Which of these spell cards hold freeze, the one spell worth holding back.
Both halves of cyan are asked for; see FREEZE_BLUE for what the green one
alone lets through.
Source code in src/ai_coc/parsers/scout.py
live_cards
Which of these card-row positions still have something left to deploy.
Lets the attack loop keep emptying only the cards that are not done yet, rather than guessing a tap count that a bulk troop card would outlast.
Source code in src/ai_coc/parsers/scout.py
selected_cards
Which of these cards the game is drawing selected, by the white border.
A selected card is the one the next tap on the field deploys from, and the
builder base's second stage opens with the surviving machine's card in that
state. The border is what CARD_SELECTED_EDGE measures: 249 to 255 bright
at both edges of the card against at most 130 for one at rest, read off the
same averaged strip card_groups cuts the row on. A three-column window
either side covers the pixel or two a centre moves between frames.
Source code in src/ai_coc/parsers/scout.py
read_scout
The opponent on screen, or None when this screenshot shows no opponent at all.
Tapping 下一個 leaves the game on 正在搜尋對手 for a moment, and that screen has no digits where the panel would be, so a failed read is how the caller learns to keep waiting rather than a separate screen classifier.
Source code in src/ai_coc/parsers/scout.py
read_builder_stock
The builder base's storages, which are its first two rows and nothing else.
read_stock cannot be used there. That village has no dark elixir, and
its gems bar sits at exactly the y the dark row is read from — measured, a
builder base holding 10 152 gems reports dark=10152, the gem count read as
if it were dark elixir. A number that wrong travelling as a
resource is how a limit ends up checked against a bar belonging to something
else, so it is not read at all: dark comes back 0, and that village's
StorageCapacity carries no dark ceiling either, so nothing compares them.
Source code in src/ai_coc/parsers/scout.py
storage_capacity
What this row's tooltip says the storage holds, or None where none is open.
row is the storage bar's own index down the corner, the same 0/1/2 the
stock rows are read at. Tapping a bar drops the tooltip open under it and
tapping again puts it away, so the caller owns that toggle; this only reads
whatever is on the frame it is handed.
None is the answer for a frame with no tooltip on it, which the caller wants rather than a guess: the village showing through where the panel would be is not a number, and a resource without a ceiling is simply left out of the comparison.
Source code in src/ai_coc/parsers/scout.py
read_stock
The village's own storages, or None when this screenshot is not showing them.
Anything other than the home village reads as None, as does a home village with a panel over the bars, so a caller is meant to treat it as "not now" rather than as an empty village. Three rows all resolving into digits is itself the evidence that the home screen is up.