Skip to content

Tetris example #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4d7e230
drawing tetrominos
sajattack Oct 8, 2020
a65be8c
WIP all blue
sajattack Oct 8, 2020
fd42324
cache fix and cleanup
sajattack Oct 8, 2020
d4f391a
more cleanup
sajattack Oct 8, 2020
c203e4b
simplify tetromino struct and add rotation
sajattack Oct 11, 2020
b67a8b5
minor changes
sajattack Oct 11, 2020
be4f76e
grid test
sajattack Oct 11, 2020
63f5846
add todo files
sajattack Oct 11, 2020
3f97f64
700fps performance boost
sajattack Oct 12, 2020
a5aaf0b
function to convert tetromino to vertices
sajattack Oct 13, 2020
bb65740
gameboard
sajattack Oct 14, 2020
58421ac
playable tetris
sajattack Oct 15, 2020
09fa84a
fix rng
sajattack Oct 15, 2020
b4c61ac
enable home button
sajattack Oct 15, 2020
c57ac7d
better background hack
sajattack Oct 15, 2020
42863d3
hardcode background vertices
sajattack Oct 15, 2020
354a5e5
make the background semi-transparent
sajattack Oct 15, 2020
e8aaeec
rotation correction
sajattack Oct 15, 2020
1fec8ac
remove unused file
sajattack Oct 15, 2020
a7052d0
cleanup background draw code a bit
sajattack Oct 15, 2020
89d7ea1
loop timing and hard drop
sajattack Oct 15, 2020
78e5eb0
clean up/silence warnings
sajattack Oct 15, 2020
2ff517c
rebase changes to gu blending
sajattack Oct 16, 2020
011ffc0
remove outdated comment
sajattack Oct 16, 2020
085f200
remove unused features
sajattack Oct 16, 2020
98d1a30
improve controls
sajattack Oct 16, 2020
3680639
method refactor
sajattack Oct 16, 2020
697073f
add music
sajattack Oct 17, 2020
780b4e6
document and refactor
sajattack Oct 18, 2020
84f29e7
move the block sprite out of the graphics module into main.rs
sajattack Oct 18, 2020
d0d6e99
minor cleanups
sajattack Oct 18, 2020
e968f16
force vram_alloc to return 16-byte aligned VramMemChunks, fix alignme…
sajattack Oct 18, 2020
63076c5
fix vram_alloc test
sajattack Oct 18, 2020
ee8cfe9
remove Box
sajattack Oct 18, 2020
aaf9952
cleanup docs and sprite stuff
sajattack Oct 18, 2020
02d123b
reduce scope of unsafe
sajattack Oct 20, 2020
83b3f48
rebase and fix
sajattack Nov 29, 2021
9ac3c4b
rebase and fix
sajattack Jul 11, 2022
4b14fe2
undo changes to allocator tests
sajattack Jul 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/tetris/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
14 changes: 14 additions & 0 deletions examples/tetris/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "rust-psp-tetris"
version = "0.1.0"
authors = ["Paul Sajna <[email protected]>"]
edition = "2018"

[dependencies]
psp = { path = "../../psp" }
rand = { version = "0.7", default-features=false }
rand_chacha = { version = "0.2", default-features=false }

[profile.release]
opt-level = 3
lto = true
1 change: 1 addition & 0 deletions examples/tetris/assets/block.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
������������������������������������������������������������aaa���������������������������������������������������������|||�BBB���������������������������������������������������������PPP�BBB���������������������������������������������������������PPP�BBB���������������������������������������������������������PPP�BBB���������������������������������������������������������PPP�BBB���������������������������������������������������������PPP�BBB���������������������������������������������������������PPP�BBB�������������������������������������������������zzz�����PPP�BBB���������������������������������������������zzz���������PPP�BBB�����������������������������������������zzz�������������PPP�666�������������������������������������zzz�����������������999�&&&���������������������������������zzz�������������zzz�qqq�///�&&&�������������������������������������������������qqq�qqq�///�&&&�����|||�PPP�PPP�PPP�PPP�PPP�PPP�PPP�PPP�PPP�999�///�///�///�&&&�aaa�BBB�BBB�BBB�BBB�BBB�BBB�BBB�BBB�BBB�222�&&&�&&&�&&&�&&&� �
Binary file added examples/tetris/assets/block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/tetris/assets/tetris.pcm.raw
Binary file not shown.
49 changes: 49 additions & 0 deletions examples/tetris/src/audio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use psp::sys;

use crate::TETRIS_SONG;

const MAX_VOL: i32 = 0x8000;
pub const MAX_SAMPLES: usize = 65472;

/// Called once per loop of the game, handles audio.
///
/// # Parameters
/// - `channel`: An audio channel initialized by `sceAudioChReserve`
/// - `start_pos`: The starting position from which to play audio
/// - `restlen`: How much audio remains to be played
///
/// # Return Value
///
/// `(restlen, start_pos)`
pub fn process_audio_loop(channel: i32, mut start_pos: usize, mut restlen: i32) -> (i32, usize) {
unsafe {
if (start_pos+MAX_SAMPLES*2) < TETRIS_SONG.len() {
if restlen == 0 {
sys::sceAudioOutput(
channel,
MAX_VOL,
TETRIS_SONG.as_ptr().add(start_pos) as *mut _
);
start_pos += MAX_SAMPLES*2;
}
} else {
let remainder: i32 = (((TETRIS_SONG.len() % (MAX_SAMPLES*2)/2)+63) & !63) as i32;
if restlen == 0 {
sys::sceAudioSetChannelDataLen(channel, remainder);
sys::sceAudioOutput(
channel,
MAX_VOL,
TETRIS_SONG.as_ptr().add(start_pos) as *mut _
);
start_pos += (remainder*2) as usize;
}
if start_pos >= TETRIS_SONG.len() {
start_pos = 0;
sys::sceAudioSetChannelDataLen(channel, MAX_SAMPLES as i32);
}
}

restlen = sys::sceAudioGetChannelRestLen(channel);
(restlen, start_pos)
}
}
296 changes: 296 additions & 0 deletions examples/tetris/src/game.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
use crate::gameboard::Gameboard;
use crate::tetromino::Tetromino;
use crate::{BLOCK_SIZE, GAMEBOARD_OFFSET, GAMEBOARD_WIDTH, GAMEBOARD_HEIGHT, BLOCK};
use crate::graphics::{Align4, sprite::Vertex, self};

use psp::{sys, sys::{CtrlButtons, SceCtrlData}};

use rand_chacha::ChaChaRng;
use rand::prelude::*;

/// Stores the state of our entire game
pub struct Game {
score: usize,
board: Gameboard,
next_shape: Tetromino,
current_shape: Tetromino,
next_shape_offset: (usize, usize),
seconds_per_tick: f64,
seconds_since_tick: f64,
shape_placed: bool,
rng: ChaChaRng,
last_input: CtrlButtons,
}

impl Game {
/// Creates a new `Game`
pub fn new() -> Self {
let mut seed: u64 = 0;
unsafe {
sys::sceRtcGetCurrentTick(&mut seed as *mut u64);
}
let mut rng = ChaChaRng::seed_from_u64(seed);

let gameboard = Gameboard::new();

let mut next_shape = Tetromino::new_random(&mut rng);
next_shape.set_pos(30, 7);

let mut current_shape = Tetromino::new_random(&mut rng);
let spawn_loc = gameboard.get_spawn_loc();
current_shape.set_pos(spawn_loc.0 as i32, spawn_loc.1 as i32);

Self {
score: 0,
board: gameboard,
next_shape,
current_shape,
next_shape_offset: (30, 7),
seconds_per_tick: 0.25,
seconds_since_tick: 0.0,
shape_placed: false,
rng,
last_input: CtrlButtons::default(),
}
}

/// Handles user input
pub fn process_input(&mut self) {
let mut pad_data = SceCtrlData::default();
unsafe {
sys::sceCtrlReadBufferPositive(&mut pad_data, 1);
}
if self.last_input.bits() == pad_data.buttons.bits() {
// no change in input, and I don't feel like doing held down buttons
return;
}
if pad_data.buttons.contains(CtrlButtons::LEFT) && !self.last_input.contains(CtrlButtons::LEFT) {
self.attempt_move(-1, 0);
}
if pad_data.buttons.contains(CtrlButtons::RIGHT) && !self.last_input.contains(CtrlButtons::RIGHT) {
self.attempt_move(1, 0);
}
if pad_data.buttons.contains(CtrlButtons::DOWN) && !self.last_input.contains(CtrlButtons::DOWN) {
self.drop();
self.current_shape.lock_to_gameboard(&mut self.board);
self.shape_placed = true;
}
if pad_data.buttons.contains(CtrlButtons::CROSS) && !self.last_input.contains(CtrlButtons::CROSS) {
self.attempt_rotate_ccw();
}
if pad_data.buttons.contains(CtrlButtons::CIRCLE) && !self.last_input.contains(CtrlButtons::CIRCLE) {
self.attempt_rotate_cw();
}
self.last_input = pad_data.buttons;
}

/// Called once per loop of the game, does all the biz.
///
/// # Parameters
///
/// - `seconds_since_last_loop`: Seconds that have passed since the last loop
///
/// # Return Value
///
/// `true` if game is over
pub fn process_game_loop(&mut self, seconds_since_last_loop: f32) -> bool {
self.process_input();
self.seconds_since_tick += seconds_since_last_loop as f64;
if self.seconds_since_tick > self.seconds_per_tick {
self.tick();
self.seconds_since_tick -= self.seconds_per_tick;
}
if self.shape_placed {
if !self.spawn_next_shape() {
// game over
return true;
} else {
self.pick_next_shape();
let rows_complete = self.board.remove_completed_rows();
self.set_score(self.score + 400 * rows_complete);
}
self.shape_placed = false;
}
false
}

/// Moves `current_shape` down 1 unit and locks to board if it collides.
pub fn tick(&mut self) {
if !self.attempt_move(0, 1) {
self.current_shape.lock_to_gameboard(&mut self.board);
self.shape_placed = true;
}
}

/// Setter for `score`
///
/// # Parameters
///
/// - `score`: Score to set.
pub fn set_score(&mut self, score: usize) {
self.score = score;
}

/// Moves the `next_shape` into the `current_shape` and sets position accordingly.
pub fn spawn_next_shape(&mut self) -> bool {
self.current_shape = self.next_shape;
let spawn_loc = self.board.get_spawn_loc();
self.current_shape.set_pos(spawn_loc.0 as i32, spawn_loc.1 as i32);
self.is_position_legal(&self.current_shape)
}

/// Picks the next Tetromino, sets it's position on the screen to be in the
/// "Next Shape:" section
pub fn pick_next_shape(&mut self) {
self.next_shape = Tetromino::new_random(&mut self.rng);
self.next_shape.set_pos(self.next_shape_offset.0 as i32, self.next_shape_offset.1 as i32);
}

/// Draws everything for the game
///
/// # Parameters
///
/// - `vertex_buffer`: Mutable reference to the main vertex buffer.
/// - `texture_buffer`: Mutable reference to the main texture buffer.
pub fn draw(
&self,
vertex_buffer: &mut [Align4<Vertex>],
texture_buffer: &mut [u8],
) {

// background
vertex_buffer[0] = Align4(Vertex {
u: 0.0,
v: 0.0,
color: 0x7f34_3434,
x: BLOCK_SIZE as f32 * GAMEBOARD_OFFSET.0 as f32,
y: BLOCK_SIZE as f32 * GAMEBOARD_OFFSET.1 as f32,
z: -1.0,
});
vertex_buffer[1] = Align4(Vertex {
u: BLOCK_SIZE as f32 * GAMEBOARD_WIDTH as f32,
v: BLOCK_SIZE as f32 * GAMEBOARD_HEIGHT as f32,
color: 0x7f34_3434,
x: BLOCK_SIZE as f32 * GAMEBOARD_OFFSET.0 as f32 + BLOCK_SIZE as f32 * GAMEBOARD_WIDTH as f32,
y: BLOCK_SIZE as f32 * GAMEBOARD_OFFSET.1 as f32 + BLOCK_SIZE as f32 * GAMEBOARD_HEIGHT as f32,
z: -1.0,
});

(*texture_buffer).copy_from_slice(&BLOCK);
(*vertex_buffer)[2..402].copy_from_slice(&self.board.as_vertices());
(*vertex_buffer)[402..410].copy_from_slice(&self.current_shape.as_vertices());
(*vertex_buffer)[410..418].copy_from_slice(&self.next_shape.as_vertices());

graphics::draw_vertices(vertex_buffer, texture_buffer, BLOCK_SIZE, BLOCK_SIZE, 0.75, 0.75);
let score_string = alloc::format!("Score: {}", self.score);
graphics::draw_text_at(327, 40, 0xffff_ffff, score_string.as_str());
graphics::draw_text_at(327, 60, 0xffff_ffff, "Next Shape:");
}

/// Attempts to add to the `current_shape` position, returns true if successful.
///
/// # Parameters
///
/// - `x`: horizontal position to add
/// - `y`: vertical position to add
///
/// # Return Value
///
/// `true` if successful
pub fn attempt_move(&mut self, x: i32, y: i32) -> bool {
let mut temp: Tetromino = self.current_shape.clone();
temp.add_pos(x, y);
if self.is_position_legal(&temp) {
self.current_shape.add_pos(x, y);
return true;
}
false
}

/// Attempts to rotate `current_shape` clockwise, returns true if successful.
///
/// # Return Value
///
/// `true` if successful
pub fn attempt_rotate_cw(&mut self) -> bool {
let mut temp: Tetromino = self.current_shape.clone();
temp.rotate_cw();
if self.is_position_legal(&temp) {
self.current_shape.rotate_cw();
return true;
}
false
}

/// Attempts to rotate `current_shape` counterclockwise, returns true if successful.
///
/// # Return Value
///
/// `true` if successful
pub fn attempt_rotate_ccw(&mut self) -> bool {
let mut temp: Tetromino = self.current_shape.clone();
temp.rotate_ccw();
if self.is_position_legal(&temp) {
self.current_shape.rotate_ccw();
return true;
}
false
}

/// Checks if the position of the given tetromino is within boundaries and does
/// not collide.
///
/// # Parameters
///
/// - `shape`: `Tetromino` to check
///
/// # Return Value
///
/// `true` if position is in bounds and does not collide
pub fn is_position_legal(&self, shape: &Tetromino) -> bool {
self.is_shape_within_borders(shape)
&& !self.does_shape_intersect_locked_blocks(shape)
}

/// Checks if the position of the given tetromino is within boundaries of the
/// gameboard
///
/// # Parameters
///
/// - `shape`: `Tetromino` to check
///
/// # Return Value
///
/// `true` if within boundaries of `board`
pub fn is_shape_within_borders(&self, shape: &Tetromino) -> bool {
let mapped_locs = shape.get_mapped_locs();
for p in mapped_locs.iter() {
if !(p.0 < GAMEBOARD_WIDTH
&& p.1 < GAMEBOARD_HEIGHT) {
return false
}
}
true
}

/// Checks if the given tetromino's position collides with a block in the gameboard
///
/// # Parameters
///
/// `shape`: `Tetromino` to check
///
/// # Return Value
///
/// `true` if shape collides
pub fn does_shape_intersect_locked_blocks(&self, shape: &Tetromino) -> bool {
let mapped_locs = shape.get_mapped_locs();
!self.board.are_locs_empty(mapped_locs.to_vec())
}

/// Hard drop function
pub fn drop(&mut self) {
while self.attempt_move(0, 1) {}
}
}


Loading