CB-WP-0011-T01: the inspector shows everything, and a gate says so
The renderer moves out of the play loop into inspect.rs and grows from 24 to 42 of the 43 leaf paths a populated GroundView carries. What it had been dropping was the whole DARVO state machine, the whole GROUND practice, the scoring mode, Focus tokens, the discard pile, per-seat protection, and every part of the outcome except the headline. The load-bearing half is every_view_field_is_classified, which walks the serialized view for leaf paths and requires each to be listed as rendered (with a token the output must contain) or omitted (with a reason). Paths rather than keys: 'problem' occurs under a DARVO target, a GROUND choice and a Selection, and a key-set walk would let one of the three vouch for the other two. Four M-D1-MUT controls, each red for its stated reason. The unclassified-field control fired for real on the first run -- players.*.hand, a field the gate's own author had missed.
This commit is contained in:
parent
c1d140626e
commit
d2ca1046c0
5 changed files with 624 additions and 125 deletions
|
|
@ -16,8 +16,9 @@ use cb_game_runtime::{Project, Setup, Viewer};
|
|||
use cb_kernel::{Actor, PlayerId};
|
||||
use games_ground::bot::{BotError, Choice, GreedyPolicy, Policy, RandomPolicy};
|
||||
use games_ground::record::to_step;
|
||||
use games_ground::view::{GroundView, PlayerView, ProblemView, SelectionView};
|
||||
use games_ground::{GroundCommand, GroundState};
|
||||
|
||||
use crate::inspect::{render, seat_name};
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
pub struct Config {
|
||||
|
|
@ -58,19 +59,11 @@ pub struct Summary {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------- rendering
|
||||
|
||||
fn suit_name(s: games_ground::Suit) -> &'static str {
|
||||
match s {
|
||||
games_ground::Suit::Clarify => "Clarify",
|
||||
games_ground::Suit::Repair => "Repair",
|
||||
games_ground::Suit::Boundary => "Boundary",
|
||||
games_ground::Suit::Change => "Change",
|
||||
}
|
||||
}
|
||||
|
||||
fn seat_name(p: PlayerId) -> String {
|
||||
format!("P{}", p.0 + 1)
|
||||
}
|
||||
//
|
||||
// The table itself is rendered by `inspect`. What stays here is the
|
||||
// vocabulary a *chooser* needs: how a legal command is described in the
|
||||
// menu. Rendering the state and naming a move are different jobs, and
|
||||
// T02 needs the first without the second.
|
||||
|
||||
fn describe(command: &GroundCommand) -> String {
|
||||
// Reuse the recorder's vocabulary rather than inventing a third one:
|
||||
|
|
@ -90,109 +83,6 @@ fn describe(command: &GroundCommand) -> String {
|
|||
out
|
||||
}
|
||||
|
||||
fn render_player(id: PlayerId, p: &PlayerView, is_viewer: bool) -> String {
|
||||
let hand = match &p.hand {
|
||||
Some(cards) => {
|
||||
let names: Vec<&str> = cards.iter().map(|c| suit_name(c.suit)).collect();
|
||||
format!("hand [{}]", names.join(", "))
|
||||
}
|
||||
None => format!("hand {} card(s)", p.hand_size),
|
||||
};
|
||||
format!(
|
||||
" {}{} stress {} freedom {} darvo {:?} blame {} {hand}",
|
||||
seat_name(id),
|
||||
if is_viewer { " (you)" } else { " " },
|
||||
p.stress,
|
||||
if p.freedom_ready { "READY" } else { "SPENT" },
|
||||
p.darvo,
|
||||
p.blame_from.len(),
|
||||
)
|
||||
}
|
||||
|
||||
/// One seat's whole picture, from the projection and nothing else.
|
||||
pub fn render(view: &GroundView) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!(
|
||||
"\nround {} step {:?} lead {} deck {}\n",
|
||||
view.round,
|
||||
view.step,
|
||||
seat_name(view.lead),
|
||||
view.solution_deck_len,
|
||||
));
|
||||
for (id, p) in &view.players {
|
||||
out.push_str(&render_player(*id, p, Some(*id) == view.viewer));
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" problems:");
|
||||
for (priority, problem) in &view.problems {
|
||||
match problem {
|
||||
ProblemView::FaceDown => out.push_str(&format!(" [{priority}] face-down")),
|
||||
ProblemView::FaceUp {
|
||||
suit,
|
||||
value,
|
||||
denied,
|
||||
claimed_by,
|
||||
..
|
||||
} => {
|
||||
out.push_str(&format!(
|
||||
" [{priority}] {} {}{}{}",
|
||||
suit_name(*suit),
|
||||
value,
|
||||
if *denied { " DENIED" } else { "" },
|
||||
match claimed_by {
|
||||
Some(p) => format!(" claimed by {}", seat_name(*p)),
|
||||
None => String::new(),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.push('\n');
|
||||
if !view.relations.is_empty() {
|
||||
out.push_str(" relations:");
|
||||
for (pair, relation) in &view.relations {
|
||||
out.push_str(&format!(" {pair} {relation:?}"));
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
if !view.selections.is_empty() {
|
||||
out.push_str(" selections:");
|
||||
for (id, sel) in &view.selections {
|
||||
match sel {
|
||||
SelectionView::Hidden => out.push_str(&format!(" {} face-down", seat_name(*id))),
|
||||
SelectionView::Shown(s) => out.push_str(&format!(
|
||||
" {} {:?}{}{}",
|
||||
seat_name(*id),
|
||||
s.action,
|
||||
match s.target {
|
||||
Some(t) => format!("→{}", seat_name(t)),
|
||||
None => String::new(),
|
||||
},
|
||||
match s.problem {
|
||||
Some(p) => format!("→[{p}]"),
|
||||
None => String::new(),
|
||||
}
|
||||
)),
|
||||
}
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
if let Some(o) = &view.outcome {
|
||||
out.push_str(&format!(
|
||||
" OUTCOME total {} / threshold {} group {} winners {:?}\n",
|
||||
o.total,
|
||||
o.threshold,
|
||||
if o.group_success {
|
||||
"SUCCESS"
|
||||
} else {
|
||||
"failure"
|
||||
},
|
||||
o.winners.iter().map(|w| seat_name(*w)).collect::<Vec<_>>(),
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- policies
|
||||
|
||||
/// A seat driven from stdin. Implements the same `Policy` the bots do, so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue