~sircmpwn/tetrominoes

a4c7760d56108f6a355a89ba5ab4fb63df3353f5 — Drew DeVault 2 years ago ddb5cf6
Implement ghost pieces
4 files changed, 106 insertions(+), 71 deletions(-)

M board.ha
M main.ha
M notes.txt
M update.ha
M board.ha => board.ha +97 -3
@@ 1,5 1,4 @@
use sdl2;
use fmt; // XXX: TEMP

def BOARD_WIDTH: int = 10;
def BOARD_HEIGHT: int = 20;


@@ 33,6 32,8 @@ type board = struct {
	active_i: int,
	active_x: int,
	active_y: int,
	ghost_x: int,
	ghost_y: int,
	offs_x: int,
	offs_y: int,
};


@@ 55,25 56,38 @@ fn draw_board(state: *state, board: *board) (void | sdl2::error) = {
			h = tex.height * BOARD_SCALE,
		})?;
	};

	switch (state.state) {
	case gamestate::CLEAR =>
		return;
	case => void;
	};

	const active = (board.active_x, board.active_y);
	draw_active(state, board.ghost_x, board.ghost_y, 128)?;
	draw_active(state, board.active_x, board.active_y, 255)?;
};

fn draw_active(
	state: *state,
	x: int, y: int,
	alpha: u8,
) (void | sdl2::error) = {
	const active = (x, y);
	const board = &state.board, tex = &state.piece;
	const offs = (board.offs_x * SCENE_SCALE, board.offs_y * SCENE_SCALE);
	for (let y = 0; y < TETROMINO_HEIGHT; y += 1)
	for (let x = 0; x < TETROMINO_WIDTH; x += 1) {
		let cell = board.active[board.active_i][y * TETROMINO_WIDTH + x];
		if (cell == color::EMPTY) {
			continue;
		};
		const x = x + board.active_x, y = y + board.active_y;
		const x = x + active.0, y = y + active.1;
		if (y < 0) {
			continue;
		};
		let color = &color_map[cell];
		sdl2::set_texture_color_mod(tex.tex, color.0, color.1, color.2)?;
		sdl2::set_texture_alpha_mod(tex.tex, alpha)?;
		sdl2::render_copy(state.render, tex.tex, null, &sdl2::rect {
			x = offs.0 + x * BOARD_SCALE * tex.width,
			y = offs.1 + y * BOARD_SCALE * tex.height,


@@ 83,6 97,49 @@ fn draw_board(state: *state, board: *board) (void | sdl2::error) = {
	};
};

fn canmove(
	state: *state,
	active: *[TETROMINO_LEN]color,
	dx: int, dy: int,
) bool = {
	const board = &state.board.cells;
	const active_x = state.board.active_x;
	const active_y = state.board.active_y;
	for (let y = 0; y < TETROMINO_HEIGHT; y += 1)
	for (let x = 0; x < TETROMINO_WIDTH; x += 1) {
		const active_ix = y * TETROMINO_WIDTH + x;
		if (active[active_ix] == 0) {
			continue;
		};
		if (x + active_x < 0 || x + active_x >= BOARD_WIDTH) {
			return false;
		};
		if (y + dy + active_y < 0) {
			continue;
		};
		const x = x + dx, y = y + dy;
		const board_ix = (y + active_y) * BOARD_WIDTH + (x + active_x);
		if (board_ix: size >= len(board) || board[board_ix] != 0) {
			return false;
		};
	};
	return true;
};

fn update_ghost(state: *state) void = {
	const board = &state.board;
	const cells = &board.cells;
	const active = &board.active[board.active_i];
	let width = 0, height = 0;
	activesize(state, &width, &height);

	let y = 1;
	for (canmove(state, active, 0, y); y += 1) void;

	board.ghost_x = board.active_x;
	board.ghost_y = board.active_y + y - 1;
};

fn commit(state: *state) void = {
	let board = &state.board.cells;
	let active = &state.board.active[state.board.active_i];


@@ 136,3 193,40 @@ fn shiftrow(state: *state, row: int) void = {
		};
	};
};

fn activesize(
	state: *state,
	width: nullable *int,
	height: nullable *int,
) void = {
	let max_x = 0, max_y = 0;
	const active = &state.board.active[state.board.active_i];
	for (let y = 0; y < TETROMINO_HEIGHT; y += 1)
	for (let x = 0; x < TETROMINO_WIDTH; x += 1) {
		const active = active[y * TETROMINO_WIDTH + x] != 0;
		if (active && x > max_x) {
			max_x = x;
		};
		if (active && y > max_y) {
			max_y = y;
		};
	};
	match (width) {
	case let ptr: *int =>
		*ptr = max_x + 1;
	case null => void;
	};
	match (height) {
	case let ptr: *int =>
		*ptr = max_y + 1;
	case null => void;
	};
};

fn clear_lines(state: *state) void = {
	for (let i = 0z; i < len(state.clearlines); i += 1)
	for (let x = 0; x < BOARD_WIDTH; x += 1) {
		const y = state.clearlines[i];
		state.board.cells[y * BOARD_WIDTH + x] = 0;
	};
};

M main.ha => main.ha +5 -1
@@ 57,7 57,8 @@ fn run() (void | sdl2::error) = {
		640, 480, window_flags::NONE)?;
	defer sdl2::destroy_window(win);

	const render = sdl2::create_renderer(win, -1, renderer_flags::NONE)?;
	const render = sdl2::create_renderer(win, -1,
		renderer_flags::ACCELERATED)?;
	defer sdl2::destroy_renderer(render);

	const seed = time::now(time::clock::MONOTONIC).sec: u64;


@@ 79,6 80,9 @@ fn run() (void | sdl2::error) = {
	};
	defer sdl2::destroy_texture(state.piece.tex);
	defer sdl2::destroy_texture(state.scene.tex);
	defer sdl2::destroy_texture(state.clear.tex);

	sdl2::set_texture_blend_mode(state.piece.tex, sdl2::blend_mode::BLEND)?;

	let controller: nullable *sdl2::gamecontroller = null;
	for (let i = 0; i < sdl2::numjoysticks()?; i += 1) {

M notes.txt => notes.txt +0 -1
@@ 1,4 1,3 @@
- ghost blocks
- instant place
- fix thumbsticks
- scorekeeping/display

M update.ha => update.ha +4 -66
@@ 97,6 97,7 @@ fn do_spawn(state: *state, now: u32) void = {
	state.board.active_y = -height;
	state.board.active_x = BOARD_WIDTH / 2 - width / 2;
	state.state = gamestate::FALL;
	update_ghost(state);
	schedule_tick(state, now);
};



@@ 138,6 139,7 @@ fn schedule_tick(state: *state, now: u32) void = {
fn move_left(state: *state) void = {
	if (state.board.active_x > 0) {
		state.board.active_x -= 1;
		update_ghost(state);
	};
};



@@ 146,6 148,7 @@ fn move_right(state: *state) void = {
	activesize(state, &width, null);
	if (state.board.active_x < BOARD_WIDTH - width) {
		state.board.active_x += 1;
		update_ghost(state);
	};
};



@@ 159,70 162,5 @@ fn rotate(state: *state) void = {
		return;
	};
	board.active_i = next;
};

fn activesize(
	state: *state,
	width: nullable *int,
	height: nullable *int,
) void = {
	let max_x = 0, max_y = 0;
	const active = &state.board.active[state.board.active_i];
	for (let y = 0; y < TETROMINO_HEIGHT; y += 1)
	for (let x = 0; x < TETROMINO_WIDTH; x += 1) {
		const active = active[y * TETROMINO_WIDTH + x] != 0;
		if (active && x > max_x) {
			max_x = x;
		};
		if (active && y > max_y) {
			max_y = y;
		};
	};
	match (width) {
	case let ptr: *int =>
		*ptr = max_x + 1;
	case null => void;
	};
	match (height) {
	case let ptr: *int =>
		*ptr = max_y + 1;
	case null => void;
	};
};

fn canmove(
	state: *state,
	active: *[TETROMINO_LEN]color,
	dx: int, dy: int,
) bool = {
	let board = &state.board.cells;
	const active_x = state.board.active_x;
	const active_y = state.board.active_y;
	for (let y = 0; y < TETROMINO_HEIGHT; y += 1)
	for (let x = 0; x < TETROMINO_WIDTH; x += 1) {
		const active_ix = y * TETROMINO_WIDTH + x;
		if (active[active_ix] == 0) {
			continue;
		};
		if (x + active_x < 0 || x + active_x >= BOARD_WIDTH) {
			return false;
		};
		if (y + active_y < 0) {
			continue;
		};
		const x = x + dx, y = y + dy;
		const board_ix = (y + active_y) * BOARD_WIDTH + (x + active_x);
		if (board_ix: size >= len(board) || board[board_ix] != 0) {
			return false;
		};
	};
	return true;
};

fn clear_lines(state: *state) void = {
	for (let i = 0z; i < len(state.clearlines); i += 1)
	for (let x = 0; x < BOARD_WIDTH; x += 1) {
		const y = state.clearlines[i];
		state.board.cells[y * BOARD_WIDTH + x] = 0;
	};
	update_ghost(state);
};