use anyhow::{Result, anyhow};
use tixel::Color;
use crate::{
clock::Clock,
game::{
ALL_GAMES,
wallet::{MemoryWallet, Wallet},
},
git::{Commit, Push},
party::{self, ALL_PARTIES, Palette, PartyRenderer, RenderContext, compositor},
scoring,
state::{self, State},
storage::{PushEntry, PushHistory},
tui::{self, clear_bg_color, enter_tui},
};
pub fn cheat(amount: i64, state: &mut State) {
let old = state.party_points;
if amount < 0 {
state.party_points = state.party_points.saturating_sub(amount.unsigned_abs());
} else {
state.party_points = state.party_points.saturating_add(amount as u64);
}
println!("{} → {} party points", old, state.party_points);
}
pub fn push(
num_commits: u64,
lines: Option<Vec<u64>>,
state: &mut State,
history: &PushHistory,
) -> Result<()> {
let clock = Clock::from_now();
let commits: Vec<Commit> = (0..num_commits)
.map(|i| {
let lines_changed = lines
.as_ref()
.map(|l| l[i as usize % l.len()])
.unwrap_or(10); Commit::new(format!("fake{}", i), lines_changed, clock.now())
})
.collect();
let push = Push::with_repo(commits, "dev://fake");
let breakdown = scoring::calculate_points(&push, state, history, &clock);
let packs_earned = state.earn_points(breakdown.total);
let lines_changed: u64 = push.commits().iter().map(|c| c.lines_changed()).sum();
let entry = PushEntry::with_current_time(
"dev://fake".to_string(),
"main".to_string(),
num_commits,
lines_changed,
breakdown.total,
);
history.record(&entry)?;
let ctx = RenderContext::new(&push, history, &breakdown, state, &clock, packs_earned);
party::display(&ctx);
Ok(())
}
pub fn reset(state: &mut State, pushes: &PushHistory) -> Result<()> {
*state = state::State::default();
pushes.reset()?;
println!("state and history reset to defaults");
Ok(())
}
pub fn bonus(track_id: &str, level: u32, state: &mut State) {
use crate::bonus_track::ALL_TRACKS;
let track = ALL_TRACKS.iter().find(|t| t.id() == track_id);
if track.is_none() {
eprintln!("unknown track: {}", track_id);
eprintln!(
"available: {:?}",
ALL_TRACKS.iter().map(|t| t.id()).collect::<Vec<_>>()
);
std::process::exit(1);
}
state.set_bonus_level(track_id, level);
println!("{} set to level {}", track_id, level);
}
pub fn palette(party_id: &str, state: &mut State) {
use crate::party::{ALL_PARTIES, palette::ALL_PALETTES};
let ids: Vec<&str> = if party_id == "all" {
ALL_PARTIES.iter().map(|p| p.info.id).collect()
} else {
if !ALL_PARTIES.iter().any(|p| p.info.id == party_id) {
eprintln!("unknown party: {}", party_id);
eprintln!(
"available: {:?} (or \"all\")",
ALL_PARTIES.iter().map(|p| p.info.id).collect::<Vec<_>>()
);
std::process::exit(1);
}
vec![party_id]
};
let palette_ids: Vec<String> = ALL_PALETTES.iter().map(|p| p.id().to_string()).collect();
for id in &ids {
state
.unlocked_palettes
.insert(id.to_string(), palette_ids.clone());
}
println!("unlocked all palettes for: {}", ids.join(", "));
}
pub fn party(party_id: &str, state: &mut State) {
use crate::party::ALL_PARTIES;
let p = ALL_PARTIES.iter().find(|p| p.info.id == party_id);
if p.is_none() {
eprintln!("unknown party: {}", party_id);
eprintln!(
"available: {:?}",
ALL_PARTIES.iter().map(|p| p.info.id).collect::<Vec<_>>()
);
std::process::exit(1);
}
state.unlock_party(party_id);
println!("{} unlocked and enabled", party_id);
}
const STARTING_BALANCE: u64 = 100;
pub fn game(game_id: &str) -> Result<()> {
let game = ALL_GAMES.iter().find(|g| g.id() == game_id);
let Some(game) = game else {
return Err(anyhow!("Game with id '{game_id}' not found."));
};
let mut wallet = MemoryWallet::new(STARTING_BALANCE);
let state = &mut None;
let mut terminal = tui::get_terminal()?;
{
let _guard = enter_tui()?;
clear_bg_color(game.clear_color())?;
game.run(&mut terminal, &mut wallet, state)?;
}
println!("Game '{}' complete.", game.name());
println!(
"Points balance: {STARTING_BALANCE} -> {}",
wallet.balance()?
);
if let Some(state) = state {
println!("Final game state: {state}");
}
Ok(())
}
pub fn demo(party_id: &str) -> Result<()> {
static DEMO_PALETTE: &Palette = &Palette::new(
"demo",
"Demo",
&[
Color::Rgb(240, 80, 240),
Color::Rgb(80, 240, 240),
Color::Rgb(240, 240, 240),
Color::Rgb(240, 240, 80),
],
);
let Some(party) = ALL_PARTIES.iter().find(|p| p.info.id == party_id) else {
eprintln!("unknown party: {}", party_id);
eprintln!(
"available: {:?}",
ALL_PARTIES.iter().map(|p| p.info.id).collect::<Vec<_>>()
);
std::process::exit(1);
};
let PartyRenderer::Fullscreen { create } = party.renderer else {
eprintln!("only fullscreen parties can be demo'd");
std::process::exit(1);
};
let (cols, rows) = crossterm::terminal::size()?;
let renderer = create(cols, rows, DEMO_PALETTE);
compositor::run(vec![renderer])?;
Ok(())
}