{"payload":{"feedbackUrl":"https://github.com/orgs/community/discussions/53140","repo":{"id":365527099,"defaultBranch":"main","name":"2048","ownerLogin":"rust-adventure","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2021-05-08T13:54:48.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/75100245?v=4","public":true,"private":false,"isOrgOwned":true},"refInfo":{"name":"","listCacheKey":"v0:1713943834.0","currentOid":""},"activityList":{"items":[{"before":"68018dec4182c529073959244c078833bad18201","after":"e18aa106bc6a4590dc150f4fb650d36a58c58575","ref":"refs/heads/bevy-0.13-steps","pushedAt":"2024-05-05T06:09:19.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"impl blocks and associated functions\n\nWe're going to spawn more tiles in with the same logic we just used so we'll refactor that logic out into the `Board` 's associated functions using an `impl` block.\n\nWe'll create two functions: `Board::new` and `Board::cell_position_to_physical`, as well as creating a new field: `physical_size` on `Board`.\n\n```rust\n\\#[derive(Resource)]\nstruct Board {\n size: u8,\n physical_size: f32,\n}\n\nimpl Board {\n fn new(size: u8) -> Self {\n let physical_size = f32::from(size) * TILE_SIZE\n + f32::from(size + 1) * TILE_SPACER;\n Board {\n size,\n physical_size,\n }\n }\n fn cell_position_to_physical(&self, pos: u8) -> f32 {\n let offset =\n -self.physical_size / 2.0 + TILE_SIZE / 2.0;\n\n offset\n + f32::from(pos) * TILE_SIZE\n + f32::from(pos + 1) * TILE_SPACER\n }\n}\n```\n\n`Board::new` will be responsible for creating a new `Board` struct for us. The pattern of defining a `new` method is fairly common in the Rust ecosystem. We can take advantage of this to add an additional `physical_size` field on the struct, even though the user will never need to know how it's created.\n\n`Self` in this case refers to `Board` because that's the type we're implementing methods for.\n\n`cell_position_to_physical` is a copy/paste of the logic we've already defined from earlier lessons. The biggest difference is that we're now using `self.physical_size` because we are going to call this method on an instantiated struct, which means we have access to the fields on the `Board` struct. `cell_position_to_physical` will turn a grid position like 0,1,2,3 into a physical position like `10.0`, `50.0`, etc.\n\nIn our `Resource` we can now instantiate the board with\n\n```rust\n.insert_resource(Board::new(4))\n```\n\nand we can remove the old `physical_board_size` in `spawn_board`, replacing it with using `board.physical_size` when spawning our board.\n\n```rust\ncustom_size: Some(Vec2::splat(\n board.physical_size,\n)),\n```\n\nOur `Transform` component for the tile_placeholders can now use the board to translate an x or y into a physical position, cleaning up our code quite a bit by removing the math bits floating around, and giving a name to what we're trying to do in each place.\n\n```rust\ntransform: Transform::from_xyz(\n board.cell_position_to_physical(tile.0),\n board.cell_position_to_physical(tile.1),\n 1.0,\n),\n```\n\nremember to remove the `offset` from `.with_children` as well, since we aren't using it anymore.\n\n![full board](https://res.cloudinary.com/dilgcuzda/image/upload/v1714888911/workshops/2048-with-bevy-ecs/bevy-0.13/009-full-board.avif)","shortMessageHtmlLink":"impl blocks and associated functions"}},{"before":"bb3c0fd8036ce84fad669206709f38c6c3eca9ce","after":"68018dec4182c529073959244c078833bad18201","ref":"refs/heads/bevy-0.13-steps","pushedAt":"2024-05-05T04:46:59.000Z","pushType":"force_push","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Filling out the board with tile placeholders\n\nWe're going to place down a series of tile positions inside of the board to form our grid to play on. To do that we'll add a new [`Color`](https://docs.rs/bevy/0.13.2/bevy/render/color/enum.Color.html) to the `colors` module called `TILE_PLACEHOLDER`. Choose any color you want.\n\n```rust\npub const TILE_PLACEHOLDER: Color = Color::Lcha {\n lightness: 0.16,\n chroma: 0.088,\n hue: 281.0,\n alpha: 1.0,\n};\n```\n\nBack in `src/main.rs`, `.with_children` will give us a builder that we can use the same way we used `commands`, which means we can use `spawn` to render sprites as children of the board.\n\nWe do mostly the same thing as before, but with `colors::TILE_PLACEHOLDER` color instead of the `colors::BOARD` color. Most importantly we specify a `transform` field so that we can set the z-index. This will layer our new tile placeholders over the top of the board material.\n\nThis only spawns a single tile. We'll get to the others soon.\n\n```rust\n.with_children(|builder| {\n builder.spawn(SpriteBundle {\n sprite: Sprite {\n color: colors::TILE_PLACEHOLDER,\n custom_size: Some(Vec2::splat(\n TILE_SIZE,\n )),\n ..default()\n },\n transform: Transform::from_xyz(0., 0., 1.0),\n ..default()\n });\n});\n```\n\n![a tile](https://res.cloudinary.com/dilgcuzda/image/upload/v1714879269/workshops/2048-with-bevy-ecs/bevy-0.13/008-a-tile.avif)\n\nThis brings up an important aspect of how Bevy handles Sprites. The center of the sprite is where `x:0, y:0` is. If you place a sprite at `x:1, y:2`, then the *center* of the sprite will be at `x:1, y:2`.\n\nWe're going to re-center the sprite to make `x:0, y:0` be the bottom-left box in the board grid. To do this we'll take half the board size and move the tile from the center to the left and downward using a negative value. This places the center of the tile on the bottom left corner of the board.\n\n```rust\nlet offset = -physical_board_size / 2.0;\n```\n\n![bottom left tile](https://res.cloudinary.com/dilgcuzda/image/upload/v1714879447/workshops/2048-with-bevy-ecs/bevy-0.13/008-bottom-left-tile.avif)\n\nThen we need to move the tile half a tile size up and to the right which places the tile in the bottom left grid slot.\n\n```rust\nlet offset = -physical_board_size / 2.0\n + TILE_SIZE / 2.0;\n```\n\nThen we use the full offset value to change the x and y position of the tile, placing it squarely in the bottom left board cell.\n\n```rust\ntransform: Transform::from_xyz(\n offset, offset, 1.0,\n),\n```\n\n\\## Rendering more tiles\n\nNow we can render one tile in a grid position that we want it, but we need a tile placeholder for every spot. To do this we'll take advantage of [`Range`](https://doc.rust-lang.org/std/ops/struct.Range.html)s and [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html)s.\n\n`0..board.size` is a [`Range`](https://doc.rust-lang.org/std/ops/struct.Range.html) and will give us the numbers from 0 to the size of the board, not including the `board.size` number. We can use a for loop to iterate over each of the numbers.\n\nThis loop we can place inside the `with_children` call uses the `dbg!` macro, which will print the source file, line number, column number, and value of the expression we give it.\n\n```rust\nfor tile in 0..board.size {\n dbg!(tile);\n}\n```\n\nWhich results in this output.\n\n```\n[src/main.rs:54:17] tile = 0\n[src/main.rs:54:17] tile = 1\n[src/main.rs:54:17] tile = 2\n[src/main.rs:54:17] tile = 3\n```\n\nWe don't just need one number though, we need numbers for every tile in the grid. While we could write two for loops to generate all the numbers, we'll instead use this as an opportunity to introduce the `itertools` crate.\n\n```bash\ncargo add itertools@0.12.1\n```\n\nItertools provides an **extension trait** for iterators that gives us additional functions on Ranges. Specifically we're going to bring the extra functions into scope by bringing the extension trait `Itertools` into scope.\n\n```rust\nuse itertools::Itertools;\n```\n\nand then we'll use the [`cartesian_product`](https://docs.rs/itertools/0.12.1/itertools/trait.Itertools.html#method.cartesian_product) function to generate all of the grid points from our two ranges. \"cartesian product\" is a mathy word that means if the first range is all of the x values in our grid, and the second range is all of the y values in our grid, then what we get is an iterator over all of the x,y positions for all of the cells in that grid.\n\n```\nfor tile in (0..board.size)\n .cartesian_product(0..board.size)\n{\n dbg!(tile);\n}\n```\n\nThe values from each Range are combined into a `tuple`, which looks like this when using `dbg!`. Note that I've removed some whitespace here.\n\n```rust\n[src/main.rs:57:17] tile = (0,0)\n[src/main.rs:57:17] tile = (0,1)\n[src/main.rs:57:17] tile = (0,2)\n[src/main.rs:57:17] tile = (0,3)\n[src/main.rs:57:17] tile = (1,0)\n[src/main.rs:57:17] tile = (1,1)\n[src/main.rs:57:17] tile = (1,2)\n[src/main.rs:57:17] tile = (1,3)\n[src/main.rs:57:17] tile = (2,0)\n[src/main.rs:57:17] tile = (2,1)\n[src/main.rs:57:17] tile = (2,2)\n[src/main.rs:57:17] tile = (2,3)\n[src/main.rs:57:17] tile = (3,0)\n[src/main.rs:57:17] tile = (3,1)\n[src/main.rs:57:17] tile = (3,2)\n[src/main.rs:57:17] tile = (3,3)\n```\n\nTuples can be collections of any sort of value, even differnet types of values. In our case we have tuples of 2 `u8`s, an x and a y position. The x position is the first item in the tuple, which is 0-indexed, so we access it by writing `tile.0`.\n\nWe can shift the tile into its position by taking its grid position and multiplying that by the physical tile size, then adding it to the offset.\n\n```rust\n.with_children(|builder| {\n let offset = -physical_board_size / 2.0\n + 0.5 * TILE_SIZE;\n\n for tile in (0..board.size)\n .cartesian_product(0..board.size)\n {\n builder.spawn(SpriteBundle {\n sprite: Sprite {\n color: colors::TILE_PLACEHOLDER,\n custom_size: Some(Vec2::new(\n TILE_SIZE, TILE_SIZE,\n )),\n ..default()\n },\n transform: Transform::from_xyz(\n offset\n + f32::from(tile.0) * TILE_SIZE,\n offset\n + f32::from(tile.1) * TILE_SIZE,\n 1.0,\n ),\n ..default()\n });\n }\n})\n```\n\n![tiles over the board](https://res.cloudinary.com/dilgcuzda/image/upload/v1714882230/workshops/2048-with-bevy-ecs/bevy-0.13/008-tiles-over-board.avif)\n\nThis gives us what looks almost exactly like the board we already had... because we didn't make any gaps between the tiles.\n\n\\## Spacing the tiles out\n\nWe'll add a new `const` named `TILE_SPACER` to add in space between the tiles. Think of this as roughly \"10 pixels\" of physical space. A good place for it is at the top of the file next to `TILE_SIZE`.\n\n```rust\nconst TILE_SPACER: f32 = 10.0;\n```\n\nThen in our `physical_board_size` variable at the top of `spawn_board` we need to add space to our board. One space before every tile plus one space at the end. We'll use the `board.size` plus 1 times the `TILE_SPACER`.\n\n```rust\nlet physical_board_size = f32::from(board.size)\n * TILE_SIZE\n + f32::from(board.size + 1) * TILE_SPACER;\n```\n\nand in our offset for each tile, we add an amount of space equal to the grid position plus 1. If it's the first tile (at index 0), it gets one space, second gets two, and so on.\n\n```rust\noffset\n + f32::from(tile.0) * TILE_SIZE\n + f32::from(tile.0 + 1)\n * TILE_SPACER,\n```\n\nResulting in a spaced grid of tiles.\n\n![board with spaced tiles](https://res.cloudinary.com/dilgcuzda/image/upload/v1714882770/workshops/2048-with-bevy-ecs/bevy-0.13/008-board-with-spaced-tiles.avif)","shortMessageHtmlLink":"Filling out the board with tile placeholders"}},{"before":null,"after":"bb3c0fd8036ce84fad669206709f38c6c3eca9ce","ref":"refs/heads/bevy-0.13-steps","pushedAt":"2024-04-24T07:30:34.000Z","pushType":"branch_creation","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Changing the color of the background with ClearColor\n\nWith a fresh new coat of paint for the board we probably want to change the background as well.\n\nOur camera is responsible for painting all of the pixels we see. Any area of the screen that doesn't have a sprite on it will be painted using a “clear color”.\n\nWe can configure what the [`ClearColor`](https://docs.rs/bevy/0.13.2/bevy/prelude/struct.ClearColor.html) is by using a [`Resource`](https://docs.rs/bevy/0.13.2/bevy/ecs/prelude/trait.Resource.html) which will get accessed and used by Bevy's rendering system.\n\nWe created our own `Resource` earlier when we created our `Board` and we'll do the same here with the pre-existing `ClearColor`.\n\n```rust\nfn main() {\n App::new()\n .insert_resource(ClearColor(\n Color::hex(\"##1f2638\")\n .expect(\"developer should have provided a valid hex code\")\n ))\n .insert_resource(Board { size: 4 })\n .add_plugins(DefaultPlugins.set(WindowPlugin {\n primary_window: Some(Window {\n title: \"2048\".to_string(),\n ..default()\n }),\n ..default()\n }))\n .add_systems(Startup, (setup, spawn_board))\n .run()\n}\n```\n\nThis time we'll use the `[hex](https://docs.rs/bevy/0.13.2/bevy/render/color/enum.Color.html#method.hex)` function on `Color` to parse a hex code into a Color. Because this is parsing: it could fail if we don't give it a well formatted hex code, which is why the `hex` function returns a `Result`.\n\nIf we put in a hex code that doesn't work we'd rather see our program crash in development, so we can [`.expect()`](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap) the `Result` here, which will return the parsed `Color` for us. The message we write here is written in the format [suggested in the docs](https://doc.rust-lang.org/std/result/enum.Result.html#recommended-message-style) for `.expect`.\n\nA hex code that doesn't parse will panic and crash our program with a message like this:\n\n```\n❯ cargo run\n Compiling boxes v0.1.0 (/rust-adventure/2048)\n Finished dev [unoptimized + debuginfo] target(s) in 1.13s\n Running `target/debug/boxes`\nthread 'main' panicked at src/main.rs:8:36:\ndeveloper should have provided a valid hex code: Length\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n```\n\nWe wrap the `Color` in the `ClearColor` struct so Bevy will find it when looking for the clear color, and insert it as a `Resource`.\n\nWhen we `cargo run` now, we see the board in the middle of a blueish background.\n\n![a board with a clear color](https://res.cloudinary.com/dilgcuzda/image/upload/v1713942738/workshops/2048-with-bevy-ecs/bevy-0.13/007-clear-color.avif)","shortMessageHtmlLink":"Changing the color of the background with ClearColor"}},{"before":"57b0dd62aed52ae7dd9b8df6a00d7d2a93a48499","after":"9fecf84f1d13448f3083e32305fd30de74d3469b","ref":"refs/heads/bevy-0.13","pushedAt":"2024-04-11T03:30:54.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"bevy 0.13 updates and endgame test","shortMessageHtmlLink":"bevy 0.13 updates and endgame test"}},{"before":"4ccdf20cce1cb9453ae7733926fa85ab91e39b2d","after":"57b0dd62aed52ae7dd9b8df6a00d7d2a93a48499","ref":"refs/heads/bevy-0.13","pushedAt":"2024-03-08T05:27:24.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"include 9-slice textures, although well have to wait until 0.13.1","shortMessageHtmlLink":"include 9-slice textures, although well have to wait until 0.13.1"}},{"before":"447b6218dd1ee2284cdf0ace89af640b503f37e6","after":"4ccdf20cce1cb9453ae7733926fa85ab91e39b2d","ref":"refs/heads/bevy-0.13","pushedAt":"2024-02-23T08:55:57.000Z","pushType":"push","commitsCount":2,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"add clippy lints","shortMessageHtmlLink":"add clippy lints"}},{"before":null,"after":"447b6218dd1ee2284cdf0ace89af640b503f37e6","ref":"refs/heads/bevy-0.13","pushedAt":"2024-02-22T19:08:52.000Z","pushType":"branch_creation","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"initial upgrade to 0.13","shortMessageHtmlLink":"initial upgrade to 0.13"}},{"before":"74a05ca23df50eda3fd50c37f16240b81bc6567b","after":"772e2806c0a3f65b9945f23f39b0209fafccd734","ref":"refs/heads/bevy-0.12","pushedAt":"2023-11-11T19:18:10.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"make board a resource","shortMessageHtmlLink":"make board a resource"}},{"before":"53fd665f03cf5b1a64b1aef1961f2d7221e6ec4b","after":"74a05ca23df50eda3fd50c37f16240b81bc6567b","ref":"refs/heads/bevy-0.12","pushedAt":"2023-11-07T16:50:45.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"0.12 release","shortMessageHtmlLink":"0.12 release"}},{"before":"a437fe1b0ab15560abe4d2b3e2a8b66b97d6e6b9","after":"53fd665f03cf5b1a64b1aef1961f2d7221e6ec4b","ref":"refs/heads/bevy-0.12","pushedAt":"2023-10-27T06:21:16.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"board.tiles() and text sections","shortMessageHtmlLink":"board.tiles() and text sections"}},{"before":null,"after":"a437fe1b0ab15560abe4d2b3e2a8b66b97d6e6b9","ref":"refs/heads/bevy-0.12","pushedAt":"2023-10-27T06:13:44.000Z","pushType":"branch_creation","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"initial 0.12 update","shortMessageHtmlLink":"initial 0.12 update"}},{"before":"0dc3ddd0f0cf60132fccc4817bdfef094c66d0c0","after":"e96b72a6adef96b8fbeb759337b40039ab1feb97","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-19T22:10:38.000Z","pushType":"push","commitsCount":5,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Animating tiles when board shifts happen\n\nThat's it, the game works, we have high scores, and we can start a new game.\n\nOn top of that, it would be nice if the tiles slid into place instead of magically \"appearing\" at their next location.\n\nTo do that we can use a plugin called `bevy_easings`. We need to add it to our `Cargo.toml`, which I'll do with cargo-edit.\n\n```bash\ncargo add bevy_easings@0.10.0\n```\n\nThen we can bring everything from `bevy_easings` into scope\n\n```rust\nuse bevy_easings::*;\n```\n\nand add the plugin to our app builder.\n\n\n\n```rust\n.add_plugin(EasingsPlugin)\n```\n\nThen we can get right to editing the `render_tiles` system. We'll need `commands` access and the tile `Entity` as well. We no longer need mutable access to `Transform` because we'll be using `bevy_easings` to handle that for us. Now that we've learned about filters we can move `Changed` into the filter position in our query so that we only operate on tiles that have changed `Position` (before, we were manually checking that for each tile).\n\n```rust\nfn render_tiles(\n mut commands: Commands,\n mut tiles: Query<\n (Entity, &mut Transform, &Position),\n Changed,\n >,\n query_board: Query<&Board>,\n) {\n```\n\nWe iterate over the queried tiles and change the part where we're setting the `transform` to declaring our new `x` and `y` variables.\n\nThen we need to get the `EntityCommands` like we did when we used `despawn_recursive` and we can use that to insert a new component.\n\nWhen we brought `bevy_easings::*` into scope, it added a function for us to `Transform` components called `ease_to`. `ease_to` creates a new component that the `bevy_easings` plugin systems can handle to ease our tiles across the screen. `ease_to` takes three arguments: the final `Transform` position, an easing function and an easing type.\n\nThe easing function defines how we interpolate between the original position and the final position of the tile. It's how we pick each of the x and y positions along the way.\n\n`EasingType` gives us a way to define how many times the loop should run. The variants are `Once`, `Loop`, and `PingPong`. In our case we want `EasingType::Once` with a duration of 100ms.\n\n```rust\nlet board = query_board.single();\nfor (entity, transform, pos) in tiles.iter_mut() {\n let x = board.cell_position_to_physical(pos.x);\n let y = board.cell_position_to_physical(pos.y);\n commands.entity(entity).insert(transform.ease_to(\n Transform::from_xyz(\n x,\n y,\n transform.translation.z,\n ),\n EaseFunction::QuadraticInOut,\n EasingType::Once {\n duration: std::time::Duration::from_millis(\n 100,\n ),\n },\n ));\n}\n```\n\nand now when we run our game, any tile that already exists on the board will get animated into it's new location when a board shift happens.","shortMessageHtmlLink":"Animating tiles when board shifts happen"}},{"before":"3c0631f7d386858b6f13637d93afd41b9d700b8c","after":"0dc3ddd0f0cf60132fccc4817bdfef094c66d0c0","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-14T20:17:31.604Z","pushType":"push","commitsCount":5,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Are there any moves left?\n\nAt some point there won't be any more tiles that can be placed on the board and we won't be able to merge any of the tiles using a board shift. This is when the game ends.\n\nWe'll use an `end_game` system to check to see if the game should be over.\n\n```rust\n.add_systems((\n render_tile_points,\n board_shift,\n render_tiles,\n new_tile_handler,\n end_game,\n))\n```\n\nThe `end_game` system will query for the `Position` and `Points` components for each tile as well as the `Board` itself.\n\n```rust\nfn end_game(\n tiles: Query<(&Position, &Points)>,\n query_board: Query<&Board>,\n) {\n ...\n}\n```\n\nThe game can only be over if the board is full, so we do a quick check to see if there are 16 tiles on the board.\n\n```rust\nif tiles.iter().len() == 16 {\n ...\n}\n```\n\nWe're going to build up a `HashMap`. A `HashMap` is very similar to a JavaScript object except we can use structs as keys and values. The key in our `HashMap` is the `Position` and the value is the `Points` value of the tile.\n\nTo use `Position` as the keys in our `HashMap`, we will need to add the `Eq` and `Hash` traits to our derive.\n\n```rust\n Debug, Component, PartialEq, Copy, Clone, Eq, Hash,\n)]\nstruct Position {\n x: u8,\n y: u8,\n}\n```\n\nWe can create the `HashMap<&Position, &Points>` in one line since we already have an `Iterator` over items of `(&Position, &Points)`.\n\n```rust\nlet map: HashMap<&Position, &Points> = tiles.iter().collect();\n```\n\nThe `HashMap` allows us to access each `Points` value by `Position`. This helps us because we need to check each `Points` value in the left, top, right, and bottom side neighbors of each tile.\n\nWe can construct an array of four x,y tuples with the relative values of each tile position. In this case, the first tuple: `(-1,0)` means that we're going to check the tile to the left of our current tile.\n\n```rust\nlet neighbor_points = [(-1, 0), (0, 1), (1, 0), (0, -1)];\n```\n\nThen we'll create a `Range` of `i8` numbers from `0` to the size of the board. We need an `i8` because we have negative numbers in our `neighbor_points` which when added to a tile position could result in a negative number.\n\n```rust\nlet board_range: Range = 0..(board.size as i8);\n```\n\nTo determine if there is any valid move on the board, we'll iterate over the `tiles` and check to see if any of them have a valid move available using `any`. The resulting boolean will then tell us whether or not we found a valid move.\n\nWe can destructure the `x` and `y` values out of a `Position` to use in the `any` closure.\n\n```rust\nlet has_move = tiles\n .iter()\n .any(|(Position { x, y }, value)| {\n ...\n })\n .is_some();\n```\n\nOnce we have a tile to work with, we can iterate over the `neighbor_points` to get each of the tiles adjacent to the one we have now.\n\nWe'll take advantage of `filter_map` which lets us both `.filter` and `.map` at the same time. This is ergonomically useful for us as we plan to return an `Option` type, and `filter` will remove `None` values. We also plan to map the relative positions into the relevant `Points` values.\n\n```rust\nneighbor_points\n .iter()\n .filter_map(...)\n```\n\nSince we could go negative (think of a tile at `Position{ x: 0, y: 0}`) we'll need to convert our `u8` `x` and `y` into `i8`s. We do this by derefencing the variable and using `as` to cast the type.\n\nIn general, we should avoid using `as` where possible. There are alternatives in many situations that are preferable such as `.from` or `.into` which are guaranteed to not fail, or `try_from` and `try_into` which explicitly encode what failures can happen. If you choose to use `as` to cast one numeric type into another, be sure that you're aware of how it can fail for those types.\n\nIn our case, the possible values for our `x` or `y` `u8`s will be at most `4` which comfortably fits in both a `u8` and an `i8`.\n\n```rust\nlet new_x = *x as i8 - x2;\nlet new_y = *y as i8 - y2;\n```\n\nAfter generating the tile position for the neighbor tile, we check to see if the new x and y positions exist inside of the board grid. If they don't, for example `(-1, 0)` would not, then we return `None` because there can be not tile there.\n\nTo wrap up our `filter_map` we can use `try_into` to convert back to `u8` and `.get` the value from our `HashMap` using the new `Position` struct. `.get` will return an `Option<&Points>` if the `Position` is valid. We can convert safely back into a `u8` because the `board_range` check will have caught any negative numbers.\n\n```rust\nneighbor_points\n .iter()\n .filter_map(|(x2, y2)| {\n let new_x = *x as i8 - x2;\n let new_y = *y as i8 - y2;\n\n if !board_range.contains(&new_x)\n || !board_range.contains(&new_y)\n {\n return None;\n };\n\n map.get(&Position {\n x: new_x.try_into().unwrap(),\n y: new_y.try_into().unwrap(),\n })\n })\n .any(|&v| v == value)\n```\n\nFinally we check to see if `.any` of the valid tile `Position`s' `Points` values can be merged with the tile we're checking.\n\nTo compare two `Points` values we need to derive the `PartialEq` trait for `Points`.\n\n```rust\n\\#[derive(Debug, Component, PartialEq)]\nstruct Points {\n value: u32,\n}\n```\n\nAll in all, we check all of the neighbor tiles for each tile in the board and return as soon as we find one valid move. If there turns out to be no valid move, then the game is over.\n\n```rust\nif has_move == false {\n dbg!(\"game over!\");\n}\n```\n\nNote that in the current state, the `game over!` notification will spam the console because the system is still running every tick.","shortMessageHtmlLink":"Are there any moves left?"}},{"before":"9900651c9d74dcf4c54927a8b8e1141a6c6a828d","after":"3c0631f7d386858b6f13637d93afd41b9d700b8c","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-14T02:25:14.080Z","pushType":"force_push","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Board Shifts in 4 directions\n\nSo now we have a left board shift done and working. To implement the other three directions we can think about them as left shifts as well. A downward shift is a left-shift applied to a board rotated 90 degrees. A right-shift is a left-shift applied to a board rotated 180 degrees, and a up-shift is a left shift applied to a board rotated 270 degrees.\n\nWith this knowledge, as long as we can order the tiles to appear as if we're applying a left-shift, we don't have to change the logic comparing values for merging.\n\nWe'll move the logic for a left-shift into an impl for `BoardShift`. We will have to have a `BoardShift` to tell which direction to handle in the first place, so putting the logic here makes sense although we could also write a set of independent functions that wasn't on the `BoardShift` struct.\n\nWe'll move the sort closure into a `sort` method. Changing the types to take shared references to `self`, and two `Position`s. We take shared references because we aren't mutating anything, so don't need exclusive access or to own the data. We're effectively telling the sort function \"hey, take a look at these and tell me what you think, but they're not yours\"\n\n```rust\nimpl BoardShift {\n fn sort(&self, a: &Position, b: &Position) -> Ordering {\n match Ord::cmp(&a.y, &b.y) {\n Ordering::Equal => Ord::cmp(&a.x, &b.x),\n ordering => ordering,\n }\n }\n fn set_column_position(\n &self,\n position: &mut Mut,\n index: u8,\n ) {\n position.x = index;\n }\n fn get_row_position(&self, position: &Position) -> u8 {\n position.y\n }\n}\n```\n\nWe're calling our other functions `set_column_position` and `get_row_position` because when we rotate the board with the sort, the row could be the x or the y. So instead of calling it `set_x` we call it `set_column` to match how we're thinking about the board. x and y are implementation details where our algorithm only makes us think about rows and columns no matter which direction we're shifting in.\n\nTo apply the changes we'll change our `match shift_direction` to be a bit more general and match on the `Some` structure, leaving the board shift in a variable for all variants of the enum.\n\n```rust\nSome(board_shift) =>\n```\n\nThis causes the other exact `Some` matches to be \"unreachable patterns\" so we can remove them. They're unreachable because the structure of `Some(variable)` will match all valid `BoardShift` variants before they get a chance to match the exact matches further below.\n\nThis brings our match down to \"if Some, do something, else do nothing\". Rust has an idiomatic way of dealing with exactly this situation: if-let.\n\nif-let does the same kind of matching as `match` with the difference that we only care about a single possible match. if `shift_direction` matches the structure `Some()` then let `board_shift` equal the inner value. In this case if we don't match, we do nothing, but we could use an `else` to do something for anything that doesn't match the if-let.\n\n```rust\nif let Some(board_shift) = shift_direction {...}\n```\n\nThe sort also changes to accept a `board_shift.sort`. Our method implicitly accepts a reference to `self`, so calling the `.sort` method on a board shift value already allows us to access it. We do need to explicitly pass in the two `&Position` arguments though. They're shared references so when we pass them in we make that clear using `&`.\n\nThis will let us use a different sort for each direction while not polluting the surrounding logic.\n\n```rust\nlet mut it = tiles\n .iter_mut()\n .sorted_by(|a, b| board_shift.sort(&a.1, &b.1))\n .peekable();\n```\n\nInside of our iteration, we replace setting the column with `set_column_position`. We pass in a mutable reference to the `Position` because it needs to be mutated, but we also need it back.\n\n```rust\nwhile let Some(mut tile) = it.next() {\n board_shift.set_column_position(\n board.size,\n &mut tile.1,\n column,\n );\n ...\n}\n```\n\nNow that we know about if-let we can use it for the `it.peek` as well since in the `None` case we do nothing.\n\n```rust\nif let Some(tile_next) = it.peek() {\n```\n\nThe only change left to make in our `board_shift` function is to replace any row accesses with `.get_row_position`.\n\n```rust\nif let Some(tile_next) = it.peek() {\n if board_shift.get_row_position(&tile.1)\n != board_shift\n .get_row_position(&tile_next.1)\n {\n // different rows, don't merge\n column = 0;\n } else if tile.2.value != tile_next.2.value\n {\n // different values, don't merge\n column = column + 1;\n } else {\n // merge\n // despawn the next tile, and\n // merge it with the current\n // tile.\n let real_next_tile = it.next()\n .expect(\"A peeked tile should always exist when we .next here\");\n tile.2.value = tile.2.value\n + real_next_tile.2.value;\n\n commands\n .entity(real_next_tile.0)\n .despawn_recursive();\n\n // if the next, next tile\n // (tile #3 of 3)\n // isn't in the same row, reset\n // x\n // otherwise increment by one\n if let Some(future) = it.peek() {\n if board_shift\n .get_row_position(&tile.1)\n != board_shift\n .get_row_position(&future.1)\n {\n column = 0;\n } else {\n column = column + 1;\n }\n }\n }\n}\n```\n\nAt this point you should be able to run the game and still be able to shift the board left the same way it ran before.\n\nWe can use Rust Analyzer here to fill out the arms of the match.\n\n```rust\nmatch self {\n BoardShift::Left => todo!(),\n BoardShift::Right => todo!(),\n BoardShift::Up => todo!(),\n BoardShift::Down => todo!(),\n}\n```\n\n`sort` continues in the same way we started. We sort each set of tiles for each `BoardShift` direction so that it looks like we're operating on all of them in the same way we did in the left shift. This is just like we tilted our head 90 degrees and looked at the board as if it was a left shift again. In this way we can treat a downward board shift as a left shift, if we look at the board 90 rotated.\n\n```rust\nfn sort(&self, a: &Position, b: &Position) -> Ordering {\n match self {\n BoardShift::Left => {\n match Ord::cmp(&a.y, &b.y) {\n Ordering::Equal => Ord::cmp(&a.x, &b.x),\n ordering => ordering,\n }\n }\n BoardShift::Right => {\n match Ord::cmp(&b.y, &a.y) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&b.x, &a.x)\n }\n a => a,\n }\n }\n BoardShift::Up => match Ord::cmp(&b.x, &a.x) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&b.y, &a.y)\n }\n ordering => ordering,\n },\n BoardShift::Down => {\n match Ord::cmp(&a.x, &b.x) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&a.y, &b.y)\n }\n ordering => ordering,\n }\n }\n }\n}\n```\n\nAs we expand the `set_column_position` we realize that we need the board size to make sure we stop the tiles at the far ends of the board.\n\nTo do that we'll add a `query_board` to our `board_shift` system.\n\n```rust\nquery_board: Query<&Board>,\n```\n\nAnd grab the existing board with `.single`\n\n```rust\nlet board = query_board.single();\n```\n\nThen we get to see the pattern in setting column positions. When shifting left, we set the x to the current index, which starts at 0. When shifting right we set x to the current index, but starting at the end of the board instead of the beginning. When the board is rotated so that y is the column, it behaves the same.\n\n```rust\nfn set_column_position(\n &self,\n board_size: u8,\n position: &mut Mut,\n index: u8,\n) {\n match self {\n BoardShift::Left => {\n position.x = index;\n }\n BoardShift::Right => {\n position.x = board_size - 1 - index\n }\n BoardShift::Up => {\n position.y = board_size - 1 - index\n }\n BoardShift::Down => {\n position.y = index;\n }\n }\n}\n```\n\n`get_row_position` has the least amount of logic out of all three methods. It returns whichever value represents the row. In a left-shift or right-shift, that's y. In an up or down-shift, that's x.\n\nWe can list all four options out as we've been doing in the other examples.\n\n```rust\nfn get_row_position(&self, position: &Position) -> u8 {\n match self {\n BoardShift::Left => position.y,\n BoardShift::Right => position.y,\n BoardShift::Up => position.x,\n BoardShift::Down => position.x,\n }\n}\n```\n\nor we can use Rust's ability to match on multiple patterns using `|`.\n\n```rust\nfn get_row_position(&self, position: &Position) -> u8 {\n match self {\n BoardShift::Left | BoardShift::Right => position.y,\n BoardShift::Up | BoardShift::Down => position.x,\n }\n}\n```\n\nWith the additional logic in place, we can now run the game and see all four directions working.","shortMessageHtmlLink":"Board Shifts in 4 directions"}},{"before":"f9aebf6aebf31521a48c620fe2c84ca9e9a26fa9","after":"9900651c9d74dcf4c54927a8b8e1141a6c6a828d","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-14T02:23:49.589Z","pushType":"push","commitsCount":5,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Board Shifts in 4 directions\n\nSo now we have a left board shift done and working. To implement the other three directions we can think about them as left shifts as well. A downward shift is a left-shift applied to a board rotated 90 degrees. A right-shift is a left-shift applied to a board rotated 180 degrees, and a up-shift is a left shift applied to a board rotated 270 degrees.\n\nWith this knowledge, as long as we can order the tiles to appear as if we're applying a left-shift, we don't have to change the logic comparing values for merging.\n\nWe'll move the logic for a left-shift into an impl for `BoardShift`. We will have to have a `BoardShift` to tell which direction to handle in the first place, so putting the logic here makes sense although we could also write a set of independent functions that wasn't on the `BoardShift` struct.\n\nWe'll move the sort closure into a `sort` method. Changing the types to take shared references to `self`, and two `Position`s. We take shared references because we aren't mutating anything, so don't need exclusive access or to own the data. We're effectively telling the sort function \"hey, take a look at these and tell me what you think, but they're not yours\"\n\n```rust\nimpl BoardShift {\n fn sort(&self, a: &Position, b: &Position) -> Ordering {\n match Ord::cmp(&a.y, &b.y) {\n Ordering::Equal => Ord::cmp(&a.x, &b.x),\n ordering => ordering,\n }\n }\n fn set_column_position(\n &self,\n position: &mut Mut,\n index: u8,\n ) {\n position.x = index;\n }\n fn get_row_position(&self, position: &Position) -> u8 {\n position.y\n }\n}\n```\n\nWe're calling our other functions `set_column_position` and `get_row_position` because when we rotate the board with the sort, the row could be the x or the y. So instead of calling it `set_x` we call it `set_column` to match how we're thinking about the board. x and y are implementation details where our algorithm only makes us think about rows and columns no matter which direction we're shifting in.\n\nTo apply the changes we'll change our `match shift_direction` to be a bit more general and match on the `Some` structure, leaving the board shift in a variable for all variants of the enum.\n\n```rust\nSome(board_shift) =>\n```\n\nThis causes the other exact `Some` matches to be \"unreachable patterns\" so we can remove them. They're unreachable because the structure of `Some(variable)` will match all valid `BoardShift` variants before they get a chance to match the exact matches further below.\n\nThis brings our match down to \"if Some, do something, else do nothing\". Rust has an idiomatic way of dealing with exactly this situation: if-let.\n\nif-let does the same kind of matching as `match` with the difference that we only care about a single possible match. if `shift_direction` matches the structure `Some()` then let `board_shift` equal the inner value. In this case if we don't match, we do nothing, but we could use an `else` to do something for anything that doesn't match the if-let.\n\n```rust\nif let Some(board_shift) = shift_direction {...}\n```\n\nThe sort also changes to accept a `board_shift.sort`. Our method implicitly accepts a reference to `self`, so calling the `.sort` method on a board shift value already allows us to access it. We do need to explicitly pass in the two `&Position` arguments though. They're shared references so when we pass them in we make that clear using `&`.\n\nThis will let us use a different sort for each direction while not polluting the surrounding logic.\n\n```rust\nlet mut it = tiles\n .iter_mut()\n .sorted_by(|a, b| board_shift.sort(&a.1, &b.1))\n .peekable();\n```\n\nInside of our iteration, we replace setting the column with `set_column_position`. We pass in a mutable reference to the `Position` because it needs to be mutated, but we also need it back.\n\n```rust\nwhile let Some(mut tile) = it.next() {\n board_shift.set_column_position(\n board.size,\n &mut tile.1,\n column,\n );\n ...\n}\n```\n\nNow that we know about if-let we can use it for the `it.peek` as well since in the `None` case we do nothing.\n\n```rust\nif let Some(tile_next) = it.peek() {\n```\n\nThe only change left to make in our `board_shift` function is to replace any row accesses with `.get_row_position`.\n\n```rust\nif let Some(tile_next) = it.peek() {\n if board_shift.get_row_position(&tile.1)\n != board_shift\n .get_row_position(&tile_next.1)\n {\n // different rows, don't merge\n column = 0;\n } else if tile.2.value != tile_next.2.value\n {\n // different values, don't merge\n column = column + 1;\n } else {\n // merge\n // despawn the next tile, and\n // merge it with the current\n // tile.\n let real_next_tile = it.next()\n .expect(\"A peeked tile should always exist when we .next here\");\n tile.2.value = tile.2.value\n + real_next_tile.2.value;\n\n commands\n .entity(real_next_tile.0)\n .despawn_recursive();\n\n // if the next, next tile\n // (tile #3 of 3)\n // isn't in the same row, reset\n // x\n // otherwise increment by one\n if let Some(future) = it.peek() {\n if board_shift\n .get_row_position(&tile.1)\n != board_shift\n .get_row_position(&future.1)\n {\n column = 0;\n } else {\n column = column + 1;\n }\n }\n }\n}\n```\n\nAt this point you should be able to run the game and still be able to shift the board left the same way it ran before.\n\nWe can use Rust Analyzer here to fill out the arms of the match.\n\n```rust\nmatch self {\n BoardShift::Left => todo!(),\n BoardShift::Right => todo!(),\n BoardShift::Up => todo!(),\n BoardShift::Down => todo!(),\n}\n```\n\n`sort` continues in the same way we started. We sort each set of tiles for each `BoardShift` direction so that it looks like we're operating on all of them in the same way we did in the left shift. This is just like we tilted our head 90 degrees and looked at the board as if it was a left shift again. In this way we can treat a downward board shift as a left shift, if we look at the board 90 rotated.\n\n```rust\nfn sort(&self, a: &Position, b: &Position) -> Ordering {\n match self {\n BoardShift::Left => {\n match Ord::cmp(&a.y, &b.y) {\n Ordering::Equal => Ord::cmp(&a.x, &b.x),\n ordering => ordering,\n }\n }\n BoardShift::Right => {\n match Ord::cmp(&b.y, &a.y) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&b.x, &a.x)\n }\n a => a,\n }\n }\n BoardShift::Up => match Ord::cmp(&b.x, &a.x) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&b.y, &a.y)\n }\n ordering => ordering,\n },\n BoardShift::Down => {\n match Ord::cmp(&a.x, &b.x) {\n std::cmp::Ordering::Equal => {\n Ord::cmp(&a.y, &b.y)\n }\n ordering => ordering,\n }\n }\n }\n}\n```\n\nAs we expand the `set_column_position` we realize that we need the board size to make sure we stop the tiles at the far ends of the board.\n\nTo do that we'll add a `query_board` to our `board_shift` system.\n\n```rust\nquery_board: Query<&Board>,\n```\n\nAnd grab the existing board with `.single`\n\n```rust\nlet board = query_board.single();\n```\n\nThen we get to see the pattern in setting column positions. When shifting left, we set the x to the current index, which starts at 0. When shifting right we set x to the current index, but starting at the end of the board instead of the beginning. When the board is rotated so that y is the column, it behaves the same.\n\n```rust\nfn set_column_position(\n &self,\n board_size: u8,\n position: &mut Mut,\n index: u8,\n) {\n match self {\n BoardShift::Left => {\n position.x = index;\n }\n BoardShift::Right => {\n position.x = board_size - 1 - index\n }\n BoardShift::Up => {\n position.y = board_size - 1 - index\n }\n BoardShift::Down => {\n position.y = index;\n }\n }\n}\n```\n\n`get_row_position` has the least amount of logic out of all three methods. It returns whichever value represents the row. In a left-shift or right-shift, that's y. In an up or down-shift, that's x.\n\nWe can list all four options out as we've been doing in the other examples.\n\n```rust\nfn get_row_position(&self, position: &Position) -> u8 {\n match self {\n BoardShift::Left => position.y,\n BoardShift::Right => position.y,\n BoardShift::Up => position.x,\n BoardShift::Down => position.x,\n }\n}\n```\n\nor we can use Rust's ability to match on multiple patterns using `|`.\n\n```rust\nfn get_row_position(&self, position: &Position) -> u8 {\n match self {\n BoardShift::Left | BoardShift::Right => position.y,\n BoardShift::Up | BoardShift::Down => position.x,\n }\n}\n```\n\nWith the additional logic in place, we can now run the game and see all four directions working.","shortMessageHtmlLink":"Board Shifts in 4 directions"}},{"before":"1cece0344ba06d877ec9e140340e01ee78907844","after":"f9aebf6aebf31521a48c620fe2c84ca9e9a26fa9","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-13T05:50:23.324Z","pushType":"push","commitsCount":5,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Updating Tile display when Point values change\n\nAs we progress into building out the movement and merging functionality for tiles, we'll want to be able to see the point values as they change.\n\nTo do this we can write a system that is responsible for updating the tile text when the point values change.\n\nStarting off, we'll change the text section we display by default to 4.\n\n```rust\nText::from_section(\"4\",...\n```\n\nRunning the game with `cargo run` will now show a board with tiles that display `4`.\n\nNote that we didn't change the underlying points value, so if our system is successful the board will change from displaying 4, the default, to 2, the underlying points value.\n\nThe system is going to run all the time while we're playing, so we can use `.add_system` to add the `render_tile_points` system.\n\n```rust\n.add_system(render_tile_points)\n```\n\nThe `render_tile_points` system is going to query for all of the `Text` components that we labelled with `TileText`. The query is labelled `mut` and the `Text` is a mutable reference because we're going to be mutating the text section displaying the score.\n\nWe also need to query for all the entities with `Points` and `Children` which will give us all of the tiles on the board. The `Points` and `Children` come in as shared references because we aren't mutating them and thus don't need exclusive access.\n\n```rust\nfn render_tile_points(\n mut texts: Query<&mut Text, With>,\n tiles: Query<(&Points, &Children)>,\n) {\n for (points, children) in tiles.iter() {\n if let Some(entity) = children.first() {\n let mut text = texts\n .get_mut(*entity)\n .expect(\"expected Text to exist\");\n let mut text_section = text.sections.first_mut().expect(\"expect first section to be accessible as mutable\");\n text_section.value = points.value.to_string()\n }\n }\n}\n```\n\nWe can use Iterators to iterate over all of the tiles, destructuring the points and children from the tuple.\n\nWe expect that our tiles will have the entity with the `Text` component as the first child (because that's how we built them) so we access that with `children.first()`.\n\n`if-let` allows us to match on the structure of the return value from `children.first()`. In this case we receive an `Option` type and we only want to do anything with it if it's some entity. We can destructure the `entity` out of the `Some` type, and if the return value is `None`, the following code isn't executed.\n\nNow that we have what we think is the entity that has the `Text` component we need to update, we can use the `texts` query like a database and get a mutable reference to the `Text` from it.\n\nThe `Entity` we got from the `Some` destructure is of type `&Entity`, which is a shared reference, so when we `get_mut` we can dereference to pass the argument in.\n\n`text.sections.first_mut()` is similar, but we're grabbing the first section as a mutable reference.\n\nThen we can set the text section to the value of the points for the associated tile. The text section needs to be a string, so we use `.to_string()` on the points value.\n\nWith the system running, you'll see the tiles display 2 again which means everything is working and we can change the original default value from 4 back to 2.","shortMessageHtmlLink":"Updating Tile display when Point values change"}},{"before":null,"after":"1cece0344ba06d877ec9e140340e01ee78907844","ref":"refs/heads/bevy-0.10-steps","pushedAt":"2023-03-12T21:00:11.781Z","pushType":"branch_creation","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"Changing the color of the background\n\nWith a fresh new coat of paint for the board we probably want to change the background as well.\n\nOur camera is responsible for painting all of the pixels we see. Any area of the screen that doesn't have a sprite on it will be painted using a “clear color”.\n\nWe can configure what the `[ClearColor](https://docs.rs/bevy/0.10.0/bevy/prelude/struct.ClearColor.html)` is by using a `[Resource](https://docs.rs/bevy/0.10.0/bevy/ecs/prelude/trait.Resource.html)` which will get accessed and used by Bevy's rendering system.\n\nA `Resource` is some data that isn't associated with any entities or components.\n\n```rust\nfn main() {\n App::new()\n .insert_resource(ClearColor(\n Color::hex(\"#1f2638\").unwrap(),\n ))\n .add_plugins(DefaultPlugins.set(WindowPlugin {\n primary_window: Some(Window {\n title: \"2048\".to_string(),\n ..default()\n }),\n ..default()\n }))\n .add_startup_systems((setup, spawn_board))\n .run()\n}\n```\n\nThis time we'll use the `[hex](https://docs.rs/bevy/0.10.0/bevy/render/color/enum.Color.html#method.hex)` function on `Color` to parse a hex code into a Color. Because this is parsing it could fail if we don't give it a well formatted hex code, which is why the `hex` function returns a `Result`.\n\nIf we put in a hex code that doesn't work we'd rather see our program crash in development, so we can `[.unwrap()](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap)` the Result here, which will return the parsed `Color` for us.\n\nWe wrap the `Color` in the `ClearColor` struct so Bevy will find it when looking for the clear color, and insert it as a resource.\n\nWhen we `cargo run` now, we see the board in the middle of a blueish background.","shortMessageHtmlLink":"Changing the color of the background"}},{"before":"ae4630ae3898d6d3878ab3c2181baa23f7dce92d","after":"d1370aee7c7f441f9cdd7a38bff0c397dbe22012","ref":"refs/heads/bevy-0.10","pushedAt":"2023-03-12T00:38:22.953Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"0.10","shortMessageHtmlLink":"0.10"}},{"before":"22150b89c3341b4552932ded81b2ecf53c01e07c","after":"ae4630ae3898d6d3878ab3c2181baa23f7dce92d","ref":"refs/heads/bevy-0.10","pushedAt":"2023-03-09T08:00:45.436Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"use lch; ClearColor seems to crash when using Color::Lcha so dont use it there","shortMessageHtmlLink":"use lch; ClearColor seems to crash when using Color::Lcha so dont use…"}},{"before":"a4b6e17cd05e692ba52078d01a7c9cb8156b665d","after":"22150b89c3341b4552932ded81b2ecf53c01e07c","ref":"refs/heads/bevy-0.10","pushedAt":"2023-03-09T07:41:28.580Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"more systems rework","shortMessageHtmlLink":"more systems rework"}},{"before":"fd96b5269ebe866490eef2ac0fc8291d7b080a73","after":"a4b6e17cd05e692ba52078d01a7c9cb8156b665d","ref":"refs/heads/bevy-0.10","pushedAt":"2023-03-09T07:32:47.057Z","pushType":"push","commitsCount":1,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"modify some system setup syntax","shortMessageHtmlLink":"modify some system setup syntax"}},{"before":null,"after":"fd96b5269ebe866490eef2ac0fc8291d7b080a73","ref":"refs/heads/bevy-0.10","pushedAt":"2023-03-09T07:10:27.635Z","pushType":"branch_creation","commitsCount":0,"pusher":{"login":"ChristopherBiscardi","name":"Chris Biscardi","path":"/ChristopherBiscardi","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/551247?s=80&v=4"},"commit":{"message":"0.10 working","shortMessageHtmlLink":"0.10 working"}}],"hasNextPage":false,"hasPreviousPage":false,"activityType":"all","actor":null,"timePeriod":"all","sort":"DESC","perPage":30,"cursor":"djE6ks8AAAAEQbbygQA","startCursor":null,"endCursor":null}},"title":"Activity · rust-adventure/2048"}