Skip to content

Commit 223944d

Browse files
committed
snake render
1 parent 9c508bc commit 223944d

6 files changed

Lines changed: 182 additions & 42 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2+
> clone from https://github.com/tsoding/snake-c-wasm
3+
14
## Run
25

36
> need Rust/Python wasm-pack

src/game.rs

Lines changed: 155 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use std::{collections::VecDeque, ops::Not};
1+
use std::collections::VecDeque;
2+
3+
use web_sys::console;
24

35
use crate::{
46
render::PlatformRenderer,
5-
utils::{lerpf, rand},
7+
utils::{emod, lerpf, rand},
68
};
79

810
// Constants
@@ -12,8 +14,10 @@ const ROWS: i32 = 9;
1214
const BACKGROUND_COLOR: u32 = 0xFF181818;
1315
const CELL1_COLOR: u32 = BACKGROUND_COLOR;
1416
const CELL2_COLOR: u32 = 0xFF183018;
15-
const SNAKE_BODY_COLOR: u32 = 0xFF189018;
16-
const SNAKE_SPINE_COLOR: u32 = 0xFF185018;
17+
const SNAKE_HEAD_COLOR: u32 = 0xFF00FF00;
18+
const SNAKE_BODY_COLOR: u32 = 0xFF32CD32;
19+
const SNAKE_TAIL_COLOR: u32 = 0xFF228B22;
20+
const SNAKE_SPINE_COLOR: u32 = 0xFF006400;
1721
const EGG_BODY_COLOR: u32 = 0xFF31A6FF;
1822
const EGG_SPINE_COLOR: u32 = 0xFF3166BB;
1923
const SNAKE_SPINE_THICKNESS_PERCENT: f32 = 0.05;
@@ -39,7 +43,27 @@ enum Direction {
3943
Down = 3,
4044
}
4145

42-
impl Not for Direction {
46+
impl Direction {
47+
pub const ALL: [Direction; 4] = [
48+
Direction::Right,
49+
Direction::Up,
50+
Direction::Left,
51+
Direction::Down,
52+
];
53+
}
54+
55+
impl Into<Cell> for Direction {
56+
fn into(self) -> Cell {
57+
match self {
58+
Direction::Right => Cell { x: 1, y: 0 },
59+
Direction::Left => Cell { x: -1, y: 0 },
60+
Direction::Up => Cell { x: 0, y: -1 },
61+
Direction::Down => Cell { x: 0, y: 1 },
62+
}
63+
}
64+
}
65+
66+
impl std::ops::Not for Direction {
4367
type Output = Self;
4468

4569
fn not(self) -> Self::Output {
@@ -64,6 +88,34 @@ struct Vec2<I> {
6488
y: I,
6589
}
6690

91+
impl<I> std::ops::Sub for Vec2<I>
92+
where
93+
I: std::ops::Sub<Output = I>,
94+
{
95+
type Output = Vec2<I>;
96+
97+
fn sub(self, rhs: Self) -> Self::Output {
98+
Vec2 {
99+
x: self.x - rhs.x,
100+
y: self.y - rhs.y,
101+
}
102+
}
103+
}
104+
105+
impl<I> std::ops::Add for Vec2<I>
106+
where
107+
I: std::ops::Add<Output = I>,
108+
{
109+
type Output = Vec2<I>;
110+
111+
fn add(self, rhs: Self) -> Self::Output {
112+
Vec2 {
113+
x: self.x + rhs.x,
114+
y: self.y + rhs.y,
115+
}
116+
}
117+
}
118+
67119
struct Rect {
68120
x: f32,
69121
y: f32,
@@ -95,6 +147,29 @@ impl From<&Sides> for Rect {
95147

96148
type Cell = Vec2<i32>;
97149

150+
impl Cell {
151+
fn determine_dir(&self, another: &Cell) -> Direction {
152+
for dir in Direction::ALL {
153+
if self.advance(dir) == *another {
154+
return dir;
155+
}
156+
}
157+
unreachable!()
158+
}
159+
160+
fn advance(&self, dir: Direction) -> Cell {
161+
let dir_cell: Cell = dir.into();
162+
let mut res: Cell = dir_cell + *self;
163+
res.wrap_by_game_size();
164+
res
165+
}
166+
167+
fn wrap_by_game_size(&mut self) {
168+
self.x = emod(self.x, COLS);
169+
self.y = emod(self.y, ROWS);
170+
}
171+
}
172+
98173
struct Sides {
99174
lens: Vec<f32>,
100175
}
@@ -128,9 +203,13 @@ struct Snake {
128203
}
129204

130205
impl Snake {
131-
pub fn contains_cell(&self, cell: &Cell) -> bool {
206+
fn contains_cell(&self, cell: &Cell) -> bool {
132207
self.body.contains(cell)
133208
}
209+
210+
fn size(&self) -> usize {
211+
self.body.len()
212+
}
134213
}
135214

136215
pub struct Game<P: PlatformRenderer> {
@@ -203,7 +282,31 @@ impl<P: PlatformRenderer> Game<P> {
203282
self.random_egg(true);
204283
}
205284

206-
pub fn update(&mut self, delta_time: f64) {}
285+
pub fn update(&mut self, dt: f32) {
286+
let mut dt = dt;
287+
#[cfg(feature = "dev")]
288+
{
289+
dt *= self.dt_scale;
290+
}
291+
292+
match self.state {
293+
State::Gameplay => {
294+
self.step_cooldown -= dt;
295+
if self.step_cooldown > 0.0 {
296+
return;
297+
}
298+
299+
let next_head = self.snake.body.back().unwrap().advance(self.dir);
300+
self.snake.body.push_back(next_head);
301+
self.snake.body.pop_front();
302+
self.eating_egg = false;
303+
304+
self.step_cooldown = STEP_INTERVAL;
305+
}
306+
State::Pause => {}
307+
State::Gameover => {}
308+
}
309+
}
207310

208311
pub fn render(&self) {
209312
match self.state {
@@ -218,18 +321,59 @@ impl<P: PlatformRenderer> Game<P> {
218321
}
219322

220323
fn snake_render(&self) {
221-
// let t = self.step_cooldown / STEP_INTERVAL;
222-
let t = 0.5;
324+
let t = self.step_cooldown / STEP_INTERVAL;
223325

224326
let head_cell = self.snake.body.back().unwrap();
225327
let head_dir = self.dir;
226-
227328
let mut head_slide_sides: Sides = (&Rect::from(head_cell)).into();
228329
head_slide_sides.adjust_2_slide_sides(!head_dir, t);
229330

230-
let tail_cell = *self.snake.body.front().unwrap();
331+
let tail_cell = self.snake.body.front().unwrap();
332+
let mut tail_slide_sides: Sides = (&Rect::from(tail_cell)).into();
333+
let tail_dir = self
334+
.snake
335+
.body
336+
.get(0)
337+
.unwrap()
338+
.determine_dir(self.snake.body.get(1).unwrap());
339+
tail_slide_sides
340+
.adjust_2_slide_sides(tail_dir, if self.eating_egg { 1.0 } else { 1.0 - t });
341+
342+
if self.eating_egg {
343+
self.fill_cell(head_cell, EGG_BODY_COLOR, 1.0);
344+
self.fill_cell(
345+
head_cell,
346+
EGG_SPINE_COLOR,
347+
SNAKE_SPINE_THICKNESS_PERCENT * 2.0,
348+
);
349+
}
231350

351+
// self.fill_sides(&head_slide_sides, SNAKE_HEAD_COLOR);
352+
// self.fill_sides(&tail_slide_sides, SNAKE_TAIL_COLOR);
232353
self.fill_sides(&head_slide_sides, SNAKE_BODY_COLOR);
354+
self.fill_sides(&tail_slide_sides, SNAKE_BODY_COLOR);
355+
356+
for i in 1..self.snake.size() - 1 {
357+
self.fill_cell(self.snake.body.get(i).unwrap(), SNAKE_BODY_COLOR, 1.0);
358+
}
359+
360+
#[cfg(feature = "dev")]
361+
{
362+
for i in 0..self.snake.size() {
363+
console::log_1(&"test".into());
364+
self.stroke_rect(self.snake.body.get(i).unwrap().into(), 0xFF0000FF);
365+
}
366+
}
367+
}
368+
369+
fn stroke_rect(&self, rect: Rect, color: u32) {
370+
self.platform_renderer.stroke_rect(
371+
(rect.x - self.camera_pos.x) as i32 + (self.width / 2) as i32,
372+
(rect.y - self.camera_pos.y) as i32 + (self.height / 2) as i32,
373+
rect.w as i32,
374+
rect.h as i32,
375+
color,
376+
);
233377
}
234378

235379
fn fill_sides(&self, sides: &Sides, color: u32) {

src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use render::*;
1111

1212
thread_local! {
1313
static GAME: RefCell<Option<Game<WebPlatformRenderer>>> = RefCell::new(None);
14-
static PREV_TIMESTAMP: RefCell<f64> = RefCell::new(0.0);
14+
static PREV_TIMESTAMP: RefCell<f32> = RefCell::new(0.0);
1515
}
1616

1717
#[wasm_bindgen(start)]
@@ -52,16 +52,16 @@ fn main() {
5252
fn game_loop_fn_start() {
5353
window()
5454
.request_animation_frame(
55-
Closure::wrap(Box::new(|ts| game_loop_fn(ts)) as Box<dyn FnMut(f64)>)
55+
Closure::wrap(Box::new(|ts| game_loop_fn(ts)) as Box<dyn FnMut(f32)>)
5656
.into_js_value()
5757
.unchecked_ref(),
5858
)
5959
.unwrap();
6060
}
6161

62-
fn game_loop_fn(timestamp: f64) {
62+
fn game_loop_fn(timestamp: f32) {
6363
PREV_TIMESTAMP.with(|prev| {
64-
let dt = (timestamp - *prev.borrow()) as f64 / 1000.0;
64+
let dt = (timestamp - *prev.borrow()) as f32 / 1000.0;
6565
*prev.borrow_mut() = timestamp;
6666

6767
GAME.with(|game| {

src/render/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ pub use web::*;
33

44
pub trait PlatformRenderer {
55
fn fill_rect(&self, x: i32, y: i32, w: i32, h: i32, color: u32);
6+
fn stroke_rect(&self, x: i32, y: i32, w: i32, h: i32, color: u32);
7+
fn fill_text(&self, x: i32, y: i32, text: &str, font_size: u32, color: u32);
68
}

src/render/web.rs

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,18 @@ impl PlatformRenderer for WebPlatformRenderer {
2626
self.ctx.set_fill_style_str(&hex);
2727
self.ctx.fill_rect(x as f64, y as f64, w as f64, h as f64);
2828
}
29-
}
3029

31-
// pub fn platform_stroke_rect(
32-
// ctx: &CanvasRenderingContext2d,
33-
// x: f64,
34-
// y: f64,
35-
// w: f64,
36-
// h: f64,
37-
// color: u32,
38-
// ) {
39-
// let hex = format!("#{:08X}", color);
40-
// ctx.set_fill_style_str(&hex);
41-
// ctx.stroke_rect(x, y, w, h);
42-
// }
43-
44-
// pub fn platform_fill_text(
45-
// ctx: &CanvasRenderingContext2d,
46-
// x: f64,
47-
// y: f64,
48-
// text: &str,
49-
// size: u32,
50-
// color: u32,
51-
// ) {
52-
// let hex = format!("#{:08X}", color);
53-
// ctx.set_fill_style_str(&hex);
54-
// ctx.set_font(&format!("{}px", size));
55-
// ctx.fill_text(text, x, y).unwrap();
56-
// }
30+
fn stroke_rect(&self, x: i32, y: i32, w: i32, h: i32, color: u32) {
31+
let hex = self.color_hex(color);
32+
self.ctx.set_stroke_style_str(&hex);
33+
self.ctx.stroke_rect(x as f64, y as f64, w as f64, h as f64);
34+
}
35+
36+
fn fill_text(&self, x: i32, y: i32, text: &str, font_size: u32, color: u32) {
37+
let hex = self.color_hex(color);
38+
self.ctx.set_fill_style_str(&hex);
39+
40+
self.ctx.set_font(&format!("{}px", font_size));
41+
self.ctx.fill_text(text, x as f64, y as f64).unwrap();
42+
}
43+
}

src/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ pub fn rand() -> u32 {
1616
((RAND_STATE >> 32) & 0xFFFFFFFF) as u32
1717
}
1818
}
19+
20+
pub fn emod(a: i32, b: i32) -> i32 {
21+
(a % b + b) % b
22+
}

0 commit comments

Comments
 (0)