AoC 2024: Day 4

Main thread for Day 4 of the 2024 advent of code. Feel free to discuss the challenge and ask questions. If particular discussions become large, we can break them off into new threads.

Some Rules:

  1. Please try to keep spoilers within spoiler tags. This is done by typing [spoiler]text to be blurred[\spoiler]. If you have longer sections you can also use the [details=“Title”] tags.
  2. Have Fun

Day 4 Challenge

Templates:

Resources:

Previous days discussions

Day 1: AoC 2024: Day 1
Day 2: AoC 2024: Day 2
Day 3: AoC 2024: Day 3

1 Like

Cheating a little bit by taking the size of the matrix as a comptime parameter to not have to do any heap allocations.

Solution
const std = @import("std");

pub fn part1(input: []const u8, comptime rows: usize, comptime cols: usize) u16 {
    var matrix: [rows][cols]u8 = undefined;
    var it = std.mem.tokenizeScalar(u8, input, '\n');
    var i: usize = 0;
    while (it.next()) |row| : (i += 1) {
        std.mem.copyForwards(u8, &matrix[i], row);
    }
    var count: u16 = 0;
    for (0..rows) |ro| {
        for (0..cols) |co| {
            if (co < cols - 3) {
                const hori: [4]u8 = .{ matrix[ro][co], matrix[ro][co + 1], matrix[ro][co + 2], matrix[ro][co + 3] };
                if (std.mem.eql(u8, &hori, "XMAS") or std.mem.eql(u8, &hori, "SAMX")) count += 1;
            }
            if (ro < rows - 3) {
                const vert: [4]u8 = .{ matrix[ro][co], matrix[ro + 1][co], matrix[ro + 2][co], matrix[ro + 3][co] };
                if (std.mem.eql(u8, &vert, "XMAS") or std.mem.eql(u8, &vert, "SAMX")) count += 1;
            }
            if (ro < rows - 3 and co < cols - 3) {
                const diag: [4]u8 = .{ matrix[ro][co], matrix[ro + 1][co + 1], matrix[ro + 2][co + 2], matrix[ro + 3][co + 3] };
                if (std.mem.eql(u8, &diag, "XMAS") or std.mem.eql(u8, &diag, "SAMX")) count += 1;
            }
            if (ro > 2 and co < cols - 3) {
                const diag: [4]u8 = .{ matrix[ro][co], matrix[ro - 1][co + 1], matrix[ro - 2][co + 2], matrix[ro - 3][co + 3] };
                if (std.mem.eql(u8, &diag, "XMAS") or std.mem.eql(u8, &diag, "SAMX")) count += 1;
            }
        }
    }
    return count;
}

pub fn part2(input: []const u8, comptime rows: usize, comptime cols: usize) u16 {
    var matrix: [rows][cols]u8 = undefined;
    var it = std.mem.tokenizeScalar(u8, input, '\n');
    var i: usize = 0;
    while (it.next()) |row| : (i += 1) {
        std.mem.copyForwards(u8, &matrix[i], row);
    }
    var count: u16 = 0;
    for (1..rows - 1) |ro| {
        for (1..cols - 1) |co| {
            if (matrix[ro][co] == 'A') {
                const lr: [2]u8 = .{ matrix[ro - 1][co - 1], matrix[ro + 1][co + 1] };
                const rl: [2]u8 = .{ matrix[ro - 1][co + 1], matrix[ro + 1][co - 1] };
                if ((std.mem.eql(u8, &lr, "SM") or std.mem.eql(u8, &lr, "MS")) and (std.mem.eql(u8, &rl, "SM") or std.mem.eql(u8, &rl, "MS"))) {
                    count += 1;
                }
            }
        }
    }
    return count;
}
1 Like

I like span

Parsing Content
const Content = struct {
    const Self = @This();

    content: std.ArrayList(u8),
    width: usize,

    pub fn parse(allocator: std.mem.Allocator, file: std.fs.File) std.mem.Allocator.Error!Self {
        const file_reader = file.reader();
        var buffered_reader = std.io.bufferedReader(file_reader);
        var reader = buffered_reader.reader();

        var content = std.ArrayList(u8).init(allocator);

        var width: ?usize = null;
        var line_legnth: usize = 0;
        while (reader.readByte()) |c| {
            if (c == '\n') {
                if (width) |w| {
                    assert(w == line_legnth);
                } else {
                    width = line_legnth;
                }
                line_legnth = 0;
            } else {
                try content.append(c);
                line_legnth += 1;
            }
        } else |_| {}

        return .{ .content = content, .width = width.? };
    }

    pub fn deinit(self: *const Self) void {
        self.content.deinit();
    }
};
Part One
fn OctoSpan(comptime length: u16) type {
    const Generator = struct {
        const Self = @This();

        const Direction = enum { E, SE, S, SW, W, NW, N, NE, ended };

        direction: Direction = .E,
        source: []const u8,
        items: [length]u8 = undefined,
        start: usize,
        height: usize,
        width: usize,

        fn get(self: *const Self, x: usize, y: usize) u8 {
            return self.source[x + y * self.width];
        }

        pub fn arm(self: *Self) ?[]const u8 {
            const x = self.start % self.width;
            const y = self.start / self.width;

            while (self.direction != .ended) {
                defer self.direction = @enumFromInt(@intFromEnum(self.direction) + 1);

                switch (self.direction) {
                    .E => {
                        if (self.width - x < length) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x + i, y);
                        }

                        return &self.items;
                    },
                    .SE => {
                        if ((self.width - x < length) or (self.height - y < length)) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x + i, y + i);
                        }

                        return &self.items;
                    },
                    .S => {
                        if (self.height - y < length) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x, y + i);
                        }
                        return &self.items;
                    },
                    .SW => {
                        if ((x < length - 1) or (self.height - y < length)) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x - i, y + i);
                        }
                        return &self.items;
                    },
                    .W => {
                        if (x < length - 1) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x - i, y);
                        }
                        return &self.items;
                    },
                    .NW => {
                        if ((x < length - 1) or (y < length - 1)) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x - i, y - i);
                        }
                        return &self.items;
                    },
                    .N => {
                        if ((y < length - 1)) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x, y - i);
                        }
                        return &self.items;
                    },
                    .NE => {
                        if ((self.width - x < length) or (y < length - 1)) {
                            continue;
                        }

                        for (0..length) |i| {
                            self.items[i] = self.get(x + i, y - i);
                        }
                        return &self.items;
                    },
                    .ended => return null,
                }
            }

            return null;
        }
    };

    return struct {
        const Self = @This();

        source: []const u8,
        width: usize,

        pub fn init(source: []const u8, width: usize) Self {
            return .{
                .source = source,
                .width = width,
            };
        }

        pub fn arms(self: *const Self, start: usize) Generator {
            return .{
                .source = self.source,
                .start = start,
                .height = self.source.len / self.width,
                .width = self.width,
            };
        }
    };
}

fn partOne(allocator: std.mem.Allocator, file: std.fs.File) !u64 {
    const xmas = "XMAS";

    const content = try Content.parse(allocator, file);
    defer content.deinit();

    const items = content.content.items;
    const width = content.width;

    const span = OctoSpan(xmas.len).init(items, width);

    var result: u64 = 0;
    for (items, 0..) |c, i| {
        if (c != 'X') {
            continue;
        }

        var generator = span.arms(i);

        while (generator.arm()) |arm| {
            if (std.mem.eql(u8, arm, xmas)) {
                result += 1;
            }
        }
    }

    return result;
}
Part Two
fn square_span(comptime side: u16, source: []const u8, width: usize, start: usize) ?[side * side]u8 {
    const height = source.len / width;
    const x = start % width;
    const y = start / width;

    if ((width - x < side) or (height - y < side)) {
        return null;
    }

    var result: [side * side]u8 = undefined;

    for (0..side) |y_span| {
        for (0..side) |x_span| {
            result[x_span + y_span * side] = source[(x + x_span) + (y + y_span) * width];
        }
    }

    std.debug.print("{} {} {s}\n", .{ x, y, result });
    return result;
}

fn partTwo(allocator: std.mem.Allocator, file: std.fs.File) !u64 {
    const content = try Content.parse(allocator, file);
    defer content.deinit();

    const items = content.content.items;
    const width = content.width;

    var result: u64 = 0;
    for (items, 0..) |c, i| {
        if (c != 'M' and c != 'S') {
            continue;
        }

        if (square_span(3, items, width, i)) |span| {
            if (span[4] != 'A') {
                continue;
            }
            if ((span[0] == 'M') and
                (span[2] == 'S') and
                (span[6] == 'M') and
                (span[8] == 'S'))
            {
                result += 1;
            }
            if ((span[0] == 'M') and
                (span[2] == 'M') and
                (span[6] == 'S') and
                (span[8] == 'S'))
            {
                result += 1;
            }
            if ((span[0] == 'S') and
                (span[2] == 'M') and
                (span[6] == 'S') and
                (span[8] == 'M'))
            {
                result += 1;
            }
            if ((span[0] == 'S') and
                (span[2] == 'S') and
                (span[6] == 'M') and
                (span[8] == 'M'))
            {
                result += 1;
            }
        }
    }

    return result;
}

Hi,
after playing with ziglings in the past few month, I decided to tackle AOC for the first time and that I would do it with zig. Here is my solution for day 4. Ugly, but it works. Btw. I come from a C/Fortran background.

Part1
const std = @import("std");

const input = @embedFile("./small.inp");
//const input = @embedFile("./big.inp");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var grid = std.ArrayList([]const u8).init(allocator);
    defer _ = grid.deinit();

    var lines = std.mem.tokenizeScalar(u8, input, '\n');
    while (lines.next()) |line| {
        try grid.append(line);
    }

    // loop over the grid
    var n_right_xmas: i32 = 0;
    var n_left_xmas: i32 = 0;
    var n_down_xmas: i32 = 0;
    var n_up_xmas: i32 = 0;
    var n_rightup_xmas: i32 = 0;
    var n_leftup_xmas: i32 = 0;
    var n_rightdown_xmas: i32 = 0;
    var n_leftdown_xmas: i32 = 0;

    for (grid.items, 0..) |row, irow| {
        for (row, 0..) |_, icol| {
            if (icol < row.len - 3) {
                if (is_right_xmas(grid, irow, icol)) {
                    n_right_xmas += 1;
                }
            }
            if (icol > 2) {
                if (is_left_xmas(grid, irow, icol)) {
                    n_left_xmas += 1;
                }
            }
            if (irow < grid.items.len - 3) {
                if (is_down_xmas(grid, irow, icol)) {
                    n_down_xmas += 1;
                }
            }
            if (irow > 2) {
                if (is_up_xmas(grid, irow, icol)) {
                    n_up_xmas += 1;
                }
            }
            if (icol < row.len - 3 and irow > 2) {
                if (is_rightup_xmas(grid, irow, icol)) {
                    n_rightup_xmas += 1;
                }
            }
            if (icol < row.len - 3 and irow < grid.items.len - 3) {
                if (is_rightdown_xmas(grid, irow, icol)) {
                    n_rightdown_xmas += 1;
                }
            }
            if (icol > 2 and irow > 2) {
                if (is_leftup_xmas(grid, irow, icol)) {
                    n_leftup_xmas += 1;
                }
            }
            if (icol > 2 and irow < grid.items.len - 3) {
                if (is_leftdown_xmas(grid, irow, icol)) {
                    n_leftdown_xmas += 1;
                }
            }
        }
        //std.debug.print("\n", .{});
    }

    const n_total_xmas = n_right_xmas + n_left_xmas + n_down_xmas + n_up_xmas + n_rightup_xmas + n_leftup_xmas + n_rightdown_xmas + n_leftdown_xmas;

    std.debug.print("Count of right \"XMAS\":      {d}\n", .{n_right_xmas});
    std.debug.print("Count of left \"XMAS\":       {d}\n", .{n_left_xmas});
    std.debug.print("Count of down \"XMAS\":       {d}\n", .{n_down_xmas});
    std.debug.print("Count of up \"XMAS\":         {d}\n", .{n_up_xmas});
    std.debug.print("Count of right up \"XMAS\":   {d}\n", .{n_rightup_xmas});
    std.debug.print("Count of left up \"XMAS\":    {d}\n", .{n_leftup_xmas});
    std.debug.print("Count of right down \"XMAS\": {d}\n", .{n_rightdown_xmas});
    std.debug.print("Count of right up \"XMAS\":   {d}\n", .{n_rightup_xmas});
    std.debug.print("\nCount of right up \"XMAS\":   {d}\n", .{n_total_xmas});
}

fn is_right_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow][icol + 0] == 'X' and
        grid.items[irow][icol + 1] == 'M' and
        grid.items[irow][icol + 2] == 'A' and
        grid.items[irow][icol + 3] == 'S')
    {
        return true;
    }
    return false;
}

fn is_left_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow][icol - 0] == 'X' and
        grid.items[irow][icol - 1] == 'M' and
        grid.items[irow][icol - 2] == 'A' and
        grid.items[irow][icol - 3] == 'S')
    {
        return true;
    }
    return false;
}

fn is_down_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol] == 'X' and
        grid.items[irow + 1][icol] == 'M' and
        grid.items[irow + 2][icol] == 'A' and
        grid.items[irow + 3][icol] == 'S')
    {
        return true;
    }
    return false;
}

fn is_up_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow - 0][icol] == 'X' and
        grid.items[irow - 1][icol] == 'M' and
        grid.items[irow - 2][icol] == 'A' and
        grid.items[irow - 3][icol] == 'S')
    {
        return true;
    }
    return false;
}

fn is_rightup_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow - 0][icol + 0] == 'X' and
        grid.items[irow - 1][icol + 1] == 'M' and
        grid.items[irow - 2][icol + 2] == 'A' and
        grid.items[irow - 3][icol + 3] == 'S')
    {
        return true;
    }
    return false;
}

fn is_leftup_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow - 0][icol - 0] == 'X' and
        grid.items[irow - 1][icol - 1] == 'M' and
        grid.items[irow - 2][icol - 2] == 'A' and
        grid.items[irow - 3][icol - 3] == 'S')
    {
        return true;
    }
    return false;
}

fn is_rightdown_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol + 0] == 'X' and
        grid.items[irow + 1][icol + 1] == 'M' and
        grid.items[irow + 2][icol + 2] == 'A' and
        grid.items[irow + 3][icol + 3] == 'S')
    {
        return true;
    }
    return false;
}

fn is_leftdown_xmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol - 0] == 'X' and
        grid.items[irow + 1][icol - 1] == 'M' and
        grid.items[irow + 2][icol - 2] == 'A' and
        grid.items[irow + 3][icol - 3] == 'S')
    {
        return true;
    }
    return false;
}
Part2
const std = @import("std");

//const input = @embedFile("./small.inp");
const input = @embedFile("./big.inp");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var grid = std.ArrayList([]const u8).init(allocator);
    defer _ = grid.deinit();

    var lines = std.mem.tokenizeScalar(u8, input, '\n');
    while (lines.next()) |line| {
        try grid.append(line);
    }

    // loop over the grid
    var n_masmas: i32 = 0;
    var n_massam: i32 = 0;
    var n_sammas: i32 = 0;
    var n_samsam: i32 = 0;

    for (grid.items[0 .. grid.items.len - 2], 0..) |row, irow| {
        for (row[0 .. row.len - 2], 0..) |_, icol| {
            if (is_masmas(grid, irow, icol)) {
                n_masmas += 1;
            }
            if (is_massam(grid, irow, icol)) {
                n_massam += 1;
            }
            if (is_sammas(grid, irow, icol)) {
                n_sammas += 1;
            }
            if (is_samsam(grid, irow, icol)) {
                n_samsam += 1;
            }
        }
    }

    const n_total_xmas = n_masmas + n_massam + n_sammas + n_samsam;

    std.debug.print("Count of \"masmas\" : {d}\n", .{n_masmas});
    std.debug.print("Count of \"massam\" : {d}\n", .{n_massam});
    std.debug.print("Count of \"sammas\" : {d}\n", .{n_sammas});
    std.debug.print("Count of \"samsam\" : {d}\n", .{n_samsam});
    std.debug.print("\nCount of \"X-MAS\": {d}\n", .{n_total_xmas});
}

fn is_masmas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol + 0] == 'M' and
        grid.items[irow + 1][icol + 1] == 'A' and
        grid.items[irow + 2][icol + 2] == 'S' and
        grid.items[irow + 2][icol + 0] == 'M' and
        grid.items[irow + 0][icol + 2] == 'S')
    {
        return true;
    }
    return false;
}
fn is_massam(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol + 0] == 'M' and
        grid.items[irow + 1][icol + 1] == 'A' and
        grid.items[irow + 2][icol + 2] == 'S' and
        grid.items[irow + 2][icol + 0] == 'S' and
        grid.items[irow + 0][icol + 2] == 'M')
    {
        return true;
    }
    return false;
}
fn is_sammas(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol + 0] == 'S' and
        grid.items[irow + 1][icol + 1] == 'A' and
        grid.items[irow + 2][icol + 2] == 'M' and
        grid.items[irow + 2][icol + 0] == 'M' and
        grid.items[irow + 0][icol + 2] == 'S')
    {
        return true;
    }
    return false;
}
fn is_samsam(grid: std.ArrayList([]const u8), irow: usize, icol: usize) bool {
    if (grid.items[irow + 0][icol + 0] == 'S' and
        grid.items[irow + 1][icol + 1] == 'A' and
        grid.items[irow + 2][icol + 2] == 'M' and
        grid.items[irow + 2][icol + 0] == 'S' and
        grid.items[irow + 0][icol + 2] == 'M')
    {
        return true;
    }
    return false;
}

My solution:

Parts 1 and 2
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() !void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    var lines: std.ArrayListUnmanaged([]const u8) = .empty;
    defer lines.deinit(allocator);

    var line_it = std.mem.tokenizeScalar(u8, input, '\n');
    while (line_it.next()) |line_maybe_with_carriage_return| {
        const line = std.mem.trimRight(u8, line_maybe_with_carriage_return, "\r");
        if (line.len == 0) continue;
        // No possible horizontal/diagonal XMAS without 4 chars in a line
        std.debug.assert(line.len >= 4);
        try lines.append(allocator, line);
    }
    // No possible vertical/diagonal XMAS without at least 4 lines
    std.debug.assert(lines.items.len >= 4);

    // We're assuming all lines have the same length
    const line_len = lines.items[0].len;

    // Part 1
    const xmas_count = xmas_count: {
        var xmas_count: usize = 0;
        var section_buf: [4]*const [4]u8 = undefined;
        var y: usize = 0;
        while (y < lines.items.len - 3) : (y += 1) {
            var x: usize = 0;
            while (x < line_len - 3) : (x += 1) {
                section_buf[0] = lines.items[y][x..][0..4];
                section_buf[1] = lines.items[y + 1][x..][0..4];
                section_buf[2] = lines.items[y + 2][x..][0..4];
                section_buf[3] = lines.items[y + 3][x..][0..4];
                xmas_count += xmasCount(&section_buf, y == lines.items.len - 4, x == line_len - 4);
            }
        }
        break :xmas_count xmas_count;
    };

    // Part 2
    const mas_in_x_count = mas_in_x_count: {
        var mas_in_x_count: usize = 0;
        var section_buf: [3]*const [3]u8 = undefined;
        var y: usize = 0;
        while (y < lines.items.len - 2) : (y += 1) {
            var x: usize = 0;
            while (x < line_len - 2) : (x += 1) {
                section_buf[0] = lines.items[y][x..][0..3];
                section_buf[1] = lines.items[y + 1][x..][0..3];
                section_buf[2] = lines.items[y + 2][x..][0..3];
                if (isMASinX(&section_buf)) mas_in_x_count += 1;
            }
        }
        break :mas_in_x_count mas_in_x_count;
    };

    std.debug.print("{}\n", .{xmas_count});
    std.debug.print("{}\n", .{mas_in_x_count});
}

fn xmasCount(section: *const [4]*const [4]u8, check_all_horizontal_lines: bool, check_all_vertical_lines: bool) u4 {
    var count: u4 = 0;
    if (isHorizontalXmas(section[0])) count += 1;
    if (check_all_horizontal_lines) {
        if (isHorizontalXmas(section[1])) count += 1;
        if (isHorizontalXmas(section[2])) count += 1;
        if (isHorizontalXmas(section[3])) count += 1;
    }
    if (isVerticalXmas(section, 0)) count += 1;
    if (check_all_vertical_lines) {
        if (isVerticalXmas(section, 1)) count += 1;
        if (isVerticalXmas(section, 2)) count += 1;
        if (isVerticalXmas(section, 3)) count += 1;
    }
    if (isDiagonalXmas(section, .top_left_to_bottom_right)) count += 1;
    if (isDiagonalXmas(section, .bottom_left_to_top_right)) count += 1;
    return count;
}

fn isDiagonalXmas(section: *const [4]*const [4]u8, dir: enum { top_left_to_bottom_right, bottom_left_to_top_right }) bool {
    switch (dir) {
        .top_left_to_bottom_right => {
            if (section[0][0] == 'X' and section[1][1] == 'M' and section[2][2] == 'A' and section[3][3] == 'S') return true;
            if (section[0][0] == 'S' and section[1][1] == 'A' and section[2][2] == 'M' and section[3][3] == 'X') return true;
            return false;
        },
        .bottom_left_to_top_right => {
            if (section[3][0] == 'X' and section[2][1] == 'M' and section[1][2] == 'A' and section[0][3] == 'S') return true;
            if (section[3][0] == 'S' and section[2][1] == 'A' and section[1][2] == 'M' and section[0][3] == 'X') return true;
            return false;
        },
    }
}

fn isVerticalXmas(section: *const [4]*const [4]u8, x: u3) bool {
    if (section[0][x] == 'X' and section[1][x] == 'M' and section[2][x] == 'A' and section[3][x] == 'S') return true;
    if (section[0][x] == 'S' and section[1][x] == 'A' and section[2][x] == 'M' and section[3][x] == 'X') return true;
    return false;
}

fn isHorizontalXmas(line: *const [4]u8) bool {
    return std.mem.eql(u8, line, "XMAS") or std.mem.eql(u8, line, "SAMX");
}

fn isMASinX(section: *const [3]*const [3]u8) bool {
    if (section[1][1] != 'A') return false;
    if (!((section[0][0] == 'M' and section[2][2] == 'S') or (section[0][0] == 'S' and section[2][2] == 'M'))) return false;
    if (!((section[2][0] == 'M' and section[0][2] == 'S') or (section[2][0] == 'S' and section[0][2] == 'M'))) return false;
    return true;
}

Checked in 4x4 chunks, but it only checks all horizontal lines for the bottom chunks and all vertical lines for the rightmost chunks. The second part was easier since you can just check 3x3 chunks without having to worry about double counting anything.

not very pretty but it works :smiley:

part 1 & 2
const std = @import("std");

const util = @import("util.zig");
const gpa = util.gpa;

const data = @embedFile("data/day04.txt");

var buf1 = [4][]const u8{ "", "", "", "" };
var buf2 = [3][]const u8{ "", "", "" };

pub fn main() !void {
    const p1: i64 = part1();
    const p2: i64 = part2();

    std.debug.print("part1: {}\n", .{p1});
    std.debug.print("part2: {}\n", .{p2});
}

fn part1() i64 {
    var p1: i64 = 0;

    var lines = std.mem.tokenizeScalar(u8, data, '\n');
    buf1[0] = lines.next().?;
    buf1[1] = lines.next().?;
    buf1[2] = lines.next().?;
    buf1[3] = lines.next().?;

    while (lines.next()) |line| {
        p1 += findXmas();
        buf1[0] = buf1[1];
        buf1[1] = buf1[2];
        buf1[2] = buf1[3];
        buf1[3] = line;
    }

    p1 += findXmas();
    for (1..4) |i| {
        for (buf1[i], 0..) |c, j| {
            if (c == 'X' and j + 3 < buf1[i].len and isXmas(buf1[i][j .. j + 4])) p1 += 1;
            if (c == 'S' and j + 3 < buf1[i].len and std.mem.eql(u8, buf1[i][j .. j + 4], "SAMX")) p1 += 1;
        }
    }

    return p1;
}

fn part2() i64 {
    var p2: i64 = 0;

    var lines = std.mem.tokenizeScalar(u8, data, '\n');
    buf2[0] = lines.next().?;
    buf2[1] = lines.next().?;
    buf2[2] = lines.next().?;

    while (lines.next()) |line| {
        p2 += findMas();

        buf2[0] = buf2[1];
        buf2[1] = buf2[2];
        buf2[2] = line;
    }

    p2 += findMas();

    return p2;
}

fn findXmas() i64 {
    var matches: i64 = 0;

    for (buf1[0], 0..) |c, i| {
        if (c == 'X') {
            if (i + 3 < buf1[0].len and isXmas(buf1[0][i .. i + 4])) matches += 1;

            const down = [_]u8{ buf1[0][i], buf1[1][i], buf1[2][i], buf1[3][i] };
            if (isXmas(&down)) matches += 1;

            if (i + 3 < buf1[0].len) {
                const diag_right = [_]u8{ buf1[0][i], buf1[1][i + 1], buf1[2][i + 2], buf1[3][i + 3] };
                if (isXmas(&diag_right)) matches += 1;
            }

            if (i >= 3) {
                const diag_left = [_]u8{ buf1[0][i], buf1[1][i - 1], buf1[2][i - 2], buf1[3][i - 3] };
                if (isXmas(&diag_left)) matches += 1;
            }
        }

        if (c == 'S') {
            if (i + 3 < buf1[0].len and std.mem.eql(u8, buf1[0][i .. i + 4], "SAMX")) matches += 1;

            const up = [_]u8{ buf1[3][i], buf1[2][i], buf1[1][i], buf1[0][i] };
            if (isXmas(&up)) matches += 1;

            if (i + 3 < buf1[0].len) {
                const diag_right = [_]u8{ buf1[3][i + 3], buf1[2][i + 2], buf1[1][i + 1], buf1[0][i] };
                if (isXmas(&diag_right)) matches += 1;
            }

            if (i >= 3) {
                const diag_left = [_]u8{ buf1[3][i - 3], buf1[2][i - 2], buf1[1][i - 1], buf1[0][i - 0] };
                if (isXmas(&diag_left)) matches += 1;
            }
        }
    }

    return matches;
}

fn isXmas(str: []const u8) bool {
    return std.mem.eql(u8, str, "XMAS");
}

fn findMas() i64 {
    var matches: i64 = 0;

    for (buf2[1], 0..) |c, i| {
        if (c != 'A') continue;
        if (i == 0 or i == buf2[1].len - 1) continue;

        var left = [_]u8{ buf2[0][i - 1], buf2[1][i], buf2[2][i + 1] };
        if (!isMas(&left)) continue;

        var right = [_]u8{ buf2[2][i - 1], buf2[1][i], buf2[0][i + 1] };
        if (!isMas(&right)) continue;

        matches += 1;
    }

    return matches;
}

fn isMas(str: []const u8) bool {
    return std.mem.eql(u8, str, "MAS") or std.mem.eql(u8, str, "SAM");
}

Dumb but simple:

Part 1
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    var line_buf = std.ArrayList([]const u8).init(arena.allocator());
    var line_iter = std.mem.tokenizeScalar(u8, input, '\n');
    while (line_iter.next()) |line| {
        try line_buf.append(line);
    }
    const lines = line_buf.items;

    var xmas_count: u32 = 0;

    for (0..lines.len) |row| {
        for (0..lines[row].len) |col| {
            if (lines[row][col] != 'X') continue;

            const check_left = col >= 3;
            const check_right = col + 3 < lines[row].len;
            const check_up = row >= 3;
            const check_down = row + 3 < line_buf.items.len;

            if (check_left and eqlMAS(&.{
                lines[row][col - 1],
                lines[row][col - 2],
                lines[row][col - 3],
            })) {
                xmas_count += 1;
            }

            if (check_right and eqlMAS(&.{
                lines[row][col + 1],
                lines[row][col + 2],
                lines[row][col + 3],
            })) {
                xmas_count += 1;
            }

            if (check_up) {
                if (eqlMAS(&.{
                    lines[row - 1][col],
                    lines[row - 2][col],
                    lines[row - 3][col],
                })) {
                    xmas_count += 1;
                }

                if (check_left and eqlMAS(&.{
                    lines[row - 1][col - 1],
                    lines[row - 2][col - 2],
                    lines[row - 3][col - 3],
                })) {
                    xmas_count += 1;
                }

                if (check_right and eqlMAS(&.{
                    lines[row - 1][col + 1],
                    lines[row - 2][col + 2],
                    lines[row - 3][col + 3],
                })) {
                    xmas_count += 1;
                }
            }

            if (check_down) {
                if (eqlMAS(&.{
                    lines[row + 1][col],
                    lines[row + 2][col],
                    lines[row + 3][col],
                })) {
                    xmas_count += 1;
                }

                if (check_left and eqlMAS(&.{
                    lines[row + 1][col - 1],
                    lines[row + 2][col - 2],
                    lines[row + 3][col - 3],
                })) {
                    xmas_count += 1;
                }

                if (check_right and eqlMAS(&.{
                    lines[row + 1][col + 1],
                    lines[row + 2][col + 2],
                    lines[row + 3][col + 3],
                })) {
                    xmas_count += 1;
                }
            }
        }
    }

    std.debug.print("{d}\n", .{xmas_count});
}

fn eqlMAS(s: *const [3]u8) bool {
    return std.mem.eql(u8, s, "MAS");
}
Part 2
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    var line_buf = std.ArrayList([]const u8).init(arena.allocator());
    var line_iter = std.mem.tokenizeScalar(u8, input, '\n');
    while (line_iter.next()) |line| {
        try line_buf.append(line);
    }
    const lines = line_buf.items;

    var xmas_count: u32 = 0;

    for (1..lines.len - 1) |row| {
        for (1..lines[row].len - 1) |col| {
            if (lines[row][col] != 'A') continue;

            const top_left = lines[row - 1][col - 1];
            const top_right = lines[row - 1][col + 1];
            const bot_left = lines[row + 1][col - 1];
            const bot_right = lines[row + 1][col + 1];

            if ((top_left == 'M' and top_right == 'M' and bot_left == 'S' and bot_right == 'S') or
                (top_left == 'S' and top_right == 'S' and bot_left == 'M' and bot_right == 'M') or
                (top_left == 'M' and top_right == 'S' and bot_left == 'M' and bot_right == 'S') or
                (top_left == 'S' and top_right == 'M' and bot_left == 'S' and bot_right == 'M'))
            {
                xmas_count += 1;
            }
        }
    }

    std.debug.print("{d}\n", .{xmas_count});
}

I tried to leverage SIMD instructions here, but I’m stuck with the wrong result, damn. The idea is to move a 4x4 window around the grid, turn it into a @Vector(16,u8) and use SIMD to check for XMAS in the first line, first column, downward and upward diagonals.

If anyone can spot what I’m doing wrong here…

Solution
const std = @import("std");
const u = @import("util.zig");

const data = @embedFile("data/day04.txt");

fn extract(line: usize, column: usize) @Vector(16, u8) {
    u.assert(line < 140 - 3);
    u.assert(column < 140 - 3);

    var s: [16]u8 = undefined;
    for (0..4) |i| {
        const start = (line + i) * 141 + column;
        @memcpy(
            s[4 * i .. 4 * (i + 1)],
            data[start .. start + 4],
        );
    }

    return s;
}

const horizontal: @Vector(16, u8) = "XMASXMASXMASXMAS".*;
const horizontal_reverse: @Vector(16, u8) = "SAMXSAMXSAMXSAMX".*;

const vertical: @Vector(16, u8) = "XXXXMMMMAAAASSSS".*;
const vertical_reverse: @Vector(16, u8) = "SSSSAAAAMMMMXXXX".*;

const horizontal_mask: @Vector(16, bool) = .{
    true,  true,  true,  true,
    false, false, false, false,
    false, false, false, false,
    false, false, false, false,
};

const vertical_mask: @Vector(16, bool) = .{
    true, false, false, false,
    true, false, false, false,
    true, false, false, false,
    true, false, false, false,
};

const downward_mask: @Vector(16, bool) = .{
    true,  false, false, false,
    false, true,  false, false,
    false, false, true,  false,
    false, false, false, true,
};

const upward_mask: @Vector(16, bool) = .{
    false, false, false, true,
    false, false, true,  false,
    false, true,  false, false,
    true,  false, false, false,
};

const allTrue: @Vector(16, bool) = @splat(true);

fn check(mask: @Vector(16, bool), direction: @Vector(16, u8), square: @Vector(16, u8)) bool {
    return @reduce(.And, @select(bool, mask, (square == direction), allTrue));
}

fn count(s: @Vector(16, u8)) usize {
    var total: usize = 0;
    // horizontal
    if (check(horizontal_mask, horizontal, s)) total += 1;
    if (check(horizontal_mask, horizontal_reverse, s)) total += 1;
    // vertical
    if (check(vertical_mask, vertical, s)) total += 1;
    if (check(vertical_mask, vertical_reverse, s)) total += 1;
    // downward
    if (check(downward_mask, horizontal, s)) total += 1;
    if (check(downward_mask, horizontal_reverse, s)) total += 1;
    // upward
    if (check(upward_mask, horizontal, s)) total += 1;
    if (check(upward_mask, horizontal_reverse, s)) total += 1;
    return total;
}

fn printSquare(s: @Vector(16, u8)) void {
    const a: [16]u8 = s;
    u.print("{s}\n", .{a[0..4]});
    u.print("{s}\n", .{a[4..8]});
    u.print("{s}\n", .{a[8..12]});
    u.print("{s}\n", .{a[12..16]});
    u.print("{}\n\n", .{count(s)});
}

pub fn main() !void {
    var total: usize = 0;
    for (0..137) |line| {
        for (0..137) |col| {
            const e = extract(line, col);
            printSquare(e);
            total += count(e);
        }
    }
    u.print("{any}\n", .{total});
 
   // u.print("{any}\n", .{count("XMAS............".*)});
    // u.print("{any}\n", .{count("SAMX............".*)});
    // u.print("{any}\n", .{count("X...M...A...S...".*)});
    // u.print("{any}\n", .{count("S...A...M...X...".*)});
    // u.print("{any}\n", .{count("X....M....A....S".*)});
    // u.print("{any}\n", .{count("S....A....M....X".*)});
    // u.print("{any}\n", .{count("S...A...M...X...".*)});
    // u.print("{any}\n", .{count("X...M...A...S...".*)});
}

EDIT: nevermind, that was silly - the SIMD part is working, I just forgot to add some padding at the end of each line and at the end of the file so I can scan the whole file.

I originally had a solution with negative offsets (translated from my Lisp version), but this caused various int conversion issues. So I rewrote to only use positive offsets.

My solution
const std = @import("std");

pub fn main() !void {
    // get allocator
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    const allocator = arena.allocator();
    defer _ = arena.deinit();

    // get command-line args
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    // see if filename is on command-line
    if (args.len == 2) {
        const filename = args[1];
        solveDay(allocator, filename) catch |err| {
            switch (err) {
                error.FileNotFound => {
                    std.debug.print("Error: File {s} was not found.\n",
                        .{filename});
                    std.process.exit(1);
                },
                else => {
                    std.debug.print("Error: in processing file.\n", .{}
                    );
                    std.process.exit(1);
                },
            }
        };
    } else {
        std.debug.print("Provide a filename of input data.\n", .{});
    }
}

fn solveDay (allocator: std.mem.Allocator, filename: []const u8) !void {
    var grid = std.ArrayList([]const u8).init(allocator);
    defer grid.deinit();
    try readData (allocator, filename, &grid);

    std.debug.print("Part 1: {d}\n", .{part1(&grid)});
    std.debug.print("Part 2: {d}\n", .{part2(&grid)});
}

fn part1 (grid: *std.ArrayList([]const u8)) usize {
    var total: usize = 0;

    for (grid.items, 0..) |_, row| {
        for (grid.items[row], 0..) |_, col| {
            // look for xmas/samx in four directions
            if (foundXmas(grid, row, col, 1, 0)) {
                total += 1;
            }
            if (foundXmas(grid, row, col, 0, 1)) {
                total += 1;
            }
            if (foundXmas(grid, row, col, 1, 1)) {
                total += 1;
            }
            // can't do this last one without a negative direction, so call directly
            if (isXmas(safe_get(grid, row, col+3), safe_get(grid, row+1, col+2), 
                    safe_get(grid, row+2, col+1), safe_get(grid, row+3, col))) {
                total += 1;
            }
        }
    }

    return total;
}

fn part2 (grid: *std.ArrayList([]const u8)) usize {
    var total: usize = 0;

    for (grid.items, 0..) |_, row| {
        for (grid.items[row], 0..) |_, col| {
            // look for mas centred on (+1, +1)
            if (('A' == safe_get(grid, row + 1, col + 1)) and
                isMAndS(safe_get(grid, row, col), safe_get(grid, row + 2, col + 2)) and
                isMAndS(safe_get(grid, row + 2, col), safe_get(grid, row, col + 2))) {
                total += 1;
            }
        }
    }

    return total;
}

fn isXmas (charA: u8, charB: u8, charC: u8, charD: u8) bool {
    return ('X' == charA and 'M' == charB and 'A' == charC and 'S' == charD) or
        ('X' == charD and 'M' == charC and 'A' == charB and 'S' == charA);
}

fn foundXmas(grid: *std.ArrayList([] const u8), row: usize, col: usize, rowDelta: usize, colDelta: usize) bool {
    return isXmas(safe_get(grid, row, col), 
        safe_get(grid, row + rowDelta, col + colDelta),
        safe_get(grid, row + rowDelta + rowDelta, col + colDelta + colDelta), 
        safe_get(grid, row + rowDelta + rowDelta + rowDelta, col + colDelta + colDelta + colDelta)); 
}

fn isMAndS (charA: u8, charB: u8) bool {
    return ('M' == charA and 'S' == charB) or ('S' == charA and 'M' == charB);
}

// When (row,col) within grid bounds, return char at that point, else return '.'.
fn safe_get (grid: *std.ArrayList([]const u8), row: usize, col: usize) u8 {
    if (0 <= row and row < grid.items.len and 0 <= col and col < grid.items[row].len) {
        return grid.items[row][col];
    } else {
        return '.';
    }
}

fn readData (allocator: std.mem.Allocator, filename: []const u8, grid: *std.ArrayList([]const u8)) !void 
{ 
    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();
    const stat = try file.stat();
    const data = try file.readToEndAlloc(allocator, stat.size);

    var lines = std.mem.tokenizeScalar(u8, data, '\n');
    while (lines.next()) |line| {
        try grid.append(line);
    }
}

First I tried to parse the data as a slice of slices, reading line by line, but then I found a simpler solution that works with the original []const u8 input.

Part 1
const std = @import("std");

const data_file = @embedFile("./data.txt");
const data: []const u8 = @as([]const u8, data_file);

pub fn main() !void {
    // get the matrix dimmensions
    var n_cols: i32 = 0;
    for (data, 0..) |v, idx| {
        if (v == '\n') {
            n_cols = @intCast(idx);
            break;
        }
    }

    var count: u32 = 0;
    // Valid directions E, SE, S, SW, W, NW, N, NE
    for (0..data.len) |i| {
        if (data[i] != 'X') continue;
        //E
        if (check_xmas(data, i, 1)) count += 1;
        //W
        if (check_xmas(data, i, -1)) count += 1;
        //S
        if (check_xmas(data, i, n_cols + 1)) count += 1;
        // N
        if (check_xmas(data, i, -(n_cols + 1))) count += 1;
        // SE
        if (check_xmas(data, i, n_cols + 2)) count += 1;
        // SW
        if (check_xmas(data, i, n_cols)) count += 1;
        // NW
        if (check_xmas(data, i, -(n_cols + 2))) count += 1;
        // NE
        if (check_xmas(data, i, -(n_cols))) count += 1;
    }

    std.debug.print("Count: {d}\n", .{count});
}

pub fn check_xmas(d: []const u8, i: usize, delta: i32) bool {
    const idx2: usize = addToUsize(i, delta) orelse return false;
    const idx3: usize = addToUsize(i, 2 * delta) orelse return false;
    const idx4: usize = addToUsize(i, 3 * delta) orelse return false;
    if (idx2 > d.len or idx3 > d.len or idx4 > d.len) return false;
    return (d[idx2] == 'M' and d[idx3] == 'A' and d[idx4] == 'S');
}

pub fn addToUsize(base: usize, offset: i32) ?usize {
    const signed_base: i64 = @intCast(base);
    const result = signed_base + offset;
    return if (result < 0) null else @as(usize, @intCast(result));
}
Part 2
const std = @import("std");

const data_file = @embedFile("./data.txt");
const data: []const u8 = @as([]const u8, data_file);

pub fn main() !void {
    // get the matrix dimmensions
    var n_cols: i32 = 0;
    for (data, 0..) |v, idx| {
        if (v == '\n') {
            n_cols = @intCast(idx);
            break;
        }
    }

    var count: u32 = 0;
    for (0..data.len) |i| {
        if (data[i] != 'A') continue;
        const top_left = getCorner(i, -(n_cols + 2), data) orelse continue;
        const top_right = getCorner(i, -n_cols, data) orelse continue;
        const bot_left = getCorner(i, n_cols, data) orelse continue;
        const bot_right = getCorner(i, n_cols + 2, data) orelse continue;

        if (((top_left == 'M' and bot_right == 'S') or (top_left == 'S' and bot_right == 'M')) and
            ((top_right == 'M' and bot_left == 'S') or (top_right == 'S' and bot_left == 'M')))
        {
            count += 1;
        }
    }

    std.debug.print("Count: {d}\n", .{count});
}

pub fn getCorner(base: usize, offset: i32, d: []const u8) ?u8 {
    const signed_base: i64 = @intCast(base);
    const result = signed_base + offset;
    return if (result < 0 or result >= d.len) null else d[@as(usize, @intCast(result))];
}

https://zigbin.io/ce7f2d. i used a grid with padding to avoid bounds checks. part2 code turned out pretty nice.

I just tried to do it with the ease, find the pattern via X for part 1 and via A for part 2 all in just 1 iteration over the nested for loops.

https://zigbin.io/41ad1b

I spent way too much time tweaking my multidimensional array. Which is very overkill here.

I like to add simd index offsets to find neighbors.

https://zigbin.io/907522

a bit late to the party but surprised that part2 was much simpler than part1

Part 1
const std = @import("std");

const Direction = enum {
    north,
    south,
    east,
    west,
    north_east,
    north_west,
    south_east,
    south_west,

    fn toPos(direction: Direction) Position {
        return switch (direction) {
            .north => Position.init(0, -1),
            .south => Position.init(0, 1),
            .east => Position.init(1, 0),
            .west => Position.init(-1, 0),
            .north_east => Position.init(1, -1),
            .south_east => Position.init(1, 1),
            .north_west => Position.init(-1, -1),
            .south_west => Position.init(-1, 1),
        };
    }

    const directions = [_]Direction{ Direction.north, .north_east, .east, .south_east, .south, .south_west, .west, .north_west };
};

const Position = struct {
    x: i32,
    y: i32,

    fn init(x: i32, y: i32) Position {
        return .{
            .x = x,
            .y = y,
        };
    }

    fn insideRangeOrNull(pos: Position, min: Position, max: Position) ?Position {
        if (pos.x < min.x or pos.x > max.x) return null;
        if (pos.y < min.y or pos.y > max.y) return null;
        return pos;
    }

    fn add(p1: Position, p2: Position) Position {
        return Position.init(p1.x + p2.x, p1.y + p2.y);
    }
};

fn buildGrid(data: []const u8, allocator: std.mem.Allocator) []const []const u8 {
    var list: std.ArrayList([]const u8) = .init(allocator);
    defer list.deinit();
    var line_iter = std.mem.tokenizeAny(u8, data, "\n");
    while (line_iter.next()) |line| {
        list.append(line) catch unreachable;
    }
    return list.toOwnedSlice() catch unreachable;
}

pub fn main() !void {
    var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
    defer arena_allocator.deinit();
    const arena = arena_allocator.allocator();
    const input = @embedFile("inputs/inputs.txt");
    const grid = buildGrid(input, arena);
    const min_pos: Position = .init(0, 0);
    const max_pos: Position = .init(@intCast(grid[0].len - 1), @intCast(grid.len - 1));

    const directions = Direction.directions;
    var count: usize = 0;

    for (grid, 0..) |row, y| {
        for (row, 0..) |_, x| {
            const start_pos = Position.init(@intCast(x), @intCast(y));

            for (directions) |dir| {
                var pos = start_pos;
                var valid = true;

                for (0..4) |step| {
                    const bounded_pos = Position.insideRangeOrNull(pos, min_pos, max_pos);

                    if (bounded_pos == null or grid[@intCast(bounded_pos.?.y)][@intCast(bounded_pos.?.x)] != "XMAS"[step]) {
                        valid = false;
                        break;
                    }
                    pos = Position.add(pos, Direction.toPos(dir));
                }

                if (valid) {
                    count += 1;
                }
            }
        }
    }

    std.debug.print("result1: {}\n", .{count});
}

Part 2
fn buildGrid(data: []const u8, allocator: std.mem.Allocator) []const []const u8 {
    var list: std.ArrayList([]const u8) = .init(allocator);
    defer list.deinit();
    var line_iter = std.mem.tokenizeAny(u8, data, "\n");
    while (line_iter.next()) |line| {
        list.append(line) catch unreachable;
    }
    return list.toOwnedSlice() catch unreachable;
}

pub fn main() !void {
    var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
    defer arena_allocator.deinit();
    const arena = arena_allocator.allocator();

    const input = @embedFile("inputs/inputs.txt");
    const grid = buildGrid(input, arena);

    var count: usize = 0;

    for (1..grid.len - 1) |row| {
        for (1..grid[row].len - 1) |col| {
            if (grid[row][col] == 'A' and isValidXmasPattern(grid, row, col)) {
                count += 1;
            } else continue;
        }
    }

    std.debug.print("result2: {}\n", .{count});
}

fn isValidXmasPattern(grid: []const []const u8, row: usize, col: usize) bool {
    const top_left = grid[row - 1][col - 1];
    const top_right = grid[row - 1][col + 1];
    const bot_left = grid[row + 1][col - 1];
    const bot_right = grid[row + 1][col + 1];

    return (top_left == 'M' and top_right == 'M' and bot_left == 'S' and bot_right == 'S') or
        (top_left == 'S' and top_right == 'S' and bot_left == 'M' and bot_right == 'M') or
        (top_left == 'M' and top_right == 'S' and bot_left == 'M' and bot_right == 'S') or
        (top_left == 'S' and top_right == 'M' and bot_left == 'S' and bot_right == 'M');
}