Data update
This commit is contained in:
parent
5150844a7d
commit
4bb20c9b71
7735 changed files with 38060 additions and 199180 deletions
|
|
@ -1,177 +0,0 @@
|
|||
with Ada.Text_IO; use Ada.Text_IO;
|
||||
with System.Random_Numbers;
|
||||
procedure Play_2048 is
|
||||
-- ----- Keyboard management
|
||||
type t_Keystroke is (Up, Down, Right, Left, Quit, Restart, Invalid);
|
||||
-- Redefining this standard procedure as function to allow Get_Keystroke as an expression function
|
||||
function Get_Immediate return Character is
|
||||
begin
|
||||
return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
|
||||
end return;
|
||||
end Get_Immediate;
|
||||
Arrow_Prefix : constant Character := Character'Val(224); -- works for windows
|
||||
function Get_Keystroke return t_Keystroke is
|
||||
(case Get_Immediate is
|
||||
when 'Q' | 'q' => Quit,
|
||||
when 'R' | 'r' => Restart,
|
||||
when 'W' | 'w' => Left,
|
||||
when 'A' | 'a' => Up,
|
||||
when 'S' | 's' => Down,
|
||||
when 'D' | 'd' => Right,
|
||||
-- Windows terminal
|
||||
when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
|
||||
when 72 => Up,
|
||||
when 75 => Left,
|
||||
when 77 => Right,
|
||||
when 80 => Down,
|
||||
when others => Invalid),
|
||||
-- Unix escape sequences
|
||||
when ASCII.ESC => (case Get_Immediate is
|
||||
when '[' => (case Get_Immediate is
|
||||
when 'A' => Up,
|
||||
when 'B' => Down,
|
||||
when 'C' => Right,
|
||||
when 'D' => Left,
|
||||
when others => Invalid),
|
||||
when others => Invalid),
|
||||
when others => Invalid);
|
||||
|
||||
-- ----- Game data
|
||||
function Random_Int is new System.Random_Numbers.Random_Discrete(Integer);
|
||||
type t_List is array (Positive range <>) of Natural;
|
||||
subtype t_Row is t_List (1..4);
|
||||
type t_Board is array (1..4) of t_Row;
|
||||
Board : t_Board;
|
||||
New_Board : t_Board;
|
||||
Blanks : Natural;
|
||||
Score : Natural;
|
||||
Generator : System.Random_Numbers.Generator;
|
||||
|
||||
-- ----- Displaying the board
|
||||
procedure Display_Board is
|
||||
Horizontal_Rule : constant String := "+----+----+----+----+";
|
||||
function Center (Value : in String) return String is
|
||||
((1..(2-(Value'Length-1)/2) => ' ') & -- Add leading spaces
|
||||
Value(Value'First+1..Value'Last) & -- Trim the leading space of the raw number image
|
||||
(1..(2-Value'Length/2) => ' ')); -- Add trailing spaces
|
||||
begin
|
||||
Put_Line (Horizontal_Rule);
|
||||
for Row of Board loop
|
||||
for Cell of Row loop
|
||||
Put('|' & (if Cell = 0 then " " else Center(Cell'Img)));
|
||||
end loop;
|
||||
Put_Line("|");
|
||||
Put_Line (Horizontal_Rule);
|
||||
end loop;
|
||||
Put_Line("Score =" & Score'Img);
|
||||
end Display_Board;
|
||||
|
||||
-- ----- Game mechanics
|
||||
procedure Add_Block is
|
||||
Block_Offset : Positive := Random_Int(Generator, 1, Blanks);
|
||||
begin
|
||||
Blanks := Blanks-1;
|
||||
for Row of Board loop
|
||||
for Cell of Row loop
|
||||
if Cell = 0 then
|
||||
if Block_Offset = 1 then
|
||||
Cell := (if Random_Int(Generator,1,10) = 1 then 4 else 2);
|
||||
return;
|
||||
else
|
||||
Block_Offset := Block_Offset-1;
|
||||
end if;
|
||||
end if;
|
||||
end loop;
|
||||
end loop;
|
||||
end Add_Block;
|
||||
|
||||
procedure Reset_Game is
|
||||
begin
|
||||
Board := (others => (others => 0));
|
||||
Blanks := 16;
|
||||
Score := 0;
|
||||
Add_Block;
|
||||
Add_Block;
|
||||
end Reset_Game;
|
||||
|
||||
-- Moving and merging will always be performed leftward, hence the following transforms
|
||||
function HFlip (What : in t_Row) return t_Row is
|
||||
(What(4),What(3),What(2),What(1));
|
||||
function VFlip (What : in t_Board) return t_Board is
|
||||
(HFlip(What(1)),HFlip(What(2)),HFlip(What(3)),HFlip(What(4)));
|
||||
function Transpose (What : in t_Board) return t_Board is
|
||||
begin
|
||||
return Answer : t_Board do
|
||||
for Row in t_Board'Range loop
|
||||
for Column in t_Row'Range loop
|
||||
Answer(Column)(Row) := What(Row)(Column);
|
||||
end loop;
|
||||
end loop;
|
||||
end return;
|
||||
end Transpose;
|
||||
|
||||
-- For moving/merging, recursive expression functions will be used, but they
|
||||
-- can't contain statements, hence the following sub-function used by Merge
|
||||
function Add_Blank (Delta_Score : in Natural) return t_List is
|
||||
begin
|
||||
Blanks := Blanks+1;
|
||||
Score := Score+Delta_Score;
|
||||
return (1 => 0);
|
||||
end Add_Blank;
|
||||
|
||||
function Move_Row (What : in t_List) return t_List is
|
||||
(if What'Length = 1 then What
|
||||
elsif What(What'First) = 0
|
||||
then Move_Row(What(What'First+1..What'Last)) & (1 => 0)
|
||||
else (1 => What(What'First)) & Move_Row(What(What'First+1..What'Last)));
|
||||
|
||||
function Merge (What : in t_List) return t_List is
|
||||
(if What'Length <= 1 or else What(What'First) = 0 then What
|
||||
elsif What(What'First) = What(What'First+1)
|
||||
then (1 => 2*What(What'First)) & Merge(What(What'First+2..What'Last)) & Add_Blank(What(What'First))
|
||||
else (1 => What(What'First)) & Merge(What(What'First+1..What'Last)));
|
||||
|
||||
function Move (What : in t_Board) return t_Board is
|
||||
(Merge(Move_Row(What(1))),Merge(Move_Row(What(2))),Merge(Move_Row(What(3))),Merge(Move_Row(What(4))));
|
||||
|
||||
begin
|
||||
System.Random_Numbers.Reset(Generator);
|
||||
|
||||
Main_Loop: loop
|
||||
Reset_Game;
|
||||
Game_Loop: loop
|
||||
Display_Board;
|
||||
case Get_Keystroke is
|
||||
when Restart => exit Game_Loop;
|
||||
when Quit => exit Main_Loop;
|
||||
when Left => New_Board := Move(Board);
|
||||
when Right => New_Board := VFlip(Move(VFlip(Board)));
|
||||
when Up => New_Board := Transpose(Move(Transpose(Board)));
|
||||
when Down => New_Board := Transpose(VFlip(Move(VFlip(Transpose(Board)))));
|
||||
when others => null;
|
||||
end case;
|
||||
|
||||
if New_Board = Board then
|
||||
Put_Line ("Invalid move...");
|
||||
elsif (for some Row of New_Board => (for some Cell of Row => Cell = 2048)) then
|
||||
Display_Board;
|
||||
Put_Line ("Win !");
|
||||
exit Main_Loop;
|
||||
else
|
||||
Board := New_Board;
|
||||
Add_Block; -- OK since the board has changed
|
||||
if Blanks = 0
|
||||
and then (for all Row in 1..4 =>
|
||||
(for all Column in 1..3 =>
|
||||
(Board(Row)(Column) /= Board(Row)(Column+1))))
|
||||
and then (for all Row in 1..3 =>
|
||||
(for all Column in 1..4 =>
|
||||
(Board(Row)(Column) /= Board(Row+1)(Column)))) then
|
||||
Display_Board;
|
||||
Put_Line ("Lost !");
|
||||
exit Main_Loop;
|
||||
end if;
|
||||
end if;
|
||||
end loop Game_Loop;
|
||||
end loop Main_Loop;
|
||||
end Play_2048;
|
||||
|
|
@ -29,7 +29,7 @@ proc show .
|
|||
gcolor 765
|
||||
grect x y 19 19
|
||||
v = brd[i]
|
||||
h = 2 * floor log10 v
|
||||
h = 2 * floor log v 10
|
||||
gcolor 000
|
||||
gtext x + 7 - h y + 7 brd[i]
|
||||
.
|
||||
|
|
|
|||
|
|
@ -1,228 +1,362 @@
|
|||
// git repository at https://codeberg.org/Vulwsztyn/2048_zig
|
||||
const std = @import("std");
|
||||
const io = std.io;
|
||||
const Random = std.Random;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
// UserMove enum representing possible moves
|
||||
const UserMove = enum {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
const Io = std.Io;
|
||||
const Random = std.Random;
|
||||
const fmt = std.fmt;
|
||||
|
||||
const mibu = @import("mibu");
|
||||
const color = mibu.color;
|
||||
const events = mibu.events;
|
||||
const term = mibu.term;
|
||||
const cursor = mibu.cursor;
|
||||
|
||||
// You can change these constants to modify the game behavior
|
||||
const ROW_SIZE = 4; // program won't work if less than 2
|
||||
const COL_SIZE = 4; // program won't work if less than 2
|
||||
const TARGET_SCORE = 11; // 2^11 = 2048
|
||||
const PROBABILITY_OF_FOUR_NUMERATOR = 10;
|
||||
const PROBABILITY_OF_FOUR_DENOMINATOR = 100; // these 2 work in tandem to give chance of spawning a 4 instead of 2
|
||||
|
||||
const Dir = enum { up, down, left, right };
|
||||
const Action = union(enum) {
|
||||
Move: Dir,
|
||||
Quit,
|
||||
Noop,
|
||||
};
|
||||
|
||||
// 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]});
|
||||
},
|
||||
fn spawn(comptime X: usize, comptime Y: usize, matrix_p: *[X][Y]u8, rand: Random) void {
|
||||
var buffer: [X * Y]u8 = undefined;
|
||||
var zero_indices = std.ArrayListUnmanaged(u8).initBuffer(&buffer);
|
||||
for (matrix_p, 0..) |row, i| {
|
||||
for (row, 0..) |val, j| {
|
||||
if (val == 0) {
|
||||
zero_indices.appendBounded(@intCast(i * Y + j)) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
const random_index = rand.intRangeAtMost(u8, 0, @intCast(zero_indices.items.len - 1));
|
||||
const chosen = zero_indices.items[random_index];
|
||||
const row = chosen / Y;
|
||||
const col = chosen % Y;
|
||||
const value: u8 = if (rand.intRangeAtMost(u8, 0, PROBABILITY_OF_FOUR_DENOMINATOR) < PROBABILITY_OF_FOUR_NUMERATOR) 2 else 1;
|
||||
matrix_p[row][col] = value;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn additives_for_previous(dir: Dir) [2]i8 {
|
||||
// assuming that for a move in a direction, we look at the values at the end
|
||||
// and check backwards
|
||||
// e.g. for up, we look at the top row and check downwards
|
||||
// so we return 1, 0 meaning one row down, same column
|
||||
switch (dir) {
|
||||
.up => {
|
||||
return .{ 1, 0 };
|
||||
},
|
||||
.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 => {
|
||||
return .{ -1, 0 };
|
||||
},
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.left => {
|
||||
return .{ 0, 1 };
|
||||
},
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.right => {
|
||||
return .{ 0, -1 };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
fn is_move_possible(comptime X: usize, comptime Y: usize, matrix_p: [X][Y]u8, dir: Dir) bool {
|
||||
// checks if move is possible by checking if any tile can be moved or combined
|
||||
const additives = additives_for_previous(dir);
|
||||
for (0..X) |i| {
|
||||
const i_signed: i8 = @intCast(i);
|
||||
const new_i = i_signed + additives[0];
|
||||
if (new_i < 0 or new_i >= X) continue;
|
||||
for (0..Y) |j| {
|
||||
const j_signed: i8 = @intCast(j);
|
||||
const new_j = j_signed + additives[1];
|
||||
if (new_j < 0 or new_j >= Y) continue;
|
||||
const val = matrix_p[i][j];
|
||||
const prev_val = matrix_p[@intCast(new_i)][@intCast(new_j)];
|
||||
if (val != 0 and val == prev_val) return true;
|
||||
if (val == 0 and prev_val != 0) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var prng = std.Random.DefaultPrng.init(@intCast(std.time.milliTimestamp()));
|
||||
const random = prng.random();
|
||||
fn is_game_lost(comptime X: usize, comptime Y: usize, matrix_p: [X][Y]u8) bool {
|
||||
// checks if move in any direction is possible
|
||||
for (std.meta.tags(Dir)) |dir| {
|
||||
if (is_move_possible(X, Y, matrix_p, dir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
fn is_game_won(comptime X: usize, comptime Y: usize, matrix_p: [X][Y]u8) bool {
|
||||
for (matrix_p) |row| {
|
||||
for (row) |val| {
|
||||
if (val >= TARGET_SCORE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validMoveExists) {
|
||||
std.debug.print("No more valid moves, you lose\n", .{});
|
||||
break :gameLoop;
|
||||
}
|
||||
fn last_positions(comptime X: usize, comptime Y: usize, dir: Dir, gpa: std.mem.Allocator) std.ArrayList([2]u8) {
|
||||
// see comment in additives_for_previous
|
||||
// e.g. for move up we start at the top row and go downwards
|
||||
// this function returns the list positions in the top row (for that scenario)
|
||||
var list: std.ArrayList([2]u8) = .empty;
|
||||
switch (dir) {
|
||||
.up => {
|
||||
for (0..Y) |i| {
|
||||
list.append(gpa, .{ 0, @intCast(i) }) catch unreachable;
|
||||
}
|
||||
},
|
||||
.down => {
|
||||
for (0..Y) |i| {
|
||||
list.append(gpa, .{ X - 1, @intCast(i) }) catch unreachable;
|
||||
}
|
||||
},
|
||||
.left => {
|
||||
for (0..X) |i| {
|
||||
list.append(gpa, .{ @intCast(i), 0 }) catch unreachable;
|
||||
}
|
||||
},
|
||||
.right => {
|
||||
for (0..X) |i| {
|
||||
list.append(gpa, .{ @intCast(i), X - 1 }) catch unreachable;
|
||||
}
|
||||
},
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// 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;
|
||||
fn move_matrix(comptime X: usize, comptime Y: usize, matrix_p: *[X][Y]u8, dir: Dir) void {
|
||||
const gpa = std.heap.page_allocator;
|
||||
const additives = additives_for_previous(dir);
|
||||
var postions = last_positions(X, Y, dir, gpa);
|
||||
defer postions.deinit(gpa);
|
||||
std.debug.print("\n", .{});
|
||||
for (postions.items) |pos| {
|
||||
var target_row: i8 = @intCast(pos[0]);
|
||||
var target_col: i8 = @intCast(pos[1]);
|
||||
var target_val: u8 = matrix_p[pos[0]][pos[1]];
|
||||
var current_row: i8 = target_row;
|
||||
var current_col: i8 = target_col;
|
||||
while (true) {
|
||||
current_row += additives[0];
|
||||
current_col += additives[1];
|
||||
const went_out_of_bounds = current_row < 0 or current_row >= (X) or
|
||||
current_col < 0 or current_col >= (Y);
|
||||
if (went_out_of_bounds) {
|
||||
break;
|
||||
}
|
||||
const current_val: u8 = matrix_p[@intCast(current_row)][@intCast(current_col)];
|
||||
if (current_val == 0) {
|
||||
// nothing to do,
|
||||
continue;
|
||||
}
|
||||
if (target_val != 0 and target_val != current_val) {
|
||||
// cannot combine, move target forward
|
||||
// e.g 0 0 2 4 (for was target, now 2 becomes target)
|
||||
matrix_p[@intCast(current_row)][@intCast(current_col)] = 0;
|
||||
target_row = (target_row) + (additives[0]);
|
||||
target_col = (target_col) + (additives[1]);
|
||||
matrix_p[@intCast(target_row)][@intCast(target_col)] = current_val;
|
||||
target_val = matrix_p[@intCast(target_row)][@intCast(target_col)];
|
||||
continue;
|
||||
}
|
||||
if (target_val == 0) {
|
||||
// move current to target
|
||||
// e.g 0 0 2 0 -> 0 0 0 2 (2 remains target, since we want 0 2 2 0 to become 0 0 0 4)
|
||||
matrix_p[@intCast(target_row)][@intCast(target_col)] = current_val;
|
||||
target_val = current_val;
|
||||
} else {
|
||||
// combine
|
||||
// e.g 0 0 2 2 -> 0 0 0 4 (with rightmost zero becoming target, since the movements are not greedy)
|
||||
matrix_p[@intCast(target_row)][@intCast(target_col)] = target_val + 1;
|
||||
target_val = 0;
|
||||
target_row = target_row + (additives[0]);
|
||||
target_col = target_col + (additives[1]);
|
||||
}
|
||||
// both in case of move and combine we clear current
|
||||
matrix_p[@intCast(current_row)][@intCast(current_col)] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print(comptime X: usize, comptime Y: usize, matrix_p: [X][Y]u8, stdout: *Io.Writer) !void {
|
||||
for (matrix_p, 0..) |row, i| {
|
||||
try cursor.goTo(stdout, 1, 2 + i);
|
||||
var buf: [100]u8 = undefined;
|
||||
const all_together_slice = buf[0..];
|
||||
for (row) |val| {
|
||||
const value = if (val != 0) std.math.pow(u16, 2, @intCast(val)) else 0;
|
||||
const value_as_str = if (val > 0) try fmt.bufPrint(all_together_slice, "{d}", .{value}) else "_";
|
||||
|
||||
try color.fg256(stdout, if (val > 1) @enumFromInt(val) else color.Color.white); // sets colour based on value
|
||||
try stdout.print("{s:>5} ", .{value_as_str}); // prints with padding
|
||||
}
|
||||
try stdout.print("\x1b[K\n", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn init_matrix(comptime X: usize, comptime Y: usize, matrix_p: *[X][Y]u8) void {
|
||||
// initializes matrix to all zeros
|
||||
for (matrix_p) |*row| {
|
||||
for (row) |*val| {
|
||||
val.* = @intCast(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn action_from_event(next: events.Event) Action {
|
||||
switch (next) {
|
||||
.key => |k| switch (k.code) {
|
||||
.char => |c| switch (c) {
|
||||
'c' => {
|
||||
return .Quit;
|
||||
},
|
||||
'q' => {
|
||||
return .Quit;
|
||||
},
|
||||
'w' => {
|
||||
return .{ .Move = .up };
|
||||
},
|
||||
's' => {
|
||||
return .{ .Move = .down };
|
||||
},
|
||||
'a' => {
|
||||
return .{ .Move = .left };
|
||||
},
|
||||
'd' => {
|
||||
return .{ .Move = .right };
|
||||
},
|
||||
'h' => {
|
||||
return .{ .Move = .left };
|
||||
},
|
||||
'j' => {
|
||||
return .{ .Move = .down };
|
||||
},
|
||||
'k' => {
|
||||
return .{ .Move = .up };
|
||||
},
|
||||
'l' => {
|
||||
return .{ .Move = .right };
|
||||
},
|
||||
else => {
|
||||
return .Noop;
|
||||
},
|
||||
},
|
||||
.up => {
|
||||
return .{ .Move = .up };
|
||||
},
|
||||
.down => {
|
||||
return .{ .Move = .down };
|
||||
},
|
||||
.left => {
|
||||
return .{ .Move = .left };
|
||||
},
|
||||
.right => {
|
||||
return .{ .Move = .right };
|
||||
},
|
||||
else => {
|
||||
return .Noop;
|
||||
},
|
||||
},
|
||||
else => {
|
||||
return .Noop;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
const row_size = ROW_SIZE;
|
||||
const col_size = COL_SIZE;
|
||||
var seed: u64 = undefined;
|
||||
try std.posix.getrandom(std.mem.asBytes(&seed));
|
||||
|
||||
var prng = Random.DefaultPrng.init(seed);
|
||||
const rand = prng.random();
|
||||
|
||||
var stdout_buffer: [1024]u8 = undefined;
|
||||
|
||||
const stdin = std.fs.File.stdin();
|
||||
var stdout_file = std.fs.File.stdout();
|
||||
var stdout_writer = stdout_file.writer(&stdout_buffer);
|
||||
const stdout = &stdout_writer.interface;
|
||||
|
||||
if (!std.posix.isatty(stdin.handle)) {
|
||||
try stdout.print("The current file descriptor is not a referring to a terminal.\n", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .windows) {
|
||||
try mibu.enableWindowsVTS(stdout.handle);
|
||||
}
|
||||
|
||||
// Enable terminal raw mode, its very recommended when listening for events
|
||||
var raw_term = try term.enableRawMode(stdin.handle);
|
||||
defer raw_term.disableRawMode() catch {
|
||||
std.debug.print("Failed to disable raw mode\n", .{});
|
||||
};
|
||||
|
||||
try term.enterAlternateScreen(stdout);
|
||||
defer term.exitAlternateScreen(stdout) catch {
|
||||
std.debug.print("Failed to exit alternate screen\n", .{});
|
||||
};
|
||||
|
||||
try cursor.hide(stdout);
|
||||
defer cursor.show(stdout) catch {
|
||||
std.debug.print("Failed to show cursor\n", .{});
|
||||
};
|
||||
|
||||
try cursor.goTo(stdout, 1, 1);
|
||||
try mibu.style.italic(stdout, true);
|
||||
|
||||
var matrix: [row_size][col_size]u8 = undefined;
|
||||
|
||||
init_matrix(row_size, col_size, &matrix);
|
||||
|
||||
spawn(row_size, col_size, &matrix, rand);
|
||||
try stdout.print("move with wsad, hjkl, or arrows; quit with c or q\x1b[K\n", .{}); // prints with padding
|
||||
|
||||
try print(row_size, col_size, matrix, stdout);
|
||||
|
||||
try stdout.flush();
|
||||
|
||||
while (true) {
|
||||
try cursor.goTo(stdout, 1, 2);
|
||||
const next = try events.next(stdin);
|
||||
const action = action_from_event(next);
|
||||
switch (action) {
|
||||
.Quit => break,
|
||||
.Noop => continue,
|
||||
.Move => |dir| {
|
||||
if (is_move_possible(row_size, col_size, matrix, dir)) {
|
||||
move_matrix(row_size, col_size, &matrix, dir);
|
||||
try print(row_size, col_size, matrix, stdout);
|
||||
if (is_game_won(row_size, col_size, matrix)) {
|
||||
try cursor.goTo(stdout, 1, row_size + 3);
|
||||
try stdout.print("You won! Congratulations!\n", .{});
|
||||
try stdout.flush();
|
||||
break;
|
||||
}
|
||||
spawn(row_size, col_size, &matrix, rand);
|
||||
try print(row_size, col_size, matrix, stdout);
|
||||
if (is_game_lost(row_size, col_size, matrix)) {
|
||||
try cursor.goTo(stdout, 1, (row_size + 3));
|
||||
try stdout.print("Game over! No more moves possible!\n", .{});
|
||||
try stdout.flush();
|
||||
break;
|
||||
}
|
||||
}
|
||||
try stdout.flush();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue