Skip to content

Commit 44bbeee

Browse files
authored
Constrain window+gameover blink (#1)
* cargo ftm * made board offsetable * did a lil cleanup upon revisiting my old code * drawing tetris frame * oops I made the board too big but now its fixed * added blinky game over screen * updated readme with stubs to fill in soon * added gif
1 parent b7dba83 commit 44bbeee

File tree

5 files changed

+182
-39
lines changed

5 files changed

+182
-39
lines changed
23.8 KB
Loading

readme.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
## Intent
44

5-
- Attempt to make use of data-oriented design
6-
- Attempt to make use entity systems
75
- Practice rust
86
- Use a TUI (cuz tuis are cool)
97

@@ -59,7 +57,10 @@
5957

6058
![Image of left-right collision](demo/7-lr-collision.gif)
6159

62-
- [ ] Constrain board size
60+
- [X] [Constrain board size and add game over screen](https://github.com/scottnm/tetrust/commit/FILL-ME-IN)
61+
62+
![Image of constrained board with game over screen](demo/8-constrained-gameover-blink.gif)
63+
6364
- [ ] Allow tetrominos to rotate
6465
- [ ] Allow clearing lines
6566
- [ ] Speed up pieces falling as more lines are cleared

src/game.rs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ where
1919
TBlockTypeRand: RangeRng<usize>,
2020
TBlockPosRand: RangeRng<i32>,
2121
{
22+
board_pos_x: i32,
23+
board_pos_y: i32,
2224
board_width: i32,
2325
board_height: i32,
2426
block_type_rng: TBlockTypeRand,
@@ -35,17 +37,21 @@ where
3537
TBlockPosRand: RangeRng<i32>,
3638
{
3739
pub fn new(
40+
board_pos_x: i32,
41+
board_pos_y: i32,
3842
board_width: i32,
3943
board_height: i32,
4044
block_type_rng: TBlockTypeRand,
4145
block_pos_rng: TBlockPosRand,
4246
) -> GameState<TBlockTypeRand, TBlockPosRand> {
4347
let max_blocks = (board_width * board_height) as usize;
4448
GameState {
45-
board_width: board_width,
46-
board_height: board_height,
47-
block_type_rng: block_type_rng,
48-
block_pos_rng: block_pos_rng,
49+
board_pos_x,
50+
board_pos_y,
51+
board_width,
52+
board_height,
53+
block_type_rng,
54+
block_pos_rng,
4955
block_count: 0,
5056
blocks: (vec![BlockType::I; max_blocks]).into_boxed_slice(),
5157
block_positions: (vec![Cell(0, 0); max_blocks]).into_boxed_slice(),
@@ -55,26 +61,32 @@ where
5561

5662
pub fn tick(&mut self) {
5763
match self.game_phase {
64+
// Add a new block to the top of the board
5865
GamePhase::GenerateBlock => {
5966
assert_eq!(self.blocks.len(), self.block_positions.len());
6067
assert!(self.block_count < self.blocks.len());
6168

6269
let new_block = BlockType::random(&mut self.block_type_rng);
63-
let start_col: i32 = self
64-
.block_pos_rng
65-
.gen_range(0, self.board_width - new_block.width());
70+
let start_col: i32 = self.block_pos_rng.gen_range(
71+
self.board_pos_x,
72+
self.board_pos_x + self.board_width - new_block.width(),
73+
);
74+
let start_row: i32 = self.board_pos_y - new_block.height();
6675

6776
self.blocks[self.block_count] = new_block;
68-
self.block_positions[self.block_count] = Cell(-new_block.height(), start_col);
77+
self.block_positions[self.block_count] = Cell(start_row, start_col);
6978
self.block_count += 1;
7079
self.game_phase = GamePhase::MoveBlock;
7180
}
7281

82+
// Move the latest block down across the board
7383
GamePhase::MoveBlock => {
74-
let moving_block_id = self.block_count - 1; // we are always moving the last block
84+
// we are always moving the last block
85+
let moving_block_id = self.block_count - 1;
7586

7687
if self.has_block_landed(moving_block_id) {
77-
self.game_phase = if self.block_positions[moving_block_id].0 < 0 {
88+
let is_block_oob = self.block_positions[moving_block_id].0 < self.board_pos_y;
89+
self.game_phase = if is_block_oob {
7890
GamePhase::GameOver
7991
} else {
8092
GamePhase::GenerateBlock
@@ -84,6 +96,7 @@ where
8496
}
8597
}
8698

99+
// The game is over; NOOP
87100
GamePhase::GameOver => (),
88101
}
89102
}
@@ -95,7 +108,7 @@ where
95108
if self.can_block_move(moving_block_id, horizontal_motion) {
96109
self.block_positions[moving_block_id].1 += horizontal_motion;
97110
}
98-
},
111+
}
99112
GamePhase::GenerateBlock | GamePhase::GameOver => (),
100113
}
101114
}
@@ -115,17 +128,25 @@ where
115128
pub fn has_block_landed(&self, block_id: usize) -> bool {
116129
assert_eq!(self.blocks.len(), self.block_positions.len());
117130

118-
is_touching_bound(
131+
let is_touching_floor = is_touching_bound(
119132
self.blocks[block_id],
120133
self.block_positions[block_id],
121-
Bound::Floor(self.board_height),
122-
) || is_touching_block(
134+
Bound::Floor(self.board_pos_y + self.board_height),
135+
);
136+
137+
if is_touching_floor {
138+
return true;
139+
}
140+
141+
let is_touching_block_below = is_touching_block(
123142
block_id,
124143
self.block_count,
125144
&self.blocks,
126145
&self.block_positions,
127146
(1, 0),
128-
)
147+
);
148+
149+
is_touching_block_below
129150
}
130151

131152
pub fn can_block_move(&self, block_id: usize, horizontal_motion: i32) -> bool {
@@ -135,17 +156,31 @@ where
135156
return false;
136157
}
137158

138-
!is_touching_bound(
159+
let wall_to_check = if horizontal_motion < 0 {
160+
Bound::LeftWall(self.board_pos_x - 1)
161+
} else {
162+
Bound::RightWall(self.board_pos_x + self.board_width)
163+
};
164+
165+
let is_touching_wall = is_touching_bound(
139166
self.blocks[block_id],
140167
self.block_positions[block_id],
141-
if horizontal_motion < 0 { Bound::LeftWall(0) } else { Bound::RightWall(self.board_width) },
142-
) && !is_touching_block(
168+
wall_to_check,
169+
);
170+
171+
if is_touching_wall {
172+
return false;
173+
}
174+
175+
let is_touching_block_side = is_touching_block(
143176
block_id,
144177
self.block_count,
145178
&self.blocks,
146179
&self.block_positions,
147180
(0, horizontal_motion),
148-
)
181+
);
182+
183+
!is_touching_block_side
149184
}
150185

151186
pub fn is_game_over(&self) -> bool {
@@ -166,7 +201,7 @@ fn translate_cells(cells: &[Cell; 4], row_translation: i32, col_translation: i32
166201
fn is_touching_bound(block: BlockType, block_pos: Cell, bound: Bound) -> bool {
167202
match bound {
168203
Bound::Floor(floor) => block_pos.0 + block.height() >= floor,
169-
Bound::LeftWall(left) => block_pos.1 <= left,
204+
Bound::LeftWall(left) => block_pos.1 <= left + 1,
170205
Bound::RightWall(right) => block_pos.1 + block.width() >= right,
171206
}
172207
}
@@ -176,7 +211,7 @@ fn is_touching_block(
176211
block_count: usize,
177212
blocks: &[BlockType],
178213
block_positions: &[Cell],
179-
touch_vector: (i32, i32)
214+
touch_vector: (i32, i32),
180215
) -> bool {
181216
assert_eq!(blocks.len(), block_positions.len());
182217
assert!(blocks.len() >= block_count);

src/main.rs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,46 @@ fn setup_colors() {
3030
}
3131
}
3232

33+
fn draw_frame(window: &pancurses::Window, left: i32, width: i32, top: i32, height: i32) {
34+
let right = left + width - 1;
35+
let bottom = top + height - 1;
36+
assert!(left < right);
37+
assert!(top < bottom);
38+
39+
// draw corners
40+
window.mvaddch(top, left, pancurses::ACS_ULCORNER());
41+
window.mvaddch(top, right, pancurses::ACS_URCORNER());
42+
window.mvaddch(bottom, left, pancurses::ACS_LLCORNER());
43+
window.mvaddch(bottom, right, pancurses::ACS_LRCORNER());
44+
45+
// draw horizontal borders
46+
for col in left + 1..right {
47+
window.mvaddch(top, col, pancurses::ACS_HLINE());
48+
window.mvaddch(bottom, col, pancurses::ACS_HLINE());
49+
}
50+
51+
// draw vertical borders
52+
for row in top + 1..bottom {
53+
window.mvaddch(row, left, pancurses::ACS_VLINE());
54+
window.mvaddch(row, right, pancurses::ACS_VLINE());
55+
}
56+
}
57+
58+
fn draw_text_centered<S>(window: &pancurses::Window, text: S, x_center: i32, y_center: i32)
59+
where
60+
S: AsRef<str>,
61+
{
62+
window.mvaddstr(y_center, x_center - (text.as_ref().len() / 2) as i32, text);
63+
}
64+
3365
fn main() {
3466
let window = pancurses::initscr();
3567

68+
const BOARD_X_OFFSET: i32 = 1;
69+
const BOARD_Y_OFFSET: i32 = 1;
70+
const BOARD_DIM_WIDTH: i32 = 10;
71+
const BOARD_DIM_HEIGHT: i32 = 20;
72+
3673
const INPUT_POLL_PERIOD: time::Duration = time::Duration::from_millis(125);
3774
const DEFAULT_GAME_TICK_PERIOD: time::Duration = time::Duration::from_millis(250);
3875
let mut game_tick_period = DEFAULT_GAME_TICK_PERIOD;
@@ -47,15 +84,18 @@ fn main() {
4784
let mut last_input_handled = time::Instant::now();
4885

4986
let mut game_state = GameState::new(
50-
window.get_max_x(),
51-
window.get_max_y(),
87+
BOARD_X_OFFSET,
88+
BOARD_Y_OFFSET,
89+
BOARD_DIM_WIDTH,
90+
BOARD_DIM_HEIGHT,
5291
ThreadRangeRng::new(),
5392
ThreadRangeRng::new(),
5493
);
5594

5695
let mut inputs = (false, false);
96+
let mut game_over_blit_timer = Option::<time::Instant>::None;
5797

58-
while !game_state.is_game_over() {
98+
loop {
5999
// Input handling
60100
if let Some(pancurses::Input::Character(ch)) = window.getch() {
61101
match ch {
@@ -64,10 +104,10 @@ fn main() {
64104
'd' => inputs.1 = true, // move right
65105

66106
// debug
67-
'q' => break, // kill game early
68-
'z' => game_tick_period *= 2, // slowdown tick rate
107+
'q' => break, // kill game early
108+
'z' => game_tick_period *= 2, // slowdown tick rate
69109
'x' => game_tick_period = DEFAULT_GAME_TICK_PERIOD, // reset tick rate
70-
'c' => game_tick_period /= 2, // speed up tick rate
110+
'c' => game_tick_period /= 2, // speed up tick rate
71111
_ => (),
72112
}
73113
}
@@ -93,10 +133,44 @@ fn main() {
93133

94134
// Render the frame
95135
window.erase();
136+
137+
draw_frame(
138+
&window,
139+
BOARD_X_OFFSET - 1,
140+
BOARD_DIM_WIDTH + 2,
141+
BOARD_Y_OFFSET - 1,
142+
BOARD_DIM_HEIGHT + 2,
143+
);
144+
96145
for block_id in 0..game_state.block_count() {
97146
let (position, block) = game_state.block(block_id);
98147
render_block(&window, position, block);
99148
}
149+
150+
if game_state.is_game_over() {
151+
const GAME_OVER_DURATION: time::Duration = time::Duration::from_secs(3);
152+
match game_over_blit_timer {
153+
None => game_over_blit_timer = Some(time::Instant::now()),
154+
Some(timer) => {
155+
if timer.elapsed() > GAME_OVER_DURATION {
156+
break;
157+
}
158+
}
159+
}
160+
161+
const GAME_OVER_TEXT_X_CENTER: i32 = BOARD_X_OFFSET + BOARD_DIM_WIDTH / 2;
162+
const GAME_OVER_TEXT_Y_CENTER: i32 = BOARD_Y_OFFSET + BOARD_DIM_HEIGHT / 2;
163+
164+
window.attron(pancurses::A_BLINK);
165+
draw_text_centered(
166+
&window,
167+
"Game Over",
168+
GAME_OVER_TEXT_X_CENTER,
169+
GAME_OVER_TEXT_Y_CENTER,
170+
);
171+
window.attroff(pancurses::A_BLINK);
172+
}
173+
100174
window.refresh();
101175
}
102176

0 commit comments

Comments
 (0)