Skip to content

Commit 1e57303

Browse files
unhappychoiceclaude
andcommitted
feat: add tier and overall ranking display to result screen
- Add tier position and overall ranking information to TypingMetrics - Display tier rank (e.g., "Expert tier - rank 3/12") with tier-based colors - Show overall ranking across all titles (e.g., "overall 25/75") - Implement calculate_tier_info method in ScoringEngine - Improve result screen layout with proper spacing - Fix ranking order to show highest score requirements as rank 1 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 89d4093 commit 1e57303

File tree

4 files changed

+98
-6
lines changed

4 files changed

+98
-6
lines changed

src/game/screens/exit_summary_screen.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ impl ExitSummaryScreen {
4040
ScoringEngine::get_ranking_title_for_score(session_summary.session_score)
4141
.name()
4242
.to_string();
43+
let (tier_name, tier_position, tier_total, overall_position, overall_total) =
44+
ScoringEngine::calculate_tier_info(session_summary.session_score);
4345

4446
TypingMetrics {
4547
cpm: session_summary.overall_cpm,
@@ -50,6 +52,11 @@ impl ExitSummaryScreen {
5052
completion_time: session_summary.total_session_time,
5153
challenge_score: session_summary.session_score,
5254
ranking_title,
55+
ranking_tier: tier_name,
56+
tier_position,
57+
tier_total,
58+
overall_position,
59+
overall_total,
5360
was_skipped: false,
5461
was_failed: false,
5562
}

src/game/screens/result_screen.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ impl ResultScreen {
317317
let rank_title_height = rank_title_lines.len() as u16;
318318

319319
// Calculate total content height and center vertically
320-
let total_content_height = rank_title_height + 1 + 1 + 4 + 2 + 2; // rank + gap + label + score + gap + summary
320+
let total_content_height = rank_title_height + 1 + 1 + 2 + 4 + 2 + 2; // rank + gap + tier + extra gap + label + score + gap + summary
321321
let rank_start_row = if total_content_height < terminal_height {
322322
center_row.saturating_sub(total_content_height / 2)
323323
} else {
@@ -332,7 +332,7 @@ impl ResultScreen {
332332
let title_col = center_col.saturating_sub(line.len() as u16 / 2);
333333
execute!(
334334
stdout,
335-
MoveTo(title_col, rank_start_row.saturating_sub(6) + i as u16)
335+
MoveTo(title_col, rank_start_row.saturating_sub(5) + i as u16)
336336
)?;
337337
execute!(
338338
stdout,
@@ -343,10 +343,10 @@ impl ResultScreen {
343343
execute!(stdout, ResetColor)?;
344344
}
345345

346-
// Display "you're:" label before rank title (no gap)
346+
// Display "you're:" label before rank title (1 line gap from rank title)
347347
let youre_label = "YOU'RE:";
348348
let youre_col = center_col.saturating_sub(youre_label.len() as u16 / 2);
349-
execute!(stdout, MoveTo(youre_col, rank_start_row.saturating_sub(1)))?;
349+
execute!(stdout, MoveTo(youre_col, rank_start_row.saturating_sub(2)))?;
350350
execute!(
351351
stdout,
352352
SetAttribute(Attribute::Bold),
@@ -364,8 +364,34 @@ impl ResultScreen {
364364
execute!(stdout, ResetColor)?;
365365
}
366366

367-
// Calculate score position based on rank title height (add gap after rank title)
368-
let score_label_row = rank_start_row + rank_title_height + 1;
367+
// Display tier information right after rank title (small gap after rank title)
368+
let tier_info_row = rank_start_row + rank_title_height;
369+
let tier_info = format!("{} tier - rank {}/{} (overall {}/{})",
370+
session_metrics.ranking_tier,
371+
session_metrics.tier_position,
372+
session_metrics.tier_total,
373+
session_metrics.overall_position,
374+
session_metrics.overall_total
375+
);
376+
let tier_info_col = center_col.saturating_sub(tier_info.len() as u16 / 2);
377+
execute!(stdout, MoveTo(tier_info_col, tier_info_row))?;
378+
execute!(stdout, SetAttribute(Attribute::Bold))?;
379+
380+
// Set color based on tier
381+
let tier_color = match session_metrics.ranking_tier.as_str() {
382+
"Beginner" => Color::Blue,
383+
"Intermediate" => Color::Green,
384+
"Advanced" => Color::Cyan,
385+
"Expert" => Color::Yellow,
386+
"Legendary" => Color::Red,
387+
_ => Color::White,
388+
};
389+
execute!(stdout, SetForegroundColor(tier_color))?;
390+
execute!(stdout, Print(&tier_info))?;
391+
execute!(stdout, ResetColor)?;
392+
393+
// Calculate score position based on rank title height and tier info (add extra gap after tier info)
394+
let score_label_row = rank_start_row + rank_title_height + 3;
369395

370396
// Display "SCORE" label in normal text with color
371397
let score_label = "SESSION SCORE";

src/scoring/engine.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,48 @@ impl ScoringEngine {
185185
RankingTitle::for_score(score)
186186
}
187187

188+
/// Calculate tier position and total for a given score
189+
pub fn calculate_tier_info(score: f64) -> (String, usize, usize, usize, usize) {
190+
let all_titles = RankingTitle::all_titles();
191+
let current_title = Self::get_ranking_title_for_score(score);
192+
193+
// Find titles in the same tier
194+
let same_tier_titles: Vec<_> = all_titles
195+
.iter()
196+
.filter(|title| title.tier() == current_title.tier())
197+
.collect();
198+
199+
let tier_name = match current_title.tier() {
200+
super::RankingTier::Beginner => "Beginner",
201+
super::RankingTier::Intermediate => "Intermediate",
202+
super::RankingTier::Advanced => "Advanced",
203+
super::RankingTier::Expert => "Expert",
204+
super::RankingTier::Legendary => "Legendary",
205+
}.to_string();
206+
207+
// Find position within tier (1-based, highest score = rank 1)
208+
let tier_position = same_tier_titles
209+
.iter()
210+
.rev() // Reverse to get highest scores first
211+
.position(|title| title.name() == current_title.name())
212+
.map(|pos| pos + 1)
213+
.unwrap_or(1);
214+
215+
let tier_total = same_tier_titles.len();
216+
217+
// Find position in all titles (1-based, highest score = rank 1)
218+
let overall_position = all_titles
219+
.iter()
220+
.rev() // Reverse to get highest scores first
221+
.position(|title| title.name() == current_title.name())
222+
.map(|pos| pos + 1)
223+
.unwrap_or(1);
224+
225+
let overall_total = all_titles.len();
226+
227+
(tier_name, tier_position, tier_total, overall_position, overall_total)
228+
}
229+
188230
/// Legacy method that returns title name as string for a score for backward compatibility
189231
pub fn get_ranking_title_string_for_score(score: f64) -> String {
190232
match score as usize {
@@ -423,6 +465,7 @@ impl ScoringEngine {
423465
let ranking_title = Self::get_ranking_title_for_score(challenge_score)
424466
.name()
425467
.to_string();
468+
let (tier_name, tier_position, tier_total, overall_position, overall_total) = Self::calculate_tier_info(challenge_score);
426469

427470
Ok(TypingMetrics {
428471
cpm: self.cpm(),
@@ -433,6 +476,11 @@ impl ScoringEngine {
433476
completion_time: self.elapsed(),
434477
challenge_score,
435478
ranking_title,
479+
ranking_tier: tier_name,
480+
tier_position,
481+
tier_total,
482+
overall_position,
483+
overall_total,
436484
was_skipped,
437485
was_failed,
438486
})
@@ -464,6 +512,7 @@ impl ScoringEngine {
464512
let ranking_title = Self::get_ranking_title_for_score(challenge_score)
465513
.name()
466514
.to_string();
515+
let (tier_name, tier_position, tier_total, overall_position, overall_total) = Self::calculate_tier_info(challenge_score);
467516

468517
TypingMetrics {
469518
cpm: temp_engine.cpm(),
@@ -474,6 +523,11 @@ impl ScoringEngine {
474523
completion_time: temp_engine.elapsed(),
475524
challenge_score,
476525
ranking_title,
526+
ranking_tier: tier_name,
527+
tier_position,
528+
tier_total,
529+
overall_position,
530+
overall_total,
477531
was_skipped: false, // Real-time metrics are not skipped
478532
was_failed: false, // Real-time metrics are not failed
479533
}

src/scoring/metrics.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ pub struct TypingMetrics {
1010
pub completion_time: Duration,
1111
pub challenge_score: f64,
1212
pub ranking_title: String,
13+
pub ranking_tier: String,
14+
pub tier_position: usize,
15+
pub tier_total: usize,
16+
pub overall_position: usize,
17+
pub overall_total: usize,
1318
pub was_skipped: bool,
1419
pub was_failed: bool,
1520
}

0 commit comments

Comments
 (0)