parent
3ca546a53b
commit
8b6387737c
@ -1,2 +1,3 @@
|
||||
septadrop
|
||||
*.deb
|
||||
*.deb
|
||||
DEBIAN/usr/games/septadrop
|
@ -1,7 +1,11 @@
|
||||
TARGET=septadrop_1.1.0_amd64
|
||||
mkdir -p ${TARGET}/usr/bin/
|
||||
rm -r ${TARGET}/usr/games/septadrop/
|
||||
mkdir -p ${TARGET}/usr/games/septadrop/
|
||||
cd ..
|
||||
cargo build --release
|
||||
cargo rustc --release -- --cfg debian
|
||||
cp target/release/septadrop build/${TARGET}/usr/bin/
|
||||
upx --best --lzma build/${TARGET}/usr/bin/septadrop
|
||||
cp -r res/* build/${TARGET}/usr/games/septadrop/
|
||||
cd build
|
||||
dpkg-deb --build ${TARGET}
|
@ -0,0 +1,37 @@
|
||||
use const_format::formatcp;
|
||||
|
||||
pub const TILE_SIZE: u32 = 20;
|
||||
|
||||
pub const GRID_WIDTH: u32 = 14;
|
||||
pub const GRID_HEIGHT: u32 = 20;
|
||||
|
||||
pub const WINDOW_WIDTH: u32 = 500;
|
||||
pub const WINDOW_HEIGHT: u32 = 440;
|
||||
|
||||
pub const PLAYFIELD_X: u32 = 20;
|
||||
pub const PLAYFIELD_Y: u32 = 20;
|
||||
|
||||
pub const NEXT_X: u32 = 370;
|
||||
pub const NEXT_Y: u32 = 70;
|
||||
pub const NEXT_WIDTH: u32 = 5;
|
||||
pub const NEXT_HEIGHT: u32 = 5;
|
||||
|
||||
pub const LINES_PER_LEVEL: u32 = 5;
|
||||
pub const POINTS_1_LINE: u32 = 40;
|
||||
pub const POINTS_2_LINES: u32 = 100;
|
||||
pub const POINTS_3_LINES: u32 = 300;
|
||||
pub const POINTS_4_LINES: u32 = 1200;
|
||||
|
||||
pub const MOVE_FRAME_INTERVAL: u32 = 125;
|
||||
pub const MAX_FAST_FORWARD_INTERVAL: u32 = 125;
|
||||
|
||||
pub const FPS: u32 = 60;
|
||||
|
||||
#[cfg(not(debian))]
|
||||
pub const RES_PATH: &str = "res";
|
||||
|
||||
#[cfg(debian)]
|
||||
pub const RES_PATH: &str = "/usr/games/septadrop";
|
||||
|
||||
pub const RES_AUDIO_PATH: &str = formatcp!("{RES_PATH}/audio");
|
||||
pub const RES_TEXTURES_PATH: &str = formatcp!("{RES_PATH}/textures");
|
@ -1,3 +1,493 @@
|
||||
use sfml::graphics::*;
|
||||
use sfml::window::*;
|
||||
use sfml::system::*;
|
||||
use sfml::audio::*;
|
||||
use rand::seq::SliceRandom;
|
||||
use gcd::Gcd;
|
||||
use std::io::Write;
|
||||
|
||||
mod structs;
|
||||
use structs::*;
|
||||
|
||||
mod config;
|
||||
use config::*;
|
||||
|
||||
pub fn audio(name: &str) -> String {
|
||||
format!("{RES_AUDIO_PATH}/{name}.wav")
|
||||
}
|
||||
|
||||
pub fn texture(name: &str) -> String {
|
||||
format!("{RES_TEXTURES_PATH}/{name}.png")
|
||||
}
|
||||
|
||||
fn get_level(lines: u32) -> u32 {
|
||||
return std::cmp::min(lines / LINES_PER_LEVEL, 15);
|
||||
}
|
||||
|
||||
fn get_update_interval(level: u32) -> u32 {
|
||||
// From Tetris Worlds, see https://harddrop.com/wiki/Tetris_Worlds#Gravity
|
||||
((0.8 - (level as i32 - 1) as f32 * 0.007).powi(level as i32 - 1) * 1000.0) as u32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let context_settings = ContextSettings::default();
|
||||
let mut window = RenderWindow::new(
|
||||
VideoMode::new(WINDOW_WIDTH, WINDOW_HEIGHT, 16),
|
||||
"septadrop",
|
||||
Style::TITLEBAR | Style::CLOSE,
|
||||
&context_settings
|
||||
);
|
||||
window.set_framerate_limit(FPS);
|
||||
window.set_key_repeat_enabled(false);
|
||||
|
||||
let icon = Image::from_file(&texture("icon")).unwrap();
|
||||
{
|
||||
let Vector2u { x: width, y: height } = icon.size();
|
||||
window.set_icon(width, height, icon.pixel_data());
|
||||
}
|
||||
|
||||
let mut thread_rng = rand::thread_rng();
|
||||
let block_types = BlockType::init_list();
|
||||
|
||||
let mut random_block = || Block::new(block_types.choose(&mut thread_rng).unwrap());
|
||||
|
||||
let mut block = random_block();
|
||||
let mut next_block = random_block();
|
||||
|
||||
// Default::default() initializes all array cells to None (no block)
|
||||
let mut grid: [[Option<&TileType>; GRID_WIDTH as usize]; GRID_HEIGHT as usize] = Default::default();
|
||||
|
||||
let blocks_texture = Texture::from_file(&texture("blocks")).unwrap();
|
||||
let mut sprite = Sprite::with_texture(&blocks_texture);
|
||||
|
||||
let background_texture = Texture::from_file(&texture("background")).unwrap();
|
||||
let background = Sprite::with_texture(&background_texture);
|
||||
|
||||
let number_renderer = NumberRenderer::default();
|
||||
|
||||
let mut paused_clear = RectangleShape::new();
|
||||
paused_clear.set_fill_color(Color::rgb(81, 62, 69));
|
||||
|
||||
let paused_texture = Texture::from_file(&texture("paused")).unwrap();
|
||||
let paused_text = {
|
||||
let mut paused_text = Sprite::with_texture(&paused_texture);
|
||||
let paused_texture_size = paused_texture.size();
|
||||
paused_text.set_position(Vector2f::new(
|
||||
PLAYFIELD_X as f32 + (GRID_WIDTH as f32 * TILE_SIZE as f32 / 2.0) - paused_texture_size.x as f32 / 2.0,
|
||||
PLAYFIELD_Y as f32 + (GRID_HEIGHT as f32 * TILE_SIZE as f32 / 2.0) - paused_texture_size.y as f32 / 2.0
|
||||
));
|
||||
paused_text
|
||||
};
|
||||
|
||||
let highscore_file_path = home::home_dir().unwrap().join(".septadrop");
|
||||
|
||||
let mut highscore: u32;
|
||||
|
||||
if highscore_file_path.exists() {
|
||||
highscore = std::fs::read_to_string(&highscore_file_path).unwrap().trim().parse::<u32>().unwrap();
|
||||
let point_gcd = POINTS_1_LINE
|
||||
.gcd(POINTS_2_LINES)
|
||||
.gcd(POINTS_3_LINES)
|
||||
.gcd(POINTS_4_LINES);
|
||||
if highscore % point_gcd != 0 {
|
||||
println!("It seems your system is misconfigured. Please see this guide for fixing the issue: https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let mut highscore_file = std::fs::File::create(&highscore_file_path).unwrap();
|
||||
write!(&mut highscore_file, "0").unwrap();
|
||||
highscore = 0;
|
||||
}
|
||||
|
||||
let mut score: u32 = 0;
|
||||
let mut lines: u32 = 0;
|
||||
let mut blocks: u32 = 0;
|
||||
let mut tiles: u32 = 0;
|
||||
|
||||
let mut update_interval = get_update_interval(0);
|
||||
|
||||
let mut rotate = false;
|
||||
let mut move_left = false;
|
||||
let mut move_right = false;
|
||||
let mut move_left_immediate = false;
|
||||
let mut move_right_immediate = false;
|
||||
let mut fast_forward = false;
|
||||
let mut snap = false;
|
||||
let mut paused = false;
|
||||
let mut paused_from_lost_focus = false;
|
||||
let mut update_clock = Clock::default();
|
||||
let mut move_clock = Clock::default();
|
||||
let mut pause_clock = Clock::default();
|
||||
let mut pause_offset: u32 = 0;
|
||||
let mut toggle_pause = false;
|
||||
|
||||
// https://sfxr.me/#57uBnWWZeyDTsBRrJsAp2Vwd76cMVrdeRQ7DirNQW5XekKxcrCUNx47Zggh7Uqw4R5FdeUpyk362uhjWmpNHmqxE7JBp3EkxDxfJ1VjzMRpuSHieW6B5iyVFM
|
||||
let rotate_buffer = SoundBuffer::from_file(&audio("rotate")).unwrap();
|
||||
let mut rotate_sound = Sound::with_buffer(&rotate_buffer);
|
||||
|
||||
// https://sfxr.me/#57uBnWTMa2LUtaPa3P8xWZekiRxNwCPFWpRoPDVXDJM9KHkiGJcs6J62FRcjMY5oVNdT73MtmUf5rXCPvSZWL7AZuTRWWjKbPKTpZjT85AcZ6htUqTswkjksZ
|
||||
let snap_buffer = SoundBuffer::from_file(&audio("snap")).unwrap();
|
||||
let mut snap_sound = Sound::with_buffer(&snap_buffer);
|
||||
|
||||
// https://sfxr.me/#57uBnWbareN7MJJsWGD8eFCrqjikS9f8JXg8jvmKzMdVtqmRsb81eToSUpnkqgFhvxD2QoAjpw4SmGZHZjbhEiPQKetRSHCHXYFZzD7Q6RVVS9CRSeRAb6bZp
|
||||
let game_over_buffer = SoundBuffer::from_file(&audio("game_over")).unwrap();
|
||||
let mut game_over_sound = Sound::with_buffer(&game_over_buffer);
|
||||
|
||||
// https://sfxr.me/#7BMHBGMfGk8EHV8czJkUucUm8EMAnMNxiqYyTfKkMpHFJu44GEdD7xP6E8NM3K7RKRExTpagPBAiWf7BLtC52CEWJVGHh8hwDLygoEG86tcPth2UtmfdrXLoh
|
||||
let row_clear_buffer = SoundBuffer::from_file(&audio("row_clear")).unwrap();
|
||||
let mut row_clear_sound = Sound::with_buffer(&row_clear_buffer);
|
||||
|
||||
// https://sfxr.me/#57uBnWg8448kTPqWAxeDvZ5CP5JWbrfJGWuRcTjva5uX3vvBnEAZ6SfiH9oLKMXgsusuJwGWx6KPfvLfHtqnhLxr476ptGv4jPbfNhQaFMYeMHFdHk9SotQ4X
|
||||
let level_up_buffer = SoundBuffer::from_file(&audio("level_up")).unwrap();
|
||||
let mut level_up_sound = Sound::with_buffer(&level_up_buffer);
|
||||
|
||||
// https://sfxr.me/#34T6PkzvrkfdahGDBAh1uYGXTwZ8rG54kxfHpgdVCPxqG7yyK5UuqgiK9Z8Q5177itxbkSNfLSHm4zTkemT4iyxJpW89VJx82feaq8qxZeA5AJR2nWZZR59hq
|
||||
let new_highscore_buffer = SoundBuffer::from_file(&audio("new_highscore")).unwrap();
|
||||
let mut new_highscore_sound = Sound::with_buffer(&new_highscore_buffer);
|
||||
|
||||
while window.is_open() {
|
||||
loop {
|
||||
match window.poll_event() {
|
||||
Some(event) => match event {
|
||||
Event::Closed => {
|
||||
window.close();
|
||||
break;
|
||||
},
|
||||
Event::GainedFocus => {
|
||||
if paused && paused_from_lost_focus {
|
||||
toggle_pause = true;
|
||||
}
|
||||
},
|
||||
Event::LostFocus => {
|
||||
if !paused {
|
||||
toggle_pause = true;
|
||||
paused_from_lost_focus = true;
|
||||
}
|
||||
},
|
||||
Event::KeyPressed { code, alt: _, ctrl: _, shift: _, system: _ } => {
|
||||
match code {
|
||||
Key::ESCAPE => toggle_pause = true,
|
||||
Key::SPACE => snap = !paused,
|
||||
Key::UP => rotate = !paused,
|
||||
Key::DOWN => fast_forward = !paused,
|
||||
Key::LEFT => {
|
||||
move_left = !paused;
|
||||
move_left_immediate = !paused;
|
||||
move_clock.restart();
|
||||
},
|
||||
Key::RIGHT => {
|
||||
move_right = !paused;
|
||||
move_right_immediate = !paused;
|
||||
move_clock.restart();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
Event::KeyReleased { code, alt: _, ctrl: _, shift: _, system: _ } => {
|
||||
match code {
|
||||
Key::DOWN => fast_forward = false,
|
||||
Key::LEFT => move_left = false,
|
||||
Key::RIGHT => move_right = false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
},
|
||||
None => break
|
||||
}
|
||||
}
|
||||
|
||||
if toggle_pause {
|
||||
paused = !paused;
|
||||
if paused {
|
||||
pause_clock.restart();
|
||||
paused_clear.set_position(Vector2f::new(
|
||||
PLAYFIELD_X as f32,
|
||||
PLAYFIELD_Y as f32
|
||||
));
|
||||
paused_clear.set_size(Vector2f::new(
|
||||
(GRID_WIDTH * TILE_SIZE) as f32,
|
||||
(GRID_HEIGHT * TILE_SIZE) as f32
|
||||
));
|
||||
window.draw(&paused_clear);
|
||||
let size = Vector2f::new(
|
||||
(NEXT_WIDTH * TILE_SIZE) as f32,
|
||||
(NEXT_HEIGHT * TILE_SIZE) as f32
|
||||
);
|
||||
paused_clear.set_position(Vector2f::new(
|
||||
NEXT_X as f32,
|
||||
NEXT_Y as f32
|
||||
) - size / 2.0);
|
||||
paused_clear.set_size(size);
|
||||
window.draw(&paused_clear);
|
||||
window.draw(&paused_text);
|
||||
window.display();
|
||||
} else {
|
||||
pause_offset = pause_clock.elapsed_time().as_milliseconds() as u32;
|
||||
}
|
||||
toggle_pause = false;
|
||||
}
|
||||
|
||||
if paused {
|
||||
// window.display() is where SFML implements frame rate limiting
|
||||
// If we don't run this here, then when paused septadrop will max out the thread
|
||||
window.display();
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_update_frame =
|
||||
update_clock.elapsed_time().as_milliseconds() - pause_offset as i32 >
|
||||
if fast_forward {
|
||||
std::cmp::min(update_interval, MAX_FAST_FORWARD_INTERVAL)
|
||||
} else {
|
||||
update_interval
|
||||
} as i32;
|
||||
if is_update_frame {
|
||||
update_clock.restart();
|
||||
}
|
||||
|
||||
let is_move_frame =
|
||||
move_clock.elapsed_time().as_milliseconds() - pause_offset as i32 >
|
||||
MOVE_FRAME_INTERVAL as i32;
|
||||
if is_move_frame {
|
||||
move_clock.restart();
|
||||
}
|
||||
|
||||
pause_offset = 0;
|
||||
|
||||
// Rotation
|
||||
if rotate {
|
||||
block.rotation_state += 1;
|
||||
// Check to see if new rotation state is overlapping any tiles
|
||||
let mut offset_required: i32 = 0;
|
||||
for tile in block.get_tiles().iter() {
|
||||
if grid[tile.y as usize][tile.x as usize].is_some() {
|
||||
// Can't wall kick off of blocks
|
||||
block.rotation_state -= 1;
|
||||
break;
|
||||
}
|
||||
if tile.x <= 0 {
|
||||
let potential_offset = -tile.x;
|
||||
if potential_offset > offset_required.abs() {
|
||||
offset_required = potential_offset;
|
||||
}
|
||||
} else if tile.x >= GRID_WIDTH as i32 {
|
||||
let potential_offset = GRID_WIDTH as i32 - tile.x - 1;
|
||||
if -potential_offset > offset_required.abs() {
|
||||
offset_required = potential_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
block.position.x += offset_required;
|
||||
rotate = false;
|
||||
rotate_sound.play();
|
||||
}
|
||||
|
||||
// Horizontal movement
|
||||
{
|
||||
let mut movement = 0;
|
||||
if move_left_immediate || (is_move_frame && move_left) {
|
||||
movement -= 1;
|
||||
move_left_immediate = false;
|
||||
}
|
||||
if move_right_immediate || (is_move_frame && move_right) {
|
||||
movement += 1;
|
||||
move_right_immediate = false;
|
||||
}
|
||||
if movement != 0 {
|
||||
for (i, tile) in block.get_tiles().iter().enumerate().rev() {
|
||||
if tile.x + movement < 0 || tile.x + movement >= GRID_WIDTH as i32 || grid[tile.y as usize][(tile.x + movement) as usize].is_some() {
|
||||
break;
|
||||
}
|
||||
if i == 0 {
|
||||
/*
|
||||
We're going through all the blocks backwards,
|
||||
so the first element is last.
|
||||
(Only for .enumerate().rev(), not .rev().enumerate(),
|
||||
since .enumerate() is what adds indexes.)
|
||||
If we managed to get through all of the tiles without breaking,
|
||||
(haven't found anything that obstructs any tile),
|
||||
we can finally add the movement.
|
||||
*/
|
||||
block.position.x += movement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Snapping
|
||||
let snap_offset = {
|
||||
let mut snap_offset = 0;
|
||||
'outer: loop {
|
||||
for tile in block.get_tiles().iter() {
|
||||
let y = tile.y + snap_offset;
|
||||
if y >= GRID_HEIGHT as i32 || grid[y as usize][tile.x as usize].is_some() {
|
||||
snap_offset -= 1;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
snap_offset += 1;
|
||||
}
|
||||
snap_offset
|
||||
};
|
||||
let mut landed = snap;
|
||||
if snap {
|
||||
block.position.y += snap_offset;
|
||||
snap = false;
|
||||
snap_sound.play();
|
||||
} else if is_update_frame { // Land checking
|
||||
for tile in block.get_tiles().iter() {
|
||||
if tile.y == GRID_HEIGHT as i32 - 1 || grid[tile.y as usize + 1][tile.x as usize].is_some() {
|
||||
landed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let landed = landed; // remove mutability
|
||||
|
||||
// Clear window
|
||||
// Normally, one would run window.clear(),
|
||||
// but the background image covers the entire window.
|
||||
window.draw(&background);
|
||||
|
||||
// Draw block
|
||||
if !landed {
|
||||
for tile in block.get_tiles().iter() {
|
||||
let snap_y = tile.y + snap_offset;
|
||||
sprite.set_texture_rect(&block.block_type.tile_type.texture_rect);
|
||||
sprite.set_position(Vector2f::new(
|
||||
(PLAYFIELD_X as i32 + tile.x * TILE_SIZE as i32) as f32,
|
||||
(PLAYFIELD_Y as i32 + tile.y * TILE_SIZE as i32) as f32
|
||||
));
|
||||
window.draw(&sprite);
|
||||
sprite.set_texture_rect(&block.block_type.tile_type.ghost_texture_rect);
|
||||
sprite.set_position(Vector2f::new(
|
||||
(PLAYFIELD_X as i32 + tile.x * TILE_SIZE as i32) as f32,
|
||||
(PLAYFIELD_Y as i32 + snap_y * TILE_SIZE as i32) as f32
|
||||
));
|
||||
window.draw(&sprite);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw next block
|
||||
{
|
||||
let next_block_tiles = next_block.get_tiles();
|
||||
// This is assuming the next block spawns unrotated.
|
||||
// Refactoring is needed if random rotations are added
|
||||
let x_offset = next_block.block_type.width * TILE_SIZE / 2;
|
||||
let y_offset = (next_block.block_type.height + next_block.block_type.starting_line * 2) * TILE_SIZE / 2;
|
||||
for tile in next_block_tiles.iter() {
|
||||
sprite.set_texture_rect(&next_block.block_type.tile_type.texture_rect);
|
||||
sprite.set_position(Vector2f::new(
|
||||
(NEXT_X + (tile.x - next_block.position.x) as u32 * TILE_SIZE - x_offset) as f32,
|
||||
(NEXT_Y + (tile.y - next_block.position.y) as u32 * TILE_SIZE - y_offset) as f32
|
||||
));
|
||||
window.draw(&sprite);
|
||||
}
|
||||
}
|
||||
|
||||
// Landing (transferring block to grid and reinitializing)
|
||||
if landed {
|
||||
tiles += block.get_tiles().len() as u32;
|
||||
blocks += 1;
|
||||
for tile in block.get_tiles().iter() {
|
||||
grid[tile.y as usize][tile.x as usize] = Some(&block.block_type.tile_type);
|
||||
}
|
||||
let mut cleared_lines = 0;
|
||||
for y in 0..GRID_HEIGHT {
|
||||
let mut completed = true;
|
||||
for x in 0..GRID_WIDTH {
|
||||
if grid[y as usize][x as usize].is_none() {
|
||||
completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !completed {
|
||||
continue;
|
||||
}
|
||||
for z in (0..y).rev() {
|
||||
let z = z as usize;
|
||||
for x in 0..GRID_WIDTH {
|
||||
let x = x as usize;
|
||||
grid[z + 1][x] = grid[z][x];
|
||||
}
|
||||
}
|
||||
cleared_lines += 1;
|
||||
}
|
||||
let mut scored = match cleared_lines {
|
||||
0 => 0,
|
||||
1 => POINTS_1_LINE,
|
||||
2 => POINTS_2_LINES,
|
||||
3 => POINTS_3_LINES,
|
||||
_ => POINTS_4_LINES
|
||||
};
|
||||
if scored > 0 {
|
||||
let level = get_level(lines);
|
||||
scored *= level + 1;
|
||||
if score + scored > highscore && score < highscore {
|
||||
new_highscore_sound.play();
|
||||
}
|
||||
score += scored;
|
||||
lines += cleared_lines;
|
||||
let new_level = get_level(lines);
|
||||
if level != new_level {
|
||||
level_up_sound.play();
|
||||
}
|
||||
if score > highscore {
|
||||
highscore = score;
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&highscore_file_path)
|
||||
.unwrap();
|
||||
file.write_all(highscore.to_string().as_bytes()).unwrap();
|
||||
file.flush().unwrap();
|
||||
}
|
||||
update_interval = get_update_interval(new_level);
|
||||
row_clear_sound.play();
|
||||
}
|
||||
for tile in next_block.get_tiles().iter() {
|
||||
if grid[tile.y as usize][tile.x as usize].is_some() {
|
||||
score = 0;
|
||||
lines = 0;
|
||||
blocks = 0;
|
||||
tiles = 0;
|
||||
grid = Default::default(); // reset grid
|
||||
update_interval = get_update_interval(0);
|
||||
game_over_sound.play();
|
||||
next_block = random_block();
|
||||
break;
|
||||
}
|
||||
}
|
||||
block = next_block;
|
||||
next_block = random_block();
|
||||
} else if is_update_frame {
|
||||
block.position.y += 1;
|
||||
}
|
||||
|
||||
// Drawing grid
|
||||
for y in 0..GRID_HEIGHT {
|
||||
for x in 0..GRID_WIDTH {
|
||||
let tile_type = grid[y as usize][x as usize];
|
||||
if tile_type.is_none() {
|
||||
continue;
|
||||
}
|
||||
let tile_type = tile_type.unwrap();
|
||||
sprite.set_texture_rect(&tile_type.texture_rect);
|
||||
sprite.set_position(Vector2f::new(
|
||||
(PLAYFIELD_X + x * TILE_SIZE) as f32,
|
||||
(PLAYFIELD_Y + y * TILE_SIZE) as f32
|
||||
));
|
||||
window.draw(&sprite);
|
||||
}
|
||||
}
|
||||
|
||||
number_renderer.render(&mut window, score, 477, 162);
|
||||
number_renderer.render(&mut window, highscore, 477, 202);
|
||||
number_renderer.render(&mut window, lines, 477, 242);
|
||||
number_renderer.render(&mut window, get_level(lines), 477, 282);
|
||||
number_renderer.render(&mut window, blocks, 477, 322);
|
||||
number_renderer.render(&mut window, tiles, 477, 362);
|
||||
|
||||
window.display();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
use crate::structs::BlockType;
|
||||
use crate::config::GRID_WIDTH;
|
||||
use sfml::system::Vector2i;
|
||||
|
||||
pub struct Block<'a> {
|
||||
pub block_type: &'a BlockType,
|
||||
pub position: Vector2i,
|
||||
pub rotation_state: i32
|
||||
}
|
||||
|
||||
impl<'a> Block<'a> {
|
||||
pub fn new(block_type: &'a BlockType, ) -> Self {
|
||||
Self {
|
||||
block_type,
|
||||
position: Vector2i::new(
|
||||
GRID_WIDTH as i32 / 2 - block_type.grid[0].len() as i32 / 2,
|
||||
0
|
||||
),
|
||||
rotation_state: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tiles(&mut self) -> Vec<Vector2i> {
|
||||
let mut tiles: Vec<Vector2i> = Vec::new();
|
||||
for (y, row) in self.block_type.grid.iter().enumerate() {
|
||||
let y = y as i32;
|
||||
for (x, cell) in row.iter().enumerate() {
|
||||
let x = x as i32;
|
||||
if !cell {
|
||||
continue;
|
||||
}
|
||||
let mut rotated = Vector2i::new(x as i32, y as i32);
|
||||
if self.block_type.rotate {
|
||||
let center_x = row.len() as i32 / 2;
|
||||
let center_y = self.block_type.grid.len() as i32 / 2;
|
||||
let offset_x = x - center_x;
|
||||
let offset_y = y - center_y;
|
||||
match self.rotation_state {
|
||||
0 => {},
|
||||
1 => {
|
||||
rotated.x = center_x + offset_y;
|
||||
rotated.y = center_y - offset_x;
|
||||
},
|
||||
2 => {
|
||||
rotated.x = center_x - offset_x;
|
||||
rotated.y = center_y - offset_y;
|
||||
},
|
||||
3 => {
|
||||
rotated.x = center_x - offset_y;
|
||||
rotated.y = center_y + offset_x;
|
||||
}
|
||||
_ => self.rotation_state %= 4
|
||||
}
|
||||
}
|
||||
let global = self.position + rotated;
|
||||
tiles.push(global);
|
||||
}
|
||||
}
|
||||
tiles
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
use crate::structs::TileType;
|
||||
use crate::config::TILE_SIZE;
|
||||
use sfml::graphics::IntRect;
|
||||
|
||||
pub struct BlockType {
|
||||
pub tile_type: TileType,
|
||||
pub grid: Vec<Vec<bool>>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub starting_line: u32,
|
||||
pub rotate: bool
|
||||
}
|
||||
|
||||
impl BlockType {
|
||||
pub fn new(tile_type: TileType, grid: Vec<Vec<bool>>, rotate: bool) -> Self {
|
||||
let mut width: u32 = 0;
|
||||
let mut height: u32 = 0;
|
||||
let mut starting_line: u32 = 0;
|
||||
for (y, row) in grid.iter().enumerate() {
|
||||
let mut has_content = false;
|
||||
for (x, cell) in row.iter().enumerate() {
|
||||
if *cell {
|
||||
width = std::cmp::max(width, x as u32 + 1);
|
||||
has_content = true;
|
||||
}
|
||||
}
|
||||
if has_content {
|
||||
if height == 0 {
|
||||
starting_line = y as u32;
|
||||
}
|
||||
height = y as u32 + 1 - starting_line;
|
||||
}
|
||||
}
|
||||
Self {
|
||||
tile_type,
|
||||
grid,
|
||||
width,
|
||||
height,
|
||||
starting_line,
|
||||
rotate
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_list() -> Vec<Self> {
|
||||
let mut list = Vec::new();
|
||||
let tile_size = TILE_SIZE as i32;
|
||||
|
||||
const Y: bool = true;
|
||||
const N: bool = false;
|
||||
|
||||
// I block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(0, 0, tile_size, tile_size),
|
||||
IntRect::new(0, tile_size, tile_size, tile_size)
|
||||
),
|
||||
vec![
|
||||
vec![N, N, N, N],
|
||||
vec![Y, Y, Y, Y],
|
||||
vec![N, N, N, N],
|
||||
vec![N, N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
// J Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size, tile_size, tile_size, tile_size),
|
||||
),
|
||||
vec![
|
||||
vec![Y, N, N],
|
||||
vec![Y, Y, Y],
|
||||
vec![N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
// L Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size * 2, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size * 2, tile_size, tile_size, tile_size),
|
||||
),
|
||||
vec![
|
||||
vec![N, N, Y],
|
||||
vec![Y, Y, Y],
|
||||
vec![N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
// O Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size * 3, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size * 3, tile_size, tile_size, tile_size),
|
||||
),
|
||||
vec![
|
||||
vec![Y, Y],
|
||||
vec![Y, Y]
|
||||
],
|
||||
false
|
||||
));
|
||||
|
||||
// S Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size * 4, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size * 4, tile_size, tile_size, tile_size),
|
||||
),
|
||||
vec![
|
||||
vec![N, Y, Y],
|
||||
vec![Y, Y, N],
|
||||
vec![N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
// T Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size * 5, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size * 5, tile_size, tile_size, tile_size),
|
||||
),
|
||||
vec![
|
||||
vec![N, Y, N],
|
||||
vec![Y, Y, Y],
|
||||
vec![N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
// Z Block
|
||||
list.push(Self::new(
|
||||
TileType::new(
|
||||
IntRect::new(tile_size * 6, 0, tile_size, tile_size),
|
||||
IntRect::new(tile_size * 6, tile_size, tile_size, tile_size)
|
||||
),
|
||||
vec![
|
||||
vec![Y, Y, N],
|
||||
vec![N, Y, Y],
|
||||
vec![N, N, N]
|
||||
],
|
||||
true
|
||||
));
|
||||
|
||||
list
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
mod block_type;
|
||||
pub use block_type::BlockType;
|
||||
|
||||
mod block;
|
||||
pub use block::Block;
|
||||
|
||||
mod tile_type;
|
||||
pub use tile_type::TileType;
|
||||
|
||||
mod number_renderer;
|
||||
pub use number_renderer::NumberRenderer;
|
@ -0,0 +1,76 @@
|
||||
use sfml::graphics::*;
|
||||
use sfml::system::Vector2f;
|
||||
use sfml::SfBox;
|
||||
|
||||
pub struct NumberRenderer {
|
||||
texture: SfBox<Texture>,
|
||||
comma_rect: IntRect,
|
||||
numeral_rects: [IntRect; 10],
|
||||
}
|
||||
|
||||
impl NumberRenderer {
|
||||
pub fn new(texture: SfBox<Texture>, comma_rect: IntRect, numeral_rects: [IntRect; 10]) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
comma_rect,
|
||||
numeral_rects
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Self {
|
||||
Self::new(
|
||||
Texture::from_file(&crate::texture("numerals")).unwrap(),
|
||||
IntRect::new(134, 0, 10, 16),
|
||||
[
|
||||
IntRect::new(0, 0, 14, 16),
|
||||
IntRect::new(14, 0, 8, 16),
|
||||
IntRect::new(22, 0, 14, 16),
|
||||
IntRect::new(36, 0, 14, 16),
|
||||
IntRect::new(50, 0, 14, 16),
|
||||
IntRect::new(64, 0, 14, 16),
|
||||
IntRect::new(78, 0, 14, 16),
|
||||
IntRect::new(92, 0, 14, 16),
|
||||
IntRect::new(106, 0, 14, 16),
|
||||
IntRect::new(120, 0, 14, 16)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(&self, window: &mut RenderWindow, number: u32, x: u32, y: u32) {
|
||||
let number_string = number.to_string();
|
||||
let get_numeral_rect = |numeral: char| self.numeral_rects[numeral.to_digit(10).unwrap() as usize];
|
||||
let mut numeral_position = Vector2f::new({
|
||||
let numeral = number_string.chars().last().unwrap();
|
||||
let numeral_rect = get_numeral_rect(numeral);
|
||||
x as f32 - numeral_rect.width as f32
|
||||
}, y as f32);
|
||||
let digits = number_string.len();
|
||||
let mut sprite = Sprite::new();
|
||||
sprite.set_texture(&self.texture, false);
|
||||
// can't reverse .chars() directly since it doesn't implement std::iter::DoubleEndedIterator
|
||||
// Instead, we must collect it to a Vec then iterate over that.
|
||||
// For more info, see https://users.rust-lang.org/t/43401/2
|
||||
for (i, numeral) in number_string.chars().collect::<Vec<char>>().iter().enumerate().rev() {
|
||||
let numeral_rect = get_numeral_rect(*numeral);
|
||||
if (digits - i) % 3 == 1 && i != digits - 1 {
|
||||
sprite.set_texture_rect(&self.comma_rect);
|
||||
sprite.set_position(numeral_position);
|
||||
window.draw(&sprite);
|
||||
numeral_position.x -= numeral_rect.width as f32;
|
||||
}
|
||||
sprite.set_texture_rect(&numeral_rect);
|
||||
sprite.set_position(numeral_position);
|
||||
window.draw(&sprite);
|
||||
if i == 0 {
|
||||
break;
|
||||
}
|
||||
if (digits - i) % 3 == 0 {
|
||||
numeral_position.x -= self.comma_rect.width as f32;
|
||||
continue;
|
||||
}
|
||||
let numeral = number_string.as_bytes()[i - 1] as char;
|
||||
let numeral_rect = get_numeral_rect(numeral);
|
||||
numeral_position.x -= numeral_rect.width as f32;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
use sfml::graphics::IntRect;
|
||||
|
||||
pub struct TileType {
|
||||
pub texture_rect: IntRect,
|
||||
pub ghost_texture_rect: IntRect,
|
||||
}
|
||||
|
||||
impl TileType {
|
||||
pub fn new(texture_rect: IntRect, ghost_texture_rect: IntRect) -> Self {
|
||||
Self {
|
||||
texture_rect,
|
||||
ghost_texture_rect
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue