use fmt; use fs; use sdl2; use sdl2::{ SDL_GameControllerAxis, SDL_EventType, SDL_RendererFlags, SDL_WindowFlags }; use sdl2::image; use sdl2::mixer; use strings; type object = struct { tex: *sdl2::SDL_Texture, w: int, h: int, x: int, y: int, dx: int, dy: int, }; type state = struct { quit: bool, window: *sdl2::SDL_Window, render: *sdl2::SDL_Renderer, nbutton: int, hare: object, cat: object, }; export fn main() void = { match (run()) { case let err: sdl2::error => fmt::fatal("SDL2 error: {}", sdl2::strerror(err)); case let err: fs::error => fmt::fatal("Error: {}", fs::strerror(err)); case void => void; }; }; fn run() (void | fs::error | sdl2::error) = { sdl2::SDL_Init(sdl2::SDL_INIT_VIDEO | sdl2::SDL_INIT_AUDIO | sdl2::SDL_INIT_GAMECONTROLLER)!; defer sdl2::SDL_Quit(); image::IMG_Init(image::IMG_InitFlags::PNG | image::IMG_InitFlags::JPG)!; defer image::IMG_Quit(); mixer::Mix_Init(mixer::MIX_InitFlags::OGG)!; defer mixer::Mix_Quit(); mixer::Mix_OpenAudio(mixer::MIX_DEFAULT_FREQUENCY, mixer::MIX_DEFAULT_FORMAT, mixer::MIX_DEFAULT_CHANNELS, 1024)!; defer mixer::Mix_CloseAudio(); const win = sdl2::SDL_CreateWindow("Hare SDL2 demo", sdl2::SDL_WINDOWPOS_UNDEFINED, sdl2::SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WindowFlags::NONE)?; defer sdl2::SDL_DestroyWindow(win); const render = sdl2::SDL_CreateRenderer(win, -1, SDL_RendererFlags::NONE)?; defer sdl2::SDL_DestroyRenderer(render); let controller: nullable *sdl2::SDL_GameController = null; for (let i = 0; i < sdl2::SDL_NumJoysticks()?; i += 1) { if (!sdl2::SDL_IsGameController(i)) { continue; }; match (sdl2::SDL_GameControllerOpen(i)) { case let c: *sdl2::SDL_GameController => controller = c; sdl2::SDL_GameControllerRumble(c, 0x4000, 0x4000, 1000): void; break; case sdl2::error => void; }; }; defer match (controller) { case null => void; case let c: *sdl2::SDL_GameController => sdl2::SDL_GameControllerClose(c); }; let sample = mixer::load_file("sample.ogg")?; defer mixer::Mix_FreeChunk(sample); mixer::Mix_PlayChannelTimed(0, sample, -1)?; let state = state { window = win, render = render, hare = load_object(render, "mascot.jpg")?, cat = load_object(render, "cat.png")?, ... }; defer sdl2::SDL_DestroyTexture(state.hare.tex); defer sdl2::SDL_DestroyTexture(state.cat.tex); state.hare.dx = 2; state.hare.dy = 2; for (!state.quit) { update(&state)?; draw(&state)?; sdl2::SDL_Delay(1000 / 60); }; }; fn update(state: *state) (void | sdl2::error) = { let ev = sdl2::event { ... }; for (sdl2::SDL_PollEvent(&ev)? == 1) switch (ev.event_type) { case SDL_EventType::QUIT => state.quit = true; return; case SDL_EventType::CONTROLLERAXISMOTION => let delta = ev.caxis.value: int * 10 / sdl2::SDL_JOYSTICK_AXIS_MAX; if (axis_x(ev.caxis.axis)) { state.cat.dx = delta; }; if (axis_y(ev.caxis.axis)) { state.cat.dy = delta; }; case SDL_EventType::CONTROLLERBUTTONDOWN => state.nbutton += 1; case SDL_EventType::CONTROLLERBUTTONUP => state.nbutton -= 1; case => void; }; let width = 0, height = 0; sdl2::SDL_GetWindowSize(state.window, &width, &height); state.hare.x += state.hare.dx; state.hare.y += state.hare.dy; if (state.hare.x <= 0 || state.hare.x + state.hare.w >= width) { state.hare.dx = -state.hare.dx; }; if (state.hare.y <= 0 || state.hare.y + state.hare.h >= height) { state.hare.dy = -state.hare.dy; }; state.cat.x += state.cat.dx; state.cat.y += state.cat.dy; if (state.cat.x <= 0) { state.cat.x = 0; }; if (state.cat.x > width - state.cat.w) { state.cat.x = width - state.cat.w; }; if (state.cat.y <= 0) { state.cat.y = 0; }; if (state.cat.y > height - state.cat.h) { state.cat.y = height - state.cat.h; }; }; fn draw(state: *state) (void | sdl2::error) = { if (state.nbutton == 0) { sdl2::SDL_SetRenderDrawColor(state.render, 50, 50, 50, 255)?; } else { sdl2::SDL_SetRenderDrawColor(state.render, 50, 50, 200, 255)?; }; sdl2::SDL_RenderClear(state.render)?; draw_object(state, &state.hare)?; draw_object(state, &state.cat)?; sdl2::SDL_RenderPresent(state.render); }; fn draw_object(state: *state, obj: *object) (void | sdl2::error) = { sdl2::SDL_RenderCopy(state.render, obj.tex, null, &sdl2::SDL_Rect { x = obj.x, y = obj.y, w = obj.w, h = obj.h, })?; }; fn axis_x(axis: SDL_GameControllerAxis) bool = { switch (axis) { case SDL_GameControllerAxis::LEFTX, SDL_GameControllerAxis::RIGHTX => return true; case => return false; }; }; fn axis_y(axis: SDL_GameControllerAxis) bool = { switch (axis) { case SDL_GameControllerAxis::LEFTY, SDL_GameControllerAxis::RIGHTY => return true; case => return false; }; }; fn load_object(render: *sdl2::SDL_Renderer, path: str) (object | sdl2::error) = { const tex = image::IMG_LoadTexture(render, path)?; let w = 0, h = 0; sdl2::SDL_QueryTexture(tex, null, null, &w, &h)?; return object { tex = tex, w = w, h = h, ... }; };