use dirs;
use fmt;
use format::ini;
use fs;
use getopt;
use io;
use math::random;
use os;
use sdl2::image;
use sdl2::mixer;
use sdl2::{renderer_flags, window_flags};
use sdl2;
use time;
use types;
type texture = struct {
tex: *sdl2::texture,
width: int,
height: int,
};
type gamestate = enum {
INTRO,
SPAWN,
FALL,
CLEAR,
GAMEOVER,
};
type action = enum {
COMMIT,
DOWN,
LEFT,
RIGHT,
ROTLEFT,
ROTRIGHT,
};
type state = struct {
run: bool,
window: *sdl2::window,
render: *sdl2::renderer,
piece: texture,
scene: texture,
clear: texture,
music: *mixer::chunk,
input: input,
board: board,
rand: random::random,
state: gamestate,
next: u32, // Timestamp of the next state change
speed: u32,
faster: bool,
lastcolor: color,
clearframe: int,
clearlines: []int,
// Intro
fadein_start: u32,
fadein_finish: u32,
keybindings: [](sdl2::keycode, action),
};
export fn main() void = {
match (run()) {
case let err: sdl2::error =>
fmt::fatal("SDL error: {}", sdl2::strerror(err));
case let err: ini::error =>
fmt::fatal("Error loading config file: {}", ini::strerror(err));
case let err: fs::error =>
fmt::fatal("Error: {}", fs::strerror(err));
case void => void;
};
};
fn run() (void | fs::error | sdl2::error | ini::error) = {
let keybindings: [](sdl2::keycode, action) = alloc([
(sdl2::keycode::UP, action::ROTLEFT),
(sdl2::keycode::x, action::ROTLEFT),
(sdl2::keycode::SPACE, action::COMMIT),
(sdl2::keycode::LCTRL, action::ROTRIGHT),
(sdl2::keycode::RCTRL, action::ROTRIGHT),
(sdl2::keycode::z, action::ROTRIGHT),
(sdl2::keycode::LEFT, action::LEFT),
(sdl2::keycode::RIGHT, action::RIGHT),
(sdl2::keycode::DOWN, action::DOWN),
(sdl2::keycode::KP_8, action::COMMIT),
(sdl2::keycode::KP_4, action::LEFT),
(sdl2::keycode::KP_6, action::RIGHT),
(sdl2::keycode::KP_2, action::COMMIT),
(sdl2::keycode::KP_1, action::ROTLEFT),
(sdl2::keycode::KP_5, action::ROTLEFT),
(sdl2::keycode::KP_9, action::ROTLEFT),
(sdl2::keycode::KP_3, action::ROTRIGHT),
(sdl2::keycode::KP_7, action::ROTRIGHT),
]);
defer free(keybindings);
const cfg = dirs::configfs("tetrominoes");
defer fs::close(cfg);
match (fs::open(cfg, "config")) {
case let cfg: io::handle =>
config(&keybindings, cfg)?;
case fs::error => void;
};
sdl2::init(sdl2::init_flags::VIDEO
| sdl2::init_flags::AUDIO
| sdl2::init_flags::GAMECONTROLLER)?;
defer sdl2::quit();
image::init(image::init_flags::PNG | image::init_flags::JPG)?;
defer image::quit();
mixer::init(mixer::init_flags::FLAC
| mixer::init_flags::MP3
| mixer::init_flags::OGG
| mixer::init_flags::OPUS)?; // That should be plenty
defer mixer::quit();
mixer::open_audio(mixer::DEFAULT_FREQUENCY,
mixer::DEFAULT_FORMAT, mixer::DEFAULT_CHANNELS, 1024)?;
defer mixer::close_audio();
const win = sdl2::create_window("Tetrominoes",
sdl2::WINDOWPOS_UNDEFINED, sdl2::WINDOWPOS_UNDEFINED,
640, 480, window_flags::NONE)?;
defer sdl2::destroy_window(win);
const render = sdl2::create_renderer(win, -1,
renderer_flags::ACCELERATED)?;
defer sdl2::destroy_renderer(render);
const seed = time::now(time::clock::MONOTONIC).sec: u64;
let state = state {
run = true,
window = win,
render = render,
piece = load_texture(render, "assets/piece.png")?,
scene = load_texture(render, "assets/concert.png")?,
clear = load_texture(render, "assets/clear.png")?,
music = mixer::load_file("assets/rhapsody-in-blue.ogg")?,
board = board {
offs_x = 88,
offs_y = 74,
...
},
rand = random::init(seed),
speed = 60 * 1000 / 84, // TODO: Set this to BPM properly
keybindings = keybindings,
...
};
defer sdl2::destroy_texture(state.piece.tex);
defer sdl2::destroy_texture(state.scene.tex);
defer sdl2::destroy_texture(state.clear.tex);
defer mixer::free_chunk(state.music);
mixer::play_channel(0, state.music, 0)?;
sdl2::set_texture_blend_mode(state.piece.tex, sdl2::blend_mode::BLEND)?;
sdl2::set_render_draw_blend_mode(state.render, sdl2::blend_mode::BLEND)?;
let controller: nullable *sdl2::gamecontroller = null;
for (let i = 0; i < sdl2::numjoysticks()?; i += 1) {
if (!sdl2::is_game_controller(i)) {
continue;
};
match (sdl2::game_controller_open(i)) {
case let c: *sdl2::gamecontroller =>
controller = c;
break;
case sdl2::error => void;
};
};
defer match (controller) {
case null => void;
case let c: *sdl2::gamecontroller =>
sdl2::game_controller_close(c);
};
const now = sdl2::get_ticks();
shuffle_tetrominoes(&state);
do_spawn(&state, now);
// Set up intro
state.state = gamestate::INTRO;
state.board.active_y -= 5;
state.fadein_start = now + 4000;
state.fadein_finish = now + 8000;
for (state.run) {
update(&state)?;
draw(&state)?;
sdl2::delay(1000 / 60);
};
};
fn load_texture(render: *sdl2::renderer, path: str) (texture | sdl2::error) = {
const tex = image::load_texture(render, path)?;
let width = 0, height = 0;
sdl2::query_texture(tex, null, null, &width, &height)?;
return texture {
tex = tex,
width = width,
height = height,
};
};