RosettaCodeData/Task/2048/Zig/2048.zig

229 lines
7.4 KiB
Zig

const std = @import("std");
const io = std.io;
const Random = std.Random;
// UserMove enum representing possible moves
const UserMove = enum {
Up,
Down,
Left,
Right,
};
// Game field type
const Field = [4][4]u32;
// Function to print the current game state
fn printGame(field: *const Field) void {
for (field) |row| {
std.debug.print("{any}\n", .{row});
}
}
// Function to get a user move
fn getUserMove() !UserMove {
const stdin = std.io.getStdIn().reader();
var buf: [2]u8 = undefined; // Buffer for input (character + newline)
while (true) {
const bytesRead = try stdin.read(&buf);
if (bytesRead < 1) continue;
switch (buf[0]) {
'a' => return UserMove.Left,
'w' => return UserMove.Up,
's' => return UserMove.Down,
'd' => return UserMove.Right,
else => {
std.debug.print("input was {c}: invalid character should be a,s,w or d\n", .{buf[0]});
},
}
}
}
// This function implements user moves.
// For every element, it checks if the element is zero.
// If the element is zero, it looks against the direction of movement if any
// element is not zero, then moves it to its place and checks for a matching element.
// If the element is not zero, it looks for a match. If no match is found,
// it looks for the next element.
fn doGameStep(step: UserMove, field: *Field) void {
switch (step) {
.Left => {
for (field) |*row| {
for (0..4) |col| {
for ((col + 1)..4) |my_testCol| {
if (row[my_testCol] != 0) {
if (row[col] == 0) {
row[col] += row[my_testCol];
row[my_testCol] = 0;
} else if (row[col] == row[my_testCol]) {
row[col] += row[my_testCol];
row[my_testCol] = 0;
break;
} else {
break;
}
}
}
}
}
},
.Right => {
for (field) |*row| {
var col: i32 = 3;
while (col >= 0) : (col -= 1) {
var my_testCol: i32 = col - 1;
while (my_testCol >= 0) : (my_testCol -= 1) {
if (row[@intCast(my_testCol)] != 0) {
if (row[@intCast(col)] == 0) {
row[@intCast(col)] += row[@intCast(my_testCol)];
row[@intCast(my_testCol)] = 0;
} else if (row[@intCast(col)] == row[@intCast(my_testCol)]) {
row[@intCast(col)] += row[@intCast(my_testCol)];
row[@intCast(my_testCol)] = 0;
break;
} else {
break;
}
}
}
}
}
},
.Down => {
for (0..4) |col_idx| {
const col = col_idx; // Convert to immutable
var row: i32 = 3;
while (row >= 0) : (row -= 1) {
var my_testRow: i32 = row - 1;
while (my_testRow >= 0) : (my_testRow -= 1) {
if (field[@intCast(my_testRow)][col] != 0) {
if (field[@intCast(row)][col] == 0) {
field[@intCast(row)][col] += field[@intCast(my_testRow)][col];
field[@intCast(my_testRow)][col] = 0;
} else if (field[@intCast(row)][col] == field[@intCast(my_testRow)][col]) {
field[@intCast(row)][col] += field[@intCast(my_testRow)][col];
field[@intCast(my_testRow)][col] = 0;
break;
} else {
break;
}
}
}
}
}
},
.Up => {
for (0..4) |col| {
for (0..4) |row| {
for ((row + 1)..4) |my_testRow| {
if (field[my_testRow][col] != 0) {
if (field[row][col] == 0) {
field[row][col] += field[my_testRow][col];
field[my_testRow][col] = 0;
} else if (field[row][col] == field[my_testRow][col]) {
field[row][col] += field[my_testRow][col];
field[my_testRow][col] = 0;
break;
} else {
break;
}
}
}
}
}
},
}
}
// Spawn a new number (2 or 4) in a random empty cell
fn spawn(field: *Field, random: Random) void {
while (true) {
const x = random.uintLessThan(usize, 16); // Random position 0-15
const row = x % 4;
const col = (x / 4) % 4;
if (field[row][col] == 0) {
// 10% chance for a 4, 90% chance for a 2
if (random.uintLessThan(usize, 10) == 0) {
field[row][col] = 4;
} else {
field[row][col] = 2;
}
break;
}
}
}
// Check if fields are equal
fn areFieldsEqual(a: *const Field, b: *const Field) bool {
for (0..4) |i| {
for (0..4) |j| {
if (a[i][j] != b[i][j]) return false;
}
}
return true;
}
// Check if player has won (any tile equals 2048)
fn checkWin(field: *const Field) bool {
for (field) |row| {
for (row) |cell| {
if (cell == 2048) return true;
}
}
return false;
}
pub fn main() !void {
var prng = std.Random.DefaultPrng.init(@intCast(std.time.milliTimestamp()));
const random = prng.random();
var field: Field = [_][4]u32{[_]u32{0} ** 4} ** 4;
var my_test: Field = undefined;
gameLoop: while (true) {
// Check if there's still an open space
@memcpy(&my_test, &field);
spawn(&field, random);
// Check if any valid moves remain
var validMoveExists = false;
const moves = [_]UserMove{ .Up, .Down, .Left, .Right };
for (moves) |move| {
@memcpy(&my_test, &field);
doGameStep(move, &my_test);
if (!areFieldsEqual(&my_test, &field)) {
validMoveExists = true;
break;
}
}
if (!validMoveExists) {
std.debug.print("No more valid moves, you lose\n", .{});
break :gameLoop;
}
// Print the current game state
printGame(&field);
std.debug.print("move the blocks\n", .{});
// Get and apply user move
@memcpy(&my_test, &field);
while (areFieldsEqual(&my_test, &field)) {
const move = try getUserMove();
doGameStep(move, &field);
}
// Check win condition
if (checkWin(&field)) {
printGame(&field);
std.debug.print("You Won!!\n", .{});
break :gameLoop;
}
}
}