|
|
@@ -1,12 +1,12 @@ |
|
|
|
use Config; |
|
|
|
use controller::Controller; |
|
|
|
use error; |
|
|
|
use geometry::Geometry; |
|
|
|
use log; |
|
|
|
use sen_colour::{Colour, Format}; |
|
|
|
use sprite::Sprite; |
|
|
|
use std::collections::HashMap; |
|
|
|
use units::*; |
|
|
|
use sprite::Sprite; |
|
|
|
use controller::Controller; |
|
|
|
use geometry::Geometry; |
|
|
|
use sen_colour::{Colour, Format}; |
|
|
|
use Config; |
|
|
|
|
|
|
|
#[derive(Clone, Copy)] |
|
|
|
pub struct Block { |
|
|
@@ -19,7 +19,7 @@ impl Block { |
|
|
|
pub fn new(col: Col) -> Block { |
|
|
|
Block { |
|
|
|
occupied: false, |
|
|
|
offset: Vec2D {x: 0.0, y: 0.0 }, |
|
|
|
offset: Vec2D { x: 0.0, y: 0.0 }, |
|
|
|
colour: col, |
|
|
|
} |
|
|
|
} |
|
|
@@ -42,10 +42,12 @@ pub struct Board { |
|
|
|
impl Board { |
|
|
|
pub fn new(config: &Config) -> Board { |
|
|
|
// width: usize, height: usize, inactive_colour: Col, crt_update_speed: f32 |
|
|
|
let inactive_colour = Col::new(config.block_inactive_col_r, |
|
|
|
config.block_inactive_col_g, |
|
|
|
config.block_inactive_col_b, |
|
|
|
config.block_inactive_col_a); |
|
|
|
let inactive_colour = Col::new( |
|
|
|
config.block_inactive_col_r, |
|
|
|
config.block_inactive_col_g, |
|
|
|
config.block_inactive_col_b, |
|
|
|
config.block_inactive_col_a, |
|
|
|
); |
|
|
|
let width = config.board_width; |
|
|
|
let height = config.board_height; |
|
|
|
let crt_update_speed = config.crt_update_speed; |
|
|
@@ -61,10 +63,12 @@ impl Board { |
|
|
|
|
|
|
|
pub fn reconfigure(&mut self, config: &Config) { |
|
|
|
// not updating board width/height in order to retain the current state of the board |
|
|
|
self.inactive_colour = Col::new(config.block_inactive_col_r, |
|
|
|
config.block_inactive_col_g, |
|
|
|
config.block_inactive_col_b, |
|
|
|
config.block_inactive_col_a); |
|
|
|
self.inactive_colour = Col::new( |
|
|
|
config.block_inactive_col_r, |
|
|
|
config.block_inactive_col_g, |
|
|
|
config.block_inactive_col_b, |
|
|
|
config.block_inactive_col_a, |
|
|
|
); |
|
|
|
self.crt_update_speed = config.crt_update_speed; |
|
|
|
} |
|
|
|
|
|
|
@@ -80,11 +84,10 @@ impl Board { |
|
|
|
|
|
|
|
pub fn remove_line(&mut self, line_number: usize, inactive_colour: &Col) -> error::Result<()> { |
|
|
|
if line_number >= self.height { |
|
|
|
return Err(error::TetrisError::LineRemoval) |
|
|
|
return Err(error::TetrisError::LineRemoval); |
|
|
|
} |
|
|
|
|
|
|
|
for line in line_number..(self.height-1) { |
|
|
|
|
|
|
|
for line in line_number..(self.height - 1) { |
|
|
|
// copy top_line onto line |
|
|
|
// |
|
|
|
let top_line = line + 1; |
|
|
@@ -146,7 +149,7 @@ pub struct Game { |
|
|
|
board_offset: Block2D, |
|
|
|
|
|
|
|
shapes: HashMap<ShapeKind, Shape>, |
|
|
|
piece: Piece, // the current piece under player control |
|
|
|
piece: Piece, // the current piece under player control |
|
|
|
next_piece_shape: ShapeKind, |
|
|
|
next_piece_colour: Col, |
|
|
|
|
|
|
@@ -163,7 +166,7 @@ pub struct Game { |
|
|
|
time_to_next_lowering: f32, // ms |
|
|
|
piece_is_dropping: bool, |
|
|
|
|
|
|
|
default_cooldown: f32, // ms |
|
|
|
default_cooldown: f32, // ms |
|
|
|
left_cooldown: f32, |
|
|
|
right_cooldown: f32, |
|
|
|
up_cooldown: f32, |
|
|
@@ -184,15 +187,15 @@ impl Game { |
|
|
|
pub fn new(config: &Config) -> Game { |
|
|
|
Game { |
|
|
|
board: Board::new(config), |
|
|
|
board_offset: Block2D { x: config.board_offset_x, y: config.board_offset_y }, |
|
|
|
board_offset: Block2D { |
|
|
|
x: config.board_offset_x, |
|
|
|
y: config.board_offset_y, |
|
|
|
}, |
|
|
|
|
|
|
|
shapes: define_shapes(), |
|
|
|
piece: Piece { |
|
|
|
shape_kind: ShapeKind::J, |
|
|
|
pos: BoardPos { |
|
|
|
x: 5, |
|
|
|
y: 19, |
|
|
|
}, |
|
|
|
pos: BoardPos { x: 5, y: 19 }, |
|
|
|
angle: PieceAngle::R0, |
|
|
|
colour: Col::new(0.4, 0.8, 0.2, 0.9), |
|
|
|
}, |
|
|
@@ -208,7 +211,7 @@ impl Game { |
|
|
|
level: 0, |
|
|
|
lines: 0, |
|
|
|
|
|
|
|
tetris: false, // was the last scoring move a tetris? |
|
|
|
tetris: false, // was the last scoring move a tetris? |
|
|
|
|
|
|
|
time_to_next_lowering: get_time_to_next_lowering(0), |
|
|
|
piece_is_dropping: false, |
|
|
@@ -244,9 +247,11 @@ impl Game { |
|
|
|
|
|
|
|
pub fn init(&mut self, random: f32) -> error::Result<()> { |
|
|
|
self.next_piece_shape = get_random_shape(1.0 - random)?; |
|
|
|
self.next_piece_colour = get_colour_from_hsl(((1.0 - random) * 360.0) as f64, |
|
|
|
self.colour_saturation, |
|
|
|
self.colour_lightness)?; |
|
|
|
self.next_piece_colour = get_colour_from_hsl( |
|
|
|
((1.0 - random) * 360.0) as f64, |
|
|
|
self.colour_saturation, |
|
|
|
self.colour_lightness, |
|
|
|
)?; |
|
|
|
|
|
|
|
use_next_shape(self, random)?; |
|
|
|
Ok(()) |
|
|
@@ -262,26 +267,25 @@ impl Game { |
|
|
|
let x = self.board_offset.x + 11; |
|
|
|
let y = self.board_offset.y + 3; |
|
|
|
|
|
|
|
if let Ok(c) = get_colour_from_hsl(0.0, |
|
|
|
self.colour_saturation, |
|
|
|
self.colour_lightness) { |
|
|
|
if let Ok(c) = get_colour_from_hsl(0.0, self.colour_saturation, self.colour_lightness) { |
|
|
|
render_score(geometry, self.score, Block2D { x, y: y + 14 }, c); |
|
|
|
render_level(geometry, self.level, Block2D { x, y: y + 10 }, c); |
|
|
|
render_lines(geometry, self.lines, Block2D { x, y: y + 6 }, c); |
|
|
|
} |
|
|
|
if let Some(shape) = self.shapes.get(&self.next_piece_shape) { |
|
|
|
render_next_shape(geometry, |
|
|
|
&shape, |
|
|
|
Block2D { x, y }, |
|
|
|
self.next_piece_colour); |
|
|
|
render_next_shape(geometry, &shape, Block2D { x, y }, self.next_piece_colour); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// counting on the fact that delta will always be a 'sensible' value. |
|
|
|
// so if the player has paused the game for a while, on the first resumed call to tick_playing |
|
|
|
// delta should be 0 rather than a large multi-second value |
|
|
|
pub fn tick_playing(&mut self, controller: &Controller, delta: f32, random: f32) -> error::Result<bool> { |
|
|
|
|
|
|
|
pub fn tick_playing( |
|
|
|
&mut self, |
|
|
|
controller: &Controller, |
|
|
|
delta: f32, |
|
|
|
random: f32, |
|
|
|
) -> error::Result<bool> { |
|
|
|
if self.is_game_over { |
|
|
|
log("game over"); |
|
|
|
return Ok(false); |
|
|
@@ -304,7 +308,6 @@ impl Game { |
|
|
|
} |
|
|
|
} |
|
|
|
} else if is_time_to_lower_piece(self, delta) { |
|
|
|
|
|
|
|
let mut piece_cant_go_lower = false; |
|
|
|
|
|
|
|
if let Some(shape) = self.shapes.get(&self.piece.shape_kind) { |
|
|
@@ -314,7 +317,13 @@ impl Game { |
|
|
|
if piece_cant_go_lower { |
|
|
|
let mut num_lines = 0; |
|
|
|
if let Some(shape) = self.shapes.get(&self.piece.shape_kind) { |
|
|
|
add_shape_to_board(&mut self.board, shape, &self.piece.pos, &self.piece.angle, &self.piece.colour); |
|
|
|
add_shape_to_board( |
|
|
|
&mut self.board, |
|
|
|
shape, |
|
|
|
&self.piece.pos, |
|
|
|
&self.piece.angle, |
|
|
|
&self.piece.colour, |
|
|
|
); |
|
|
|
num_lines = remove_complete_lines(&mut self.board)?; |
|
|
|
} |
|
|
|
|
|
|
@@ -337,7 +346,13 @@ impl Game { |
|
|
|
} |
|
|
|
|
|
|
|
if let Some(shape) = self.shapes.get(&self.piece.shape_kind) { |
|
|
|
add_shape_to_board(&mut self.board, shape, &self.piece.pos, &self.piece.angle, &self.piece.colour); |
|
|
|
add_shape_to_board( |
|
|
|
&mut self.board, |
|
|
|
shape, |
|
|
|
&self.piece.pos, |
|
|
|
&self.piece.angle, |
|
|
|
&self.piece.colour, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// update the colour of all the blocks that aren't occupied |
|
|
@@ -361,7 +376,11 @@ fn score_for_current_move(game: &Game, num_lines: i32) -> i32 { |
|
|
|
1 => 100, |
|
|
|
2 => 300, |
|
|
|
3 => 500, |
|
|
|
4 => if game.tetris { 1200 } else { 800 }, |
|
|
|
4 => if game.tetris { |
|
|
|
1200 |
|
|
|
} else { |
|
|
|
800 |
|
|
|
}, |
|
|
|
_ => 0, |
|
|
|
} |
|
|
|
} |
|
|
@@ -396,9 +415,11 @@ fn use_next_shape(game: &mut Game, random: f32) -> error::Result<()> { |
|
|
|
} |
|
|
|
|
|
|
|
game.next_piece_shape = get_random_shape(random)?; |
|
|
|
game.next_piece_colour = get_colour_from_hsl((random * 360.0) as f64, |
|
|
|
game.colour_saturation, |
|
|
|
game.colour_lightness)?; |
|
|
|
game.next_piece_colour = get_colour_from_hsl( |
|
|
|
(random * 360.0) as f64, |
|
|
|
game.colour_saturation, |
|
|
|
game.colour_lightness, |
|
|
|
)?; |
|
|
|
|
|
|
|
Ok(()) |
|
|
|
} |
|
|
@@ -411,10 +432,8 @@ fn get_colour_from_hsl(hue: f64, saturation: f64, lightness: f64) -> error::Resu |
|
|
|
fn col_from_colour(colour: &Colour) -> error::Result<Col> { |
|
|
|
if let Ok(rgb) = colour.clone_as(Format::RGB) { |
|
|
|
match rgb { |
|
|
|
Colour::RGB(r, g, b, a) => { |
|
|
|
Ok(Col::new(r as f32, g as f32, b as f32, a as f32)) |
|
|
|
}, |
|
|
|
_ => Err(error::TetrisError::InvalidColourFormat) |
|
|
|
Colour::RGB(r, g, b, a) => Ok(Col::new(r as f32, g as f32, b as f32, a as f32)), |
|
|
|
_ => Err(error::TetrisError::InvalidColourFormat), |
|
|
|
} |
|
|
|
} else { |
|
|
|
Err(error::TetrisError::InvalidColourFormat) |
|
|
@@ -448,7 +467,10 @@ fn is_time_to_lower_piece(game: &mut Game, delta: f32) -> bool { |
|
|
|
// if possible vertically drop the piece by one row, otherwise return false |
|
|
|
// |
|
|
|
fn lower_piece(board: &Board, shape: &Shape, piece: &mut Piece) -> bool { |
|
|
|
let new_position = BoardPos { x: piece.pos.x, y: piece.pos.y - 1 }; |
|
|
|
let new_position = BoardPos { |
|
|
|
x: piece.pos.x, |
|
|
|
y: piece.pos.y - 1, |
|
|
|
}; |
|
|
|
|
|
|
|
if is_allowed(board, shape, &new_position, &piece.angle) { |
|
|
|
piece.pos = new_position; |
|
|
@@ -460,22 +482,42 @@ fn lower_piece(board: &Board, shape: &Shape, piece: &mut Piece) -> bool { |
|
|
|
|
|
|
|
fn apply_user_input(game: &mut Game, controller: &Controller, delta: f32) { |
|
|
|
if let Some(shape) = game.shapes.get(&game.piece.shape_kind) { |
|
|
|
|
|
|
|
if can_apply_input(&mut game.left_cooldown, controller.left, game.default_cooldown, delta) { |
|
|
|
let new_position = BoardPos { x: game.piece.pos.x - 1, y: game.piece.pos.y }; |
|
|
|
if can_apply_input( |
|
|
|
&mut game.left_cooldown, |
|
|
|
controller.left, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
let new_position = BoardPos { |
|
|
|
x: game.piece.pos.x - 1, |
|
|
|
y: game.piece.pos.y, |
|
|
|
}; |
|
|
|
if is_allowed(&game.board, shape, &new_position, &game.piece.angle) { |
|
|
|
game.piece.pos = new_position; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if can_apply_input(&mut game.right_cooldown, controller.right, game.default_cooldown, delta) { |
|
|
|
let new_position = BoardPos { x: game.piece.pos.x + 1, y: game.piece.pos.y }; |
|
|
|
if can_apply_input( |
|
|
|
&mut game.right_cooldown, |
|
|
|
controller.right, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
let new_position = BoardPos { |
|
|
|
x: game.piece.pos.x + 1, |
|
|
|
y: game.piece.pos.y, |
|
|
|
}; |
|
|
|
if is_allowed(&game.board, shape, &new_position, &game.piece.angle) { |
|
|
|
game.piece.pos = new_position; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if can_apply_input(&mut game.up_cooldown, controller.up, game.default_cooldown, delta) { |
|
|
|
if can_apply_input( |
|
|
|
&mut game.up_cooldown, |
|
|
|
controller.up, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
let new_angle; |
|
|
|
if shape.fully_rotate { |
|
|
|
new_angle = match game.piece.angle { |
|
|
@@ -496,11 +538,21 @@ fn apply_user_input(game: &mut Game, controller: &Controller, delta: f32) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if can_apply_input(&mut game.down_cooldown, controller.down, game.default_cooldown, delta) { |
|
|
|
if can_apply_input( |
|
|
|
&mut game.down_cooldown, |
|
|
|
controller.down, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
game.piece_is_dropping = true; |
|
|
|
} |
|
|
|
|
|
|
|
if can_apply_input(&mut game.a_cooldown, controller.a, game.default_cooldown, delta) { |
|
|
|
if can_apply_input( |
|
|
|
&mut game.a_cooldown, |
|
|
|
controller.a, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
let new_angle; |
|
|
|
if shape.fully_rotate { |
|
|
|
new_angle = match game.piece.angle { |
|
|
@@ -521,7 +573,12 @@ fn apply_user_input(game: &mut Game, controller: &Controller, delta: f32) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if can_apply_input(&mut game.b_cooldown, controller.b, game.default_cooldown, delta) { |
|
|
|
if can_apply_input( |
|
|
|
&mut game.b_cooldown, |
|
|
|
controller.b, |
|
|
|
game.default_cooldown, |
|
|
|
delta, |
|
|
|
) { |
|
|
|
let new_angle; |
|
|
|
if shape.fully_rotate { |
|
|
|
new_angle = match game.piece.angle { |
|
|
@@ -605,7 +662,13 @@ fn remove_complete_lines(board: &mut Board) -> error::Result<i32> { |
|
|
|
Ok(offset as i32) |
|
|
|
} |
|
|
|
|
|
|
|
fn add_shape_to_board(board: &mut Board, shape: &Shape, pos: &BoardPos, angle: &PieceAngle, colour: &Col) { |
|
|
|
fn add_shape_to_board( |
|
|
|
board: &mut Board, |
|
|
|
shape: &Shape, |
|
|
|
pos: &BoardPos, |
|
|
|
angle: &PieceAngle, |
|
|
|
colour: &Col, |
|
|
|
) { |
|
|
|
for pos in get_board_positions(shape, pos, angle) { |
|
|
|
if is_valid_board_position(board, &pos) { |
|
|
|
set_block(board, pos.x, pos.y, true, colour); |
|
|
@@ -637,7 +700,6 @@ fn get_board_positions(shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> Vec |
|
|
|
for y_offset in 0..shape.aabb_height { |
|
|
|
for x_offset in 0..shape.aabb_width { |
|
|
|
if shape.form[y_offset as usize][x_offset as usize] { |
|
|
|
|
|
|
|
match angle { |
|
|
|
PieceAngle::R0 => { |
|
|
|
x = x_offset; |
|
|
@@ -646,7 +708,7 @@ fn get_board_positions(shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> Vec |
|
|
|
PieceAngle::R90 => { |
|
|
|
x = y_offset; |
|
|
|
y = shape.aabb_width - x_offset - 1; |
|
|
|
}, |
|
|
|
} |
|
|
|
PieceAngle::R180 => { |
|
|
|
x = shape.aabb_width - x_offset - 1; |
|
|
|
y = shape.aabb_height - y_offset - 1; |
|
|
@@ -657,7 +719,10 @@ fn get_board_positions(shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> Vec |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
res.push(BoardPos {x: pos.x + x, y: pos.y + y}); |
|
|
|
res.push(BoardPos { |
|
|
|
x: pos.x + x, |
|
|
|
y: pos.y + y, |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@@ -678,7 +743,11 @@ fn update_block_colours(board: &mut Board) { |
|
|
|
for mut row in &mut board.board { |
|
|
|
for mut block in row { |
|
|
|
if !block.occupied { |
|
|
|
block.colour = move_col_closer(&block.colour, &board.inactive_colour, board.crt_update_speed); |
|
|
|
block.colour = move_col_closer( |
|
|
|
&block.colour, |
|
|
|
&board.inactive_colour, |
|
|
|
board.crt_update_speed, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@@ -715,10 +784,15 @@ fn render_board(geometry: &mut Geometry, board: &Board, board_offset: &Block2D) |
|
|
|
|
|
|
|
for row in &board.board { |
|
|
|
for block in row { |
|
|
|
geometry.push_sprite(Sprite::Block, |
|
|
|
block.colour, |
|
|
|
Block2D{ x: x + board_offset.x, y: y + board_offset.y }, |
|
|
|
block.offset); |
|
|
|
geometry.push_sprite( |
|
|
|
Sprite::Block, |
|
|
|
block.colour, |
|
|
|
Block2D { |
|
|
|
x: x + board_offset.x, |
|
|
|
y: y + board_offset.y, |
|
|
|
}, |
|
|
|
block.offset, |
|
|
|
); |
|
|
|
x += 1; |
|
|
|
} |
|
|
|
x = 0; |
|
|
@@ -728,33 +802,58 @@ fn render_board(geometry: &mut Geometry, board: &Board, board_offset: &Block2D) |
|
|
|
|
|
|
|
fn render_score(geometry: &mut Geometry, score: i32, offset: Block2D, colour: Col) { |
|
|
|
geometry.push_text("SCORE", offset, colour); |
|
|
|
geometry.push_text(&score.to_string(), Block2D { x: offset.x, y: offset.y - 1 }, colour); |
|
|
|
geometry.push_text( |
|
|
|
&score.to_string(), |
|
|
|
Block2D { |
|
|
|
x: offset.x, |
|
|
|
y: offset.y - 1, |
|
|
|
}, |
|
|
|
colour, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
fn render_level(geometry: &mut Geometry, level: i32, offset: Block2D, colour: Col) { |
|
|
|
geometry.push_text("LEVEL", offset, colour); |
|
|
|
geometry.push_text(&level.to_string(), Block2D { x: offset.x, y: offset.y - 1 }, colour); |
|
|
|
geometry.push_text( |
|
|
|
&level.to_string(), |
|
|
|
Block2D { |
|
|
|
x: offset.x, |
|
|
|
y: offset.y - 1, |
|
|
|
}, |
|
|
|
colour, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
fn render_lines(geometry: &mut Geometry, lines: i32, offset: Block2D, colour: Col) { |
|
|
|
geometry.push_text("LINES", offset, colour); |
|
|
|
geometry.push_text(&lines.to_string(), Block2D { x: offset.x, y: offset.y - 1 }, colour); |
|
|
|
geometry.push_text( |
|
|
|
&lines.to_string(), |
|
|
|
Block2D { |
|
|
|
x: offset.x, |
|
|
|
y: offset.y - 1, |
|
|
|
}, |
|
|
|
colour, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
fn render_next_shape(geometry: &mut Geometry, shape: &Shape, offset: Block2D, colour: Col) { |
|
|
|
let board_positions = get_board_positions(shape, &BoardPos { x: 0, y: 0}, &PieceAngle::R0); |
|
|
|
let board_positions = get_board_positions(shape, &BoardPos { x: 0, y: 0 }, &PieceAngle::R0); |
|
|
|
for pos in board_positions { |
|
|
|
geometry.push_sprite(Sprite::Block, |
|
|
|
colour, |
|
|
|
Block2D {x: pos.x + offset.x, y: pos.y + offset.y}, |
|
|
|
Vec2D {x: 0.0, y: 0.0 }); |
|
|
|
geometry.push_sprite( |
|
|
|
Sprite::Block, |
|
|
|
colour, |
|
|
|
Block2D { |
|
|
|
x: pos.x + offset.x, |
|
|
|
y: pos.y + offset.y, |
|
|
|
}, |
|
|
|
Vec2D { x: 0.0, y: 0.0 }, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn define_shapes() -> HashMap<ShapeKind, Shape> { |
|
|
|
let mut res = HashMap::new(); |
|
|
|
|
|
|
|
|
|
|
|
// origin is in the bottom left |
|
|
|
// indices are in [y][x] order |
|
|
|
// |