diff --git a/docs/courses/csintro.md b/docs/courses/csintro.md
index f1177af0f11..a4d75050faa 100644
--- a/docs/courses/csintro.md
+++ b/docs/courses/csintro.md
@@ -1,18 +1,18 @@
-# CS Intro
+# Introduction to Computer Science with MakeCode Arcade
-A collection of courses meant to teach introductory programmers using Blocks and JavaScript
+Hello! Welcome to the new student guide for "Introduction to Computer Science with MakeCode Arcade"!
+
+This page will provide you with additional activities that you can complete in order to better understand the lessons in the official curriculum. Please note, this page does not contain any answer keys or exemplar projects.
### ~hint
-These courses are currently in beta - this means that they are likely to have bugs and changes made fairly regularly. If you see anything that doesn't seem quite right, or if you have any suggestions, please file an issue on [github](https://github.com/microsoft/pxt-arcade).
+If you are looking for our previous "CS Intro" course, you may now find that at:"
+https://arcade.makecode.com/courses/csintro-archive
### ~
-## Courses on Flipgrid
-
-Flipcode for the **Intro to CS** course grid: **[csintroarcade](https://flipgrid.com/csintroarcade)**
-## Course Sections
+## Unit 0 Activities
```codecard
[
@@ -35,23 +35,23 @@ Flipcode for the **Intro to CS** course grid: **[csintroarcade](https://flipgrid
]
```
-## About the CS Intro Series
+## Unit 1 Activities
-The CS Intro Series is designed to teach new developers how to code from the ground up.
-In CS Intro 1, students are introduced to programming through the MakeCode Blocks editor. They can create their own games, while learning concepts that are crucial to software development: creating variables, responding to events, and using iteration to simplify and extend their programs. Throughout the course, they will learn to develop their own games through small daily tasks, as well as projects that guide them through the process of turning basic ideas into full-fledged games.
-In CS Intro 2, students will continue to develop the software development skills they were introduced to in the first course, with more advanced programming concepts such as functions, logical comparisons, and arrays. These new skills will allow students to create more advanced and compelling games.
+## Unit 2 Activities
+
+
+
+## Unit 3 Activities
+
+
-### ~hint
-In the future CS Intro 3 and CS Intro 4 courses, students will transition the skills they have learned in a Block based environment into skills in a text based coding environment, allowing them to dig deeper into the games they make, as well as transition their skills in the @boardname@ into other environments.
-### ~
## See also
[Courses Home Page](/courses),
-[CS Intro 1](/courses/csintro1),
-[CS Intro 2](/courses/csintro2),
-[CS Intro 3](/courses/csintro3)
\ No newline at end of file
+[Arcade Tutorials](/tutorials),
+[Beginner Skillmaps](/beginner-maps),
diff --git a/docs/courses/csintro/unit-0/s01-lab0006.md b/docs/courses/csintro/unit-0/s01-lab0006.md
new file mode 100644
index 00000000000..89b72a40040
--- /dev/null
+++ b/docs/courses/csintro/unit-0/s01-lab0006.md
@@ -0,0 +1,223 @@
+# Lab 0.6: Self-portrait
+
+## It's all about you! @showdialog
+
+In this tutorial, you will continue your exploration of MakeCode Arcade,
+creating a simple "self-portrait" to introduce yourself to your instructors
+and your classmates.
+
+## Bell ringer @showdialog
+
+**How would you fill in this sentence? I am ______.**
+
+This could be answered in many ways:
+
+1. What you enjoy doing: I am *a musician*.
+1. Physically: I am *tall*.
+1. Personal characteristic: I am *funny*.
+1. Career aspirations: I am *an engineer*.
+1. Self-reflective: I am *a deep thinker*.
+1. As an athlete: I am *a runner*.
+
+Want some other ideas?
+
+**Complete this sentence: I like ______.**
+
+Take a few minutes to think who you are and what you like,
+and write down a few of them.
+You will use these descriptions shortly!
+
+## Set the scene!
+
+In this tutorial, you will be adding blocks to your
+``||loops(noclick):on start||``
+container.
+
+- From the
+``||scene:Scene||``
+drawer, set the background color.
+
+View the hint by selecting the light bulb below
+if you need to see an example of the code that you are building.
+
+```blocks
+scene.setBackgroundColor(7)
+```
+
+## Here I am!
+
+Now, let's add your avatar. An **avatar** is an image that represents
+someone or something.
+
+1. From the
+``||sprites:Sprites||``
+drawer, create a new sprite that represents you.
+1. Give your sprite an image that represents you.
+**Do not** use an existing image from the gallery.
+
+View the hint by selecting the light bulb below
+if you need to see an example of the code that you are building.
+
+```blocks
+scene.setBackgroundColor(7)
+// @highlight
+let mySprite = sprites.create(sprites.builtin.cat0, SpriteKind.Player)
+```
+
+## You don't say!
+
+Now, let's have your avatar say some facts about you.
+
+- Using a block in the
+``||sprites:Sprites||``
+drawer, make the sprite "say" several things about you, including:
+ 1. Your name and grade
+ **Internet safety note**: When creating projects
+ that may be shared on the Internet,
+ **only** use your given (first) name.
+ **Do not** use your first and last names.
+ 1. At least three things from the bell ringer activity.
+ 1. "Goodbye!"
+
+View the hint by selecting the light bulb below
+if you need to see an example of the code that you are building.
+
+```blocks
+scene.setBackgroundColor(7)
+let mySprite = sprites.create(img`
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+`, SpriteKind.Player)
+mySprite.sayText("My name and grade")
+mySprite.sayText("Fact #1")
+mySprite.sayText("Fact #2")
+mySprite.sayText("Fact #3")
+mySprite.sayText("Goodbye!")
+```
+
+## Too fast!
+
+Is the sprite saying your facts too quickly?
+
+- Add a
+``||loops:pause (100) ms||``
+block after any
+``||sprites(noclick):say||``
+block that goes too quickly.
+- Adjust the length of the pause by changing the value
+in the ``||loops(noclick):pause||`` block.
+
+```blocks
+scene.setBackgroundColor(7)
+let mySprite = sprites.create(img`
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+`, SpriteKind.Player)
+mySprite.sayText("My name and grade")
+pause(1000)
+mySprite.sayText("Fact #1")
+pause(1000)
+mySprite.sayText("Fact #2")
+pause(1000)
+mySprite.sayText("Fact #3")
+pause(1000)
+mySprite.sayText("Goodbye!")
+```
+
+## Whoops! Wrong spot!
+
+Let's move your avatar elsewhere on the screen.
+
+- Using the coordinate system that you learned in Lab 0.5,
+move the sprite to a different location on the screen.
+- Use a block in the ``||sprites:Sprites||`` drawer to do so.
+- Be sure to place the block in the correct place.
+Where does it need to go?
+
+View the hint by selecting the light bulb below
+if you need to see an example of the code that you are building.
+
+```blocks
+scene.setBackgroundColor(7)
+let mySprite = sprites.create(img`
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . 5 5 5 5 5 5 5 5 5 5 . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . 5 5 5 5 5 5 5 5 . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . 5 5 5 5 5 5 . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . 5 5 5 5 . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+ . . . . . . . 5 5 . . . . . . .
+`, SpriteKind.Player)
+// @highlight
+mySprite.setPosition(80, 60)
+mySprite.sayText("My name and grade")
+pause(1000)
+mySprite.sayText("Fact #1")
+pause(1000)
+mySprite.sayText("Fact #2")
+pause(1000)
+mySprite.sayText("Fact #3")
+pause(1000)
+mySprite.sayText("Goodbye!")
+```
+
+## Congratulations! @showdialog
+
+If there is time left, you can add more actions to your program!
+
+Here are some ideas.
+
+- Instead of just appearing in a different position on the screen,
+have the sprite move smoothly.
+- Add a second sprite which also talks to the player.
+- Make the sprite only talk when the player hits a button.
+
+Follow your instructor's directions to submit your project.
+
+```ghost
+scene.setBackgroundColor(7)
+let mySprite = sprites.create(sprites.builtin.cat0, SpriteKind.Player)
+mySprite.sayText("My name and grade")
+mySprite.sayText("Fact #1")
+mySprite.sayText("Fact #2")
+mySprite.sayText("Fact #3")
+mySprite.sayText("Goodbye!")
+mySprite.setPosition(80, 60)
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0101-part1.md b/docs/courses/csintro/unit-1/s01-lab0101-part1.md
new file mode 100644
index 00000000000..f19493eed45
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0101-part1.md
@@ -0,0 +1,165 @@
+# Lab 1.1 Part 1: Blocks scavenger hunt!
+
+## Blocks scavenger hunt! @showdialog
+
+This activity is a scavenger hunt.
+Some of the blocks will be familiar to you from previous labs.
+Others may be new.
+
+If your instructor provided you with a worksheet for this lab,
+complete the worksheet as you go through this tutorial.
+
+## First block!
+
+1. Find the
+``||variables(sprites):set mySprite to||`` ``||sprites:sprite [] of kind Player||``
+block and add it to the
+``||loops(noclick):on start||``
+container already in the workspace.
+1. Give your sprite an image.
+(Select one from the **Gallery** for now.)
+
+---
+
+**Questions**
+
+*Note*: If your instructor provided you with a worksheet,
+write the answers to these questions on your worksheet.
+
+- Which drawer in the toolbox holds this block?
+- What does this block do?
+
+If you need help, remember that you can select the help icon
+(the question mark) to see a hint!
+
+```blocks
+// @highlight
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+```
+
+## Next block!
+
+1. Find the
+``||sprites:set||`` ``||variables(sprites):mySprite||``
+``||sprites:position to x [0] y[0]||``
+ block.
+1. Add the block to **the bottom** of your ``||loops(noclick):on start||`` container.
+
+Try different values for **x** and **y**.
+
+---
+
+**Questions**
+
+- Which drawer in the toolbox holds this block?
+- What does this block do?
+- Are there any numbers that are **not** allowed for **x** or **y**?
+
+```blocks
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+// @highlight
+mySprite.setPosition(0, 0)
+```
+
+## Let's move!
+
+1. Find the
+``||controller:move||`` ``||variables(controller):mySprite||``
+``||controller:with buttons (+)||``
+ block.
+2. Add the block to **the bottom** of your ``||loops(noclick):on start||`` container.
+
+---
+
+**Questions**
+
+- Which drawer in the toolbox holds this block?
+- What does this block do?
+- Which buttons are used to move the sprite?
+
+```blocks
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+mySprite.setPosition(0, 0)
+// @highlight
+controller.moveSprite(mySprite)
+```
+
+## Move some more!
+
+1. In your
+``||controller(noclick):move||`` ``||variables(noclick):mySprite||``
+``||controller(noclick):with buttons (+)||``
+block, click the **expand** button.
+ - The *expand* button is the plus sign at the end of the block.
+
+---
+
+**Questions**
+
+- What new items appear?
+- What happens when you change the values of **vx** and **vy**?
+- How can you use those values to make the sprite **only** move
+**horizontally** (left and right)?
+- How can you use those values to make the sprite **only** move
+** vertically** (up and down)?
+- Are there any numbers that are **not** allowed for **vx** and **vy**?
+
+~hint What kind of numbers should I try?
+- Try some negative numbers.
+- What happens when the velocity is zero?
+hint~
+
+```blocks
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+mySprite.setPosition(0, 0)
+// @highlight
+controller.moveSprite(mySprite, 50, 0)
+```
+
+## Sprites can talk?
+
+1. Find the
+``||variables(sprites):mySprite||`` ``||sprites:say ":)"||``
+block.
+2. Add the block to **the bottom** of your ``||loops(noclick):on start||`` container.
+
+---
+
+**Questions**
+
+- Which drawer in the toolbox holds this block?
+- What does this block do?
+
+```blocks
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+mySprite.setPosition(0, 0)
+controller.moveSprite(mySprite, 50, 0)
+// @highlight
+mySprite.say(":)")
+```
+
+## Sprites CAN talk!
+
+1. In your
+``||variables(noclick):mySprite||`` ``||sprites(noclick):say ":)"||``
+block,
+select the **expand** button.
+
+~hint What is the "expand" button?
+- The *expand* button is the plus sign at the end of the block.
+hint~
+
+---
+
+**Questions**
+
+- What new items appear?
+- What is the purpose of each of the new items?
+
+```blocks
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+mySprite.setPosition(0, 0)
+controller.moveSprite(mySprite, 50, 0)
+// @highlight
+mySprite.say(":)", 1000)
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0101-part2.md b/docs/courses/csintro/unit-1/s01-lab0101-part2.md
new file mode 100644
index 00000000000..29c59655f01
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0101-part2.md
@@ -0,0 +1,43 @@
+# Lab 1.1 Part 2: Renaming variables
+
+## Renaming sprites @showdialog
+
+Before our next scavenger hunt, let's learn how to change the name
+of a sprite. Follow these instructions to change the
+name of your sprite from **mySprite** to **heroSprite**.
+
+## Make a hero!
+
+Let's change the name of your sprite to **heroSprite**.
+
+Watch the animation below to see how this is done.
+Then, follow the instructions below to try it yourself!
+
+**Note**: Look at the hint to see a video of these steps.
+
+1. In any of the red ovals with the name **mySprite**,
+select the **down arrow**. A menu appears.
+1. In the menu that appears, select **Rename variable....**.
+1. In the dialog that appears, enter the new name, **heroSprite**.
+1. Select **OK** or press **Enter** on your keyboard to close the dialog.
+
+Now, in every block where your sprite is used, the name has changed!
+
+In all of your programs, give your sprites meaningful names!
+
+![Renaming a variable in MakeCode](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L01.01.P02.rename_variable.gif)
+
+```blocks
+// @highlight
+let heroSprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+heroSprite.setPosition(0, 0)
+controller.moveSprite(heroSprite, 50, 0)
+heroSprite.say(":)", 1000)
+```
+
+```template
+let mySprite: Sprite = sprites.create(img`.`, SpriteKind.Player)
+mySprite.setPosition(0, 0)
+controller.moveSprite(mySprite, 50, 0)
+mySprite.say(":)", 1000)
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0102.md b/docs/courses/csintro/unit-1/s01-lab0102.md
new file mode 100644
index 00000000000..4dff723aef0
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0102.md
@@ -0,0 +1,154 @@
+# Lab 1.2: Sprites in the corners
+
+## Sprites in the corners @showdialog
+
+In this lab, you will add multiple sprites to a project.
+
+You also will learn about **sprite kinds**.
+
+You will work on this project in multiple labs,
+so have fun and be creative!
+
+## Meet our hero!
+
+Let's add our hero to the project.
+Remember, if you need a hint, then select the hint tool
+(the light bulb icon)
+below these instructions.
+
+1. Add a sprite to your project for the player.
+This is called the **hero sprite**.
+1. Give your sprite an appropriate name.
+1. Draw an image for your hero sprite.
+Keep the image simple for now. You can improve it later.
+1. Place your hero sprite in one of the corners of the screen.
+Use a
+``||sprites:set||`` ``||variables(sprites):heroSprite||``
+``||sprites:position to x (0) y (0)||``
+block.
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+```
+
+## Time for an ememy!
+
+Now, let's add an enemy.
+
+1. Add another sprite to your project for an enemy.
+1. Give your new sprite an appropriate name.
+1. Draw an image for your enemy sprite.
+1. Change the sprite kind to **Enemy**.
+Watch the animation below to see how to do this.
+1. Place your enemy sprite in the corner
+**opposite your hero sprite**.
+Use a
+``||sprites:set||`` ``||variables(sprites):enemySprite||``
+``||sprites:position to x (0) y (0)||``
+block.
+
+![An animation showing how to change a sprite kind.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L01.02.change_sprite_kind.gif)
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+// @highlight
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+// @highlight
+enemySprite.setPosition(120, 90)
+```
+
+## Your hero is hungry!
+
+Let's add some food to your project.
+
+1. Add another sprite to your project for some sort of food.
+1. Give your new sprite an appropriate name.
+1. Draw an image for your food sprite.
+1. Change the sprite kind to **Food**.
+Go back to the previous step if you need to see how to do this.
+1. Place your food sprite in one of the empty corners of the screen.
+Use a
+``||sprites:set||`` ``||variables(sprites):foodSprite||``
+``||sprites:position to x (0) y (0)||``
+block.
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+// @highlight
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+// @highlight
+foodSprite.setPosition(120, 30)
+```
+
+## Throw something in the air!
+
+Finally, let's add a **projectile** to your project
+
+~hint What is a projectile?
+A projectile is something that is thrown or launched, usually into the air.
+hint~
+
+1. Add one more sprite to your project for a projectile.
+(It will not move in your project ... yet!)
+1. Give your new sprite an appropriate name.
+1. Draw an image for your projectile.
+1. Change the sprite kind to **Projectile**.
+If you need a reminder, then switch to the previous step in the tutorial to see how this is done.
+1. Place your projectile in the remaining empty corner of the screen.
+Use a
+``||sprites:set||`` ``||variables(sprites):projectile||``
+``||sprites:position to x (0) y (0)||``
+block.
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+// @highlight
+let projectile: Sprite= sprites.create(img`4`, SpriteKind.Projectile)
+// @highlight
+projectile.setPosition(40, 90)
+```
+
+## So ... what's the story?
+
+Now, we need a story!
+
+1. Your project contains four characters.
+Write a story that includes these four sprites.
+1. Include a *synopsis* of the story at the beginning of the game.
+Use either a
+``||variables(sprites):mySprite||`` ``||sprites:say||``
+block or a
+``||game:show long text ("") bottom||``
+block.
+You can find the
+``||game:show long text ("") bottom||``
+block in the **Game** drawer.
+1. If you have extra time, then improve your sprite images.
+You also can add to your story.
+
+~hint What is a synopsis?
+A synopsis is a short overview of a story.
+hint~
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite= sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+// @highlight
+game.showLongText("Once upon a time, in a land far away....", DialogLayout.Center)
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0103.md b/docs/courses/csintro/unit-1/s01-lab0103.md
new file mode 100644
index 00000000000..771f4838cc3
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0103.md
@@ -0,0 +1,249 @@
+# Lab 1.3: Moving sprites
+### @explicitHints true
+
+## Moving sprites @showdialog
+
+Sprites are pretty boring if they stay in one place!
+
+In this lab, we will learn different ways to move sprites around the screen.
+
+## I am the hero!
+
+You have done this before, so give it a try!
+
+Add a block that lets the player move the hero sprite around the screen.
+
+> **Which block lets the player move a sprite?**
+>
+> The
+> ``||controller:move||`` ``||variables(controller):heroSprite||``
+> ``||controller:with buttons||``
+> block is in the **Controller** drawer.
+
+Need another hint? Select the hint icon below.
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+// @highlight
+controller.moveSprite(heroSprite)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+```
+
+## I can go anywhere!
+
+Move your hero sprite around the screen.
+
+You can even move the sprite off of the screen!
+
+This can be confusing to the player.
+Move to the next step to see how we can fix this.
+
+## Let's stay inside today
+
+Let's keep the player sprite on the screen.
+
+1. From the **Sprites** drawer, add a
+``||sprites:set||``
+``||variables(sprites):mySprite||`` ``||sprites:stay in screen||``
+``||loops(sprites):ON||``
+block.
+1. In this new block, click on the down arrow to change the
+name of the sprite to your hero sprite.
+1. Wait for the simulator to restart.
+1. Move the hero sprite around the screen. It stays on the screen now!
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+controller.moveSprite(heroSprite)
+// @highlight
+heroSprite.setStayInScreen(true)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+
+```
+
+## The chase is on!
+
+Now, let's make the enemy chase the hero!
+
+1. From the **Sprites** drawer, add a
+``||sprites:set||``
+``||variables(sprites):myEnemy||`` ``||sprites:follow||``
+``||variables(sprites):mySprite||``
+block.
+1. Make the enemy chase the hero.
+ * In this new block, make the first variable your enemy sprite.
+ * In this new block, make the second variable your hero sprite.
+1. Wait for the simulator to restart.
+1. Move your hero sprite around the screen.
+1. How does the enemy sprite react?
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+// @highlight
+enemySprite.follow(heroSprite)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+```
+
+## Too fast for you?
+
+Does the enemy move too fast? Let's see what we can do about that.
+
+1. Find the new
+``||sprites:set||``
+``||variables(sprites):myEnemy||`` ``||sprites:follow||``
+``||variables(sprites):mySprite||``
+block that you just added to your project.
+1. *Expand* the block.
+
+~hint How do I expand a block?
+Select the plus sign (+) at the end of the block.
+hint~
+
+**Questions**
+
+If your instructor gave you a worksheet, then answer these questions on the worksheet.
+
+- What new items appear in the block?
+- When you change the values, which sprite changes?
+- How do different values change the sprite's movement?
+- Are there any numbers that are not allowed?
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+// @highlight
+enemySprite.follow(heroSprite, 25)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+```
+
+## Time to move!
+
+We can make a sprite move in any direction we want. Let's try!
+
+1. From the **Sprites** drawer, add a
+``||sprites:set||``
+``||variables(sprites):mySprite||``
+``||sprites:velocity to vx (50) vy (50)||``
+block to your project.
+1. In this new block, select the down arrow and change the
+variable to your food sprite.
+1. Wait for the simulator to restart.
+1. Watch your food sprite. It may not be on the screen for very long!
+How did it move?
+1. Try different numbers for **vx** and **vy**.
+
+**Questions**
+
+If your instructor gave you a worksheet, then answer these questions on the worksheet.
+
+- How do different numbers change the sprite's movement?
+- Are there any numbers that are not allowed for **vx** and **vy**?
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+enemySprite.follow(heroSprite, 25)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+// @highlight
+foodSprite.setVelocity(50, 50)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+```
+
+## Not that way! This way!
+
+Can you make the food sprite move in a specific direction?
+
+![A compass rose.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L01.03.compass_rose.png)
+[This image](https://commons.wikimedia.org/wiki/File:Compass_rose_simple.svg) by Henrik is licensed under [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/)
+
+**Questions**
+
+What values can you use for **vx** and **vy** to make the food sprite move ...
+
+- ... north (or up)?
+- ... south (or down)?
+- ... east (or right)?
+- ... west (or left)?
+- ... northeast?
+- ... southeast?
+- ... southwest?
+- ... northwest?
+
+#### ~ tutorialhint
+
+```blocks
+let heroSprite: Sprite = sprites.create(img`1`, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+let enemySprite: Sprite = sprites.create(img`2`, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+enemySprite.follow(heroSprite, 25)
+let foodSprite: Sprite = sprites.create(img`3`, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+// @highlight
+foodSprite.setVelocity(0, 25)
+let projectile: Sprite = sprites.create(img`4`, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("This is my story!", DialogLayout.Center)
+```
+
+```template
+let heroSprite: Sprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+heroSprite.setPosition(40, 30)
+let enemySprite: Sprite = sprites.create(sprites.castle.skellyFront, SpriteKind.Enemy)
+enemySprite.setPosition(120, 90)
+let foodSprite: Sprite = sprites.create(sprites.food.smallBurger, SpriteKind.Food)
+foodSprite.setPosition(120, 30)
+let projectile: Sprite= sprites.create(sprites.projectile.star1, SpriteKind.Projectile)
+projectile.setPosition(40, 90)
+game.showLongText("Once upon a time in a land far away....", DialogLayout.Center)
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0104-part1.md b/docs/courses/csintro/unit-1/s01-lab0104-part1.md
new file mode 100644
index 00000000000..0ca8730231b
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0104-part1.md
@@ -0,0 +1,108 @@
+# Part 1: A screen full of food
+
+## A screen full of food! @showdialog
+
+In this lab, we will write a game where the player must eat all the food
+which appears--while avoiding an enemy which bounces around the screen!
+
+## Sprite-o-rama!
+
+First, let's add a sprite to the screen every three seconds.
+
+1. From the **Game** drawer, add an
+``||game:on game update every (500) ms||``
+block to your project. Place it anywhere on your workspace.
+1. Change the time from 500 milliseconds to **3000** milliseconds.
+You can just type the number. You don't have to select an item from the
+list that appears.
+1. From the **Sprites** drawer, add a
+``||variables(sprites):set my sprite to||``
+``||sprites:sprite [] of kind Player||``
+block to the
+``||game(noclick):on game update||``
+block that you just added to your workspace.
+1. This sprite represents food, so change the sprite kind to **Food**.
+1. Select or create any food image that you like for your sprite.
+1. Give the sprite variable a meaningful name, like `foodSprite`.
+
+Watch your game run in the simulator. What's wrong?
+
+~hint What is a millisecond?
+A millisecond is 1/1000 of a second. In other words, 1,000 milliseconds
+is the same as 1 second.
+
+So, 3,000 milliseconds is the same as 3 seconds.
+hint~
+
+```blocks
+let foodSprite: Sprite = null
+game.onUpdateInterval(3000, () => {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+})
+```
+
+## Go your own way, food!
+
+It might look like you only have one sprite on your screen.
+
+It only *looks* that way, though ... because the sprites are on top of each other!
+
+Let's put them at random locations on the screen.
+Be sure to use the hint button at the bottom of the screen if you want to
+see the code that you are trying to build in these steps.
+
+1. Add a
+``||sprites:set||`` ``||variables(sprites):foodSprite||``
+``||sprites:position to x (0) y (0)||``
+block to the **bottom** of
+``||game(noclick):on game update every (3000) ms||``.
+1. Select the dropdown next to the variable name and select the
+variable that you created earlier.
+1. Instead of entering a number for the **x** and **y** coordinates,
+we are going to use a block that picks a random number.
+1. From the **Math** drawer, drag a
+``||math:pick random (0) to (10)||``
+block and drop it into the **x** value of the
+``||sprites(noclick):set||``
+``||variables(noclick):foodSprite||`` ``||sprites(noclick):position to x (0) y (0)||``
+block.
+1. Instead of the numbers **0** and **10**, change the values to pick
+a random number from **8** to **152**.
+
+Let your program run in the simulator. That's a little better, right?
+Now, let's pick a random **y** value, too.
+
+1. Drag another
+``||math:pick random (0) to (10)||``
+block and drop it into the **y** value of the
+``||sprites(noclick):set||``
+``||variables(noclick):foodSprite||`` ``||sprites(noclick):position to x (0) y (0)||``
+block.
+1. Change the random values to include numbers from **8** to **112**.
+
+Now, you should have food appearing all over the screen!
+
+**Questions**
+
+- Why **8** to **152** for **x**?
+- Why **8** to **112** for **y**?
+
+```blocks
+let foodSprite: Sprite = null
+game.onUpdateInterval(3000, () => {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
+
+## Complete
+
+Good work! Now, we need to add a player. Onward to Part 2!
+
+```ghost
+let foodSprite: Sprite = null
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0104-part2.md b/docs/courses/csintro/unit-1/s01-lab0104-part2.md
new file mode 100644
index 00000000000..95498885ce0
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0104-part2.md
@@ -0,0 +1,185 @@
+# Part 2: Eat it all!
+
+## We need a hero! @showdialog
+
+In this lab, we will write a game where the player must eat all the food
+which appears--while avoiding an enemy which bounces around the screen!
+
+## Hero time!
+
+Now, let's add a hero sprite who will collect the food on the screen.
+
+1. Add a
+``||variables(sprites):set my sprite to||``
+``||sprites:sprite [] of kind Player||``
+to your
+``||loops(noclick):on start||``
+container.
+1. Give the variable an appropriate name.
+1. Give your hero sprite an appropriate image.
+1. Add blocks to
+``||loops(noclick):on start||``
+so that the hero sprite:
+ - Starts in the middle of the screen.
+ - Stays on the screen.
+ - Moves with the d-pad buttons.
+
+Check the hint to see the finished code.
+
+```blocks
+let heroSprite: Sprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+```
+
+## What is that?
+
+Now, let's add an enemy sprite that the hero needs to avoid.
+
+1. Add another
+``||variables(sprites):set my sprite to||``
+``||sprites:sprite [] of kind Player||``
+to your
+``||loops(noclick):on start||``
+container to represent the enemy.
+1. Give the variable an appropriate name.
+1. Give the enemy sprite an appropriate image.
+1. Add blocks to
+``||loops(noclick):on start||``
+so that the enemy sprite:
+ - Starts in a random location.
+ - Moves with a random velocity.
+ - Bounces off of the edges of the screen.
+
+Check the hint to see the finished code.
+
+```blocks
+let heroSprite: Sprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+// @highlight
+let enemySprite: Sprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+// @highlight
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+// @highlight
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+// @highlight
+enemySprite.setBounceOnWall(true)
+```
+
+## Run!
+
+Now, we need something to happen when your enemy sprite touches your hero sprite.
+
+1. From the ``||sprites:Sprites||`` drawer, add an
+``||sprites:on||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player) overlaps||`` ``||variables(sprites):otherSprite||``
+``||sprites:of kind (Player)||``
+to your workspace.
+1. Change the kind for
+``||variables(noclick):otherSprite||``
+to
+``||sprites(noclick):Enemy||``.
+1. Read the text in this block out loud. It should say:
+``||sprites(noclick):on sprite of kind Player overlaps otherSprite of kind Enemy||``.
+1. Check the hint to make sure your block looks correct.
+
+This container block is called an **event handler**. You will learn more
+about these kinds of blocks in another lab.
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+
+})
+```
+
+## Jump out of the way!
+
+Let's make the enemy sprite jump to a new location on the screen whenever it
+collides with our hero sprite.
+
+1. In the ``||sprites(noclick):on overlap||`` container that you just added to
+the workspace, add blocks that:
+ * Moves the enemy to a random location.
+ * Moves the enemy with a new, random velocity.
+
+In your new blocks, drag the
+``||variables(noclick):otherSprite||``
+block from the
+top of the
+``||sprites(noclick):on overlap||``
+block to the blocks that change the
+enemy sprite.
+
+Use the hint to see a screenshot of this and to check your code.
+
+Play your game in the simulator to see what happens when the enemy sprite
+collides with your hero!
+
+![Using a local variable from a function header.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L01.04.P02.function_use_local_variable.png)
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+```
+
+## Eat the fruit
+
+Now, let's make the fruit disappear when the hero eats it.
+
+1. Refer to the previous step to add a new ``||sprites:on overlap||``
+block to your workspace.
+1. Change the block so that it says the following:
+``||sprites(noclick):on sprite of kind Player overlaps otherSprite of kind Food||``.
+1. In your new
+``||sprites(noclick):on overlap||``
+container,
+add a block that
+deletes the food sprite with some sort of effect.
+
+Use the hint to check your code.
+
+Play your game, eat some fruit, and avoid the enemy!
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+```
+
+## Complete
+
+Good work! Now, we need to keep track of score and lives.
+Onward to Part 3!
+
+```template
+let foodSprite: Sprite = null
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
+
+```ghost
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0104-part3.md b/docs/courses/csintro/unit-1/s01-lab0104-part3.md
new file mode 100644
index 00000000000..2d00f565837
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0104-part3.md
@@ -0,0 +1,142 @@
+# Part 3: Points! Lives!
+
+## Scores and lives @showdialog
+
+Your game has a player, an enemy, and some food to eat.
+Now, let's track the player's score and lives.
+
+## Get started!
+
+Let's give the player a starting score and set of lives at the start of the game.
+
+From the ``||info:Info||`` drawer, add the following blocks to your
+``||loops(noclick):on start||`` container:
+
+- ``||info:set score to (0)||``
+- ``||info:set life to (3)||``
+
+Feel free to change these values if you want the player to start with a
+different score or a different number of lives.
+
+```blocks
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+// @highlight
+info.setScore(0)
+// @highlight
+info.setLife(3)
+```
+
+## Whoops!
+
+Now, let's make the player lose a life when they collide with the enemy.
+
+From the ``||info:Info||`` drawer, drag a
+``||info:change life by (-1)||``
+block and add it to the appropriate
+``||sprites(noclick):on overlap||``
+container.
+
+Use the hint to check your code.
+
+Play your game and make sure you lose a life when you collide into the enemy.
+
+**Question**
+
+1. What happens in the game when you run out of lives?
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+ // @highlight
+ info.changeLifeBy(-1)
+})
+```
+
+## What's the score?
+
+Now, let's increase the player's score whenever they eat food.
+
+From the ``||info:Info||`` drawer, drag a
+``||info:change score by (1)||``
+block and add it to the appropriate
+``||sprites(noclick):on overlap||``
+container.
+
+Feel free to change the value in the
+block to add different amount to the player's score
+whenever they eat food.
+
+Use the hint to check your code.
+
+Play your game and make sure you earn points whenever you eat food.
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+ // @highlight
+ info.changeScoreBy(1)
+})
+```
+
+## Complete @showdialog
+
+Good work! Now, make the game your own!
+
+- Make **both** the player **and** the enemy relocate when they collide.
+- Have the player lose points if the enemy eats food.
+- Play a sound when the enemy and player collide, and when the player eats food.
+- Design your own sprites.
+- Add a background.
+- What else can you think of?
+
+```template
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
+
+```ghost
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
diff --git a/docs/courses/csintro/unit-1/s01-lab0105.md b/docs/courses/csintro/unit-1/s01-lab0105.md
new file mode 100644
index 00000000000..6b45ec33de3
--- /dev/null
+++ b/docs/courses/csintro/unit-1/s01-lab0105.md
@@ -0,0 +1,223 @@
+# Lab 1.5: Eat some more!
+
+## Power up! @showdialog
+
+Different sprite kinds can do different things.
+
+We'll add a new kind of sprite which, when eaten by the player,
+gives them an extra life!
+
+## Time for a new sprite!
+
+Let's add a new sprite to the game every 10 seconds.
+
+1. From the ``||game:Game||`` drawer, add an
+``||game:on game update every (500) ms||``
+container to your workspace.
+1. Change the interval to `10000` milliseconds.
+1. In this new container, add a block that creates a new sprite. This new
+sprite will give the player an extra life when they eat it.
+ - Give this sprite variable an appropriate name.
+ - Give this sprite an appropriate image.
+1. Also in this container, add blocks so that the new sprite:
+ - Appears in a random location.
+ - Moves with a random velocity.
+
+We'll add the new sprite kind in the next step.
+
+View the hint to check your code.
+
+```blocks
+let extraLifeSprite: Sprite = null
+game.onUpdateInterval(10000, function () {
+ extraLifeSprite = sprites.create(sprites.projectile.bubble1, SpriteKind.Player)
+ extraLifeSprite.setPosition(randint(8, 152), randint(8, 112))
+ extraLifeSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+```
+
+## A new kind of sprite!
+
+Now, let's give our new sprite its own kind.
+
+1. In the dropdown list that sets the kind for our new sprite,
+select **Add a new kind...**.
+1. A dialog box appears. Give the new kind a name, like *ExtraLife*.
+1. Notice that the new sprite is automatically assigned the new kind.
+
+View the hint to see this in action!
+
+~hint Why do we need a new kind?
+We need a new kind of sprite because, when the player collides with this
+kind of sprite, we want something to happen that is *special* or *unique*
+to this kind of sprite.
+
+If we just made this new sprite a kind of food, then it will act like
+the other food in this game.
+hint~
+
+![Adding a new sprite kind.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L01.05.new_sprite_kind.gif)
+
+## New life!
+
+Now, we need to add some blocks to award the player an extra life for
+eating the new kind of sprite.
+
+1. Add a new
+``||sprites:on sprite overlaps||``
+block to your workspace.
+1. Change the block so that it says the following:
+``||sprites(noclick):on sprite of kind Player overlaps otherSprite of kind ExtraLife||``.
+(Use your new sprite kind if you called it something else.)
+1. Add blocks to your new container so that:
+ - The extra life sprite is destroyed with some sort of effect.
+ - The player's lives are increased by 1.
+
+View the hint to check your code.
+
+Play your game and get that extra life!
+
+```blocks
+namespace SpriteKind {
+ export const ExtraLife = SpriteKind.create()
+}
+sprites.onOverlap(SpriteKind.Player, SpriteKind.ExtraLife, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.hearts, 500)
+ info.changeLifeBy(1)
+})
+```
+
+## It went *where*?
+
+Our extra life sprite leaves the screen.
+When a sprite leaves the screen, it continues to exist unless we destroy it.
+We have a way to destroy a sprite automatically when it leaves the screen.
+Let's make sure that happens to our extra life sprite.
+
+1. From the ``||sprites:Sprites||`` drawer, drag a
+``||sprites:set||`` ``||variables(sprites):mySprite||``
+``||sprites:auto destroy (off)||``
+block and add it to the container
+where your extra lives are created.
+1. Change the variable to your extra life sprite variable.
+1. Select **off** so it changes to **on**.
+
+View the hint to check your code.
+
+```blocks
+namespace SpriteKind {
+ export const ExtraLife = SpriteKind.create()
+}
+let extraLifeSprite: Sprite = null
+game.onUpdateInterval(10000, function () {
+ extraLifeSprite = sprites.create(sprites.projectile.bubble1, SpriteKind.ExtraLife)
+ // @highlight
+ extraLifeSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ extraLifeSprite.setPosition(randint(8, 152), randint(8, 112))
+ extraLifeSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+```
+
+## Challenge time!
+
+Your enemy gets hungry, too! Here are some challenges to practice some of
+the techniques that you've learned.
+
+- Allow the enemy to eat **Food** and **ExtraLife** sprites, too!
+- Whenever the enemy eats a sprite, the player's score should **decrease**.
+- Be sure to destroy the sprites that the enemy eats!
+- Use some effects when the sprites are destroyed.
+
+Check the hint if you need some help.
+
+```blocks
+namespace SpriteKind {
+ export const ExtraLife = SpriteKind.create()
+}
+sprites.onOverlap(SpriteKind.Enemy, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.fire, 500)
+ info.changeScoreBy(-1)
+})
+sprites.onOverlap(SpriteKind.Enemy, SpriteKind.ExtraLife, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.ashes, 500)
+ info.changeScoreBy(-10)
+})
+```
+
+## Complete @showdialog
+
+Good work! Now, make the game your own!
+
+- Add other new sprite kinds to do different things in your game.
+- Add other Food sprites which act the same but have different pictures.
+- Design your own sprites or backgrounds.
+
+```template
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
+
+```ghost
+namespace SpriteKind {
+ export const ExtraLife = SpriteKind.create()
+}
+sprites.onOverlap(SpriteKind.Player, SpriteKind.ExtraLife, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.hearts, 500)
+ info.changeLifeBy(1)
+})
+sprites.onOverlap(SpriteKind.Enemy, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.fire, 500)
+ info.changeScoreBy(-1)
+})
+sprites.onOverlap(SpriteKind.Enemy, SpriteKind.ExtraLife, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.ashes, 500)
+ info.changeScoreBy(-10)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite, effects.spray, 500)
+ info.changeScoreBy(1)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ otherSprite.setPosition(randint(8, 152), randint(8, 112))
+ otherSprite.setVelocity(randint(25, 50), randint(25, 50))
+ info.changeLifeBy(-1)
+})
+let extraLifeSprite: Sprite = null
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.castle.princessFront2, SpriteKind.Player)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+let enemySprite = sprites.create(sprites.builtin.forestBat0, SpriteKind.Enemy)
+enemySprite.setPosition(randint(8, 152), randint(8, 112))
+enemySprite.setVelocity(randint(25, 50), randint(25, 50))
+enemySprite.setBounceOnWall(true)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(3000, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+game.onUpdateInterval(10000, function () {
+ extraLifeSprite = sprites.create(sprites.projectile.bubble1, SpriteKind.ExtraLife)
+ extraLifeSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ extraLifeSprite.setPosition(randint(8, 152), randint(8, 112))
+ extraLifeSprite.setVelocity(randint(25, 50), randint(25, 50))
+})
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0201-part1.md b/docs/courses/csintro/unit-2/s01-lab0201-part1.md
new file mode 100644
index 00000000000..9dc305a1804
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0201-part1.md
@@ -0,0 +1,169 @@
+# Lab 2.1 Part 1: Shooting gallery
+
+## Fire away! @showdialog
+
+In this lab, you will learn about event handlers and how they can be used
+in your projects.
+
+You also will learn ways to play sounds.
+
+## Step right up!
+
+We will be creating a "shooting gallery" game.
+First, let's add a hero sprite to our project.
+
+1. From the ``||sprites:Sprites||`` drawer, add a block to your
+``||loops(noclick):on start||`` container to create your player's sprite.
+1. Give your variable an appropriate name.
+1. Give the sprite an appropriate image.
+1. Add more blocks so that the player sprite:
+ 1. Starts near the bottom of the screen.
+ 1. Moves when the player uses the d-pad.
+ 1. Stays on the screen.
+
+Test your project to make sure your code runs as expected.
+View the hint to check your code.
+
+```blocks
+let heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+```
+
+## Press the button!
+
+Let's add our first event handler.
+
+- From the ``||controller:Controller||`` drawer, drag an
+``||controller:on A button pressed||``
+container block onto your workspace.
+
+Any blocks that you place in this container will run whenever the player
+presses the **A** button on their controller.
+
+```block
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+
+})
+```
+
+## Projectile ... what?
+
+Now, let's launch a *projectile* every time the player presses the **A** button.
+
+1. Inside the
+``||controller(noclick):on A button pressed||``
+container that you just added to your workspace,
+add a
+``||sprites:Sprites||`` block that
+creates a new sprite. This sprite will be the projectile.
+1. Change the sprite's kind to **Projectile**.
+1. Give the sprite an appropriate image.
+1. Give the sprite an appropriate name.
+
+Now, let's make the sprite look like it is launched from the player's sprite!
+
+~hint What is a projectile?
+A projectile is something that is thrown or launched forward, like a ball,
+a bullet, or a laser beam.
+hint~
+
+```blocks
+let plasmaSprite: Sprite = null
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+})
+```
+
+## Fire away!
+
+Let's add some blocks so that the projectile looks like it was launched
+by the player.
+
+1. To the bottom of your
+``||controller(noclick):on A button pressed||``
+container, add a
+``||sprites:set||`` ``||variables(sprites):mySprite||``
+``||sprites:position to x (0) y (0)||``
+block.
+1. Change the variable name to your projectile's name.
+1. For the x-coordinate, add a
+``||variables(sprites):mySprite||``
+``||sprites:x||``
+block. You'll find that block in the
+``||sprites:Sprites||`` drawer.
+1. In the new block for the x-coordinate, change the variable name to
+your player's sprite.
+1. Add another
+``||variables(sprites):mySprite||``
+``||sprites:x||``
+block for the y-coordinate. Use the dropdown to change the
+**x** to **y**. Also, change the variable name to match your player's sprite,
+just like before.
+
+These blocks will move the projectile so that it at the same location
+as your player sprite.
+
+Test your project to make sure that it runs as expected.
+View the hint to check your code.
+
+```blocks
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+})
+```
+
+## Get a move on!
+
+It's not much of a projectile if it doesn't move! Let's make the projectile
+move up the screen when it is launched.
+
+1. To the bottom of your
+``||controller(noclick):on A button pressed||``
+container, add a
+``||sprites:set||`` ``||variables(sprites):mySprite||``
+``||sprites:velocity to vx (50) vy (50)||``
+block. You'll find that block
+in the ``||sprites:Sprites||`` drawer.
+1. Change the variable name to your projectile.
+1. Set the **vx** and **vy** values to make the projectile move up towards
+the top of the screen.
+1. Add a block to destroy the projectile when it moves off of the screen.
+
+Use the hint if you get stuck!
+
+```blocks
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+## Complete! @showdialog
+
+Good work! You just made a project where the player can launch some
+projectiles. In the next part, we'll add some enemies to hurl those
+projectiles at!
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0201-part2.md b/docs/courses/csintro/unit-2/s01-lab0201-part2.md
new file mode 100644
index 00000000000..0a7b40cd1be
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0201-part2.md
@@ -0,0 +1,111 @@
+# Lab 2.1 Part 2: Shooting gallery
+
+## Here come the enemies! @showdialog
+
+A game without enemies isn't much of a game. Let's add some!
+
+## Standard orbit, Mr. Sulu.
+
+Let's park some enemies at the top of the screen.
+
+1. From the ``||game:Game||`` drawer, add an
+``||game:on game update every (500) ms||``
+container block to your workspace.
+1. Change the interval from `500` ms to `1000` ms.
+**Question**: How long is 1,000 milliseconds?
+1. Within your new
+``||game(noclick):on game update every (1000) ms||``
+container,
+add blocks to accomplish the following:
+ 1. Add an enemy sprite to the screen.
+ 1. Give the enemy sprite variable an appropriate name.
+ 1. Give the enemy sprite an appropriate image.
+ 1. Place the enemy sprite just above the top of the screen.
+ Make sure at least part of the image is visible, though!
+ 1. Give the enemy sprite a random x-coordinate.
+
+Test your code to make sure it runs as you expect it.
+You should see a bunch of enemies created at the top of the screen, with only
+part of the image peeking into the screen.
+View the hint to check your code.
+
+```blocks
+let enemySprite: Sprite = null
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+})
+```
+
+## Let's roll!
+
+Now, let's move the enemies toward the player!
+
+1. At the bottom of your
+``||game(noclick):on game update every (1000) ms||``
+container, add a
+``||sprites:set||`` ``||variables(sprites):mySprite||``
+``||sprites:velocity to vx (50) vy (50)||``
+block.
+1. Change the variable name to your enemy sprite.
+1. Change the velocity values so that the sprites move slowly
+toward the bottom of the screen.
+1. Add another block so that the enemy sprite is destroyed when it leaves
+the screen.
+
+Test your code to make sure it runs as you expect it.
+View the hint to check your code.
+
+```blocks
+let enemySprite: Sprite = null
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ // @highlight
+ enemySprite.setVelocity(0, 25)
+ // @highlight
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+## Complete! @showdialog
+
+Good work! You've added enemies to your project that move toward the player.
+In the next part, we'll add the ability to destroy those enemies!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0201-part3.md b/docs/courses/csintro/unit-2/s01-lab0201-part3.md
new file mode 100644
index 00000000000..190dd33d44a
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0201-part3.md
@@ -0,0 +1,120 @@
+# Lab 2.1 Part 3: Shooting gallery
+
+## Villains be gone! @showdialog
+
+Our project allows the player to fire at enemies moving toward them,
+but nothing happens to the enemies when they are hit. Let's fix that!
+
+## Gotta start somewhere!
+
+First, let's give the player a starting score and set of lives.
+
+1. At the bottom of your
+``||loops(noclick):on start||``
+container, add blocks
+from the ``||info:Info||`` drawer to give the player:
+ 1. A starting score
+ 1. A starting set of lives.
+
+Test your project to see if your code runs as expected.
+View the hint if you need help.
+
+```blocks
+let enemySprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+// @highlight
+info.setScore(0)
+// @highlight
+info.setLife(3)
+```
+
+## Bye bye, enemies!
+
+Now, let's make our projectiles remove the enemy it strikes.
+Let's add to our player's score, too!
+
+1. From the ``||sprites:Sprites||`` drawer, add an
+``||sprites:on||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player) overlaps||`` ``||variables(sprites):otherSprite||``
+``||sprites:of kind (Player)||``
+container to your workspace.
+1. Change the kinds so that the block reads as follows:
+``||sprites(noclick):on sprite of kind Projectile overlaps otherSprite of kind Enemy||``
+1. Into your new container, drag a block from the
+``||info:Info||`` drawer
+that increases the player's score. Feel free to change the value to anything
+you like.
+1. Add more blocks so that
+``||variables(noclick):sprite||`` and
+``||variables(noclick):otherSprite||`` are destroyed. Use any effect
+that you like!
+
+Test your project to see if your code runs as expected.
+View the hint if you need help.
+
+```block
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+```
+
+## Complete! @showdialog
+
+Good work! The player now has a way to destroy enemies and earn points.
+Now we need to put the player in some jeopardy. On to part 4!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0201-part4.md b/docs/courses/csintro/unit-2/s01-lab0201-part4.md
new file mode 100644
index 00000000000..8f314734d0b
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0201-part4.md
@@ -0,0 +1,97 @@
+# Lab 2.1 Part 4: Shooting gallery
+
+## Collision alert! @showdialog
+
+When enemies hit your player, the player should lose a life. Let's finish
+the game by adding one more event handler.
+
+## Ouch!
+
+1. From the ``||sprites:Sprites||`` drawer, add another
+``||sprites:on||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player) overlaps||`` ``||variables(sprites):otherSprite||``
+``||sprites:of kind (Player)||``
+container to your workspace.
+1. Change the block so that it reads as follows:
+``||sprites(noclick):on sprite of kind Player overlaps otherSprite of kind Enemy||``
+1. Into your new container, drag a block from the
+``||info:Info||`` drawer
+that removes one of the player's lives.
+1. Add another block that destroys the enemy. Use any effect that you like.
+
+Test your project to see if your code runs as expected.
+View the hint if you need help.
+
+```block
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+})
+```
+
+## Complete! @showdialog
+
+Good work! Your game is now complete! If you have time, then move on to the bonus
+section, where we will add some sound effects!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0201-part5.md b/docs/courses/csintro/unit-2/s01-lab0201-part5.md
new file mode 100644
index 00000000000..3efb55a8ee2
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0201-part5.md
@@ -0,0 +1,165 @@
+# Lab 2.1 Part 5 (BONUS): Shooting gallery
+
+## BONUS -- Pew pew! @showdialog
+
+Time to add some sound effects to your project!
+
+## Pew pew!
+
+Let's add a sound whenever a projectile is fired.
+We'll use a new event handler for this.
+
+1. From the ``||sprites:Sprites||`` drawer, add an
+``||sprites:on created||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player)||``
+container to your workspace.
+1. Change the block so that it reads as follows:
+``||sprites(noclick):on created sprite of kind Projectile||``
+1. Into your new container, drag a
+``||music:play sound (ba ding) until done||``
+block from the
+``||music:Music||`` drawer.
+1. Change the sound to **pew pew**.
+1. Change the playback mode to **in background**.
+
+This new event handler will run anytime a projectile is created in your project.
+
+Test your project to see if your code runs as expected.
+View the hint if you need help.
+
+Feel free to try different sounds!
+
+```block
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+```
+
+## Crash!
+
+Now, let's add a sound whenever an enemy is destroyed.
+We'll use yet another new event handler for this.
+
+1. From the ``||sprites:Sprites||`` drawer, add an
+``||sprites:on destroyed||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player)||``
+container to your workspace.
+1. Change the block so that it reads as follows:
+``||sprites(noclick):on created sprite of kind Enemy||``
+1. Into your new container, drag a
+``||music:play sound (ba ding) until done||``
+block from the
+``||music:Music||`` drawer.
+1. Change the sound to **small crash**.
+1. Change the playback mode to **in background**.
+
+This new event handler will run anytime an enemy is destroyed.
+
+Test your project to see if your code runs as expected.
+View the hint if you need help.
+
+Feel free to try different sounds!
+
+**Question**: Did you notice that the crash plays when an enemy flies off of the screen,
+too? Why is that? How could you fix it?
+
+~hint Answer
+Instead of using the new "on destroyed" event handler, you could add the
+sound blocks to the existing event handlers where the enemies are destroyed.
+hint~
+
+```block
+sprites.onDestroyed(SpriteKind.Enemy, function (sprite) {
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+```
+
+## Complete! @showdialog
+
+Good work! If you still have some time, then give these other bonuses a try!
+
+- Let the player fire projectiles with the **B** button also, but use
+this block instead:
+```block
+let projectile = sprites.createProjectileFromSprite(sprites.projectile.explosion1, heroSprite, 0, -50)
+```
+How does this block work compared to the blocks that you used for the **A** button?
+
+- Create your own images for the sprites.
+- Create a background image.
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
+ projectile = sprites.createProjectileFromSprite(sprites.projectile.explosion1, heroSprite, 0, -50)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Enemy, function (sprite) {
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+let projectile: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0202-part1.md b/docs/courses/csintro/unit-2/s01-lab0202-part1.md
new file mode 100644
index 00000000000..aec064a486c
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0202-part1.md
@@ -0,0 +1,226 @@
+# Lab 2.2 Part 1: Introduction to variables
+
+## Introduction to variables @showdialog
+
+Variables help us keep track of information that our project needs.
+MakeCode Arcade has a bunch of built-in variables that we can use, like
+a player's score or lives.
+
+We can create our own variables, too!
+
+## Splash!
+
+Let's learn about a new block that you can use to display a message on
+the screen.
+
+1. In your
+``||loops(noclick):on start||``
+container, add a
+``||game:splash ("")||``
+block. You can find that block in the ``||game:Game||`` drawer of the toolbox.
+1. Type a phrase into the new block. Any phrase will do!
+
+Run your project to see how this block works.
+
+```blocks
+game.splash("Hello, world!")
+```
+
+## Join together!
+
+Instead of just one space for text, let's give ourselves some more room.
+We need a new block ... and this time, it's in a secret location!
+
+The drawer that we need is called ``||text:Text||`` **Text**,
+and it is in the **Advanced** section of the toolbox.
+Click on **Advanced** to see the secret drawers in the toolbox!
+
+Check the hint to see a screenshot showing this secret drawer.
+
+1. From the
+``||text:Text||``
+**Text**
+drawer, drag a
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block into your
+``||game(noclick):splash("")||``
+block.
+
+Run your project to see how these blocks work together.
+Check the hint if you need help.
+
+![Advanced drawers in the toolbox.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L02.02.P01.text_drawer.png)
+
+```blocks
+game.splash("Hello" + "World")
+```
+
+## Speak your mind!
+
+Let's learn more about how the **Join** block works.
+
+1. Change the text in your
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block.
+1. Run your project and see how it's changed.
+1. Do this a few times until your understand how this block works.
+
+Now, let's try something else!
+
+- Expand the
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block.
+
+~hint How do I expand a block?
+Some blocks have a plus sign on the right side of the block. Select the
+plus sign to expand it. The block will show more information!
+hint~
+
+- Now, collapse the
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block.
+
+~hint How do I collapse a block?
+Some blocks will have a minus sign on the right side of the block.
+Select the minus sign to collapse it. The block will hide some information!
+hint~
+
+Play around with the
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block.
+Expand it, collapse it, and enter your own text.
+Watch how your project responds.
+
+When you are ready, move on to the next step.
+
+## Only room for three!
+
+Let's get our project ready for our first variable.
+
+1. Expand or collapse your
+``||text:join ("Hello") ("World")||``
+**join ("Hello") ("World")**
+block
+so that it shows three spaces for text.
+1. In the first space, enter **You are ** *with a space at the end*.
+1. In the second space, enter your age (or any number that you wish).
+1. In the third space, enter ** years old** *with a space at the beginning*.
+
+Run your project to see how it works.
+Check the hint if you need help.
+
+**Question**: Why were the spaces in steps 2 and 4 necessary?
+
+```blocks
+game.splash("You are " + "0" + " years old")
+```
+
+## Whose age?
+
+Our project works, but it doesn't show the player's age ... it shows yours.
+Sure, we could ask the player to change the code and type in their age,
+but that's not very friendly!
+
+Let's get our project ready so that we can ask the player to enter their age.
+To do that, we need to create a variable.
+
+1. Open the ``||variables:Variables||`` drawer.
+1. At the top of the drawer, select the
+**Make a Variable...**
+button.
+1. In the dialog that appears, enter an appropriate name (like *age*).
+1. Select the **Ok** button.
+
+Now, we have a variable that we can use!
+
+1. Drag a
+``||variables:set age to (0)||``
+block **to the top** of your
+``||loops(noclick):on start||``
+container.
+1. Set the variable's value to your own age.
+
+We've created a variable. Now, we need to use it!
+
+1. From the
+``||variables:Variables||`` drawer, drag an
+``||variables:age||``
+block and drop it in the middle spot of your
+``||text:join ("You are ") ("0") (" years old.")||``
+**join ("You are ") ("0") (" years old.")**
+block.
+1. Run your project to see how it works.
+1. Change the value of the **age** variable and watch how your program
+responds.
+1. Do this a few times until you are comfortable with how the variable
+works with the **join** block.
+
+Check the hint if you need help.
+
+```blocks
+let age: number = 14
+game.splash("You are " + age + " years old.")
+```
+
+## Does order matter?
+
+What happens when we switch the order of these two blocks?
+
+1. Switch the order of the blocks in your
+``||loops(noclick):on start||``
+container.
+1. Run your code to see how your program responds.
+
+**Question**: Does the order of these two blocks matter? Why or why not?
+
+## I want your input!
+
+We have a variable now, but we still haven't asked the player for their age.
+Let's do that now!
+
+1. From the
+``||game:Game||`` drawer, drag an
+``||game:ask for number ("")||``
+block and drop it into the red block
+that sets the value of your variable.
+1. In this new block, enter a prompt for the player,
+like "How old are you?"
+
+Check the hint if you need help.
+
+```blocks
+let age = game.askForNumber("How old are you?")
+game.splash("You are " + age + " years old.")
+```
+
+## Ooh! A keyboard!
+
+Run your program and notice how it responds now.
+
+Now you know how to display a keyboard on the screen!
+
+Use these instructions to interact with the keyboard.
+
+- Use the D-pad (up, down, left, and right) to move the cursor.
+- To press a key, use the **A** button.
+- If you make a mistake, use the **B** button as a backspace.
+- When you are done, use the D-pad to move the cursor to the **Ok** button.
+Then, use the **A** button to select **Ok**.
+
+Run your project a few times so that you see how your project works
+and to get used to the on-screen keyboard.
+
+## Complete! @showdialog
+
+Good work! We will return to these ideas in the next couple of lessons.
+For now, move on to Part 2!
+
+```ghost
+let age = game.askForNumber("How old are you?")
+game.splash("You are " + age + " years old.")
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0202-part2.md b/docs/courses/csintro/unit-2/s01-lab0202-part2.md
new file mode 100644
index 00000000000..02a472f6f61
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0202-part2.md
@@ -0,0 +1,173 @@
+# Lab 2.2 Part 2: Introduction to variables
+
+## How many items? @showdialog
+
+Let's look at another way that we can use variables.
+
+In this project, we will use a variable to keep track of how many sprites
+are on the screen.
+
+## Let's find a hero!
+
+First, let's create a hero for our story.
+
+Create your hero sprite. Add block to your project so that your hero:
+
+- Has an appropriate variable name.
+- Has an image assigned to it.
+- Is controlled by the player with the directional pad.
+- Stays on the screen.
+
+Check the hint if you need help and to verify your code.
+
+```blocks
+let heroSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+```
+
+## Add some sprites!
+
+Now, let's add a sprite to the screen whenever the player wants one.
+
+Add an event handler to your project so that, whenever the player
+presses the **A** button, a new sprite appears. Make sure the new sprite:
+
+- Has an appropriate variable name.
+- Has an image assigned to it.
+- Has a kind assigned to it (anything except *Player*).
+- Appears at a random location on the screen.
+
+Run your project to see if it works as you expect it to.
+Check the hint if you need help.
+
+```blocks
+let foodSprite: Sprite = null
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ foodSprite = sprites.create(sprites.food.smallStrawberry, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+```
+
+## Story time!
+
+Let's create a simple story for your project.
+
+- Who is your hero?
+- What are the objects that your hero is adding to the screen?
+- Add an appropriate splash screen at the start of your game
+that explains your story.
+
+When you have added a story to your game, move on to the next step.
+
+## Count 'em up!
+
+Now, let's keep track of how many items the player has added to the screen.
+
+1. Make a new variable for your project. This variable will store
+the number of sprites that the player has created. Give the variable
+an appropriate name.
+1. Add a block to your
+``||loops(noclick):on start||``
+container that sets
+the value of the variable to zero.
+1. In your event handler for the **A** button, add a block that changes
+the value of your variable by **1**.
+
+~hint What is a increment?
+When we increase the value of a variable by one, we often say that we are
+"incrementing" the variable.
+hint~
+
+We've created the variable, but we aren't using it yet. We'll do that next!
+
+```blocks
+let foodSprite: Sprite = null
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ foodSprite = sprites.create(sprites.food.smallStrawberry, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+ // @highlight
+ foodCount += 1
+})
+```
+
+## You don't say!
+
+Let's show a message whenever the player wants to know how many sprites
+are on the screen.
+
+Add an event handler for the **B** button to your workspace.
+In your event handler:
+
+- Use a
+``||game:splash||``
+block or a
+``||sprites:say||``
+block to display the value of your variable.
+- Feel free to use a
+**join** block to make your message more interesting!
+
+Run your project to see if it works as expected.
+Check the hint if you need some help.
+
+```blocks
+let foodCount = 0
+controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
+ game.splash("I see " + foodCount + " strawberries!")
+})
+```
+
+## Pick up after yourself!
+
+Now, let's say that the player can pick up objects, too.
+
+1. Add an
+``||sprites:on overlap||``
+event handler to your workspace.
+1. Change the block so that it runs when the player collides with another sprite.
+1. In the event handler, destroy the sprite when the player touches it.
+1. Remember to update your counting variable!
+
+Run your project to see if it works as expected.
+Check the hint if you need some help.
+
+~hint What is a decrement?
+When we decrease the value of a variable by one, we often say that we are
+"decrementing" the variable.
+hint~
+
+```blocks
+let foodCount = 0
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ foodCount += -1
+})
+```
+
+## Complete! @showdialog
+
+Good work! You have learned a few ways to use variables!
+
+Submit your project to your instructor if requested to do so.
+
+```ghost
+controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
+ game.splash("I see " + foodCount + " strawberries!")
+})
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ foodSprite = sprites.create(sprites.food.smallStrawberry, SpriteKind.Food)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+ foodCount += 1
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ foodCount += -1
+})
+let foodSprite: Sprite = null
+let foodCount = 0
+foodCount = 0
+let heroSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+heroSprite.say("Hi!")
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-2/s01-lab0203.md b/docs/courses/csintro/unit-2/s01-lab0203.md
new file mode 100644
index 00000000000..c9ea3aabc2b
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0203.md
@@ -0,0 +1,91 @@
+# Lab 2.3: Variables and math
+
+## Variables and math! @showdialog
+
+Variables are helpful for our programs,
+since they let us store information that we need in our program.
+
+Variables become even more powerful when we can combine them!
+
+In this lab, you will discover different ways to use math with variables.
+
+## Ooh! A playground!
+
+First, let's build a playground.
+
+Add blocks to your
+``||loops(noclick):on start||``
+container that:
+
+- Asks the player for two numbers.
+- Stores those numbers in two different variables.
+
+View the hint to check your code.
+
+```blocks
+let number1 = game.askForNumber("First number?")
+let number2 = game.askForNumber("Second number?")
+```
+
+## Sum? Product? What's the difference?
+
+Now, let's use these two numbers that your project has collected from the player.
+
+Add blocks to your
+``||loops(noclick):on start||``
+container so that your project shows the *sum* of the two numbers.
+The block to add two numbers is in the
+``||math:Math||``
+drawer.
+
+Run your project to test that it works. Try different numbers.
+View the hint to check your code.
+
+~hint What is a sum?
+The sum is the result of two numbers added together. In other words,
+use the addition (or "plus") operator.
+hint~
+
+```blocks
+let number1 = game.askForNumber("First number?")
+let number2 = game.askForNumber("Second number?")
+game.splash("" + number1 + " + " + number2 + " = " + (number1 + number2))
+```
+
+## Complete @showdialog
+
+Good work! Now, learn about some of the blocks in the ``||math:Math||`` drawer!
+
+- Start with the block that you already have in your project.
+Notice that the "plus sign" can be changed to different symbols.
+- For each of these operations, run your project a few times.
+ - Enter some numbers. (Try small ones at first.)
+ - Make a record of each of your trials.
+ (In other words, write down the numbers that you enter each time
+ you run your program.)
+ - See if you can figure out what each operation does.
+- Once you have explored all of the operators in this first math block,
+try some of the remaining blocks in the
+``||math:Math||``
+drawer in the same way.
+ - You may not understand all of the operators that you find. That's OK!
+ - Be sure to write down all of your trials to show the work you have done.
+
+Your instructor may give you a worksheet that you can use to write down
+your trials and your explanations. If not, then use any method that you like
+to record your trials and your results.
+
+Have fun!
+
+```ghost
+let number1 = game.askForNumber("First number?")
+let number2 = game.askForNumber("Second number?")
+game.splash("" + number1 + " + " + number2 + " = " + (number1 + number2))
+let number3 = Math.sqrt(number1)
+number3 = 0 % 1
+number3 = Math.min(0, number1)
+number3 = Math.abs(number1)
+number3 = Math.round(number1)
+number3 = Math.constrain(0, 0, 0)
+number3 = Math.map(0, 0, 1023, 0, 4)
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0204.md b/docs/courses/csintro/unit-2/s01-lab0204.md
new file mode 100644
index 00000000000..a71a0d2e792
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0204.md
@@ -0,0 +1,93 @@
+# Lab 2.4: Silly story time!
+
+## Silly stories @showdialog
+
+In this lab, you will create a silly story in MakeCode Arcade!
+
+## First draft
+
+First, you need to create a silly story.
+
+Start by writing a story. Nothing very long.
+Just two or three sentences for now.
+
+To create a silly version of your story, remove some of the words
+and replace them with blanks.
+For each blank, decide what prompt you will use.
+
+You could ask for a part of speech (like, "Enter a noun").
+Or, you could ask for something a little more specific
+(like, "Enter a number between 1 and 10").
+
+It's up to you!
+
+## Ask me anything!
+
+Now, let's collect information from the player.
+
+1. For each of the blanks in your silly story, create a variable.
+Give your variable a good name (like *adjective* or *firstNoun*).
+1. Ask the player to provide a value for each variable.
+ - Use the
+ ``||game:ask for number||``
+ for variables that will hold numbers.
+ - For the rest of the variables, use the
+ ``||game:ask for string||``
+ block instead.
+
+Run your project to make sure that it asks the player for all of the
+information that you need for your story.
+
+Check the hint if you need some help.
+
+```blocks
+let aNumber = game.askForNumber("Enter a number.")
+let aColor = game.askForString("Enter a color.")
+```
+
+## Tell me a story!
+
+Now it's time to show your story. We'll start with a simple presentation.
+
+Use a
+``||game:show long text||``
+block with a **join** block
+to display your silly story.
+
+Remember that the **join** block is the **Text** drawer in the
+**Advanced** section of the toolbox.
+
+Check the hint if you need some help.
+
+```blocks
+let aNumber = game.askForNumber("Enter a number.")
+let aColor = game.askForString("Enter a color.")
+game.showLongText("I am " + aNumber + " years old and my hair color is " + aColor + "!", DialogLayout.Center)
+```
+
+## Complete @showdialog
+
+Good work! Now, get creative! Try some of these ideas, or try some of your own!
+
+- Illustrate your story with a background and sprites.
+- Is there a hero sprite, and if so, will the player be in control of it? Will any sprites move automatically?
+- Expand your story. Perhaps include multiple scenes.
+- Add sound effects to your story.
+- Add background music to your story. Explore the
+ ``||music:play melody||``
+ block and the
+ ``||music:play song||``
+ block, found in the
+ ``||music:Music||``
+ drawer of the toolbox.
+
+Have fun!
+
+```ghost
+let aNumber = game.askForNumber("Enter a number.")
+let aColor = game.askForString("Enter a color.")
+game.splash("I am " + aNumber + " years old and my hair color is " + aColor + "!")
+game.showLongText("", DialogLayout.Center)
+music.play(music.stringPlayable("- - - - - - - - ", 120), music.PlaybackMode.UntilDone)
+music.play(music.createSong(hex`00780004080200`), music.PlaybackMode.UntilDone)
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0205-part1.md b/docs/courses/csintro/unit-2/s01-lab0205-part1.md
new file mode 100644
index 00000000000..b3397fd97d5
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0205-part1.md
@@ -0,0 +1,181 @@
+# Lab 2.5 Part 1: Guess what!
+
+## Pick a number! @showdialog
+
+Let's make a game where the player guesses a number that the game show host
+has chosen.
+
+## Set the stage
+
+Let's set the stage with a sprite for the game show host.
+
+1. To your
+``||loops(noclick):on start||``
+container, add a block that creates a
+sprite for your game show host.
+ - Give the sprite an appropriate variable name.
+ - Give the sprite an appropriate image.
+ - Have the game show host say,
+ "Guess which number I am thinking of."
+ - At the bottom of your
+ ``||loops(noclick):on start||``
+ container, add a
+ ``||loops:pause (500) ms||``
+ block from the
+ ``||loops:Loops||``
+ drawer.
+ - Set the pause to `1000` milliseconds. We want the game to pause
+ long enough for the player to read the prompt.
+ - Feel free to change the length of the pause to a different value.
+
+Run your project to make sure it works as expected.
+View the hint if you need some help.
+
+```blocks
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(1000)
+```
+
+## Let's be random!
+
+Now, we need the host to pick a number.
+
+1. Create a new variable that will hold your game show host's secret number.
+1. To the **bottom** of your
+``||loops(noclick):on start||``
+container,
+set the value of your new variable to a random number from 1 to 10.
+ - Remember that the
+ ``||math:pick random||``
+ block is in the
+ ``||math:Math||``
+ drawer.
+
+View the hint if you need some help.
+
+```blocks
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(2000)
+// @highlight
+let hostsNumber = randint(1, 10)
+```
+
+## Take a guess!
+
+Now, let's get the player's guess.
+
+1. Create a new variable that will hold the player's guess.
+1. To the **bottom** of your
+``||loops(noclick):on start||``
+container, ask the player for a number.
+Store the guess in your new variable.
+
+Run your project to make sure it works as expected.
+View the hint if you need some help.
+
+```blocks
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(2000)
+let hostsNumber = randint(1, 10)
+// @highlight
+let playerGuess = game.askForNumber("Choose a number between 1 and 10.")
+```
+
+## Was I right?
+
+Now, let's see if the player was correct! To do so, we need to create a
+*conditional statement*.
+
+~hint What is a conditional statement?
+A conditional statement is sometimes called an "if" statement.
+It tests whether something is true or false.
+hint~
+
+1. To the **bottom** of your
+``||loops(noclick):on start||``
+container,
+add an
+``||logic:if (true) then [] else []||``
+block. You'll find that block in the
+``||logic:Logic||``
+drawer.
+1. In place of the
+``||logic(noclick):(true)||``
+value, drop a
+``||logic:(0) = (0)||``
+block. You will find that in the
+``||logic:Logic||`` drawer, also.
+1. Use blocks from the
+``||variables:Variables||``
+drawer to make the
+block say something like this:
+``||logic(noclick):if||`` ``||variables(noclick):hostsNumber||``
+``||logic(noclick):=||`` ``||variables(noclick):playerGuess||``
+
+Use the hint to check your code. We'll fill in the
+``||logic(noclick):if||``
+container next.
+
+```blocks
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(2000)
+let hostsNumber = randint(1, 10)
+let playerGuess = game.askForNumber("Choose a number between 1 and 10.")
+if (hostsNumber == playerGuess) {
+
+} else {
+
+}
+```
+
+## Say what?
+
+Now, complete the ``||logic(noclick):if||`` container.
+
+- In the top half of the
+``||logic(noclick):if||``
+container, add blocks to make the game show host
+say that the player was correct.
+Use a **join** block to also show the host's number.
+- In the bottom half of the
+``||logic(noclick):if||``
+container, add block to make the game show host
+say that the player was incorrect.
+Use a **join** block to also show the host's number.
+
+Run your project to see if your code works as expected.
+Use the hint to check your code.
+
+```blocks
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(2000)
+let hostsNumber = randint(1, 10)
+let playerGuess = game.askForNumber("Choose a number between 1 and 10.")
+if (hostsNumber == playerGuess) {
+ hostSprite.sayText("Great job! " + hostsNumber + " was my number!")
+} else {
+ hostSprite.sayText("Sorry! " + hostsNumber + " was my number.")
+}
+```
+
+## Complete @showdialog
+
+Good work! Let's use conditional statements to create another game in Part 2!
+
+```ghost
+let hostSprite = sprites.create(sprites.castle.princessFront0, SpriteKind.Player)
+hostSprite.sayText("Guess which number I am thinking of.")
+pause(2000)
+let hostsNumber = randint(1, 10)
+let playerGuess = game.askForNumber("Choose a number between 1 and 10.")
+if (hostsNumber == playerGuess) {
+ hostSprite.sayText("Great job! " + hostsNumber + " was my number!")
+} else {
+ hostSprite.sayText("Sorry! " + hostsNumber + " was my number.")
+}
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0205-part2.md b/docs/courses/csintro/unit-2/s01-lab0205-part2.md
new file mode 100644
index 00000000000..d451b3db8cc
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0205-part2.md
@@ -0,0 +1,72 @@
+# Lab 2.5 Part 2: Guess what!
+
+## Is it even? @showdialog
+
+Many programs need to test whether a number is even or odd. Let's write one!
+
+## Collecting the evidence
+
+First, let's get a number to test from the player.
+
+1. Create a variable that will store the player's number.
+1. To your
+``||loops(noclick):on start||``
+container,
+add blocks that ask the player for a number.
+Store the response in your new variable.
+
+Run your project to test your code. You can also check the hint.
+
+```blocks
+let theNumber = game.askForNumber("Enter a number.")
+```
+
+## So, is it even or not?
+
+Now, let's display a message to say whether the number is even.
+
+You guessed it: We need to write another conditional statement!
+
+1. To the **bottom** of your
+``||loops(noclick):on start||``
+container, add an
+``||logic:if (true) then [] else []||``
+block.
+1. Use blocks from the
+``||variables:Variables||``,
+``||logic:Logic||``, and
+``||math:Math||``
+drawers to make the block say the following:
+``||logic(noclick):if||`` ``||math(noclick):remainder of||``
+``||variables(noclick):(your variable)||`` ``||math(noclick):/ (2)||``
+``||logic(noclick):= (0) then||``.
+1. In each half of the ``||logic(noclick):if||`` container,
+add blocks to display an appropriate message.
+
+~hint How do I know if the number is even?
+If the number is even then, when you divide it by two, the remainder is zero.
+hint~
+
+Run your project to test your code. Check the hint if you get stuck.
+
+```blocks
+let theNumber = game.askForNumber("Enter a number.")
+if (theNumber % 2 == 0) {
+ game.splash("" + theNumber + " is even!")
+} else {
+ game.splash("" + theNumber + " is not even.")
+}
+```
+
+## Complete @showdialog
+
+Good work! Let's make a fortune telling device in Part 3!
+
+```ghost
+let theNumber = game.askForNumber("Enter a number.")
+if (theNumber % 2 == 0) {
+ game.splash("" + theNumber + " is even!")
+} else {
+ game.splash("" + theNumber + " is not even.")
+}
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0205-part3.md b/docs/courses/csintro/unit-2/s01-lab0205-part3.md
new file mode 100644
index 00000000000..9e7a8935a3c
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0205-part3.md
@@ -0,0 +1,109 @@
+# Lab 2.5 Part 3: Guess what!
+
+## Magic four ball! @showdialog
+
+Some people need help making big decisions.
+You can help them by making a magic four ball.
+It's like a magic eight ball, but with only four answers!
+
+## Ask me anything (again)!
+
+Let's set the scene so that the player has a character to talk to.
+
+1. In your
+``||loops(noclick):on start||``
+container, create a sprite that represents
+the fortune teller.
+ - Give the sprite variable an appropriate name.
+ - Give the sprite an appropriate image.
+ - Make the sprite say something like,
+ "Think of a question, then press the A button."
+
+Run your program and see if it behaves as expected.
+Check the hint if you need some help.
+
+```blocks
+let fortuneTellerSprite: Sprite = null
+scene.setBackgroundColor(9)
+fortuneTellerSprite = sprites.create(sprites.builtin.villager1WalkFront1, SpriteKind.Player)
+fortuneTellerSprite.sayText("Think of a question, then press the A button.")
+```
+
+## The future is ... anything!
+
+Now, let's respond to the player's question with a random response.
+
+1. Add a
+``||controller:on (A) button (pressed)||``
+container to your
+workspace. All of the following blocks will go in this container.
+1. Create a new variable called **answer**.
+1. Set this new variable to a random number from 1 to 4.
+Remember that the
+``||math:pick random||`` block is in the
+``||math:Math||`` drawer.
+1. Now, add an
+``||logic:if (true) then [] else []||``
+block.
+1. Select the **(+)** plus sign to add enough branches to your
+``||logic(noclick):if||``
+block to handle four different options.
+1. Add blocks to your
+``||logic(noclick):if||``
+block to make each branch say
+something like
+``||logic(noclick):if||`` ``||variables(noclick):answer||``
+``||logic(noclick): = (1) then||``
+1. In each branch of the
+``||logic(noclick):if||``
+block, make the fortune teller say a different message.
+ - Make each message unique.
+ - Be creative!
+ - Try to have an equal number of positive and negative responses.
+
+Run your program and see if it behaves as expected.
+Check the hint if you need some help.
+
+```blocks
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ option = randint(1, 4)
+ if (option == 1) {
+ fortuneTellerSprite.sayText("Signs look good!")
+ } else if (option == 2) {
+ fortuneTellerSprite.sayText("Not very likely.")
+ } else if (option == 3) {
+ fortuneTellerSprite.sayText("Could be!")
+ } else {
+ fortuneTellerSprite.sayText("Try again later.")
+ }
+})
+let option = 0
+let fortuneTellerSprite: Sprite = null
+scene.setBackgroundColor(9)
+fortuneTellerSprite = sprites.create(sprites.builtin.villager1WalkFront1, SpriteKind.Player)
+fortuneTellerSprite.sayText("Think of a question, then press the A button.")
+```
+
+## Complete @showdialog
+
+Good work! What are some other ways that you could use conditional statements?
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ option = randint(1, 4)
+ if (option == 1) {
+ fortuneTellerSprite.sayText("Signs look good!")
+ } else if (option == 2) {
+ fortuneTellerSprite.sayText("Not very likely.")
+ } else if (option == 3) {
+ fortuneTellerSprite.sayText("Could be!")
+ } else {
+ fortuneTellerSprite.sayText("Try again later.")
+ }
+})
+let option = 0
+let fortuneTellerSprite: Sprite = null
+scene.setBackgroundColor(9)
+fortuneTellerSprite = sprites.create(sprites.builtin.villager1WalkFront1, SpriteKind.Player)
+fortuneTellerSprite.sayText("Think of a question, then press the A button.")
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0206-part1.md b/docs/courses/csintro/unit-2/s01-lab0206-part1.md
new file mode 100644
index 00000000000..3b52685072a
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0206-part1.md
@@ -0,0 +1,215 @@
+# Lab 2.6 Part 1: Changing conditions
+
+## There's only room for three of us! @showdialog
+
+Many games limit the number of projectiles that you can create at any one time.
+Let's update one of your previous projects to do just that!
+
+## How many do you want?
+
+First, let's define the maximum number of projectiles that we can have
+on the screen.
+
+1. Create a new variable that will hold the maximum number of projectiles
+allowed on the screen. Call it something like **maxProjectiles**.
+1. We also need a new variable that will keep track of the number of
+projectiles currently on the screen. Call it something like
+**projectileCount**.
+1. In your
+``||loops(noclick):on start||``
+container, set the value of
+``||variables:maxProjectiles||``
+to **3**.
+1. In your
+``||loops(noclick):on start||``
+container, set the value of
+``||variables:projectileCount||``
+to **0**.
+
+Check the hint if you need help.
+
+```blocks
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+// @highlight
+maxProjectiles = 3
+// @highlight
+projectileCount = 0
+```
+
+## Count 'em up!
+
+Now we need to *increment* our variable when we create a projectile.
+
+1. In your
+``||controller(noclick):on (A) button (pressed)||``
+container,
+add a block that changes the
+``||variables:projectileCount||``
+variable by **1**.
+
+We also need to *decrement* our variable when a projectile is destroyed.
+The easiest way to do this is to add a new event handler.
+
+1. From the ``||sprites:Sprites||`` drawer, add an
+``||sprites:on destroyed||`` ``||variables(sprites):sprite||``
+``||sprites:of kind (Player)||``
+block.
+1. In this new container, change the kind to **Projectile**.
+1. In this new container,
+add a block that changes the
+``||variables:projectileCount||``
+variable by **-1**.
+
+Run your project. You should not see any difference from before. Why not?
+View the hint to check your code.
+
+```blocks
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ // @highlight
+ projectileCount += 1
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+```
+
+## Testing! Testing! 1 ... 2 ... 3!
+
+Nothing seems different in your project.
+We are counting the number of projectiles on the screen,
+but we aren't doing anything to limit them.
+Let's fix that!
+
+1. To the **top** of your
+``||controller(noclick):on (A) button (pressed)||``
+container, add an
+``||logic:if||``
+block.
+1. Move all of the blocks beneath the
+``||logic(noclick):if||``
+block inside of it.
+1. Add blocks so that the ``||logic:if||`` block reads:
+``||logic(noclick):if||`` ``||variables(noclick):projectileCount||``
+``||logic(noclick):is less than||`` ``||variables(noclick):maxProjectiles||``.
+
+Run your project and see if it limits the number of projectiles that you are
+able to create. View the hint if you need help.
+
+```blocks
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+let maxProjectiles = 0
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+```
+
+## Conclusion @showdialog
+
+Good job! Now, let's make the enemies speed up! On to Part 2!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0206-part2.md b/docs/courses/csintro/unit-2/s01-lab0206-part2.md
new file mode 100644
index 00000000000..6c889b1d708
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0206-part2.md
@@ -0,0 +1,170 @@
+# Lab 2.6 Part 2: Changing conditions
+
+## Here come the enemies ... faster! @showdialog
+
+Many games get more difficult the longer you play.
+Let's update your game so that enemies fall faster as you play!
+
+## Variable speed
+
+We need to keep track of the current speed of the enemies.
+
+1. Take a look at your
+``||game(noclick):on game update every (1000) ms||``
+container.
+Make a note of the **vy** value for your enemy sprites.
+1. Create a new variable that will keep track of the enemy speed.
+Call it something like **enemySpeed**.
+1. In your
+``||loops(noclick):on start||``
+container, set the value of your new
+variable to the value that you noted in Step 1.
+1. Drop your variable in place of the number for your **vy** value.
+
+Run your project to make sure nothing has changed.
+Check the hint if you need help.
+
+```blocks
+let enemySprite: Sprite = null
+let enemyVelocity = 25
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ // @highlight
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+## Let's speed things up!
+
+Now, let's make the game more difficult the longer you play.
+
+1. Find the
+``||sprites(noclick):on overlap||``
+container where enemies are
+destroyed when they collide when a projectile.
+1. In that
+``||sprites(noclick):on overlap||``
+container, add a block that
+changes the enemy speed variable. Set the change to a small value.
+
+Run your project and see how it works.
+As enemies are destroyed, you should see new enemies moving faster.
+Experiment with different values to find a good speed.
+Check the hint if you need help.
+
+```blocks
+let enemyVelocity = 25
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ // @highlight
+ enemyVelocity += 5
+})
+```
+
+## Conclusion @showdialog
+
+Good job! But maybe that's too fast? Let's put a speed limit on the enemies.
+On to Part 3!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, 25)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+let enemyVelocity = 25
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0206-part3.md b/docs/courses/csintro/unit-2/s01-lab0206-part3.md
new file mode 100644
index 00000000000..9ee65694723
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0206-part3.md
@@ -0,0 +1,187 @@
+# Lab 2.6 Part 3: Changing conditions
+
+## Not too fast! @showdialog
+
+At some point, your enemies will be moving too fast.
+Let's update your game so that enemies have a speed limit!
+
+## What's the limit?
+
+First, let's determine the enemy's speed limit.
+
+1. Create a new variable that sets the enemy speed limit.
+Call it something like **maxEnemySpeed**.
+1. In your
+``||loops(noclick):on start||``
+container, set your new variable to
+a reasonable speed limit.
+
+Run your project to make sure nothing has changed ... yet!
+Check the hint to verify your code.
+
+```blocks
+let maxEnemySpeed = 0
+let enemyVelocity = 0
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+enemyVelocity = 25
+// @highlight
+maxEnemySpeed = 150
+```
+
+## Mind your speed!
+
+Now, let's enforce our new speed limit.
+
+1. Go back to that
+``||sprites(noclick):on overlap||``
+container where enemies are
+destroyed when they collide when a projectile.
+1. Add blocks **to the bottom** of that container
+to create the following ``||logic(noclick):if||`` statement:
+
+``||logic:if||`` ``||variables:enemyVelocity||``
+``||logic:is greater than||``
+``||variables:maxEnemySpeed||`` ``||logic:then||``
+
+- ``||variables:set (enemyVelocity) to (maxEnemySpeed)||``
+
+Run your project to test your speed limit. Try different speed limit values
+and see what works best for your project.
+Check the hint if you need help.
+
+```blocks
+let enemyVelocity = 0
+let maxEnemySpeed = 0
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+ // @highlight
+ if (enemyVelocity > maxEnemySpeed) {
+ enemyVelocity = maxEnemySpeed
+ }
+})
+```
+
+## Conclusion @showdialog
+
+Good job! Now, let's ask the player how difficult they want the game to be.
+On to Part 4!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+let enemyVelocity = 25
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+ if (enemyVelocity > maxEnemySpeed) {
+ enemyVelocity = maxEnemySpeed
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let maxEnemySpeed = 0
+let enemyVelocity = 0
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+enemyVelocity = 25
+maxEnemySpeed = 150
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
diff --git a/docs/courses/csintro/unit-2/s01-lab0206-part4.md b/docs/courses/csintro/unit-2/s01-lab0206-part4.md
new file mode 100644
index 00000000000..3dfe71428ed
--- /dev/null
+++ b/docs/courses/csintro/unit-2/s01-lab0206-part4.md
@@ -0,0 +1,234 @@
+# Lab 2.6 Part 4: Changing conditions
+
+## Starting difficulty @showdialog
+
+Let's update your game so that the player can choose a difficulty level!
+
+## Selecting difficulties
+
+Let review some of the variables that we have set for your project.
+Let's consider this the "normal difficulty" level.
+
+**Normal difficulty**
+
+- Maximum projectiles: 3
+- Starting enemy speed: 25
+- Maximum enemy speed: 150
+
+Jot down some values that you think are reasonable for an **easy** difficulty
+level.
+
+Also jot down some values for a **challenging** difficulty level.
+
+## What would you like?
+
+To being, let's ask the player for a difficulty level.
+
+1. Create a new variable that will store the player's choice of difficulty.
+Give the variable a good name. How about **difficulty**?
+1. In your
+``||loops(noclick):on start||``
+container, ask the player for a
+difficulty number and store it in your new variable.
+
+Run your project to make sure nothing has changed after choosing a difficulty.
+Check the hint to verify your code.
+
+```blocks
+let projectileCount = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+projectileCount = 0
+// @highlight
+let difficulty = game.askForNumber("Difficulty?")
+```
+
+## Set 'em up!
+
+Now, let's set some values to reflect the player's difficulty selection.
+
+1. At the **bottom** of your
+``||loops(noclick):on start||``
+container, add an
+``||logic:if (true) then [] else []||``
+block.
+1. Add enough branches to test three options for our difficulty.
+1. Add conditional statements to test whether the player selected
+a difficulty of
+**1**, **2**, or **3**.
+1. In each branch of the
+``||logic(noclick):if||`` block,
+set the three variables
+to the values that you noted at the beginning of this activity.
+
+Run your project to test the three different difficulties.
+Try different values until you are happy with the three difficulties.
+
+Check the hint if you need help.
+
+```blocks
+let maxEnemySpeed = 0
+let enemyVelocity = 0
+let maxProjectiles = 0
+let projectileCount = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+projectileCount = 0
+let difficulty = game.askForNumber("Difficulty?")
+// @highlight
+if (difficulty == 1) {
+ maxProjectiles = 5
+ enemyVelocity = 15
+ maxEnemySpeed = 100
+} else if (difficulty == 3) {
+ maxProjectiles = 2
+ enemyVelocity = 50
+ maxEnemySpeed = 200
+} else {
+ maxProjectiles = 3
+ enemyVelocity = 25
+ maxEnemySpeed = 150
+}
+```
+
+## Conclusion @showdialog
+
+Congratulations! You've built a game that changes as the game is played and
+allows the player to choose a difficulty.
+
+- What other settings could you change or add to make your game
+easier or harder to play?
+- Are there other changes you could make to make the game easier or harder?
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+ if (enemyVelocity > maxEnemySpeed) {
+ enemyVelocity = maxEnemySpeed
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let maxEnemySpeed = 0
+let enemyVelocity = 0
+let projectileCount = 0
+let maxProjectiles = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+maxProjectiles = 3
+projectileCount = 0
+enemyVelocity = 25
+maxEnemySpeed = 150
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < maxProjectiles) {
+ plasmaSprite = sprites.create(sprites.projectile.explosion1, SpriteKind.Projectile)
+ plasmaSprite.setPosition(heroSprite.x, heroSprite.y)
+ plasmaSprite.setVelocity(0, -50)
+ plasmaSprite.setFlag(SpriteFlag.AutoDestroy, true)
+ projectileCount += 1
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.spray, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ enemyVelocity += 5
+ if (enemyVelocity > maxEnemySpeed) {
+ enemyVelocity = maxEnemySpeed
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeLifeBy(-1)
+ sprites.destroy(otherSprite, effects.fire, 500)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let plasmaSprite: Sprite = null
+let maxEnemySpeed = 0
+let enemyVelocity = 0
+let maxProjectiles = 0
+let projectileCount = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.space.spaceOrangeShip, SpriteKind.Player)
+heroSprite.setPosition(80, 110)
+heroSprite.setStayInScreen(true)
+controller.moveSprite(heroSprite)
+info.setScore(0)
+info.setLife(3)
+projectileCount = 0
+let difficulty = game.askForNumber("Difficulty?")
+if (difficulty == 1) {
+ maxProjectiles = 5
+ enemyVelocity = 15
+ maxEnemySpeed = 100
+} else if (difficulty == 3) {
+ maxProjectiles = 2
+ enemyVelocity = 50
+ maxEnemySpeed = 200
+} else {
+ maxProjectiles = 3
+ enemyVelocity = 25
+ maxEnemySpeed = 150
+}
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.space.spaceAsteroid0, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(8, 152), 0)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
diff --git a/docs/courses/csintro/unit-3/s01-lab0301-part1.md b/docs/courses/csintro/unit-3/s01-lab0301-part1.md
new file mode 100644
index 00000000000..94a0a05ec44
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0301-part1.md
@@ -0,0 +1,159 @@
+# Lab 3.1 Part 1: Introduction to loops
+
+## Repeat that, please! @showdialog
+
+In this lab, we will explore a family of loops called *definite loops*.
+
+*Definite loops* are loops that run a specific number of times.
+
+In Part 1, we'll use one of those loops: The **repeat** loop.
+
+```block
+let foodSprite: Sprite = null
+for (let index = 0; index < 6; index++) {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Player)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+}
+```
+
+## Take an apple; leave an apple
+
+Let's create a hero character and drop food randomly on the screen.
+
+1. Create a sprite for your hero character.
+ - Give the sprite's variable an appropriate name.
+ - Give the sprite an image.
+1. Allow the player to move the hero sprite around the screen.
+1. Whenever the player presses **A**, add a sprite to the screen.
+ - Give the new sprite's variable an appropriate name.
+ - Give the sprite an image.
+ - Place the sprite at a random location on the screen.
+
+Run your project and verify that it works as described.
+Check the hint if you need any help.
+
+```blocks
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Player)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+})
+let foodSprite: Sprite = null
+let heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+controller.moveSprite(heroSprite)
+```
+
+## Drop two!
+
+Instead of dropping just one piece of fruit (or other food), let's say we
+wanted to drop two pieces of food. That's easy enough to do: just duplicate
+the blocks that we have.
+
+But what if we wanted to drop three? Or five?
+Or ten? Or what if you wanted to change the number of sprites throughout
+the game?
+
+There is a better way to do this: Use a **repeat** loop.
+
+1. From the
+``||loops:Loops||`` drawer, drop a
+``||loops:repeat (4) times||``
+loop into your
+``||controller(noclick): on (A) button (pressed)||``
+container.
+1. Move the other blocks in that container to the **inside** of the
+``||loops(noclick):repeat||`` container.
+1. Change the number of repeats to **2**.
+
+Run your project and verify that the player drops **two** pieces of food
+each time you press **A**.
+
+Check the hint if you need help.
+
+```blocks
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ for (let index = 0; index < 2; index++) {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Player)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+ }
+})
+let foodSprite: Sprite = null
+```
+
+## Kick the tires!
+
+Give your new loop a try! As you try different **repeat** values,
+answer the questions below.
+
+**Questions**
+
+- Are there any numbers that are **not** allowed as a repeat value?
+- Are there any numbers that work in unexpected ways?
+- How might you use a **repeat** block in your own projects?
+
+## Food fight!
+
+Instead of dropping food at random locations, let's throw projectiles
+from the player.
+
+1. **Delete** all of the blocks inside of the ``||loops(noclick):repeat||`` container.
+Keep the loop, though!
+1. Add the following blocks to your ``||loops(noclick):repeat||`` loop:
+ 1. Create a variable called **speedX**.
+ 1. Set **speedX** to a random value between 10 and 40.
+ 1. Create another variable called **speedY**.
+ 1. Add blocks so that it reads:
+ ``||variables:set speedY to||`` ``||math:50 -||``
+ ``||variables:speedX||``.
+ 1. Create a projectile that starts at your hero sprite with
+ velocities **speedX** and **speedY**.
+
+These blocks will send the projectiles in random directions, but will
+keeps their speeds roughly the same.
+
+Run your project and see how it operates differently from before.
+
+Try different repeat values!
+
+How might you use this mechanism in your own projects?
+
+Check the hint if you need any help.
+
+```block
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ for (let index = 0; index < 2; index++) {
+ let speedX = randint(10, 40)
+ let speedY = 50 - speedX
+ let foodSprite: Sprite = sprites.createProjectileFromSprite(sprites.food.smallApple, heroSprite, speedX, speedY)
+ }
+})
+```
+
+## Complete! @showdialog
+
+Good work! You have worked with the **repeat** loop!
+
+Try these extensions if you have time:
+
+- Can you generate a random number of projectiles with each button press?
+- Because both speed values are positive, the projectiles always move
+toward the bottom-right corner of the screen. Can you find a way to make
+the directions more random?
+- Instead of sending the projectiles in random directions, can you
+distribute them more uniformly around the hero?
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ for (let index = 0; index < 2; index++) {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Player)
+ foodSprite.setPosition(randint(8, 152), randint(8, 112))
+ speedX = randint(10, 40)
+ speedY = 50 - speedX
+ foodSprite = sprites.createProjectileFromSprite(sprites.food.smallApple, heroSprite, speedX, speedY)
+ }
+})
+let speedY = 0
+let speedX = 0
+let heroSprite: Sprite = null
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+controller.moveSprite(heroSprite)
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-3/s01-lab0301-part2.md b/docs/courses/csintro/unit-3/s01-lab0301-part2.md
new file mode 100644
index 00000000000..deae28770bd
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0301-part2.md
@@ -0,0 +1,168 @@
+# Lab 3.1 Part 2: Introduction to loops
+
+## For! @showdialog
+
+In this lab, we will explore a family of loops called *definite loops*.
+
+*Definite loops* are loops that run a specific number of times.
+
+In Part 1, we learned about the **repeat** loop.
+
+Now, we will learn about the **for** loop.
+
+```block
+let foodSprite: Sprite = null
+for (let index = 0; index < 6; index++) {
+ foodSprite = sprites.create(sprites.food.smallApple, SpriteKind.Player)
+ foodSprite.setPosition(10 + index * 20, 10)
+}
+```
+
+## What's the same? What's different?
+
+Sometimes, we want to repeat a set of blocks, but we need to make some
+calculations based on how many times we've been through the loop.
+
+The simplest tool for this task is the **for** loop.
+
+Look at the code in this project. Run it and see how the sprites appear.
+
+Now, look *closely* at the blocks that create the sprites.
+
+Each sprite uses a pair of blocks.
+
+- What is the same about the blocks in each pair?
+- What is different in each pair?
+
+Head to the next step when you are ready.
+
+## For! Again!
+
+Did you notice that the *only* difference in each pair is the number
+that is multiplied with the **distance** variable?
+
+- In the first pair, that multiplier is **zero**.
+- In the second pair, the multiplier is **one**.
+- In the next, it is **two**.
+- In the last, it is **three**.
+
+We have a loop that does *exactly* that!
+It counts starting at zero, and counts up to a number that you choose.
+It's the ``||loops(noclick):for||`` block!
+
+Move to the next step to learn how to use the ``||loops(noclick):for||`` loop
+in this project.
+
+## Step aside!
+
+Let's replace these repetitive blocks with a **for** loop.
+
+1. Drag the blocks that create the sprites off to the side.
+We will use a couple of them shortly.
+1. At the bottom of the
+``||loops(noclick):on start||``
+container, add a
+``||loops:for||`` ``||variables(loops):index||``
+``||loops:from 0 to (4)||``
+block.
+1. Change the ``||loops(noclick):for||`` loop so that
+``||variables(noclick):index||``
+counts from
+**0** to **3** (or any other number that you like).
+1. Drag **just one pair** of blocks that creates and places a sprite
+on the screen into the
+``||loops(noclick):for||`` container.
+
+Run your project and check the simulator. It might not look like it's
+working correctly, but it is! We need to make one minor adjustment, though.
+
+Compare your code to the hint before moving forward.
+
+```blocks
+let duckSprite: Sprite = null
+let spriteX = 15
+let spriteY = 10
+let distance = 20
+for (let index = 0; index <= 4; index++) {
+ duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+ duckSprite.setPosition(spriteX + 0 * distance, spriteY)
+}
+```
+
+## Use the index!
+
+The ``||loops(noclick):for||`` loop uses the ``||variables(noclick):index||`` variable
+to count, but we are not using it inside of our loop! Let's fix that.
+
+- From the top of the
+``||loops(noclick):for||`` container, drag a copy of the
+``||variables(noclick):index||`` variable and drop it into the place
+that is being multiplied by
+``||variables(noclick):distance||``.
+
+Run your project again and see that the ducks are in a row again!
+
+Check the hint if you need help.
+
+```blocks
+let duckSprite: Sprite = null
+let spriteX = 15
+let spriteY = 10
+let distance = 20
+for (let index = 0; index <= 4; index++) {
+ duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+ duckSprite.setPosition(spriteX + index * distance, spriteY)
+}
+```
+
+## Complete! @showdialog
+
+Good work!
+
+Notice how much simpler your code looks now that you've used a
+**for** loop!
+
+How might you use this in your own projects?
+
+Try these extensions if you have time:
+
+- Change the numbers and notice how the sprites are drawn on the screen.
+- Can you generate a single column of sprites instead of a row?
+- Can you generate a series of sprites drawn diagonally?
+- Notice that the loop control variable, ``||variables(noclick):index||``,
+is the same shape and color as any other variable.
+If you don't like the name of the loop control variable,
+you can drop one of your own in its place. Give it a try!
+- Let's say you want to draw a grid of sprites on the screen,
+like the image below:
+![Ducks in a grid.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L03.01.P02.duck_grid.png)
+How might you do that?
+- Create an interesting pattern of sprites.
+Challenge your "helping trios" teammates to duplicate your pattern using loops.
+Compare your code and see if you came up with different ways to produce
+the same pattern.
+
+```template
+let spriteX = 15
+let spriteY = 10
+let distance = 20
+let duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+duckSprite.setPosition(spriteX + 0 * distance, spriteY)
+duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+duckSprite.setPosition(spriteX + 1 * distance, spriteY)
+duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+duckSprite.setPosition(spriteX + 2 * distance, spriteY)
+duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+duckSprite.setPosition(spriteX + 3 * distance, spriteY)
+```
+
+```ghost
+let duckSprite: Sprite = null
+let spriteX = 15
+let spriteY = 10
+let distance = 20
+for (let index = 0; index <= 4; index++) {
+ duckSprite = sprites.create(sprites.duck.duck3, SpriteKind.Player)
+ duckSprite.setPosition(spriteX + index * distance, spriteY)
+}
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-3/s01-lab0304-part1.md b/docs/courses/csintro/unit-3/s01-lab0304-part1.md
new file mode 100644
index 00000000000..78bf04bcdea
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0304-part1.md
@@ -0,0 +1,295 @@
+# Lab 3.4 Part 1: High scores
+
+## High scores! @showdialog
+
+In this project, you will work with arrays in MakeCode to build a
+high scores table.
+
+You will be able to add this to other projects, too!
+
+## Let's start at the beginning
+
+We will begin with a project that you created in labs 2.1 and 2.6.
+
+If you would like to use your own project, then check with your instructor.
+
+Run the project to refresh your memory. When you are ready, move on to the
+next step.
+
+## More scores! @showdialog
+
+MakeCode Arcade can hold a single high score for any project that you create,
+but what if you wanted to keep the top five scores and player names instead?
+
+Let's build a high score table that does just that!
+
+## Variables!
+
+1. Use the
+``||variables:Variables||`` drawer to create three variables:
+ - One variable will hold the **high scores**.
+ - One variable will hold the **high score names**.
+ - One variable will hold the **number of high scores**.
+1. Add a block to your
+``||loops(noclick):on start||``
+container to set the
+**number of high scores** to `3`.
+1. Add blocks to your
+``||loops(noclick):on start||``
+container to set the
+**high scores** variable to an array with three numbers.
+ - Add whatever scores you like for your initial high score table!
+ - Make sure the scores are in *descending* order. In other words,
+ make sure the highest score is first and the lowest score is last.
+1. Add blocks to your
+``||loops(noclick):on start||``
+container to set the
+**high score names** variable to a list of three empty strings.
+Add whatever names you like for your initial high score table.
+
+Check the hint if you need help.
+
+```blocks
+let number_of_high_scores = 3
+let high_scores = [500, 300, 100]
+let high_score_names = ["Charlie", "Bravo", "Alfa"]
+```
+
+## When does it end!
+
+When the game ends, we need to check the high scores table and add an entry
+if needed. But how do we know when the game ends?
+
+In our current project, the game ends when the player runs out of lives.
+We have an event handler that can run when that happens.
+
+- From the ``||info:Info||`` drawer, add an
+``||info:on life zero||``
+container to your workspace.
+
+Any blocks that we add to this container will run when the player runs
+out of lives. Because we added this event handler, we need to remember
+to make the game end, because it will not end on its own now!
+
+## Show me!
+
+Before we end the game, let's display the high scores table.
+
+1. Create a new variable called **high scores text**.
+1. In your
+``||info(noclick):on life zero||``
+container,
+set the value of
+**high scores text** to the string
+**High scores\n**.
+ - Those special characters at the end ask MakeCode to put
+the characters on their own line.
+ - Remember that you can find an empty string block in the **Text** drawer
+of the toolbox.
+1. To your
+``||info(noclick):on life zero||``
+container, add a
+``||loops:for||`` ``||variables(loops):index||``
+``||loops:from 0 to (4)||``
+container. The
+``||variables(noclick):index||``
+variable will count through the
+indexes of your array.
+1. In place of the
+``||loops(noclick):(4)||`` value in your loop, use blocks
+to build the following expression:
+``||variables:number of high scores||``
+``||math:- (1)||``.
+Remember that computers start counting at zero, and the highest index
+for an array is one less than its size.
+1. In the
+``||loops(noclick):for||`` loop, build your high score message.
+Set the
+**high scores text** variable to join the following strings:
+ 1. ``||variables:high scores text||``
+ (In other words, you are adding to the existing value.)
+ 1. ``||variables(arrays):high score names||``
+ ``||arrays:get value at||`` ``||variables(arrays):index||``
+ 1. The string
+ **`: `**. (That's a colon followed by a space.)
+ 1. ``||variables(arrays):high scores||``
+ ``||arrays:get value at||`` ``||variables(arrays):index||``
+ 1. The string
+ **`\n`**. This special character
+ will put each high score on a separate line.
+1. To your
+``||info(noclick):on life zero||``
+container, display
+**high scores message** with a
+``||game:show long text||``
+block, and then end the game.
+
+Run your project to see if your empty high scores table appears before
+the game ends. Check the hint if you need help.
+
+```blocks
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let high_scores_message: string = ""
+info.onLifeZero(function () {
+ high_scores_message = "High scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+```
+
+## Complete! @showdialog
+
+Good work! We have created a high scores table and displayed it at the end
+of the game. Now, we can add scores to it! On to Part 2!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let projectile: Sprite = null
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+info.onLifeZero(function () {
+ high_scores_message = "High Scores\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let high_scores_message = ""
+let projectile: Sprite = null
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+number_of_high_scores = 3
+high_scores = [500, 300, 100]
+high_score_names = ["Charlie", "Bravo", "Alfa"]
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-3/s01-lab0304-part2.md b/docs/courses/csintro/unit-3/s01-lab0304-part2.md
new file mode 100644
index 00000000000..4915983ea88
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0304-part2.md
@@ -0,0 +1,369 @@
+# Lab 3.4 Part 2: High scores
+
+## My high score! @showdialog
+
+We've added a high scores table to our project and displayed it at the end
+of the game. Now, let's add the player's entry if they qualify!
+
+## Where does it go?
+
+Let's figure out where we need to insert our high score. To do that, we will
+go through the high scores table one entry at a time. If we find a score that
+is smaller, then we've found the right spot!
+
+1. **At the beginning** of your
+``||info(noclick):on life zero||``
+container, insert another
+``||loops:for||`` ``||variables(loops):index||``
+``||loops:from 0 to (4)||``
+container. Again, the
+``||variables(noclick):index||`` variable will count through the
+indexes of your array.
+1. Again, in place of the
+``||loops(noclick):(4)||`` value in your loop, use blocks
+to build the following expression:
+``||variables:number of high scores||`` ``||math:- (1)||``.
+Feel free to duplicate the blocks from your other
+``||loops(noclick):for||`` loop for that expression.
+1. Inside of this new
+``||loops(noclick):for||``
+loop, add an
+``||logic:if (true) then||``
+block.
+1. Replace the
+``||logic(noclick):(true)||`` value with blocks that build
+the following conditional statement:
+``||logic:if||`` ``||info:score||``
+``||logic:is greater than||`` ``||variables(arrays):high scores||``
+``||arrays:get value at||`` ``||variables(noclick):index||``
+ - Remember to drag the
+ ``||varibles(noclick):index||``
+ variable from the top of your
+ ``||loops(noclick):for||`` loop.
+ - If the editor has changed the name of the variable to something
+ like
+ ``||variables(noclick):index2||``,
+ then that's OK! Use whatever variable
+ is shown in the top of your
+ ``||loops(noclick):for||`` loop.
+ - The ``||info:score||`` value is in the ``||info:Info||`` drawer.
+
+Feel free to check the hint to verify your code. In the next step, we'll
+be able to test your logic!
+
+```blocks
+let high_scores: number[] = []
+let high_score_names: string[] = []
+let number_of_high_scores = 0
+let high_scores_message = ""
+info.onLifeZero(function () {
+ // @highlight
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+
+ }
+ }
+ high_scores_message = "High Scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+```
+
+## What's your name?
+
+If the player gets a high score, then we should collect their name.
+Let's do that now!
+
+1. Create a new variable called something like **playerName**.
+1. Inside of the empty
+``||logic(noclick):if||`` block that you just added
+to your code, prompt the player for their name and place the value
+in your new variable.
+
+Now, play your game and try to get a high score! If you do, then your project
+should ask you for your name.
+
+Check the hint if you need help.
+
+```blocks
+let high_scores: number[] = []
+let number_of_high_scores = 0
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ // @highlight
+ let playerName = game.askForString("High score! What is your name?")
+ }
+ }
+})
+```
+
+## Add the score
+
+Now, let's add the player's score to the table!
+
+After asking for the player's name, add the blocks described below.
+These blocks all go inside of the
+``||logic(noclick):if||`` container.
+
+1. ``||variables(arrays):high scores||`` ``||arrays:insert at||``
+``||variables(noclick):index||`` ``||arrays:value||``
+``||info:score||``.
+1. ``||variables(arrays):high score names||``
+``||arrays:insert at||``
+``||variables(noclick):index||`` ``||arrays:value||``
+``||variables:playerName||``.
+
+Again, play your game and try to get a high score! If you do, then your project
+should ask you for your name. When your project displays the high score table,
+your name and score should appear in the list!
+
+You may notice a problem, though. We'll fix that in the next step.
+
+Check the hint if you need help.
+
+```blocks
+let high_scores: number[] = []
+let high_score_names: string[] = []
+let number_of_high_scores = 0
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ }
+ }
+})
+```
+
+## Too many!
+
+Inserting those values into our arrays makes them too long. Let's get rid of the
+extra entries at the end of our arrays.
+
+Add the following blocks inside of your
+``||logic(noclick):if||`` container,
+beneath the ones that you already have there.
+
+1. ``||arrays:remove last value from||``
+``||variables(arrays):high scores||``
+1. ``||arrays:remove last value from||``
+``||variables(arrays):high score names||``
+1. ``||loops:break||``
+
+Remember that error that we mentioned in the previous step? Once we add
+the player's name and score to the arrays, then we are done. We don't need to
+continue with the rest of the entries in the array. The
+``||loops:break||``
+block jumps out of the
+``||loops:for||`` loop early.
+
+You can find that block in the ``||loops:Loops||`` drawer.
+
+Your new high score table should work correctly now!
+
+Check the hint if you need help.
+
+```blocks
+let high_scores: number[] = []
+let high_score_names: string[] = []
+let number_of_high_scores = 0
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ high_scores.pop()
+ high_score_names.pop()
+ break;
+ }
+ }
+})
+```
+
+## Complete! @showdialog
+
+Good work! We have made a high scores table that works!
+
+You might notice that, when you play the game multiple times,
+the high scores table resets after each play.
+
+In Part 3, we will learn how to save your high scores table!
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+info.onLifeZero(function () {
+ high_scores_message = "High Scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let high_scores_message = ""
+let projectile: Sprite = null
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+number_of_high_scores = 3
+high_scores = [500, 300, 100]
+high_score_names = ["Charlie", "Bravo", "Alfa"]
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ high_scores.pop()
+ high_score_names.pop()
+ break;
+ }
+ }
+ high_scores_message = "High Scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let high_scores_message = ""
+let playerName = ""
+let projectile: Sprite = null
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+number_of_high_scores = 3
+high_scores = [500, 300, 100]
+high_score_names = ["Charlie", "Bravo", "Alfa"]
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-3/s01-lab0304-part3.md b/docs/courses/csintro/unit-3/s01-lab0304-part3.md
new file mode 100644
index 00000000000..e983830228a
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0304-part3.md
@@ -0,0 +1,312 @@
+# Lab 3.4 Part 3: High scores
+
+## Don't lose it! @showdialog
+
+We've added a high scores table to our project, displayed it at the end
+of the game, and added the player's name if they qualify.
+
+Now, let's save the high scores table so that it doesn't get lost each
+time we play our game!
+
+## Where is the table?
+
+We've added an extension to your project, and you'll notice a new
+drawer in your toolbox called **BetterSettings**.
+
+In your ``||loops(noclick):on start||`` blocks, we simply create the **high scores**
+and **high score names** arrays and add some values to it.
+
+Instead, let's load the high score table if one has been saved.
+
+- First, find the two blocks where you create the
+**high scores** and
+**high score names** variables, and drag them off to the side. You'll use
+them again later.
+
+Now, at the bottom of your
+``||loops(noclick):on start||`` container, add the
+following blocks.
+
+1. Add an
+``||logic:if (true) then else||`` container and add blocks so that it reads
+as follows:
+``||logic:if||`` ``||blockSettings:setting with name ("high scores") exists||``
+``||logic:then||``
+1. In the
+``||logic(noclick):if||`` branch, add blocks that read as follows:
+``||variables:set (high scores) to||``
+``||blockSettings:read settings ("high scores") as number array||``
+1. In the
+``||logic:else||`` branch, place your block that you moved
+off to the side that
+creates the
+**high scores** array with your starting values.
+
+Do something similar for your **high score names** variable.
+
+*Watch your spelling and capitalization!*
+
+Check the hint if you need some help.
+
+```blocks
+if (blockSettings.exists("high scores")) {
+ let high_scores = blockSettings.readNumberArray("high scores")
+} else {
+ let high_scores = [500, 300, 100]
+}
+if (blockSettings.exists("high score names")) {
+ let high_score_names = blockSettings.readStringArray("high score names")
+} else {
+ let high_score_names = ["Charlie", "Bravo", "Alfa"]
+}
+```
+
+## Save it!
+
+We've loaded the high score table if one has been saved. Now, we need to save
+the high score table when we change it!
+
+In your
+``||info(noclick):on life zero||`` container, after we update the
+**high scores** and **high score names** variables
+and right before the
+``||loops:break||`` block,
+add the following blocks:
+
+- ``||blockSettings:set setting ("high scores") to number array||``
+``||variables:high scores||``
+- ``||blockSettings:set setting ("high score names") to string array||``
+``||variables:high score names||``
+
+Give it a try! Your high score table should save between plays!
+
+Check the hint if you need some help.
+
+```blocks
+let number_of_high_scores = 0
+let high_scores: number [] = []
+let high_score_names: string[] = []
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ let playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ high_scores.pop()
+ high_score_names.pop()
+ // @highlight
+ blockSettings.writeNumberArray("high scores", high_scores)
+ // @highlight
+ blockSettings.writeStringArray("high score names", high_score_names)
+ break;
+ }
+ }
+})
+```
+
+## Complete! @showdialog
+
+Good work! We have made a high scores table that works!
+
+Feel free to use this in any of your projects!
+
+If you would like to use **BetterSettings** or its cousin, simply called
+**Settings**, in your own projects, then ask your instructor.
+
+```template
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ high_scores.pop()
+ high_score_names.pop()
+ break;
+ }
+ }
+ high_scores_message = "High Scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let high_scores_message = ""
+let playerName = ""
+let projectile: Sprite = null
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+number_of_high_scores = 3
+high_scores = [500, 300, 100]
+high_score_names = ["Charlie", "Bravo", "Alfa"]
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```ghost
+controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
+ if (projectileCount < MAX_PROJECTILES) {
+ projectileCount += 1
+ projectile = sprites.create(sprites.food.smallApple, SpriteKind.Projectile)
+ projectile.setPosition(heroSprite.x, heroSprite.y)
+ projectile.setVelocity(0, -50)
+ projectile.setFlag(SpriteFlag.AutoDestroy, true)
+ }
+})
+info.onLifeZero(function () {
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ if (info.score() > high_scores[index]) {
+ playerName = game.askForString("High score! What is your name?")
+ high_scores.insertAt(index, info.score())
+ high_score_names.insertAt(index, playerName)
+ high_scores.pop()
+ high_score_names.pop()
+ blockSettings.writeNumberArray("high scores", high_scores)
+ blockSettings.writeStringArray("high score names", high_score_names)
+ break;
+ }
+ }
+ high_scores_message = "High Scores\\n"
+ for (let index = 0; index <= number_of_high_scores - 1; index++) {
+ high_scores_message = "" + high_scores_message + high_score_names[index] + ": " + high_scores[index] + "\\n"
+ }
+ game.showLongText(high_scores_message, DialogLayout.Center)
+ game.gameOver(false)
+})
+sprites.onCreated(SpriteKind.Projectile, function (sprite) {
+ music.play(music.melodyPlayable(music.pewPew), music.PlaybackMode.InBackground)
+})
+sprites.onDestroyed(SpriteKind.Projectile, function (sprite) {
+ projectileCount += -1
+})
+sprites.onOverlap(SpriteKind.Projectile, SpriteKind.Enemy, function (sprite, otherSprite) {
+ info.changeScoreBy(100)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+ sprites.destroy(sprite)
+ sprites.destroy(otherSprite, effects.coolRadial, 500)
+ if (enemyVelocity < MAX_ENEMY_VELOCITY) {
+ enemyVelocity += 1
+ }
+})
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, function (sprite, otherSprite) {
+ sprites.destroy(otherSprite)
+ info.changeLifeBy(-1)
+ music.play(music.melodyPlayable(music.smallCrash), music.PlaybackMode.InBackground)
+})
+let enemySprite: Sprite = null
+let high_scores_message = ""
+let playerName = ""
+let projectile: Sprite = null
+let high_score_names: string[] = []
+let high_scores: number[] = []
+let number_of_high_scores = 0
+let heroSprite: Sprite = null
+let projectileCount = 0
+let enemyVelocity = 0
+let MAX_ENEMY_VELOCITY = 0
+let MAX_PROJECTILES = 0
+let difficulty = game.askForNumber("Enter starting difficulty (1, 2, or 3)")
+if (difficulty == 1) {
+ MAX_PROJECTILES = 5
+ MAX_ENEMY_VELOCITY = 100
+ enemyVelocity = 10
+} else if (difficulty == 3) {
+ MAX_PROJECTILES = 2
+ MAX_ENEMY_VELOCITY = 200
+ enemyVelocity = 50
+} else {
+ MAX_PROJECTILES = 3
+ MAX_ENEMY_VELOCITY = 150
+ enemyVelocity = 25
+}
+projectileCount = 0
+heroSprite = sprites.create(sprites.food.smallTaco, SpriteKind.Player)
+heroSprite.setPosition(80, 100)
+controller.moveSprite(heroSprite)
+heroSprite.setStayInScreen(true)
+info.setScore(0)
+info.setLife(3)
+number_of_high_scores = 3
+if (blockSettings.exists("high scores")) {
+ high_scores = blockSettings.readNumberArray("high scores")
+} else {
+ high_scores = [500, 300, 100]
+}
+if (blockSettings.exists("high score names")) {
+ high_score_names = blockSettings.readStringArray("high score names")
+} else {
+ high_score_names = ["Charlie", "Bravo", "Alfa"]
+}
+game.onUpdateInterval(1000, function () {
+ enemySprite = sprites.create(sprites.food.smallBurger, SpriteKind.Enemy)
+ enemySprite.setPosition(randint(10, 150), -5)
+ enemySprite.setVelocity(0, enemyVelocity)
+ enemySprite.setFlag(SpriteFlag.AutoDestroy, true)
+})
+```
+
+```package
+betterSettings=github:sargedev/bettersettings
+```
\ No newline at end of file
diff --git a/docs/courses/csintro/unit-3/s01-lab0305-part1.md b/docs/courses/csintro/unit-3/s01-lab0305-part1.md
new file mode 100644
index 00000000000..fa8621c9b89
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0305-part1.md
@@ -0,0 +1,416 @@
+# Lab 3.5 Part 1: Animated sprites
+
+## Starting difficulty @showdialog
+
+In this lab, you'll learn different ways to create animated sprites!
+
+Have you created a flipbook? Flipbooks have images on each page. If you
+flip through the pages quickly, then it looks like the image is moving!
+
+This is the style of animation that MakeCode Arcade uses.
+It is called *frame-based animation*.
+![An example of a flipbook.](https://alex-kulcsar.github.io/introcs-tutorials/assets/images/S01.L03.05.P01.flipbook.jpg)
+Image credit: John Barnes Linnett. Public domain.
+
+## I like to do things the hard way!
+
+Let's manually animate a sprite. We'll learn some automated ways later,
+but sometimes, you will need to animate a sprite yourself.
+
+1. Create a new variable that will hold the frames (images) of your
+animation. Name your variable something like **frameList**.
+1. Set your new variable to an array of images.
+ 1. Drag a
+ ``||variables:set (frameList) to (0)||``
+ block into your
+ ``||loops(noclick):on start||`` block.
+ 1. Drag an
+ ``||arrays:empty array||``
+ block into the block for your
+ variable. This changes the variable into an array.
+ 1. Select the **(+)** sign to add an element to your array.
+ Notice that the new element is blank.
+ 1. Drag an empty image from the
+ ``||images:Images||``
+ **Images**
+ drawer into the blank
+ space in your array. The
+ ``||images:Images||``
+ **Images**
+ drawer is in the
+ **Advanced** section of the toolbox.
+ Your array now is an array of images!
+1. Edit the new image in your array. Choose a bright color and fill in
+a single pixel near the center of the image.
+1. Duplicate the image block and add it as a new element in the array.
+1. Edit the new image and add a couple of pixels to each side of the
+single pixel, turning it into a line.
+1. Repeat a couple of times, extending the line a few pixels in each frame.
+1. Repeat a few more times, this time removing a few pixels from the line
+until it returns to a single pixel.
+
+Check the hint if you need any help.
+
+```blocks
+let frameList = [
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 6 . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . 6 6 6 6 6 . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . 6 6 6 6 6 6 6 6 6 . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . 6 6 6 6 6 6 6 6 6 6 6 6 6 . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . 6 6 6 6 6 6 6 6 6 . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . 6 6 6 6 6 . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 6 . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `
+]
+```
+
+## Where to hang these new images?
+
+Now, we need a place for our images. Let's create a sprite, and then let's
+keep track of which frame in our array we're viewing.
+
+1. Add a block to your
+``||loops(noclick):on start||``
+container that creates a
+sprite.
+ - Give the sprite variable an appropriate name.
+ - It does not need an image. We'll take care of that later.
+1. Create a new variable to store your
+**frameNumber**.
+1. Add a block to
+``||loops(noclick):on start||`` that sets the
+frame number to
+**zero**.
+
+Check the hint if you need any help.
+
+```blocks
+let animatedSprite = sprites.create(img`
+ .
+ `, SpriteKind.Player)
+let frameNumber = 0
+```
+
+## Time to animate!
+
+Now, let's animate your sprite!
+
+1. Add an
+``||game:on game update every (500) ms||``
+container to your
+workspace.
+1. Add these blocks to your new container:
+ - Use blocks from the
+ ``||sprites:Sprites||`` and the
+ ``||arrays:Arrays||`` drawer to set the sprite's image to the
+ current frame in the array.
+ - Increment the current frame number.
+ - If the current frame number is too big, then reset it to zero.
+
+Run your project -- your sprite should be animated! The animation should
+repeat without errors.
+
+Try different values in the
+``||game:on game update every (500) ms||``
+container to speed up or slow down the animation.
+
+Check the hint if you need any help.
+
+```blocks
+let frameNumber = 0
+let frameList: Image[] = []
+let animatedSprite: Sprite = null
+game.onUpdateInterval(500, function () {
+ animatedSprite.setImage(frameList[frameNumber])
+ frameNumber += 1
+ if (frameNumber >= frameList.length) {
+ frameNumber = 0
+ }
+})
+```
+
+## Conclusion @showdialog
+
+Congratulations! You've manually animated a sprite!
+
+Try some other animations of your own creation.
+
+There are several frame-based animations available in the gallery, too.
+Be sure to take a look at those!
+
+Have fun!
+
+```ghost
+let frameList = [
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 6 . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . 6 6 6 6 6 . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . 6 6 6 6 6 6 6 6 6 . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . 6 6 6 6 6 6 6 6 6 6 6 6 6 . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . 6 6 6 6 6 6 6 6 6 . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . 6 6 6 6 6 . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,
+img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . 6 . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `
+]
+let animatedSprite = sprites.create(img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `, SpriteKind.Player)
+let frameNumber = 0
+game.onUpdateInterval(500, function () {
+ animatedSprite.setImage(frameList[frameNumber])
+ frameNumber += 1
+ if (frameNumber >= frameList.length) {
+ frameNumber = 0
+ }
+})
+```
diff --git a/docs/courses/csintro/unit-3/s01-lab0305-part2.md b/docs/courses/csintro/unit-3/s01-lab0305-part2.md
new file mode 100644
index 00000000000..731df530dcf
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0305-part2.md
@@ -0,0 +1,154 @@
+# Lab 3.5 Part 2: Animated sprites
+
+## Built-in animations! @showdialog
+
+In the previous tutorial, you learned how to manually animate a sprite.
+In this tutorial, you will use a built-in block to do the same thing.
+
+## Two simple steps!
+
+To create an animated sprite using the built-in blocks takes just two
+simple steps!
+
+1. In your
+``||loops(noclick):on start||``
+container, create a new sprite.
+ - Name your sprite anything you like.
+ - As before, don't worry about setting an image. We will do that
+ next.
+1. Open the
+**Advanced**
+section of the toolbox. From the
+``||animation:Animation||``
+drawer, add an
+``||animation:animate||`` ``||variables(animation):mySprite||``
+``||animation:frames [ ] interval (ms) (500) loop (OFF)||``
+block.
+ - Set the variable name to your sprite's variable name.
+ - Select the blank animation. (It's the block next to the word
+ *frames*.) Switch to the gallery and select any of the built-in
+ animations.
+ - Notice that, in this block, you also can set the speed of the
+ animation and whether the animation repeats.
+
+That's it! Give it a try! Check the hint if you need help.
+
+```blocks
+let animatedSprite = sprites.create(img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `, SpriteKind.Player)
+animation.runImageAnimation(
+animatedSprite,
+[img`
+ . . f f f . . . . . . . . f f f
+ . f f c c . . . . . . f c b b c
+ f f c c . . . . . . f c b b c .
+ f c f c . . . . . . f b c c c .
+ f f f c c . c c . f c b b c c .
+ f f c 3 c c 3 c c f b c b b c .
+ f f b 3 b c 3 b c f b c c b c .
+ . c b b b b b b c b b c c c . .
+ . c 1 b b b 1 b b c c c c . . .
+ c b b b b b b b b b c c . . . .
+ c b c b b b c b b b b f . . . .
+ f b 1 f f f 1 b b b b f c . . .
+ f b b b b b b b b b b f c c . .
+ . f b b b b b b b b c f . . . .
+ . . f b b b b b b c f . . . . .
+ . . . f f f f f f f . . . . . .
+ `,img`
+ . . f f f . . . . . . . . . . .
+ f f f c c . . . . . . . . f f f
+ f f c c . . c c . . . f c b b c
+ f f c 3 c c 3 c c f f b b b c .
+ f f b 3 b c 3 b c f b b c c c .
+ . c b b b b b b c f b c b c c .
+ . c b b b b b b c b b c b b c .
+ c b 1 b b b 1 b b b c c c b c .
+ c b b b b b b b b c c c c c . .
+ f b c b b b c b b b b f c . . .
+ f b 1 f f f 1 b b b b f c c . .
+ . f b b b b b b b b c f . . . .
+ . . f b b b b b b c f . . . . .
+ . . . f f f f f f f . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `,img`
+ . . . . . . . . . . . . . . . .
+ . . c c . . c c . . . . . . . .
+ . . c 3 c c 3 c c c . . . . . .
+ . c b 3 b c 3 b c c c . . . . .
+ . c b b b b b b b b f f . . . .
+ c c b b b b b b b b f f . . . .
+ c b 1 b b b 1 b b c f f f . . .
+ c b b b b b b b b f f f f . . .
+ f b c b b b c b c c b b b . . .
+ f b 1 f f f 1 b f c c c c . . .
+ . f b b b b b b f b b c c . . .
+ c c f b b b b b c c b b c . . .
+ c c c f f f f f f c c b b c . .
+ . c c c . . . . . . c c c c c .
+ . . c c c . . . . . . . c c c c
+ . . . . . . . . . . . . . . . .
+ `,img`
+ . f f f . . . . . . . . f f f .
+ f f c . . . . . . . f c b b c .
+ f c c . . . . . . f c b b c . .
+ c f . . . . . . . f b c c c . .
+ c f f . . . . . f f b b c c . .
+ f f f c c . c c f b c b b c . .
+ f f f c c c c c f b c c b c . .
+ . f c 3 c c 3 b c b c c c . . .
+ . c b 3 b c 3 b b c c c c . . .
+ c c b b b b b b b b c c . . . .
+ c b 1 b b b 1 b b b b f c . . .
+ f b b b b b b b b b b f c c . .
+ f b c b b b c b b b b f . . . .
+ . f 1 f f f 1 b b b c f . . . .
+ . . f b b b b b b c f . . . . .
+ . . . f f f f f f f . . . . . .
+ `],
+500,
+true
+)
+```
+
+## Change that animation!
+
+Select the animation again to open the animation editor. Take a look around.
+
+- Notice that it looks just like the image editor. After all, each frame
+in an animation is just an image!
+- Notice the extra toolbar on the right side of the screen. That is the
+*frame list*. You can change the order of the frames, add frames, and delete
+frames with this tool.
+
+Give the animation editor a try! Change some of the frames of the animation,
+click **Done** (just like in the image editor), and then watch your new
+animation in the simulator.
+
+## Conclusion @showdialog
+
+Congratulations! You've animated a sprite using the built-in block!
+You also learned how to use the animation editor.
+
+Try some other animations of your own creation.
+
+In Part 3, you'll try something a bit more challenging.
+
+Have fun!
diff --git a/docs/courses/csintro/unit-3/s01-lab0305-part3.md b/docs/courses/csintro/unit-3/s01-lab0305-part3.md
new file mode 100644
index 00000000000..fa6c93a167d
--- /dev/null
+++ b/docs/courses/csintro/unit-3/s01-lab0305-part3.md
@@ -0,0 +1,119 @@
+# Lab 3.5 Part 3: Animated sprites
+
+## What a great character! @showdialog
+
+In the previous tutorial, you learned use a built-in block to animate a sprite.
+
+In this tutorial, we will use an extension to do some more complex actions!
+
+## One sprite to start!
+
+First, we need a hero sprite for our project.
+
+1. In your
+``||loops(noclick):on start||``
+container, add blocks that
+do the following:
+ - Create a hero sprite for your player.
+ - Give the sprite an appropriate image and variable name.
+ - Let the player control the hero sprite using the d-pad.
+ - Keep the hero sprite on the screen.
+
+Check the hint if you need any help.
+
+```blocks
+let movingSprite = sprites.create(sprites.builtin.forestMonkey0, SpriteKind.Player)
+controller.moveSprite(movingSprite)
+movingSprite.setStayInScreen(true)
+```
+
+## Show some character!
+
+We've added an extension for this tutorial. Notice the new
+``||characterAnimations:Character||`` drawer in your toolbox.
+
+1. Add a
+``||variables(characterAnimations):mySprite||``
+``||characterAnimations:loop frames [ ] (500) when (not moving)||``
+block to the bottom of your
+``||loops(noclick):on start||``
+container.
+1. Change the variable name to your hero sprite's variable name.
+
+Give this new block a try! Use the built-in images and animations in the
+gallery to learn about this new block.
+
+Can you make your hero sprite animate appropriately when it is moving
+in any of the four directions on the screen?
+
+```blocks
+let movingSprite = sprites.create(sprites.builtin.forestMonkey0, SpriteKind.Player)
+controller.moveSprite(movingSprite)
+movingSprite.setStayInScreen(true)
+characterAnimations.loopFrames(
+movingSprite,
+[img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `],
+500,
+characterAnimations.rule(Predicate.NotMoving)
+)
+```
+
+## Conclusion @showdialog
+
+Congratulations! You've used the **Character** extension to do some complex
+animations with sprites! If you want to use the **Character** extension in
+your own projects, then check with your instructor.
+
+Try some other animations of your own creation.
+
+Have fun!
+
+```ghost
+let movingSprite = sprites.create(sprites.builtin.forestMonkey0, SpriteKind.Player)
+controller.moveSprite(movingSprite)
+movingSprite.setStayInScreen(true)
+characterAnimations.loopFrames(
+movingSprite,
+[img`
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ . . . . . . . . . . . . . . . .
+ `],
+500,
+characterAnimations.rule(Predicate.NotMoving)
+)
+```
+
+```package
+characterAnimations=github:microsoft/arcade-character-animations
+```
\ No newline at end of file
diff --git a/docs/courses/csintro_archive.md b/docs/courses/csintro_archive.md
new file mode 100644
index 00000000000..f1177af0f11
--- /dev/null
+++ b/docs/courses/csintro_archive.md
@@ -0,0 +1,57 @@
+# CS Intro
+
+A collection of courses meant to teach introductory programmers using Blocks and JavaScript
+
+### ~hint
+
+These courses are currently in beta - this means that they are likely to have bugs and changes made fairly regularly. If you see anything that doesn't seem quite right, or if you have any suggestions, please file an issue on [github](https://github.com/microsoft/pxt-arcade).
+
+### ~
+
+## Courses on Flipgrid
+
+Flipcode for the **Intro to CS** course grid: **[csintroarcade](https://flipgrid.com/csintroarcade)**
+
+## Course Sections
+
+```codecard
+[
+ {
+ "name": "CS Intro 1",
+ "description": "A semester length computer science course for beginning coders",
+ "url":"/courses/csintro1",
+ "imageUrl": "/static/courses/csintro1.gif"
+ }, {
+ "name": "CS Intro 2",
+ "description": "A continuation of the Intro to CS course",
+ "url":"/courses/csintro2",
+ "imageUrl": "/static/courses/csintro2.gif"
+ }, {
+ "name": "CS Intro 3",
+ "description": "A quarter length transition from blocks to JavaScript",
+ "url": "/courses/csintro3",
+ "imageUrl": "/static/courses/csintro3.gif"
+ }
+]
+```
+
+## About the CS Intro Series
+
+The CS Intro Series is designed to teach new developers how to code from the ground up.
+
+In CS Intro 1, students are introduced to programming through the MakeCode Blocks editor. They can create their own games, while learning concepts that are crucial to software development: creating variables, responding to events, and using iteration to simplify and extend their programs. Throughout the course, they will learn to develop their own games through small daily tasks, as well as projects that guide them through the process of turning basic ideas into full-fledged games.
+
+In CS Intro 2, students will continue to develop the software development skills they were introduced to in the first course, with more advanced programming concepts such as functions, logical comparisons, and arrays. These new skills will allow students to create more advanced and compelling games.
+
+### ~hint
+
+In the future CS Intro 3 and CS Intro 4 courses, students will transition the skills they have learned in a Block based environment into skills in a text based coding environment, allowing them to dig deeper into the games they make, as well as transition their skills in the @boardname@ into other environments.
+
+### ~
+
+## See also
+
+[Courses Home Page](/courses),
+[CS Intro 1](/courses/csintro1),
+[CS Intro 2](/courses/csintro2),
+[CS Intro 3](/courses/csintro3)
\ No newline at end of file
diff --git a/docs/static/hero-gallery/hubble_banner.jpg b/docs/static/hero-gallery/hubble_banner.jpg
new file mode 100644
index 00000000000..ae081bde2ac
Binary files /dev/null and b/docs/static/hero-gallery/hubble_banner.jpg differ
diff --git a/docs/static/tutorials/hubble/hubble_4x3.jpg b/docs/static/tutorials/hubble/hubble_4x3.jpg
new file mode 100644
index 00000000000..23f33b183d0
Binary files /dev/null and b/docs/static/tutorials/hubble/hubble_4x3.jpg differ
diff --git a/docs/test/tutorials/rockscout.md b/docs/test/tutorials/rockscout.md
index 281bf1e6ad5..c769dab8d49 100644
--- a/docs/test/tutorials/rockscout.md
+++ b/docs/test/tutorials/rockscout.md
@@ -77,14 +77,24 @@ let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
---
-You might notice that you can design your own sprite image (aka avatar) if you click on the rockscout already in the workspace.
+You might notice that you can design your own sprite image (aka avatar) by clicking on the rockscout already in the workspace.
While it might look like you're drawing with pixels, what you are really doing is
-creating an array of numbers that tells the computer what your avatar should look like.
+creating an array of symbols that tells the computer what your avatar should look like.
![Look at your array](/static/skillmap/rockstar/rockscout-imgs-array.gif "Your sprite image is an array of hexadecimal digits.")
+~hint What's an array? 💡
+
+---
+
+In Arcade, an **Array** is an ordered list of items.
+
+In this activity, you won't see the array of image symbols until the very end, but they're
+
+
+hint~
diff --git a/docs/test/tutorials/stackem_smurfs.md b/docs/test/tutorials/stackem_smurfs.md
new file mode 100644
index 00000000000..7417638ca64
--- /dev/null
+++ b/docs/test/tutorials/stackem_smurfs.md
@@ -0,0 +1,567 @@
+# Stack'em Smurfs
+### @explicitHints true
+
+
+## Introduction @showdialog
+
+![Game animation](/static/tutorials/chase-the-pizza/chasing.gif)
+
+Create a game where the goal is to eat as much pizza as you can
+before the time runs out!
+
+
+## {Step 2}
+
+**Set the background color**
+
+---
+
+- :tree: Open the
+``||scene:Scene||``
+toolbox drawer and drag
+``||scene:set background color [ ]||``
+into **the empty** ``||loops(noclick):on start||`` container already in your workspace.
+
+~hint What does that mean? 🤷🏽
+
+---
+
+When giving instructions, we'll highlight some text to give you a better idea of what you are looking for.
+
+For example, when we suggest the
+``||scene:set background color to [ ]||``
+block, we are pointing you toward
+
+```block
+scene.setBackgroundColor(13)
+```
+
+hint~
+
+💡 _Feel free to choose your own color if you don't like the swatch in the block._
+
+
+---
+
+- :mouse pointer: Click the button that says **Next** to go to the
+next step of the tutorial.
+
+
+#### ~ tutorialhint
+```blocks
+// @highlight
+scene.setBackgroundColor(13)
+```
+
+
+## {Step 3}
+
+Add a player **sprite**.
+
+---
+
+- :paper plane: Open the ``||sprites:Sprites||`` drawer and drag
+``||variables(sprites):set [mySprite] to sprite [ ] of kind [Player]||``
+into **the end of** the ``||loops(noclick):on start||`` block already in your workspace.
+
+---
+
+
+~hint What's a sprite? 💡
+
+---
+
+In Arcade, each character or image that does something is called a **SPRITE**.
+
+Sprites have properties that you can use and change —
+things like scale, position, and lifespan are all properties of sprites.
+
+Our player will be a sprite, too.
+
+hint~
+
+
+~hint Show me 🔍
+
+![Add a sprite block](/static/tutorials/chase-the-pizza/mySprite.gif)
+
+hint~
+
+
+#### ~ tutorialhint
+```blocks
+let mySprite: Sprite = null
+scene.setBackgroundColor(13)
+// @highlight
+mySprite = sprites.create(img`.`, SpriteKind.Player)
+```
+
+## {Step 4}
+
+- :mouse pointer: Draw your sprite by clicking on the empty grey square in the
+``||variables(sprites):set [mySprite] to sprite [ ] of kind [Player]||``
+block to open the **Sprite Editor**.
+
+
+- :mouse pointer: Click **Done** when you are finished drawing.
+
+~hint Show me 🔍
+
+![Image editor](/static/tutorials/chase-the-pizza/draw.gif)
+
+hint~
+
+
+
+#### ~ tutorialhint
+```blocks
+let mySprite: Sprite = null
+scene.setBackgroundColor(13)
+// @highlight
+mySprite = sprites.create(img`
+. . . . . 5 5 5 5 5 5 5 . . . .
+. . . 5 5 5 5 5 5 5 5 5 5 5 . .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+. 5 5 5 5 f 5 5 5 5 f 5 5 5 5 5
+. 5 5 5 5 f f 5 5 5 f f 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 f f f f f f f f f 5 5 5
+. 5 5 5 5 f b b b b b f 5 5 5 5
+. . 5 5 5 5 f b b b f 5 5 5 5 .
+. . 5 5 5 5 5 f f f 5 5 5 5 5 .
+. . . 5 5 5 5 5 5 5 5 5 5 5 . .
+. . . . . 5 5 5 5 5 5 5 . . . .
+. . . . . . . . . . . . . . . .
+`, SpriteKind.Player)
+```
+
+
+## {Step 5}
+
+**Make the sprite move**
+
+---
+
+- :game: Open ``||controller:Controller||`` and drag
+``||controller:move [mySprite] with buttons||``
+into **the end of** the
+``||loops(noclick):on start||`` block already in your workspace.
+
+Now you can move your sprite around the screen using the arrow buttons on the game pad or your keyboard.
+
+
+~hint Show me 🔍
+
+![Add the move block](/static/tutorials/chase-the-pizza/move.gif)
+
+hint~
+
+
+#### ~ tutorialhint
+```blocks
+let mySprite: Sprite = null
+scene.setBackgroundColor(13)
+mySprite = sprites.create(img`
+. . . . . 5 5 5 5 5 5 5 . . . .
+. . . 5 5 5 5 5 5 5 5 5 5 5 . .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+. 5 5 5 5 f 5 5 5 5 f 5 5 5 5 5
+. 5 5 5 5 f f 5 5 5 f f 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+. 5 5 5 f f f f f f f f f 5 5 5
+. 5 5 5 5 f b b b b b f 5 5 5 5
+. . 5 5 5 5 f b b b f 5 5 5 5 .
+. . 5 5 5 5 5 f f f 5 5 5 5 5 .
+. . . 5 5 5 5 5 5 5 5 5 5 5 . .
+. . . . . 5 5 5 5 5 5 5 . . . .
+. . . . . . . . . . . . . . . .
+`, SpriteKind.Player)
+// @highlight
+controller.moveSprite(mySprite)
+```
+
+
+
+
+## {Step 6}
+
+
+- :binoculars: Test your project in the game window!
+
+You should be able to move your sprite with the joypad or arrow keys on your keyboard.
+
+
+![Look for the game window in the lower right](/static/tutorials/chase-the-pizza/game.png)
+
+
+
+
+
+
+
+## {Step 7}
+
+**Add some pizza**
+
+---
+
+- :paper plane: Open ``||sprites:Sprites||`` and drag
+``||variables(sprites):set [pizza] to sprite [ ] of kind [Player]||``
+into **the end of** the
+``||loops(noclick):on start||`` block already in your workspace.
+
+
+- :mouse pointer: Click **Player** in
+``||variables(noclick):set [pizza] to sprite [ ] of kind [Player]||``
+and choose **Food** instead.
+
+---
+
+~hint Show me 🔍
+
+![Change the pizza to food](/static/tutorials/chase-the-pizza/food.gif)
+
+hint~
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+
+#### ~ tutorialhint
+```blocks
+let mySprite: Sprite = null
+let pizza: Sprite = null
+scene.setBackgroundColor(13)
+mySprite = sprites.create(img`
+. . . . 5 5 5 5 5 5 5 . . . . .
+. . 5 5 5 5 5 5 5 5 5 5 5 . . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+5 5 5 5 f 5 5 5 5 f 5 5 5 5 5 .
+5 5 5 5 f f 5 5 5 f f 5 5 5 5 .
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+5 5 5 f f f f f f f f f 5 5 5 .
+5 5 5 5 f b b b b b f 5 5 5 5 .
+5 5 5 5 5 f b b b f 5 5 5 5 5 .
+. 5 5 5 5 5 f f f 5 5 5 5 5 . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. . 5 5 5 5 5 5 5 5 5 5 5 . . .
+. . . . 5 5 5 5 5 5 5 . . . . .
+. . . . . . . . . . . . . . . .
+`, SpriteKind.Player)
+controller.moveSprite(mySprite)
+// @highlight
+pizza = sprites.create(img`.`, SpriteKind.Food)
+```
+
+
+## {Step 8}
+
+
+- :mouse pointer: Choose your pizza by clicking the empty grey square inside
+``||variables(noclick):set [pizza] to sprite [ ] of kind [Food]||``
+to open the **Sprite Editor**.
+
+- :mouse pointer: Switch to the **Gallery** tab at the top.
+![Select the gallery](/static/skillmap/assets/gallery.png)
+
+
+- :mouse pointer: Choose your pizza, then click **Done**.
+
+~hint Show me 🔍
+
+![Image gallery](/static/tutorials/chase-the-pizza/gallery.gif)
+
+hint~
+
+
+💡 _Feel free to draw your own pizza if you prefer!_
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+
+#### ~ tutorialhint
+```blocks
+let pizza: Sprite = null
+let mySprite: Sprite = null
+scene.setBackgroundColor(13)
+mySprite = sprites.create(img`
+. . . . 5 5 5 5 5 5 5 . . . . .
+. . 5 5 5 5 5 5 5 5 5 5 5 . . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+5 5 5 5 f 5 5 5 5 f 5 5 5 5 5 .
+5 5 5 5 f f 5 5 5 f f 5 5 5 5 .
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+5 5 5 f f f f f f f f f 5 5 5 .
+5 5 5 5 f b b b b b f 5 5 5 5 .
+5 5 5 5 5 f b b b f 5 5 5 5 5 .
+. 5 5 5 5 5 f f f 5 5 5 5 5 . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. . 5 5 5 5 5 5 5 5 5 5 5 . . .
+. . . . 5 5 5 5 5 5 5 . . . . .
+. . . . . . . . . . . . . . . .
+`, SpriteKind.Player)
+controller.moveSprite(mySprite)
+pizza = sprites.create(img`
+. . . . . . b b b b . . . . . .
+. . . . . . b 4 4 4 b . . . . .
+. . . . . . b b 4 4 4 b . . . .
+. . . . . b 4 b b b 4 4 b . . .
+. . . . b d 5 5 5 4 b 4 4 b . .
+. . . . b 3 2 3 5 5 4 e 4 4 b .
+. . . b d 2 2 2 5 7 5 4 e 4 4 e
+. . . b 5 3 2 3 5 5 5 5 e e e e
+. . b d 7 5 5 5 3 2 3 5 5 e e e
+. . b 5 5 5 5 5 2 2 2 5 5 d e e
+. b 3 2 3 5 7 5 3 2 3 5 d d e 4
+. b 2 2 2 5 5 5 5 5 5 d d e 4 .
+b d 3 2 d 5 5 5 d d d 4 4 . . .
+b 5 5 5 5 d d 4 4 4 4 . . . . .
+4 d d d 4 4 4 . . . . . . . . .
+4 4 4 4 . . . . . . . . . . . .
+`, SpriteKind.Food)
+```
+
+
+
+## {Step 9}
+
+**Make something happen when the sprites overlap!**
+
+---
+
+- :paper plane: Open ``||sprites:Sprites||`` and drag the
+``||sprites:on [sprite] of kind [Player] overlaps [otherSprite] of kind [Food]||``
+container into **an empty area** of the workspace.
+
+
+🤷🏽♀️ _Need help? Click the lightbulb in the circle below to see what blocks you need in this step._
+
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+
+#### ~ tutorialhint
+```blocks
+// @highlight
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+
+})
+```
+
+
+
+## {Step 10}
+
+**Add a point when the sprites overlap**
+
+---
+
+- :id card: Open ``||info:Info||`` and drag
+``||info:change score by [1]||``
+into **the empty**
+``||sprites(noclick):on [sprite] ... overlaps [otherSprite]||``
+container already in the workspace.
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+#### ~ tutorialhint
+```blocks
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ // @highlight
+ info.changeScoreBy(1)
+})
+```
+
+
+
+
+
+## {Step 11}
+
+
+- :binoculars: Check your game!
+
+Notice that you get WAAAYYYYY too many points when your player
+sprite overlaps the pizza?
+
+We'll fix that in the next step.
+
+
+
+
+## {Step 12}
+
+**Teleport the pizza to a random location each time the sprites overlap.**
+
+~hint What is random? 🤷🏽♀️
+
+---
+
+A "random" number is a value that you can't predict ahead of time.
+
+In Arcade, we use this block:
+
+```block
+randint(0, scene.screenWidth())
+```
+
+to ask for a random number between **0** and the **width of the screen**.
+
+hint~
+
+---
+
+- :paper plane: Open ``||sprites:Sprites||``, and drag
+``||sprites:set [pizza] position to...||``
+into the **end of the**
+``||sprites(noclick):on [sprite] ... overlaps [otherSprite]||``
+container already in the workspace.
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+#### ~ tutorialhint
+```blocks
+let pizza: Sprite = null
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ // @highlight
+ pizza.setPosition(randint(0, scene.screenWidth()), randint(0, scene.screenHeight()))
+})
+```
+
+
+
+
+## {Step 13}
+
+**Let’s start a countdown each time the sprites overlap.**
+
+---
+
+- :id card: From ``||info:Info||``, drag
+``||info:start countdown [3] (s)||``
+into the **end of the**
+``||sprites(noclick):on [sprite] ... overlaps [otherSprite]||``
+container already in the workspace.
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+#### ~ tutorialhint
+```blocks
+let pizza: Sprite = null
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ pizza.setPosition(randint(0, scene.screenWidth()), randint(0, scene.screenHeight()))
+ // @highlight
+ info.startCountdown(3)
+})
+```
+
+
+## {Finale}
+
+**🎉 Great job! 🎉**
+
+You've made a **Chase the Pizza** game.
+
+Try playing your game. How many points can you get before time runs out?
+
+When you're finished playing, click **Done** to share your game with family and friends!
+
+
+
+```blockconfig.local
+let pizza = sprites.create(img`.`, SpriteKind.Player)
+```
+
+#### ~ tutorialhint
+```blocks
+let pizza: Sprite = null
+let mySprite: Sprite = null
+scene.setBackgroundColor(13)
+mySprite = sprites.create(img`
+. . . . . 5 5 5 5 5 5 . . . . .
+. . . 5 5 5 5 5 5 5 5 5 5 . . .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. 5 5 5 5 5 5 5 5 5 5 5 5 5 5 .
+. 5 5 5 f f 5 5 5 5 f f 5 5 5 .
+5 5 5 5 f f 5 5 5 5 f f 5 5 5 5
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+5 5 f 5 5 5 5 5 5 5 5 5 5 f 5 5
+5 5 5 f 5 5 5 5 5 5 5 5 f 5 5 5
+. 5 5 5 f 5 5 5 5 5 5 f 5 5 5 .
+. 5 5 5 5 f f f f f f 5 5 5 5 .
+. . 5 5 5 5 5 5 5 5 5 5 5 5 . .
+. . . 5 5 5 5 5 5 5 5 5 5 . . .
+. . . . . 5 5 5 5 5 5 . . . . .
+`, SpriteKind.Player)
+controller.moveSprite(mySprite)
+pizza = sprites.create(img`
+. . . . . . b b b b . . . . . .
+. . . . . . b 4 4 4 b . . . . .
+. . . . . . b b 4 4 4 b . . . .
+. . . . . b 4 b b b 4 4 b . . .
+. . . . b d 5 5 5 4 b 4 4 b . .
+. . . . b 3 2 3 5 5 4 e 4 4 b .
+. . . b d 2 2 2 5 7 5 4 e 4 4 e
+. . . b 5 3 2 3 5 5 5 5 e e e e
+. . b d 7 5 5 5 3 2 3 5 5 e e e
+. . b 5 5 5 5 5 2 2 2 5 5 d e e
+. b 3 2 3 5 7 5 3 2 3 5 d d e 4
+. b 2 2 2 5 5 5 5 5 5 d d e 4 .
+b d 3 2 d 5 5 5 d d d 4 4 . . .
+b 5 5 5 5 d d 4 4 4 4 . . . . .
+4 d d d 4 4 4 . . . . . . . . .
+4 4 4 4 . . . . . . . . . . . .
+`, SpriteKind.Food)
+
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {
+ info.changeScoreBy(1)
+ pizza.setPosition(randint(0, scene.screenWidth()), randint(0, scene.screenHeight()))
+ info.startCountdown(3)
+})
+```
+
+
+```blockconfig.global
+let pizza: Sprite = null
+
+scene.setBackgroundColor(13)
+sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite, otherSprite) {})
+randint(0, scene.screenWidth())
+pizza.setPosition(randint(0, scene.screenWidth()), randint(0, scene.screenHeight()))
+info.startCountdown(3)
+
+```
+
+```package
+chase-the-pizza=github:kiki-lee/chase-the-pizza
+```
\ No newline at end of file
diff --git a/docs/tutorials/rockscout.md b/docs/tutorials/rockscout.md
new file mode 100644
index 00000000000..4ecdbaad49f
--- /dev/null
+++ b/docs/tutorials/rockscout.md
@@ -0,0 +1,540 @@
+# Florida Rockscout
+### @explicitHints true
+
+
+## {Welcome @showdialog}
+
+Congratulations! You've just accepted a job managing Terri, the last remaining **Florida Rockscout!**
+
+![Navigate the maze backstage](/static/skillmap/rockstar/rockscout.png "Look what we're about to do today!")
+
+We'll walk you through the skills you'll need to help Terri navigate backstage to collect equipment from the show while avoiding screaming fans!
+
+To win, you need to get to the exit before time runs out.
+
+
+
+
+## {2. We need a HERO}
+
+**👏 We need a sprite!**
+
+---
+
+- :paper plane: From the ``||sprites:Sprites||`` category, drag
+``||variables(sprites):set [rockscout] to sprite [ ] of kind [Player]||``
+into **the empty**
+``||loops(noclick):on start||``
+container that's already in the workspace.
+
+
+~hint What does that mean? 🤷🏽
+
+---
+
+When giving instructions, we'll highlight some text to give you a better idea of
+what you are looking for.
+
+For example, when we suggest the
+``||variables(sprites):set [rockscout] to sprite [ ] of kind [Player]||``
+block, we are pointing you toward
+
+```block
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+```
+
+which is located in the ``||sprites:Sprites||`` category in the toolbox.
+
+hint~
+
+
+
+~hint What's a sprite? 💡
+
+---
+
+In Arcade, each character or image that does something is called a **SPRITE**.
+
+Sprites have properties that you can use and change — things like scale, position, and lifespan are all properties of sprites.
+
+Our rockscout will be a sprite, too.
+
+hint~
+
+
+
+
+#### ~ tutorialhint
+```blocks
+// @highlight
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+```
+
+
+
+
+## {3. Drawing an Avatar}
+
+**Designing an Avatar**
+
+---
+
+You might notice that you can design your own sprite image (aka avatar) by clicking on the rockscout already in the workspace.
+
+While it might look like you're drawing with pixels, what you are really doing is
+creating an array of symbols that tells the computer what your avatar should look like.
+
+![Look at your array](/static/skillmap/rockstar/rockscout-imgs-array.gif "Your sprite image is an array of hexadecimal digits.")
+
+
+~hint What's an array? 💡
+
+---
+
+In Arcade, an **Array** is an ordered list of items.
+
+In this activity, you won't see the array of image symbols until the very end, but they're
+
+
+hint~
+
+
+
+## {4. Control the Player}
+
+**Time to get the sprite moving**
+
+---
+
+- :game: From ``||controller: Controller||``, drag
+``||controller:move [rockscout] with buttons ⊕||``
+to **the end** of the ``||loops(noclick):on start||`` container.
+
+
+
+#### ~ tutorialhint
+```blocks
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+// @highlight
+controller.moveSprite(rockscout)
+```
+
+
+## {5. Try It}
+
+**Check the Game Window**
+
+Terri appears in the middle of a wall and gets completely lost when moving off screen!
+
+We'll fix that over the next couple of steps.
+
+
+
+## {6. Trapped Backstage}
+
+The Rockscout sprite should start at the stage door.
+
+---
+
+- :tree: To start Terri at the stage door, go to ``||scene:Scene||`` and drag
+``||scene: place [rockscout] on top of random [ ]||``
+to **the end** of the
+``||loops(noclick):on start||``
+container.
+
+- :mouse pointer: Click the checkered tile and select the teal stage door called **stage** from the grid.
+
+_💡 Don't forget to play with your project after each step to see the changes your code has made._
+
+
+
+#### ~ tutorialhint
+```blocks
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+controller.moveSprite(rockscout)
+// @highlight
+tiles.placeOnRandomTile(rockscout, assets.tile`stage`)
+
+```
+
+
+## {7. Follow with Camera}
+
+
+- :tree: To keep Terri in sight, go to ``||scene:Scene||`` and drag
+``||scene:camera follow sprite [rockscout]||``
+to **the end** of the
+``||loops(noclick):on start||`` container.
+
+#### ~ tutorialhint
+```blocks
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+controller.moveSprite(rockscout)
+tiles.placeOnRandomTile(rockscout, assets.tile`stage`)
+// @highlight
+scene.cameraFollowSprite(rockscout)
+
+```
+
+
+## {8. Look Again}
+
+**🕹️ Try your maze in the game window**
+
+---
+
+You should be able to see your sprite as you move it around backstage.
+
+Can you get to the exit?
+
+
+
+## {9. Add Points}
+
+**🎸 Award points when Terri grabs an instrument**
+
+---
+
+
+- :tree: To detect an overlap, go to ``||scene:Scene||`` and drag an
+``||scene:on [sprite] of kind [Player] overlaps [ ] at [location]||``
+bundle into an empty area of the workspace.
+
+- :mouse pointer: Change the "overlaps" checkerboard to the red guitar called **instrument1**.
+
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+
+#### ~ tutorialhint
+```blocks
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+## {10. Try It!}
+
+**🕹️ Try your maze in the game window**
+
+---
+
+You should get one point for every guitar you collect.
+
+What about the drums and keyboard?
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+
+
+## {11. More Instruments}
+
+**🎹 Do it all again**
+Follow the same steps two more times to add points for the **drums** and **keyboard** tiles.
+
+
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+#### ~ tutorialhint
+```blocks
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument0`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument4`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+
+## {12. Look Again...Again}
+
+**🕹️ Try your maze again**
+
+---
+
+You should get one point for every instrument you collect!
+
+Can you make it to the exit?
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+```
+
+
+## {13. Out the Door}
+
+**🚪 Make it out the door**
+
+Let's finish the game with a WIN when Terri overlaps the exit door!
+
+---
+
+- :tree: To detect an overlap, go to ``||scene:Scene||`` and drag the
+``||scene:on [sprite] of kind [Player] overlaps [ ] at [location]||``
+bundle into an empty area of the workspace.
+
+- :mouse pointer: Change the checkerboard to the red door tile named **exit**.
+
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`exit`, function (sprite, location) {
+ game.over(true)
+})
+```
+
+
+#### ~ tutorialhint
+```blocks
+scene.onOverlapTile(SpriteKind.Player, assets.tile`exit`, function (sprite, location) {
+ game.over(true)
+})
+```
+
+
+
+## {14. Spot the Fan}
+
+**Remove points when Terri runs into a screaming fan 📸**
+
+When your sprite overlaps a fan, change the score.
+
+---
+
+- :tree: To detect an overlap, go to ``||scene:Scene||`` and drag the
+``||scene:on [sprite] of kind [Player] overlaps [ ] at [location]||``
+bundle into an empty area of the workspace.
+
+- :mouse pointer: Change the checkerboard to the tile called **fan1**.
+
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+```
+
+
+#### ~ tutorialhint
+```blocks
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+```
+
+
+## {15. More Fans}
+
+**🎤 Encore 🎤**
+
+Follow the same steps two more times to include the other two fans.
+
+
+```blockconfig.local
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+```
+
+
+#### ~ tutorialhint
+```blocks
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan2`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan3`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+```
+
+
+## {16. Look Again}
+
+**🕹️ Try your maze in the game window**
+
+---
+
+You should lose one point for every fan you run into.
+
+How many points can you keep on your way to the door?
+
+
+## {17. Out of Time}
+
+**🕒 Make it out in time**
+
+Use a timer to add some hustle to the whole experience!
+
+---
+
+- :id card: From ``||info:Info||``, drag
+``||info:start countdown [30] (s)||``
+into **the end** of the
+``||loops(noclick):on start||``
+container.
+
+
+#### ~ tutorialhint
+```blocks
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+controller.moveSprite(rockscout)
+scene.cameraFollowSprite(rockscout)
+tiles.placeOnRandomTile(rockscout, assets.tile`stage`)
+//@highlight
+info.startCountdown(30)
+```
+
+
+
+## {18. Go Further}
+
+**Make it your own!**
+
+---
+
+Now that you have created this game, what else could you gamify?
+
+What new skill could you help your friends/sister scouts learn?
+_(For example, pilots and astronauts may use simulation games to learn how to fly a plane or land a rover.)_
+
+
+
+## {19. Decision Tree}
+
+
+Now you can change anything you want.
+
+Sometimes it's easiest to write down your plan so you know how each change will affect the game.
+
+Try making a decision tree!
+
+![Map it out](/static/skillmap/rockstar/decision-tree.png "Draw a decision tree - or flow chart - to detail what happens for each decision.")
+
+
+
+
+## {Finale}
+
+🔥 **Congratulations** 🔥
+
+---
+
+Grab your instruments and get Terri to the end of the maze before time runs out!
+
+When you're done playing, click **Done** to share your final game for feedback.
+
+
+
+```blockconfig.global
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+controller.moveSprite(rockscout)
+scene.cameraFollowSprite(rockscout)
+tiles.placeOnRandomTile(rockscout, assets.tile`stage`)
+game.over(true)
+info.startCountdown(30)
+```
+
+
+
+```package
+pxt-tilemaps=github:microsoft/pxt-tilemaps/
+```
+
+
+```ghost
+
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument0`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument4`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`exit`, function (sprite, location) {
+ game.over(true)
+})
+
+
+let rockscout = sprites.create(assets.image`Rockscout`, SpriteKind.Player)
+controller.moveSprite(rockscout)
+scene.cameraFollowSprite(rockscout)
+tiles.placeOnRandomTile(rockscout, assets.tile`stage`)
+
+
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument3`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument2`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`instrument1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(1)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`door`, function (sprite, location) {
+ game.over(true)
+})
+scene.onOverlapTile(SpriteKind.Player, assets.tile`fan1`, function (sprite, location) {
+ tiles.setTileAt(location, assets.tile`transparency16`)
+ info.changeScoreBy(-1)
+})
+
+```
+
+```customts
+scene.setBackgroundColor(13)
+tiles.setTilemap(tilemap`level1`)
+
+```
+
+
+
+```assetjson
+{
+ "README.md": " ",
+ "assets.json": "",
+ "images.g.jres": "{\n \"HR6MQZ`+AH8fF_EGjDHy\": {\n \"data\": \"hwQQABAAAAAAAAAAAAAAAAAAAAAAAAAAAADw////DwAA8P/v7u5NAAD/7//k/v8AAP/ustEe0f8A7+7y3xTR/wDv7kLdFNEPAF/u8t8U0f8AX+Wy0R7R/wD/5f/k/v8AAPD/7+7uTQAAAPD///8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"displayName\": \"Rockscout\"\n },\n \"*\": {\n \"mimeType\": \"image/x-mkcd-f4\",\n \"dataEncoding\": \"base64\",\n \"namespace\": \"myImages\"\n }\n}",
+ "images.g.ts": "// Auto-generated code. Do not edit.\nnamespace myImages {\n\n helpers._registerFactory(\"image\", function(name: string) {\n switch(helpers.stringTrim(name)) {\n case \"HR6MQZ`+AH8fF_EGjDHy\":\n case \"Rockscout\":return img`\n. . . . . . . . . . . . . . . . \n. . . . . . . . . . . . . . . . \n. . . . f f f f f f f . . . . . \n. . . f f f e e 5 5 f f . . . . \n. . . f f e e e e 5 5 f . . . . \n. . f f e e e e e e e f f . . . \n. . f f f 2 2 2 2 2 f f f . . . \n. . f e f b f 4 f b f e f . . . \n. . f e 4 1 f d f 1 4 e f . . . \n. . f e e d d d d d e e f . . . \n. . f e e e 4 4 4 e e e f . . . \n. . f e f 1 1 1 1 1 f e f . . . \n. . f d f 1 1 1 1 1 f d f . . . \n. . . 4 f d d d d d f 4 . . . . \n. . . . . f f f f f . . . . . . \n. . . . . f f . f f . . . . . . \n`;\n }\n return null;\n })\n\n helpers._registerFactory(\"animation\", function(name: string) {\n switch(helpers.stringTrim(name)) {\n\n }\n return null;\n })\n\n}\n// Auto-generated code. Do not edit.\n",
+ "main.blocks": "PlayerProjectileFoodEnemyrockscoutlist",
+ "main.ts": "\n",
+ "pxt.json": "{\n \"name\": \"80s Rockscout - Assets Only\",\n \"description\": \"\",\n \"dependencies\": {\n \"device\": \"*\",\n \"tilemaps\": \"github:microsoft/pxt-tilemaps#v1.11.0\"\n },\n \"files\": [\n \"main.blocks\",\n \"main.ts\",\n \"README.md\",\n \"assets.json\",\n \"tilemap.g.jres\",\n \"tilemap.g.ts\",\n \"images.g.jres\",\n \"images.g.ts\"\n ],\n \"targetVersions\": {\n \"branch\": \"v1.5.56\",\n \"tag\": \"v1.5.56\",\n \"commits\": \"https://github.com/microsoft/pxt-arcade/commits/fb33e381b78eca2163bc40e2b26bdfdc9e7ebd81\",\n \"target\": \"1.5.56\",\n \"pxt\": \"7.1.35\"\n },\n \"preferredEditor\": \"blocksprj\"\n}\n",
+ "tilemap.g.jres": "{\n \"transparency16\": {\n \"data\": \"hwQQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true\n },\n \"tile4\": {\n \"data\": \"hwQQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8//////////zu7u7u7u7+/P7u7v/u/v787u7+u+/u/vzu7r/M++7+/O7uv8z77v787u7+u+/u/vz+7u7/7v7+/O7u7u7u7v78/////////8zMzMzMzMzMzMzMzMzMzAwAAAAAAAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"instrument3\"\n },\n \"tile5\": {\n \"data\": \"hwQQABAAAAAAwBEREQwAAADAETMRDAAAAMARMRMMAAAAwBEzEQwAAADAERERDAAAAMD87s8MAAAAAPDuwQAAAAD///8RDADA8O/v7h7LDM/P7+///xGM/8/s7vEvEWb///zu8SIRhgD//P7u7v9m/8/8/u7+7o//wP/v7v/uD88A////D/8AwA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"fan1\"\n },\n \"tile6\": {\n \"data\": \"hwQQABAAAAAAwBEREQwAAADAETMRDAAAAMARMRMMAAAAwBEzEQwAAADAERERDAAAAAD/RMwMAAAA8P9EwQAAAAD//7wRDADA8L9PRBvLDM/Pv0/u7hGM/8+8S+EuEWb//8xL4SIRhgD//PtEtMxm/8/8+/S/RIj/wP+7/89EDM8A////D8wAwA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"fan2\"\n },\n \"tile7\": {\n \"data\": \"hwQQABAAAAAAwBEREQwAAADAETMRDAAAAMARMRMMAAAAwBEzEQwAAADAERERDAAAAMDM3cwMAAAAAMDdwQAAAAD7/88RDADA0LXf3RvLDM8dtdzu7hGM/x3R2+EuEWb/XVHb4SIRhgBdUcvdvbtm/x1R+9293Yj/EFW13cvdC88AXVXPDLsAwA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"fan3\"\n },\n \"tile3\": {\n \"data\": \"hwQQABAAAAAAAAAA/+8AAAAAAPD/7g4AAAAA8G5E5AAAAADwRkREDgAA8P9GRETkAAD/L0TuROQAAO9GRORO5AAAb0T+Qk7kAABuRP9PROQAAOBE8k5EDgAA/09EJO4AAPDv7kTkAADw/+4A7g4AAO/kDgAAAAAAT+QAAAAAAADvDgAAAAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"instrument2\"\n },\n \"tile9\": {\n \"data\": \"hwQQABAAAAAAAAAAy8wMAAAAALDb280AAAAA+xHbzQAAALD7G73dDAAA+/0fsd0MALD727/R280A+/3fzBHbzbD727/RHbHMsP3fzBEd3c2w3b/RHbHLDADbzBEd3c0AANvRHbHLDAAAsBEd3c0AAACwHbHLDAAAAAAb3c0AAAAAALvLDAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"instrument4\"\n },\n \"tile10\": {\n \"data\": \"hwQQABAAAAAAwMz8////AAC8LLLMzPwAwLss0sy7/AC8u9zdvMzLD7y7u93f37wPvLvL3dv/vA+8u8vdzf2/D8C7y9v93Pv/wLu73L3////Au7vcvf///MC7u8zMzMz8ALy7MzMzM/sAvDuzu7u7DAC8M7u7u8sAADyzu7u7DAAAwMzMzMwAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"camera\"\n },\n \"tile2\": {\n \"data\": \"hwQQABAAAAAAAAAA/+4AAAAAAPDu7g4AAAAA4G4i4gAAAADwJiIiDgDw7/8mzCLiAP/u/yLcLOIA7yIiIrHN4gAuIiISEcziAC4C8BMRIeIA7gDvPhEiDgAA8O4u4+4AAADvDi7iAADw/+4AIuIAAO/kDgDuDgAAT+QAAAAAAADvDgAAAAAAAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"instrument1\"\n },\n \"tile12\": {\n \"data\": \"hwQQABAAAADM/P//zPzM/Mz8zPzMzMz8zMzM/MzMzPzM/Mz8zPzM/Mz8zPzM/Mz8zPzMzMz8zPzM/MzMzPzMzP//zPzM/MzMzPzM/Mz8zPzM/Mz8zPzM/Mz8zPz//8z8zMzM/Mz8zPzMzMz8zPzM/Mz8zPzM/Mz8zPzMzMz8zMzM/MzMzPz/zw==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"wall\"\n },\n \"tile13\": {\n \"data\": \"hwQQABAAAAD//////////x/RHbHbu9u9H7u7sd3P/L3fu7u9/c/8v9+7u73/zMz/37u7vczMzMzfu7u9zMzMzN+7u73MzMzM37u7vczMzMzfu7u9zMzMzN+7u73MzMzM37u7vf/MzP/fu7u9/c/8vx+7u7Hdz/y9H9Edsdu7273//////////w==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"stack\"\n },\n \"tile11\": {\n \"data\": \"hwQQABAAAAC7iIiIiIiIiLGIERZmZmZmEYhhEWZmZmYRiGdmZoiIZxGIERFmaGZnEYhnZmZoZmcRiBERZmhmZxGIcWdmaGZnEYgWEWZoZmcRiGZmZmhmZxGIEXFmaGZnEYhhEWZIaGcRiGZmZlSFZxGIERFmVIVmsYhxFmZGaGa7iIiIiIiIiA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"stage\"\n },\n \"tile1\": {\n \"data\": \"hwQQABAAAAC77u7u7u7u7rHuIiIiIiIiEe4SESEiIiIR7hIjIe7uIxHuIiIiLiIjEe4SMyEuIiMR7iIRIi4iIxHuEjMhLiIjEe4iIiIuIiMR7hIRIS4iIxHuIiIiLiIjEe4SIiJOLiMR7hIR4VTlIxHuEiLiVOUise4iIiJOLiK77u7u7u7u7g==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"exit\"\n },\n \"tile8\": {\n \"data\": \"hwQQABAAAAAAALALsAsAAAAA27u7vQAAAAC7HdG9DgAAsB0RsfvrALDbEd0R+78O2xvR3RHr/uu7Hd3d0dvuv7AR3R2x3ev+sBEREbu/3e67vRG96/LS7du9u9svIi/tsPvv3fsi8g4Avu++LS8iDwDg++7d8uIAAAC+797tDgAAAOD77g4AAA==\",\n \"mimeType\": \"image/x-mkcd-f4\",\n \"tilemapTile\": true,\n \"displayName\": \"instrument0\"\n },\n \"level1\": {\n \"id\": \"level1\",\n \"mimeType\": \"application/mkcd-tilemap\",\n \"data\": \"MTAxMDAwMTAwMDBhMGEwYTBhMGEwYTBhMGEwYTBhMGEwYTBhMGEwYjBiMGEwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMGEwOTAwMGIwYjAwMGEwNTBhMDAwYjAwMGEwMDBiMDAwYTBhMDAwYjBiMDAwYTBhMGEwMDBhMDAwYTAwMGEwMDBhMGEwMDAwMDAwYjBhMDMwYTAwMGEwMDBiMDAwYTAwMGEwYTAwMGEwYTBhMGEwMDBiMDAwYTAwMDAwMDBhMDAwYTBhMDAwMDAwMDAwMDAwMDAwMDBhMDMwYTAwMGEwMDBhMGEwMDA3MGEwYTBhMGEwYjAwMGEwYTBhMDAwYTAwMGEwYTAwMDAwMDBhMDAwNDAwMDAwYTA4MGEwMDBhMDAwYTBhMDAwYTBhMGEwMDBhMGEwYTBhMDAwYjAwMGEwMDBhMGEwMDAwMDAwMDAwMGEwMDAwMDAwMDAwMDAwYTAwMGEwYTAwMGIwYTBhMGEwYjBhMGEwYTBhMGEwNjBhMDAwYTBhMDAwMDAwMDAwYzBhMGIwYjBiMDAwMDAwMGEwMDBhMGEwMDBhMGEwYTBhMGEwYTBhMGEwYTBhMGEwYjAwMGIwYTA2MGEwODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTBhMGEwYTBhMGEwYTBhMGEwYTBhMGEwYTBhMGEwYTAyMjIyMjIyMjIyMjIyMjIyMjAyMDAwMDIwMDAwMDAwMjAwMDIyMjAyMDAwMjAwMDIwMDIyMjIwMjIyMDIwMjAyMDAyMDAyMjIwMjAwMDIwMjAwMjIyMjIwMDIwMDAyMDIwMDIwMDAwMDAyMDIwMjAyMDAyMjAyMjIyMjAyMjIwMjAwMjAwMDIwMjIwMjAyMDIwMDIyMjAyMjIyMjAwMjAyMDAyMDAwMDAyMDAwMDIwMjAwMjIwMjIyMjIyMjIyMDIwMDIwMDAwMDIwMDAwMjAyMDAyMjIyMjIyMjIyMjIyMDAwMjAyMDAwMDAwMDAwMDAwMjIyMjIyMjIyMjIyMjIwMg==\",\n \"tileset\": [\n \"myTiles.transparency16\",\n \"myTiles.tile1\",\n \"sprites.castle.tileGrass2\",\n \"myTiles.tile2\",\n \"myTiles.tile4\",\n \"myTiles.tile5\",\n \"myTiles.tile6\",\n \"myTiles.tile7\",\n \"myTiles.tile8\",\n \"myTiles.tile11\",\n \"myTiles.tile12\",\n \"myTiles.tile13\",\n \"myTiles.tile9\"\n ],\n \"displayName\": \"level1\"\n },\n \"*\": {\n \"mimeType\": \"image/x-mkcd-f4\",\n \"dataEncoding\": \"base64\",\n \"namespace\": \"myTiles\"\n }\n}",
+ "tilemap.g.ts": "// Auto-generated code. Do not edit.\nnamespace myTiles {\n //% fixedInstance jres blockIdentity=images._tile\n export const transparency16 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile4 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile5 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile6 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile7 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile3 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile9 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile10 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile2 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile12 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile13 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile11 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile1 = image.ofBuffer(hex``);\n //% fixedInstance jres blockIdentity=images._tile\n export const tile8 = image.ofBuffer(hex``);\n\n helpers._registerFactory(\"tilemap\", function(name: string) {\n switch(helpers.stringTrim(name)) {\n case \"level1\":\n case \"level1\":return tiles.createTilemap(hex`100010000a0a0a0a0a0a0a0a0a0a0a0a0a0a0b0b0a0000000000000a000000000000000a09000b0b000a050a000b000a000b000a0a000b0b000a0a0a000a000a000a000a0a0000000b0a030a000a000b000a000a0a000a0a0a0a000b000a0000000a000a0a00000000000000000a030a000a000a0a00070a0a0a0a0b000a0a0a000a000a0a0000000a000400000a080a000a000a0a000a0a0a000a0a0a0a000b000a000a0a00000000000a0000000000000a000a0a000b0a0a0a0b0a0a0a0a0a060a000a0a000000000c0a0b0b0b0000000a000a0a000a0a0a0a0a0a0a0a0a0a0a0b000b0a060a080000000000000000000000010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a02`, img`\n2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 \n2 . . . . . . 2 . . . . . . . 2 \n. . 2 2 . 2 . 2 . . . 2 . . . 2 \n2 . 2 2 . 2 2 2 . 2 . 2 . 2 . 2 \n2 . . . 2 2 . 2 . 2 . . . 2 . 2 \n2 . 2 2 2 2 . . . 2 . . . 2 . 2 \n2 . . . . . . . . 2 . 2 . 2 . 2 \n2 . . 2 2 2 2 2 . 2 2 2 . 2 . 2 \n2 . . . 2 . 2 . . 2 . 2 . 2 . 2 \n2 . 2 2 2 . 2 2 2 2 . . . 2 . 2 \n2 . . . . . 2 . . . . . . 2 . 2 \n2 . . 2 2 2 2 2 2 2 2 2 . 2 . 2 \n2 . . . . . 2 . . . . . . 2 . 2 \n2 . 2 2 2 2 2 2 2 2 2 2 2 2 . . \n2 . 2 . . . . . . . . . . . . . \n2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 . \n`, [myTiles.transparency16,myTiles.tile1,sprites.castle.tileGrass2,myTiles.tile2,myTiles.tile4,myTiles.tile5,myTiles.tile6,myTiles.tile7,myTiles.tile8,myTiles.tile11,myTiles.tile12,myTiles.tile13,myTiles.tile9], TileScale.Sixteen);\n }\n return null;\n })\n\n helpers._registerFactory(\"tile\", function(name: string) {\n switch(helpers.stringTrim(name)) {\n case \"transparency16\":return transparency16;\n case \"instrument3\":\n case \"tile4\":return tile4;\n case \"fan1\":\n case \"tile5\":return tile5;\n case \"fan2\":\n case \"tile6\":return tile6;\n case \"fan3\":\n case \"tile7\":return tile7;\n case \"instrument2\":\n case \"tile3\":return tile3;\n case \"instrument4\":\n case \"tile9\":return tile9;\n case \"camera\":\n case \"tile10\":return tile10;\n case \"instrument1\":\n case \"tile2\":return tile2;\n case \"wall\":\n case \"tile12\":return tile12;\n case \"stack\":\n case \"tile13\":return tile13;\n case \"stage\":\n case \"tile11\":return tile11;\n case \"exit\":\n case \"tile1\":return tile1;\n case \"instrument0\":\n case \"tile8\":return tile8;\n }\n return null;\n })\n\n}\n// Auto-generated code. Do not edit.\n"
+}
+```
+
+
+