Skip to content

Commit a819261

Browse files
committed
Change: support moving left and right with collisions against blocks and bounding walls
1 parent 59665db commit a819261

File tree

6 files changed

+244
-26
lines changed

6 files changed

+244
-26
lines changed

demo/7-lr-collision.gif

123 KB
Loading

readme.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,16 @@
5555
![Image of unit tests passing](demo/6-test.png)
5656

5757
- [X] [Handle game lose state](https://github.com/scottnm/tetrust/commit/b72efb7eb834d442885c35f5cbb8173c2b1ba887)
58+
- [X] [Handle left-right inputs](https://github.com/scottnm/tetrust/commit/b72efb7eb834d442885c35f5cbb8173c2b1ba887)
59+
60+
![Image of left-right collision](demo/7-lr-collision.gif)
61+
5862
- [ ] Constrain board size
59-
- [ ] Handle left-right inputs
6063
- [ ] Allow tetrominos to rotate
6164
- [ ] Allow clearing lines
62-
- [ ] Generate/Preview random blocks
65+
- [ ] Speed up pieces falling as more lines are cleared
66+
- [ ] Preview blocks
67+
- [ ] Handle pause
6368
- [ ] Handle scoring
69+
- [ ] Handle quick fall
6470

src/block.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub enum BlockType {
1212
L,
1313
}
1414

15-
#[derive(Clone, Copy, Debug)]
15+
#[derive(Clone, Copy, Debug, PartialEq)]
1616
pub struct Cell(pub i32, pub i32);
1717

1818
macro_rules! cell_array {
@@ -37,7 +37,7 @@ pub static BLOCKTYPES: [BlockType; 7] = [
3737

3838
impl BlockType {
3939
pub fn random<T: RangeRng<usize>>(rng: &mut T) -> BlockType {
40-
BLOCKTYPES[rng.gen_range(0, BLOCKTYPES.len())]
40+
BLOCKTYPES[rng.gen_range(1, BLOCKTYPES.len() + 1) - 1]
4141
}
4242

4343
pub fn sprite_char(&self) -> char {

src/game.rs

+61-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ enum GamePhase {
88
GameOver,
99
}
1010

11+
enum Bound {
12+
Floor(i32),
13+
LeftWall(i32),
14+
RightWall(i32),
15+
}
16+
1117
pub struct GameState<TBlockTypeRand, TBlockPosRand>
1218
where
1319
TBlockTypeRand: RangeRng<usize>,
@@ -82,6 +88,18 @@ where
8288
}
8389
}
8490

91+
pub fn move_block_horizontal(&mut self, horizontal_motion: i32) {
92+
match self.game_phase {
93+
GamePhase::MoveBlock => {
94+
let moving_block_id = self.block_count - 1; // we are always moving the last block
95+
if self.can_block_move(moving_block_id, horizontal_motion) {
96+
self.block_positions[moving_block_id].1 += horizontal_motion;
97+
}
98+
},
99+
GamePhase::GenerateBlock | GamePhase::GameOver => (),
100+
}
101+
}
102+
85103
pub fn block_count(&self) -> usize {
86104
self.block_count
87105
}
@@ -97,15 +115,36 @@ where
97115
pub fn has_block_landed(&self, block_id: usize) -> bool {
98116
assert_eq!(self.blocks.len(), self.block_positions.len());
99117

100-
is_resting_on_floor(
118+
is_touching_bound(
101119
self.blocks[block_id],
102120
self.block_positions[block_id],
103-
self.board_height,
104-
) || is_resting_on_other_block(
121+
Bound::Floor(self.board_height),
122+
) || is_touching_block(
105123
block_id,
106124
self.block_count,
107125
&self.blocks,
108126
&self.block_positions,
127+
(1, 0),
128+
)
129+
}
130+
131+
pub fn can_block_move(&self, block_id: usize, horizontal_motion: i32) -> bool {
132+
assert_eq!(self.blocks.len(), self.block_positions.len());
133+
134+
if horizontal_motion == 0 {
135+
return false;
136+
}
137+
138+
!is_touching_bound(
139+
self.blocks[block_id],
140+
self.block_positions[block_id],
141+
if horizontal_motion < 0 { Bound::LeftWall(0) } else { Bound::RightWall(self.board_width) },
142+
) && !is_touching_block(
143+
block_id,
144+
self.block_count,
145+
&self.blocks,
146+
&self.block_positions,
147+
(0, horizontal_motion),
109148
)
110149
}
111150

@@ -124,22 +163,29 @@ fn translate_cells(cells: &[Cell; 4], row_translation: i32, col_translation: i32
124163
translated_cells
125164
}
126165

127-
fn is_resting_on_floor(block: BlockType, block_pos: Cell, floor_pos: i32) -> bool {
128-
block_pos.0 + block.height() >= floor_pos
166+
fn is_touching_bound(block: BlockType, block_pos: Cell, bound: Bound) -> bool {
167+
match bound {
168+
Bound::Floor(floor) => block_pos.0 + block.height() >= floor,
169+
Bound::LeftWall(left) => block_pos.1 <= left,
170+
Bound::RightWall(right) => block_pos.1 + block.width() >= right,
171+
}
129172
}
130173

131-
fn is_resting_on_other_block(
174+
fn is_touching_block(
132175
block_id: usize,
133176
block_count: usize,
134177
blocks: &[BlockType],
135178
block_positions: &[Cell],
179+
touch_vector: (i32, i32)
136180
) -> bool {
137181
assert_eq!(blocks.len(), block_positions.len());
138182
assert!(blocks.len() >= block_count);
139183

140-
let block = blocks[block_id];
141-
let block_pos = block_positions[block_id];
142-
let block_cells = translate_cells(&block.cells(), block_pos.0, block_pos.1);
184+
let block_cells = translate_cells(
185+
&blocks[block_id].cells(),
186+
block_positions[block_id].0 + touch_vector.0,
187+
block_positions[block_id].1 + touch_vector.1,
188+
);
143189

144190
// Only need to check for collisions against blocks that were created before this block id
145191
// since all other blocks will always be higher up in the grid.
@@ -148,14 +194,15 @@ fn is_resting_on_other_block(
148194
continue;
149195
}
150196

151-
let other_block = blocks[other_block_id];
152-
let other_block_pos = block_positions[other_block_id];
153-
let other_block_cells =
154-
translate_cells(&other_block.cells(), other_block_pos.0, other_block_pos.1);
197+
let other_block_cells = translate_cells(
198+
&blocks[other_block_id].cells(),
199+
block_positions[other_block_id].0,
200+
block_positions[other_block_id].1,
201+
);
155202

156203
for cell in block_cells.iter() {
157204
for other_cell in other_block_cells.iter() {
158-
if (cell.1 == other_cell.1) && (cell.0 + 1 == other_cell.0) {
205+
if cell == other_cell {
159206
return true;
160207
}
161208
}

src/main.rs

+27-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fn setup_colors() {
3333
fn main() {
3434
let window = pancurses::initscr();
3535

36+
const INPUT_POLL_PERIOD: time::Duration = time::Duration::from_millis(125);
3637
const DEFAULT_GAME_TICK_PERIOD: time::Duration = time::Duration::from_millis(250);
3738
let mut game_tick_period = DEFAULT_GAME_TICK_PERIOD;
3839

@@ -43,27 +44,47 @@ fn main() {
4344
setup_colors();
4445

4546
let mut last_game_tick = time::Instant::now();
47+
let mut last_input_handled = time::Instant::now();
48+
4649
let mut game_state = GameState::new(
4750
window.get_max_x(),
4851
window.get_max_y(),
4952
ThreadRangeRng::new(),
5053
ThreadRangeRng::new(),
5154
);
5255

56+
let mut inputs = (false, false);
57+
5358
while !game_state.is_game_over() {
5459
// Input handling
5560
if let Some(pancurses::Input::Character(ch)) = window.getch() {
5661
match ch {
57-
// slowdown time scale
58-
'a' => game_tick_period *= 2,
59-
// reset time scale
60-
's' => game_tick_period = DEFAULT_GAME_TICK_PERIOD,
61-
// speed up time scale
62-
'd' => game_tick_period /= 2,
62+
// check for movement inputs
63+
'a' => inputs.0 = true, // move left
64+
'd' => inputs.1 = true, // move right
65+
66+
// debug
67+
'q' => break, // kill game early
68+
'z' => game_tick_period *= 2, // slowdown tick rate
69+
'x' => game_tick_period = DEFAULT_GAME_TICK_PERIOD, // reset tick rate
70+
'c' => game_tick_period /= 2, // speed up tick rate
6371
_ => (),
6472
}
6573
}
6674

75+
if last_input_handled.elapsed() >= INPUT_POLL_PERIOD {
76+
last_input_handled = time::Instant::now();
77+
let mut horizontal_motion: i32 = 0;
78+
if inputs.0 {
79+
horizontal_motion -= 1;
80+
}
81+
if inputs.1 {
82+
horizontal_motion += 1;
83+
}
84+
game_state.move_block_horizontal(horizontal_motion);
85+
inputs = (false, false);
86+
}
87+
6788
// Tick the game state
6889
if last_game_tick.elapsed() >= game_tick_period {
6990
last_game_tick = time::Instant::now();

0 commit comments

Comments
 (0)