This commit is contained in:
2026-03-02 08:09:02 +13:00
commit cdd88973dc
11 changed files with 1434 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
log.log
.zig-cache/
zig-out/

0
README.md Normal file
View File

82
build.zig Normal file
View File

@@ -0,0 +1,82 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mod = b.addModule("terimal_tetris", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
});
const exe = b.addExecutable(.{
.name = "terimal_tetris",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "terimal_tetris", .module = mod },
},
}),
});
b.installArtifact(exe);
exe.addIncludePath(b.path("include"));
switch (builtin.os.tag) {
.windows => {
exe.addCSourceFiles(.{
.files = &.{
"c-src/terminal_windows.c",
},
.flags = &.{
"-lc",
"-w",
},
});
exe.linkLibC();
},
.linux, .macos => {
exe.addCSourceFiles(.{
.files = &.{
"c-src/terminal_linux.c",
},
.flags = &.{
"-lc",
"-w",
},
});
exe.linkLibC();
},
else => unreachable,
}
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
test_step.dependOn(&run_exe_tests.step);
}

13
build.zig.zon Normal file
View File

@@ -0,0 +1,13 @@
.{
.name = .terimal_tetris,
.version = "0.0.0",
.fingerprint = 0xe0ee61f87efe174a,
.minimum_zig_version = "0.15.1",
.dependencies = .{
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

27
c-src/terminal_linux.c Normal file
View File

@@ -0,0 +1,27 @@
#include "../include/terminal.h"
#include <termios.h>
#include <unistd.h>
static struct termios orig_termios;
void exit_raw_mode() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
void enter_raw_mode() {
tcgetattr(STDIN_FILENO, &orig_termios);
// atexit(exit_raw_mode); // zig is handling this step
struct termios raw = orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 0;
// raw.c_cc[VTIME] = 1;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

51
c-src/terminal_windows.c Normal file
View File

@@ -0,0 +1,51 @@
#include "../include/terminal.h"
#include <Windows.h>
#include <consoleapi.h>
#include <minwindef.h>
#include <processenv.h>
#include <winbase.h>
#define DEFAULT_CONSOLE_MODE (\
ENABLE_ECHO_INPUT |\
ENABLE_LINE_INPUT |\
ENABLE_MOUSE_INPUT |\
ENABLE_PROCESSED_INPUT |\
ENABLE_QUICK_EDIT_MODE |\
ENABLE_WINDOW_INPUT |\
ENABLE_PROCESSED_OUTPUT |\
ENABLE_WRAP_AT_EOL_OUTPUT |\
ENABLE_VIRTUAL_TERMINAL_PROCESSING |\
DISABLE_NEWLINE_AUTO_RETURN |\
ENABLE_LVB_GRID_WORLDWIDE)
#define RAW_CONSOLE_MODE \
ENABLE_WINDOW_INPUT |\
ENABLE_VIRTUAL_TERMINAL_INPUT
// ENABLE_PROCESSED_INPUT |\
static DWORD mode = 0;
void enter_raw_mode() {
GetConsoleMode(
GetStdHandle(STD_INPUT_HANDLE),
&mode
);
SetConsoleMode(
GetStdHandle(STD_INPUT_HANDLE),
RAW_CONSOLE_MODE
);
}
void exit_raw_mode() {
SetConsoleMode(
GetStdHandle(STD_INPUT_HANDLE),
mode
);
}

10
include/terminal.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef _TERMINAL_H_
#define _TERMINAL_H_
void enter_raw_mode();
void exit_raw_mode();
#endif

199
src/colour.zig Normal file
View File

@@ -0,0 +1,199 @@
const std = @import("std");
/// colour type
pub const Colour = struct {
const Self = @This();
r: u8,
g: u8,
b: u8,
/// inits the struct with a custom colour
pub fn custom(r: u8, g: u8, b: u8) Self {
return Self{ .r = r, .g = g, .b = b };
}
/// inits the struct with a red colour
pub fn red() Self {
return Self{ .r = 255, .g = 0, .b = 0 };
}
/// inits the struct with a orange colour
pub fn orange() Self {
return Self{ .r = 255, .g = 128, .b = 0 };
}
/// inits the struct with a yellow colour
pub fn yellow() Self {
return Self{ .r = 255, .g = 255, .b = 0 };
}
/// inits the struct with a gold colour
pub fn gold() Self {
return Self{ .r = 255, .g = 200, .b = 0 };
}
/// inits the struct with a lightGreen colour
pub fn lightGreen() Self {
return Self{ .r = 128, .g = 255, .b = 0 };
}
/// inits the struct with a green colour
pub fn green() Self {
return Self{ .r = 0, .g = 255, .b = 0 };
}
/// inits the struct with a cyan colour
pub fn cyan() Self {
return Self{ .r = 0, .g = 255, .b = 128 };
}
/// inits the struct with a turquoise colour
pub fn turquoise() Self {
return Self{ .r = 0, .g = 255, .b = 190 };
}
/// inits the struct with a lightBlue colour
pub fn lightBlue() Self {
return Self{ .r = 0, .g = 128, .b = 255 };
}
/// inits the struct with a blue colour
pub fn blue() Self {
return Self{ .r = 0, .g = 0, .b = 255 };
}
/// inits the struct with a darkPurple colour
pub fn darkPurple() Self {
return Self{ .r = 128, .g = 0, .b = 255 };
}
/// inits the struct with a purple colour
pub fn purple() Self {
return Self{ .r = 170, .g = 0, .b = 255 };
}
/// inits the struct with a lightPurple colour
pub fn lightPurple() Self {
return Self{ .r = 196, .g = 78, .b = 255 };
}
/// inits the struct with a magenta colour
pub fn magenta() Self {
return Self{ .r = 255, .g = 0, .b = 255 };
}
/// inits the struct with a pink colour
pub fn pink() Self {
return Self{ .r = 255, .g = 0, .b = 128 };
}
/// inits the struct with a peach colour
pub fn peach() Self {
return Self{ .r = 255, .g = 158, .b = 158 };
}
/// inits the struct with a black colour
pub fn black() Self {
return Self{ .r = 0, .g = 0, .b = 0 };
}
/// inits the struct with a white colour
pub fn white() Self {
return Self{ .r = 255, .g = 255, .b = 255 };
}
/// inits the struct with a grey colour
pub fn grey() Self {
return Self{ .r = 128, .g = 128, .b = 128 };
}
/// inits the struct with a greyScale colour
pub fn greyScale(scale: u8) Self {
// 255 max
return Self{ .r = scale, .g = scale, .b = scale };
}
/// this is the background colour of the defualt windows terminal
pub fn windowsTerminalBackground() Self {
return greyScale(12);
}
/// this is the colour of the defualt windows terminal front
pub fn windowsTerminalFont() Self {
return greyScale(204);
}
/// point must be an unsigned number type
pub fn pointToColour(point: anytype) Colour {
const normed_point_hue: u16 = @as(u16, @intFromFloat((@as(f32, @floatFromInt(point)) / @as(f32, @floatFromInt(std.math.maxInt(@TypeOf(point))))) * 255 * 5)) + 255;
const normed_point_rgb: u8 = @intCast((normed_point_hue + 1) % 256);
if (normed_point_hue <= 255 * 2) { //count up
return custom(255, normed_point_rgb, 0);
}
// 0, 255, 0, 255*3
if (normed_point_hue <= 255 * 3) { // count down
return custom(255 - normed_point_rgb, 255, 0);
}
// 0, 255, 255, 255*4
if (normed_point_hue <= 255 * 4) { // count up
return custom(0, 255, normed_point_rgb);
}
// 0, 0, 255, 255*5
if (normed_point_hue <= 255 * 5) { // count down
return custom(0, 255 - normed_point_rgb, 255);
}
// 255, 0, 255, 255*6
if (normed_point_hue <= 255 * 6) { // count up
return custom(normed_point_rgb, 0, 255);
}
return Colour.white();
}
pub fn rangeToColour(start: usize, end: usize, point: usize) Colour {
const delta = end - start;
const shifted_point: usize = point - start;
// wrapping mul to insure that this will never overflow
const normed_point_pos: usize = (std.math.maxInt(usize) / delta) *% shifted_point;
return pointToColour(normed_point_pos);
}
/// this will return a set colour based on the usize number
/// how ever at at point it will start to be random
pub fn usizeToColour(number: usize) Self {
return switch (number) {
0 => red(),
1 => orange(),
2 => yellow(),
3 => gold(),
4 => lightGreen(),
5 => green(),
6 => cyan(),
7 => turquoise(),
8 => lightBlue(),
9 => blue(),
10 => darkPurple(),
11 => purple(),
12 => lightPurple(),
13 => magenta(),
14 => pink(),
15 => peach(),
else => random(), // just make a random colour
};
}
/// returns a random colour
pub fn random() Self {
const static = struct {
var prng = std.rand.DefaultPrng.init(69420);
};
const rand = static.prng.random();
return custom(
rand.int(u8),
rand.int(u8),
rand.int(u8),
);
}
};

546
src/keys.zig Normal file
View File

@@ -0,0 +1,546 @@
const std = @import("std");
const c = @cImport({
@cInclude("ctype.h");
});
const escape_char = 27;
pub fn isControlChar(char: u8) bool {
return c.iscntrl(@as(c_int, char)) != 0;
}
pub const Key = struct {
const Self = @This();
// home and end keys will be added
pub fn get(stdin: *std.Io.Reader) !?Key {
// if (self.poller.pollTimeout(100) catch false) {
// https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal%20input%20sequences
// <char> -> char
// <esc> <nochar> -> esc
// <esc> <esc> -> esc
// <esc> <char> -> Alt-keypress or keycode sequence
// <esc> '[' <nochar> -> Alt-[
// <esc> '[' (<modifier>) <char> -> keycode sequence, <modifier> is a decimal
// number and defaults to 1 (xterm)
// <esc> '[' 1 ; <modifier> <char> -> xterm
// <esc> '[' (<keycode>) (';'<modifier>) '~' -> keycode sequence, <keycode> and <modifier>
// are decimal numbers and default to 1 (vt)
const char = try stdin.takeByte();
var k: Key = Key{};
if (isControlChar(char)) {
k.escape = char;
} else {
k.keycode = char;
}
// not part of an espace sequence
if (char != '\x1b') {
return k;
}
k.escape = char;
const char_2 = try stdin.takeByte();
// not part of an Alt-[ sequence
if (char_2 != '[' and char_2 != 'O') {
k.keycode = char_2;
return k;
}
const char_3 = try stdin.takeByte();
k.keycode = char_3;
// bt function keys the strange one
if (char_2 == 'O' and char_3 >= 'P' and char_3 <= 'S') {
k.keycode = char_2;
k.keycode_2 = char_3;
return k;
}
// strange vt function keys we do the only convertion here
std.debug.print("char2 {} {c}\r\n", .{ char_2, char_2 });
std.debug.print("char3 {} {c}\r\n", .{ char_3, char_3 });
// xterm check
if (char_3 >= 'A' and char_3 <= 'O' or char_3 >= 'T' and char_3 <= 'Z') {
return k;
}
const char_4 = try stdin.takeByte();
std.debug.print("char_4 {}\r\n", .{char_4});
// modified xterm check
if (char_3 == '1' and char_4 == ';') {
const char_5 = try stdin.takeByte();
const char_6 = try stdin.takeByte();
k.modifier = char_5;
k.keycode = char_6;
return k;
}
// xterm sequence
if (char_3 == 1 and char_4 >= 'P' and char_4 <= 'S') {
k.keycode_2 = char_4;
return k;
}
if (char_4 == 0x1A) {
return k;
}
// vt sequence with single keycode
if (char_4 == '~') {
return k;
}
// vt sequence with multi keycode
if (char_3 >= '1' and char_3 <= '3' and char_4 != ';') {
k.keycode_2 = char_4;
const tilde = try stdin.takeByte();
if (tilde != '~') {
return null;
}
return k;
}
if (char_4 != ';') {
k.modifier = char_3;
k.keycode = char_4;
return k;
}
if (char_4 == ';') {
const char_5 = try stdin.takeByte();
_ = try stdin.takeByte();
k.modifier = char_5;
k.keycode = char_3;
return k;
}
return null;
}
escape: u8 = 0,
keycode: u8 = 0,
// this fucker is only needed for 2 chars sequences
keycode_2: u8 = 0,
// Shift 1
// (Left) Alt 2
// ctrl 4
// meta 8
modifier: u8 = 0,
pub fn eq(a: Self, b: Self) bool {
return (a.escape == b.escape and
a.keycode == b.keycode and
a.modifier == b.modifier);
}
// make it 64 to make things nice
// this allows for switch cases to be used
pub fn keyToU64(self: Self) u64 {
return (@as(u64, self.escape) << 24) +
(@as(u64, self.modifier) << 16) +
(@as(u64, self.keycode_2) << 8) +
@as(u64, self.keycode);
}
pub fn isRenderable(self: Self) bool {
return switch (self.escape) {
0 => true,
9 => true,
else => false,
};
}
pub fn toChar(self: Self) u8 {
return switch (self.escape) {
0 => self.keycode,
9 => '\t',
else => 0,
};
}
// pub fn format(
// self: Self,
// comptime fmt: []const u8,
// options: std.fmt.FormatOptions,
// writer: anytype,
// ) !void {
// _ = fmt;
// _ = options;
// // if (self.keycode_2 == 0) {
// // try writer.print(
// // "key{{esc={}, keycode={c}, mod={}}}",
// // .{
// // self.escape,
// // self.keycode,
// // self.modifier,
// // },
// // );
// // } else {
// try writer.print(
// "key{{esc={}, keycode=|{c}|{}|, mod={}, id={}}}",
// .{
// self.escape,
// self.keycode,
// self.keycode_2,
// self.modifier,
// self.keyToU64(),
// },
// );
// // }
// }
};
// normal seqs
pub const Enter: Key = Key{
.escape = 13,
};
pub const Backspace: Key = Key{
.escape = 127,
};
// vt sequences:
pub const Home_0: Key = Key{
.escape = escape_char,
.keycode = '1',
};
pub const Insert: Key = Key{
.escape = escape_char,
.keycode = '2',
};
pub const Delete: Key = Key{
.escape = escape_char,
.keycode = '3',
};
pub const End_0: Key = Key{
.escape = escape_char,
.keycode = '4',
};
pub const PgUp: Key = Key{
.escape = escape_char,
.keycode = '5',
};
pub const PgDn: Key = Key{
.escape = escape_char,
.keycode = '6',
};
pub const Home_1: Key = Key{
.escape = escape_char,
.keycode = '7',
};
pub const End_1: Key = Key{
.escape = escape_char,
.keycode = '8',
};
pub const F0: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '0',
};
pub const F1: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '1',
};
pub const F2: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '2',
};
pub const F3: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '3',
};
pub const F4: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '4',
};
pub const F5: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '5',
};
pub const F6: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '7',
};
pub const F7: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '8',
};
pub const F8: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '9',
};
pub const F9: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '0',
};
pub const F10: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '1',
};
pub const F11: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '3',
};
pub const F12: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '4',
};
pub const F13: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '5',
};
pub const F14: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '6',
};
pub const F15: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '8',
};
pub const F16: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '9',
};
pub const F17: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '1',
};
pub const F18: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '2',
};
pub const F19: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '3',
};
pub const F20: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '4',
};
pub const vt_9: Key = Key{
.escape = escape_char,
.keycode = '9',
};
pub const vt_16: Key = Key{
.escape = escape_char,
.keycode = '1',
.keycode_2 = '6',
};
pub const vt_22: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '2',
};
pub const vt_27: Key = Key{
.escape = escape_char,
.keycode = '2',
.keycode_2 = '7',
};
pub const vt_30: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '0',
};
pub const vt_35: Key = Key{
.escape = escape_char,
.keycode = '3',
.keycode_2 = '5',
};
// xterm sequences:
pub const Up = Key{
.escape = escape_char,
.keycode = 'A',
};
pub const Down = Key{
.escape = escape_char,
.keycode = 'B',
};
pub const Right = Key{
.escape = escape_char,
.keycode = 'C',
};
pub const Left = Key{
.escape = escape_char,
.keycode = 'D',
};
pub const xterm_End = Key{
.escape = escape_char,
.keycode = 'F',
};
pub const Keypad_5 = Key{
.escape = escape_char,
.keycode = 'G',
};
pub const xterm_Home = Key{
.escape = escape_char,
.keycode = 'H',
};
pub const xterm_F1 = Key{
.escape = escape_char,
.modifier = 1,
.keycode = 'P',
};
pub const xterm_F2 = Key{
.escape = escape_char,
.modifier = 1,
.keycode = 'Q',
};
pub const xterm_F3 = Key{
.escape = escape_char,
.modifier = 1,
.keycode = 'R',
};
pub const xterm_F4 = Key{
.escape = escape_char,
.modifier = 1,
.keycode = 'S',
};
pub const xterm_E = Key{
.escape = escape_char,
.keycode = 'E',
};
pub const xterm_I = Key{
.escape = escape_char,
.keycode = 'I',
};
pub const xterm_J = Key{
.escape = escape_char,
.keycode = 'J',
};
pub const xterm_K = Key{
.escape = escape_char,
.keycode = 'K',
};
pub const xterm_L = Key{
.escape = escape_char,
.keycode = 'L',
};
pub const xterm_M = Key{
.escape = escape_char,
.keycode = 'M',
};
pub const xterm_N = Key{
.escape = escape_char,
.keycode = 'N',
};
pub const xterm_O = Key{
.escape = escape_char,
.keycode = 'O',
};
pub const xterm_T = Key{
.escape = escape_char,
.keycode = 'T',
};
pub const xterm_U = Key{
.escape = escape_char,
.keycode = 'U',
};
pub const xterm_V = Key{
.escape = escape_char,
.keycode = 'V',
};
pub const xterm_W = Key{
.escape = escape_char,
.keycode = 'W',
};
pub const xterm_X = Key{
.escape = escape_char,
.keycode = 'X',
};
pub const xterm_Y = Key{
.escape = escape_char,
.keycode = 'Y',
};
pub const xterm_Z = Key{
.escape = escape_char,
.keycode = 'Z',
};

480
src/main.zig Normal file
View File

@@ -0,0 +1,480 @@
const std = @import("std");
const terimal_tetris = @import("terimal_tetris");
const Colour = @import("colour.zig").Colour;
const keys = @import("keys.zig");
const c = @cImport({
@cInclude("terminal.h");
});
const colour_set_string_fg_fmt = "\x1B[38;2;{d};{d};{d}m";
const colour_set_string_bg_fmt = "\x1B[48;2;{d};{d};{d}m";
const end_mode = "\x1b[m";
const height = 20;
const width = 10;
const Cell = struct {
colour: Colour,
filled: bool = false, // determins if a give cell as something in it
};
const Tetromino = struct {
const Self = @This();
// this is needed for SRS rotations
// as j, l, s, z, piece can be rotated
// around a 3x3 grid but i need to be
// on a 4x4 grid and o should also be
// on this grid since the transform
// will be the same also i dont
is_4_piece: bool = false, // because it is 90% of the time
colour: Colour = .white(),
bitmap: [4][4]u1 = .{
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
pub fn i() Self {
return Self{
.is_4_piece = true,
.colour = .cyan(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 1, 1, 1, 1 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn t() Self {
return Self{
.colour = .purple(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 1, 1, 1, 0 },
.{ 0, 1, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn j() Self {
return Self{
.colour = .blue(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 1, 1, 1, 0 },
.{ 0, 0, 1, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn l() Self {
return Self{
.colour = .orange(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 1, 1, 1, 0 },
.{ 1, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn s() Self {
return Self{
.colour = .green(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 0, 1, 1, 0 },
.{ 1, 1, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn z() Self {
return Self{
.colour = .red(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 1, 1, 0, 0 },
.{ 0, 1, 1, 0 },
.{ 0, 0, 0, 0 },
},
};
}
pub fn o() Self {
return Self{
.is_4_piece = true,
.colour = .yellow(),
.bitmap = .{
.{ 0, 0, 0, 0 },
.{ 0, 1, 1, 0 },
.{ 0, 1, 1, 0 },
.{ 0, 0, 0, 0 },
},
};
}
// uses SRS rotations
// index[r][c]
// 0 1 2 3 = c
// +-----------
// 0 | 1 2 3 4
// 1 | 5 6 7 8
// 2 | 9 10 11 12
// 3 | 13 14 15 16
// ║
// r
// 3x3
// 0 1 2
// +------
// 0 | 1 2 3
// 1 | 4 5 6
// 2 | 7 8 9
//
pub fn rotate(self: *Self, clockwise: bool) void {
const b = self.bitmap;
if (self.is_4_piece and clockwise) self.bitmap = .{
.{ b[3][0], b[2][0], b[1][0], b[0][0] },
.{ b[3][1], b[2][1], b[1][1], b[0][1] },
.{ b[3][2], b[2][2], b[1][2], b[0][2] },
.{ b[3][3], b[2][3], b[1][3], b[0][3] },
};
if (self.is_4_piece and !clockwise) self.bitmap = .{
.{ b[0][3], b[1][3], b[2][3], b[3][3] },
.{ b[0][2], b[1][2], b[2][2], b[3][2] },
.{ b[0][1], b[1][1], b[2][1], b[3][1] },
.{ b[0][0], b[1][0], b[2][0], b[3][0] },
};
if (!self.is_4_piece and clockwise) self.bitmap = .{
.{ b[2][0], b[1][0], b[0][0], 0 },
.{ b[2][1], b[1][1], b[0][1], 0 },
.{ b[2][2], b[1][2], b[0][2], 0 },
.{ 0, 0, 0, 0 },
};
if (!self.is_4_piece and !clockwise) self.bitmap = .{
.{ b[0][2], b[1][2], b[2][2], 0 },
.{ b[0][1], b[1][1], b[2][1], 0 },
.{ b[0][0], b[1][0], b[2][0], 0 },
.{ 0, 0, 0, 0 },
};
}
};
const Board = struct {
const Self = @This();
grid: [height][width]Cell,
active_tetromino: Tetromino,
tetromino_pos_x: usize = @divTrunc(width, 2) + 2,
tetromino_pos_y: usize = height - 1,
stop: bool = false,
random: std.Random,
pub fn init() Self {
var prng: std.Random.DefaultPrng = .init(@as(usize, @intCast(std.time.timestamp())));
const rand = prng.random();
var self = Self{
.random = rand,
.active_tetromino = undefined,
.grid = undefined,
};
self.clearGrid();
self.resetActiveTetromino();
return self;
}
pub fn resetActiveTetromino(self: *Self) void {
self.tetromino_pos_x = @divTrunc(width, 2) + 2;
self.tetromino_pos_x = 0;
self.tetromino_pos_y = height;
self.active_tetromino = self.newTetromino();
}
pub fn newTetromino(self: Self) Tetromino {
const minos = [_]Tetromino{
Tetromino.i(),
Tetromino.t(),
Tetromino.j(),
Tetromino.l(),
Tetromino.s(),
Tetromino.z(),
Tetromino.o(),
};
_ = self;
// return minos[@mod(self.random.int(usize), 7)];
return minos[0];
}
pub fn clearGrid(self: *Self) void {
for (0..height) |y| {
for (0..width) |x| {
self.grid[y][x] = Cell{
.colour = .black(),
.filled = false,
};
}
}
}
pub fn collided(self: *Self, mino: Tetromino) bool {
for (mino.bitmap, 0..) |mino_row, y| {
for (mino_row, 0..) |mino_cell, x| {
const mino_x = self.tetromino_pos_x + x;
const mino_y = height -| self.tetromino_pos_y + y;
if (mino_cell == 0) continue;
std.debug.print("mino : x {}, y {}\n", .{ mino_x, mino_y });
// make sure that the empty cells dont go out of bounds
if (mino_x >= width and mino_cell == 0) continue;
// hit the bottom
if (mino_y == height - 1) // because of indexing
return true;
// debug check to see where mino is
if (mino_cell == 1) {
self.grid[mino_y][mino_x] = Cell{
.colour = .white(),
.filled = false,
};
}
if (self.grid[mino_y + 1][mino_x].filled)
return true;
}
}
return false;
}
pub fn setGridCells(self: *Self, mino: Tetromino) void {
std.debug.print("========== SETTING GRID CELLS ==========", .{});
for (mino.bitmap, 0..) |mino_row, y| {
for (mino_row, 0..) |mino_cell, x| {
const mino_x = self.tetromino_pos_x + x;
const mino_y = height -| self.tetromino_pos_y + y;
if (mino_cell == 1) {
self.grid[mino_y][mino_x] = Cell{
.colour = mino.colour,
.filled = true,
};
}
}
}
}
// returns if the given tetromino is out of bound from the current board
// the values are the amount x and amount y the mino is out of bounds
pub fn outOfBounds(self: Self, mino: Tetromino) struct { isize, isize } {}
pub fn processInputs(self: *Self, inputs: *std.Io.Reader) void {
const key = getKey(inputs) catch |err| switch (err) {
error.EndOfStream => return,
else => {
self.stop = true;
return;
},
};
if (key == null) return;
std.debug.print("key : {?}\n", .{key});
const exit_key = comptime (keys.Key{ .escape = 17 }).keyToU64();
switch (key.?.keyToU64()) {
exit_key => self.stop = true,
keys.Left.keyToU64() => self.tetromino_pos_x -|= 1,
keys.Right.keyToU64() => {
self.tetromino_pos_x += 1;
// width - 4 since the minos are 4 wide
if (self.tetromino_pos_x + 1 > width - 4) {
self.tetromino_pos_x = width - 4;
}
},
keys.Down.keyToU64() => self.tetromino_pos_y -|= 1,
(keys.Key{ .keycode = 'r' }).keyToU64() => self.active_tetromino.rotate(true),
else => {},
}
}
pub fn step(self: *Self) void {
// if (self.tetromino_pos_y == 0) {}
const active_mino_collided = self.collided(self.active_tetromino);
if (active_mino_collided) {
self.setGridCells(self.active_tetromino);
self.resetActiveTetromino();
}
self.tetromino_pos_y -|= 1;
}
pub fn enterRawMode() void {
c.enter_raw_mode();
}
pub fn exitRawMode() void {
c.exit_raw_mode();
}
pub fn getKey(stdin: *std.Io.Reader) !?keys.Key {
return keys.Key.get(stdin);
}
pub fn clearScreen(writer: *std.Io.Writer) !void {
_ = try writer.write("\x1b[2J");
}
pub fn resetCursor(writer: *std.Io.Writer) !void {
try moveCursorTo(writer, 0, 0);
}
pub fn moveCursorTo(writer: *std.Io.Writer, x: usize, y: usize) !void {
try writer.print("\x1b[{};{}H", .{ y + 1, x + 1 });
}
pub fn drawTeromino(
mino: Tetromino,
x_offset: usize,
y_offset: usize,
writer: *std.Io.Writer,
) !void {
try moveCursorTo(
writer,
x_offset,
y_offset,
);
_ = try writer.write("@");
for (mino.bitmap, 0..) |row, y| {
for (row, 0..) |bit, x| {
if (bit == 1) {
try moveCursorTo(
writer,
((x_offset) + x) * 2 - 1, // mul 2 since the cell is 2 chars wide
y_offset + y,
);
try writer.print(
colour_set_string_fg_fmt ++ "██" ++ end_mode,
.{
mino.colour.r,
mino.colour.b,
mino.colour.g,
},
);
}
}
}
}
pub fn draw(self: Self, writer: *std.Io.Writer) !void {
try resetCursor(writer);
try clearScreen(writer);
// ======== draw top bar ========
_ = try writer.write("\n\r");
for (0..height) |_| {
_ = try writer.write("");
}
_ = try writer.write("\n\r");
// ======== draw top bar ========
// ======== draw play space ========
for (self.grid) |row| {
_ = try writer.write("");
// try writer.print("{} ║", .{i});
for (row) |cell| {
if (cell.filled) {
try writer.print(
colour_set_string_fg_fmt ++ "██" ++ end_mode,
.{
cell.colour.r, cell.colour.b, cell.colour.g,
},
);
} else {
_ = try writer.write(" ");
}
}
_ = try writer.write("\n\r");
}
// ======== draw play space ========
// ======== draw bottom bar ========
_ = try writer.write("");
for (0..height) |_| {
_ = try writer.write("");
}
_ = try writer.write("\n\r");
// ======== draw bottom bar ========
// ======== draw debug info ========
try writer.print("x : {}, y : {}\n\r", .{
self.tetromino_pos_x,
self.tetromino_pos_y,
});
// ======== draw debug info ========
// ======== draw tetromino ========
try drawTeromino(
self.active_tetromino,
self.tetromino_pos_x + 1,
height -| self.tetromino_pos_y + 1,
writer,
);
// ======== draw tetromino ========
try resetCursor(writer);
try writer.flush();
}
};
// we wrap the game to make sure/hope we can exit out of rawmode if something occurs
pub fn gameMain(stdout: *std.Io.Writer, stdin: *std.Io.Reader) !void {
var b = Board.init();
Board.enterRawMode();
while (!b.stop) {
const current_time = std.time.microTimestamp() + 1 * 500000;
while (std.time.microTimestamp() < current_time) continue;
// b.active_tetromino.rotate(true);
b.processInputs(stdin);
b.step();
try b.draw(stdout);
}
Board.exitRawMode();
}
pub fn main() !void {
var stdout_buffer: [1024]u8 = undefined;
var stdin_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer);
const stdout = &stdout_writer.interface;
const stdin = &stdin_reader.interface;
gameMain(stdout, stdin) catch |e| {
Board.exitRawMode();
try Board.clearScreen(stdout);
try stdout.print("game exit got error {any}\n", .{e});
};
}

23
src/root.zig Normal file
View File

@@ -0,0 +1,23 @@
//! By convention, root.zig is the root source file when making a library.
const std = @import("std");
pub fn bufferedPrint() !void {
// Stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
const stdout = &stdout_writer.interface;
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try stdout.flush(); // Don't forget to flush!
}
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
test "basic add functionality" {
try std.testing.expect(add(3, 7) == 10);
}