use math;
use math::random;
use sdl2::{event_type, controller_axis, controller_button};
use sdl2;
def THRESHOLD: i16 = sdl2::JOYSTICK_AXIS_MAX / 2;
def TETROMINO_WIDTH: int = 4;
def TETROMINO_HEIGHT: int = 4;
def TETROMINO_LEN: size = 4 * 4;
type input = struct {
axis_x: i16,
};
fn update(state: *state) (void | sdl2::error) = {
const now = sdl2::get_ticks();
const input = &state.input;
const idle = math::absi16(input.axis_x): i16 < THRESHOLD;
let ev = sdl2::event { ... };
for (sdl2::poll_event(&ev)? == 1) switch (ev.event_type) {
case event_type::QUIT =>
state.run = false;
break;
case event_type::CONTROLLERAXISMOTION =>
// TODO: This is buggy as fuck
if (ev.caxis.axis == controller_axis::LEFTX
|| ev.caxis.axis == controller_axis::RIGHTX) {
if (idle && ev.caxis.value < -THRESHOLD) {
move_left(state);
};
if (idle && ev.caxis.value > THRESHOLD) {
move_right(state);
};
input.axis_x = ev.caxis.value;
};
case event_type::CONTROLLERBUTTONDOWN =>
switch (ev.cbutton.button) {
case controller_button::DPAD_LEFT =>
move_left(state);
case controller_button::DPAD_RIGHT =>
move_right(state);
case controller_button::A =>
rotate(state, 1);
case controller_button::B =>
rotate(state, -1);
case controller_button::DPAD_UP =>
if (state.state != gamestate::INTRO) {
state.board.active_y = state.board.ghost_y;
commit(state);
state.next = now;
};
case controller_button::DPAD_DOWN =>
state.faster = true;
case => void;
};
case event_type::CONTROLLERBUTTONUP =>
switch (ev.cbutton.button) {
case controller_button::DPAD_DOWN =>
state.faster = false;
case => void;
};
case event_type::KEYDOWN =>
for (let i = 0z; i < len(state.keybindings); i += 1) {
if (state.keybindings[i].0 == ev.key.keysym.sym) {
switch (state.keybindings[i].1) {
case action::COMMIT =>
if (state.state != gamestate::INTRO) {
state.board.active_y =
state.board.ghost_y;
commit(state);
state.next = now;
};
case action::DOWN =>
state.faster = true;
case action::LEFT =>
move_left(state);
case action::RIGHT =>
move_right(state);
case action::ROTLEFT =>
rotate(state, 1);
case action::ROTRIGHT =>
rotate(state, -1);
};
};
};
case event_type::KEYUP =>
for (let i = 0z; i < len(state.keybindings); i += 1) {
if (state.keybindings[i].0 == ev.key.keysym.sym) {
if (state.keybindings[i].1 == action::DOWN) {
state.faster = false;
};
};
};
case => void;
};
if (state.next <= now) {
switch (state.state) {
case gamestate::INTRO =>
do_intro(state, now);
case gamestate::SPAWN =>
do_spawn(state, now);
case gamestate::FALL =>
do_fall(state, now);
case gamestate::CLEAR =>
do_clear(state, now);
case gamestate::GAMEOVER =>
// TODO: Game over screen
state.run = false;
};
};
};
fn do_intro(state: *state, now: u32) void = {
state.board.active_y += 1;
if (state.fadein_finish <= now) {
state.state = gamestate::FALL;
};
schedule_tick(state, now);
};
fn do_spawn(state: *state, now: u32) void = {
let next = state.board.tetrominoes[state.board.next];
state.board.next += 1;
if (state.board.next >= len(state.board.tetrominoes)) {
shuffle_tetrominoes(state);
};
let col = color::EMPTY;
for (true) {
let next = random::next(&state.rand): color - 1;
let next = next % color::MAX;
let next = next + 1;
if (next != state.lastcolor) {
col = next;
break;
};
};
state.lastcolor = col;
state.board.active = tetrominoes[next];
state.board.active_i = 0;
let active = &state.board.active;
for (let i = 0z; i < len(active); i += 1) {
let active = &active[i];
for (let i = 0z; i < len(active); i += 1) {
if (active[i] != 0) {
active[i] = col;
};
};
};
let width = 0, height = 0;
activesize(state, &width, &height);
state.board.active_y = -(height - 1);
state.board.active_x = BOARD_WIDTH / 2 - width / 2;
state.state = gamestate::FALL;
update_ghost(state);
schedule_tick(state, now);
};
fn do_fall(state: *state, now: u32) void = {
const active = &state.board.active[state.board.active_i];
if (canmove(state, active, 0, 1)) {
state.board.active_y += 1;
schedule_tick(state, now);
} else {
commit(state);
};
};
fn do_clear(state: *state, now: u32) void = {
state.clearframe += 1;
state.clearframe %= 12;
state.next = now + 25; // XXX: Based on tempo?
if (state.clearframe == 4) {
clear_lines(state);
};
if (state.clearframe == 9) {
shiftrows(state);
state.state = gamestate::SPAWN;
state.board.active_y = -4;
};
};
fn schedule_tick(state: *state, now: u32) void = {
if (state.faster) {
state.next = now + 100; // TODO: Based on BPM multiplier
} else {
state.next = now + state.speed;
};
};
fn move_left(state: *state) void = {
const active = &state.board.active[state.board.active_i];
if (canmove(state, active, -1, 0)) {
state.board.active_x -= 1;
update_ghost(state);
};
};
fn move_right(state: *state) void = {
const active = &state.board.active[state.board.active_i];
if (canmove(state, active, 1, 0)) {
state.board.active_x += 1;
update_ghost(state);
};
};
fn rotate(state: *state, d: int) void = {
const board = &state.board;
const next = (board.active_i + d): size % len(board.active);
const active = &state.board.active[next];
let i = 0z;
const mods = [0, -1, 1];
for (i < len(mods) && !canmove(state, active, mods[i], 0); i += 1) void;
if (i >= len(mods)) {
return;
};
board.active_x += mods[i];
board.active_i = next: int;
update_ghost(state);
};