Add input and scoring logic
This commit is contained in:
+1
-1
@@ -9,4 +9,4 @@ edition = "2021"
|
||||
anyhow = "1.0.71"
|
||||
inpt = "0.1.3"
|
||||
num-rational = "0.4.1"
|
||||
sdl2 = { version = "0.35.2", features = ["gfx"] }
|
||||
sdl2 = { version = "0.35.2", features = ["gfx", "ttf"] }
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
Copyright 2011-2016 Severin Meyer <sev.ch@web.de>,
|
||||
with Reserved Font Name Xolonium.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1. This license is copied below, and is also available
|
||||
with a FAQ at <http://scripts.sil.org/OFL>
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
+102
@@ -0,0 +1,102 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{Button::*, Note, NoteKind, Phrase, Ratio, Song};
|
||||
|
||||
use inpt::inpt;
|
||||
use inpt::split::Line;
|
||||
|
||||
use anyhow::{anyhow, bail, Error};
|
||||
|
||||
#[derive(inpt::Inpt)]
|
||||
struct Section {
|
||||
#[inpt(split = "Bracketed")]
|
||||
title: String,
|
||||
#[inpt(split = "Braced")]
|
||||
contents: String,
|
||||
}
|
||||
|
||||
#[derive(inpt::Inpt, Debug)]
|
||||
struct ChartDetails {
|
||||
#[inpt(split = "Word")]
|
||||
field_name: String,
|
||||
_equals: char,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(inpt::Inpt, Debug)]
|
||||
struct ChartNote {
|
||||
time: i64,
|
||||
_equals: char,
|
||||
kind: char,
|
||||
index: usize,
|
||||
length: i64,
|
||||
}
|
||||
|
||||
pub fn parse_chart(path: impl AsRef<Path>) -> Result<super::Song, Error> {
|
||||
let song_str = std::fs::read_to_string(path)?;
|
||||
|
||||
// Unicode was a mistake
|
||||
let song_str = song_str.trim_start_matches('\u{feff}');
|
||||
|
||||
let sections: Vec<Section> = inpt(&song_str).map_err(|err| anyhow!("{err}"))?;
|
||||
|
||||
let Some(song) = sections.iter().find(|s| s.title == "Song") else {
|
||||
bail!("Could not find Song")
|
||||
};
|
||||
|
||||
let Some(expert_single) = sections.iter().find(|s| s.title == "ExpertSingle") else {
|
||||
bail!("Could not find ExpertSingle")
|
||||
};
|
||||
|
||||
let chart_details: Vec<Line<ChartDetails>> =
|
||||
inpt(&song.contents).map_err(|err| anyhow!("{err}"))?;
|
||||
let Some(resolution) = chart_details.iter().find(|c| c.inner.field_name == "Resolution") else {
|
||||
bail!("Could not find Resolution")
|
||||
};
|
||||
let resolution: i64 = resolution.inner.value.parse()?;
|
||||
|
||||
let chart_notes: Vec<ChartNote> =
|
||||
inpt(&expert_single.contents).map_err(|err| anyhow!("{err}"))?;
|
||||
|
||||
let mut song = Song::default();
|
||||
|
||||
for &ChartNote {
|
||||
time,
|
||||
kind,
|
||||
index,
|
||||
length,
|
||||
..
|
||||
} in &chart_notes
|
||||
{
|
||||
if kind == 'N' {
|
||||
song.notes.push(Note {
|
||||
start: Ratio::new(time, resolution),
|
||||
button: match index {
|
||||
0 => GREEN,
|
||||
1 => RED,
|
||||
2 => YELLOW,
|
||||
3 => BLUE,
|
||||
4 => ORANGE,
|
||||
7 => OPEN,
|
||||
_ => continue,
|
||||
},
|
||||
kind: NoteKind::NORMAL,
|
||||
length: if length > 0 {
|
||||
Some(Ratio::new(length, resolution))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
} else if kind == 'S' {
|
||||
song.star_phrases.push(Phrase {
|
||||
start: Ratio::new(time, resolution),
|
||||
length: Ratio::new(length, resolution),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
song.notes.sort_by_key(|k| k.start);
|
||||
song.star_phrases.sort_by_key(|k| k.start);
|
||||
|
||||
Ok(song)
|
||||
}
|
||||
+212
-100
@@ -1,11 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::default;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{anyhow, bail, Error};
|
||||
use inpt::inpt;
|
||||
use inpt::split::{Braced, Bracketed, Line};
|
||||
use anyhow::{anyhow, Error};
|
||||
|
||||
use num_rational::Ratio;
|
||||
use sdl2::event::Event;
|
||||
@@ -13,7 +9,7 @@ use sdl2::gfx::primitives::{DrawRenderer, ToColor};
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::pixels::Color;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum Button {
|
||||
GREEN,
|
||||
RED,
|
||||
@@ -34,6 +30,9 @@ impl Button {
|
||||
OPEN => panic!(),
|
||||
}
|
||||
}
|
||||
fn index(&self) -> usize {
|
||||
(self.lane() - 1) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl ToColor for Button {
|
||||
@@ -49,22 +48,25 @@ impl ToColor for Button {
|
||||
}
|
||||
}
|
||||
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{TextureQuery, WindowCanvas};
|
||||
use sdl2::ttf::Font;
|
||||
use Button::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum NoteKind {
|
||||
NORMAL,
|
||||
HOPO,
|
||||
HOP,
|
||||
TAP,
|
||||
}
|
||||
|
||||
type Time = Ratio<i64>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
struct Note {
|
||||
start: Time,
|
||||
button: Button,
|
||||
kind: NoteKind,
|
||||
start: Time,
|
||||
length: Option<Time>,
|
||||
}
|
||||
|
||||
@@ -75,108 +77,140 @@ struct Phrase {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct Song {
|
||||
pub struct Song {
|
||||
notes: Vec<Note>,
|
||||
star_phrases: Vec<Phrase>,
|
||||
}
|
||||
|
||||
// .chart file parsing
|
||||
|
||||
#[derive(inpt::Inpt)]
|
||||
struct Section {
|
||||
#[inpt(split = "Bracketed")]
|
||||
title: String,
|
||||
#[inpt(split = "Braced")]
|
||||
contents: String,
|
||||
mod chart;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
enum StateEvent {
|
||||
WindowOpen(usize),
|
||||
WindowClosed(usize),
|
||||
NoteCrossed(usize),
|
||||
Pressed(Button),
|
||||
Released(Button),
|
||||
Strum,
|
||||
}
|
||||
|
||||
#[derive(inpt::Inpt, Debug)]
|
||||
struct ChartDetails {
|
||||
#[inpt(split = "Word")]
|
||||
field_name: String,
|
||||
_equals: char,
|
||||
value: String,
|
||||
//#[derive(PartialEq, Eq)]
|
||||
//enum NoteState {
|
||||
// NONE,
|
||||
// PENDING(usize),
|
||||
// HIT,
|
||||
//}
|
||||
|
||||
//use NoteState::*;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct State {
|
||||
pending_notes: HashSet<usize>,
|
||||
hit_notes: HashSet<usize>,
|
||||
held: [bool; 5],
|
||||
score: u64,
|
||||
combo: u64,
|
||||
}
|
||||
|
||||
#[derive(inpt::Inpt, Debug)]
|
||||
struct ChartNote {
|
||||
time: i64,
|
||||
_equals: char,
|
||||
kind: char,
|
||||
index: usize,
|
||||
length: i64,
|
||||
fn update(
|
||||
State {
|
||||
pending_notes,
|
||||
hit_notes,
|
||||
held,
|
||||
score,
|
||||
combo,
|
||||
}: &mut State,
|
||||
song: &Song,
|
||||
event: StateEvent,
|
||||
) {
|
||||
use StateEvent::*;
|
||||
|
||||
dbg!(&event);
|
||||
|
||||
match event {
|
||||
WindowOpen(n) => {
|
||||
pending_notes.insert(n);
|
||||
}
|
||||
WindowClosed(n) => {
|
||||
pending_notes.remove(&n);
|
||||
if hit_notes.remove(&n) {
|
||||
if *combo < 32 {
|
||||
*combo += 1;
|
||||
}
|
||||
*score += 50 * *combo / 8;
|
||||
}
|
||||
}
|
||||
NoteCrossed(n) => {
|
||||
// TODO: if note is HOPO, do stuff
|
||||
}
|
||||
Pressed(n) => {
|
||||
held[n.index()] = true;
|
||||
}
|
||||
Released(n) => {
|
||||
held[n.index()] = false;
|
||||
}
|
||||
Strum => {
|
||||
// find earliest note that is currently being held down
|
||||
let Some(&earliest_index) = pending_notes.iter().filter(|&&i| held[song.notes[i].button.index()]).min() else {
|
||||
*combo = 0;
|
||||
return
|
||||
};
|
||||
let mut current_notes = vec![earliest_index];
|
||||
|
||||
let mut current_note_buttons = [false; 5];
|
||||
|
||||
// find all notes with same time
|
||||
for &i in pending_notes
|
||||
.iter()
|
||||
.filter(|&&i| song.notes[i].start == song.notes[earliest_index].start)
|
||||
{
|
||||
current_notes.push(i);
|
||||
current_note_buttons[song.notes[i].button.index()] = true;
|
||||
}
|
||||
|
||||
// compare buttons with notes
|
||||
if held == ¤t_note_buttons {
|
||||
for i in current_notes {
|
||||
hit_notes.insert(i);
|
||||
pending_notes.remove(&i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_score(canvas: &mut WindowCanvas, font: &Font, score: u64) -> Result<(), String> {
|
||||
let texture_creator = canvas.texture_creator();
|
||||
|
||||
// render a surface, and convert it to a texture bound to the canvas
|
||||
let surface = font
|
||||
.render(&score.to_string())
|
||||
.blended(Color::RGBA(255, 0, 0, 255))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let texture = texture_creator
|
||||
.create_texture_from_surface(&surface)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
canvas.set_draw_color(Color::RGBA(195, 217, 255, 255));
|
||||
|
||||
let TextureQuery { width, height, .. } = texture.query();
|
||||
|
||||
let target = Rect::new(0, 0, width, height);
|
||||
|
||||
canvas.copy(&texture, None, Some(target))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
println!("WELCOME TO THE GAME!");
|
||||
let song_str = std::fs::read_to_string(
|
||||
std::env::args()
|
||||
.nth(1)
|
||||
.ok_or(anyhow!("Please supply a song"))?,
|
||||
)?;
|
||||
let song_path = std::env::args()
|
||||
.nth(1)
|
||||
.ok_or(anyhow!("Please supply a song"))?;
|
||||
|
||||
// Unicode was a mistake
|
||||
let song_str = song_str.trim_start_matches('\u{feff}');
|
||||
|
||||
let sections: Vec<Section> = inpt(&song_str).map_err(|err| anyhow!("{err}"))?;
|
||||
|
||||
let Some(song) = sections.iter().find(|s| s.title == "Song") else {
|
||||
bail!("Could not find Song")
|
||||
};
|
||||
|
||||
let Some(expert_single) = sections.iter().find(|s| s.title == "ExpertSingle") else {
|
||||
bail!("Could not find ExpertSingle")
|
||||
};
|
||||
|
||||
let chart_details: Vec<Line<ChartDetails>> =
|
||||
inpt(&song.contents).map_err(|err| anyhow!("{err}"))?;
|
||||
let Some(resolution) = chart_details.iter().find(|c| c.inner.field_name == "Resolution") else {
|
||||
bail!("Could not find Resolution")
|
||||
};
|
||||
let resolution: i64 = resolution.inner.value.parse()?;
|
||||
|
||||
let chart_notes: Vec<ChartNote> =
|
||||
inpt(&expert_single.contents).map_err(|err| anyhow!("{err}"))?;
|
||||
|
||||
let mut song = Song::default();
|
||||
|
||||
for &ChartNote {
|
||||
time,
|
||||
kind,
|
||||
index,
|
||||
length,
|
||||
..
|
||||
} in &chart_notes
|
||||
{
|
||||
if kind == 'N' {
|
||||
song.notes.push(Note {
|
||||
start: Ratio::new(time, resolution),
|
||||
button: match index {
|
||||
0 => GREEN,
|
||||
1 => RED,
|
||||
2 => YELLOW,
|
||||
3 => BLUE,
|
||||
4 => ORANGE,
|
||||
7 => OPEN,
|
||||
_ => continue,
|
||||
},
|
||||
kind: NoteKind::NORMAL,
|
||||
length: if length > 0 {
|
||||
Some(Ratio::new(length, resolution))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
} else if kind == 'S' {
|
||||
song.star_phrases.push(Phrase {
|
||||
start: Ratio::new(time, resolution),
|
||||
length: Ratio::new(length, resolution),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
song.notes.sort_by_key(|k| k.start);
|
||||
song.star_phrases.sort_by_key(|k| k.start);
|
||||
let song = chart::parse_chart(song_path)?;
|
||||
|
||||
dbg!(&song);
|
||||
|
||||
@@ -192,13 +226,20 @@ fn main() -> Result<(), Error> {
|
||||
let mut canvas = window.into_canvas().build().map_err(Error::msg)?;
|
||||
let mut event_pump = sdl_context.event_pump().map_err(Error::msg)?;
|
||||
|
||||
let ttf_context = sdl2::ttf::init()
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(Error::msg)?;
|
||||
let mut font = ttf_context
|
||||
.load_font("./font/font.ttf", 128)
|
||||
.map_err(Error::msg)?;
|
||||
font.set_style(sdl2::ttf::FontStyle::BOLD);
|
||||
|
||||
let start = Instant::now() + Duration::from_secs(0);
|
||||
|
||||
let base_x = 600;
|
||||
let base_y = 900;
|
||||
|
||||
let draw_axis = true;
|
||||
let mut y_offset = 0;
|
||||
let note_dim = 20;
|
||||
let note_space = 50;
|
||||
|
||||
@@ -206,6 +247,14 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
let bpm = 105;
|
||||
|
||||
// window size in ms
|
||||
let window_before = 70;
|
||||
let window_after = 70;
|
||||
|
||||
let mut sent_set = HashSet::new();
|
||||
|
||||
let mut state = State::default();
|
||||
|
||||
'running: loop {
|
||||
let now = start.elapsed();
|
||||
let current_beat = Ratio::new(now.as_millis() as i64, 1000 * 60) * bpm;
|
||||
@@ -219,6 +268,28 @@ fn main() -> Result<(), Error> {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => break 'running,
|
||||
Event::KeyDown {
|
||||
keycode: Some(k), ..
|
||||
} => match k {
|
||||
Keycode::O => update(&mut state, &song, StateEvent::Pressed(GREEN)),
|
||||
Keycode::H => update(&mut state, &song, StateEvent::Pressed(RED)),
|
||||
Keycode::E => update(&mut state, &song, StateEvent::Pressed(YELLOW)),
|
||||
Keycode::A => update(&mut state, &song, StateEvent::Pressed(BLUE)),
|
||||
Keycode::I => update(&mut state, &song, StateEvent::Pressed(ORANGE)),
|
||||
Keycode::Space => update(&mut state, &song, StateEvent::Strum),
|
||||
_ => {}
|
||||
},
|
||||
Event::KeyUp {
|
||||
keycode: Some(k), ..
|
||||
} => match k {
|
||||
Keycode::O => update(&mut state, &song, StateEvent::Released(GREEN)),
|
||||
Keycode::H => update(&mut state, &song, StateEvent::Released(RED)),
|
||||
Keycode::E => update(&mut state, &song, StateEvent::Released(YELLOW)),
|
||||
Keycode::A => update(&mut state, &song, StateEvent::Released(BLUE)),
|
||||
Keycode::I => update(&mut state, &song, StateEvent::Released(ORANGE)),
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +306,14 @@ fn main() -> Result<(), Error> {
|
||||
note_dim,
|
||||
button,
|
||||
);
|
||||
if state.held[button.index()] {
|
||||
let _ = canvas.filled_circle(
|
||||
base_x + (button.lane() - 3) * note_space,
|
||||
base_y,
|
||||
note_dim / 2,
|
||||
button,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.thick_line(
|
||||
@@ -246,13 +325,44 @@ fn main() -> Result<(), Error> {
|
||||
0xFFFF00FFu32,
|
||||
);
|
||||
|
||||
for note in &song.notes {
|
||||
let window_before_pixels = window_before * bpm * beat_space / 1000 / 60;
|
||||
let window_after_pixels = window_after * bpm * beat_space / 1000 / 60;
|
||||
|
||||
let _ = canvas.thick_line(
|
||||
base_x - note_space * 3,
|
||||
base_y + window_before_pixels as i16,
|
||||
base_x + note_space * 3,
|
||||
base_y + window_before_pixels as i16,
|
||||
4,
|
||||
0xFFFFAA00u32,
|
||||
);
|
||||
|
||||
let _ = canvas.thick_line(
|
||||
base_x - note_space * 3,
|
||||
base_y - window_after_pixels as i16,
|
||||
base_x + note_space * 3,
|
||||
base_y - window_after_pixels as i16,
|
||||
4,
|
||||
0xFFFFAA00u32,
|
||||
);
|
||||
|
||||
for (index, note) in song.notes.iter().enumerate() {
|
||||
if note.button == OPEN {
|
||||
continue;
|
||||
}
|
||||
|
||||
let beats_until_note = note.start - current_beat;
|
||||
|
||||
let ms_until_note = (beats_until_note / bpm * 60_000).to_integer();
|
||||
|
||||
if ms_until_note < window_before && sent_set.insert(StateEvent::WindowOpen(index)) {
|
||||
update(&mut state, &song, StateEvent::WindowOpen(index));
|
||||
}
|
||||
|
||||
if -ms_until_note > window_after && sent_set.insert(StateEvent::WindowClosed(index)) {
|
||||
update(&mut state, &song, StateEvent::WindowClosed(index));
|
||||
}
|
||||
|
||||
let x = base_x + (note.button.lane() - 3) * note_space;
|
||||
let y = base_y - (beats_until_note * beat_space).to_integer() as i16;
|
||||
|
||||
@@ -269,6 +379,8 @@ fn main() -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
render_score(&mut canvas, &font, state.score).map_err(Error::msg)?;
|
||||
|
||||
canvas.present();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user