I'm trying to build Tetris in Rust. I have this struct for Tetrominoes whose functionality I want to split into different parts.
So, to me, a Tetromino at its core is just an array of 4 coordinates, and that's how I represent it in my code.
struct Tetromino {
minoes: [Mino; 4]
}
struct Mino {
x_to_center: HalfStep,
y_to_center: HalfStep
}
/* HalfStep is a numerical type I made that rounds itself to the nearest half. */
But, a Tetromino isn't just a bunch of coordinates; it has to move too! My original solution was to add another field to my Tetromino struct to give it a position, then subtract from the y coordinate of that position to make it slowly fall to the bottom of the screen.
struct Tetromino {
minoes: [Mino; 4],
center: OnGrid
}
impl Tetromino {
fn fall(mut self, speed: f32, delta_time: Duration) -> Self {
self.center.row -= speed * delta_time.as_secs_f32();
self
}
fn snap_to_grid(&self) -> [Snapped; 4] {
self.minoes.map(|mino| Snapped {
row: (
self.center.row + f32::from(mino.y_to_center)
).floor() as i8,
column: (
f32::from(self.center.column) +
f32::from(mino.x_to_center)
).floor() as i8,
})
}
}
struct OnGrid {
row: f32,
column: HalfStep
}
struct Snapped {
row: i8,
column: i8,
}
Pretty standard stuff so far, and my original solution served me well. Now, here's where my problem comes in.
So far, I've been using terminal graphics for my Tetris implementation, but now I want to upgrade to raylib. Now that I'm starting to switch to raylib, I've realized that it would be better if I separated my center field from my Tetromino struct.
Why? Because I don't just need to display Tetrominoes on the playfield grid, but I need to display them on the Next Queue and the Hold Queue too.
Though the Tetromino would need a position in each of those cases, they all need a different kind of position. For the playfield grid, I need the Tetromino's position to be in terms of rows and columns, but for the Next Queue, I need its position to be more in terms of pixels on the screen.
What I want to have is something like this:
struct Tetromino {
minoes: [Mino; 4]
}
struct OnGrid {
row: f32,
column: HalfStep
}
struct OnScreen {
x: Pixels,
y: Pixels
}
struct Game {
falling_tetromino: Positioned<Tetromino, OnGrid>
}
struct Graphics {
falling_tetromino: Positioned<Tetromino, OnScreen>
}
My question is, how do you best implement this "Positioned" thing?
Here's what I've tried so far.
struct Position<X: Numeric, Y: Numeric> {
x: X,
y: Y
}
struct Positioned<T, X: Numeric, Y: Numeric> {
positioned: T,
position: Position<X, Y> // Maybe rewrite OnGrid as OnGrid(Position<HalfStep, f32>) ???
}
trait Position {
type X: Numeric;
type Y: Numeric;
fn x(&self) -> &Self::X;
fn y(&self) -> &Self::Y;
fn x_mut(&mut self) -> &mut Self::X;
fn y_mut(&mut self) -> &mut Self::Y;
}
trait Positioned {
type T;
type P: Position;
fn positioned(&self) -> &Self::T;
fn position(&self) -> &Self::P;
}
trait Position {
type X: Numeric;
type Y: Numeric;
fn x(&self) -> &Self::X;
fn y(&self) -> &Self::Y;
fn x_mut(&mut self) -> &mut Self::X;
fn y_mut(&mut self) -> &mut Self::Y;
}
struct Positioned<T, P: Position> {
positioned: T,
position: P
}
I've tried these, but none of just them feel right. So now, I'm here, asking you guys for help.