#[macro_use] extern crate bitflags; pub(crate) mod controller; pub(crate) mod twitch; use anyhow::{anyhow, Result}; use log::{debug, error, info, warn}; use std::{ fs::{File, OpenOptions}, io::{Read, Write}, str::from_utf8, sync::{Arc, RwLock}, thread::spawn, time::Instant, }; #[derive(Debug)] pub(crate) struct State { controller: [u8; 4], last_got: Box, ok: bool, frame: u64, } pub(crate) type MTState = Arc>; fn main() -> Result<()> { pretty_env_logger::try_init()?; kankyo::init()?; let mut vblank = File::open("vblank")?; let mut input = OpenOptions::new().write(true).open("input")?; let st = { let st = State { controller: [0; 4], last_got: Box::new(Instant::now()), ok: true, frame: 0, }; Arc::new(RwLock::new(st)) }; info!("ready"); { let st = st.clone(); spawn(move || twitch::run(st)); } const LERP_TIME: f64 = 330.0; // 330 frames to lerp stick positions down to 0 let mut xmax_frame: u64 = 0; let mut ymax_frame: u64 = 0; loop { let mut data = [0; 3]; debug!("waiting for vblank"); vblank.read(&mut data)?; let str = from_utf8(&data)?; debug!("got data: {}", str); match str { "OK\n" => { let mut data = st.write().unwrap(); data.frame += 1; let mut stickx = data.controller[2] as i8; let mut sticky = data.controller[3] as i8; let dist = stick_distance(stickx, sticky); if dist <= 10 { stickx = 0; sticky = 0; xmax_frame = 0; ymax_frame = 0; } stickx = match stickx { 0 => stickx, 127 => { xmax_frame = data.frame; stickx - 10 }, -128 => { xmax_frame = data.frame; stickx + 10 }, _ => { let t = (data.frame - xmax_frame) as f64 / (LERP_TIME as f64); lerp(stickx, 0, t) }, }; sticky = match sticky { 0 => sticky, 127 => { ymax_frame = data.frame; sticky - 10 }, -128 => { ymax_frame = data.frame; sticky + 10 }, _ => { let t = (data.frame - ymax_frame) as f64 / (LERP_TIME as f64); lerp(sticky, 0, t) }, }; input.write(&data.controller)?; data.controller[0] = 0; data.controller[1] = 0; data.controller[2] = stickx as u8; data.controller[3] = sticky as u8; } "BYE" => { warn!("asked to exit by the game"); return Ok(()); } _ => { error!("got unknown FIFO data {}", str); return Err(anyhow!("unknown FIFO data received")); } }; } } fn lerp(start: i8, end: i8, t: f64) -> i8 { (start as f64 * (1.0 - t) + (end as f64) * t) as i8 } fn stick_distance(x: i8, y: i8) -> i8 { let x = (x as f64).powi(2); let y = (y as f64).powi(2); (x + y).sqrt() as i8 } #[cfg(test)] mod test { #[test] fn lerp_scale() { for case in [(0.1, 10), (0.5, 31)].iter() { let t = case.0; let start = 127.0 * t; assert_eq!(crate::lerp(start as i8, 0, t), case.1); } } #[test] fn stick_distance() { for case in [ (0, 0, 0), (127, 0, 127), (64, 64, 90), (-64, 64, 90), (-64, -64, 90), ] .iter() { let x = case.0; let y = case.1; assert_eq!(crate::stick_distance(x, y), case.2); } } }