Перейти к содержимому

Уроки · 67

Идентификатор кружка

В уроке «Ловушка индекса» кольцо скакало с кружка на кружок, хотя сами кружки стояли на месте: TARGET_INDEX — это просто место в списке, а место при удалении соседей меняется. Нужно что-то, что остаётся при кружке навсегда, даже когда список перестраивается — идентификатор, или просто ID.

Дадим каждому кружку свой номер один раз, при рождении, и будем хранить его вместе с координатами в том же кортеже:

let mut circles: Vec<(u32, f32, f32)> = Vec::new();

Первое число — ID кружка, дальше как раньше x и y.

Для ID годится u32 — ещё один целый тип из той же семьи, что и usize из урока «Целые числа». Разница в смысле: usize в Rust означает именно количество или порядковый номер в чём-то — то, чем и был наш старый TARGET_INDEX. ID кружка — не индекс и не количество, он никак не участвует в обращении к списку по номеру, так что usize тут ни при чём. u32 — обычный целый тип без этой особой роли, и вмещает больше четырёх миллиардов разных значений — кружков столько никогда не будет.

При создании ряда просто пронумеруем кружки по порядку — i-й кружок получает ID, равный i:

for i in 0..COUNT {
circles.push((i as u32, START_X + STEP * (i as f32), screen_height() / 2.0));
}

Выглядит почти как старый TARGET_INDEX, но есть разница: индекс был местом в списке и менялся вместе со списком, а ID — просто число, однажды написанное на кружке, и список на него никак не влияет:

// Кружок, которого держим на прицеле — его ID, а не номер в списке.
const TARGET_ID: u32 = 3;

По индексу можно было сразу написать circles[TARGET_INDEX]. С ID так не выйдет — ID не говорит, под каким номером кружок лежит в списке сейчас. Остаётся единственный способ — сравнить id каждого кружка с TARGET_ID. Сделаем это прямо в цикле отрисовки — впишем туда же проверку и кольцо:

for &(id, x, y) in &circles {
draw_circle(x, y, RADIUS, YELLOW);
draw_text(&id.to_string(), x, y, 128.0, BLACK);
if id == TARGET_ID {
draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE);
}
}

Если кружка с TARGET_ID в списке больше нет, условие id == TARGET_ID просто ни разу не сработает за весь цикл, и кольцо не нарисуется.

Замени содержимое src/main.rs на это:

src/main.rs
use macroquad::prelude::*;
// Радиус один и тот же у всех кружков — фиксированный, как в уроке «Константы».
const RADIUS: f32 = 84.0;
// Сколько кружков рисуем и как их расставить в ряд — те же START_X и STEP,
// что и в уроке «Цикл for».
const COUNT: usize = 10;
const START_X: f32 = 100.0;
const STEP: f32 = 228.0;
// Своя функция: расстояние между двумя точками, как в уроке «Расстояние».
fn distance(from_x: f32, from_y: f32, to_x: f32, to_y: f32) -> f32 {
let dx = to_x - from_x;
let dy = to_y - from_y;
(dx * dx + dy * dy).sqrt()
}
// Кружок, которого держим на прицеле — его ID, а не номер в списке.
const TARGET_ID: u32 = 3;
#[macroquad::main("Идентификатор кружка")]
async fn main() {
// Начальный набор кружков — ровный ряд; у каждого свой ID, присвоенный один раз.
let mut circles: Vec<(u32, f32, f32)> = Vec::new();
for i in 0..COUNT {
circles.push((i as u32, START_X + STEP * (i as f32), screen_height() / 2.0));
}
loop {
clear_background(BLACK);
// Клик только что случился — убираем кружки под курсором.
if is_mouse_button_pressed(MouseButton::Left) {
let (mouse_x, mouse_y) = mouse_position();
// Новый пустой список — специально для кружков, которые стоит оставить.
let mut kept_circles: Vec<(u32, f32, f32)> = Vec::new();
// Забираем старый список circles в собственность.
for (id, x, y) in circles {
let clicked_inside = distance(mouse_x, mouse_y, x, y) < RADIUS;
if !clicked_inside {
kept_circles.push((id, x, y));
}
}
// На этот кадр circles становится тем, что мы только что собрали.
circles = kept_circles;
}
// Рисуем каждый кружок, подписываем его ID и, если это и есть
// TARGET_ID, тут же обводим его кольцом.
for &(id, x, y) in &circles {
draw_circle(x, y, RADIUS, YELLOW);
draw_text(&id.to_string(), x, y, 128.0, BLACK);
if id == TARGET_ID {
draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE);
}
}
next_frame().await;
}
}

Запусти как обычно:

Окно терминала
cargo run

Один из кружков обведён белым кольцом — его ID совпадает с числом внутри кольца. Кликни по любому другому кружку левее него, чтобы убрать его, и посмотри, что станет с кольцом на этот раз:

Кружки по-прежнему не двигаются при удалении — но теперь не меняются и их подписи: у каждого кружка как был свой ID с рождения, так и остался. Удаление просто выбрасывает один кортеж из списка — соседние ID никуда не сдвигаются, потому что они никак не связаны с положением в списке.

Кольцо ищет кружок с TARGET_ID заново каждый кадр — и раз ID не меняются, оно каждый раз находит тот же самый кружок, где бы он теперь ни лежал в списке.

  • У каждого кружка в кортеже теперь три числа вместо двух: id, x и y.
  • ID присваивается один раз, при создании ряда, и дальше никогда не меняется — в отличие от индекса, который всего лишь текущее место в списке.
  • Чтобы найти кружок по ID, приходится сравнивать id каждого кружка с TARGET_ID — обратиться напрямую, как circles[TARGET_INDEX] в прошлом уроке, здесь не получится. Проверка и кольцо встроены прямо в цикл отрисовки.
  • Если убрать кружок с TARGET_ID, условие id == TARGET_ID ни разу не сработает, и кольцо просто не нарисуется.

Индекс — это место в списке, а ID — то, что остаётся при кружке, где бы он в этом списке ни оказался.