Snake: Difference between revisions

1,595 bytes removed ,  11 months ago
→‎{{header|Rust}}: minimization and renaming + screenshot
(Integer BASIC)
(→‎{{header|Rust}}: minimization and renaming + screenshot)
Line 4,602:
=={{header|Rust}}==
Implemented smooth (per-pixel) animation on Win32 API (tested on Windows 7 and Windows 11)
 
Used winsafe - a safe rust bindings library for Win32 GUI: young but very handy, with links to docs.microsoft.com from doc and src for all Win32 entities involved.
 
Along the way, the possibility of restarting while maintaining the length of the snake has been implemented. Now a long snake is available to everyone!
 
[http://github.com/rust-rosetta/rust-rosetta/blob/master/tasks/snake/SnkRust.png snake game screenshot]
<syntaxhighlight lang="rust">/* add to file Cargo.toml:
[dependencies]
winsafe = "0.0.8"
rand = "0.8.4"
derive-new = "0.5"
*/
Line 4,620 ⟶ 4,614:
use rand::Rng;
use std::{cell::RefCell, rc::Rc};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, HWNDRECT, SIZE};
 
const STEP: i32 = 3; // px, motion per frame. STEP and FPS determine the smoothness and speed of the animation.
const FPSGCW: u32i32 = 907; // game grid cell width in STEPs
const CELL: i32 = 21; // px, game grid (logical step). Will be aligned by STEP
const FIELD_W: i32 = 20; // width of the square field in CELLs
const SNAKE_W: i32 = 20; // px
const FIELD_WFW: i32 = 20; // the width of the square field in CELLsthe cells of the game grid
const ROUNDING: SIZE = SIZE::new(SNAKE_W / 2, SNAKE_W / 2);
/const TW: i32 = (FW + 2) * GCW; // total field width (with overlap for collisions) in STEPs
 
const RATIOID0: i32 = CELLFW / STEP2 * GCW; // starting position id
const STARTCELL: i32 = FIELD_W / 2 * RATIO;
/// total field width (with overlap for collisions) in STEPs
const TW: i32 = (FIELD_W + 2) * RATIO;
#[derive(Clone, Copy)]
#[repr(i32)]
enum Direction {
Start = 0,
A = -1,
D = 1,
W = -TW,
S = TW,
}
use Direction::{Start, A, D, S, W};
 
#[rustfmt::skip]
#[derive(new)]
struct Context {
hwndwnd: HWNDgui::WindowMain,
#[new(default) ] snake: Vec<i32>, // [ids_rectrect_ids] where id_rectrect_id = y * TW + x (where x, y: nSTEPs)
#[new(value = "[STARTCELLID0; 6]")] r: [i32; 6], // ID 6 rect to color in next frame (bg, tail, turn, body, food, head)
#[new(default) ] incr: i32, // 0 | -1 | 1 | -TW | TW - increment r[head] in next STEP
id_r: [i32; 6], // ID 6 rectangles to color in next frame (bg, tail, turn, body, food, head)
#[new(value = "TW") ] next_incr: i32, // `incr` in the next grid cell
#[new(default)]
#[new(default) ] gap: i32, // gapinterval in STEPs betweento animationthe andnext logicgrid cell; (negative - remove tail) clipping mark
#[new(value = "Direction::Start")]
dir: Direction,
#[new(value = "Direction::S")]
ordered_dir: Direction,
}
 
pub fn main() {
let [bg, tail, turn, body, food, head] = [0usize0_usize, 1, 2, 3, 4, 5];
let context = Rc::new(RefCell::new(Context::new(HWND::NULL, Vec::new())));
let grid: Vec<_> = (1..=FIELD_WFW).flat_map(|y| (1..=FIELD_WFW).map(move |x| (y * TW + x) * RATIOGCW)).collect();
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 mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
colors[bg] = (0x00, 0x50, 0x90);
Line 4,672 ⟶ 4,645:
let wnd = gui::WindowMain::new(gui::WindowMainOpts {
title: "Snake - Start: Space, then press W-A-S-D".to_string(),
size: winsafe::SIZE::new(FIELD_WFW * RATIOGCW * STEP, FIELD_WFW * RATIOGCW * STEP),
ex_style: co::WS_EX::CLIENTEDGE,
class_bg_brush: brushes[bg],
..Default::default()
});
let wnd = wnd.clone(); // WindowMain is based on Arc, so wnd.clone() is a shallow copy of a reference.
let context = Rc::new(RefCell::new(Context::new(HWND::NULL, Vec::newwnd.clone())));
 
wnd.on().wm_paint({
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_mutborrow();
ctx.hwnd = wnd.hwnd();
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_rectrect_id, &brush) in ctx.id_rr.iter().zip(&brushes[bg..=head]) {
hdc.SelectObjectBrush(brush)?;
let left = id_rectrect_id % TW * STEP - (STEP * RATIOGCW + SNAKE_W) / 2;
let top = id_rectrect_id / TW * STEP - (STEP * RATIOGCW + SNAKE_W) / 2;
let rect = RECT { left, top, right: left + SNAKE_W, bottom: top + SNAKE_W };
hdc.RoundRect(
hdc.RoundRect(rect, winsafeSIZE::RECTnew(SNAKE_W {/ left2, top, right: left + SNAKE_W, bottom:/ top + SNAKE_W },2))?;
ROUNDING,
)?;
}
Ok(ctx.wnd.hwnd().EndPaint(&ps);)
Ok(())
}
});
Line 4,703 ⟶ 4,672:
wnd.on().wm_key_down({
let context = Rc::clone(&context);
move |kkey| {
let mut ctx = context.borrow_mut();
Ok(match (ctx.dirincr.abs(), kkey.char_code as u8) {
(Start0, bt @ (b' ') |=> 113_ = ctx.wnd.hwnd().SetTimer(1, 10, None)?, =>// Start / {Restart
(TW, bt @ (b'A' | b'D')) => ctx.next_incr = if bt == 113b'A' { -1 } else { 1 },
(1, bt @ (b'S' | b'W')) => ctx.snake.clear()next_incr //= 113if bt == F2b'S' { TW } key:else restart{ without-TW save},
};
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 },
(A | D, bt @ (b'S' | b'W')) => ctx.ordered_dir = if bt == b'S' { S } else { W },
_ => (),
})
Ok(())
}
});
Line 4,723 ⟶ 4,685:
wnd.on().wm_timer(1, move || {
let mut ctx = context.borrow_mut();
let new_h = ctx.id_rr[head] + ctx.dir as i32incr;
(ctx.id_rr[body], ctx.r[head]) = (ctx.id_rr[head], new_h);
ctx.id_r[head] = new_h;
if ctx.gap < 0 {
ctx.id_rr[bg] = ctx.snake.remove(0);
ctx.id_rr[tail] = ctx.snake[0];
ctx.id_rr[turn] = ctx.snake[RATIOGCW as usize / 2];
}
ctx.gap -= ctx.gap.signum();
if ctx.gap == 0 {
letctx.gap hw= if new_h == ctx.hwndr[food] { GCW } else { -GCW };
let eatmut =snake_cells: new_hVec<_> == ctx.id_r[food]snake.iter().step_by(GCW as usize).collect();
letif mut snk_cells: Vec<_>new_h == ctx.snake.iter().step_by(RATIOr[food] as usize).collect();{
hwctx.wnd.hwnd().SetWindowText(&format!("Snake - Eaten: {}.", snk_cellssnake_cells.len()))?;
if !eat && (cells.binary_search(&new_h).is_err() || snk_cells.contains(&&new_h)) {
hwsnake_cells.KillTimersort(1)?;
hwctx.SetWindowTextr[food] = *(&(hwgrid.GetWindowTextiter()? + " Restart: F2 (with save - Space)"))?;
*ctx = Context::new(hw, vec![STARTCELL; ctx.snake.lenfilter()|i| -**i RATIO!= asnew_h usize]&& snake_cells.binary_search(i).is_err());
.nth(rand::thread_rng().gen_range(0..cellsgrid.len() - snk_cells1 - snake_cells.len() - 1))
ctx.hwnd = wnd .hwndunwrap();
if} !eatelse &&if (cellsgrid.binary_search(&new_h).is_err() || snk_cellssnake_cells.contains(&&new_h)) {
ctx.wnd.hwnd().SetTimerKillTimer(1, 1000)?; // FPS, None)?;Stop
let title = hwctx.SetWindowTextwnd.hwnd(&(hw).GetWindowText()? + " ALL !!!"))?;
ctx.wnd.hwnd().SetWindowText(&(title + ". Restart: Space"))?;
*ctx = snk_cellsContext::new(ctx.wnd.sortclone());
return Ok(());
} else if eat || ctx.id_r[food] == STARTCELL && ctx.id_r[tail] != STARTCELL {
if eat {
hw.SetWindowText(&format!("Snake - Eaten: {}.", snk_cells.len()))?;
}
if eat && snk_cells.len() == cells.len() - 2 {
hw.SetWindowText(&(hw.GetWindowText()? + " ALL !!!"))?;
ctx.id_r[food] = 0; // hide food: all eaten
} else if new_h != STARTCELL {
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.dirincr = ctx.ordered_dirnext_incr;
ctx.gap = if eat { RATIO } else { -RATIO }
}
ctx.snake.push(new_h);
ctx.wnd.hwnd().InvalidateRect(None, falsenew_h == ID0)?; // call .wm_paint(), withoutwith erase on Restart
Ok(())
});
 
if let Err(e) = wnd.run_main(None) {.unwrap();
HWND::NULL.MessageBox(&e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap();
}
}</syntaxhighlight>
{{out}}
[[File:Snake rust.png|thumb]]
 
=={{header|Sidef}}==
106

edits