iolivia / rust-sokoban Goto Github PK
View Code? Open in Web Editor NEWRust Sokoban book and code samples
Home Page: https://sokoban.iolivia.me
License: MIT License
Rust Sokoban book and code samples
Home Page: https://sokoban.iolivia.me
License: MIT License
Hi Olivia! First of all thanks for your time and effort put into preparing this tutorial! I pick up Rust recently and I try to follow your explanation of the code step by step but in Chapter 1.4 Rendering Game
the implementation of the run
method seems outdated due to changes in ggez::graphics
crate, so that some methods can't be found anymore, are deprecated or their signatures have changed.
It would be cool and helpful for everyones, who finds and starts to follow your tutorial if the code is up-to-date, so it works and doesn't confuses.
My Cargo.toml
:
[package]
name = "sokoban"
version = "0.1.0"
edition = "2021"
[dependencies]
ggez = "0.8.1"
glam = { version = "0.22.0", features = ["mint"]}
specs = { version = "0.18.0", features = ["specs-derive"] }
I'm trying to fix all places, where code is outdated and compile no longer but it's a quite challenging task for someone, who has absolutely no experience in Rust and libraries used in the project π. Based on examples from ggez
repository, the code could be refactored as follows:
let (positions, renderables) = data;
// Clearing the screen (this gives us the background colour)
let mut canvas = Canvas::from_frame(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0));
// Get all the renderables with their positions and sort by the position z
// This will allow us to have entities layered visually.
let mut rendering_data = (&positions, &renderables).join().collect::<Vec<_>>();
rendering_data.sort_by_key(|&k| k.0.z);
// Iterate through all pairs of positions & renderables, load the image
// and draw it at the specified position.
for (position, renderable) in rendering_data.iter() {
// Load the image
let image = Image::from_path(self.context, renderable.path.clone()).expect("expected image");
let x = position.x as f32 * TILE_WIDTH;
let y = position.y as f32 * TILE_WIDTH;
// draw
let draw_params = DrawParam::new().dest(Vec2::new(x, y));
canvas.draw(&image, draw_params);
}
// Finally, present the context, this will actually display everything
// on the screen.
canvas.finish(self.context).expect("expected to present");
This is a release task so will probably only be done when the content is ready, but planning to integrate netlify and deploy to https://iolivia.me/rust-sokoban
or something very similar and short.
We have a CI thanks to @wbprice! π
Some future improvements:
Need to have a think about the book overall structure as it grows.
My thinking so far:
Ideas for prompts for the user to implement an addition themselves:
Was thinking it would be cool to have a build task for the code samples in the code
folder, it would prevent accidental breaks.
At first I thought: you could just use cmp
instead of partial_cmp.unwrap
but then at the second look, why don't you just use sort_by_key
(or better sort_unstable_key_by
which will improve performance for large slices because of the underlying sorting algorithmn)
Hi, really enjoying your tutorial series, however you mention you will be using traits like VecStorage
in chapter 3 but don't actually show them in the code and as it stands it won't compile.
Or is it intended that people look at the code in git repo or read the specs
docs instead?
Steps to reproduce:
N N W W W W W W
W W W . . . . W
W . P W B . . W
W . . . . . . W
W . . . . . . W
W . . . . . . W
W . . S . . . W
W . . . . . . W
W W W W W W W W
I was able to stop the behaviour by changing the match expression on line 173 to
match immov.get(&pos) {
Some(id) => {
to_move.clear();
break
},
None => break,
}
I haven't tested rigorously to see whether or not this introduces any new unwanted behaviour, but it seems to not do so.
InputSystem
uses input_queue.keys_pressed.pop()
and is commented as // Get the first key pressed
but pop()
removes the last item.
If you input keys fast enough (very easy when not using release mode and batch rendering) this can be seen because of erratic movement of the player character.
After the character is able to move and push boxes around, checking for the success condition is a good next step. Maybe a "You Win!" message is displayed.
mdbook allows you to put in a google analytics id and track usage per page - https://github.com/rust-lang/mdBook/blob/master/book-example/src/format/config.md
The GGEZ engine cannot create a window in said environment, returning [wayland-client error] Attempted to dispatch unknown opcode 0 for wl_shm, aborting.
error. This is tied to a compatibility issue with glutin
library, which is resolved in version 0.25
.
You can circumvent the issue by upgrading to ggez
version 0.16.0-rc0
, which has all the necessary fixes. With one quirk though: on Wayland it produces a window that has no styling. On XOrg everything works, resulting in a normal GNOME-styled window.
I'm not sure this bug needs any fixes on your side per se. Just leaving this here for anyone who stumbles upon a similar issue.
Thanks a lot everyone who offered to be an early beta tester!
Here are some questions to help structure your feedback:
I'll edit this issue if I can think of more stuff, but it would be awesome once you've gone through the book if you could write a sentence or two on each of the topics above. You can reply here on the issue or if you are not comfortable with that please DM me on Twitter π
EDIT: feedback summary
πpositives
π€to work on/think about
βquestions/more vague
#[storage(VecStorage)]
Attempting to just follow along, there are two missing code pieces in the book for the Gameplay section:
Incrementing the move count in the input system:
// We've just moved, so let's increase the number of moves
if to_move.len() > 0 {
gameplay.moves_count += 1;
}
And running the new system:
// Run gameplay state system
{
let mut gss = GameplayStateSystem {};
gss.run_now(&self.world);
}
It took me a while to track down the changes in the full code.
File books/en_US/src/images/readme.gif is quite big (55.65 MB) and is not used in the book. The only place where it seems to be referenced is the README.md file but using an incorrect path:
<img src="src/images/readme.gif" width="80%">
As the file is in books/en_US, it is duplicated with every translation, increasing disk usage. Github shows a warning message when pushing this file because of its size:
Total 69 (delta 8), reused 54 (delta 7), pack-reused 0
remote: Resolving deltas: 100% (8/8), completed with 1 local object.
remote: warning: See http://git.io/iEPt8g for more information.
remote: warning: File 23acb375f216b5989782ed46e8ce1a50cbd12bea is 55.65 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: warning: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote:
I think of two options:
I'm working on option 1, you can have a look at https://github.com/mexchip/rust-sokoban/tree/reduce-readme-gif-size, let me know if itΒ΄d be an acceptable change for you.
Section 3.4 states that the game is running slowly due to unbatched draw calls, but the reality is that rust's debug mode is very slow. Compiling in release mode or turning on optimizations when compiling in debug mode will make the game much faster. I added the following to make rust optimize in debug mode:
[profile.dev]
opt-level = 2
Here's a table with the performance I got with the various techniques:
Optimizations | Spritebatching | FPS |
---|---|---|
No | No | 7 fps |
No | Yes | 47 fps |
Yes | No | 144 fps |
Yes | Yes | 144 fps |
I know of this technique from following (and occasionally contributing to) veloren, which is a good example of how specs can be used to make a larger game.
The example on the first chapter fails to compile:
use ggez;
use ggez::{conf, event, Context, GameResult};
// This struct will hold all our game state
// For now there is nothing to be held, but we'll add
// things shortly.
struct Game {}
// This is the main event loop. ggez tells us to implement
// two things:
// - updating
// - rendering
impl event::EventHandler for Game {
fn update(&mut self, _context: &mut Context) -> GameResult {
// TODO: update game logic here
Ok(())
}
fn draw(&mut self, _context: &mut Context) -> GameResult {
// TODO: update draw here
Ok(())
}
}
pub fn main() -> GameResult {
// Create a game context and event loop
let context_builder = ggez::ContextBuilder::new("rust_sokoban", "sokoban")
.window_setup(conf::WindowSetup::default().title("Rust Sokoban!"))
.window_mode(conf::WindowMode::default().dimensions(800.0, 600.0))
.add_resource_path(path::PathBuf::from("./resources"));
let (context, event_loop) = &mut context_builder.build()?;
// Create the game state
let game = &mut Game {};
// Run the main event loop
event::run(context, event_loop, game)
}
β rust-sokoban git:(master) β cargo run
Compiling rust-sokoban v0.1.0 (/Users/wbprice/Documents/rust-sokoban)
error[E0433]: failed to resolve: use of undeclared type or module `path`
--> src/main.rs:30:28
|
30 | .add_resource_path(path::PathBuf::from("./resources"));
| ^^^^ use of undeclared type or module `path`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0433`.
error: could not compile `rust-sokoban`.
To learn more, run the command again with --verbose.
Probably just needs a use path;
at the top.
Using anchors will make it less painless to make changes without breaking the markdown and we could also use the line hiding feature of mdbook to make some of the explanations better by highlighting which lines are new/modified. High effort though, a good starting point might be doing this for new code.
https://rust-lang.github.io/mdBook/format/mdbook.html
I'm just reading the book and I noticed that the procedural macros in Chapter 1, Section 3 are missing, because they are treated as comments due to the leading #
.
This makes following this specific section a little hard as the text explains procedural macros, yet there are none in the code of the whole section. So it may be a good idea to either explain the macro annotations outside the snippet or alter the snippet (which may be not that easy as it is imported from the source file itself)?
Anyway, it's been a fun book so far, even for a long-time Rustacean, so thanks!
Will fill this in with the first round feedback nitpicks + text tweaks.
Add everyone who contributed with at least one PR to the front page of the book and also add a section with thanks to folks who beta tested and provided feedback!
Hi,
It should be impl Rendering system instead of pub struct when trying to do the animation.
I am talking about this function in the book:
// rendering_system.rs
pub struct RenderingSystem<'a> {
//...
pub fn get_image(&mut self, renderable: &Renderable, delta: Duration) -> Image {
let path_index = match renderable.kind() {
RenderableKind::Static => {
// We only have one image, so we just return that
0
}
RenderableKind::Animated => {
// If we have multiple, we want to select the right one based on the delta time.
// First we get the delta in milliseconds, we % by 1000 to get the seconds only
// and finally we divide by 250 to get a number between 0 and 4. If it's 4
// we technically are on the next iteration of the loop (or on 0), but we will let
// the renderable handle this logic of wrapping frames.
((delta.as_millis() % 1000) / 250) as usize
}
};
let image_path = renderable.path(path_index);
Image::new(self.context, image_path).expect("expected image")
}
}
First of all, thank you for the awesome book! I just started learning Rust and it was fun and helpful to build the game.
I had the issue that even after implementing batch rendering the FPS were somewhere between 40 and 50, but never 60. After finishing the book I continued playing around with the code trying to reach 60 FPS. I managed by introducing a cache for the images. Now the game reaches 60 FPS after a few seconds. Would this be an addition to the book you would be interested in?
I am not sure if my implementation is well done (really new to rust), but if you are interested:
The biggest change is https://github.com/NiklasEi/sokoban-rust/blob/master/src/systems/rendering_system.rs#L56-L63
Opening this as the master tracker issue for translations.
There are three main things to be tackled:
Also, if you come to this issue interested in a specific translation or would like to contribute by translating, leave a comment below including the language so we can keep track of interest, etc.
I'm coming into this with a few months of Rust experience, but nearly no experience with making games or using an ECS.
While working through this project I found some of the presentation of imports confusing. For example, when going from chapter 01-01 to chapter 01-03 a whole bunch of new imports are added all at once. Not all of them are actually used at the chapter 01-03 stage of the project, so I was confused by why the unused ones were added. I'm using chapter 01-03 as one example, but it seems there are unused imports at most stages of the book.
I also found the structure of the import statements unclear, e.g. importing ggez, then importing ggez::graphics, then ggez::graphics::DrawParam, expecially when some lines later the more compact curly brace notation is used. The imports feel unexplained to me in general---I would like to know things such as: whether the imported elements are modules, functions, traits, structs, or enums, a brief summary of the imported elements, and finally why they are imported at the current stage of the project. The first two questions are answered to an extent by the documentation on the crates themselves, so I feel that answering the third question here is most important.
Speaking more generally about the book itself, it is pretty good. I found it a useful exercise to go off the book's track at various places and implement my own additions to the sokoban game---the book provides enough detail to enable this. I'd like to know more about why games use ECS so often, as I do not know what kinds of problems come up that ECS is introduced to solve.
Moving the player is a bit slow, I remember a bug in ggez related with the event input queue, but it also might be my dodgy implementation, maybe what we are doing is a bit too expensive. Would be good to investigate if just getting input is slow or the movement system itself is slow, that should narrow it down between us and ggez.
Would be great to add a chapter about testing - some basic stuff like:
I am thinking this would be an ideal end to the Advanced gameplay chapter.
Just stumbled across this example of Sokoban, those animations are super neat! https://imgur.com/gallery/apzREUB
cc @mysterycommand this might lead to some next level graphics!
The only tricky bit here is to ensure all code references are changed correctly since the line numbers will be different.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.