From eff4ccef56128572631cf99c4fd45c51161d08ff Mon Sep 17 00:00:00 2001 From: Galen Nickel Date: Mon, 22 Oct 2018 13:22:20 -0700 Subject: [PATCH] Port 'crashy-bird' game to v1 (#1484) --- docs/projects/crashy-bird.md | 271 ++++++++++++++++++++++++ docs/projects/games.md | 5 + docs/static/mb/projects/crashy-bird.png | Bin 0 -> 3287 bytes 3 files changed, 276 insertions(+) create mode 100644 docs/projects/crashy-bird.md create mode 100644 docs/static/mb/projects/crashy-bird.png diff --git a/docs/projects/crashy-bird.md b/docs/projects/crashy-bird.md new file mode 100644 index 00000000..3a01768d --- /dev/null +++ b/docs/projects/crashy-bird.md @@ -0,0 +1,271 @@ +# Crashy Bird + +## ~avatar avatar + +All the fun from the Flappy Bird game is coming to the @boardname@ as Crashy Bird! + +## ~ + +This is a simple version of the Flappy Bird game for @boardname@. The objective is to direct a flying bird, which is moving continuously to the right, between sets of obstacles. If the player touches an obstacle, they lose. The purpose of this tutorial is to teach the basics of game sprites, arrays, and loops. + +## Step 1: Add the Bird to the Game + +First, we are going to add a sprite for the bird from the **Game** menu and make it blink. + +```blocks +let bird: game.LedSprite = null +bird = game.createSprite(0, 2) +bird.set(LedSpriteProperty.Blink, 300) +``` + +## Step 2: Make the Bird fly + +Before creating the code for the game actions, let's first add some controls so that we can move around. We'll control the bird by pressing the **A** button to go up or the **B** button to go down. + +```blocks +let bird: game.LedSprite = null + +input.onButtonPressed(Button.A, () => { + bird.change(LedSpriteProperty.Y, -1) +}) +input.onButtonPressed(Button.B, () => { + bird.change(LedSpriteProperty.Y, 1) +}) +``` + +## Step 3: Generating obstacles + +This is where things will start to get interesting. We're going to randomly generate obstacles. We'll keep all obstacles inside the array. All obstacles will have a single hole for the bird to fly through. + +First, create an array of `obstacles` which will hold all of the obstacle sprites. + +```blocks +let obstacles: game.LedSprite[] = [] +``` + +Now generate vertical obstacles consisting of 4 sprites and 1 random hole. +Create new variable called `emptyObstacleY`. Using ``||math:pick random||``, generate a random number from `0` to `4` and store it inside `emptyObstacleY`. + +Using ``||loops:for||`` loop, iterate from `0` to `4`. For every coordinate not equal to `emptyObstacleY` create and add obstacle sprites to the endo fo the `obstacles` array. + +```blocks +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] + +emptyObstacleY = Math.randomRange(0, 4) +for (let index = 0; index <= 4; index++) { + if (index != emptyObstacleY) { + obstacles.push(game.createSprite(4, index)) + } +} +``` + +Now with every @boardname@ restart you should see different autogenerated vertical obstacles. + +Before continuing, make sure that obstacles are generated randomly and that the bird is moving up and down. + +```blocks +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] +let bird: game.LedSprite = null + +bird = game.createSprite(0, 2) +bird.set(LedSpriteProperty.Blink, 300) + +emptyObstacleY = Math.randomRange(0, 4) +for (let index = 0; index <= 4; index++) { + if (index != emptyObstacleY) { + obstacles.push(game.createSprite(4, index)) + } +} + +input.onButtonPressed(Button.A, () => { + bird.change(LedSpriteProperty.Y, -1) +}) + +input.onButtonPressed(Button.B, () => { + bird.change(LedSpriteProperty.Y, 1) +}) +``` + +## Step 4: Make obstacles move + +Access each obstacle using a loop (_iterate_ over the `obstacles` array) and decrease the `obstacle` `X` coordinate by 1. + +```blocks +let obstacles: game.LedSprite[] = [] + +basic.forever(() => { + for (let obstacle of obstacles) { + obstacle.change(LedSpriteProperty.X, -1) + } + basic.pause(1000) +}) +``` + +Obstacles should move towards left every second. + +## Step 5: Make obstacles disappear + +Make obstacles disappear after reaching leftmost corner. Iterate over all obstacles, delete the obstacle sprites where the `X` coordinate equals `0`, and remove them from the ``obstacles`` array. + +```blocks +let obstacles: game.LedSprite[] = [] + +basic.forever(() => { + while (obstacles.length > 0 && obstacles[0].get(LedSpriteProperty.X) == 0) { + obstacles.removeAt(0).delete() + } + + for (let obstacle of obstacles) { + obstacle.change(LedSpriteProperty.X, -1) + } + basic.pause(1000) +}) +``` + +## Step 6: Generate more obstacles + +At the moment, our code generates just one vertical obstacle. We need to put obstacle generation code into the ``||basic:forever||`` loop so that it keeps generating more and more obstacles. + +```blocks +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] + +basic.forever(() => { + while (obstacles.length > 0 && obstacles[0].get(LedSpriteProperty.X) == 0) { + obstacles.removeAt(0).delete() + } + + for (let obstacle of obstacles) { + obstacle.change(LedSpriteProperty.X, -1) + } + emptyObstacleY = Math.randomRange(0, 4) + for (let index = 0; index <= 4; index++) { + if (index != emptyObstacleY) { + obstacles.push(game.createSprite(4, index)) + } + } + basic.pause(1000) +}) +``` + +Now our screen is full of moving obstacles. Create some spaces between generated obstacles. Let's introduce a `ticks` variable to count how many iterations the ``||basic:forever||`` loop has done and execute obstacle creation only if `ticks` is divisible by 3. + +```blocks +let ticks = 0 +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] + +basic.forever(() => { + while (obstacles.length > 0 && obstacles[0].get(LedSpriteProperty.X) == 0) { + obstacles.removeAt(0).delete() + } + + for (let obstacle of obstacles) { + obstacle.change(LedSpriteProperty.X, -1) + } + if (ticks % 3 == 0) { + emptyObstacleY = Math.randomRange(0, 4) + for (let index = 0; index <= 4; index++) { + if (index != emptyObstacleY) { + obstacles.push(game.createSprite(4, index)) + } + } + } + ticks += 1 + basic.pause(1000) +}) +``` + +## Step 7: Game Over + +Right now nothing happens when the bird is hit by obstacle. Fix this by iterating over the `obstacles` array and checking if any obstacle sprite coordinate equals the bird coordinate. + +```blocks +let bird: game.LedSprite = null +let ticks = 0 +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] + +basic.forever(() => { + while (obstacles.length > 0 && obstacles[0].get(LedSpriteProperty.X) == 0) { + obstacles.removeAt(0).delete() + } + + for (let obstacle of obstacles) { + obstacle.change(LedSpriteProperty.X, -1) + } + if (ticks % 3 == 0) { + emptyObstacleY = Math.randomRange(0, 4) + for (let index = 0; index <= 4; index++) { + if (index != emptyObstacleY) { + obstacles.push(game.createSprite(4, index)) + } + } + } + + for (let obstacle of obstacles) { + if (obstacle.get(LedSpriteProperty.X) == bird.get(LedSpriteProperty.X) && obstacle.get(LedSpriteProperty.Y) == bird.get(LedSpriteProperty.Y)) { + game.gameOver() + } + } + + ticks += 1 + basic.pause(1000) +}) +``` + +## The final code + +```blocks +let ticks = 0 +let emptyObstacleY = 0 +let obstacles: game.LedSprite[] = [] +let index = 0 +let bird: game.LedSprite = null +input.onButtonPressed(Button.A, () => { + bird.change(LedSpriteProperty.Y, -1) +}) +input.onButtonPressed(Button.B, () => { + bird.change(LedSpriteProperty.Y, 1) +}) +index = 0 +obstacles = [] +bird = game.createSprite(0, 2) +bird.set(LedSpriteProperty.Blink, 300) +basic.forever(() => { + while (obstacles.length > 0 && obstacles[0].get(LedSpriteProperty.X) == 0) { + obstacles.removeAt(0).delete() + } + for (let obstacle2 of obstacles) { + obstacle2.change(LedSpriteProperty.X, -1) + } + if (ticks % 3 == 0) { + emptyObstacleY = Math.randomRange(0, 4) + for (let index2 = 0; index2 <= 4; index2++) { + if (index2 != emptyObstacleY) { + obstacles.push(game.createSprite(4, index2)) + } + } + } + for (let obstacle3 of obstacles) { + if (obstacle3.get(LedSpriteProperty.X) == bird.get(LedSpriteProperty.X) && obstacle3.get(LedSpriteProperty.Y) == bird.get(LedSpriteProperty.Y)) { + game.gameOver() + } + } + ticks += 1 + basic.pause(1000) +}) +``` + +## Exercises + +Here are some additional features you can add to the game: + +1. Count and show the Crashy Bird game score. +2. Make the obstacles move faster every time an obstacle is passed. + +## About the authors + +This project was created by [Karolis Vycius](https://www.linkedin.com/in/vycius/). The original Flappy Bird game was developed by [Dong Nguyen](https://en.wikipedia.org/wiki/Flappy_Bird). \ No newline at end of file diff --git a/docs/projects/games.md b/docs/projects/games.md index d98f7faf..b8c370ce 100644 --- a/docs/projects/games.md +++ b/docs/projects/games.md @@ -45,5 +45,10 @@ Fun games to build with your @boardname@. "description": "Karel likes to draw, help Karel make LED art!", "url": "/projects/karel", "imageUrl": "/static/mb/projects/karel.png" +}, { + "name": "Crashy bird", + "description": "Help the flying bird move through the obstacles on the LED screen", + "url":"/projects/crashy-bird", + "imageUrl":"/static/mb/projects/crashy-bird.png" }] ``` diff --git a/docs/static/mb/projects/crashy-bird.png b/docs/static/mb/projects/crashy-bird.png new file mode 100644 index 0000000000000000000000000000000000000000..b8ee6a813f7ad3bf07e1ab001c0ec8b4533bee9d GIT binary patch literal 3287 zcmai%`8(8$7srQ&J7dtG8IkNm%vei{OB2^VWXl-CWrh+nmQ1!miFUcOX8V7=4U=*vy4uf_W9gi2ZJPcW|ka5 z`dK8QYi+xX#m&vlCFkRf-pZoGTuQ1NROnF5byM%3IZhn#oEJ2ER( zNm=DkCJG)|Ue_&QS?kiIZ}rwvT8I}6y5Dx8!uSiS}5SlftL-qVIq58r!0X4dIio%m7j+*Dl@bT>(Z%ahH{LT~1 zi`>K>B+rwB#ng$df9TTaa2--Gi{|!Qb-YnM3_qX8_!b}(FnOJ~9HY{5MCTdiX~w9I zO205)=CeHhGuadJPlFS8IYA3{wlAD*5(=ZHAe}lBjHG;wz`ZJj9F7rqBoT(c4_mN+ z{Zy1;2n9*Zs*e?ivb$s1MP5-pWNL15;neB2Ggh@L)^(L zsL);Ahx62LOUJF-z&PMY<>kTdGKAJ_mGyMJY%%?8x-8eQr1p7}6|P5oc-+S0><{dY zpeie?Wo-a^J0@ned7xkVBF~b2S4+pRk84M4qmLPD=_YlJgt0obowFGFin_EbPJ;od z-U8!eTC<%ujxqJ(w&uIB1^ep@58^bUxzd}Q5HW_nLZuvcV4u>J^>Sb?h{- zM2nzp1;%2GdXqrR!6>I6=aQIWLpb9m33HY07~=4BKF zZ@Eg!$OW2|s)~aJ6x~^I>YOHL#a8 zJ>&F~1nqW3$_$xYHK#XW9=spzfj?4#@GY!iNlt46@=Os%G8!j}>$xlBI^<>DSzzL%@JZFHQuvnu#oYQMQ@~C^DrAy8;zfrdZ@P6X#U=VP^zh9Se8lo^yGcFi^7=L?jBWBq7ZJjj|cbo3CoiLtGPe>!Y9WY#DUA_z?tnccb zBSR~-n6=6&gHbwRz71QqK4nE=HxefDij}kYAK$>4uUPhu;B?Zf@bIn?&lByGyJ(K| z|M)b)Iz54~7HM<|%3VGVT!}knoqz6{wJU-%Q)Pr-Zs3P322Ydd_mLgWz`Kc0)C>3d zoK=Q?d9Xy*`C=KSL8fnFgjbu`(<6Xs0wv!CID4f^jbK)7k?xe?_UK<)QN*ywMyhx} zDfE4Pa`63U7;_`!Fmub7Cw#=_`MR$NL<9Kc8FPtvs6(qF8Hxt^8hPV@FYOxm55)6? zc1lw*K}vg2pDeh?9DcOEr&!3#F1^!nGdv>#fFOtlWPu^nsp0s?bi_N#N0Pzs)8~vs zbj%JBF>-(lppP8A;KlJ`Q#ig2^2ttuiqe*_`-JH?by3%MdV{the3Mb5)-##%R=}1( z9%1F7N@(wre1;yAIw)*6LHE-vRK?e$gV^eqY6xaNVlMMZ;lWit;|N1Sbm6_}_-Q+( z0)?G^aa(3%K@%^QzIX!vF2^V6>b+(1SIq~F-)x>k(+NVI{;|IOQDgetijFiL<;CXy z{`a?<;M|dPM7&|DP2aWB?ctcaY#)?5{=%bR!l_Wx`os9Z237n8d$J}5v{WyN96+#< zbzD_^>n$%)=DM3%Psc3n6VI#7w?Vi!$I;Lz@|9n^)1&f+IF*QOMu*J-Nk zc(Y>i&gLTGw_j^#Lk6oVhIja)uCn7-{#=$be!{So4=_LxN@b#^oGhb%lz#iI`YAbz zoibE+dHcxHHaquzf7?5f6tk)*N$h(d;UM8&S(t`ARhw8(ed7*I9nxcozFFaP6`b@j z$fq_UO>fiL(QHbATe0Y@-ThiUv+7Apc906;7gc+A(>njsHYGtW3fzrc;Lb!y;3UO4 z6*;o3_0ROZ*uH?wOU9b<_!$}gC+IW^U#wY?2jR-vdqf2Fl}~A6eAnnr`lX|CSNEfp zjccH@A;7ZJU2!jJi9c8!%d#ibw{0hsN&`&J2(06A;;ZI~;_blHp&k*uaqXufOZMEN zA~5R7BLbJ5hmef?R-MALvva$OyLH#Ed3ePiuhRgU|DMs!_}gaA^5tt{$@1h}EKfQ{ zHP>$%O6A)|`lo4^@5cUd9;4pMB4crc8yeEmdEOjz>?aNL58=NOA~o)vjB{5z<*b1& zN_Q)_Cj@c}no-unymjRF_1o7+`{trK!vD0b>IjAirQd73er_nDi^Qr<>D8x~AIz(U zgOzpR$L($F4Iv=lSVpjY)Azj)iD=@p;<5F(jgUnXEz{w7#&E-Xs$eNR<>^h`z_|3^ zw36hjHmXT$DMJxL)?Uh+H#7+KGhJJaxXHCAw zI2a9jh5=|K5wmKxFl`DxY7V54&bg%_U6H-_LUpEN2Q1jM>;NfUg=wiA`L~@agv|M1 ziBu%TDv^SlH7ILQLf)KvEY5H7IC)sBY$9rN^Zm+j?9@Vf_{N$)Pwc`)+DK|^Nn&L- zM|xz#ku6Lna^;F|XV0vM{RokO;`K`d!c-KU;Xr(DH5|bet2iAf^h62+3V{Qyxi8=n zT;YF2xnh~H#gqh+nTV&u-PWwHn5V06Z{=ZMhUeEwlCrT6i0 zK9nj{Dq}%a)gE`8jg>*?Te6V`Xh|X&4EE$De(`K;9OX3{d>%ZOrK188H)kUwuA2KY zljR_M^XghsyVy%O&@uE`ZGSH)w-=l}>t0VI34R@QdMxoE;lwy41;J7!q@PRcaL%X1 zc{lj*z`Lv!DDG9w% zD_`FZG)68w`P34-eLZ@2Yi)q(UZof(rURy$)wGUD$i6$qB@T|4p9o&aW$8;}^Iq0B z`!u$fOC84M9jZGx5CMP3ZLb^$Q_1#D)PaAO?KcHa^COx%hszRou!g8750qYdd$zg2 zp~y>LG!4ougHD=Pg0?=%NUKDP9i&y_U-5q|wXAGk$kj1oc;2Wa R?cf;!*;-z-V40I>{{^nKH&Flp literal 0 HcmV?d00001