Snake: Difference between revisions

Content added Content deleted
m (Craft Basic moved to the BASIC section.)
m (→‎{{header|Rust}}: refactoring)
Line 4,537: Line 4,537:
winsafe = "0.0.8"
winsafe = "0.0.8"
rand = "0.8.4"
rand = "0.8.4"
derive-new = "0.5"
*/
*/


#![windows_subsystem = "windows"]
#![windows_subsystem = "windows"]


use derive_new::new;
use rand::Rng;
use rand::Rng;
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, rc::Rc};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, SIZE};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, HWND, SIZE};


const STEP: i32 = 3; // px, motion per frame. STEP and FPS determine the smoothness and speed of the animation.
const STEP: i32 = 3; // px, motion per frame. STEP and FPS determine the smoothness and speed of the animation.
Line 4,553: Line 4,555:


const RATIO: i32 = CELL / STEP;
const RATIO: i32 = CELL / STEP;
const START_CELL: i32 = FIELD_W / 2 * RATIO;
const STARTCELL: i32 = FIELD_W / 2 * RATIO;
/// total field width (with overlap for collisions) in STEPs
/// total field width (with overlap for collisions) in STEPs
const TW: i32 = (FIELD_W + 2) * RATIO;
const TW: i32 = (FIELD_W + 2) * RATIO;
Line 4,565: Line 4,567:
S = TW,
S = TW,
}
}
use Direction::*;
use Direction::{Start, A, D, S, W};


#[derive(new)]
struct Context {
struct Context {
wnd: gui::WindowMain,
hwnd: HWND,
snake: Vec<i32>, // [ids_rect] where id_rect = y * TW + x (where x, y: nSTEPs)
snake: Vec<i32>, // [ids_rect] where id_rect = y * TW + x (where x, y: nSTEPs)
#[new(value = "[STARTCELL; 6]")]
id_r: [i32; 6], // ID 6 rectangles to color in next frame (bg, tail, turn, body, food, head)
id_r: [i32; 6], // ID 6 rectangles to color in next frame (bg, tail, turn, body, food, head)
#[new(default)]
gap: i32, // gap in STEPs between animation and logic cell (negative - remove tail)
gap: i32, // gap in STEPs between animation and logic cell (negative - remove tail)
#[new(value = "Direction::Start")]
dir: Direction,
dir: Direction,
#[new(value = "Direction::S")]
ordered_dir: Direction,
ordered_dir: Direction,
}
impl Context {
fn new(wnd: gui::WindowMain, len: usize) -> Self {
Self {
wnd,
snake: vec![START_CELL; len.saturating_sub(RATIO as usize)],
id_r: [START_CELL; 6],
gap: 0,
dir: Start,
ordered_dir: S,
}
}
}
}


pub fn main() {
pub fn main() {
let context = Rc::new(RefCell::new(Context::new(HWND::NULL, Vec::new())));
let cells: Vec<i32> =
(1..=FIELD_W).flat_map(|y| (1..=FIELD_W).map(move |x| (y * TW + x) * RATIO)).collect();
let [bg, tail, turn, body, food, head] = [0usize, 1, 2, 3, 4, 5];
let [bg, tail, turn, body, food, head] = [0usize, 1, 2, 3, 4, 5];
let mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
let mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
Line 4,604: Line 4,603:
});
});


wnd.on().wm_paint({
let context = Rc::new(RefCell::new(Context::new(wnd.clone(), 0)));
let wnd = wnd.clone(); // WindowMain is based on Arc, so wnd.clone() is a shallow copy
let context = Rc::clone(&context);
move || {
let mut ctx = context.borrow_mut();
ctx.hwnd = wnd.hwnd();
let mut ps = winsafe::PAINTSTRUCT::default();
let hdc = ctx.hwnd.BeginPaint(&mut ps)?;
hdc.SelectObjectPen(HPEN::CreatePen(co::PS::NULL, 0, COLORREF::new(0, 0, 0))?)?;
for (&id_rect, &brush) in ctx.id_r.iter().zip(&brushes) {
hdc.SelectObjectBrush(brush)?;
let left = id_rect % TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
let top = id_rect / TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
hdc.RoundRect(
winsafe::RECT { left, top, right: left + SNAKE_W, bottom: top + SNAKE_W },
ROUNDING,
)?;
}
ctx.hwnd.EndPaint(&ps);
Ok(())
}
});


wnd.on().wm_key_down({
wnd.on().wm_key_down({
Line 4,612: Line 4,632:
match (ctx.dir, k.char_code as u8) {
match (ctx.dir, k.char_code as u8) {
(Start, bt @ (b' ' | 113)) => {
(Start, bt @ (b' ' | 113)) => {
let len = ctx.snake.len(); // 113 == F2 key
if bt == 113 {
*ctx = Context::new(ctx.wnd.clone(), if bt == b' ' { len } else { 0 });
ctx.snake.clear() // 113 == F2 key: restart without save
ctx.wnd.hwnd().InvalidateRect(None, true)?; // call .wm_paint() with erase
};
ctx.wnd.hwnd().SetTimer(1, 1000 / FPS, None)?;
ctx.hwnd.InvalidateRect(None, true)?; // call .wm_paint() with erase
ctx.hwnd.SetTimer(1, 1000 / FPS, None)?;
}
}
(W | S, bt @ (b'A' | b'D')) => ctx.ordered_dir = if bt == b'A' { A } else { D },
(W | S, bt @ (b'A' | b'D')) => ctx.ordered_dir = if bt == b'A' { A } else { D },
Line 4,625: Line 4,646:
});
});


wnd.on().wm_timer(1, {
wnd.on().wm_timer(1, move || {
let context = Rc::clone(&context);
let mut ctx = context.borrow_mut();
let cells: Vec<i32> = (1..=FIELD_W)
let new_h = ctx.id_r[head] + ctx.dir as i32;
ctx.id_r[body] = ctx.id_r[head];
.flat_map(|y| (1..=FIELD_W).map(move |x| (y * TW + x) * RATIO))
.collect();
ctx.id_r[head] = new_h;
move || {
if ctx.gap < 0 {
let mut ctx = context.borrow_mut();
ctx.id_r[bg] = ctx.snake.remove(0);
let new_h = ctx.id_r[head] + ctx.dir as i32;
ctx.id_r[tail] = ctx.snake[0];
ctx.id_r[body] = ctx.id_r[head];
ctx.id_r[turn] = ctx.snake[RATIO as usize / 2];
ctx.id_r[head] = new_h;
}
if ctx.gap < 0 {
ctx.gap -= ctx.gap.signum();
ctx.id_r[bg] = ctx.snake.remove(0);
if ctx.gap == 0 {
ctx.id_r[tail] = ctx.snake[0];
let hw = ctx.hwnd;
ctx.id_r[turn] = ctx.snake[RATIO as usize / 2];
let eat = new_h == ctx.id_r[food];
let mut snk_cells: Vec<_> = ctx.snake.iter().step_by(RATIO as usize).collect();
}
if !eat && (cells.binary_search(&new_h).is_err() || snk_cells.contains(&&new_h)) {
ctx.gap -= ctx.gap.signum();
if ctx.gap == 0 {
hw.KillTimer(1)?;
let hw = ctx.wnd.hwnd();
hw.SetWindowText(&(hw.GetWindowText()? + " Restart: F2 (with save - Space)"))?;
let eat = new_h == ctx.id_r[food];
*ctx = Context::new(hw, vec![STARTCELL; ctx.snake.len() - RATIO as usize]);
let mut snk_cells: Vec<_> = ctx.snake.iter().step_by(RATIO as usize).collect();
return Ok(());
if !eat && (cells.binary_search(&new_h).is_err() || snk_cells.contains(&&new_h)) {
} else if eat || ctx.id_r[food] == STARTCELL && ctx.id_r[tail] != STARTCELL {
hw.KillTimer(1)?;
if eat {
hw.SetWindowText(&(hw.GetWindowText()? + " Restart: F2 (with save - Space)"))?;
hw.SetWindowText(&format!("Snake - Eaten: {}.", snk_cells.len()))?;
ctx.dir = Start;
}
return Ok(());
if eat && snk_cells.len() == cells.len() - 2 {
} else if eat || ctx.id_r[food] == START_CELL && ctx.id_r[tail] != START_CELL {
hw.SetWindowText(&(hw.GetWindowText()? + " ALL !!!"))?;
if eat {
ctx.id_r[food] = 0; // hide food: all eaten
hw.SetWindowText(&format!("Snake - Eaten: {}.", snk_cells.len()))?;
} else if new_h != STARTCELL {
}
snk_cells.sort();
if eat && snk_cells.len() == cells.len() - 2 {
ctx.id_r[food] = *(cells.iter())
hw.SetWindowText(&(hw.GetWindowText()? + " ALL !!!"))?;
.filter(|i| **i != new_h && snk_cells.binary_search(i).is_err())
ctx.id_r[food] = 0; // hide food: all eaten
.nth(rand::thread_rng().gen_range(0..cells.len() - snk_cells.len() - 1))
} else if new_h != START_CELL {
.unwrap();
snk_cells.sort();
ctx.id_r[food] = *(cells.iter())
.filter(|i| **i != new_h && snk_cells.binary_search(i).is_err())
.nth(rand::thread_rng().gen_range(0..cells.len() - snk_cells.len() - 1))
.unwrap();
}
}
}
ctx.dir = ctx.ordered_dir;
ctx.gap = if eat { RATIO } else { -RATIO }
}
}
ctx.snake.push(new_h);
ctx.dir = ctx.ordered_dir;
ctx.wnd.hwnd().InvalidateRect(None, false)?; // call .wm_paint() without erase
ctx.gap = if eat { RATIO } else { -RATIO }
Ok(())
}
});

wnd.on().wm_paint(move || {
let ctx = context.borrow();
let mut ps = winsafe::PAINTSTRUCT::default();
let hdc = ctx.wnd.hwnd().BeginPaint(&mut ps)?;
hdc.SelectObjectPen(HPEN::CreatePen(co::PS::NULL, 0, COLORREF::new(0, 0, 0))?)?;
for (&id_rect, &brush) in ctx.id_r.iter().zip(&brushes) {
hdc.SelectObjectBrush(brush)?;
let left = id_rect % TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
let top = id_rect / TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
hdc.RoundRect(
winsafe::RECT {
left,
top,
right: left + SNAKE_W,
bottom: top + SNAKE_W,
},
ROUNDING,
)?;
}
}
ctx.wnd.hwnd().EndPaint(&ps);
ctx.snake.push(new_h);
ctx.hwnd.InvalidateRect(None, false)?; // call .wm_paint() without erase
Ok(())
Ok(())
});
});


if let Err(e) = wnd.run_main(None) {
if let Err(e) = wnd.run_main(None) {
HWND::NULL.MessageBox(&e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap();
winsafe::HWND::NULL
.MessageBox(&e.to_string(), "Uncaught error", co::MB::ICONERROR)
.unwrap();
}
}
}</syntaxhighlight>
}</syntaxhighlight>