Untitled
unknown
plain_text
a year ago
7.0 kB
16
Indexable
use rand::seq::SliceRandom;
use rand::Rng;
use std::io::{self, Write};
const N: usize = 4;
#[derive(Clone)]
struct Game {
board: [[u16; N]; N],
score: u32,
}
impl Game {
fn new() -> Self {
let mut g = Self {
board: [[0; N]; N],
score: 0,
};
g.spawn_tile();
g.spawn_tile();
g
}
fn reset(&mut self) {
self.board = [[0; N]; N];
self.score = 0;
self.spawn_tile();
self.spawn_tile();
}
fn spawn_tile(&mut self) -> bool {
let mut empties: Vec<(usize, usize)> = Vec::new();
for r in 0..N {
for c in 0..N {
if self.board[r][c] == 0 {
empties.push((r, c));
}
}
}
if empties.is_empty() {
return false;
}
let mut rng = rand::thread_rng();
let &(r, c) = empties.choose(&mut rng).unwrap();
// 90% chance of 2, 10% chance of 4
self.board[r][c] = if rng.gen::<f32>() < 0.9 { 2 } else { 4 };
true
}
fn render(&self) {
// Clear screen (ANSI); safe to ignore if the terminal doesn't support it
print!("\x1B[2J\x1B[H");
println!("2048 (W/A/S/D to move, N=new, Q=quit)");
println!("Score: {}", self.score);
println!("+------+------+------+------+");
for r in 0..N {
for c in 0..N {
let v = self.board[r][c];
if v == 0 {
print!("|{:^6}", ".");
} else {
print!("|{:^6}", v);
}
}
println!("|");
println!("+------+------+------+------+");
}
io::stdout().flush().ok();
}
fn can_move(&self) -> bool {
// If any zero exists, can move
for r in 0..N {
for c in 0..N {
if self.board[r][c] == 0 {
return true;
}
}
}
// Otherwise, if any adjacent equal tiles exist, can move
for r in 0..N {
for c in 0..N {
if r + 1 < N && self.board[r][c] == self.board[r + 1][c] {
return true;
}
if c + 1 < N && self.board[r][c] == self.board[r][c + 1] {
return true;
}
}
}
false
}
fn move_left(&mut self) -> bool {
let mut moved_any = false;
for r in 0..N {
let line = self.board[r];
let (new_line, gained, changed) = slide_and_merge_line(line);
if changed {
self.board[r] = new_line;
moved_any = true;
self.score = self.score.saturating_add(gained);
}
}
moved_any
}
fn move_right(&mut self) -> bool {
let mut moved_any = false;
for r in 0..N {
let mut line = self.board[r];
line.reverse();
let (mut new_line, gained, changed) = slide_and_merge_line(line);
new_line.reverse();
if changed {
self.board[r] = new_line;
moved_any = true;
self.score = self.score.saturating_add(gained);
}
}
moved_any
}
fn move_up(&mut self) -> bool {
let mut moved_any = false;
for c in 0..N {
let mut line = [0u16; N];
for r in 0..N {
line[r] = self.board[r][c];
}
let (new_line, gained, changed) = slide_and_merge_line(line);
if changed {
for r in 0..N {
self.board[r][c] = new_line[r];
}
moved_any = true;
self.score = self.score.saturating_add(gained);
}
}
moved_any
}
fn move_down(&mut self) -> bool {
let mut moved_any = false;
for c in 0..N {
let mut line = [0u16; N];
for r in 0..N {
line[r] = self.board[r][c];
}
line.reverse();
let (mut new_line, gained, changed) = slide_and_merge_line(line);
new_line.reverse();
if changed {
for r in 0..N {
self.board[r][c] = new_line[r];
}
moved_any = true;
self.score = self.score.saturating_add(gained);
}
}
moved_any
}
}
/// Slide non-zeros to the left, merge equal neighbors once, then slide again.
/// Returns: (new_line, score_gained_from_merges, changed_flag)
fn slide_and_merge_line(line: [u16; N]) -> ([u16; N], u32, bool) {
// Compress: remove zeros
let mut non_zero: Vec<u16> = line.iter().copied().filter(|&x| x != 0).collect();
// Merge once per tile from left to right
let mut merged: Vec<u16> = Vec::with_capacity(N);
let mut score_gain: u32 = 0;
let mut skip = false;
for i in 0..non_zero.len() {
if skip {
skip = false;
continue;
}
if i + 1 < non_zero.len() && non_zero[i] == non_zero[i + 1] {
let v = non_zero[i] * 2;
merged.push(v);
score_gain = score_gain.saturating_add(v as u32);
skip = true;
} else {
merged.push(non_zero[i]);
}
}
// Pad with zeros to the right
while merged.len() < N {
merged.push(0);
}
let mut new_line = [0u16; N];
for i in 0..N {
new_line[i] = merged[i];
}
let changed = new_line != line;
(new_line, score_gain, changed)
}
fn read_input() -> Option<char> {
print!("Move (W/A/S/D, N=new, Q=quit): ");
io::stdout().flush().ok()?;
let mut s = String::new();
io::stdin().read_line(&mut s).ok()?;
s.chars()
.find(|ch| !ch.is_whitespace())
.map(|ch| ch.to_ascii_lowercase())
}
fn main() {
let mut game = Game::new();
loop {
game.render();
if !game.can_move() {
println!("Game Over! Final score: {}", game.score);
match read_input() {
Some('n') => {
game.reset();
continue;
}
Some('q') => break,
_ => continue,
}
}
let Some(ch) = read_input() else { continue };
let moved = match ch {
'w' => game.move_up(),
'a' => game.move_left(),
's' => game.move_down(),
'd' => game.move_right(),
'n' => {
game.reset();
continue;
}
'q' => break,
_ => {
// ignore invalid keys
false
}
};
if moved {
// Only spawn a tile if the board actually changed
game.spawn_tile();
}
}
}Editor is loading...
Leave a Comment