Rewrite in Rust

main
Elnu 3 years ago
parent 3ca546a53b
commit 8b6387737c

93
Cargo.lock generated

@ -14,6 +14,26 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "const_format"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "csfml-audio-sys"
version = "0.6.0"
@ -54,6 +74,15 @@ dependencies = [
"sfml-build",
]
[[package]]
name = "gcd"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37978dab2ca789938a83b2f8bc1ef32db6633af9051a6cd409eff72cbaaa79a"
dependencies = [
"paste",
]
[[package]]
name = "getrandom"
version = "0.2.5"
@ -65,6 +94,15 @@ dependencies = [
"wasi",
]
[[package]]
name = "home"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
dependencies = [
"winapi",
]
[[package]]
name = "libc"
version = "0.2.120"
@ -77,12 +115,36 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "paste"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -117,6 +179,9 @@ dependencies = [
name = "septadrop"
version = "1.1.0"
dependencies = [
"const_format",
"gcd",
"home",
"rand",
"sfml",
]
@ -142,6 +207,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e0893aaf18583de27202b17007258377d5c4be16e1d0b601fd6943bc36c98b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
@ -153,3 +224,25 @@ name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -5,6 +5,14 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
strip = true
lto = true
codegen-units = 1
[dependencies]
sfml = "0.16.0"
rand = "0.8.5"
home = "0.5.3"
gcd = "2.1.0"
const_format = "0.2.22"

1
build/.gitignore vendored

@ -1,2 +1,3 @@
septadrop
*.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}

@ -1,3 +1,8 @@
septadrop (1.1.0) impish; urgency=low
* Rewrite in Rust
* Prevent block overlapping at top of screen; immediate game over
septadrop (1.0.1) impish; urgency=low
* Fix performance issue in paused state

@ -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…
Cancel
Save