about summary refs log tree commit diff
path: root/src/main.zig
blob: 9accfc8717686c6be98a7f6974964f99ff76cddc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Main function
// Copyright (C) 2021-2023  Nguyễn Gia Phong
//
// This file is part of Black Shades.
//
// Black Shades is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Black Shades is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Black Shades.  If not, see <https://www.gnu.org/licenses/>.

const DefaultPrng = std.rand.DefaultPrng;
const allocator = std.heap.c_allocator;
const free = std.c.free;
const std = @import("std");

const Loca = @import("loca").Loca;
const al = @import("zeal");
const gf = @import("gfz");
const gl = @import("zgl");

const Decals = @import("Decals.zig");
const Scores = misc.Scores;
const Textures = @import("Textures.zig");
const c = @import("cimport.zig");
const configuration = @import("config.zig");
const misc = @import("misc.zig");
const loadScores = misc.loadScores;
const saveScores = misc.saveScores;

var game: *c.Game = undefined;
var prng: DefaultPrng = undefined;

comptime {
    _ = @import("geom.zig");
    _ = @import("model.zig");
} // export functions in C ABI

/// Return a floating point value evenly distributed in the range [0, 1).
export fn randFloat() f32 {
    return prng.random().float(f32);
}

/// Return a random integer i where at_least <= i <= at_most.
/// The results of this function may be biased.
export fn randInt(at_least: i32, at_most: i32) i32 {
    return prng.random().intRangeAtMostBiased(i32, at_least, at_most);
}

/// Return a random integer i where 0 <= i < less_than.
/// The results of this function may be biased.
export fn randUint(less_than: u32) u32 {
    return prng.random().uintLessThanBiased(u32, less_than);
}

fn resizeWindow(window: gf.Window, width: c_int, height: c_int) void {
    _ = window;
    c.resizeWindow(game, width, height);
}

fn handleKey(window: gf.Window, key: gf.Key, scancode: c_int,
             action: gf.KeyAction, mods: gf.Mods) void {
    _ = window;
    _ = scancode;
    c.handleKey(game, @intFromEnum(key), @intFromEnum(action), mods.toInt());
}

fn look(window: gf.Window, xpos: f64, ypos: f64) void {
    _ = window;
    c.look(game, xpos, ypos);
}

fn click(window: gf.Window, button: gf.MouseButton,
         action: gf.MouseButton.Action, mods: gf.Mods) void {
    _ = window;
    c.click(game, @intFromEnum(button), @intFromEnum(action), mods.toInt());
}

pub fn main() !void {
    const loca = try Loca.init(allocator, .{});
    defer loca.deinit();
    const config = try configuration.parse(loca.user_config);
    defer free(config.levels.ptr);

    try gf.init();
    defer gf.deinit() catch unreachable;
    const window = try gf.Window.create(config.width, config.height,
                                        "Black Shades", .{}, .{});
    try window.makeCurrent();

    prng = DefaultPrng.init(@bitCast(try gf.getTime()));
    const textures = try Textures.init();
    defer textures.deinit();
    var decals = Decals.init(allocator, &textures);
    defer decals.deinit();
    game = c.makeGame(&decals, @bitCast(config),
                      @bitCast(try loadScores(loca.user_data))).?;
    defer saveScores(loca.user_data, @bitCast(c.getScores(game)))
        catch unreachable;

    try window.setSizeCallback(resizeWindow);
    try gf.swapInterval(@intFromBool(config.vsync));
    c.initGl(game);

    if (try gf.rawMouseMotionSupported())
        try window.setInputMode(.raw_mouse_motion, true);
    try window.setCursorPosCallback(look);
    try window.setInputMode(.sticky_mouse_buttons, true);
    try window.setMouseButtonCallback(click);
    try window.setInputMode(.sticky_keys, true);
    try window.setKeyCallback(handleKey);

    const device = try al.Device.init(null);
    defer device.deinit() catch unreachable;
    const context = try al.Context.init(device, .{});
    defer context.deinit() catch unreachable;
    try context.makeCurrent();

    c.initGame(game);
    defer c.closeGame(game);
    while (!try window.shouldClose()) {
        c.eventLoop(game);
        decals.update();
        decals.draw();
        try window.swapBuffers();
        try gf.pollEvents();
    }
}