00
Days
00
Hours
00
Mins
00
Secs
Artificial Intelligence Challenge / Ends February 11
๐ŸŽ Prizes: $100 / $50 / $25 Amazon Gift Cards

Learn to Code via Tutorials on Repl.it

Posts
โ–ฒ
11
What tutorials would you like to see here?
Hey all, Last year I wrote several tutorials and shared them here. These tutorials were on [Discord Bots](https://www.codementor.io/garethdwyer/building-a-discord-bot-with-python-and-repl-it-miblcwejz), building a [Django Application](https://www.codementor.io/garethdwyer/creating-and-hosting-a-basic-web-application-with-django-with-repl-it-lohsyub20), [Web Scraping](https://www.codementor.io/garethdwyer/beginner-web-scraping-with-python-and-repl-it-nzr27jvnq), and [Algorithms](https://www.codementor.io/garethdwyer/quicksort-tutorial-python-implementation-with-line-by-line-explanation-p9h7jd3r6). I'm preparing some new topics to write about this year and I'd love to get your input on what kind of tutorials you'd like to see here. Please comment below with either (or both!) * Topics you'd like to see more of, for example - Data Science, Machine Learning, Web Development, Data Structures and Algorithms, Information Security (hacking), specific languages and frameworks, or any other area that you're interested in and would like to read and work through tutorials on. * Specific titles and topics - if you're battling with something specific, feel free to drop an exact title you'd like to see, like "Setting up a Flask web app on Repl with a PostgreSQL database hosted on AWS" If you already see the topic in the comments below, feel free to upvote it instead of duplicating. Looking forward to publishing more posts soon!
9
posted by GarethDwyer1 (64) 9 days ago
โ–ฒ
24
Build your very own URL shortener ๐Ÿ”—๐Ÿš€
## Build a tiny URL shortener, using a remote database ![](https://boring-lamport-1b901a.netlify.com/2018-12-2214-5f2c1bbe-59a2-4ba7-9544-438b1ab5e1dd.39.01.gif) [Demo](https://l.4ty2.fun) โฏ๏ธ [Code](https://repl.it/@jajoosam/tyni) ๐Ÿ‘จโ€๐Ÿ’ป Setting up a URL shortener is a lot of work - either you have to pay, or spend hours setting up your own server. This is a guide to making your own URL shortener with [repl.it](http://repl.it) - using `express`, and a remote database - all on `node.js` ## ๐Ÿ› ๏ธ Getting our environment running First up, fork the [https://repl.it/@jajoosam/tyni-starter](https://repl.it/@jajoosam/tyni-starter) repl, so that you have a running project. Next, create a new file - `.env` ![](https://boring-lamport-1b901a.netlify.com/Untitled-675a864b-7c8c-487d-8ca7-595eb8af67ba.png) A `.env` file can store secrets for you, that no one else will be able to see. This is where we want to store our token for accessing the remote database. ## ๐Ÿ“ Making our database We're going to be using `[jsonstore.io](http://jsonstore.io)` for storing all our URLs. Head over to [jsonstore.io/get-token](https://www.jsonstore.io/get-token) - and copy the token you see - this is the secret we want to store in our `.env` file. Open up your `.env` file, and set `KEY` to your token, like this ๐Ÿ‘‡ ```bash KEY=yourTokenGoesHere ``` Remember to keep **no whitespace**, or your token might not be recognized right! When you open `index.js` you'll see that I've already initialized the database, and a small web server for you. Now let's get to making our API so we can shorten them URLs ๐Ÿš€ ## ๐Ÿ‘จโ€๐Ÿ’ป The API There are two parts to our URL shortener: 1. Storing the short link, corresponding to the long URL - in our database. 2. Redirecting visitors from the short link to the long link Both of these are super simple to implement, thanks to the `express` server we're using - we'll just be collecting `get` requests for both of the tasks. For adding links to our database, we have a special endpoint - requests to it have two parts: the long URL, and the short path. ```javascript app.get('/shorten', (req, res) => { db.write(req.query.key, {"url": req.query.url}); res.status(200); }); ``` Adding this to our code lets us correspond the short path (`key`) to the long `url`, and then we finally send a successful response. For the second task, we'll just be collecting the short path (`key`) from a request, finding the corresponding URL in our database, and then redirecting our visitor โฌ‡๏ธ ```javascript app.get('/:key', (req, res) => { db.read(req.params.key + "/url").then( (url) => { res.redirect(url); }); }); ``` That's prety much it - we have a fully functional URL shortener ๐Ÿคฏ - check it out for yourself, open a new URL which looks like this ๐Ÿ”— ``` https://tyni.jajoosam.repl.co/shorten?key=yay&url=https://dsh.re/50854 ``` Now, going to [`http://tyni.jajoosam.repl.co/yay`](http://tyni.jajoosam.repl.co/yay) will be nice to see ๐Ÿ‘‡ ![](https://boring-lamport-1b901a.netlify.com/1-8380b52f-92c2-4acc-87dc-64011c6fd502.jpg) Of course, you'll be replacing `tyni.jajoosam` in those URLs with your own repl! ## โœจ The Frontend Our URL shortener does work, but it's tedious, having to type out a huge URL before shortening it - we can make the whole process much simpler with a simple frontend. I've already created this - and gone for a neat and minimal look using [wing.css](https://github.com/kbrsh/wing) ![](https://boring-lamport-1b901a.netlify.com/Untitled-0fa3af54-ddae-46ca-abdf-4fcbcb218f8b.png) You just have to add code to send visitors to the hompage in the `static` folder ๐Ÿ  ```javascript app.get('/', (req, res) => { res.sendFile("static/index.html", {root: __dirname});; }); ``` If you browse through the `static` folder, you'll find a simple `HTML` file with a form, `CSS` to style our page, and most importantly, `JS` to send requests to our URL shortening API. The `HTML` is quite straightforward, we're asking for the long URL, and *optionally* a short path. Open up `script.js` and you'll see the `shorten()` function. Here's what the JS file does (*I've also annotated the code with comments*) ๐Ÿ‘‡ ๐Ÿ” Getting the path(`key`) and the long `url` from the form. ๐Ÿ“ Possibly generating a random 5 character hash as our path (in case there's no path entered) ๐Ÿ”— Sending a get request to our API, with our `key` and `url` as parameters ๐Ÿ–ฅ๏ธ Displaying the shortened URL on our page ## ๐ŸŒ Getting our custom domain Sure, our links are shorter - but we still don't have them on our own domain, and the `repl.co` links can be pretty long ๐Ÿ‘€ Luckily for us, the folks at [repl.it](http://repl.it) recently allowed custom domains to be used! That means this project could be something you actually use all the time ๐Ÿ˜„ Check out `dotcomboom`'s guide on [using custom domains](https://repl.it/talk/learn/How-to-use-a-custom-domain/8834), it should only take a few minutes. It also teaches you about getting free domains ๐Ÿ’ธ Be sure to put down any questions or improvements down in the comments ๐Ÿ’ฌ - and here's all the code for you to go over again ๐Ÿค– https://repl.it/@jajoosam/tyni
6
posted by jajoosam (334) 29 days ago
โ–ฒ
92
Making a Phaser Game with HTML5 and JavaScript
# Making a Phaser Game with HTML5 and JavaScript Hi guys! Everybody likes to make games, right? And everybody has their own way of doing it. In this tutorial I wanted to share a very simple, yet effective way to make games in your browser! It should be easy enough for most people with javascript knowledge to follow along and, if you want to investigate further, there are endless possibilities out there! ![image](https://storage.googleapis.com/replit/images/1539468462288_75406edb94f04ca097cc5b4705ccbc85.pn) ### Phaser As Phaser describes itself it is a fast, free and fun open source framework for Canvas and WebGL powered browser games. And it really is! It is super simple to use and is quite easy to set up. No super extensive javascript knowledge is necessary and the process of making games is fun and rewarding. It also comes with tons of extra features that you may need in some more complicated games so while it caters to starters as well, it also does not lack depth if you want to look further. Anything from image editing to complex game mechanic mathematics is possible. ##### Sites to use The official Phaser website is [here](https://phaser.io). Additionally, because we are going to use Phaser 3, the latest release, the examples on the site will most probably not work for v3. If you want some examples of v3 features the link is [here](https://labs.phaser.io/). You should not need the examples during this tutorial but if you want to learn further that is where you start. Google works as well but be careful about which version is being discussed. Version 3 is relatively new and v2 has loads more documentation and examples and tutorials on it. However, I would recommend learning v3 because it is generally better in many ways and the knowledge will last you longer and it will be more current. #### Prerequisites (what you need before doing this tutorial) The pre-requisites are: * A basic understanding of HTML, CSS and Javascript. * Knowledge in Javascript about the `this` keyword. * Some time and patience. * 3 rolls of duct tape. * Lots of cardboard * Creativity ### Let's Get Started! The repl.it project that I will be using for this tutorial is [here](https://repl.it/@kaldisberzins/Phaser-Tutorial) and the website for it if you just wanna play the game is [here](https://phaser-tutorial.kaldisberzins.repl.co/). If you ever get stumped on a step that I take in this tutorial just check the repl and see how the code looks in it. If all else fails a bit of copy-paste will solve your issues. Make a new HTML/CSS/JS repl and follow along... So, first of all we need to include the Phaser script into our website. The only piece of HTML in this tutorial will be the following:`<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>`Just paste this into your project's HTML file right above your script tag that links to `script.js`. The order is important and if you get it wrong nothing will work. If your project is not working you should definitely have a look at the order of your scripts. The Phaser script should be first. With that out of the way, let's get into making our game! The first bit of code is a standard template that is in most simple Phaser games (more advanced ones may use a slightly different structure but the idea is the same). The code looks like this: ```javascript let config = { type: Phaser.AUTO, width: 800, height: 500, physics: { default: 'arcade', arcade: { debug: false } }, scene: { preload: preload, create: create, update: update } }; const game = new Phaser.Game(config); function preload(){ } function create(){ } function update(){ } ``` While this may look alien to you, don't stress. To follow along this tutorial you don't need to understand what everything does exactly. The main things you should pay attention to are: * The three functions at the bottom `preload`, `create` and `update`. These we will fill in with the game's code. * The `width` and `height` properties. You can set these to anything you like, I did not make it `window.innerWidth` and `window.innerHeight` because scaling can quickly become messy. It is easier to make it a fixed width for everybody. So now if you run your repl you should see a black square in your browser window. Success! If you do not, make sure you have the Phaser script in the right place and that you have the code in your `script.js` exactly like above. You should also get a message in the console, something like: ```%c %c %c %c %c Phaser v3.14.0 (WebGL | Web Audio) %c https://phaser.io background: #ff0000 background: #ffff00 background: #00ff00 background: #00ffff color: #ffffff; background: #000000 background: #fff``` This may look awful in the repl.it console but if you open it in a new tab and check the console it should be a colorful banner. ### Loading Assets The `preload` function that we are going to use for this section is where you load your assets. If you want some images or audio (Phaser does that as well) in your game you first have to load it here. This way you are loading all the required assets immediately and you can use them throughout the game. I have made some assets for this tutorial so that you do not need to find or make some yourself. Go [here](https://drive.google.com/drive/folders/1TzRicUBL8V0T_9fPMaNCL6M0UEV51irQ?usp=sharing) and click download like so to get the files: ![image](https://storage.googleapis.com/replit/images/1539468612344_eeb16f0a94e74fa401749d11f7b89333.pn) If you get the files in a `.zip` folder just unzip them and drop them into your repl. Once you have them in your repl we have to load them into our game. The following code in the `preload` function will do the trick: ```javascript this.load.atlas("player", "spritesheet.png", "sprites.json"); this.load.image("platform", "platform.png"); this.load.image("spike", "spike.png"); this.load.image("coin", "coin.png"); ``` The first parameter in all of the functions is the "key" for the image. This key you would use when you need to add the image into the game. You can put it as whatever you want but make sure it is descriptive of the image in some way. I suggest you keep them the same as mine so that later code in my tutorial works for you. The second parameter is the path to the image. Because I put the assets in the same folder as the html and js files the path is just the name of the image. If you put your assets in another folder the file path string would look like `"folder_name/file_name.png"`. You may also have noticed that the first command is a bit different. It loads an __atlas__ and not an image. An atlas is a collection of images put together to make a larger image accompanied by a file that states where all the smaller images are. If you open the file `sprites.json` in the assets I gave you you should see that it contains a bunch of named objects that have x, y, width and height properties among others. Each object is an image inside the larger image. In this tutorial we will use the atlas for the player animations. All of the frames for the player (in our case only three) are in the `spritesheet.png` file. The third parameter for the atlas is the path to the `.json` file which we looked at already. If you now run the current code the screen should remain black and no errors should be in the console. If you see a web audio warning that is fine, it does not mean anything important. It's just chrome messing with you. ### Adding Objects to Our Game The `create` function is where the building of our game happens. It is run right after `preload` and is run only once. If you want to add an object to the game, this is where you do it. If you want to repeatedly create some object. Make a function (read below) that creates the object and run that as may times as you like. So we now have loaded some images but we need to have something happen on the screen. Let's add a function in the `create` function that will spawn our player in. Add this code to the `create`function: ```javascript this.spawnPlayer = ()=>{ this.player = this.physics.add.sprite(400, 250, "player", "sprite_0"); } this.spawnPlayer(); ``` I put this in a seperate function so that we can spawn the player multiple times. We are saving the player to __`this`__ which is the Phaser game object so that we can access it from anywhere. The function itself creates a sprite (image/object) that is in the Phaser physics system. The parameters are: 1. X position 2. Y position 3. Image key 4. (optional) If the image is an atlas, which frame in the atlas. There may be a few more parameters but those are not important for this tutorial. The way we find out which frame is which in the atlas is by looking at the `sprites.json` file. Find an object and look at its x and y properties. For example `sprite_2` has the following object: ```javascript "sprite_2":{"frame":{"x":0,"y":0,"w":48,"h":64}... ``` We can see that the x and y coordinates of the frame are `0, 0`. This means that it will be in the top left corner. If you look at the top left corner of the `spritesheet.png` image you will see which frame is `sprite_2`. Try changing the last parameter in the add function to be `sprite_2`. You will see that it has changed. ##### Adding a Background If the only background color we could have would be black Phaser would look really bad. Luckily enough, Phaser has an easy way to add a background to our game. Just add this code to the top of your `create` function above the `spawnPlayer` function: ```javascript this.cameras.main.setBackgroundColor('#ffffff'); ``` This sets the background color for our main camera to white. If you have not used hex color codes before don't worry about it, just know that `#ffffff` is white. The only problem with that is that now we can't see where our canvas windw starts and ends. We can fix this with a little CSS: ```css canvas{ border: 1px solid black; } ``` Now if you run your code it should look something like this: ![image](https://storage.googleapis.com/replit/images/1539468654218_bf2de4d9dc55069fde105a14f6c9818a.pn) You can see we have our little character in the middle of the screen. The background is now white. You may have noticed that the character is not offset to a side even though we put in the coordinates for the center of the screen. This is because Phaser draws images from their center. This makes it easier to center images. Another simple thing we can add to the game is a camera that follows the player. This is quite easy to do in Phaser: ```javascript this.spawnPlayer = ()=>{ this.player = this.physics.add.sprite(400, 250, "player", "sprite_0"); this.cameras.main.startFollow(this.player); }; this.spawnPlayer(); ``` The function should be quite self-explanatory and if you run it you should see no change for now. As long as you do not get any errors you are fine. ### Adding Platforms Before we start I wanted to show you the most basic way to add an image to the game. The method used above has a very specific use case (only for sprites). Here is a more general use way of doing it: ```javascript // This goes beneath the spawnPlayer function call this.platform = this.add.image(404, 302, "platform"); ``` This is good for some simple use cases like for example a logo image in your title screen. However it has its shortcomings. Imagine you want to create a map of these platforms. You would have to add `platform1` `platform2` and so on... It would be a nightmare. Let's not get started on collisions. So by now you can see why we are not going to use this to add our platforms. Instead we will have a group. Defining a new group is easy. Remove the above code and add this instead. ```javascript this.platforms = this.physics.add.staticGroup(); ``` Currently we are just defining a new static (non-moving) group and assigning it to the variable `this.platforms`. If you run this now the platform image will disappear. That is because we need to add some platforms to the group. This can be done simply like this: ```javascript //Below the spawnPlayer function this.platforms = this.physics.add.staticGroup(); this.platfroms.create(404, 302, "platform"); ``` There we go! Now we have our platform back! But what is the benefit? In a moment when we deal with collisions you will see why. For now we will leave the platforms and get back to them later. ### Keyboard Inputs As you have probably gathered by now, Phaser has made its own version of everything you may need when developing games. Keyboard inputs are no exception. Phaser even supports many ways to do keyboard inputs. We are going to do the shortest and simplest. We are going to have a bunch of variables, one for each key. And we will check each frame if any of the keys are pressed and set velocities accordingly. The code for the keyboard variables in the `create` function looks like this: ```javascript this.key_W = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W); this.key_A = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A); this.key_D = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D); ``` You do not need to understand this, just get the idea of what is happening. When a player presses a key the variable associated with that key will have `isDown` set to `true`. This makes adding keybinds really easy. Now for the rest of this section we are using the `update` function. `update` is your game loop. This function is run very fast repeatedly all throughout your game. This is where you would handle things like movement and other stuff you would want to check every frame. If you would be coding your own physics this would be where you do it. In the `update` function now let's check if the W key is pressed: ```javascript if(this.key_W.isDown){ this.player.setVelocityY(-50); }else{ this.player.setVelocityY(0); } ``` Instead of incrementing or decrementing the Y property of the player we set its velocity. We do this because it sets a velocity within Phaser which has some benefits. First of all Phaser object velocities take into account the frame rate. If every frame you increase the position X of a player, the higher the frame rate the faster the player moves. However, Phaser counteracts this. We do not need to know how, just that no matter the frame rate the player will always move at the same speed. The value we put into `setVelocityY` is the amount of pixels we want it to move in one second. If you run this now you will see that is you press the W key your character will move up. Success! Now let's add keybinds for A and D. This is only a few more lines of code: ```javascript if(this.key_A.isDown){ this.player.setVelocityX(-50); }else if(this.key_D.isDown){ this.player.setVelocityX(50); }else{ this.player.setVelocityX(0); } ``` We have this in an if/else if statement because we don't want to head left and right at the same time. We can only go in one direction or the other. And that's it! We now have linked up our keyboard keys to our Phaser game! Now it's time to deal with physics. ### Game Physics Phaser also has its own physics engine. In fact it has three but we will only use the most basic one for this tutorial. Just simple square and square collisions. Before we can do collisions, how about we add some gravity. We only need it on the player so it would look like this: ```javascript this.spawnPlayer = ()=>{ this.player = this.physics.add.sprite(400, 250, "player", "sprite_0"); this.player.body.setGravityY(800); this.cameras.main.startFollow(this.player); }; ``` Now if you run your game you will see that the player drops. But he is dropping very slowly. Why so? This is because each frame we are setting his velocity to 0 if the W key is not pressed. Previously that was needed so that he would not just fly away but now we need to remove that bit: ```javascript //In the update function if(this.key_W.isDown){ this.player.setVelocityY(-50); }/*else{ this.player.setVelocityY(0); } NO LONGER NEEDED*/ ``` Now if you run it the player falls a bit faster. You can still fly with W but we will change that in a second. #### Collisions Now that we have gotten gravity mostly out of the way let's make the player collide with the platform that we have. We can do this with one simple line of code. Add this to your `spawnPlayer` function: ```javascript this.physics.add.collider(this.player, this.platforms); ``` That's it. Just one line of code does everything. But if you run this now it will not work. The player will fall right through. And this is actually for a really stupid reason. We are running this code before we add the platforms. All you have to do is move the `spawnPlayer` function __call__ (not the function itself) below where we add the platforms. And Viola! We have the player not falling through the platform. There are some small problems that we should address before moving on. First of all, When we press W we can fly endlessly. That defeats the point of the game. To prevent this all we need to do is to only let us jump when we are on the ground. This is easy to do: ```javascript if(this.key_W.isDown && this.player.body.touching.down)... ``` When the key W is down and the player's body is touching a platform with its bottom it will jump. If you run this now you will see that the player now makes many little jumps if you press W. To make the jumps larger we have to increase the `setVelocitY`: ```javascript if(this.key_W.isDown && this.player.body.touching.down){ this.player.setVelocityY(-550); } ``` And also while we are at it we can make the left/right movement a bit faster: ```javascript if(this.key_A.isDown){ this.player.setVelocityX(-150); }else if(this.key_D.isDown){ this.player.setVelocityX(150); }else{ this.player.setVelocityX(0); } ``` So there we have it! A running and jumping player! Now let's give him a map to run around in. #### Map Building Phaser supports multiple ways to build a map (of course). However, I have decided that it would be better to cook up our own map builder that would work off of a string. Spaces would indicate that at that position there is no platform, 1 would mean that there is, 2 that this is a spawn point for the player and a dot(.) would mean that this is the end of a row. The map I designed looks something like this: ```javascript //At the top of your js file const map = '11111111111111111111111111.'+ '1 1.'+ '1 1.'+ '1 2 1 1 1 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 1.'+ '1 1 1 1 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 1.'+ '1 1 1 1 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 1.'+ '11111111111111111111111111'; ``` You can see that it is a box that is riddled with platforms. How do we turn this into a map? The parser for this that I made is only a few lines of code: ```javascript //Goes instead of the previous platform adding code this.platforms = this.physics.add.staticGroup(); let mapArr = map.split('.'); let drawX = 0; let drawY = 0; mapArr.forEach(row=>{ drawX = 0; for(let i = 0; i<row.length; i++){ if(row.charAt(i)==='1'){ this.platforms.create(drawX, drawY, "platform"); }else if(row.charAt(i)==='2'){ if(row.charAt(i+1)==='1'){ this.spawnPlayer(drawX-4, drawY-12); }else if(row.charAt(i-1)==='1'){ this.spawnPlayer(drawX+4, drawY-12); }else{ this.spawnPlayer(drawX, drawY-12); } } drawX+=40; } drawY+=40; }); ``` First we split the string that we have into an array of rows using the . that says that a row ends at that point. Then we loop through each row and at each row we loop through each character in the row. If the character is a 1, we add a platform at that place. If the character is 2 we spawn the player. I have a bit more code there that checks if there is a platform to the left or right and that nudges the character to a side just so that the player does not spawn in a platform. Also, you may have noticed that we are calling `spawnPlayer` here with some parameters. These are just x and y coordinates of where to spawn. To make that work we just have to edit the `spawnPlayer` function like so: ```javascript this.spawnPlayer = (x, y)=>{ this.player = this.physics.add.sprite(x, y, "player", "sprite_0"); this.player.body.setGravityY(800); this.cameras.main.startFollow(this.player); }; ``` Now if you run this you should get a map inside of which the player can run around. You can mess around with the map string if you want and design your own map. I would love to see what you come up with in the comments! ### Player Animations A while ago, I mentioned that we would use the atlas for player animations. Now is the time! We have three frames in our atlas and we have only used one. It's time to use the other two. Phaser has its own animation manager (by now you get the idea - Phaser === everything) that makes it super simple to do animations. First we have to set up our animations: ```javascript // At the bottom of the create function this.anims.create({ key:"walk", frames:[{key:"player", frame:"sprite_2"}, {key:"player", frame:"sprite_1"}], frameRate:10, repeat:-1 }); this.anims.create({ key:"stand", frames:[{key:"player", frame:"sprite_0"}], frameRate:1 }); ``` This creates an animation for our player that we can play when we want. The array `frames` is what Phaser will loop though and play. `frameRate` is quite self explanatory - the amount of frames that are played each second. `repeat` with the value -1 will make the animation loop again and again. Not specifying `repeat` will just make it run once. The key is the string that we can use to reference to the animation later. Just the same way as with images. Now let's run the animations when we walk right or left: ```javascript //In the update function if(this.key_A.isDown){ this.player.setVelocityX(-200); this.player.anims.play("walk", true); }else if(this.key_D.isDown){ this.player.setVelocityX(200); this.player.anims.play("walk", true); }else{ this.player.anims.play("stand", true); this.player.setVelocityX(0); } ``` The `true` parameter is just whether if there is already an animation running, should Phaser continue it? If you set this to false you will see that it will just freeze on a frame. That is because every frame it is checking if a key is pressed and then playing the animation. It will start again every frame making it look like it is frozen. Now if you run this you will see that we have a running animation with the legs moving and the hat bobbing up and down. There is only one more problem with the sprite. The player does not flip when he runs to the left. This is an easy fix: ```javascript //In the update function if(this.key_A.isDown){ this.player.setVelocityX(-200); this.player.anims.play("walk", true); this.player.flipX = true; }else if(this.key_D.isDown){ this.player.setVelocityX(200); this.player.anims.play("walk", true); this.player.flipX = false; }else{ this.player.anims.play("stand", true); this.player.setVelocityX(0); } ``` There we go! Now we have a map, player animations, keybinds, physics and most of all - a weird blob of a character who has a hat that flaps in the breeze! ### The Final Step - Spikes and Coins Now let's add some spikes that the player has to dodge and some coins that the player can collect. First, let's add a score counter in the top of the screen that displays our score: ```javascript this.spawnPlayer = (x, y)=>{ this.player = this.physics.add.sprite(x, y, "player", "sprite_0"); this.player.body.setGravityY(800); this.physics.add.collider(this.player, this.platforms); this.cameras.main.startFollow(this.player); //====================================== this.player.score = 0; this.scoreText = this.add.text(0, 0, "Score: "+this.player.score, { fill:"#000000", fontSize:"20px", fontFamily:"Arial Black" }).setScrollFactor(0).setDepth(200); }; ``` `setScrollFactor(0)` will make sure that when our camera moves, the text does not. This way it will always be in the same position in the top-left of the screen. Text is drawn from its top-left (don't ask me why it is one way for one thing and another for another) so drawing it at `0, 0` will put in the top-left corner. `setDepth(200)` will make sure the text always appears on top. We also make a variable for the score of the player that can be increased when we collect a coin. #### Coins Time to make an incentive to run and jump around. Coins will be a `c` in our map string. So, the map would now look like this: ```javascript const map = '11111111111111111111111111.'+ '1 c 1.'+ '1 c c c 1.'+ '1 2 1 1 c 1 c 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c c 1.'+ '1 c 1 1 c 1 c 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c c c 1.'+ '1 1 c 1 c 1 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c c c c 1.'+ '11111111111111111111111111'; ``` Now to make this work we have to add an option of what to do if the current character is a `c` in our map parser. I added something like this: ```javascript this.platforms = this.physics.add.staticGroup(); //================================== this.coins = this.physics.add.group(); //================================= let mapArr = map.split('.'); let drawX = 0; let drawY = 0; mapArr.forEach(row=>{ drawX = 0; for(let i = 0; i<row.length; i++){ if(row.charAt(i)==='1'){ this.platforms.create(drawX, drawY, "platform"); }else if(row.charAt(i)==='2'){ if(row.charAt(i+1)==='1'){ this.spawnPlayer(drawX-4, drawY-12); }else if(row.charAt(i-1)==='1'){ this.spawnPlayer(drawX+4, drawY-12); }else{ this.spawnPlayer(drawX, drawY-12); } //================================= }else if(row.charAt(i)==='c'){ this.coins.create(drawX, drawY+10, "coin"); } //================================= drawX+=40; } drawY+=40; }); ``` If you run this you will see that a bunch of little coins appear. But we can't collect them! This is fairly easy to add: ```javascript // Add this after the map parsing code this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this); ``` This function will check if there is an overlap between two objects. The two objects are the first two parameters. If there is an overlap, it will run the function that is passed in with the third parameter. `null` is just there for reasons and `this` is just passing on the `this` value to the function. We now need to make a function `collectCoin` that will run if there is an overlap: ```javascript this.collectCoin = (player, coin)=>{ player.score+=10; this.scoreText.setText("Score: "+ this.player.score); coin.destroy(); }; ``` If you run this you will see that you can now collect coins and increase your score. Success! There is only one more step before we are done. #### Spikes Time to add some difficulty to the game. We are going to have spikes that if you step on they will clear your score and respawn you. Let's first add them to our map as an `s`: ```javascript const map = '11111111111111111111111111.'+ '1 c 1.'+ '1 c c s c 1.'+ '1 2 1 s 1 c 1 c 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c c s s 1.'+ '1 c 1 s 1 c 1 c 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c s c c 1.'+ '1 s 1 c 1 c 1 s 1 1.'+ '1 1 1 1 1 1.'+ '1 1.'+ '1 c c c c 1.'+ '11111111111111111111111111'; ``` And now we can render them into our game: ```javascript this.platforms = this.physics.add.staticGroup(); this.coins = this.physics.add.group(); //================================== this.spikes = this.physics.add.group(); //================================== let mapArr = map.split('.'); let drawX = 0; let drawY = 0; mapArr.forEach(row=>{ drawX = 0; for(let i = 0; i<row.length; i++){ if(row.charAt(i)==='1'){ this.platforms.create(drawX, drawY, "platform"); }else if(row.charAt(i)==='2'){ if(row.charAt(i+1)==='1'){ this.spawnPlayer(drawX-4, drawY-12); }else if(row.charAt(i-1)==='1'){ this.spawnPlayer(drawX+4, drawY-12); }else{ this.spawnPlayer(drawX, drawY-12); } }else if(row.charAt(i)==='c'){ this.coins.create(drawX, drawY+10, "coin"); //================================== }else if(row.charAt(i)==='s'){ this.spikes.create(drawX, drawY+10, "spike"); } //================================== drawX+=40; } drawY+=40; }); ``` Let's do what we did last time - add an overlap detector between the player and the spikes. The code is pretty much the same: ```javascript //Next to the other overlap checker for the coins this.physics.add.overlap(this.player, this.spikes, this.die, null, this); ``` And now we have to make a function `die` that will be run when the player hits the spike. All we will do is stop the game and display text saying **YOU DIED**: ```javascript this.die = ()=>{ this.physics.pause(); let deathText = this.add.text(0, 0, "YOU DIED", { color:"#d53636", fontFamily:"Arial Black", fontSize:"50px" }).setScrollFactor(0); Phaser.Display.Align.In.Center(deathText, this.add.zone(400, 250, 800, 500)); } ``` `this.physics.pause` is what stops the game. The text adding should be pretty self explanatory. The bit that may be confusing is the line after that. This is the code I used to center the text. It accepts two arguments - the object to center and the zone in which to center it in. `this.add.zone` in turn accepts four arguments - the x, y, width and height of the zone. The x and y are in the center of the screen and the width is the width of the screen and the same for the height. When you run this code and jump on a spike you will see that it shows some big red text saying __YOU DIED__. And there we have it! Our completed game! Make sure to celebrate by wrapping __lots__ of duct tape around some cardboard. That was what the duct tape and cardboard were for. Nothing, really :). ## Final Word Thank you for sticking to the end of this monster of a tutorial. I hope you are proud of what you have made. If you liked this tutorial, please show support by voting for it. If you have any questions, suggestions or if you found a typo don't hesitate to post it in the comments! Also, if you put a spin on the game or make a cool map that be sure to share it! I would love to see what you guys can make out of this :). If you are too lazy to scroll up, the link to the repl that I made is [here](https://phaser-tutorial.kaldisberzins.repl.co/). Also, if you would like me to make some follow up tutorials outside of the competition about some more advanced features like scenes and (multiplayer?) then be sure to leave a comment. If enough people want it I will be sure to make some more tutorials. ## __EDIT__ I made an additional demo that delves into some more complicated concepts but it looks a lot better (I stole some sprites off the internet). It is just some uncommented code that you can play around with and try and see if you can make anything out of it. If you want to check it out you can find it [here](https://repl.it/@kaldisberzins/Phaser-Demo). Just wanna play it? Go [here](https://phaser-demo--kaldisberzins.repl.co/). Also, thank you guys for all the support in the comments! So heartwarming to see that many people like it. [email protected]_
18
posted by kaldisberzins (194) 3 months ago
โ–ฒ
67
Building AI: Neural Networks for beginners ๐Ÿ‘พ
Teaching Machine to recognize Hand-written Numbers! I am excited to share some of my experience studying machine learning with you, guys! I'm not an expert but I'll try to explain it the way I see it myself. I'm going to try to give you some intuition about how Neural Networks work, omitting most of the math to make it more understandable but, for the most curious of you, I'll leave the links to complete explanations/courses in the end. ![Predicted class_ 4](https://storage.googleapis.com/replit/images/1541033154259_e80e9609e421c89077d1b92aa1d33f36.pn) In 29 mins, you'll be able to configure an algorithm that's going to recognize the written digits in python :) ## **๐Ÿง  What is a Neural Network?** Imagine Neural Network as an old wise wizard who knows everything and can predict your future by just looking at you. ![magicbox](https://storage.googleapis.com/replit/images/1541033176006_a6af80c5d6034944a58c1fc595834691.pn) It turns out that he manages to do so in a very non-magical way: 1. Before you visited him, he trained, carefully studied everything about many thousands of people who came to see him before you. 2. He now collects some data about what you look like (your apparent age, the website you found him at, etc). 3. He then compares it to the historical data he has about people that came to see him before. 4. Finally, he gives his best guess on what kind of person you are based on the similarities. ![nn](https://storage.googleapis.com/replit/images/1541033195545_03966c8b28c6a5a6e037593ca2d5fd2f.gi) In very general terms, it is the way many machine learning algorithms work. They are often used to predict things based on the history of similar situations: Amazon suggesting the product you might like to buy, or Gmail suggesting to finish the sentence for you, or a self-driving car learning to drive. ## **๐Ÿ“™ Part 1: Import libraries** Let's start! I have put together a class that is doing all the math behind our algorithm and I'd gladly explain how it works in another tutorial or you could go through my comments and try to figure it out yourself if you know some machine learning. **For now, create a file called `NN.py` and paste this code:** ```python import numpy as np from scipy.optimize import minimize class Neural_Network(object): def configureNN(self, inputSize, hiddenSize, outputSize, W1 = np.array([0]), W2 = np.array([0]), maxiter = 20, lambd = 0.1): #parameters self.inputSize = inputSize self.outputSize = outputSize self.hiddenSize = hiddenSize #initialize weights / random by default if(not W1.any()): self.W1 = np.random.randn( self.hiddenSize, self.inputSize + 1) # weight matrix from input to hidden layer else: self.W1 = W1 if (not W2.any()): self.W2 = np.random.randn( self.outputSize, self.hiddenSize + 1) # weight matrix from hidden to output layerself.W2 = W2 else: self.W2 = W2 # maximum number of iterations for optimization algorithm self.maxiter = maxiter # regularization penalty self.lambd = lambd def addBias(self, X): #adds a column of ones to the beginning of an array if (X.ndim == 1): return np.insert(X, 0, 1) return np.concatenate((np.ones((len(X), 1)), X), axis=1) def delBias(self, X): #deletes a column from the beginning of an array if (X.ndim == 1): return np.delete(X, 0) return np.delete(X, 0, 1) def unroll(self, X1, X2): #unrolls two matrices into one vector return np.concatenate((X1.reshape(X1.size), X2.reshape(X2.size))) def sigmoid(self, s): # activation function return 1 / (1 + np.exp(-s)) def sigmoidPrime(self, s): #derivative of sigmoid return s * (1 - s) def forward(self, X): #forward propagation through our network X = self.addBias(X) self.z = np.dot( X, self.W1.T) # dot product of X (input) and first set of 3x2 weights self.z2 = self.sigmoid(self.z) # activation function self.z2 = self.addBias(self.z2) self.z3 = np.dot( self.z2, self.W2.T) # dot product of hidden layer (z2) and second set of 3x1 weights o = self.sigmoid(self.z3) # final activation function return o def backward(self, X, y, o): # backward propgate through the network self.o_delta = o - y # error in output self.z2_error = self.o_delta.dot( self.W2 ) # z2 error: how much our hidden layer weights contributed to output error self.z2_delta = np.multiply(self.z2_error, self.sigmoidPrime( self.z2)) # applying derivative of sigmoid to z2 error self.z2_delta = self.delBias(self.z2_delta) self.W1_delta += np.dot( np.array([self.z2_delta]).T, np.array([self.addBias(X)])) # adjusting first set (input --> hidden) weights self.W2_delta += np.dot( np.array([self.o_delta]).T, np.array([self.z2])) # adjusting second set (hidden --> output) weights def cost(self, nn_params, X, y): #computing how well the function does. Less = better self.W1_delta = 0 self.W2_delta = 0 m = len(X) o = self.forward(X) J = -1/m * sum(sum(y * np.log(o) + (1 - y) * np.log(1 - o))); #cost function reg = (sum(sum(np.power(self.delBias(self.W1), 2))) + sum( sum(np.power(self.delBias(self.W2), 2)))) * (self.lambd/(2*m)); #regularization: more precise J = J + reg; for i in range(m): o = self.forward(X[i]) self.backward(X[i], y[i], o) self.W1_delta = (1/m) * self.W1_delta + (self.lambd/m) * np.concatenate( (np.zeros((len(self.W1),1)), self.delBias(self.W1)), axis=1) self.W2_delta = (1/m) * self.W2_delta + (self.lambd/m) * np.concatenate( (np.zeros((len(self.W2),1)), self.delBias(self.W2)), axis=1) grad = self.unroll(self.W1_delta, self.W2_delta) return J, grad def train(self, X, y): # using optimization algorithm to find best fit W1, W2 nn_params = self.unroll(self.W1, self.W2) results = minimize(self.cost, x0=nn_params, args=(X, y), options={'disp': True, 'maxiter':self.maxiter}, method="L-BFGS-B", jac=True) self.W1 = np.reshape(results["x"][:self.hiddenSize * (self.inputSize + 1)], (self.hiddenSize, self.inputSize + 1)) self.W2 = np.reshape(results["x"][self.hiddenSize * (self.inputSize + 1):], (self.outputSize, self.hiddenSize + 1)) def saveWeights(self): #sio.savemat('myWeights.mat', mdict={'W1': self.W1, 'W2' : self.W2}) np.savetxt('data/TrainedW1.in', self.W1, delimiter=',') np.savetxt('data/TrainedW2.in', self.W2, delimiter=',') def predict(self, X): o = self.forward(X) i = np.argmax(o) o = o * 0 o[i] = 1 return o def predictClass(self, X): #printing out the number of the class, starting from 1 print("Predicted class out of", self.outputSize,"classes based on trained weights: ") print("Input: \n" + str(X)) print("Class number: " + str(np.argmax( np.round(self.forward(X)) ) + 1)) def accuracy(self, X, y): #printing out the accuracy p = 0 m = len(X) for i in range(m): if (np.all(self.predict(X[i]) == y[i])): p += 1 print('Training Set Accuracy: {:.2f}%'.format(p * 100 / m)) ``` ## **๐Ÿ“Š Part 2: Understanding Data** Cool! Now, much like the wizard who had to study all the other people who visited him before you, we need some data to study too. Before using any optimization algorithms, all the data scientists first try to *understand* the data they want to analyze. **Download files `X.in` (stores info about what people looked like - question) and `y.in`(stores info about what kind of people they were - answer) from [here](https://www.dropbox.com/sh/b04b2xb5j3ncir3/AABlau8wnzWmuyekJ8iVlmPga?dl=0) and put them into folder `data` in your repl.** * X: We are given 5,000 examples of 20x20 pixel pictures of handwritten digits from 0 to 9 (classes 1-10). Each picture's numerical representation is a single vector, which together with all the other examples forms an array `X`. * Y: We also have an array `y`. Each column represents a corresponding example (one picture) from `X`. `y` has 10 rows for classes 1-10 and the value of only the correct class' row is one, the rest is zeros. It looks similar to this: ``` [0, 0, 0, 0, 0, 0, 0, 0, 0, 1] # represents digit 0 (class 10) [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # represents digit 1 (class 1) ...... [1, 0, 0, 0, 0, 0, 0, 0, 1, 0] # represents digit 9 (class 9) ``` Now, let's plot it! ![TrainingData](https://storage.googleapis.com/replit/images/1541033299328_0478599ab1b7884435cc3bf629b1edc1.pn) In the end, I'd want a function `displayData(displaySize, data, selected, title)`, where * `displaySize` - the numer of images shown in any one column or row of the figure, * `data` - our X array, * `selected` - an index (if displaying only one image) or vector of indices (if displaying multiple images) from X, * `title` - the title of the figure **Create a `plots` folder to save your plots to. Also, if you use repl, create some empty file in the folder so that it doesn't disappear.** **Create a `display.py` file and write the following code in there. Make sure to read the comments:** ```python import matplotlib.pyplot as plt # Displaying the data def displayData( displaySize, data, selected, title ): # setting up our plot fig=plt.figure(figsize=(8, 8)) fig.suptitle(title, fontsize=32) # configuring the number of images to display columns = displaySize rows = displaySize for i in range(columns*rows): # if we want to display multiple images, # then 'selected' is a vector. Check if it is here: if hasattr(selected, "__len__"): img = data[selected[i]] else: img = data[selected] img = img.reshape(20,20).transpose() fig.add_subplot(rows, columns, i+1) plt.imshow(img) # We could also use plt.show(), but repl # can't display it. So let's insted save it # into a file plt.savefig('plots/' + title) return None ``` Great, we are halfway there! ## **๐Ÿ’ช Part 3: Training Neural Network** Now, after we understand what our data looks like, it's time to train on it. Let's make that wizard study! It turns out that the results of the training process of the Neural Networks have to be stored in some values. These values are called *parameters* or *weights* of the Neural Network. If you were to start this project from scratch, your initial weights would be just some random numbers, however, it would take your computer forever to train to do such a complex task as recognizing digits. For this reason, I will provide you with the initial weights that are somewhat closer to the end result. **Download files `W1.in` and `W2.in` from [here](https://www.dropbox.com/sh/b04b2xb5j3ncir3/AABlau8wnzWmuyekJ8iVlmPga?dl=0) and put them into `data` folder.** We are now ready to write code to use our Neural Network library! ![training](https://storage.googleapis.com/replit/images/1541033327074_30bf734c3f1fa129d15be3285be6e453.gi) **Create a `train.py` file and write the following code in there. Make sure to read the comments:** ```python # This code trains the Neural Network. In the end, you end up # with best-fit parameters (weights W1 and W2) for the problem in folder 'data' # and can use them to predict in predict.py import numpy as np import display from NN import Neural_Network NN = Neural_Network() # Loading data X = np.loadtxt("data/X.in", comments="#", delimiter=",", unpack=False) y = np.loadtxt("data/y.in", comments="#", delimiter=",", unpack=False) W1 = np.loadtxt("data/W1.in", comments="#", delimiter=",", unpack=False) W2 = np.loadtxt("data/W2.in", comments="#", delimiter=",", unpack=False) # Display inputs sel = np.random.permutation(len(X)); sel = sel[0:100]; display.displayData(5, X, sel, 'TrainingData'); # Configuring settings of Neural Network: # # inputSize, hiddenSize, outputSize = number of elements # in input, hidden, and output layers # (optional) W1, W2 = random by default # (optional) maxiter = number of iterations you allow the # optimization algorithm. # By default, set to 20 # (optional) lambd = regularization penalty. By # default, set to 0.1 # NN.configureNN(400, 25, 10, W1 = W1, W2 = W2) # Training Neural Network on our data # This step takes 12 mins in Repl.it or 20 sec on your # computer NN.train(X, y) # Saving Weights in the file NN.saveWeights() # Checking the accuracy of Neural Network sel = np.random.permutation(5000)[1:1000] NN.accuracy(X[sel], y[sel]) ``` **Now, you have to run this code either from:** * **Repl.it** - but you would need to move code from `train.py` into `main.py`. Don't delete `train.py` just yet. It would also take approximately 12 minutes to compute. You can watch [this](https://www.youtube.com/watch?v=z-EtmaFJieY) Crash Course video while waiting :) * **Your own computer** - just run `train.py`, which takes 20 sec on my laptop to compute. If you need help installing python, watch [this](https://www.youtube.com/watch?v=LrMOrMb8-3s) tutorial. ![trained](https://storage.googleapis.com/replit/images/1541033350157_38c60efaabc74234ee72285f0e17048b.pn) ## **๐Ÿ”ฎ Part 4: Predicting!** By now, you are supposed to have your new weights (`TrainedW1.in`,`TrainedW2.in`) saved in `data` folder and the accuracy of your Neural Network should be over 90%. Let's now write a code to use the trained weights in order to predict the digits of any new image! ![giphy](https://storage.googleapis.com/replit/images/1541033387111_bcb40155cc2e8ef38749e91c207033d8.gi) **Create a `predict.py` file and write the following code in there. Make sure to read the comments:** ```python import numpy as np import display from NN import Neural_Network NN = Neural_Network() # Loading data X = np.loadtxt("data/X.in", comments="#", delimiter=",", unpack=False) y = np.loadtxt("data/y.in", comments="#", delimiter=",", unpack=False) trW1 = np.loadtxt("data/TrainedW1.in", comments="#", delimiter=",", unpack=False) trW2 = np.loadtxt("data/TrainedW2.in", comments="#", delimiter=",", unpack=False) # Configuring settings of Neural Network: NN.configureNN(400, 25, 10, W1 = trW1, W2 = trW2) # Predicting a class number of given input testNo = 3402; # any number between 0 and 4999 to test NN.predictClass(X[testNo]) # Display output display.displayData(1, X, testNo, 'Predicted class: ' + str(np.argmax(np.round(NN.forward(X[testNo]))) + 1) ) ``` **Change the value of `testNo` to any number between 0 and 4999. In order to get a digit (class) prediction on the corresponding example from array X, run the code from:** * **Repl.it** - but you would need to move code from `predict.py` into `main.py`. Don't delete `predict.py` just yet. * **Your own computer** - just run `predict.py`. Yay, you are officially a data scientist! You have successfully: 1. Analyzed the data 2. Implemented the training of your Neural Network 3. Developed a code to predict new testing examples ![congrats](https://storage.googleapis.com/replit/images/1541033425490_308fdfc015d494a8aeceae5564956f4b.gi) ## **๐Ÿš€ Acknowledgments** Hat tip to @shamdasani whose code I used as a template for Neural Network architecture and Andrew Ng from Stanford whose data I used. Plenty of things I told you are not completely correct because I rather tried to get you excited about the topic I am passionate about, not dump some math on you! If you guys seem to enjoy it, please follow through with studying machine learning because it is just an amazing experience. I encourage you to take [this free online course](https://www.coursera.org/learn/machine-learning) on it to learn the true way it works. Also, it's my first post here and I'd appreciate any feedback on it to get better. Keep me updated on your progress, ask any questions, and stay excited! โœจโœจโœจ
14
posted by ArtemLaptiev1 (66) 3 months ago
โ–ฒ
78
Build a WhatsApp bot in 30 minutes ๐Ÿ•
A few months ago, I'd started making chatbots on [Telegram](https://t.me) - I'd seen APIs for WhatsApp but they were unoffical and there was a chance for getting your number blocked ๐Ÿ“ฑ โŒ A while ago, I saw that [Twilio](https://twilio.com) had an official WhatsApp API. 30 minutes later, I made a [Wikipedia bot on WhatsApp](https://wikibot.4ty2.fun) ๐Ÿ‘‡ ![](https://wikibot.surge.sh/Untitled-b9da3f92-94c0-4f97-8afb-787110d8a9d3.png) This is a tutorial to help you make a something like this, your own chatbots on WhatsApp - these bots are immediately available to 2 billion users, and there are so many things possible ๐ŸŽ“ I can't wait to see what you make! Now, let's get started ๐Ÿƒโ€โ™‚๏ธ ## ๐Ÿ”‘ Accounts and Keys First, Sign up for [Twilio](https://www.twilio.com/try-twilio) - it's free and you won't need a credit card ๐Ÿ’ณ ![](https://wikibot.surge.sh/screely-1535885763017-fc654067-9557-4bf7-98b5-4337911ff4ba.png) Once you're done verifying your phone number, select Procuts > Programmable SMS and then continue to name your project. ![](https://wikibot.surge.sh/screely-1535885937977-c5a924ec-8cc3-4430-9345-9b5e1dc74ef3.png) Feel free to skip steps for adding teammates - you won't need that for now. You must now take note of some authentication keys you'll need for building the WhatsApp bot ๐Ÿ‘‡ ![](https://wikibot.surge.sh/screely-1535886250966-f68b6cfb-c104-4adf-80e7-4e3f9bd15b5b.png) The final step - setup your WhatsApp Sandbox [here](https://www.twilio.com/console/sms/whatsapp/sandbox) - choose any number, and join your sandbox following instructions on the page. ![](https://wikibot.surge.sh/screely-1535886798623-1dac1ba9-c362-4e49-87ab-7bbb6138e8c7.png) Aaaaaand you're done with credential setup! Don't worry, that was the toughest part of this tutorial ๐Ÿ˜› ## ๐Ÿš€ Getting Started So that we don't spend too much time on setup, I've created an environment (with repl.it!) you can use within your browser. Head over [here](https://repl.it/@jajoosam/wikibot-start), and wait for a couple of seconds to fork it. Next, open up `server.js` and put in your Account SID and Auth Token, on lines `7` and `8` ```javascript const accountSid ="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //Account SID const authToken ="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Auth Token ``` You can see, this environment already has dependencies installed, and an `express` server set up. We still need to give Twilio a URL to send incoming messages to, though ๐Ÿ”— Let's go back to the [WhatsApp Sandbox](https://www.twilio.com/console/sms/whatsapp/sandbox), and put in a webhook URL for incoming messages. ![](https://wikibot.surge.sh/Untitled-3ed5263b-c6d8-492b-ba08-b4644ab502cf.png) This URL must be what you see on the preview panel of your [repl.it](http://repl.it) project + `/incoming` ![](https://wikibot.surge.sh/Untitled-1779b21f-9100-4942-b732-320dc48c5f76.png) We can now finally read messages that are sent to the bot. Add a simple `console.log()` in your webhook handler ๐Ÿ‘‡ ```javascript app.post('/incoming', (req, res) => { console.log(req.body) }); ``` When you send a message to your bot, you should be able to see something like this in your repl console ๐Ÿ‘จโ€๐Ÿ’ป ![](https://wikibot.surge.sh/Untitled-163eb09e-e6ab-4910-badb-d8aa0aa789f7.png) Building an echo bot would look something like this, using `twiml` to write a message ๐Ÿ‘‡ ```javascript app.post('/incoming', (req, res) => { const twiml = new MessagingResponse(); twiml.message(req.body.Body); res.writeHead(200, {'Content-Type': 'text/xml'}); res.end(twiml.toString()); }); ``` But, since we're actually trying to build a useful bot - let's use informative APIs! ## ๐ŸŒ Fetching Information DuckDuckGo has an amazing, free instant answer API. It takes in a query and returns back a summary from WikiPedia and more. A few examples ๐Ÿ‘‰ [WikiPedia](https://api.duckduckgo.com/?skip_disambig=1&format=json&pretty=1&q=WikiPedia), [Macbook Air](https://api.duckduckgo.com/?skip_disambig=1&format=json&pretty=1&q=MacBook%20Air), [Twilio](https://api.duckduckgo.com/?skip_disambig=1&format=json&pretty=1&q=Twilio) I spent some time creating a decent parser which usually returns information from this API. Try pasting this code in your [repl.it](http://repl.it) project, and your [console](https://dsh.re/f7477c) should have stuff about Trump in it ๐Ÿ˜› ```javascript var base = 'https://api.duckduckgo.com/?skip_disambig=1&format=json&pretty=1&q='; var query = 'Donald Trump'; request(base + query, function (error, response, body) { body = JSON.parse(body) if(body["Abstract"] == ""){ body["Abstract"]= body["RelatedTopics"][0]["Text"] } var msg = body["Heading"]+"\n\n"+body["Abstract"]; console.log(msg) }); ``` Pretty straight forward, right? ๐Ÿ˜„ ## ๐Ÿ› ๏ธ Putting it all together To make our actual bot, all we need to do is get the query from our request - which we can get as `req.body.Body` - and use `twmil` to send across the data we collected in `msg` ```javascript app.post('/incoming', (req, res) => { const twiml = new MessagingResponse(); var base = 'https://api.duckduckgo.com/?skip_disambig=1&format=json&pretty=1&q='; var query = req.body.Body; request(base + query, function (error, response, body) { body = JSON.parse(body) if(body["Abstract"] == ""){ body["Abstract"]= body["RelatedTopics"][0]["Text"] } var msg = twiml.message(body["Heading"]+"\n\n"+body["Abstract"]); res.writeHead(200, {'Content-Type': 'text/xml'}); res.end(twiml.toString()); }); }); ``` You now have a fully functionaing WhatsApp bot! Send anything you want to know about your bot ๐Ÿค– and you should see it respond super fast ๐Ÿ’ฌ โšก Adding welcome messages and a little formatting is quite simple, look at the final [repl](https://repl.it/@jajoosam/wikibot) to see how I did it ๐Ÿ‘จโ€๐Ÿ’ป ## ๐Ÿ”— Sharing the bot For others to use this bot, they'll need to join your sandbox first - and send a message just like you did earlier ๐Ÿ‘‰ `join <two-words>` You can create links with this text too - For example this link lets you join my bot ๐Ÿ‘‡ ``` https://wa.me/14155238886?text=join ultramarine-tapir ``` `14155238886` is my bot's number, while `ultramarine-tapir` is the sandbox phrase. ## โšก What's next? Now that you know how to build a bot on WhatsApp, try sending notifications to yourself, and building more useful tools! Twilio has loads of [other mediums](https://www.twilio.com/channels) to message through too! All code for my WikiBot is on [Github](https://github.com/jajoosam/wikibot)! I'm a 15 year old maker ๐Ÿ‘จโ€๐Ÿ’ป For more cool things to make and to stay update with my progress, sign up for [my newsletter ๐Ÿ“ง](https://buttondown.email/jajoosam)
14
posted by jajoosam (334) 3 months ago
โ–ฒ
12
Javascript Games Tutorial #2: Awari
# Javascript Games Tutorial #2: Awari ## Introduction In this tutorial, we present a variation on the Ashanti game _Awari_. We have translated the BASIC game from David H. Ahl's 1978 book, "BASIC Computer Games -- Microcomputer Edition" into Javascript. This tutorial builds on the concepts discussed in Javascript Games Tutorial #1: GuessIt. It extends those ideas by introducing a game board and a computer opponent -- these will be the main focus of the tutorial. ## The Rules Before we begin presenting code, let's define the rules of the game. The game is played on a board with 14 spaces: two rows of six small "bins", and two larger "bins", one on either side of the two rows (see the image below). ![basic_board](https://storage.googleapis.com/replit/images/1545874722135_776cc70a4ee0d8a1ed0690b0082add63.pn) In this image, the green bins (the top row and the large bin on the left), belong to the computer, and the brown ones (bottom row and large bin on the right) belong to the human. Each of the twelve small bins start with three tokens each. The large "scoring" areas are empty. ![start_board](https://storage.googleapis.com/replit/images/1545875467417_1d566d9bb6955156e27ab1170bd38043.pn) The human player moves first. You can select the tokens from any of the small bins in the bottom row. You then "sow" the tokens by dropping one into each bin, starting immediately to the right of the bin you chose and moving counterclockwise around the board. For example, if you drew the tokens in bin #4 and sowed them, this would be the result: ![sown_board](https://storage.googleapis.com/replit/images/1545875467394_24603cde782c6c200ab7b353f63268dc.pn) If the last token you sow falls into your scoring bin, you may immediately take another turn. You may only earn one extra move each turn, even if your last token lands in your scoring area during your extra move. If the last token you sow falls in an empty small bin and there are tokens in the small bin opposite it, you may collect the tokens from _both_ bins and move them into your scoring bin. Once you have completed your move (or _moves_, if you earned a bonus turn), the computer will take its turn, following the same rules. If, after any turn, all the small bins in either row are empty, the game ends and the winner is the player whose scoring area contains the most tokens. Ties are allowed. ## Concepts This tutorial will cover two topics: * Representing the game board * Creating a computer opponent As with tutorial #1, we will present high-level topics here, and provide comments within the code itself with extra context and detail. # Representing the Board The Awari board is really just a line with 14 elements that wraps around on itself: ![numbered_board](https://storage.googleapis.com/replit/images/1545876858375_b4b46f91a3d8b8caea57f547a488f7ce.pn) Notice that we have started our numbering at 0, rather than 1. We'll explain why in a bit. Each "bin" on the board holds exactly one piece of information: the number of tokens it contains. So, to represent this board in Javascript, we need a data structure consisting of adjacent cells, each of which can hold a single number. The "array" is the perfect fit. In tutorial #1, we learned to define an array as follows: ```js var board[14]; ``` It's a good convention to define "constants" (i.e., variables whose values never change) and then define your variable sizes in terms of these constants, rather than using the constants directly. So: ```js var BINS_PER_BOARD = 14; var board[BINS_PER_BOARD]; ``` This defines a linear list of cells. Recall from tutorial #1 that the first cell in the list has index '0': _board[0]_ = first cell in the list We can read values from the array as follows: ```js var numTokensInCellFive = board[4]; ``` and write them like this: ```js board[11] = 12; // puts 12 tokens in the twelfth cell ``` In general, we access the nth cell like this: _board[n - 1]_ Keep these general array behaviors in mind as we discuss the functions that will manipulate the values on the board. ## Manipulating the Board To initialize the board for a new game, we could do this: ```js var STARTING_TOKEN_COUNT = 3; // 3 tokens per bin at game start var setUpBoard = function() { for (var i=0; i<BINS_PER_BOARD; ++i) { if (i == PLAYER_SCORE_BIN || i == COMPUTER_SCORE_BIN) { board[i] = 0; } else { board[i] = STARTING_TOKEN_COUNT; } } }; ``` That's nice and simple, but we need a way to convey this configuration to the player. We can modify the 'print' function from tutorial #1 to facilitate display of an ASCII representation of the board. ```js var print = function(text, stayOnLine) { if (!text) text = ""; if (!stayOnLine) text = text + "<br>"; text = text.replace(/\s/g, "&nbsp"); document.writeln(text); document.body.style.fontFamily = "monospace"; }; ``` This new 'print' function has several key features. This line: ```js if (!text) text = ""; ``` checks to see if the 'text' argument is invalid (the '!', or 'not' operator, reverses the truth value of whatever follows. If the _text_ variable is invalid, it evaluates to _false_, and the 'not' operator will reverse this to _true_). If the text _is_ invalid, we replace it with a valid, but empty, string. This next line: ```js if (!stayOnLine) text = text + "<br>"; ``` checks the value of the 'stayOnLine' argument. Here again, we use '!' to reverse the truth value of the argument. So, is 'stayOnLine' is _false_, we add the final "<br>" line break to the text, which will force the output down to the next line. Conversely, if 'stayOnLine' is _true_, then !stayOnLine is _false_, and we don't add any <br>. *This means that the next call to _print_ will start where the last one left off.* We will use that to display our board in ASCII text. This line: ```js text = text.replace(/\s/g, "&nbsp"); ``` uses regular expressions (the text demarked with forward slashes /.../) to replace all whitespace characters (\s) globally (the trailing 'g') within the the string with html space characters ("&nbsp"). We do this because, normally, web browsers purge whitespace as they see fit. This makes it difficult to display ASCII-art style drawings. This allows us to use "regular" spaces in code and have them render as HTML spaces on the web page. Finally, we get to this line: ```js document.body.style.fontFamily = "monospace"; ``` which tells the web page to render everything in a monospace font. Modern web fonts usually render different letters in different widths. This looks beautiful, but it makes it hard to align text when displaying console output. This line causes our text to render in a monospace style, in which all characters have the same width. This makes it easy to line up all our game board elements. Armed with this modified _print_ function, we can now display the game board with a couple of relatively simple functions: ```js var PLAYER_SCORE_BIN = 6; var COMPUTER_SCORE_BIN = BINS_PER_BOARD - 1; var BINS_PER_PLAYER = 6; var drawBoard = function() { print(" COMPUTER'S BINS"); print(" ", true); drawBins(COMPUTER); print("COMP YOUR"); print("SCORE SCORE"); if (board[COMPUTER_SCORE_BIN] < 10) { print(" " + board[COMPUTER_SCORE_BIN], true); } else { print(" " + board[COMPUTER_SCORE_BIN], true); } print(" ", true); if (board[PLAYER_SCORE_BIN] < 10) { print (" " + board[PLAYER_SCORE_BIN]); } else { print (" " + board[PLAYER_SCORE_BIN]); } print(" ", true); drawBins(PLAYER); print(" (1) (2) (3) (4) (5) (6)"); print(" YOUR BINS"); print(""); ; var drawBins = function(startIndex) { var i = 0; for (i=0; i<BINS_PER_PLAYER; ++i) { var iBin = i + startIndex; if (startIndex === COMPUTER) { iBin = board.length - 2 - i; } var numTokens = board[iBin]; if (numTokens < 10) { print (" " + numTokens + " ", true); } else { print (" " + numTokens + " ", true); } } print(); }; ``` Now that we can display the board, how do we make it change in reponse to player input? Assume that we somehow obtain a number from 0-5 to identify the player bin from which to pull tokens to sow. Here is the algorithm that sows the tokens where _startIndex_ is the bin from which we are drawing tokens and _scoreBin_ is the index of the scoring bin for the current player (6 if it's the player's move and 13 if it's the computer's move): ```js var sowTokens = function(startIndex, scoreBin) { var i=0; var numTokens = board[startIndex]; // How many tokens in the selected bin? var doMoveAgain = false; var iBin = 0; // Remove tokens from the selected bins. board[startIndex] = 0; // Starting with the bin after the selected one, // loop numToken times... for (i=startIndex+1; i<startIndex+1 + numTokens; ++i) { // ...wrapping around if we went past the end of the board... iBin = i % BINS_PER_BOARD; // ...and adding 1 token to each space. board[iBin] += 1; } // Find the bin across from our end point. var acrossBin = (BINS_PER_BOARD - 2) - iBin; // Did we place the last token on our score bin? if (iBin === scoreBin) { doMoveAgain = true; } // Did our last token land in an empty bin across from a non-empty bin? else if (board[iBin] === 1 && board[acrossBin] > 0) { // Yes -- so gather up the tokens from both bins... var capturedTokens = board[acrossBin] + board[iBin]; board[acrossBin] = 0; board[iBin] = 0; // ...and pu them in our score bin. board[scoreBin] += capturedTokens; } return doMoveAgain; }; ``` There are a couple of lines that need further explanation. First, this line: ```js iBin = i % BINS_PER_BOARD; ``` As the comment says, it's supposed to wrap around if we go past the end of the board. This works because the modulus operator (%) works as follows: A % B = the remainder of A divided by B. In our case, B is always BINS_PER_BOARD, or 14. Try plugging some numbers in to see how this works. 3 % 14 = 3 7 % 14 = 7 11 % 14 = 11 etc. In fact, for any 'A' less than 14, we just get 'A' back. At exactly 14, we get 0, because 14 / 14 has a remainder of 0. Note that board[14 % 14] is the same as board[0] -- which returns us to the starting bin. 15 % 14 has a remainder of 1. So board[15 % 14] is the same as board[1]. In other words, we've wrapped back around to the second space. Similary, 18 % 14 has a remainder of 4, which wraps us back to the 5th space. And so on. Then there's this line: ```js var acrossBin = (BINS_PER_BOARD - 2) - iBin; ``` This formula returns the index of the small bin across from any other small bin. For instance, the bin across from bin the player's second bin (which has index 1) is: acrossBin = (14 - 2) - 1 = 11 With a quick glance at our diagrams above, we can see this is correct. Feel free to plug in more values to make sure you believe it. Finally, notice that sowTokens() returns the value of doMoveAgain. If this is 'true', this indicates the last token fell in the appropriate scoring bin. On the player's turn, let's assume that she presses a key from 1-6. We have to convert this text value to a number value and subtract one so it corresponds to the correct spaces in the array (0-5). We also have to ensure that the chosen bin contains some tokens. If all that is true, we then sow the tokens and watch out for the case in which the player earned a free move. That routine will look something like this: ```js var isExtraMove = false; var doPlayerMove = function(key) { // Use parseInt to convert the text value of the key // the user pressed into an integer value. Subtract 1 // because the user pressed 1-6 to select from the first // six spaces, but these correspond to locations 0-5 in // the 'board' array. var binIndex = parseInt(key) - 1; // Check: if the chosen space is empty, report an invalid move. if (board[binIndex] === 0) { // TODO: report illegal move and try again. } // Call 'sowTokens'. This distributes the tokens around the board // and returns 'true' if the last token fell in the PLAYER_SCORE_BIN. // If that hapened (sowTokens returnd 'true') and we're not already // in the extra move phrase... else if (sowTokens(binIndex, PLAYER_SCORE_BIN) && !isExtraMove) { // ...check for game over... if (isGameOver()) { // TODO: tell the player the game is over. } else { // ...grant the player a second move. isExtraMove = true; // TODO: allow player to move again. } } // If the player is already taking an extra move, or if his move // didn't end in his score bin, go here: else { if (isGameOver()) { // TODO: tell the player the game is over. } else { // TODO: let the computer move. } } }; ``` Finally, we need to check to see if the game has ended. Recall that the end game condition is either row of small bins being empty. The check for that looks like this: ```js var isGameOver = function() { var i=0; var topTotal = 0; var bottomTotal = 0; for (i=0; i<BINS_PER_PLAYER; ++i) { topTotal += board[i + PLAYER_SCORE_BIN + 1]; bottomTotal += board[i]; } return topTotal === 0 || bottomTotal === 0; }; ``` # Creating a Computer Opponent In this section, we will outline how to make a computer opponent for the game. Our opponent will not do much thinking -- it will just make the play that earns the most points, given the current state of the board. The simplest way to do that is to loop through the top row of small bins (from index 7 through 12), simulate a turn taken at each bin, and keep the best resulting board. The trick to this is using the same starting board configuration each time through the loop. This requires that we have a way to save the board, simulate a move, then restore the board: var tryAllMoves = function() { saveCurrentBoard(); for (int i=FIRST_COMPUTER_BIN; i<COMPUTER_SCORE_BIN; ++i) { if (board[i] > 0) { if (sowTokens(i, COMPUTER_SCORE_BIN)) { // TODO: go again, unless this is already a bonus move. } } loadSavedBoard(); } } Notice that we have to handle the case where the computer earns a bonus turn. This is tricky, because when this happens, we need to try all possible moves based on the new board configuration, then return to the *original* configuration and try the remaining possibilities. For example, suppose that we're trying all possible moves, and on move #3, the computer gets an extra turn. We then need to check all possible moves based on the board state after making move 3. Once we have tested those, we need to return back to the original configuration and try moves 4, 5, and 6: ![hierarchy](https://storage.googleapis.com/replit/images/1545960484387_3800468a9b78268985218545ef75be4f.pn) In this diagram, all the moves in green represent moves simulated starting from the board's current configuration. The moves in black would be simulated using the state of the board after making move 3. Ugh. This looks complicated. How are we going to use the new board configuration when simulating a bonus turn? How will we return to the old board configuration afterwards? Fortunately, there's a clever way to do all this, but first we have to learn about _variable scope_. ## Variable Scope and the Execution Stack In everyday English, **scope** means, "extent or range," and broadly refers to the breadth of a region. It's much the same in computer jargon: "scope" refers to the region of a program over which a variable is accessible. "Wait, what? Variables aren't accessible everywhere?" Short answer: usually, no. We have been writing our programs in a very sloppy way, so far, putting all our variables in the "global scope", which **is** accessible everywhere, but this isn't great practice. It's good for us because it makes life easier, allowing us to focus on the fundamentals of programming, but soon, we'll develop better habits. For now, though, we'll use a simple rule for scoping variables: **_In a Javascript program, a variable is visible to anything within the same set of curly braces._** Here's an example: ```js { var valueOut = 5; var myFunc = function() { var valueIn = 3; console.log("Sum is " + (valueOut + valueIn)); }; var myOtherFunc = function() { console.log("ValueOut: " + valueOut); console.log("valueIn: " + valueIn); // Error! }; }; ``` In this case, valueOut is visible everywhere, because everything is contained within that outer set of curly braces. The variable valueIn is visible only within the function _myFunc_, whose scope is defined by the curly braces wrapping its code. This means that _myOtherFunc_ will throw an error because it's trying to access valueIn, which is defined outside its braces. "But wait...isn't valueOut *also* defined outside the _myOtherFunc_'s braces?" Yes -- but _myOtherFunc_ is contained within the same braces that contain valueOut, so valueOut is still visible to _myOtherFunc_ -- they are both defined within the scopt of the outer braces. In contrast, _valueIn_ is defined only within the scope of _myFunc_, and _myOtherFunc_ is defined outside of _myFunc_'s scope. Now consider another example: ```js var myFunc2 = function() { var outVar = 3; for (var i=0; i<3; ++i) { var inVar = i + outVar; console.log("Value is " + inVar); } console.log("inVar = " + inVar); // Error! }; ``` The curly braces of the 'for' loop create a new _local scope_ in which we define _inVar_. Therefore, _inVar_ is available to any code inside those braces. Notice that the last _console.log_ function is outside this scope, so trying to access _inVar_ would be an error. There is one final note to add to this entire discussion: you can think of all the code you write as being surrounded by a single set of invisible braces. This is known as the _global scope_, and variables defined in that scopre are visible everywhere in your program. So far, we have been defining everything in the global scope. As we mentioned above, that makes it easy to focus on learning programming fundamentals, but it's generally not a good idea, especially for larger programs. OK, so now we know what scope is. Is that helpful? By itself, not so much, but in conjunction with the concept of the _execution stack_, it's going to solve our problem. So, what's the execution stack? Without getting too technical, it's the program that runs our program. It performs two important functions: 1) it makes sure our code runs in the correct order, and 2) it reserves memory our program needs to run, as it is running. #2 is the key for us: the execution stack is responsible for reserving the memory our program needs. More specifically, every time the stack executes one of our game functions, it reserves memory required by the variables in that function's _scope_. _This is true even if it's executing a function inside of another function._ Consider this function: var myFunc = function() { var value = 1; value = value + 1; if (value < 3) { // Call this function again. myFunc(); } }; Think through how this might work when we call it. First, the exectuion stack creates a new scope, and within that scope, it creates the variable _value_, which contains 1. Next, in adds 1 to _value_'s contents, which become 2. Since the value is less than 3, the execution stack calls _myFunc_ again. At this point, you might be tempted to think that _value_ already exists, so the next time we add 1 to it, it will increase to 3 and we will skip over the 'if', but that's not what happens. Each time the execution stack enters the _if_ statement and calls _myFunc_, it creates a _new_ scope, because this is a new call to the function. Within that scope is a new _value_ variable that gets set to 1. That means it always executes the contents of the _if_ clause. This leads to an endless cycle called _infinite recursion_. The word _recurse_ refers to the process in which a function calls itself, as in _myFunc_ above. The important takeaway here is that each version of the function maintains its own local data, which is visible only to that version of the function. With that knowledge, and these functions: ```js var copyBoard = function(srcBoard, destBoard) { destBoard.length = 0; for (var i=0; i<srcBoard.length; ++i) { destBoard.push(srcBoard[i]); } }; var saveBoard = function(boardCopy) { var i=0; boardCopy.length = 0; for (i=0; i<board.length; ++i) { boardCopy.push(board[i]); } }; var restoreBoard = function(boardCopy) { var i=0; for (i=0; i<board.length; ++i) { board[i] = boardCopy[i]; } } ``` we can now write the function that will act as the brain for our computer opponent: ```js var tryAllMoves = function(bestBoard) { // Find the best move from among the computer's six choices. var i = 0; var bestScore = -1; var bestBin = -1; var boardCopy = []; var newBestBoard = []; saveBoard(boardCopy); for (i=0; i<BINS_PER_PLAYER; ++i) { var iBin = PLAYER_SCORE_BIN + 1 + i; if (board[iBin] > 0) { if (sowTokens(iBin, COMPUTER_SCORE_BIN)) { if (!isExtraMove) { isExtraMove = true; tryAllMoves(newBestBoard); copyBoard(newBestBoard, board); } } if (board[COMPUTER_SCORE_BIN] > bestScore) { saveBoard(bestBoard); bestScore = board[COMPUTER_SCORE_BIN]; } restoreBoard(boardCopy); } } }; ``` Here are the keys to understanding how it works: 1) It accepts a single argument called bestBoard. This is an array into which the best move will be copied. 2) It defines a local variable called boardCopy into which we copy the current board configuration. 3) It uses restoreBoard(boardCopy) to return the board to it's original configuration after every simulated run. 4) If sowTokens() returns 'true', meaning the computer earned an extra turn, _it calls itself before restoring the board._ Think about #4 for a moment, then think about the last illustration with the green and black "Play #" diagram. When the computer moves, it calls _tryAllMoves_. The program will start making simulated plays. These correspond to the green "Play #" entries above. If the computer earns an extra turn, it will call _tryAllMoves_ again. Since the board hasn't been restored for this pass through the loop, it will reflect the state of the board after the current simulated play. As the program starts to execute this second version of _tryAllMoves_ (i.e., the black "Play #" entries above), it will save this modified board and start trying all the moves using **it** as the original state. Once this second version of _tryAllMoves_ has finished, it will copy its best result into the _bestBoard_ array, then exit back into the first version of _tryAllMoves_. This takes us back to the lower, green "Play #" entries in the diagram. In terms of code, we come back at the 'copyBoard(newBestBoard, board)' command inside the _if_ statement. This copies the best board from our free move back into the current board. The first version of _tryAllMoves_ then tests this configuration to see if it's the best result, and if so, copies it into the _bestBoard_ variable. Then it restores the board back to its original configuration and tries the next move. Don't worry if this makes your brain hurt. Recursion is a difficult concept to grasp. Carefully stepping through a specific example can help. But, believe it or not, that's all there is to our simple brain for Awari. There isn't much else to the program, as you will see if you examine the source code. One thing you'll notice if you play a few games is that your opponent will always choose exactly that same options, given a particular board configuration. Even if there are two plays that yield the identical amount of points, it will always pick the move closest to the player's starting goal. Here's a challenge: see if you can figure out why _tryAllMoves_ produces this behavior. Once you figure it out, see if you can modify it so that there's a chance it might pick a different move of equal worth. You will also notice that your opponent isn't smart enough to protect itself. Specifically, if it has tokens across from an empty space, these tokens are subject to theft from the human player. Can you modify _tryAllMoves_ to detect these vulnerabilities and avoid them? # Conclusion That wraps up this tutorial. We hope this gives you some idea how to represent your game board as data inside your program, as well as how to make your program "play" a game. That subject -- which blurs into "game AI" (artificial intelligence) is broad and deep -- we've barely scratched the surface of the tip of the iceberg. But, hopefully, you have some idea this solution works, and how you might adapt it for other, similar games.
11
posted by MarkKreitler (18) 28 days ago
โ–ฒ
65
A Crash Course in LOLCODE ๐Ÿฑ
# A Crash Course in LOLCODE ___ OHAI! This is a crash course in the beautiful programming language known as LOLCODE! LOLCODE is a great language, and while it is similar to many other programming languages, trying to learn it may confuse some people because of the syntax. For this tutorial, any time I say `[variable1]`, `[variable2]`, or `[variable]`, as long as I'm not talking about initializing a variable, you can put a value instead. ### Creating a new program To begin a program, you need to have the line `HAI 1.2`. This will declare that it's a LOLCODE program that's written in LOLCODE 1.2. The last line in the program must be `KTHXBYE`. ### Comments There are two different ways of doing commenting - `BTW` and `OBTW`. The differences are shown below: ```lolcode BTW This is a one-line comment. OBTW This is a multi-line comment TLDR ``` The indentation is not necessary, but it makes it easier to read. ### Variables Variables are dynamically typed in LOLCODE, so you don't have to give them a type when declared. To declare a variable, use `I HAS A [variable]`. However, if you do want to give it a type, you can use `I HAS A [variable] ITZ A [type]`. There are 4 usable types of variables: - TROOF (a boolean - `WIN` or `FAIL`, corresponding to true or false.) - NUMBR (an integer - whole numbers) - NUMBAR (a float - decimal places) - YARN (a string - text, defined by "") Variable names are case-sensitive, so INT is different from Int. You can use capital and lowercase letters, underscores, and numbers - as long as neither underscores nor numbers begin the variable name. To assign one variable to another, use `[variable 1] R [variable 2]`. ### Concatenation It's very simple to concatenate YARNS in LOLCODE - you use `SMOOSH [variables to concatenate, seperated by AN] MKAY`. It will cast any input given to it to a YARN before concatenating. See below for an example. ```lolcode I HAS A VAR1 ITZ "Hi" I HAS A VAR2 ITZ 1234 I HAS A VAR3 ITZ WIN I HAS A VAR4 ITZ SMOOSH VAR1 AN VAR2 AN VAR3 MKAY VISIBLE VAR4 BTW The output will be Hi1234WIN ``` ### Casting There are a couple different ways of casting a variable from 1 type to another. The first is `MAEK [variable] A [type]`. This will attempt to cast from whatever type the variable is to the desired type. However, this will not work if it's illogical - for instance, trying to cast letters into a NUMBR or NUMBAR. To cast a variable to a different type and save the output in a different variable, use `[variable 1] R MAEK [variable 2] A [type]`. ### Expressions There are 3 different types of expressions in LOLCODE - **Math**, **Boolean**, and **Comparison**. The basic form for all expressions is either `[expression] [variable]` or `[expression] [variable1] AN [variable2]`. #### Math In LOLCODE, you have all the typical expression types - addition, subtraction, multiplication, division, and modulus (remainder), as well as some less-common ones - min (returns the value of the smaller of 2 variables) and max (returns the value of the larger of 2 variables). If either variable is a YARN and has a decimal, it is cast to a NUMBAR for the calculation. If it doesn't have a decimal, it's cast to a NUMBR. If both variables used are NUMBRs, then integer math is performed. If one or both are NUMBARS, floating floating point math is invoked. See below for a list of math expressions: ```lolcode SUM OF [variable 1] AN [variable 2] BTW This is addition DIFF OF [variable 1] AN [variable 2] BTW This is subtraction PRODUKT OF [variable 1] AN [variable 2] BTW This is multiplication QUOSHUNT OF [variable 1] AN [variable 2] BTW This is division MOD OF [variable 1] AN [variable 2] BTW This is modulus (remainder) BIGGR OF [variable 1] AN [variable 2] BTW This returns the bigger variable's value SMALLR OF [variable 1] AN [variable 2] BTW This returns the smaller variable's value ``` #### Boolean The boolean expressions work pretty much as you would expect, comparing WINs and FAILs. You can use and, or, xor, and not. Any value passed to this is cast to a TROOF. See below for the complete list: ```lolcode BOTH OF [variable 1] AN [variable 2] BTW This is an and statement EITHER OF [variable 1] AN [variable 2] BTW This is an or statement WON OF [variable 1] AN [variable 2] BTW This is an XOR statement NOT [variable] BTW This is a not statement ALL OF [variable 1] AN ... MKAY BTW This is an infinite and statement, keep adding variable names and ANs to check more ANY OF [variable 1] AN ... MKAY BTW This is an infinite or statement, see above ``` ### Comparisons This is very similar to boolean expressions - it takes in 2 variables and checks if they're either the same or different. However, it doesn't have to be 2 TROOFS, but they do have to be the same type. "9" and 9 will NOT be recognized as the same. See below for examples ```lolcode BOTH SAEM [variable 1] AN [variable 2] BTW returns WIN if variable 1 == variable 2 DIFFRINT [variable 1] AN [variable 2] BTW returns WIN if variable 1 != variable 2 BOTH SAEM [variable 1] AN BIGGR OF [variable 1] AN [variable 2] BTW variable 1 >= variable 2 BOTH SAEM [variable 1] AN SMALLR OF [variable 1] AN [variable 2] BTW variable 1 <= variable 2 DIFFRINT [variable 1] AN BIGGR OF [variable 1] AN [variable 2] BTW variable 1 < variable 2 DIFFRINT [variable 1] AN SMALLR OF [variable 1] AN [variable 2] BTW variable 1 > variable 2 ``` ### Printing To output text, you have to use the `VISIBLE [output]` command. This can also be used with variables by using `VISIBLE [variable]`. See below for examples: ```lolcode VISIBLE "Invisible" VISIBLE INT ``` ### Input To get input from the user, you can use `GIMMEH [variable]`. For this, you MUST specify a variable because that is where the output is stored. GIMMEH stores input as a YARN, so if you want to get a NUMBR or NUMBAR you have to cast it as such. ### Conditionals Creating conditionals is fairly straightforward in LOLCODE. There are 2 basic formats - one utilizing TROOFs, and one utilizing other types of variables. To create a conditional using TROOFs, use the following: ```lolcode [expression], O RLY? YA RLY BTW This code will execute if the result of [expression] is WIN NO WAI BTW This code will execute if the result of [expression] is FAIL OIC ``` To create a conditional using other variable types is a little more involved. Basically, `OMG [value]` is the same as checking if the expression is equal to [value], and `OMGWTF` is an else. To end a statement, you must put GTFO. ```lolcode [expression], WTF? OMG 5 BTW This code will execute if the result of [expression] is 5 GTFO OMG 91 OMG 21 BTW This code will execute if the result of [expression] is 91 or 21 GTFO OMGWTF BTW This code will execute if the result of [expression] is not 5, 91, or 21 OIC ``` ### Loops Loops are a somewhat confusing beast at first, but actually aren't that hard. First, you need `IM IN YR [label for the loop - I would recommend just calling it LOOP]`. Then, if you want to increase the iterator variable have `UPPIN YR [variable]`, and if you want to decrease the iterator variable have `NERFIN YR [variable]`. Finally, if you want to go until a certain value, use `TIL [expression]`, and if you want to go while a certain expression is true, use `WILE [expression]`. To end the loop, use `IM OUTTA YR [label]`. See below for an example: ```lolcode I HAS A ITERATOR ITZ 0 IM IN YR LOOP UPPIN YR ITERATOR TIL BOTH SAEM ITERATOR AN 9 VISIBLE ITERATOR IM OUTTA YR LOOP BTW This will output 0 through 8, and then stop before printing 9. ``` ### Conclusion Aaaand that's pretty much everything I could possibly find on the internet about LOLCODE... There is documentation for functions and BUKKITs (arrays), but I couldn't get them to work and so I decided against detailing them. If you still want MOAR LOLCODE documentation, go [here](https://github.com/justinmeza/lolcode-spec/blob/master/v1.2/lolcode-spec-v1.2.md), [here](https://esolangs.org/wiki/LOLCODE), or [here](https://learnxinyminutes.com/docs/LOLCODE/). If you want a fairly simple random python with turtle spiral generator, go check out my tutorial for that [here](https://repl.it/talk/challenge/Python-Turtle-Graphics-Random-Spirals/7651). If you want a super long tutorial about how to make your own game using only python with turtle graphics, go [here](https://repl.it/talk/challenge/How-to-make-a-fairly-basic-game-using-Python-with-Turtle-Graphics/8182). If you liked this tutorial, feel free to leave an upvote. Thanks! :)
10
posted by minermaniac447 (130) 3 months ago
โ–ฒ
29
How to use a custom domain
In case you've worked with repl.it before, you may have noticed that you are given a decently long subdomain for your project, like the following: `my-little-blog--dotcomboom.repl.co` You may have wanted to change that into a shorter, more memorable domain name, like this: `mylittleblog.cf` Let's see how. ## Getting your domain This section will cover registering a domain name with Freenom. If you already have a domain, [skip this part](https://repl.it/talk/learn/How-to-use-a-custom-domain/8834#attaching-to-repl.it). ### A word on Freenom #### You don't own it Freenom hands a selection of domain names out like free candy. If you've heard of dot.tk, it's these guys. In this tutorial I'll walk you through them, but I'd also like to point out that by registering a domain name with Freenom, you are still not entitled to it. It's perfectly valid and legal for them to all of a sudden deactivate your domain and use it to make money with ads, without notification. This is just a fair warning, some people have had domains from Freenom for a long time without any issues, but anything can happen. In short, nothing could be actually free. You are borrowing Freenom's domains for your purposes, and don't own them like you would from another registrar. #### HTTPS ~~If you use Freenom, you won't be able to use HTTPS. You need to use "http://" before your domain instead. You will still be able to use your repl.co domain name if you need to use HTTPS, so make note of that.~~ **As https://repl.it/@turbio pointed out, HTTPS does work with a Freenom domain! You just need to wait a little bit of time for it to get set up.** As for "mission critical" applications, like, say, Uptime Robot or something like that, you will probably want to use your repl.co domain. ### Registering the domain You will want to make or log into a Freenom account first. Then, you will be brought to the [Client Area](https://my.freenom.com/clientarea.php). Once you're signed in, you will find "Register a New Domain" under the Services menu. ![image](https://storage.googleapis.com/replit/images/1543204601000_441af0314e3fc5e71a0139e9f766e678.pn) This will bring you to Freenom's domain search. Type in the name you want. In this example, I searched for "mylittleblog". It will give you a list of what domains they have available. ![image](https://storage.googleapis.com/replit/images/1543204667654_d8c9142df0d4f1487abdc49631a435ad.pn) Choose the one you want, then scroll down to the bottom and choose "Checkout". Don't touch the section in the middle, we'll cover that later. Choose the period you'll be using the domain, which is from 3 months to 12 months. (If memory serves, when you're going close to the due date, you are offered the opportunity to renew.) ![image](https://storage.googleapis.com/replit/images/1543204740295_ac2737cabaa59f81f9e162774f281755.pn) Check the box to agree to the Terms and Conditions (reading them is a good idea too) and complete the order. Then, go back to your client area. ## Attaching to repl.it ### Repl.it setup Go to your repl and click on the pencil icon next to the address in the preview pane. ![image](https://storage.googleapis.com/replit/images/1543205234697_4f6cbafaf7aae5af99d0c74ae423b95f.pn) Type in your domain name and if the domain is registered correctly, it will prompt you to add a `CNAME` record to your domain: ![image](https://storage.googleapis.com/replit/images/1543205288588_5e41390b98167f4e887b0de892808666.pn) Copy where the record should point to, then go to your client area at your registrar (in my case, Freenom). ### Registrar setup From [My Domains](https://my.freenom.com/clientarea.php?action=domains), go to the management page for the domain you chose. From Management Tools, choose Nameservers. Make sure it is checked to use default nameservers. ![image](https://storage.googleapis.com/replit/images/1543205432252_a191c52c1f996716d91e8266a41efd7c.pn) Then, find the area where you can add records; for Freenom, it is "Manage Freenom DNS". In the Name textbox, enter your domain name, nothing more, nothing less. Change the type to `CNAME`. Then, set the target to what repl.it asked you. Then, choose Save Changes. ![image](https://storage.googleapis.com/replit/images/1543205550116_8df6cb0139894e925b9a6fb80ea454aa.pn) ### Finish Now go back to your repl. Repl.it now should have noticed that your domain is set up correctly and now lets you click on the Link button. ![image](https://storage.googleapis.com/replit/images/1543205653984_83f953b10c6a8cbea3959c8f3709b06d.pn) You're done! Your domain should now be attached to your repl.
8
posted by dotcomboom (35) 2 months ago
โ–ฒ
21
How to make Rest Api in Python
## Introduction In today's lesson, we will learn how to build basic `Rest Api` in `Python` and `Flask`. We will specifically focus on two different way of creating apis, both will be using flask. List of two ways. 1. using flask 2. using flask extension called `flask restful` In this Lesson we are going to use `flask restful` to make our final api. But I'm also going to show to how to create one in `flask > `Note!` using flask is not the most official way of creating api. Flask is not efficient, code will look bad and have difficulty managing large files. > `flask restful` flask extension restful is the best way of creating api. Because it handlea big files wasily. Very easy to work with And it was created special for making rest apis. > I recommend you should use `flask restful` >`Here what our final api will look like` ![Api final Result](https://cdn.discordapp.com/attachments/485343377983012864/518852699572273182/restapi_final.gif) It will generate random content from a category and print it out in json format ## What is REST? Representational State Transfer (REST) is a software architectural style that defines a set of constraints to be used for creating web services. Web services that conform to the REST architectural style, termed RESTful web services, provide interoperability between computer systems on the Internet. RESTful web services allow the requesting systems to access and manipulate textual representations of web resources by using a uniform and predefined set of stateless operations. Other kinds of web services, such as SOAP web services, expose their own arbitrary sets of operations. In a RESTful web service, requests made to a resource's URI will elicit a response with a payload formatted in either HTML, XML, JSON, or some other format. For more info [click here](https://en.wikipedia.org/wiki/Representational_state_transfer) > **We will only work with json format** ## Requirements - Basic Python knowledge - Flask - Flask-Restful - Jsonify - json ## Installation Inside your repl, creating an empty file called `requirements.txt`. Once you have to empty txt file ready, copy and paste this **Flask==1.0.2** to your **requirements.txt** to install flask. ![Installation Successful](https://cdn.discordapp.com/attachments/487306993288347649/518847564968361984/unknown.png) If this started to happen, that mean flask have been install, and if not, something is wrong and should re-copy and paste `Flask==1.0.2` To install flask-restful, repeat the same procedure but use this line `Flask-RESTful==0.3.6` ## Creating web server We'll need to create basic web server. We need web server to run our code on [repl.it](https://repl.it/repls) here the basic code for creating basic web server ```python from flask import Flask from threading import Thread app = Flask('') @app.route('/') def home(): return "I'm alive" def run(): app.run(host='0.0.0.0',port=8080) t = Thread(target=run) t.start() ``` >I'm not going to explain this code, as I'm bad when it come to server stuff. But up there we just created a simple server. if you run this code this screen is going to pop up on top right hand right with this text `i'm alive`. Now we have successfully created a web server. ## Rest REST have 4 methods: - GET - PUT - POST - DELETE In this tutorial, we ony going to work with `GET` method. ## Creating api first we need to import another flask extension `jsonify`. ```python from flask import jsonify ``` >we need jsonify to convert data in json and send it out to our server. >Important Note:- we're going only going to work with json data, because json can be used in almost every modern language. ```python from flask import Flask, jsonify from threading import Thread app = Flask('') #make sure you code is between this code t = Thread(target=run) t.start() ``` here's a example of **GET** methods ```python from flask import Flask, jsonify from threading import Thread app = Flask('') @app.route('/') def home(): return "I'm alive" @app.route('/api/add', methods=['GET']) def add(): return jsonify({"2 + 2": 2 + 2}) def run(): app.run(host='0.0.0.0',port=7000) t = Thread(target=run) t.start() ``` ```python @app.route('/api/add', methods=['GET']) ``` **/api/add** is our api endpoint. you can name it what ever you want, im just gonna called it that. we need an endpoint to get to make requests to content, without any endpoint it will give you an error or simply return you home page if available. You also noticed that we in our **@app.route**. we have **methods**. We use methods to tell what kind of `rest option`, we are using. In this case we are `GET`. **ENDPOINT**= your web server url + your app route so my url is `https://rest-api-python.badvillain01.repl.co` + route `/api/add` our endpoint is `https://rest-api-python.badvillain01.repl.co/api/add` > **Note**:- your url name will be different than mine. so put your url name and route together. If you run my example you will get this result. `{"2 + 2":4}` > I recommend running my example first and once you have hold of it, then run you're owns. Now in this example we will take `user input` and convert this to json data. I'm going to use to previous example but add user input. So user will put any number and it will double user input. ```python @app.route('/api/add/<int:num>', methods=['GET']) def add(num): return jsonify({f"{num} + {num}" : num + num}) ``` You may have noticed that our in route we have this `<int:num>`. This is how to take input. `int` is specify what type of content is it. And `num` is the name of variable we will storing our input. Once we have our input. Then we are going to use `jsonify` to convert data into json format and print it out on server. If you run this code now and endpoint `/api/add/<any num you want>` i'll be using 23, json data should look something like that. ```py {"23 + 23" = 46} ``` Now he's another example that takes string as input ```py @app.route('/api/name/<string:name>', methods=['GET']) def get_name(name): return jsonify({"Your name": name}) ``` we bascially did same thing, just change our variable type to string. and if we run this example result show look like this ```json {"Your name": "bad"} ``` _That all i have for flask. If you wanna continued with flask, **Good Luck**, but i'll suggest you checkout `flask-restful`_ # Getting ready for flask-restful Api So, the api i'm creating have two main categories, `facts` and `quote`. And then inside the folder, there are four `json` files, that contain some sort of json content. _I hope this made any sense to you. If not, then i'm sorry_ ![v](https://cdn.discordapp.com/attachments/485343377983012864/518870277812256771/unknown.png) # flask-restful first we need to import two extension from `flask-restful`, We need `Resource`, and `Api`. ```py from flask_restful import Resource, Api ``` Here a simple example of **flask-restful** ```py from flask import Flask, jsonify from threading import Thread from flask_restful import Resource, Api app = Flask('') api = Api(app) class Test(Resource): def get(self): return "Example with Flask-Restful" #creating api endpoint api.add_resource(Test, '/api/restful') def run(): app.run(host='0.0.0.0',port=7210) t = Thread(target=run) t.start() ``` If you compare this with **flask**. You can clearly see this is more readable, official and best. #### How flask-restful works first we need to build `api` on top of `app`. ```py app = Flask('') api = Api(app) ``` We just creating `api`. Now we dont need `app`. And we are going to use `api` to add new content. ```py class Test(Resource): def get(self): return "Example with Flask-Restful" ``` > In `flask-restful`, all content need to be in class with `Resource` as parameters > **Methods** are a little different. You don't assign Methods in route endpoint, instead you add methods directly to class. `def get(self):` or `def post(self)` > Self: becuase we are using `def` inside class, so we need to add `self` as first parameter. To create `endpoint` in flask-restful, it's pretty easy. `api.add_resource(<your class name>, <your endpoint>)` For this example `api.add_resource(Test, '/api/restful')` If you run this now. You should get this `"Example with Flask-Restful"` **NOTE** ```py def get(self): return "Example with Flask-Restful" ``` As you see, we didn't use `jsonify`. **WHY**. Becuase the content we're returning in not `json`. So to return `json` data. Here's an example ```py def get(self): return jsonify({"Type": "flask-restful"}) ``` Output should be `{"Type":"flask-restful"}` ## Creating final Api Becuase i'm gonna be selecting random from json fromat, so i need to import `random` and also need to import `json` ```py import random import json ``` Now we are going to create a class called `Facts`. This class will return a random facts. ```py def get_facts(fact_type): if fact_type == "random": file_address = 'Facts/random.json' elif fact_type == "technology": file_address = 'Facts/technology.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) return fact class Facts(Resource): def get(self, fact_type): return get_facts(fact_type) api.add_resource(Facts, '/api/facts/<string:fact_type>') ``` So what i did that, instead of adding everything to my `Facts` class, i created a new def, outside of class. Now everythime, I'm calling Facts endpoint. It's sending requests back to `get_facts()` and return the data to `def get()`. And then this whole return the data to our server. > Creating new separate data will make your code more readable. you may wonder what this for ```py def get_facts(fact_type): if fact_type == "random": file_address = 'Facts/random.json' elif fact_type == "technology": file_address = 'Facts/technology.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) return fact ``` > **fact_type** is user input. So i only want user to choose from my inputs, so im using `if` statement to check user input. If user input is equal to one of my inputs, I'm creating a new variable called `file_address`. This will contain file address i want to open. ```py with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) ``` now once `if else` are done. We need to open the file and select random items from it. Remember, we are storing file address in `file_address`. We are going to called this file `factfile` Once we open the file, we need to load all content to `json` file. `data = json.load(factfile)` and now open we have all content in json file, we need to random select one. `fact = random.choice(list(data['Facts']))` > data:- name of variable which contain our json content > data['Facts]: `Facts` is what we want from our json file. It will randomly select one thing from `Facts` and return to `def get_fact` and this will return everything to `class Facts` and this will return it to our server. Now if you run your code, the output should look like this ![Facts Results](https://cdn.discordapp.com/attachments/485343377983012864/518911100968763392/ezgifcomvideocutter.gif) Now we are going to do the same thing with `Quotes`. We'll create a class and remember to give `Resource` parameter to class. ```py def get_quotes(quote_type): if quote_type == "motivation": file_address = 'Quotes/motivation.json' elif quote_type == "funny": file_address = 'Quotes/funny.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as quotefile: data = json.load(quotefile) quote = random.choice(list(data['Quotes'])) return quote class Quotes(Resource): def get(self, quote_type): return get_quotes(quote_type) api.add_resource(Quotes, '/api/quotes/<string:quote_type>') ``` So we are just doing the same thing we did previously. **Congrats**, you have successfully created your first rest api. >**I hope you learned something today** >**I know it's bad but i try my best to make a great tutorial** _If you have any question dm me on discord, ask in **repl.it official discord server** or comment below_ > If you see any error or mistake. Please notify me. > **Important Note**:- your repl web server won't stay up 24/7. I will die after 60 min. So i suggest using free service `UptimeRobot`. If you never used `UptimeRobot`, then read [this tutorial for help](https://repl.it/talk/learn/How-to-use-and-setup-UptimeRobot/9003) This api was just for tutorial. I'm not working on it anymore. [Please check out of my main api.](https://gold-miners-api.badvillain01.repl.co/docs.html) ### Source code https://repl.it/@badvillain01/rest-api-python
12
posted by badvillain01 (47) 2 months ago
โ–ฒ
13
Game Tutorial: Space Invaders
Hi everyone, I put together a little [Space Invaders](https://repl.it/@ericqweinstein/Space-Invaders) game and thought I'd write a tutorial on how it works. (You'll want to run it in the [REPL Run](https://space-invaders.ericqweinstein.repl.run/) environment, since Space Invaders requires a fair amount of screen real estate to play.) Feel free to fork the REPL and add to it! The game is broken up into six main files: `game.py` (which handles the game logic), `invader.py` and `fleet.py` (which handle drawing individual invaders and a whole fleet of invaders, respectively), `player.py` (which manages moving the player on the screen), `laser.py` (so the player can fire at the invading fleet), and `main.py` (which ties everything together and creates a new game). Let's look at each one in turn. ## `game.py` The `game.py` file houses our `Game` class, which manages the behavior and data needed to run a Space Invaders game. We'll go through each method one at a time, but here's the file in its entirety if you're curious: ```py import curses import datetime as dt import sys from datetime import datetime from fleet import Fleet from player import Player class Game(object): def __init__(self, stdscr): self.stdscr = stdscr self._initialize_colors() self.last_tick = datetime.now() self.window = self.stdscr.getmaxyx() self.fleet = Fleet(stdscr, self.window) self.player = Player(stdscr, self.window) def run(self): while True: self.tick() def tick(self): self.update() def update(self): new_tick = dt.timedelta(milliseconds=10) self.last_tick += new_tick self.fleet.tick(self.last_tick) self.player.tick(self.last_tick) self.detect_collisions() if self.is_over(): if self.won(): self.end('You won!') else: self.end('Oh no, you lost!') def detect_collisions(self): for laser in self.player.lasers: for invader in self.fleet.invaders: if self._collision_found(laser, invader): invader.block_color += 1 if invader.block_color == 7: self.fleet.remaining_invaders -= 1 if invader.block_color > 8: invader.block_color = 8 def won(self): return self.fleet.remaining_invaders == 0 def lost(self): return self.fleet.y() >= self.player.y def is_over(self): return self.won() or self.lost() def end(self, message): sys.stdout.write(message) sys.exit(0) def _collision_found(self, laser, invader): # Left if laser.x + laser.width < invader.x: return False # Right elif invader.x + invader.width < laser.x: return False # Above elif laser.y + 1 < invader.y: return False # Below elif invader.y + 8 < laser.y: return False return True def _initialize_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_RED) curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLUE) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_GREEN) curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_CYAN) curses.init_pair(6, curses.COLOR_YELLOW, curses.COLOR_YELLOW) curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_WHITE) curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) curses.init_pair(10, 10, 10) ``` All right! Let's start with our `__init__()` method. ```py def __init__(self, stdscr): self.stdscr = stdscr self._initialize_colors() self.last_tick = datetime.now() self.window = self.stdscr.getmaxyx() self.fleet = Fleet(stdscr, self.window) self.player = Player(stdscr, self.window) ``` As you can see, when we initialize a new game, we save a reference to `stdscr` (a window object representing the entire screen). This is part of the `curses` Python library, which you can read more about [here](https://docs.python.org/3/howto/curses.html). We also call `_initialize_colors` to set up our terminal colors (more on this soon), initialize our `last_tick` to the current time, and save references to our window dimensions (`self.window = self.stdscr.getmaxyx()`), fleet of invaders (`self.fleet = Fleet(stdscr, self.window)`), and the human player (`self.player = Player(stdscr, self.window)`). Note that our fleet of invaders and player each get passed references to the overall screen in the form of `stdscr` and `self.window`; we'll see why in a little bit. Next, our `run` method just creates an infinite loop that starts our game a-tickin': ```py def run(self): while True: self.tick() ``` As for `tick`, all we do at the moment is delegate to our `update` method. (We could imagine including other functionality here as well; even though all we do is `update`, I like wrapping that behavior in `tick`, since it creates a common API for all our game components.) ```py def tick(self): self.update() ``` As for `update`, it handles... well, updating our game! ```py def update(self): new_tick = dt.timedelta(milliseconds=10) self.last_tick += new_tick self.fleet.tick(self.last_tick) self.player.tick(self.last_tick) self.detect_collisions() if self.is_over(): if self.won(): self.end('You won!') else: self.end('Oh no, you lost!') ``` First, we create a `new_tick` equal to ten milliseconds (this is how long we wait between updatesโ€”that is, the amount of time that passes between each refresh of the game screen). We update our `self.last_tick` by adding the `new_tick` amount, then call `tick` on our fleet and player so they can update, too (passing in the `self.last_tick` in order to keep our game clock synchronized). We check to see if there are any collisions (that is, if any of the lasers fired by the player have hit any of the invaders), and finally check `self.is_over()` to see if our game has ended, providing appropriate messages depending on whether the player has won or lost (more on this soon). Let's see how we detect collisions between lasers and invaders: ```py def detect_collisions(self): for laser in self.player.lasers: for invader in self.fleet.invaders: if self._collision_found(laser, invader): invader.block_color += 1 if invader.block_color == 8: self.fleet.remaining_invaders -= 1 if invader.block_color > 8: invader.block_color = 8 ``` We loop over all the lasers and invaders, and if we find a collision (more on this soon), we do three things: 1. We increment the invader's color (this has the effect of making the invader flicker when hit, since it will cycle through all the colors from red to black); we'll see more about how colors work with the `curses` library when we get to our `_initialize_colors()` method. 2. If the invader's color is `8` (this happens to be the color black), we decrement the number of `remaining_invaders` by one (treating the invader as destroyed). 3. If the invader's color ever exceeds `8`, we just set it back to `8` (to ensure the blocks that make up the invader stay black, matching the game background). The three methods we use to check whether the game has ended are `is_over()`, `won()`, and `lost()`; each is pretty short, so let's look at them all at once. ```py def won(self): return self.fleet.remaining_invaders == 0 def lost(self): return self.fleet.y() >= self.player.y def is_over(self): return self.won() or self.lost() ``` To check if a player has `won()`, we just check whether there are no remaining invaders. A player has `lost()` when the fleet's `y` value (its height above the bottom of the screen) is greater than or equal to the player's (meaning the fleet has landed/invaded, since it's gotten down to where the player is on the screen). The game `is_over()` when the player either wins or loses. We `end()` the game like so, by writing an appropriate message (like "You won!" or "Oh no, you lost!") and exiting the program using Python's `sys.exit()`. ```py def end(self, message): sys.stdout.write(message) sys.exit(0) ``` Okay! Let's get back to collision detection. We know there's a collision if any of part of a laser overlaps with any part of an invader. This can be a little tricky to compute, since we have to take the `x` and `y` coordinates of each block into account, as well as those blocks' heights and widths. One way to do it is to say that there's no collision if we shoot wide (too far left or right), high, or low, and that otherwise, we _must_ have a collision. So! That's what we do in `_collision_found()`: we check to see if we've missed by going too far left, right, high, or low, and if we haven't missed in those directions, we must have made a hit: ```py def _collision_found(self, laser, invader): # Too far left if laser.x + laser.width < invader.x: return False # Too far right elif invader.x + invader.width < laser.x: return False # Too high elif laser.y + 1 < invader.y: # The laser is one block wide return False # Too low elif invader.y + 8 < laser.y: # The invader is eight blocks high return False return True ``` Finally, we finish up our `Game` class with a little utility method that sets all the colors we're going to use (you can read more about setting colors in `curses` using the `init_pair()` function [here](https://docs.python.org/3/howto/curses.html#attributes-and-color): ```py def _initialize_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_RED) curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLUE) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_GREEN) curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_CYAN) curses.init_pair(6, curses.COLOR_YELLOW, curses.COLOR_YELLOW) curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_WHITE) curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) curses.init_pair(10, 10, 10) ``` ## `invader.py` All right! Let's move on to our `Invader` class, where we'll start to see how to draw objects on the screen using `curses`. We'll also start to see a common API emerge among our game components: most of them have an `__init__()` method (to set up the object with attributes like location, color, and direction), a `draw()` method (to draw the object on the screen), and a `tick()` method (to determine how our game objects should change and behave with each game step). (Oftentimes, our `tick()` method just delegates to an `update()` method, but as mentioned, we wrap that for now in case we want to add extra functionality later.) Again, we'll go through each method one-by-one, but here's the whole class if you just want to dive in: ```py import curses from datetime import datetime class Invader(object): def __init__(self, stdscr, window, position): self.stdscr = stdscr self.window = window self.width = 11 self.speed = 5 self.direction = 1 self.range = (0, self.window[1] - self.width - 1) self.x = position[0] self.y = position[1] self.block_color = 1 self.empty_color = 8 self.block_width = 1 self.last_tick = datetime.now() self.move_threshold = 0.5 def __repr__(self): return [ [' ', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', ' '], [' ', ' ', ' ', 'O', ' ', ' ', ' ', 'O', ' ', ' ', ' '], [' ', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', ' '], [' ', 'O', 'O', ' ', 'O', 'O', 'O', ' ', 'O', 'O', ' '], ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'], ['O', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', 'O'], ['O', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', 'O'], [' ', ' ', ' ', 'O', 'O', ' ', 'O', 'O', ' ', ' ', ' '] ] def draw(self): for y, row in enumerate(self.__repr__()): for x, char in enumerate(row): if char == ' ': self._draw_block(x, y, self.empty_color) else: self._draw_block(x, y, self.block_color) def _draw_block(self, x, y, color): self.stdscr.addstr( self.y + y, self.x + x, ' ' * self.block_width, curses.color_pair(color) ) def _move(self, tick_number): # This is a kind of "brake" to ensure that the invaders don't move for every single game tick # (since we want to animate the player's motion quickly, but the invaders should move more slowly). if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: x = self.x + 1 x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x * self.speed * self.direction self.last_tick = datetime.now() def update(self, tick_number): self._move(tick_number) self.draw() def tick(self, tick_number): self.update(tick_number) ``` Okay! As usual, let's start by looking at our `__init__()` method: ```py def __init__(self, stdscr, window, position): self.stdscr = stdscr self.window = window self.width = 11 self.speed = 5 self.direction = 1 self.range = (0, self.window[1] - self.width - 1) self.x = position[0] self.y = position[1] self.block_color = 1 self.empty_color = 8 self.block_width = 1 self.last_tick = datetime.now() self.move_threshold = 0.5 ``` As mentioned, we start off by saving references to our screen and window objects via `self.stdscr = stdscr` and `self.window = window`. We also set a `width` (to help detect how far across the screen our invader extends) and `speed` (to control how quickly it moves), as well as a `direction` (+1 for left-to-right and -1 for right-to-left). We also set a `self.range` (equal to the max width minus one block and the width of our invader) that ensures our invaders don't try to wander off the screen, as well as `x` and `y` coordinates.(Note that we pass a `position` to our constructor to tell the invader where to draw itself on the screen; the `position` is an `(x, y`) tuple.) We set our `block_color` to `1` and `empty_color` to `8` (red and black, respectively), set our `last_tick` to the current time, and our `move_threshold` to 0.5 (this will help us slow our invaders down, ensuring they only move once every half-second). Next up is our `__repr__()` function! `__repr__()` is a built-in Python function that you can override to control the printed representation of your object. We return a two-dimensional list of characters, using `'O'` to represent a red block and `' '` to represent a black (empty) block. If you look closely, you can see it looks like our on-screen invader! ```py def __repr__(self): return [ [' ', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', ' '], [' ', ' ', ' ', 'O', ' ', ' ', ' ', 'O', ' ', ' ', ' '], [' ', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', ' '], [' ', 'O', 'O', ' ', 'O', 'O', 'O', ' ', 'O', 'O', ' '], ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'], ['O', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', 'O'], ['O', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', 'O'], [' ', ' ', ' ', 'O', 'O', ' ', 'O', 'O', ' ', ' ', ' '] ] ``` ![!invader](https://duaw26jehqd4r.cloudfront.net/items/0h3U0T1P2i1u2V0s072E/invader.png?v=a4032ac9) In order to `draw()` our invader, we iterate over the characters in our `self.__repr__()` two-dimensional list, drawing a red block when we see a `'O'` and an empty/black block when we see `' '`: ```py def draw(self): for y, row in enumerate(self.__repr__()): for x, char in enumerate(row): if char == ' ': self._draw_block(x, y, self.empty_color) else: self._draw_block(x, y, self.block_color) ``` Our `_draw_block()` method takes an `x` (column position), `y` (row position), and `color` and adds the block to the screen using `curses`' `stdscr.addstr()` method (which you can read more about [here](https://docs.python.org/3/library/curses.html#curses.window.addstr)): ```py def _draw_block(self, x, y, color): self.stdscr.addstr( self.y + y, self.x + x, ' ' * self.block_width, curses.color_pair(color) ) ``` Now that we know what we need in order to draw our invader, let's take a look at how we get it to move. Every game `tick`, we want to make a decision about our invader's position (using its `x` and `y` coordinates) so we can redraw it in its new position: ```py def _move(self, tick_number): # This is a kind of "brake" to ensure that the invaders don't move for every single game tick # (since we want to animate the player's motion quickly, but the invaders should move more slowly). if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: x = self.x + 1 x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x * self.speed * self.direction self.last_tick = datetime.now() ``` The first line of code in this method is a little confusing, but what we're doing is looking at the difference between the current time and our prior tick. If enough time has passed, update our `x` value by one (moving a little to the right), adjusting our `x` to the screen range minimum (in case we're about to fall off the left side of the screen) or the screen range maximum (in case we're about to fall off the right side of the screen). We update our `x` position by multiplying by our speed (how many columns we move per tick) and direction (+1 to go right-to-left, -1 to go left-to-right). Finally, we update our `self.last_tick` in preparation for the next game loop. In order to `update()` our screen, we just need to move and redraw: ```py def update(self, tick_number): self._move(tick_number) self.draw() ``` As mentioned, our `tick()` method just wraps `update()` for now: ```py def tick(self, tick_number): self.update(tick_number) ``` ...and that's all we need to set up our `Invader` class! Now let's look at what we need to do to organize our invaders into a `Fleet`. ## `fleet.py` Our `Fleet` class is pretty simple! We'll walk through its three methods (`__init__()`, `tick()`, and `y()`), but here's the whole thing: ```py from datetime import datetime from invader import Invader class Fleet(object): def __init__(self, stdscr, window): self.stdscr = stdscr # This is actually the width of an invader self.width = 11 self.window = window self.range = (0, self.window[1] - self.width - 1) self.invaders = [ Invader(stdscr, window, (5, 2)), Invader(stdscr, window, (20, 2)), Invader(stdscr, window, (35, 2)), Invader(stdscr, window, (50, 2)), ] self.step = 5 self.last_tick = datetime.now() self.move_threshold = 1 self.number_of_invaders = len(self.invaders) self.remaining_invaders = self.number_of_invaders def tick(self, tick_number): [invader.tick(tick_number) for invader in self.invaders] if self.invaders[self.number_of_invaders - 1].x + self.width // 2 >= max(self.range): # This is the "brake" for things that should animate more slowly than the main game loop. if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = -1 invader.y += self.step self.last_tick = datetime.now() elif self.invaders[0].x <= min(self.range): if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = 1 invader.y += self.step self.last_tick = datetime.now() def y(self): return self.invaders[0].y + 8 ``` As usual, our `__init__()` method starts by saving references to `stdscr` and `window`, as well as setting a `width` and `range` (these are actually identical to what we did in our `Invader` class, since we only need a single invader's width in order to determine whether we're about to crash into a wall). If you fork this REPL to add new functionality, fix bugs, or refactor the code, it might be a good idea to use the invader's width and range (rather than duplicating that code here)! Next, we create a list of `self.invaders`. In our case, we set up four invaders that are 15 blocks apart (`x`s of 5, 20, 35, and 50) and all at the same `y` (2). (Again, if you fork this code, it might be a good idea to set these `x` values based on the number of invaders we have, rather than hard-code them.) We also set a `step` of `5` (we'll use this to determine how far to "drop down" after our fleet has moved all the way across the screen), a `last_tick` of the current time, a `move_threshold` of 1 (similar to what we did to control the rate of movement for our invaders), a `number_of_invaders` equal to the length of our `self.invaders` list, and finally, `remaining_invaders` equal to `number_of_invaders` (we'll decrement this value as invaders are destroyed by the player). ```py def __init__(self, stdscr, window): self.stdscr = stdscr # This is actually the width of an invader self.width = 11 self.window = window self.range = (0, self.window[1] - self.width - 1) self.invaders = [ Invader(stdscr, window, (5, 2)), Invader(stdscr, window, (20, 2)), Invader(stdscr, window, (35, 2)), Invader(stdscr, window, (50, 2)), ] self.step = 5 self.last_tick = datetime.now() self.move_threshold = 1 self.number_of_invaders = len(self.invaders) self.remaining_invaders = self.number_of_invaders ``` Next, our `tick()` method controls the movement of our overall fleet. Since invaders have a tick method and can control their won left-to-right movement, we simply call `tick()` on all the invaders in `self.invaders` to move them left-to-right. We use the same "brake" we used for our invaders to prevent them from updating too quickly, and if our invading fleet is about to drive off the screen, we reverse direction, drop down, and update our `last_tick`. (The first branch in our `if` statement handles left-to-right movement causes us to drop down and reverse direction instead of falling off the right side of the screen; the `elif` handles right-to-left movement and preventing us from falling off the left side of the screen.) ```py def tick(self, tick_number): [invader.tick(tick_number) for invader in self.invaders] if self.invaders[self.number_of_invaders - 1].x + self.width // 2 >= max(self.range): # This is the "brake" for things that should animate more slowly than the main game loop. if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = -1 invader.y += self.step self.last_tick = datetime.now() elif self.invaders[0].x <= min(self.range): if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = 1 invader.y += self.step self.last_tick = datetime.now() ``` Finally, we create a helper function called `y()` that just gets the current `y` value (row position) of our invading fleet. (All our invaders have the same `y` value, so we arbitrarily take the first one in our fleet, since a fleet should include at least one invader): ```py def y(self): return self.invaders[0].y + 8 # Invaders are 8 blocks tall. ``` Again, if you fork this REPL, it might be a good idea to set an `invader.height = 8` so we don't have to sprinkle this "magic number" throughout our code in order to take the invaders' heights into account. On to the `Player` class! ## `player.py` You know the drill by now! Here's the whole `Player` class: ```py import curses from datetime import datetime from laser import Laser class Player(object): def __init__(self, stdscr, window): self.stdscr = stdscr self.width = 6 self.window = window self.range = (0, self.window[1] - self.width) self.speed = 1 self.color = 3 self.x = self.window[1] // 2 self.y = self.window[0] - 5 self.lasers = [] def draw(self): self.stdscr.erase() self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) def tick(self, tick_number): [laser.tick(tick_number) for laser in self.lasers] self._handle_user_input() self.draw() def _handle_user_input(self): instruction = self.stdscr.getch() if instruction == curses.KEY_LEFT: x = self.x - 1 elif instruction == curses.KEY_RIGHT: x = self.x + 1 else: x = self.x if instruction == ord(' '): self.lasers.append(Laser(self.stdscr, self.x, self.y)) # Ensure we don't drive off the board x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x ``` In our `__init__()` method, we do a lot of familiar things: save references to our screen (`stdscr`) and window (`window`), set a width (`self.width = 6`), a window range (so we don't fly off the screen), a speed, a color, and `x` and `y` coordinates. And just like a `Fleet` has a list of invaders, a `Player` has a list of lasers to fire! (We'll see how lasers work soon.) ```py def __init__(self, stdscr, window): self.stdscr = stdscr self.width = 6 self.window = window self.range = (0, self.window[1] - self.width) self.speed = 1 self.color = 3 self.x = self.window[1] // 2 self.y = self.window[0] - 5 self.lasers = [] ``` Our `draw()` method is pretty straightforward: we erase our old position with `self.stdscr.erase()`, then draw a new player (which is just a green rectangle) at the specified `x` and `y` coordinates. ```py def draw(self): self.stdscr.erase() self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) ``` Our `tick` method does a few things (and we could delegate some of them to an `update()` method if we wanted!): we update each laser in our array of lasers, respond to user input (which we'll cover in just a minute), and redraw the screen to reflect our changes in the terminal. ```py def tick(self, tick_number): [laser.tick(tick_number) for laser in self.lasers] self._handle_user_input() self.draw() ``` Unlike our other game object, the `Player` has to respond to human input (and the game loop has to be pretty fast in order for the animation to be fastโ€”that's why we set the overall game loop to 10 milliseconds earlier, but we use our "brake" to ensure invaders move more slowly). To accomplish that, we have our `_handle_user_input()` method: ```py def _handle_user_input(self): instruction = self.stdscr.getch() if instruction == curses.KEY_LEFT: x = self.x - 1 elif instruction == curses.KEY_RIGHT: x = self.x + 1 else: x = self.x if instruction == ord(' '): self.lasers.append(Laser(self.stdscr, self.x, self.y)) # Ensure we don't drive off the board x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x ``` Here, we use `curses`' `stdscr.getch()` method to determine what key the player is pressing, storing that in `instruction`. If it's the left arrow key (`if instruction == curses.KEY_LEFT`), we move left; if it's the right arrow key (`if instruction == curses.KEY_RIGHT`), we move right. We ensure we don't drive off the board by setting `x` to its min value (the left side of the screen) if we're about to go below that, and we set `x` to its max value (the right edge of the screen) any time we're about to go above that and drive off the right side of the screen. If the user presses the space bar (`if instruction == ord(' ')`), we fire a laser! Next up: the `Laser` class! ## `laser.py` This is a short oneโ€”here's the file in its entirety: ```py import curses class Laser(object): def __init__(self, stdscr, x, y): self.stdscr = stdscr self.x = x self.y = y self.color = 7 self.width = 1 def tick(self, tick_number): if (self.y <= 0): self.color = 8 else: self.y -= 1 self.draw() def draw(self): self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) ``` There's not a ton going on here, so while we'll still go through each method, we won't look at each code snippet individually. As usual, we have `__init__()`, `tick()`, and `draw()` methods. There's nothing you haven't seen before in `__init__()` or `draw()`, so we'll focus on `tick()`, which does two things: it changes our laser color from white to black when it reaches the top of the screen (to simulate our lasers going off the top of the terminal window), and it decrements the laser's `y` value (moving it one row up on the screen) for each tick of the game. Since a laser gets its initial `x` and `y` values from the player, the end result is a little white laser bolt flying across the screen from the player toward the invading fleet! ## `main.py` Now that we have all the pieces of our game in place, we can tie everything together neatly by creating a new `Game` instance in `game.py`: ```py import curses from curses import wrapper from game import Game def main(stdscr): curses.curs_set(False) stdscr.nodelay(True) stdscr.clear() Game(stdscr).run() if __name__ == '__main__': wrapper(main) ``` We have only a single function here, `main`, that we call at the bottom of our file (using the `wrapper` object from `curses` in order to automate all the setup and teardown work needed to neatly move from the regular REPL into the game terminal; you can read more about it [here](https://docs.python.org/3.3/library/curses.html#curses.wrapper)). Here's what `main` does: 1. It sets `curses.curs_set(False)`, which makes the cursor invisible. 2. It sets `stdscr.nodelay(True)`, which makes our `stdscr.getch()` call non-blocking (that is, our game will keep ticking while it waits for user input). 3. It clears the screen in preparation for a new game using `stdscr.clear()`. 4. Finally, we create and run a new game via `Game(stdscr).run()`. ...and that's it! ![ta-da](https://duaw26jehqd4r.cloudfront.net/items/3T090O1J1G09271x3728/space_invaders.png?v=ded56fc9) There are lots of opportunities to make this game better: making it so multiple hits are required to kill an invader, adding multiple rows of invaders, adding scorekeeping functionality, making the invaders move faster over time, and so on. The sky's the limit! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/Space-Invaders) to add more functionality.
3
posted by ericqweinstein (149) 1 month ago
โ–ฒ
36
How to draw Pixel Art on Python with Turtle!
# How to Draw Pixel Art on Python with Turtle! In this Tutorial you shall learn how to create your own pixel art on Python with Turtle, I hope you enjoy. The first step to creating our art is to import turtle and set a background. My personal tip is to use black as it works best with pixel art. Here is model code: ``` import turtle t=turtle.Turtle() wn=turtle.Screen() wn.bgcolor("Black") #Remember Speech Marks. ``` Then, for step 2, we will set the speed of your turtle to be a high value, using this command: ` t.speed(0) ` Then, now the technical aspects are over with, let us get into the real juicy code! First, we must define square. My optimum size after a lot of testing was a 20 by 20 square. You define a function using this piece of code: ``` def square(): for x in range(4): t.forward(20) t.right(90) #Remember Indentation. ``` Then, After that step, we can start making pixel art! To add colours to our squares, we use the begin_fill command. Let me show an example about how this works. ``` def Tetris_Piece_1(): for x in range(4): t.begin_fill() square() t.color("Blue") t.end_fill() t.color("Black") t.forward(20) ``` We created this function. Now we may uses it whenever we like with this command: ` Tetris_Piece_1(). ` This code will make a Horizontal line of 4 pixels, like the Tetris piece! Look at the example below to see Pac-Man also. You've come to the end of the Tutorial. However, if you would like to extend this, here are some ideas. Find a way to define Red_Square or Blue_Square. Make a video-game character. Create a model of a Tetris screen. It has lots of uses, so try it today. Remember to click on Python with Turtle and not python 2.7, Python or Django. I recommend you watch the example in larger screen by pressing Open in Repl.it. Special Credit to JSer for teaching me how to use markdown on this post! Up the pensize to 4 if you want it really blocky using this command! ` t.pensize(4) `
21
posted by John_WardWard (110) 3 months ago
โ–ฒ
13
How to program MineSweeper in Python! (fully explained in depth tutorial)
# How to program MineSweeper in Python # *** *Difficulty: moderate* **Please note: this tutorial is currently in creation!** ##### Welcome to my tutorial on how to program the classic game, MineSweeper, in Python! Before you begin, I would highly recommend playing a few games to get the hang of the rules. You can do this either [online](http://minesweeperonline.com/#beginner) or in [my program](https://repl.it/@ThomasS1/MineSweeper). ![Vintage Windows MineSweeper](https://storage.googleapis.com/replit/images/1545220146051_48f373ca2fe75d80cde9b1235a088351.pn) We will be building our game in the Python terminal, which, as you probably know, has its limitations. Instead of clicking on the square in the grid, as you do in the original game, the player will type its coordinates. But what we will create is a fully functional 9x9 MineSweeper game, that will entertain you and your friends for hours on end (sorry, that sounded very cheesy and predictable). ###### Note: I would encourage you, especially if you are a beginner, to write out the code rather than just copy it, as this will help you to understand it. ### Part 1: Planning *** You should now understand the game mechanics of MineSweeper, but before we can get started we need to think about what the *computer* does. This will help us when writing our code later. First, just to avoid confusion later, a few definitions/variables I will use: * **Bomb:** I will use this word instead of 'mine', to avoid confusion with the possessive pronoun! In our game, bombs will be represented by asterixes, `*`. * **Marker:** a flag which the player can place to help them remember any locations of bombs they have deduced. In our game, these will be represented by the unicode flag symbol, `โš`. * **Solution grid:** this contains the locations of all the bombs and numbered cells. * **Known grid:** this contains the squares that the player knows about. * **To open:** to move data from the solution grid to the known grid, when the player makes their move. Now we've got that cleared up, here's a quick outline of what the program will do. * Display menu to player. * *If* they ask for instructions, print them. * Generate random locations of 10 bombs and place them in the solution grid. * Update the numbers around them in the solution grid. * Until the player wins/loses, loop: * Display the known grid to the player and ask for their move. * *If* they chose to open a square, open it. * *If* that square is a bomb, they lose. * Offer to play again. * *If* the number in that square is a 0, open up all of the squares around it automatically, as there could not be any bombs there, and do the same if any of those squares are 0, etc etc. * *If* all squares in the grid except the 10 bomb squares are open, they win! * *If* they chose to place a marker in a square, place that marker. ### Part 2: Setting up our program structure. *** Because this is not going to be a very long program, (about 250 lines - don't worry if you do think this is quite long!) we will write almost all of it in one file. The only exception will be the instruction text, which we will put in a seperate .txt (text) file to declutter our code a bit. So, if you're going to write the program as we go along, now is the time to create your repl, call it something, and create the new file. In the panel on the left, click 'files' and then 'new file'. Call it `instructions.txt`. Paste into it the following text: ``` INSTRUCTIONS ============ The aim of MineSweeper is to determine the locations of 10 bombs, randomly placed in a 9x9 grid. On each go, you type in the coordinates of a square, e.g. E4. If there is a bomb in that square, you lose. Otherwise, the number of bombs directly surrounding that square, including diagonally, will appear in that square. If that number is a 0, the squares around it will be 'opened' automatically, as there cannot be any bombs there, to save you time. If you think you know the position of a bomb, type 'M' followed by the coordinates of that square, e.g. ME4. You win by 'opening' all of the squares except those with bombs in. NB: It is luck on the first move, and you may get to a stage where it is luck later in the game too. Good luck! ``` In Part 5 we will write the code that gets the text from the file and prints it so the user can read it. ### Part 3: Beginning our code. *** Now move back to `main.py`, and write one of the following. If you're writing your code on repl.it: ```py import random, time, copy, replit from termcolor import cprint ``` Otherwise: ```py import random, time, copy from termcolor import cprint ``` This imports four/five packages, `random`, `time`, `copy`, and certain parts of `termcolor`, which we will use later in the program. If you are on repl.it, it also imports the module `replit` which allows us to clear the terminal. If you are on a different platform and know how to clear the terminal, that's fine - just use your method whenever I refer to `replit.clear()`. If you don't, that's ok too - sometimes I provide an alternative, but if I don't, just leave it out and it should be fine. But it doesn't do anything yet. Let's change that. First things first: we need to write an introduction for the player. Here's one I made earlier, but feel free to edit it / write your own. ```py #Introduction print() cprint('Welcome to MineSweeper v.3.0!', 'red') cprint('=============================', 'red') print() print('Excited to declare version 3.0 of MineSweeper as almost fully functional!') ``` 'cprint()' is a function we imported just a minute ago. It allows us to print colour in the terminal, with the syntax `cprint('text', 'colour')`. I've set it up to print the title in red, but you can choose any of the following colours: ``` grey red green yellow blue magenta cyan white ``` Termcolor has lots of other cool features such as text highlights - see the [package website](https://pypi.org/project/termcolor/). You can now run your program and it will, for the first time, do something! Oh and, if it hasn't already it will first chuck out some rubbish about importing `termcolor`. Ignore it - it only does it once. ### Part 4: A Python Sandwich *** By the end of this tutorial, you will be sick of functions (`def example():`). Apart from one line, which initiates all of the functions, the rest of the program... will be entirely functions. Why? Because MineSweeper is a repetitive game where a 'go' always leads to one of a few outcomes. This means that it is much simpler and faster to write the rest of the code in functions. Functions allow us to do one of two things. In the mathematical sense, they can be used as a quick way of writing one equation which would otherwise take up several lines (a mathematical function is a term you have probably heard of/used). But they can also be used for more substantial pieces of code that will be used several times, or as a substitute for a loop. Functions can do both, and quite often do. This may all sound a bit wierd, but hopefully it will become clear as we continue. Let's write the last line that's not part of a function. It's pretty simple, and it goes right at the end of our program. Leave some space for the rest of the code, and type: ``` reset() ``` The code we have written so far are the slices of bread, and what we will write now is the filling. There you go: a Python Sandwich! ###### Side note: this will be a *very* well filled sandwich. ### Part 5: `def`, `def`, `def` *** If you try to run your code now, it will give you an error that looks something like this... `NameError: name 'reset' is not defined` ... because we haven't defined `reset()`. That is what we need to do next. `reset()` will be the function that runs every time we want a new game. It resets all the variables, as well as printing the menu and starting the timer. In the space we left in Part 4, write this: ```py #Sets up the game. def reset(): print(''' MAIN MENU ========= -> For instructions on how to play, type 'I' -> To play immediately, type 'P' ''') choice = input('Type here: ').upper() if choice == 'I': replit.clear() #Prints instructions. print(open('instructions.txt', 'r').read()) input('Press [enter] when ready to play. ') elif choice != 'P': replit.clear() reset() ``` This, when called upon by `reset()`, prints the main menu and asks the player for your choice - to play immediately or to see the instructions. If the player typed 'I', it clears the terminal, then gets the text from the file `instructions.txt` that we made earlier, reads it, and prints it (`print(open('instructions.txt', 'r').read())`). It then waits until the player presses enter before continuing. If the player **didn't** type 'I' or 'P', it clears the terminal and runs `reset()` again, as they didn't enter one of the given options. If the player typed 'P', it continues straight on. ### Part 6: Generating the Solution Grid *** The next thing we need to think about is generating the solution grid for each particular game. It must have 10 bombs in random places, and every cell must be marked with how many bombs surround it First, we need to set up the array (two-dimensional list) for the solution grid. We'll call it `b`, for bombs. Copy this code inside `reset()`, and make sure they are both tabulated the correct distance. ```py #The solution grid. b = [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]] ``` But what is this mess of zeros? As you can see, `b` is a list, but it also *contains* some lists - 9 to be exact. Each of these lists contain 9 objects, which are all zeros at the moment. This essentially makes it a two-dimensional list, which is called an array, to define the solution grid. The next step is generating the random locations of the 10 bombs, and placing them in the solution grid. For each bomb, we need to do three things: * Generate a random location. * Check if there's already a bomb there; if there is, go back to the first step. * Place the bomb in the solution grid. We're going to do this through another function, and you'll see why later. To call it, add this to the end of `reset()`. ```py for n in range (0, 10): placeBomb(b) ``` That should be pretty self-explanatory - it simply calls upon `placeBomb(b)` 10 times. In case you don't know, that `(b)` is there because a function can't access external variables without being passed ('given') them. Now we need to define `placeBomb(b)`. Write this *after* `reset()`. ```py #Places a bomb in a random location. def placeBomb(b): r = random.randint(0, 8) c = random.randint(0, 8) #Checks if there's a bomb in the randomly generated location. If not, it puts one there. If there is, it requests a new location to try. currentRow = b[r] if not currentRow[c] == '*': currentRow[c] = '*' else: placeBomb(b) ``` This does all three things that we mentioned earlier: First, it generates a random location in the grid through the variables `r` (row) and `c` (column). Note that they are a random number from 0 to 8, because indexing in Python starts at 0 not 1. Next, it checks if there is already a bomb at that location. It gets the row (one of the lists in `b`) and then the column (one of the values in that row). If it's *not* already a bomb, it puts one there. If it is, it runs the function again. Once it has succesfully placed a bomb, it returns to the line in `reset()` which it was called from. Since it runs `placeBomb(b)` 10 times, we end up with 10 randomly placed bombs in the grid! The next thing we need to do is make sure that all the 0s in the grid are changed to the number of bombs surrounding that square. By far the easiest and quickest way of doing this is to add 1 to the numbers in all of the squares surrounding each bomb. This will lead to all of the numbers correctly reflecting the number of bombs surrounding them. We are going to do that with this code. Write it at the end of `reset()`. ```py for r in range (0, 9): for c in range (0, 9): value = l(r, c, b) if value == '*': updateValues(r, c, b) ``` The two `for` loops go through each square in the grid, by cycling through the x and y coordinates, `r` and `c`. In the next line, we have another function which we are yet to build, `l(r, c, b)`, which gets the value at the given coordinates - we'll write that in a minute. If that value is a bomb, it updates the numbers around it through yet another function, which we will write now. `updateValues(r, c, b)` is an annoying bit of code which is too boring, repetitive and overcomplicated for the code to be worth explaining now (if you want a challenge, feel free to read through it!). I will, however, explain what it does. The variables that you pass to it are the row and column of the bomb, as well as `b`, our solution grid. It goes through all 8 squares directly surrounding the given square, and adds 1 to the value there in the solution grid, unless that square is also a bomb. Don't worry if that's unclear, I'll explain it with an example in a moment. The full function is here (copy it anywhere after the `reset()` function): ```py #Adds 1 to all of the squares around a bomb. def updateValues(rn, c, b): #Row above. if rn-1 > -1: r = b[rn-1] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if not r[c] == '*': r[c] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 #Same row. r = b[rn] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 #Row below. if 9 > rn+1: r = b[rn+1] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if not r[c] == '*': r[c] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 ``` The other function that we need is `l(r, c, b)`, which, as I mentioned earlier, gets the value in the solution grid at the given coordinates (`r` and `c`). It's a very simple bit of code, but we are going to be using it a *lot* and we will want to be able to write it as shorthand as possible (`l` stands for location). Here's the code (copy it after `reset()`): ``` #Gets the value of a coordinate on the grid. def l(r, c, b): row = b[r] c = row[c] return c ``` It's one of the more mathematical-type functions, as it only does one thing and then *returns* the output. Whenever we call this function, it will simply return the value at the given location - it doesn't change anything, it just gives us information. We'll see why this is so useful in Part 7! The first line gets the correct row from our solution grid, and the second gets the correct column from (value in) that row. The third line simply returns that value. We can actually shorten it to just two lines... ``` #Gets the value of a coordinate on the grid. def l(r, c, b): c = b[r][c] return c ``` ... because previously `row` in the second line was just standing for `b[r]`. And even to just one line! ``` #Gets the value of a coordinate on the grid. def l(r, c, b): return b[r][c] ``` Your program should now look something like [this](https://repl.it/@ThomasS1/MineSweeper-2). There shouldn't be any errors, but if you try to play a game it will just stop abruptly, because although we have the gird generation going on in the background, we haven't asked the program to print anything yet. That's all going to change in Part 7! #### Review of Part 6 Some of that was probably quite confusing, so I'll run through an example (with a smaller grid). If you feel comfortable so far, skip to Part 7. We started by setting up the variable for the solution grid, a two-dimensional list of zeros. So at the moment, our solution grid looks like this: ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` Next, we added 10 random bombs (I'll just do 4 here as it's a smaller grid): ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 0 โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` Then we went through each bomb and added one to each of the numbers around it, to get this: ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 0 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ 3 โ•‘ 3 โ•‘ 2 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 2 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` ### Part 7: Printing the Grid *** Part 6 was by far the longest and most complicated stage we've done, so well done for getting this far. Part 7 should be a bit easier! In this stage, we're going to write a function which prints the grid so our player can see it. Of course in the real game we'll print a blank grid at first, but so that we can see that the grid generation worked we'll print the solution grid for now. The function we're going to write will be called `printBoard(b)`. Eventually, it should output something like this (using our smaller example from earlier): ``` A B C D E โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— 0 โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 1 โ•‘ 2 โ•‘ 3 โ•‘ 3 โ•‘ 2 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 2 โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 2 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 3 โ•‘ 1 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 4 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` The letters at the top and the numbers to the left will be used as coordinates when the player types their chosen move. To create the grid shape, we'll use some of the Unicode box-drawing characters: `โ•โ•‘โ•”โ•ฆโ•—โ• โ•ฌโ•ฃโ•šโ•ฉโ•` To start our function, type this: ```py #Prints the given board. def printBoard(b): ``` The first thing we need to do in our function is to move the grid from the player's previous go off the visible screen. We can do this in a couple of different ways: * Print 40 or so empty lines * Use the repl.it `clear()` function This will trick the player into thinking that we are updating the grid each time they make a move rather than reprinting it. To do this, type one of these inside the new function: If writing your code on repl.it: ```py replit.clear() ``` Otherwise: ```py for n in range (0, 40): print() ``` Next, we need to print the letters and then the 'top' of the grid/board/box. These two lines will do that: ```py print(' A B C D E F G H I') print(' โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•—') ``` Now for the slightly trickier bit. Until now, what we've printed will always be the same, whatever the board (`b`) that was passed to the function. But now we need to write the code that prints the grid *with the correct values in it*. It needs to get the data at each location in the grid. How do we do that? With `l(r, c, b)` of course! This is the code we're going to use - I'll explain it in just a second. Add it to our new function: ```py for r in range (0, 9): print(r,'โ•‘',l(r,0,b),'โ•‘',l(r,1,b),'โ•‘',l(r,2,b),'โ•‘',l(r,3,b),'โ•‘',l(r,4,b),'โ•‘',l(r,5,b),'โ•‘',l(r,6,b),'โ•‘',l(r,7,b),'โ•‘',l(r,8,b),'โ•‘') if not r == 8: print(' โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ') ``` Don't panic - this is a *lot* less complicated than it looks. Because we need to print a 9x9 grid, the `for` loop runs the above code 9 times, changing `r` (the row number) each time. Let's break down the `print()` line that follows: ![image](https://storage.googleapis.com/replit/images/1545223996589_a30373455341fcc31890e5cdeab8bab2.pn) As you can see, it is essentially made up of three things - the line number, the box-drawing characters, and the `l(r, c, b)` functions. Not as complicated as it might have looked at first, hopefully! The next two lines are an `if` statement, which prints the line that runs between each row on the board... `โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ` ... unless the row number is 8 (the 9th and last row). After the last row, we need to print the closing line - to do this, add the following after the loop: ```py print(' โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•') ``` And that's the `printBoard()` function finished! The complete function should look like this: ```py #Prints the given board. def printBoard(b): replit.clear() print(' A B C D E F G H I') print(' โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•—') for r in range (0, 9): print(r,'โ•‘',l(r,0,b),'โ•‘',l(r,1,b),'โ•‘',l(r,2,b),'โ•‘',l(r,3,b),'โ•‘',l(r,4,b),'โ•‘',l(r,5,b),'โ•‘',l(r,6,b),'โ•‘',l(r,7,b),'โ•‘',l(r,8,b),'โ•‘') if not r == 8: print(' โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ') print(' โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•') ``` Now all we need to do is check that it - and all the code we've done so far - work! To do this, we just need to call the function, `printBoard(b)`, at the end of `reset()`. Just type `printBoard(b)`, and run the program. When the main menu comes up, type 'I' to check that the instructions are working, and then press enter to see your randomly generated grid! Working? Great - proceed to Part 8. Somthing wrong? Your entire program should now look like [this](https://repl.it/@ThomasS1/MineSweeper-3). ### Part 8: So nearly ready to start the gameplay! *** We are so nearly ready to start programming the actual gameplay - there are just a few more things we need to set up, primarily the **known grid**. Way back in Part 1 I mentioned the known grid. It contains the squares that the player knows about, and, unlike the solution grid, it changes when the player makes their move. At first, of course, it needs to be blank, as the player doesnt know anything about the grid. In `reset()`, *before* the line where we call on `printBoard(b)`, add the following: ```py #Sets the variable k to a grid of blank spaces, because nothing is yet known about the grid. k = [[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']] ``` `k`, the known grid, is another array like our solution grid, but it contains just blanks spaces because at the beginning of the game the player knows nothing about the solution grid. Data will be copied into it from the solution grid as the player makes their moves. We still have the line `printBoard(b)` at the end of the `reset()` function, but we don't want the player to be given the solution grid at the beginning! Instead, we want them to see a blank grid. To do this, we just need to pass the known grid to `printBoard` rather than the solution grid. So just change it to `printBoard(k)`! If you run your code now, when you start the game you should just see a blank grid; if not, check your code looks like [this](https://repl.it/@ThomasS1/MineSweeper-4). ### Part 9: Ready Player One *** We've now set up pretty much everything we need, and can (finally, I hear you sigh) get started on the gameplay! *Minesweeper* isn't just about locating all of the bombs in the grid, it's also about doing that in the fastest time possible. So let's set up a timer so players can see how well they did. The timer will work by getting the exact time at the start of the game, and then the same when the player wins/loses. It will then find the difference between them, and that's our time! So at the end of `reset(` add the following lines: ```py #Start timer startTime = time.time() ``` We'll then initiate gameplay by calling on our yet-to-be-created `play()` function: ```py #The game begins! play(b, k, startTime) ``` Now, of course, we need to write the `play()` function. It's the longest function in the program, at, excluding comments, about 40 lines long. Each time we run through the `play()` function, one 'go' happens - i.e, the player places their move and we update the known grid / end the game accordingly. Here's a rough breakdown of what the function will do: * Let the player choose a square (which will be verified as a valid square though another function, `choose()`). * *If* the player asked to place a marker: * Change the value in the known grid at the given location to the marker symbol, `โš`. * *Otherwise*: * Get the value at the given location. * *If* it is a bomb: * Tell the player they lose, and offer them the chance to play again. * *Otherwise*: * Change the value in the known grid at the given location to the real value. * If it is a zero, run `checkZeros()` (another function - we will make this later). * *If* there are only 10 'unopened' squares left in the grid: * Tell the player that they win, and offer them the chance to play again. So now let's start writing the function. As usual, we need to write `def play()`, but since our function needs the solution and known grids, as well as the time the game started (`b`, `k`, `startTime`), we'll write the following: ```py def play(b, k, startTime): ``` I appreciate that this is probably really annoying, but we are going to start our function by calling another funtion, `choose()`, which we'll write later. We will use it to get the coordinates of the square that our player wants to 'open' / place a marker in, which `play()` will use - for this reason, rather than just calling `choose()`, we want to set a variable to the output from that function. In fact, since `choose()` gives us two variables (the coordinates of the square that the player chose), we'll write this: ```py #Player chooses square. c, r = choose(b, k, startTime) ``` We now want to find out what the value at that square is, so we'll add the following, using our `l(r, c, b)` function from earlier: ```py #Gets the value at that location. v = l(r, c, b) ``` If there's a bomb in that location, we need to end the game. We also need to show the player the solution grid, tell them their time, and offer them the chance to play again. Write this in `play()` - it should be pretty self explanatory: ```py #If you hit a bomb, it ends the game. if v == '*': printBoard(b) print('You Lose!') #Print timer result. print('Time: ' + str(round(time.time() - startTime)) + 's') #Offer to play again. playAgain = input('Play again? (Y/N): ').lower() if playAgain == 'y': replit.clear() reset() else: quit() ``` The fourth line in the if statement just calculates the time the game took, by subtracting the start time from the current time, and prints it. Now, assuming that the game hasn't just ended, we need to put the value that the player 'found' into the known grid. To do that, we just write the following: ```py #Puts that value into the known grid (k). k[r][c] = v ``` Now, if that value is a zero, we need to run `checkZeros()`, a function that we'll write later. It will open up any unopened squares around that zero. Just add this: ```py #Runs checkZeros() if that value is a 0. if v == 0: checkZeros(k, b, r, c) printBoard(k) ``` The last thing to do in this function is to work out if the player has won. We'll do this by counting up how many unopened squares there are left.
4
posted by ThomasS1 (12) 1 month ago
โ–ฒ
5
Creating Functions in Ruby: Part II
[First Part](https://repl.it/talk/learn/Creating-Functions-in-Ruby/9859) # Returning a value You may have noticed in the first tutorial that all the functions outputted text the console. Perhaps you don't want to do that. Maybe you just want a function to **return** a value. Every function returns a value. The value returned will be the last line of code that outputs a value. For example. ```ruby def my_function var1 = nil var2 = false var3 = true var3 end ``` Calling this function would return `true`. If we added `puts my_function` to the code it would output `true` to the console, the value of `var3`, which is the last line in the function. But what if we wanted to return `var1` or `var2`? That's where the `return` keyword comes in play. ```ruby def my_function var1 = nil var2 = false var3 = true return var1 end ``` When we call this function, we get the value of `var1`. We can output this to the console using `puts` or manipulate it in any way you want. # Variable arguments We know that we can specify multiple arguments for a function by seperating them by commas like this: `def func(arg1, arg2)`. But what if we don't know how many arguments our function will be taking? That's where **variable arguments** comes in hand. A variable argument looks like this: `*variable_name`. You see that asterisk? That tells Ruby that you are specifying a variable argument. Instead of passing a single value to the function, it passes an array. Here's an example. ```ruby def var_func_test(*args) puts *args[0] puts *args[1] puts *args[2] end var_func_test("Potato", 1, true) ``` This outputs: ``` Potato 1 true ``` What the function is doing with `*args` is storing all of the info given in an array. The array for this function would look like `["Potato", 1, true]`. # Some things to note 1. Variables declared within a function are inaccessible outside of the function, unless the variable is a global or class variable. 2. Functions can be put inside functions! You would call them like this: `func1::func2`. 3. You can create a default value for parameters. Like this: `def func(var1 = true)`. Now the function will automatically set var1 to true unless you set it to something else in the calling. Thanks for reading my tutorial. I hope it helped. Perhaps a part III might make it's way out.
2
posted by Zavexeon (31) 12 days ago
โ–ฒ
36
A Quick Guide to Repl.it Talk Markdown
Guys! In this quick tutorial, I'll be showing you how to make your posts pretty using Markdown styling! # Headers First we'll learn about headers. A header starts with a hash symbol `#` followed by a space: ```md # A header ``` Output: # A header Headers can be a variety of sizes. A smaller header starts with more hash symbols. The number of hash symbols can be 1 to 6: ```md #### A header with 4 hash symbols ``` Output: #### A header with 4 hash symbols Alternatively, you can also "underline" a text with `=` or `-` to produce headers ```md First header (Same as # First header) -------------- Second header (Same as ## Second header) =========== ``` ___ # Text Styles You can make *italic*, **bold** or ~~strikethrough~~ text. Put the text between a pair of `*` or `_` to make it *italic*. ```md *italic text* _also italic_ ``` Output: *italic text* _also italic_ Put two `*` or `_` on both sides of text to make it **bold**. ```md **bold text** __also bold__ ``` Output: **bold text** __also bold__ You can also do a ~~strikethrough~~ by putting two tildes (`~`) on each side: ```md ~~strikethrough~~ ``` Output: ~~strikethrough~~ It's ok to mix up those stylings: ```md **_~~bold, italic and strikethrough~~_** ``` Output: **_~~bold, italic and strikethrough~~_** ___ # Lists There's two kind of lists in Markdown: **unordered** (bulleted) and **ordered** (numbered). Since repl.it talk Markdown doesn't support ordered lists (sadly), we'll only deal with unordered lists. An unordered list item starts with either a `*`, `+` or `-` followed by a space: ```md * this + that - and stuff ``` Output: * this + that - and stuff Use indentations of 2 spaces to make sublists ```md * list item * sublist item * yet another sublist item ``` Output: * list item * sublist item * yet another sublist item ___ # Links Just paste the URL and it'll work: ```md https://repl.it ``` Output: https://repl.it If you want **custom link text**, try this: `[link text](URL)`: ```md [Repl.it](https://repl.it) ``` Output: [Repl.it](https://repl.it) ___ # Images The Markdown syntax for **images** is pretty simple: `![alt text](URL)`: ```md ![Repl.it logo](https://repl.it/public/images/icon-square.png) ``` Output: ![Repl.it logo](https://repl.it/public/images/icon-square.png) **Wait... what if my image is stored in my computer? It doesn't have a URL!** Well, repl.it provided an easy way to upload images. All you need is to click the **select files** button below to upload it. After that, you'll see the Markdown code for your image in the text box. ___ # Code And finally, code!! **Inline code** and **code blocks** are widely used in repl.it talk since repl.it talk is a platform for coders to share. Wrap a pair of **backticks** (`` ` ``) around text to make a span of code (inline code): ```md `$ node index.js` ``` Output: `$ node index.js` To indicate a block of code, put three backticks (` ``` `) at both the start and end of your code: ````md ``` This is a code block. Everything here is monospaced. ``` ```` Output: ``` This is a code block. Everything here is monospaced. ```` Additionally, repl.it supports code block syntax highlighting, which is pretty useful for emphasizing readability. Just put the language name (or its short form) after the three backticks: ````md ```js while (true) console.log("MARKDOWN IS AWESOME!!!"); ``` ```` Output: ```js while (true) console.log("MARKDOWN IS AWESOME!!!"); ``` ___ # Blockquotes To do blockquotes put a `>` before each line of the block: ```md > Timchen is the greatest and > we should praise him ``` Output: > Timchen is the greatest and > we should praise him Don't forgot to leave a blank line after each blockquote! ___ # Horzontal rules A horzontal rule (a line that separates content) can be made of either three asterisks (`*`) or underscores (`_`): ```md There's a horizontal rule below *** There's a horizontal rule above ``` Output: There's a horizontal rule below *** There's a horizontal rule above That's all what I can teach in this very tutorial. Start using Markdown to style your posts, and find more about it!
11
posted by JSer (934) 4 months ago
โ–ฒ
18
Making a Discord bot in Ruby!
# How to get started ### Step 1: install discordrb The discordrb gem (library) is required to make a discord bot in Ruby. You can install it by typing ``` `gem install discordrb` ``` or by using bundler. Then, in your code put `require "discordrb"` on the first line. ### Step 2: setup your bot Assuming that you have created your bot on Discord already, you need to create a bot by doing the following (of course, you can change the name): ```ruby @my_bot = Discordrb::Bot.new token: <my-token>, client_id: <my-id> @my_bot.run true # commands go here! @my_bot.join ``` And of course, `<my-token>` and `<my-id>` are replaced with your bot's token and id. `@my_bot.run true` and `@my_bot.join` help keep the bot running btw. If you want a predetermined prefix, add `, prefix: "<my-prefix>"` to the end of the first line (and `"<my-token>"` is replaced with your token). ### Step 3: adding commands to your bot #### If you have a prefix Having a prefix makes things easier (though a bit limited). To add a simple command, use the `command` method on your bot, followed by the command name (as a string or symbol), any options (will be described later), and the event block (like an anonymous function that looks like a control structure). Here's an example of a command that takes 2 or more numbers, and adds them up: ```ruby @my_bot.command :sum, min_args: 2 do |event, *args| numbers = args.collect &:to_i # turns each argument into a number sum = numbers.collect &:+ # adds up everything in "numbers" event.respond sum # I could have just done `event.respond args.collect(&:to_i).collect(&:+)` btw end ``` This would be used as `<my-prefix> sum num1 num2 [... num]`. #### If you don't have a prefix Not having a prefix is not a bad thing, it just means that you want your bot to do more. You can get every message by using the `message` command on your bot. For each message, you can specify options like: - `contains: "some text"` (searching for text) - `content: "some text"` (to match entire message) or `content: /some_regex/` (to match a pattern) - `starts_with: "text"` or `ends_with: "text"` (for matching the beginning or end of a message ...and other things. To create something like the previous example: ```ruby @my_bot.message content: /^<my\-prefix>\s+sum\s+.+$/ do |event| args = event.content.split " " # "event.content" is the message numbers = args.collect &:to_i # turns each string in "args" into a number sum = numbers.collect &:+ # adds up everything in "numbers" event.respond sum # I could have just done `event.respond event.content.split(" ").collect(&:to_i).collect(&:+)` btw end ``` This would be used as `<my-prefix> sum num1 num2 [... num]`. # Step 4: profit Because you made your bot in Ruby, everyone will love it. Eventually, your bot turns into a paid subscription service. Good night.
17
posted by theangryepicbanana (244) 2 months ago
โ–ฒ
37
Python Advanced Concepts Explanation (for beginners)
(just wrote this up in a few minutes, so there might be typos and stuff) (the reader is expected to have at least basic knowledge of python) (and programming in general) (if not, read [fullern's introduction][0]) (if you have a question about it (or feedback), comment here or join the repl.it discord and DM elias#7990) This document is an explanation of how Python handles and manipulates data. It is an attempt to clarify some common misconceptions about Python. Important vocabulary and facts will be **bolded**. First, forget what you know about Python objects (but not syntax). I will be explaining most of the useful features here. ## Expressions: Expressions in python are the main way of manipulating data. They calculate (**evaluate**) a value based on the current state of the program. There are a few types of expressions: - Literals: Literals always evaluate to the same thing. For example, the literal `5` always evaluates to the number `5`. - Operations: Operations combine values. For example, the `+` operations adds numbers (or concatenates strings). An expression with an operation looks like `<some expression> <operation (+, -, *, etc.)> <other expression>` (without the angle brackets). Operations follow an extended version of PEMDAS, described [here][1]. - Function calls: These are described in the Functions section. - Ternaries: Ternary expressions look like `<expression a> if <condition expression> else <expression b>`. First `<condition expression>` is evaluated, and if it is **truthy** (acts like True; see Truthiness section for more), `<expression a>` is evaluated. If it is not truthy (**falsy**), `<expression b>` is evaluated instead. - Comprehensions: See the Comprehension section ## Code structure: Python code is composed of lines and blocks. Lines in python can have a few different forms (more than the ones shown here, but the others are used less): ```python <expression> # <expression> is evaluated, and then the value is discarded variable = <expression> # <expression> is evaluated, and assigned to variable as described in Scoping. var1, var2 = <expression> # <expression> is evaluated, and (assuming it's a list, tuple, etc.; see Iterables) var1 is set to its zeroth element and # var2 is set to its first (var1, var2), var3 = <expression> # <expression> is evaluated, and (var1, var2) is set to its zeroth element and var3 is set to its first # other patterns similar to those above also work, like (var1, var2), (var3, var4) # i use <lvalue> to represent any of those patterns if <expression>: # <expression> is evaluated <more lines of code, indented by some amount> # if <expression> was truthy, <more lines of code, indented> is run and the code skips to <more lines, not indented> elif <expression2>: # <expression2> is evaluated (only if <expression> was falsy). elifs are optional, and don't have to be included <more indented code> # if <expression2> was truthy, <more indented code> is run and the code skips. ... # the pattern continues else: <last indented code> # if none of <expression>, <expression2>, ... was truthy, <last indented code> is run. <more lines, not indented> while <expression>: # evaluate <expression>. if it is true, <indented code> # run <indented code> and go back to the start for <lvalue> in <expression>: # for every element in <expression> (probably a list, tuple, or iterable), set <lvalue> to that element and <indented code> # run <indented code> def name(value): # described in Functions <indented code> class name(): # described in User-Defined Classes <indented code> ``` `if`, `while`, `for`, `def`, and `class` can also be used in indented code. ## Scoping: When a program starts running, all variables are in a **scope** (collection of variables) called the **global scope**. Other scopes can be created and manipulated as described in Functions and User-Defined Classes. ## Objects & Types: Data in Python is an object. Numbers are **objects**, string are objects, and functions are objects. Statements like `if`, `for`, and `while` are not objects, since they are code, not data. Every object in Python has a **type**, a.k.a. class. The type of a number is `int` or `float`. The type of a string is `str`. The type of an object can be determined with `type(obj)`. These types are also objects. Their type is called `type`. `type`'s type is itself. Types (but not non-type objects) also have superclasses. These superclasses are also types, and specify additional data about the type (more info in Attributes). If a type has multiple superclasses, [this algorithm][2] is used to determine priorities for them in certain situations. ## Attributes: Objects also have **attributes**. These attributes are named. Their names start with a character a-z, A-Z, or \_. The characters after the first can be a-z, A-Z, \_, or a digit. For example, an attribute could be called `an_attribute`, `AbC123`, or `__add__`, but not `123AbC` (the first character cannot be a digit). These attributes can be accessed with `the_object.attribute_name`. When an attribute is accessed, if it is not found, the object's class (a.k.a. type) is checked. If the attribute is not present there, the superclasses of the type are checked next, in order by priority. If none of the superclasses have the attribute, an exception is raised (see Exceptions). Attributes also have **values**, which are the values returned when an attribute is accessed. These values can be any object. There are also **descriptors**, which allow code to be run when an attribute is accessed. They are described in more detail in the Descriptors section. Some attribute names are special. For example, an attribute named `__add__` specifies how objects are added together with `+`. This will be described in more detail in the User-Defined Classes section. ## Functions: **Functions** are snippets of Python code, and are also objects. They can be **called** with `the_function_object(argument_1, argument_2, ...)`. When a function is called, the code that called it is paused, the passed arguments are placed in variables, and the function's code is run. When the function executes a `return` statement, the function's code stops, and the returned value is send back to the calling code. If the function reaches the last line, it returns `None`, an object representing no data. For example, ```python def a_function(number): # when a_function(value) is called, the calling code is paused # then a new scope is created, # and a variable called number is created with the argument in it # the function's code is then run in the new scope result = number + 1 # result is created in this function's scope return result # the function's code stops here, and result is sent to the calling code. function_result = a_function(41) # calling a_function, where argument is set to 41 # function_result is set to the value returned from a_function print(function_result) # print is a function too! it returns None ``` When run, this code takes the following path: 1: ```python def a_function(number): # all that code above ``` This line (and the code in the `def`) creates a function object, then puts it in a variable called `a_function`. 2: ```python function_result = a_function(41) ``` This line calls `a_function` with the argument `41`. We'll come back to this line once the function returns. 3: ```python def a_function(number): ``` The function is called, so a new scope is created. Here, the argument `41` corresponds to the variable `number`, so the variable `number` in the new scope is set to `41`. 4: ```python result = number + 1 ``` This line calculates `41` (the value of the `number` variable) `+ 1`, which equals `42`. The `result` variable is then set to `42`. 5: ```python return result ``` This line ends the function, returning the value of `result` (`42`). You don't need to return a variable, so this function could have just returned `number + 1` instead. This also goes back to the scope that existed before the function was called (the global scope). 6: ```python function_result = a_function(41) ``` Now that the function has returned, `function_result` is set to the value it returned, which is `42`. Note that this variable is created in the global scope, not in `a_function`'s scope. 7: ```python print(function_result) ``` This outputs the value of `function_result`, or `42`. ## Generators, Iterators, and Iterables **Generators** are one of my favorite features in Python. They are easiest to understand with an example, so here is one: ``` def a_generator_func(): print("about to yield first value") yield "first value" print("just yielded first value") print("about to yield second value") yield "second_value" print("done yielding, there is no third value") a_generator = a_generator_func() print("about to get first value") print("the first value is", repr(next(a_generator))) print("about to get second value") print("the first value is", repr(next(a_generator))) print("about to get third value") print("the first value is", repr(next(a_generator))) ``` This code will output ``` about to get first value about to yield first value the first value is 'first value' about to get second value just yielded first value about to yield second value the first value is 'second_value' about to get third value done yielding, there is no third value Traceback (most recent call last): File "python", line 17, in <module> StopIteration ``` `a_generator_func` is a function, that, instead of pausing the main program when called, returns an object representing the current state. `next` can be repeatedly called on that object to run it until it `yield`s a value. When a generator runs out of things to yield, a `StopIteration` exception (TODO: section on exceptions) is raised. These can be caught with: ``` try: next_val = next(a_generator) except StopIteration: print("the generator ended") ``` Generators can also be used with `for`: ``` for elem in a_generator_func(): print(elem) ``` This would output ``` about to yield first value elem: first value just yielded first value about to yield second value elem: second_value done yielding ``` , but would not raise `StopIteration` (`for` automatically handles this). Generators are a special class of **iterators**, which are objects that can have `next` called on them, and can be used with `for`. `list`s and `tuple`s are not iterators, but **iterables**, which can be converted to iterators by calling `iter` on them. ## User-Defined Classes There are many online tutorials about creating classes, most of which are better than I can write. So before you read this, go look at one of those tutorials (but keep in mind that everything in play is an object). So, now that you have read one of those tutorials (go read one if you haven't), consider the following example class: ```python class Point(): def __init__(self, x_pos, y_pos): self.x_pos = x_pos self.y_pos = y_pos def print(not_self): print(not_self.x_pos, not_self.y_pos) def __add__(tomayto, tomahto): return Point(tomayto.x_pos + tomahto.x_pos, tomayto.y_pos + tomahto.y_pos) a = Point(1, 2) b = Point(3, 4) a.print() b.print() (a + b).print() ``` This code creates a `Point` class, and 2 `instances` of it (`a` and `b`). It then calls `.print` on both instances and `a + b`. The only peculiarity you might notices about this is that it doesn't use `self`, but different argument names. Despite what most tutorials would have you believe, there is nothing special or magic about `self`. You can replace it with any other name and your code will work fine. If your tutorial didn't cover `__add__` and similar functions, they basically define what happens when `a + b` is executed. More information can be found [here][3]. The only "magic" thing left in this code is how the `self`/`not_self`/`tomayto` arguments actually get added; `a.print()` never specifies a value for `not_self`. This is covered in the next section, ## Descriptors Descriptors are a way to make code run when an attribute is accessed. They are objects with `__get__` or `__set__` special methods. When they are accessed as an attribute of a class (but not from an instance), the `__get__` method is called with the instance and the class the descriptor was found on. When attributes are set, a similar thing is done with `__set__`. This is how functions automatically add a `self` argument. They are descriptors, so when they are accessed, it returns a "bound" version of the function, that has the instance (`self`) already added as an argument. A shorthand way to create descriptors is: ```python class Test(): @property def x(): print("getting x") @x.setter def x(): print("setting x") a = Test() a.x a.x = 1 ``` The `@property` and `@x.setter` lines are decorators, described in the next section. ## Decorators Consider the following code: ```python def function_making_function(old_function): print("making a new function from", old_function) def new_function(): print("calling the old function") old_function() print("done calling the old function") print("made the new function") return new_function ``` This code is relatively simple, except for the fact that a function is defined inside another function. This gives the inner defined function access to all the variable in the outer function. Now, running ```python def some_function(): print("in some_function") print("starting to make a new function") new_function = function_making_function(some_function) # not calling it here, just moving at around (remember, functions are objects!) print("done making the new function") print("calling the new function") new_function() print("done") ``` will output: ``` starting to make a new function making a new function from <function old_function at 0xhesyfyua> made the new function done making the new function calling the new function calling the old function in some_function done calling the old function done ``` The code ```python9o print("starting to make a new function") @function_making_function def some_function(): print("in some_function") print("done making the new function") print("calling the new function") some_function() print("done") ``` does the exact same thing as the other code above. `@function_making_function` is just shorthand for `some_function = function_making_function(some_function)` (but it must be before the `def`). This is called a decorator. Multiple decorators can be used on the same function, and are applied bottom-to-top. Decorators do not have to return functions; they can also return other objects (such as descriptors in the case of `@property`). `@property_name.setter` works by having a `setter()` method on the descriptor objects `@property` returns. This `setter()` method changes the behavior of the property's `__set__` method (but not the value as an instance attribute, then the function binding descriptor wouldn't work). That is most of the features I regularly use in Python. There are a few simplifications and some things are left out, but for the most part this should be accurate. However, I left out a lot of information about the standard library (built-in functions and modules). Information about them can be found [here][4] and [here][5]. ## Internals This section details how a certain Python interpreter ([CPython][6]) actually runs Python code. Most things discussed here don't apply to other interpreters, although some concepts might be the same. ### Processing order When Python code is run, it is **lexed**, **parsed**, converted to **bytecode**, and then run. #### Lexing Lexing, or tokenizing, is the process of breaking code up into groups of symbols so it is easier to process later. A simple example of lexing (not with Python code) could look like: ``` 1 + 2*(3 +4- 5) -> NUM 1 OP + NUM 2 OP * OPEN_PAREN NUM 3 OP + NUM 4 OP - NUM 5 CLOSE_PAREN ``` Note that this takes care of unused whitespace and other things that might complicate the process later on. TODO: explain python lex #### Parsing Parsing converts tokenized code into a tree structure that is easier for code to process. For example, using the arithmetic expression from earlier: ``` NUM 1 OP + ... NUM 5 CLOSE_PAREN -> ADD( 1, MUL( 2, SUB( ADD(3,4), 5 ) ) ) ``` This takes care of things like parentheses that don't really affect how code actually runs. TODO: explain python parse #### Bytecode: What is it? Bytecode is an **intermediate language**, which means that code is translated to it before being run. For example, again with the arithmetic expression: ``` ADD(1, MUL(...)) -> PUSH 1 PUSH 3 PUSH 2 PUSH 4 ADD // 3 + 4 PUSH 5 SUB // 3 + 4 - 5 MUL // 2 * (3 + 4 - 5) ADD // 1 + 2 * (3 + 4 - 5) ``` That code is actually not the bytecode, just a simplified version. Bytecode would actually be represented as a sequence of bytes, like in the following example (with bytes as hexadecimal): ``` PUSH 1: 00 01 // the stack is currently 1 PUSH 3: 00 03 // the stack is currently 1 3 PUSH 2: 00 02 // the stack is currently 1 3 2 PUSH 4: 00 04 // the stack is currently 1 3 2 4 ADD: 01 // the stack is currently 1 3 6 PUSH 5: 00 05 // the stack is currently 1 3 6 5 SUB: 02 // the stack is currently 1 3 1 MUL: 03 // the stack is currently 1 3 ADD: 01 // the stack is currently 4 ``` Here, the **opcode** for `PUSH` is `00` (with 1 parameter), `ADD` is `01`, etc. The bytecode for Python is complicated and has many operations (detailed [here][7]), so I will not fully document it here. It is stack-based, which means most of its instructions operate on a data stack, similar to the arithmetic example above. This stack contains Python objects. There are also a set of slots, which are used to store variables, constants, names, etc. Here are some common instructions: ``` POP_TOP: remove the top element of the stack (TOS) BINARY_ADD: pop the TOS and the element below the TOS (TOS1), add them, and push it back onto the stack BINARY_SUBTRACT: see above (-) BINARY_MULTIPLY: see above (*) BINARY_FLOOR_DIVIDE: see above (//) BINARY_TRUE_DIVIDE: see above (/) STORE_FAST(slot_num): pop the TOS and store to a slot LOAD_FAST(slot_num): read a slot and push to the stack LOAD_CONST(slot_num): load a constant and push it CALL_FUNCTION(arg_count): pop the some elements and call the last one as a function with the other elements RETURN_VALUE: return from a function JUMP_ABSOLUTE(where): jump to the <where>th bytecode instruction POP_JUMP_IF_TRUE(where): pop the TOS, if true, jump to <where> POP_JUMP_IF_FALSE: ... GET_ITER: pop the TOS, convert to an iterator, and push it FOR_ITER(delta): use the iterator in TOS (w/o popping) and get the next element. If there is no next element, jump ahead by <delta> instructions UNPACK_SEQUENCE(unpack_len): pop an iterable as the TOS and push its elements (there should be <unpack_len>) to the stack ``` #### Bytecode: How is it made? In Python, bytecode for arithmetic expressions is implemented similarly to the example with the arithmetic expressions. For example, ``` a + b: <bytecode for a> <bytecode for b> BINARY_ADD a - b: <bytecode for a> <bytecode for b> BINARY_SUBTRACT a * b: <a> <b> BINARY_MULTIPLY a / b: ... a // b: ... a(b, c, d): <a> <b> <c> <d> CALL_FUNCTION(3) ... ``` Bytecode for an `<expression>` line looks like `<bytecode for <expression>> POP_TOP` Bytecode for a `<variable> = <expression>` line in the global scope looks like `<bytecode for <expression>> STORE_NAME(<variable>)` Bytecode for a `<variable> = <expression>` line in the local scope looks like `<bytecode for <expression>> STORE_FAST(<variable slot>)` Bytecode for a `<var1>, <var2>, <var3> = <expression>` line looks like `UNPACK_SEQUENCE(3) <assign to var1, global or local> <assign to var2> <assign to var3>` Bytecode for a `(<var1>, <var2>), <var3> = <expression>` line looks like `UNPACK_SEQUENCE(2) UNPACK_SEQUENCE(2) <assign to var1> <assign to var2> <assign to var3>` Bytecode for an `if <expr>: <code>` line looks like `<bytecode for <expr>> POP_JUMP_IF_TRUE(END) <bytecode for <code>> LABEL(END)` (this will sometimes be optimized) Bytecode for an if-elif-else statement looks similar, but more complicated Bytecode for a `while <expr>: <code>` line looks like `LABEL(START) <bytecode for if <expr>: <code>> JUMP_ABSOLUTE(START)` Bytecode for a `for <lvalue> in <expr>: <code>` line looks like `<bytecode for <expr>> GET_ITER LABEL(START) FOR_ITER(END)`<br> `<assign to <lvalue>> <bytecode for <code>> JUMP_ABSOLUTE(START) LABEL(end)` TODO: `def`, `class` Python applies some rudimentary optimizations, like converting `JUMP_ABSOLUTE`s to more efficient `JUMP` instructions. [0]: https://repl.it/talk/challenge/Introduction-To-Python/7484 [1]: https://docs.python.org/3/reference/expressions.html#operator-precedence [2]: https://www.python.org/download/releases/2.3/mro/ [3]: https://docs.python.org/3/reference/datamodel.html#special-method-names [4]: https://docs.python.org/3/library/functions.html [5]: https://docs.python.org/3/library/ [6]: https://github.com/python/cpython [7]: https://docs.python.org/3/library/dis.html#python-bytecode-instructions
8
posted by pyelias (601) 4 months ago
โ–ฒ
54
Game Tutorial: Canyon Runner
Hey everyone, I made a canyon runner game (https://repl.it/@ericqweinstein/EverlastingEmbellishedDevelopments) and wrote up a tutorial for it here: https://medium.com/@eric.q.weinstein/canyon-runner-repl-it-tutorial-cdc6208d3358 Hope you like it!
26
posted by ericqweinstein (149) 5 months ago
โ–ฒ
29
3D graphics, a beginners mind.
# Preface In this tutorial I would like to show how 3d graphics is done today, why it's important, and how it will change the way you see 3d graphics applications. To better understand, we'll end up creating a 3d engine with Python. # Requirements I expect you to be familiar with Python, if you understand what `class` means you're probably qualified in this department. I also expect you to understand what the terms fov (field of view), vertex, mesh..etc. mean. # But 3d graphics is hard! No, it's not. This is an awesome area of programming you'll be able to show to your friends, there *will* be math(s) involved, so strap in, but it'll all be explained. If there are aspects you do not understand, simply copy my solution. # Fundamentals To start, let's go through the basic building blocks. Imagine we have a simple object, a cube. ![blender_2018-10-24_10-42-00](https://image.ibb.co/b5RdgV/blender-2018-10-24-10-42-00.png) There's more going on under the hood, this cube is made up of two things, *vertexes* and *triangles*. Vertexes are essentially points in 3d space. Look around your room, and imagine a speck of dust, a single point in 3d space. Triangles are, well just triangles, normal 2d flat triangles. However their three points are connected to specific vertexes. Let's look at the vertexes. ![blender_2018-10-24_10-43-32](https://image.ibb.co/dtgpoA/blender-2018-10-24-10-43-32.png) On the above image of a cube, you can see there are eight points, these are the points which make up the cube. In memory, these points each have a 3d coordinate: X, Y, Z axis. however when we go to *render* the cube, we map each 3d coordinate to 2d screen space. And the way we do that is surprisingly simple. Next, let's look at the triangles. ![blender_2018-10-24_10-43-33](https://image.ibb.co/fMqh8A/blender-2018-10-24-10-43-33.png) As you can see, a triangle is now connected to three of the points. Do this 12(*) times and you'll get a cube. *: A cube is made up of 6 faces, however to make a face with a triangle, you must use two triangles, so it ends up being 12 triangles. # Enough "fundamentals", more coding! Alright, now that we understand the basic structure for rendering 3d shapes. Let's get more technical. We'll be doing this in `Python (with Turtle)`. First, we import Turtle, I will assume you already know how to use Turtle and are familiar with it's functionality. In short, it's just a graphics library aimed at kids learning how to code with graphics, and making flowers and all sorts of things... Except we'll be going much further than flowers. ```python import turtle ``` Next we need to store our object data. We need to store all our vertexes and triangles. ```python VERTEXES = [(-1, -1, -1), ( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1), (-1, -1, 1), ( 1, -1, 1), ( 1, 1, 1), (-1, 1, 1)] TRIANGLES = [(0, 1, 2)] ``` For now, we only have one triangle connected to the first three points. # Our basic main loop We want to simulate a normal graphics library with turtle. Our program will follow this structure: ```python # Create turtle, pointer = turtle.Turtle() # Turn off move time, makes drawing instant, turtle.tracer(0, 0) pointer.up() while True: # Clear screen, pointer.clear() # Draw, # ... # Update, turtle.update() ``` # Rendering Alright, now we need to somehow map these 3d vertex coordinates to 2d screen coordinates. To do this, let's use the *Perspective Formula*. Before we dive into the details of what exactly this formula does, let's start with an observation. Place an object in front of you, for instance a cup. As you move away, the cup shrinks; now this is all very obvious, but it is an essential property of 3d space we must consider. When we're creating a 3d engine, what we're doing is simulating this observation. When we move away from our objects, that is - the Z axis, we're essentially *converging* the X and Y axis toward zero. Look at this front-view of a cube, you can see the back vertexes are closer to the center (zero). ![blender_2018-10-24_19-01-34](https://image.ibb.co/kzOfvq/blender-2018-10-24-19-01-34.png) # So what is this "formula"? ```python f = field_of_view / z screen_x = x * f screen_y = y * f ``` Where x, y, z are vertex coordinates. We can simplify this to: ```python f = fov / z sx, sy = x * f, y * f ``` Easy right? So let's add `FOV` at the top of the file: ```python FOV = 100 ``` # Drawing the points Let's iterate through each vertex: ```python # Draw, for vertex in VERTEXES: # Get the X, Y, Z coords out of the vertex iterator, x, y, z = vertex # Perspective formula, f = FOV / z sx, sy = x * f, y * f # Move to and draw point, pointer.goto(sx, sy) pointer.dot(3) ``` What we get is: ![chrome_2018-10-24_19-45-21](https://image.ibb.co/kONUoA/chrome-2018-10-24-19-45-21.png) But where are our four other points from before? The ones behind? The issue is we're inside the cube, we need to move the camera out. # The camera Alright, I won't go into the camera in this tutorial, you can look at my repl at the bottom to see how to properly implement a 3d engine, but we're taking baby steps here. When we think of moving the camera, we think of the camera object moving, simple right? Well that's not easy to implement in a rasterized renderer. However what's easier is to move the *world* around it. Think about it, either you can move the camera, or move the world; it's the same effect. As it turns out, it's a lot easier to offset the vertex positions than somehow change the perspective formula to equate the position; it would be a whole lot more complex. So quickly solve this, let's move the camera out: ```python # Perspective formula, z += 5 f = FOV / z sx, sy = x * f, y * f ``` ![chrome_2018-10-24_19-52-05](https://image.ibb.co/ceSJgV/chrome-2018-10-24-19-52-05.png) And adjust the `FOV` to say, `400`. ![chrome_2018-10-24_19-53-04](https://image.ibb.co/i62jMV/chrome-2018-10-24-19-53-04.png) Nice! # Drawing triangles To draw triangles, consider this code. By this point you should be able to understand it: ```python # Draw, for triangle in TRIANGLES: points = [] for vertex in triangle: # Get the X, Y, Z coords out of the vertex iterator, x, y, z = VERTEXES[vertex] print(x, y, z) # Perspective formula, z += 5 f = FOV / z sx, sy = x * f, y * f # Add point, points.append((sx, sy)) # Draw trangle, pointer.goto(points[0][0], points[0][1]) pointer.down() pointer.goto(points[1][0], points[1][1]) pointer.goto(points[2][0], points[2][1]) pointer.goto(points[0][0], points[0][1]) pointer.up() ``` # Rotation To rotate our object, we'll be using the *Rotation Matrix*. It sounds scary, right? If you're familiar with linear algebra, you should already know this, but the rotation matrix is commonly defined as: ``` [x'] = [cos(0), -sin(0)] [y'] = [sin(0), cos(0)] ``` *using `0` as theta* I won't go into detail of the matrix. If you're unfamiliar, feel free to either research or copy & paste. To implement this, we'll first need the `math` library: ```python from math import sin, cos ``` Let's make a function to rotate: ```python def rotate(x, y, r): s, c = sin(r), cos(r) return x * c - y * s, x * s + y * c ``` Then let's place this before we do our perspective formula calculations: ```python # Rotate, x, z = rotate(x, z, 1) ``` As you can see the triangle is now rotated: ![chrome_2018-10-24_20-21-51](https://image.ibb.co/n60tFq/chrome-2018-10-24-20-21-51.png) Let's make the rest of the triangles: ```python TRIANGLES = [ (0, 1, 2), (2, 3, 0), (0, 4, 5), (5, 1, 0), (0, 4, 3), (4, 7, 3), (5, 4, 7), (7, 6, 5), (7, 6, 3), (6, 2, 3), (5, 1, 2), (2, 6, 5) ] ``` ![chrome_2018-10-24_20-23-06](https://image.ibb.co/k3Knaq/chrome-2018-10-24-20-23-06.png) Awesome! Let's initialize a counter at the start of the file: ```python counter = 0 ``` and increment this at the end of every loop: ```python # Update, turtle.update() counter += 0.025 ``` And replace our rotation function: ```python x, z = rotate(x, z, counter) ``` It's rotating, awesome! To rotate on the X, Y and Z axis: ```python x, z = rotate(x, z, counter) y, z = rotate(y, z, counter) x, y = rotate(x, y, counter) ``` We're done! # Complete code Before you read, I recommend you do read through the above, I know it's easier to just skip down to the bottom for the solutions. However, if you're here after reading through the above, feel free to post `Full read` in the comments as a token of my respect, and feel free to copy this code =) ```python from math import sin, cos import turtle VERTEXES = [(-1, -1, -1), ( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1), (-1, -1, 1), ( 1, -1, 1), ( 1, 1, 1), (-1, 1, 1)] TRIANGLES = [ (0, 1, 2), (2, 3, 0), (0, 4, 5), (5, 1, 0), (0, 4, 3), (4, 7, 3), (5, 4, 7), (7, 6, 5), (7, 6, 3), (6, 2, 3), (5, 1, 2), (2, 6, 5) ] FOV = 400 # Create turtle, pointer = turtle.Turtle() # Turn off move time, makes drawing instant, turtle.tracer(0, 0) pointer.up() def rotate(x, y, r): s, c = sin(r), cos(r) return x * c - y * s, x * s + y * c counter = 0 while True: # Clear screen, pointer.clear() # Draw, for triangle in TRIANGLES: points = [] for vertex in triangle: # Get the X, Y, Z coords out of the vertex iterator, x, y, z = VERTEXES[vertex] # Rotate, x, z = rotate(x, z, counter) y, z = rotate(y, z, counter) x, y = rotate(x, y, counter) # Perspective formula, z += 5 f = FOV / z sx, sy = x * f, y * f # Add point, points.append((sx, sy)) # Draw trangle, pointer.goto(points[0][0], points[0][1]) pointer.down() pointer.goto(points[1][0], points[1][1]) pointer.goto(points[2][0], points[2][1]) pointer.goto(points[0][0], points[0][1]) pointer.up() # Update, turtle.update() counter += 0.025 ``` # Conclusion If you want to see an expanded and better written version: https://repl.it/@CoolqB/3D-Engine If there's demand I will perhaps dive into shading, lighting, culling, clipping and even texturing. If you've got any questions, fire away in the comments. Good luck!
2
posted by CoolqB (59) 3 months ago
โ–ฒ
32
Basic Platformer With Javascript and HTML
# BASIC PLATFORMER TUTORIAL **End Result:** https://basic-platformer--luchutton.repl.co/ **The Code:** https://repl.it/@LucHutton/Basic-Platformer ## Prerequisites And Advice: For this tutorial you should already have a basic understanding of programming (preferrably in Javascript) as **I will not be explaining basic Javascript concepts in this tutorial.** If you wish to learn some/more Javascript I recommend you visit: https://developer.mozilla.org/en-US/docs/Web/JavaScript ## Setup: It is best if you follow along using a **HTML + CSS + JS REPL**, as when created the REPL will already have all the boiler plate stuff, but also because you will be able to see the result straight away on the same screen (when you run the code). The first (and most important) line of code you will write will be in the ```index.html``` file. Don't worry this is the only bit of HTML you will have to write :). Place the line of code after the opening ```body``` tag but before the ```script``` tag, like so: ```html <body> <canvas id="canvas" width="512" height="512"></canvas> <script src="script.js"></script> </body> ``` Make sure the id of the ```canvas``` is "canvas", but you can change the width and height to whatever number you want as long as the **width and height are multiples of 32!** ---- Navigate yourself to the ```script.js``` file where we will be spending the remainder of this tutorial. The first thing that we need to do is to create a place in which we can store the attributes of the player, this will be done using an **Object**. For starters our player needs to have an X coordinate, a Y coordinate, a Width and a Height. For simplicity's sake the player will be a 32 x 32 pixel square, and the initial X will be set to half the ```canvas``` width, and Y will be set to half the ```canvas``` height. ```javascript const player = { x: 256, y: 256, width: 32, height: 32 } ``` **Make sure the final value doesn't have a comma after it!** At the top of the Javascript file we need to create a reference to the ```canvas``` element we created in the first step like so: ```javascript const c = document.getElementById("canvas").getContext("2d"); ``` It basically gets the ```canvas``` element -> specifys that we will be working in two dimensions -> sets the value of c to that. Now we need a way to draw the player at its location with its size. To do this we will create a **draw function**, inside it we will need to specify the fill colour of the player and how to draw the player. To specify the colour to fill the next object drawn, you update the canvas' fill colour. ```javascript c.fillStyle = "red"; ``` You can set the colour to whatever you want. On the next line you need to actually draw the player rectangle using a canvas function called ```fillRect``` which draws and fills the rectangle at the same time. The ```fillRect``` function takes four parameters in the following order: an x value, a y value, a width and a height. ```javascript c.fillRect(player.x, player.y, player.width, player.height); ``` As shown above, you access the attributes of an object using the template: ```objectName.attribute```. To call this draw function: at the bottom of the Javascript file put: ```javascript draw(); ``` If you followed the steps above correctly your Javascript code should look like this: ```javascript const c = document.getElementById("canvas").getContext("2d"); const player = { x: 256, y: 256, width: 32, height: 32 } function draw(){ c.fillStyle = "red"; c.fillRect(player.x, player.y, player.width, player.height); } draw(); ``` When you run this code you should see a red square appear somewhere in the top-left of your screen. Now remove the line calling the draw function as it was only for testing. --- ## Going Loopy Every meaningful game needs to have a main function that is looped, to update and draw new things so fast that it is almost imperceptible to the human eye. For our game we need a main function that is called when all the updating for one frame is done. So create a function called main, and inside you can put a call to the draw function: ```javascript function main(){ draw(); } ``` In this state the main function will only be called once, to fix this we need to put another line below the call to draw that re-runs the main function. ```javascript function main(){ draw(); requestAnimationFrame(main); } ``` It basically allows the HTML to render what is drawn and then calls the main function again. --- ## Startup If you run the code you will notice that nothing happens, this is because nothing calls the main function in the first place. What we need is something that runs when the page is fully loaded, luckily Javascript has a function just for this: ```javascript window.onload = function(){ main(); } ``` When the page loads, whatever is in the function will be executed, so if you run the code now you should now see the red square appear again! --- ## Level Making And Drawing We will be making this game's level using a tile map based system because it is easy to use and design. The main concept is that there will be a multi-line string with each line being a certain length long filled with either a 0 or a 1 (Air or a Wall). You can define the level variable like: ```javascript const level = `0000000000000000 0000000000000000 0010000000000000 0000000000001111 0000111000000000 0000000000011111 0000000000000000 0000000000111111 0000000000011000 1110000000000000 0000000010000110 0001111111100000 0000000000000000 0000000000000000 0000000001111110 0000000000000000`; ``` This gives us the ability to create multiple levels really easily. Feel free to tweak the positions of the 1s and 0s until you see fit. *Side Note: Levels should really be defined in external text files, but file handling would distract us too much from the making of the game so I decided just to define it in the Javascript.* Before being able to use this level data we need to make a function that parses it into a 2D Array (Arrays within arrays (but only to one layer)). We need to: + Split the string up by line + Split each line up by character + Return that result To split by line and store the result in an array we can do: ```javascript const lines = lvl.split("\n"); ``` Where lvl is the level data that we pass to the function. To split each line in the array by characters we need to use the ```map``` function *(Introduced in ES5)*. ```javascript const characters = lines.map(l => l.split("")); ``` If you are unsure on how to use the ```map``` function refer to [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). The final level-parsing function should look like: ```javascript function parse(lvl){ const lines = lvl.split("\n"); const characters = lines.map(l => l.split("")); return characters; } ``` To make the level data accessible to all of the program we need to define a variable in the global scope, at the top of the Javascript file, below the ```c``` variable, write: ```javascript let currentLevel; ``` This just tells Javascript that we want this variable to be declared but that we don't need to use it yet. In the ```window.onload``` function update the value of ```currentLevel``` to the return value of the parse function: ```javascript window.onload = function(){ currentLevel = parse(level); main(); } ``` Now all we need to do is draw the level! In the draw function we need to loop through each of the level data lines and on each line we need to loop through each character, and if the character is a 1 then we need to draw a square at the position on canvas relative to the character's position. Also before the looping we need to define a new colour for walls! ```javascript function draw(){ c.fillStyle = "red"; c.fillRect(player.x, player.y, player.width, player.height); c.fillStyle = "black"; for (let row = 0; row < currentLevel.length; row++) { for (let col = 0; col < currentLevel[0].length; col++) { if (currentLevel[row][col] === "1") { c.fillRect(col * 32, row * 32, 32, 32); } } } } ``` Here we are using that ```fillRect``` function again, but instead of the player we are drawing each wall tile. If you now run the code you should see squares that match what is written in the level variable. --- ## Handling User Input Our game right now is pretty boring as there is nothing to do, only stare at the beautifully arranged squares that should now populate you screen. To fix this we need to add a way to listen for keyboard input and move the player based on which key is pressed. At the top of the Javascript file write the line: ```javascript let keysDown = {}; ``` This is an empty **object** which will hold the keys currently pressed. If you are wondering why we need to store current keys down it is because this way allows multiple keys to be pressed at once. To listen for key presses, and then store that key press in the```keysDown``` variable we will use an ```eventListener```: ```javascript addEventListener("keydown", function(event){ keysDown[event.keyCode] = true; }); ``` This function executes whenever the user presses a key on their keyboard, the key pressed information is stored in the event parameter, it then sets the key to true in ```keysDown```. Now we need a similar function that executes when a key is released: ```javascript addEventListener("keyup", function(event){ delete keysDown[event.keyCode]; }); ``` It removes the entry of the released key from ```keysDown```. --- To detect and act upon keys that are pressed we need to create an input function that checks whether certain keys are pressed. ```javascript function input(){ if(65 in keysDown){ player.x -= 3; } if(68 in keysDown){ player.x += 3; } } ``` In Javascript keys are represented numerically, in this example 65 is the number for the "A" key and 68 is the number for the "D" key, if you wish to find out the keycodes for other keys you can use [this website](https://keycode.info/). In main you need to call this function so that input is checked every time main is executed. If you run the code you should see that when you press "A" or "D" the player moves horizontally, but you may also notice that the red square's "previous states" are still visible, to fix this write the following line at the top of the draw function: ```javascript c.clearRect(0, 0, canvas.width, canvas.height); ``` --- ## Horizontal Collisions When moving the red square you will notice that it passes straight through walls, this is because we haven't defined any collisions yet! But before we do collisions we need to create a function called ```getTile``` that gets the tile at a certain X and Y value: ```javascript function getTile(x,y){ return(currentLevel[Math.floor(y / 32)][Math.floor(x / 32)]); } ``` To start collisions we need to define another player attribute called ```speed```, this will determine how fast the player can move horizontally: ```javascript const player = { x: 256, y: 256, width: 32, height: 32, speed: 3 } ``` Now move back to the input function and add two if statements around the updating of the ```player.x``` value, and update the previous code to add ```player.speed``` instead of 3: ```javascript function input(){ if(65 in keysDown){ if (getTile((player.x - player.speed) + 1, player.y + 16) !== "1") { player.x -= 3; } } if(68 in keysDown){ if (getTile(((player.x + player.width) + player.speed) - 1, player.y + 16) !== "1") { player.x += 3; } } } ``` This checks whether the tile, when the player will move by ```player.speed```, at the player's location is a wall or air, if it is air then the player is allowed to move in the direction, else do nothing. If you now run the code, the red square should stop whenever it hits a wall. --- ## Gravity This section will involve a little bit of physics, if you are unaware of how gravity and jumping works you can look at [this website](https://www.khanacademy.org/science/physics/work-and-energy/work-and-energy-tutorial/a/what-is-gravitational-potential-energy), but note that this knowledge isn't required to follow along the next section (it just clarifies things). Firstly we need to update the player object with three attributes required for calculating Gravitational Potential Energy, they are: + Mass (mass) + Kinetic Energy On The Y-Axis (yke) + Gravitational Potential Energy (gpe) ```javascript const player = { x: 256, y: 256, width: 32, height: 32, speed: 3, mass: 64, yke: 0, gpe: 0 } ``` Now we need to create a function to update the player's Y position, the player's Y Kinetic Energy and the player's GPE, but first we will create a function that takes the player as a parameter and calculates the GPE of it. GPE = mass * 9.8 * height. As we will be working in pixels instead of meters we will need to divide the Gravitational Field Strength (9.8) by a million so it scales correctly, and as (0,0) on ```canvas``` is the top left we need to take the player's Y value from the height of the canvas (512 in my case), and finally so the GPE doesn't increase so quickly per pixel we will divide the player's 'height' by 32. ```javascript function calcGPE(obj) { return obj.mass * (9.8 / 1000000) * ((canvas.height - obj.height) - (obj.y / 32)); } ``` Now that that is out of the way we can create a function called ```gravity``` that takes the player as a parameter and: 1. Takes yke away from y 2. Takes GPE away from yke 3. Recalculates GPE ```javascript function gravity(obj){ obj.y -= obj.yke; obj.yke -= obj.gpe; obj.gpe = calcGPE(obj); } ``` Now add a call to ```gravity``` in the ```main``` function and pass ```player``` to it. Now if you run the code you should see that the red square now falls off the screen in a realistic manner. --- ## Vertical Collisions When our red square falls, it goes straight through the walls/floors, to fix this we need to add some more code to the gravity function. Firstly we will check for downwards collisions (landing on the floor), to do this we need to utilise the check squares function to see if the tile at the player's feet is a wall, if yes then we need to set the player's Y Kinetic Energy to 0 and move the player up until it looks seamless with the floor. Also as a preventative measure against future bugs we will need to check that the player is actually falling (Y Kinetic Energy is less than 0). ```javascript // Place Below Previous Code Written if (getTile(obj.x + 32, (obj.y + 32)) !== "0" || getTile(obj.x, (obj.y + 32)) !== "0") { if (obj.yke <= 0){ obj.yke = 0; obj.y -= (obj.y % 32); } } ``` Now we need to do the same for upwards collisions, and also we should only check one vertical collision if the other is false. ```javascript if (getTile(obj.x, obj.y) !== "0" || getTile(obj.x + 32, obj.y) !== "0") { if (obj.yke >= 0){ obj.yke = -0.5; obj.y += 1; } } ``` This does almost the same thing except it sets the Y Kinetic Energy to a small negative number so that it doesn't get stuck on the wall above. Your ```gravity``` function should now look like this: ```javascript function gravity(obj) { obj.y -= obj.yke; obj.yke -= obj.gpe; obj.gpe = calcGPE(obj); if (getTile(obj.x, obj.y) !== "0" || getTile(obj.x + 32, obj.y) !== "0") { if (obj.yke >= 0){ obj.yke = -0.5; obj.y += 1; } } else { if (getTile(obj.x + 32, (obj.y + 32)) !== "0" || getTile(obj.x, (obj.y + 32)) !== "0") { if (obj.yke <= 0){ obj.yke = 0; obj.y -= (obj.y % 32); } } } } ``` On running the code you can see that the red square stops when it hits a wall! --- ## Jumping Congratulations if you made it this far (this tutorial is very long...), this will be the final section before the basic platformer is complete! The final thing that we need to add is jumping. If we just move the player's Y value by a certain amount the jumping will look very unnatural, so instead we will increase the player's Y Kinetic Energy which gives it a nice realistic looking jump. The keycode for the "W" key (the jump key) is 87, so in the ```input``` function add a check for if 87 is in ```keysDown``` and if so increase the player's Y Kinetic Energy by however much you desire (5-8 is generally good), but to disallow the player to jump when directly below a wall we need to add a check to see if they are (and disallow jumping). ```javascript if (87 in keysDown && player.yke === 0) { if (getTile(player.x,player.y - 1) !== "1" && getTile(player.x + 32,player.y - 1) !== "1"){ player.yke += 8; } } ``` This also checks whether the player's Y Kinetic Energy is 0 (is on floor) to prevent mid-air jumping. And now... if you run the code you should be able to jump! --- ## Finished Thank you for taking the time to read through this tutorial! If you enjoyed this tutorial please give me an **upvote**! by lucdadukey
7
posted by LucHutton (68) 3 months ago
โ–ฒ
17
Game Tutorial: SSSnake!
Hi everyone, I put together a little [Snake](https://repl.it/@ericqweinstein/SSSnake) game based on [the arcade game _Blockade_](https://en.wikipedia.org/wiki/Snake_(video_game_genre)) (created in 1976!) and thought I'd write a tutorial on how the game works. (This code is based on work by [Ahira Patel](https://github.com/ahirapatel/python-snake-cli).) Feel free to fork the REPL and add to it! The code is broken up into four main files: `board.py`, `constants.py`, `snake.py`, and `main.py`. The first file manages drawing the game board, `constants.py` sets up nice [enums](https://en.wikipedia.org/wiki/Enumerated_type) for our board colors and symbols, `snake.py` handles drawing the snake itself, and the last file, `main.py`, ties everything together to run our game. ## `constants.py` Since `constants.py` is the simplest, we'll start there. The entire file looks like this: ```py from enum import Enum class Colors(Enum): ''' Terminal colors. ''' BLUEBACK = '\x1b[44m' END = '\033[0m' GREEN = '\033[92m' RED = '\033[91m' class Symbols(Enum): ''' Symbols used during gameplay. ''' SNAKE = f'{Colors.GREEN.value}o{Colors.END.value}' EMPTY = ' ' GRID = '.' FOOD = f'{Colors.RED.value}*{Colors.END.value}' WALL = f'{Colors.BLUEBACK.value}{Colors.GREEN.value}|{Colors.END.value}' ``` All we're doing is setting up some nicer syntax so we can use `Colors.RED.value` or `Symbols.FOOD.value` throughout our code. This also makes it easier for us to change things throughout our program by centralizing logic in one placeโ€”for example, we can change the "food" symbol from `*` to `+` by just changing one character. ## `board.py` Next, let's take a look at `board.py`. This file is a little longer, so we won't reproduce it here, but feel free to follow along in the REPL as we move from function to function. All our functions are inside a `Board` class, which keeps track of the symbols we use in the terminal for the various pieces of our board (such as walls and empty squares). (Note that functions inside classes are called _methods_, so we'll call them that throughout this tutorial.) We also do some arithmetic to build the board from rows and columns, which are in turn dictated by the dimensions of our terminal. (Feel free to play around with different terminal sizes! You can find these values in the `get_terminal_dimensions` function in `main.py`.) Next, we define [getters and setters](https://en.wikipedia.org/wiki/Mutator_method), `get` and `set`. The `get` method gives us the symbol associated with a particular pair of terminal coordinates (that is, what symbol is located at a particular row and column), and the `set` method lets us change the symbol located at a set of coordinates. We also define `width` and `height` to give us the number of columns and rows, respectively, as well as an `is_valid_coord` method that checks if a proposed row/column coordinate pair is within the bounds of our terminal. Our last two methods handle drawing the board itself. The `draw_initial_board` method does exactly that: it goes through and draws the board row-by-row (including the outer walls). Finally, the `draw` method goes to the provided coordinates on the board and draws the provided symbol (for example, drawing the food symbol at a random spot on the board). Note that both methods use `sys.stdout.flush()` to ensure the changes we've made are written to the screen (since content we write to the terminal is buffered; you can read more about data buffers in [here](http://en.wikipedia.org/wiki/Data_buffer)). You can think of `write`ing as telling the program how the terminal content is going to change, and `flush`ing makes those changes appear in the terminal. ## `snake.py` Moving on, we get to the `Snake` class in our `snake.py` file. In our `__init__` method, we keep track of the snake's current direction, head, tail, and what the snake has eaten, as well as a bit of bookkeeping to help us draw the snake. (A discussion of threading in Python is beyond the scope of this tutorial, but you can Google "Python threading" to find some more information on the topic, such as [this WikiBook](https://en.wikibooks.org/wiki/Python_Programming/Threading).) Most of our snake's functionality can be found in the `move` method, which controls the snake's movement across the board: we acquire the lock (ensuring no other threads can change the game state while we're in the middle of moving), move the head to its new position, remove the tail, and then signal that we're done moving by updating our `movement_processed` flag and releasing the thread lock. The result? Our snake moves one cell at a time across the board! The `consume` method handles the snake's behavior when it eats something: when it successfully eats the food symbol, we grow the snake by one unit. The `is_hungry` method helps us figure out whether the snake has recently eaten any food, and `is_dead` handles the two conditions that make us lose the game: if we "eat" a wall or "eat" our own tail (that is, collide with a wall or ourselves). The `set_movement` method allows the snake to change direction (note that it prevents a snake that's more than one unit long from "reversing"โ€”that is, the snake can't immediately go down while it's going up, left while it's going right, and so on). Last but not least, the `get_head` and `get_old_tail` methods are helpers that make it easier for us to get ahold of the snake's head and tail while updating its state on the board. ## `main.py` Finally, we'll tie everything together in a `Game` class inside our `main.py` file. Most of our game logic lives here, so we'll spend a bit more time unpacking this code. First, we `__init__`ialize our class with a board (`self.board`), how many pieces of food the player has to eat to win (`self.num_food`), and the snake (`self.snake`), as well as some bookkeeping to keep track of whether the game is over (`self.over`) and, if so, how the player signalled they wanted to quit the game (`self.key_quit`, `self.sig_quit`). Next, we have our `play` method, which `sleep`s for two seconds to give the player time to switch over to the REPL, then does the following inside a loop: * Ends the game as needed (if the player crashes into a wall/themselves, eats all the food, or quits); * Updates the game board state; * Draws the updated board on the terminal; * Briefly sleeps to allow the user time to react. Next, the `spawn_new_food` method does what you'd expect: it generates a random coordinate, tries to place the food at that spot, and retries if it's not successful (for example, if the snake is already occupying that spot on the board). The `draw_game_board` and `update_game_board` are also fairly straightforward: the first method updates the board (and the snake as well, as long as it's still hungry), and the second updates the game state by moving the snake and updating the board, allowing the snake to consume whatever's in the current square (growing the snake if it eats food and ending the game if the snake bites the wall or itself). Outside of our `Game` class, we have a handful of helper functions that allow us to handle user input and nicely transition the terminal from "game mode" back to the regular REPL (and vice-versa). These can be a little confusing, since they have more to do with the particulars of the terminal than the mechanics of our game, so we'll go over them one by one: * `movement_listener` listens for user input and sets the snake's direction via `snake.set_movement()`. (It also does some nice things to ensure the terminal looks good; these are described in the comments.) * `get_terminal_dimensions` is just a nice helper function for getting the height and width of our terminal. (We actually do some subtraction to make the dimensions slightly smaller than the actual terminal, in order to ensure the entire game is visible on-screen.) * `start_alternate_screen` and `end_alternate_screen` are pretty much as-advertised: they handle the switch to the terminal game from the REPL and back again. * `exit_as_needed` is also pretty straightforward: it handles the four ways our game can end (the user loses by crashing the snake, the user wins by getting all the food, or the user quits the game by either explicitly quitting or stopping the game by sending `Ctrl-C`). * `signal_handler` is a callback function we provide to `signal.signal` to tell Python what to do if it gets an interrupt (`SIGINT`) or terminate (`SIGTERM`) signal from the user: in either case, we quit the game. * Finally, `quit` sets `game.over` to `True` and cleans up the game terminal, restoring our settings to the way they were before the game started. The last few lines of code in `main.py` set up our game for us by getting the terminal dimensions, showing a countdown timer for the game to begin, handle the switch from the "regular" REPL to the game (lines 181 to 183), and finally, creating a new game and starting it. ...and that's it! I hope you enjoyed this tutorial, and feel free to [fork the REPL](https://repl.it/@ericqweinstein/SSSnake) and add more to the game.
0
posted by ericqweinstein (149) 2 months ago
โ–ฒ
40
From Scratch: AI Balancing Act in 50 Lines of Python
![Cart Pole Balancing](https://media.giphy.com/media/QKFkgF2ZtTVbsZBeb2/giphy.gif) Hi everyone! Today I want to show how in 50 lines of Python, we can teach a machine to balance a pole! Weโ€™ll be using the standard OpenAI Gym as our testing environment, and be creating our agent with nothing but numpy. I'll also be going through a crash course on reinforcement learning, so don't worry if you don't have prior experience! The cart pole problem is where we have to push the cart left and right to balance a pole on top of it. Itโ€™s similar to balancing a pencil vertically on our finger tip, except in 1 dimension (quite challenging!) You can check out the final repl here: https://repl.it/@MikeShi42/CartPole. ## RL Crash Course If this is your first time in machine learning or reinforcement learning, Iโ€™ll cover some basics here so youโ€™ll have grounding on the terms weโ€™ll be using here :). If this isnโ€™t your first time, you can go on and hop down to developing our policy! **Reinforcement Learning** Reinforcement learning (RL) is the field of study delving in teaching agents (our algorithm/machine) to perform certain tasks/actions without explicitly telling it how to do so. Think of it as a baby, moving it's legs in a random fashion; by luck if the baby stands upright, we hand it a candy/reward. Similarly the agent's goal will be to maximise the total reward over its lifetime, and we will decide the rewards which align with the tasks we want to accomplish. For the standing up example, a reward of 1 when standing upright and 0 otherwise. An example of an RL agent would be AlphaGo, where the agent has learned how to play the game of Go to maximize its reward (winning games). In this tutorial, weโ€™ll be creating an agent that can solve the problem of balancing a pole on a cart, by pushing the cart left or right. **State** A state is what the game looks like at the moment. We typically deal with numerical representation of games. In the game of pong, it might be the vertical position of each paddle and the x, y coordinate of the ball. In the case of cart pole, our state is composed of 4 numbers: the position of the cart, the speed of the cart, the position of the pole (as an angle) and the angular velocity of the pole. These 4 numbers are given to us as an array (or vector). This is important; understanding the state is an array of numbers means we can do some mathematical operations on it to decide what action we want to take according to the state. **Policy** A policy is a function that can take the state of the game (ex. position of board pieces, or where the cart and pole are) and output the action the agent should take in the position (ex. move the knight, or push the cart to the left). After the agent takes the action we chose, the game will update with the next state, which weโ€™ll feed into the policy again to make a decision. This continues on until the game ends in some way. The policy is very important and is what weโ€™re looking for, as it is the decision making ability behind an agent. **Dot Products** A dot product between two arrays (vectors) is simply multiplying each element of the first array by the corresponding element of the second array, and summing all of it together. Say we wanted to find the dot product of array A and B, itโ€™ll simply be A[0]*B[0] + A[1]*B[1]... Weโ€™ll be using this operation to multiply the state (which is an array) by another array (which will be our policy). Weโ€™ll see this in action in the next section. ## Developing our Policy To solve our game of cart pole, weโ€™ll want to let our machine learn a strategy or a policy to win the game (or maximize our rewards). For the agent weโ€™ll develop today, weโ€™ll be representing our policy as an array of 4 numbers that represent how โ€œimportantโ€ each component of the state is (the cart position, pole position, etc.) and then weโ€™ll dot product the policy array with the state to output a single number. Depending on if the number is positive or negative, weโ€™ll push the cart left or right. If this sounds a bit abstract, letโ€™s pick a concrete example and see what will happen. Letโ€™s say the cart is centered in the game and stationary, and the pole is tilted to the right and is also falling towards the right. Itโ€™ll look something like this: ![game](https://i.imgur.com/KjSx230.png) And the associated state might look like this: ![state table](https://i.imgur.com/E6luv5U.png) The state array would then be [0, 0, 0.2, 0.05]. Now intuitively, weโ€™ll want to straighten the pole back up by pushing the cart to the right. Iโ€™ve taken a good policy from one of my training runs and its policy array reads: [-0.116, 0.332, 0.207 0.352]. Letโ€™s do the math real quick by hand and see what this policy will output as an action for this state. Here weโ€™ll dot product the state array [0, 0, 0.2, 0.05] and the policy array (pasted above). If the number is positive, we push the cart to the right, if the number is negative, we push left. ![dot product between policy and state](https://i.imgur.com/OStbHOq.png) The result is positive, which means the policy also wouldโ€™ve pushed the cart to the right in this situation, exactly how weโ€™d want it to behave. Now this is all fine and dandy, and clearly all we need are 4 magic numbers like the one above to help solve this problem. Now, how do we get those numbers? What if we just totally picked them at random? How well would it work? Letโ€™s find out and start digging into the code! ## Start Your Editor! Letโ€™s pop open a Python instance on repl.it. Repl.it allows you to quickly bring up cloud instances of a ton of different programming environments, and edit code within a powerful cloud IDE that is accessible anywhere as you might know already! ![New Python Project on Repl.it](https://media.giphy.com/media/4T1KlYdYoabwmzSfIS/giphy.gif) ## Install the Packages Weโ€™ll start off by installing the two packages we need for this project: numpy to help with numerical calculations, and OpenAI Gym to serve as our simulator for our agent. ![Installing Gym Package on Repl.it](https://media.giphy.com/media/82OOeCknK40hPvkDQv/giphy.gif) Simply type `gym` and `numpy` into the package search tool on the left hand side of the editor and click the plus button to install the packages. ## Laying Down the Foundations Letโ€™s first import the two dependencies we just installed into our main.py script and set up a new gym environment: ```python import gym import numpy as np env = gym.make('CartPole-v1') ``` Next weโ€™ll define a function called โ€œplayโ€, that will be given an environment and a policy array, and will play the policy array in the environment and return the score, and a snapshot (observation) of the game at each timestep. Weโ€™ll use the score to tell us how well the policy played and the snapshots for us to watch how the policy did in a single game. This way we can test different policies and see how well they do in the game! Letโ€™s start off with the function definition, and resetting the game to a starting state. ```python def play(env, policy): observation = env.reset() ``` Next weโ€™ll initialize some variables to keep track if the game is over yet, the total score of the policy, and the snapshots (observations) of each step during the game. ```python done = False score = 0 observations = [] ``` Now weโ€™ll simply just play the game for a lot of time steps, until the gym tells us the game is done. ```python for _ in range(5000): observations += [observation.tolist()] # Record the observations for normalization and replay if done: # If the simulation was over last iteration, exit loop break # Pick an action according to the policy matrix outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 # Make the action, record reward observation, reward, done, info = env.step(action) score += reward return score, observations ``` The bulk of the code above is mainly just in playing the game and recording the outcome. The actual code that is our policy is simply these two lines: ```python outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 ``` All weโ€™re doing is doing the dot product operation between the policy array and the state (observation) array like weโ€™ve shown in the concrete example earlier. Then we either choose an action of 1 or 0 (left or right) depending if the outcome is positive or negative. So far our main.py should look like this: [Github Gist](https://gist.github.com/MikeShi42/c6ea4f19bf628cc40dc9c76087f5d4fb) Now weโ€™ll want to start playing some games and find our optimal policy! ## Playing the First Game Now that we have a function to play the game and tell how good our policy is, weโ€™ll want to start generating some policies and see how well they do. What if we just tried to plug in some random policies at first? How far can we go? Letโ€™s use numpy to generate our policy, which is a 4 element array or a 4x1 matrix. Itโ€™ll pick 4 numbers between 0 and 1 to use as our policy. ```python policy = np.random.rand(1,4) ``` With that policy in place, and the environment we created above, we can plug them into play and get a score. ```python score, observations = play(env, policy) print('Policy Score', score) ``` Simply hit run to run our script. It should output the score our policy got. ![policy score of 9.0](https://i.imgur.com/z7EUsWM.png) The max score for this game is 500, chances are is that your policy didnโ€™t fare so well. If yours did, congrats! It must be your lucky day! Just seeing a number though isnโ€™t very rewarding, itโ€™d be great if we could visualize how our agent plays the game, and in the next step weโ€™ll be setting that up! ## Watching our Agent To watch our agent, weโ€™ll use [flask](http://flask.pocoo.org/) to set up a lightweight server so we can see our agentโ€™s performance in our browser. Flask is a light Python HTTP server framework that can serve our HTML UI and data. Iโ€™ll keep this part brief as the details behind rendering and HTTP servers isnโ€™t critical to training our agent. Weโ€™ll first want to install โ€˜flaskโ€™ as a Python package, just like how we installed gym and numpy in the previous sections. ![Installing Flask Gif](https://media.giphy.com/media/67s8AmPt8GbZdDlhCD/giphy.gif) Next, at the bottom of our script, weโ€™ll create a flask server. Itโ€™ll expose the recording of each frame of the game on the `/data` endpoint and host the UI on `/`. ```python from flask import Flask import json app = Flask(__name__, static_folder='.') @app.route("/data") def data(): return json.dumps(observations) @app.route('/') def root(): return app.send_static_file('./index.html') app.run(host='0.0.0.0', port='3000') ``` Additionally weโ€™ll need to add two files. One will be a blank Python file to the project. This is a technicality of how repl.it detects if the repl is either in [eval mode or project mode](https://repl.it/site/docs/repls/files). Simply use the new file button to add a blank Python script. After that we also want to create an index.html that will host the rendering UI. I wonโ€™t dive into details here, but simply **upload this [index.html](https://gist.github.com/MikeShi42/7b5ff55e2320e41228b5c25ad1113321) to your repl.it project**. You now should have a project directory that looks like this: ![project directory screenshot](https://i.imgur.com/xOTcsbj.png) Now with these two files, when we run the repl, it should now also play back how our policy did. With this in place, letโ€™s try to find an optimal policy! ![Policy Replay Gif](https://media.giphy.com/media/1ZuOyqmDLilxJrsQi0/giphy.gif) ## Policy Search In our first pass, we simply randomly picked one policy, but what if we picked a handful of policies, and only kept the one that did the best? Letโ€™s go back to the part where we play the policy, and instead of just generating one, letโ€™s write a loop to generate a few and keep track of how well each policy did, and save only the best policy. Weโ€™ll first create a tuple called `max` that will store the score, observations, and policy array of the best policy weโ€™ve seen so far. ```python max = (0, [], []) ``` Next weโ€™ll generate and evaluate 10 policies, and save the best policy in max. ```python for _ in range(10): policy = np.random.rand(1,4) score, observations = play(env, policy) if score > max[0]: max = (score, observations, policy) print('Max Score', max[0]) ``` Weโ€™ll also have to tell our /data endpoint to return the replay of the best policy. ```python @app.route("/data") def data(): return json.dumps(observations) ``` should be changed to ```python @app.route("/data") def data(): return json.dumps(max[1]) ``` Your main.py should look something like [this now](https://gist.github.com/MikeShi42/3c270ce2d13f2709ef2d5983492a1693). If we run the repl now, we should get a max score of 500, if not, try running the repl one more time! We can also watch the policy balance the pole perfectly fine! Wow that was easy! ## Not So Fast Or maybe it isnโ€™t. We cheated a bit in the first part in a couple of ways. First of all we only randomly created policy arrays between the range of 0 to 1. This just *happens* to work, but if we flipped the greater than operator around, weโ€™ll see that the agent will fail pretty catastrophically. To try it yourself change `action = 1 if outcome > 0 else 0` to `action = 1 if outcome < 0 else 0`. This doesnโ€™t seem very robust, in that if we just happened to pick less than instead of greater than, we could never find a policy that could solve the game. To alleviate this, we actually should generate policies with negative numbers as well. This will make it more difficult to find a good policy (as a lot of the negative ones arenโ€™t good), but weโ€™re no longer โ€œcheatingโ€ by fitting our specific algorithm to this specific game. If we tried to do this on other environments in the OpenAI gym, our algorithm would definitely fail. To do this instead of having `policy = np.random.rand(1,4)`, weโ€™ll change to `policy = np.random.rand(1,4) - 0.5`. This way each number in our policy will be between -0.5 and 0.5 instead of 0 to 1. But because this is more difficult, weโ€™d also want to search through more policies. In the for loop above, instead of iterating through 10 policies, letโ€™s try 100 policies by changing the code to read `for _ in range(100):`. I also encourage you to try to just iterate through 10 policies first, to see how hard it is to get good policies now with negative numbers. Now our main.py should look [like this](https://gist.github.com/MikeShi42/e1c5551bbf2cb2064da962ad8b198c1b) If you run the repl now, no matter if weโ€™re using greater than or less than, we can still find a good policy for the game. ## Not So Fast Pt. 2 But wait, thereโ€™s more! Even though our policy might be able to achieve the max score of 500 on a single run, can it do it every time? When weโ€™ve generated 100 policies, and pick the policy that did best on its single run, the policy mightโ€™ve just gotten very lucky, and in it could be a very bad policy that just happened to have a very good run. This is because the game itself has an element of randomness to it (the starting position is different every time), so a policy could be good at just one starting position, but not others. So to fix this, weโ€™d want to evaluate how well a policy did on multiple trials. For now, letโ€™s take the best policy we found from before, and see how well itโ€™ll do on 100 trials. ```python scores = [] for _ in range(100): score, _ = play(env, max[2]) scores += [score] print('Average Score (100 trials)', np.mean(scores)) ``` Here weโ€™re playing the best policy (index 2 of `max`) 100 times, and recording the score each time. We then use numpy to calculate the average score and print it to our terminal. Thereโ€™s no hard published definition of โ€œsolvedโ€, but it should be only a few points shy of 500. You might notice that the best policy might actually be subpar sometimes. However, Iโ€™ll leave the fix up to you to decide! ## done=True Congrats! ๐ŸŽ‰ Weโ€™ve successfully created an AI that can solve cart pole very effectively, and rather efficiently. Now thereโ€™s a lot of room for improvement to be made thatโ€™ll be part of an article in a later series. Some things we can investigate more on: - Finding a โ€œrealโ€ optimal policy (will do well in 100 separate plays) - Optimizing the number of times we have to search to find an optimal policy (โ€œsample efficiencyโ€) - Doing a proper search of the policy instead of trying to just randomly pick them. - Solving [other environments](https://gym.openai.com/envs/#classic_control). If youโ€™re interested in experimenting more with ML with pretrained models and out-of-the-box working code, check out [ModelDepot](https://modeldepot.io)!
5
posted by MikeShi42 (88) 4 months ago
โ–ฒ
22
Colorful Circle Spiral Generator Using Python Turtle
Welcome to the python turtle circle pattern tutorial! In this tutorial you will learn how to make a colorful circle pattern creator with python using turtle! This patterns will look somewhat like this and the best part is that you can customize them! ![Screen Shot 2018-10-12 at 3.48.46 PM](https://storage.googleapis.com/replit/images/1539373814562_2b0b2e8bfd93e89ec57a4c6d39e0cc42.pn) First log on to your repl.it account and make a new python turtle code ![Screen Shot 2018-10-11 at 9.44.49 PM](https://storage.googleapis.com/replit/images/1539308815295_06a93f67ba00ca4869f5981583a9a8b3.pn) On your new repl.it first you are going to put in your imports. For this code you will need to import math, and obviously turtle. It should look like this now! ```python import turtle import math ``` We now want to set the background color! To do this you are going to type ```python turtle.Screen().bgcolor('black') #This sets the color to black but you can choose any background color! ``` Now you need to create 5 turtles for this code. I named mine Johny, Steve, Terry, Barry, ,Will, and Nich yours can be named anything you would like. Just make sure to use the same name every time. I also defined its speed and color! ```python Johny = turtle.Turtle() Johny.speed(0) Johny.color('white') #When the speed of the turtle is set to 0 then it will turn off the animation and it will go as fast as possible. I used the color white for my turtle but you can use any color you want it does not matter :). Do this 5 times for 5 different names and 5 different colors but keep the speed the same! ``` So far our code looks like this: ```python import turtle import math turtle.Screen().bgcolor('black') Johny = turtle.Turtle() Johny.speed(0) Johny.color('white') Steve = turtle.Turtle() Steve.speed(0) Steve.color('yellow') Barry = turtle.Turtle() Barry.speed(0) Barry.color('blue') Terry = turtle.Turtle() Terry.speed(0) Terry.color('orange') Will = turtle.Turtle() Will.speed(0) Will.color('pink') Nich = turtle.Turtle() Nich.speed(0) Nich.color('red') #When the speed of the turtle is set to 0 then it will turn off the animation and it will go as fast as possible ``` To make this colorful circle generator you would want a user input so that you can get a new pattern each time!. To do this use a input statement and then a statement to make sure your number is a integer. It will look like this ```python random = input("Enter a random number") random = int(random) ``` Now we will need to make a while true loop so that the pattern will keep drawing forever and this will look like this: ```python while True: ``` _Anything which is in this while true loop needs to be tabbed in!_ We are very close to finishing our code! Inside this while true loop you will need to put this code in ```python def drawCircles(t,size): for i in range(10): t.circle(size) size=size- minus+62 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Johny ,100,10) ``` There are comments in the code to tell you what this code means. In this code the turtle Johny is drawing which means it will be the color white. Now you will want to put this code in for each turtle and you will be done! I recommend that for every size = size - minus + 62 line that you change the +62 to something else like -12 or *32 to get a very interesting pattern each time. Here is the final code. ```python import turtle import math turtle.Screen().bgcolor('black') Johny = turtle.Turtle() Johny.speed(0) Johny.color('white') Steve = turtle.Turtle() Steve.speed(0) Steve.color('yellow') Barry = turtle.Turtle() Barry.speed(0) Barry.color('blue') Terry = turtle.Turtle() Terry.speed(0) Terry.color('orange') Will = turtle.Turtle() Will.speed(0) Will.color('pink') Nich = turtle.Turtle() Nich.speed(0) Nich.color('red') minus = input("Give me a random number") minus = int(minus) print "Click on results now" while True: def drawCircles(t,size): for i in range(10): t.circle(size) size=size- minus+62 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Johny,100,10) def drawCircles(t,size): for i in range(4): t.circle(size) size=size - minus -3 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Steve,100,10) def drawCircles(t,size): for i in range(4): t.circle(size) size=size- minus def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Barry,100,10) def drawCircles(t,size): for i in range(4): t.circle(size) size=size- minus + 2 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Terry,100,10) def drawCircles(t,size): for i in range(4): t.circle(size) size=size-minus*3 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Will,100,10) def drawCircles(t,size): for i in range(4): t.circle(size) size=size-minus-45 def drawSpecial(t,size,repeat): for i in range (repeat): drawCircles(t,size) t.right(360/repeat) drawSpecial(Nich,100,10) ``` To run the code you first must press run button and then you have to go to console and type your random number. Your random number can be a negative number and if you make the number too big the code does not work. After you do that then you should be good to go into result and view your pattern. If you discovered a cool looking pattern put it down below in the comments! Here is are some cool patterns the generator made! ![Screen Shot 2018-10-12 at 3.49.37 PM](https://storage.googleapis.com/replit/images/1539373802126_aa9e1ced7f74cb7c611b9d79fc39c527.pn) ![Screen Shot 2018-10-12 at 3.49.15 PM](https://storage.googleapis.com/replit/images/1539373805966_2798667f8c3d949293c2851387f98b53.pn) ![Screen Shot 2018-10-12 at 3.48.46 PM](https://storage.googleapis.com/replit/images/1539373814562_2b0b2e8bfd93e89ec57a4c6d39e0cc42.pn) _Thanks to nothplus for the idea of making a tutorial for python turtle! Be sure to check his tutorial right here>>>https://repl.it/talk/challenge/How-To-Create-A-Black-Hole-Spiral-in-Python-W-Turtle/7423_ # **Stay Tuned For More! And make sure to smash that upvote button**
10
posted by IEATPYTHON (374) 3 months ago
โ–ฒ
13
Game Tutorial: Tic-Tac-Toe (Ruby)
Hi everyone, I put together a little [tic-tac-toe](https://repl.it/@ericqweinstein/tictactoerb) game that uses the [minimax AI algorithm](https://en.wikipedia.org/wiki/Minimax) and thought I'd write a tutorial on how it works. (This code is based on work by [Clederson Cruz](https://github.com/Cledersonbc/tic-tac-toe-minimax); I've also created a [Python version](https://repl.it/@ericqweinstein/TicTacToe).) Feel free to fork the REPL and add to it! The game is only a couple hundred lines of Ruby in a single `main.py` file, so we'll walk through it in this tutorial step-by-step. (This will be almost exactly the same as my [tutorial for the Python version](https://repl.it/talk/learn/Game-Tutorial-Tic-Tac-Toe-Python/8926).) ## The Board The nice thing about tic-tac-toe is that the game is easy to model! While we could build out an entire `Board` class (and that's something you could add if you wanted to fork this REPL and update it), but for simplicity's sake, we'll just model our board is a two-dimensional array: ```rb @board = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ] ``` The board-related functions in our game will be `empty_cells` (which returns open spots on the board), `set_move` (which places an `X` or an `O` in the provided spot, assuming it's available), and `render` (which draws the board). Let's look at each one in turn. ```rb def empty_cells(state) [].tap do |cells| state.each_with_index do |row, x| row.each_with_index do |cell, y| cells << [x, y] if cell.zero? end end end end ``` Here, we iterate through all the cells on the board and check to see if the value is still `0` (which is how we initialized the board, as you can see above). If so, we add that cell's coordinates to the `cells` array, then return that array so we know which spots are still available for a player to choose. If you're curious about the `[].tap` magic, all that does is let us modify our array inside the block, then automatically return the completed array; you can read more about `Object#tap` [here](https://ruby-doc.org/core-2.5.3/Object.html#method-i-tap). ```rb def set_move(x, y, player) if empty_cells(@board).include? [x, y] @board[x][y] = player return true end false end ``` This is also pretty straightforward: we check to see if our chosen move is valid (that is, it's in the `empty_cells` array), and if so, we set that square on the board to `X` or `O` (depending on which player is moving), returning `true` on success and `false` if the move can't be made because the square is already occupied. ```rb def render(state, c_choice, h_choice) state.each do |row| puts "\n---------------" row.each do |cell| if cell == COMP print "| #{c_choice} |" elsif cell == HUMAN print "| #{h_choice} |" else print "| |" end end end puts "\n---------------" end ``` Our last board method just prints out the current state of the board, printing out every cell and showing a blank for free cells, `X` for cells taken by the `X` player, and `O` for cells taken by the `O` player. ## Evaluating Moves Next, we'll look at our functions for evaluating moves: `evaluate`, `wins`, and `game_over`. ```rb def evaluate(state) if wins(state, COMP) 1 elsif wins(state, HUMAN) -1 else 0 end end ``` So far, so good! We return `1` if the provided `state` (which is a two-dimensional array) would result in the computer winning, `-1` if the human player would win, and `0` if it would be a tie. There are two important things to note here: 1. We choose `-1` for a human player win because we want the minimax algorithm to run from the computer player's perspective: it's trying to **min**imize the **max**imum score the human player can achieve; 2. This function is run for _possible_ game states, meaning that the computer player uses this function to look at the results of all possible moves, all the possible moves those moves would allow, all the possible moves _those_ moves would allow, and so on, generally limited to a certain depth (at which point the computer chooses the move that leads to the worst "possible futures" for the human player; that is, the branch in the tree of possible states with the smallest possible maximum score). This might seem a little confusing now, but we'll go through all the details when we get to the `minimax` function itself. Next is the `wins` function, which looks like this: ```rb def wins(state, player) win_state = [ [state[0][0], state[0][1], state[0][2]], [state[1][0], state[1][1], state[1][2]], [state[2][0], state[2][1], state[2][2]], [state[0][0], state[1][0], state[2][0]], [state[0][1], state[1][1], state[2][1]], [state[0][2], state[1][2], state[2][2]], [state[0][0], state[1][1], state[2][2]], [state[2][0], state[1][1], state[0][2]] ] win_state.include? [player, player, player] end ``` Here, we simply return `true` if the player has achieved a set of winning moves. There are eight winning moves in tic-tac-toe: the top row, the middle row, the bottom row, the left column, the middle column, the right column, and the two diagonals (corresponding to the eight entries in our `win_state` array). ```rb def game_over(state) wins(state, HUMAN) || wins(state, COMP) end ``` This one's pretty easy! If either the human or the computer wins, the game is immediately over. ## The Minimax Algorithm On to the main event: the `minimax` algorithm! This is how our computer player decides which moves to make. As mentioned earlier, this algorithm looks at "possible futures" in the game, assigning expected scores to each, and makes the move that maximizes the score (for the computer) and minimizes the score (for the human). (Remember, since the algorithm is being used by the computer player to select a move, it will want to maximize its score and minimize the human opponent's score.) We take the state of the game, the depth to recurse to in our game tree, and a player, and initialize our "best move" to `-1`, `-1`, `-INFINITY` (that is, a non-existent square with worst/lowest possible score) for the computer player and `-1`, `-1`, `+INFINITY` (a non-existent square with the best/highest possible score) for a human player. Then, for as long as we're recursing and the game isn't over, we continually make imaginary moves and check the resulting scores, assigning as the "best" score the one that represents the highest possible (for the computer) or lowest possible (for the human). In the base case, we return the result of `evaluate` on our game state, and the overall algorithm returns the move with the highest perceived score for the computer and the lowest one for the human. ```rb def minimax(state, depth, player) if player == COMP best = [-1, -1, -INFINITY] else best = [-1, -1, INFINITY] end if depth.zero? || game_over(state) score = evaluate state return [-1, -1, score] end empty_cells(state).each do |cell| x, y = cell[0], cell[1] state[x][y] = player score = minimax state, depth - 1, -player state[x][y] = 0 score[0], score[1] = x, y if player == COMP if score[2] > best[2] best = score end else if score[2] < best[2] best = score end end end best end ``` ## Tying It All Together Finally, we'll complete our game with three functions to manage gameplay: `ai_turn`, `human_turn`, and `main`. ```rb def ai_turn(c_choice, h_choice) depth = empty_cells(@board).size return if depth.zero? || game_over(@board) clear_screen puts "Computer turn [#{c_choice}]" render @board, c_choice, h_choice if depth == 9 x = [0, 1, 2].sample y = [0, 1, 2].sample else move = minimax @board, depth, COMP x, y = move[0], move[1] end set_move x, y, COMP sleep 1 end ``` The computer player uses `minimax` to make the best possible move given the current state of the board, with the exception of when `depth == 9` (that is, every square on the board is empty), in which case since the computer player is moving first, there's no "best" move and the computer selects a random square. ```rb def human_turn(c_choice, h_choice) depth = empty_cells(@board).size return if depth.zero? || game_over(@board) # Hash of valid moves move = -1 moves = { 1 => [0, 0], 2 => [0, 1], 3 => [0, 2], 4 => [1, 0], 5 => [1, 1], 6 => [1, 2], 7 => [2, 0], 8 => [2, 1], 9 => [2, 2] } clear_screen puts "Human turn [#{h_choice}]" render @board, c_choice, h_choice while move < 1 || move > 9 begin print 'Use numpad (1..9): ' move = gets.chomp.to_i coord = moves[move] try_move = set_move coord[0], coord[1], HUMAN if !try_move puts 'Invalid move' move = -1 end rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end end ``` Here, we handle the input from the human player, mapping the numbers `1` through `9` to squares in our board (a two-dimensional array). ```rb def main h_choice = '' c_choice = '' first = '' while h_choice != 'O' && h_choice != 'X' begin puts 'Choose X or O:' h_choice = gets.chomp.upcase puts "Chosen: #{h_choice}" rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end if h_choice == 'X' c_choice = 'O' else c_choice = 'X' end while first != 'Y' && first != 'N' begin puts 'First to start? [y/n]: ' first = gets.chomp.upcase rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end while empty_cells(@board).size > 0 && !game_over(@board) if first == 'N' ai_turn c_choice, h_choice first = '' end human_turn c_choice, h_choice ai_turn c_choice, h_choice end if wins @board, HUMAN clear_screen puts "Human turn [#{h_choice}]" render @board, c_choice, h_choice puts 'You win!' elsif wins @board, COMP clear_screen puts "Computer turn [#{c_choice}]" render @board, c_choice, h_choice puts 'Computer wins!' else clear_screen render @board, c_choice, h_choice puts 'It\'s a tie!' end exit 0 end ``` Finally, our `main` function lets the human player select `X` or `O` and whether they want to move first or second, then enters the game loop: as long as there's an empty square on the board, alternate human and computer player turns, displaying the appropriate messages for when the human player wins, loses, or ties with the computer. We kick everything off just by calling `main` at the very end of the file. ...and that's it! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/tictactoerb) to add more functionality.
3
posted by ericqweinstein (149) 2 months ago
โ–ฒ
14
Chat on the terminal
# Chat on the terminal ## Build a realtime chat in your terminal ๐Ÿ’ฌ ![](https://i.imgur.com/OqMNLC2.gif) [Demo](https://chat.jajoosam.repl.run) โฏ๏ธ [Code](https://repl.it/@jajoosam/chat) ๐Ÿ‘จโ€๐Ÿ’ป This is a quick tutorial which helps you build a realtime chat on your terminal - using [socket.io](https://socket.io) - we can build and test super quick thanks to [repl.it](https://repl.it)'s [repl.run](http://repl.run) platform - which displays a terminal on the web ๐ŸŒ ๐Ÿ‘จโ€๐Ÿ’ป ## โš™๏ธ Dependencies We're only using one external dependency in this project - [socket.io](http://socket.io), which helps us communicate realtime between several clients and a server. Along with this, we'll be using node's built in `readline` which lets us collect input from the terminal. ## ๐Ÿ’ Setting up the server Because we're using [socket.io](http://socket.io) - we need to spin up a small server to relay messages too! Just fork this, and you'll be all set ๐Ÿ‘‰ [repl.it/@jajoosam/chat-server](https://repl.it/@jajoosam/chat-server) ```javascript io.on('connection', function (socket) { socket.on('message', function(data) { io.emit("msg", data); }); }); ``` This small block of code listens for any `message`s from any user, and then broadcasts that message to all the users - that simple โœจ ## ๐Ÿ’ฌ Let's get chatting Let's start off by forking [this repl](https://repl.it/@jajoosam/chat-starter) - I've already declared dependencies to make things simple. On `line 3`, replace the `url` with that of the server you just created. Whenever a user connects, let's ask them for their name. Put this code inside the `socket.connect` block. ```javascript rl.question(`What's your name? `, (answer) => { socket.emit("message", `๐Ÿ‘ค : ${answer} has joined the chat`); id = answer; chat(); }); ``` We're sending a message which says a new user has joined the chat, and storing their name for later reference. As you can see - we're also calling the `chat` function, which is currently empty - let's change that! ```javascript rl.question("ยป ", (answer) => { // "ยป " Gives a prompt for the user socket.emit("message", `${id}: ${answer}`); chat(); }); ``` We're prompting for a user to type out a message, then sending it back to the server - also at the end - we're calling `chat` again in case they want to send more messages โŒจ๏ธ ## ๐Ÿ–จ๏ธ Printing out messages If you try running this code now, you'll see that no message you type is printed in the terminal. Let's create a new event listener inside `socket.connect` to listen for messages ๐Ÿ‘‡ ```javascript socket.on('msg', function(data){ console.log("\n" + data); chat(); }); ``` Whenever the server broadcasts a new message to us, we print it out to the `console` - and call `chat` again - so that our user can keep sending messages! ## ๐Ÿ› ๏ธ Hacking this It was pretty simple to build this chat - but it doesn't seem like a conventional one in many ways. Luckily, you can change that ๐Ÿ˜„ **Here are some ideas to tinker with this project ๐Ÿ‘‡** - Let everyone know when a user is typing โŒจ๏ธ - Don't print out messages sent by the same user repeatedly ๐Ÿ”„ - Add color + formatting to the chat with [chalk](https://github.com/chalk/chalk) ๐ŸŒˆ - Take realtime to the next level by broadcasting every character as it's typed ๐Ÿ’ก Check out [this](https://github.com/aviaryan/chattt) open source project for more ideas! Here's the code for the completed project (with a few tweaks!) - feel free to refer to it ๐Ÿ‘จโ€๐Ÿ’ป [https://repl.it/@jajoosam/chat](https://repl.it/@jajoosam/chat) Now, go ahead and share what you've built on [repl talk](https://repl.it/talk/share), and tag me - [@jajoosam](https://repl.it/@jajoosam) ๐Ÿ‘ค
6
posted by jajoosam (334) 2 months ago
โ–ฒ
17
How to make a python email bot
# How to make a python email bot In this tutorial we will be learning how to make a python program that can read emails and respond based on the content. Once you have the fundamentals of the program, you can write your own functions to do anything. I will provide a 2 examples: a weather bot and a remote commands/shell script executor (useful on your own machine rather than repl.it). Sorry for the tutorial being so long. If you just want to get to the bot, skip the "IMAP in Python Basics" section and the "SMTP in Python Basics" sections. ### What is IMAP? IMAP stands for Internet Mail Access Protocol. Almost all email clients, even web based ones, login to your email providers email server using IMAP. With IMAP there are folders to organize emails and emails stay on the server unless explicitly deleted. ### What is SMTP? SMTP stands for Simple Mail Transfer Protocol. It is the standard protocol used for sending emails across the internet. SMTP is used to both send and receive emails, but we will only be sending. ## IMAP in Python Basics To implement IMAP we will be using the third-party module ```imapclient``` (docs [here](https://imapclient.readthedocs.io/en/2.1.0/)). ### Connecting to the Server Most servers can be found with a simple google search of "*your provider* imap settings" or by contacting you email provider, but here are the most common ones: **Gmail:** imap.gmail.com **IMPORTANT:** Gmail users may only check for emails every 10 minutes, or 600 seconds, so make sure to change your config! See this articlefor more information: https://support.google.com/mail/answer/7126229?hl=en **Outlook.com/Hotmail.com:** imap-mail.outlook.com **Yahoo.com:** imap.mail.yahoo.com **AT&T:** imap.mail.att.net **Comcast:** imap.comcast.net **Verizon:** incoming.verizon.net Now you can connect to the server, the ```imapclient``` module makes this easy. All you have to do is initialize an ```imapclient.IMAPClient()``` object using the server name, and it will connect to it on the default IMAP port using SSL. For example: ```python import imapclient i = imapclient.IMAPClient('imap-mail.outlook.com') # i is easy to type ``` ### Logging in Logging in is very simple. ```python i.login('[email protected]', 'mySuperSecretPassw0rd') ``` The ```i.login()``` function is pretty self-explanatory, it logs in using the email and password provided. ### Selecting a folder Next we need to select a folder. ```i.list_folders()``` shows the available folders. You should choose the one you want, usually "INBOX". ```python i.select_folder('INBOX') ``` You can also, for debugging, pass in the keyword argument ```readonly=True``` to select_folder() to make the server not mark the messages you fetch as read. ### Search for messages Now we can search for a message. There are many search criteria, here is [a full list](https://gist.github.com/martinrusev/6121028). Some of the most common ones are ```'ALL'``` which selects all message, ```'UNSEEN'``` which gets all unread messages. The search flags behave differently which each email server, so you should first experiment with them in the shell. You can search through the messages in the folder using ```i.search(['criteria'])```. To get a unread messages, use the following code: ```python i.search(['UNSEEN']) ``` This will not return the emails themselves, but a list of unique IDs or UIDs, for example ```[40032, 40033, 40034]``` which we are about to use. You can also use a special method if you are using Gmail, ```i.gmail_search()``` which behaves like the search box in Gmail. #### Size Limits If your search matches lots of messages, you can get an ```imaplib.erorr: got more than 10000 bytes```. This can be fixed by the following code: ```python import imaplib imaplib._MAXLINE = 1000000 ``` This sets the limit to 10,000,000 bytes instead of 10,000. ### Fetching emails When you fetch an email, you download it from the server. Unless you are using ```readonly=True```, when you fetch an email it will mark it as read. You fetch an email like this: ```python rawmsgs = i.fetch(uids, ['BODY[]']) # uids is the uids returned by search() ``` The object returned by fetch is complicated and hard to parse, so we will be using a second third-party module, ```pyzmail``` to parse them. ** Important: If you are installing pyzmail using pip on python 3.6 or above, you need to install ```pyzmail36``` instead of ```pymail``` or you will get an error in pip. ** This is how you parse a message with pyzmail: ```python import pyzmail msg = pyzmail.PyzMessage.factory(rawmsgs[40041]['BODY[]']) # Be sure to change the uid number ``` Using this object you can get a lot of information on the message. ```python msg.get_subject() msg.get_addresses('from') msg.get_addresses('to') msg.get_addresses('cc') ``` Sample Output: ``` Thanks! [('Alice Doe', '[email protected]')] [('Bob Smith', '[email protected]')] [] ``` These methods get the subject and addresses the message was sent to. ### Reading Messages ```python msg.text_part != None msg.text_part.get_payload().decode(msg.text_part.charset) msg.html_part != None msg.html_part.get_payload().decode(msg.html_part.charset) ``` Sample Output: ``` True 'Thanks for buying lunch yesterday. \r\n\r\n-Alice\r\n' True '<div style="font-family: courier;">Thanks for buying lunch yesterday. <br><br>-Alice</div>' ``` Email messages can have 2 parts: a text part and an HTML part. In pyzmail, they will either be ```None``` if it doesn't exist or if it does exist it will have a ```get_payload()``` method which returns ```bytes``` which can be decoded using ```.decode()``` with the charset stored in either ```msg.text_part.charset``` or ```msg.html_part.charset```. The text part is plaintext, the HTML part is HTML to be rendered for the user. ## SMTP in python basics SMTP is similar to IMAP, but doesn't have as many commands. To implement SMTP, we will be using the python built in library, ```smtplib```. ### Connecting to the server You should first find your providers SMTP settings by finding them in the list below, searching *your provider* smtp settings, or by contacting you email provider. **Gmail:** imap.gmail.com **IMPORTANT:** Gmail users may only check for emails every 10 minutes, or 600 seconds, so make sure to change your config! See this articlefor more information: https://support.google.com/mail/answer/7126229?hl=en **Outlook.com/Hotmail.com:** imap-mail.outlook.com **Yahoo.com:** imap.mail.yahoo.com **AT&T:** imap.mail.att.net **Comcast:** imap.comcast.net **Verizon:** incoming.verizon.net Once you have your settings, you can connect to the server by initializing a new ```smtplib.SMTP()``` object and starting TLS: ```python s = smtplib.SMTP('smtp.example.com', 587) s.starttls() s.ehlo() ``` ### Troubleshooting If you get an error while connecting or if your email provider is listed above as *port 465* then your email provider may not support TLS on port 587. In this case, you should connect using SSL to port 465 like this: ``` s = smtplib.SMTP_SSL('smtp.example.com', 465) s.ehlo() ``` ### Logging in Logging in is similar to IMAP, you call the ```login()``` function of your SMTP connection, using your email and password: **Be careful about leaving passwords in your code!** ``` s.login('[email protected]', 'mySuperSecretPassw0rd') ``` ### Sending emails To send emails, I have found that the ```sendmail()``` function doesn't really work as it can mess with email headers, instead we should use the python built-in module ```email```'s ```email.message.EmailMessage()``` class which makes settings the headers really easy and has many advanced capabilities which are listed in [the docs](https://docs.python.org/3/library/email.message.html#email.message.EmailMessage). Here is an example of how to send a text message: ```python text = """Dear Alice, You are welcome. Think nothing of it! -Bob """ msg = email.message.EmailMessage() msg['from'] = '[email protected]' msg["to"] = '[email protected]' msg["Subject"] = "Re: Thanks! " msg.set_content(text) res = s.send_message(msg) ``` In most email providers, though not required, prefacing the subject with Re: denotes a reply. In this example, Re: Thanks! would show up in the same thread as the original Thanks! message in both email clients. To return value of the ```send_message()``` function is a dictionary of the addresses to which sending failed. If successful, it should return ```{}```. ## Putting it all together: making the bot Using our knowledge, we are going to write the bot. First thing we need to do is store the configuration information. For repl.it, we will use ```input()``` but on your own computer you can hard-code values (not passwords! ). ### Using a configuration file Add a new file in repl.it called ```config.py``` and add the following: ```python import getpass radr = input("Adresses to log in to") # address to check and send from imapserver = input("IMAP server domain: ") # imap server for account smtpserver = input("SMTP server domain: ") # smtp server for account smtpserverport = input("SMTP Server port [587]: ") # smtp server port for starttls if not smtpserverport or smtpserverport == "": smtpserverport = 587 pwd = getpass.getpass("Account password: ") # password for account encoded with base64.b64encode sadr = input("Trusted addresses to receive from: ") # address to receive commands from check_freq = 5 ``` Here we set the config values to be used by the main program. For security, we will set a trusted email that is the only one that commands will be accepted from. This is so that no random people can email us commands. ### Initializing the IMAP connection ```python from config import * def imap_init(): """ Initialize IMAP connection """ print("Initializing IMAP... ", end='') global i i = imapclient.IMAPClient(imapserver) c = i.login(radr, pwd) i.select_folder("INBOX") print("Done. ") ``` Here we initialize the imap connection by import our config file (using ```import *``` is usually a bad idea because you don't know where things came from but we are just importing variables so it is okay). We also define i using ```global i``` so that it is available to the rest of our program. We also login to the server and select the "INBOX" folder. ### Initializing the SMTP connection ```python def smtp_init(): """ Initialize SMTP connection """ print("Initializing SMTP...") global s s = smtplib.SMTP(smtpserver, smtpserverport) c = s.starttls()[0] # The returned status code if c is not 220: raise Exception('Starting tls failed: ' + str(c)) c = s.login(radr, pwd)[0] if c is not 235: raise Exception('SMTP login failed: ' + str(c)) print("Done. ") ``` In this block, we initialize the SMTP connection. We use the same ```global``` technique as with IMAP and connect to the SMTP server and login, but we also check the status codes returned by the server to make sure everything was successful (we can't do this with IMAP). ### Getting unread emails The next step is getting any unread emails. ```python def get_unread(): """ Fetch unread emails """ uids = i.search(['UNSEEN']) if not uids: return None else: print("Found %s unreads" % len(uids)) return i.fetch(uids, ['BODY[]', 'FLAGS']) ``` Here we define a function to get unread emails. This function searches the IMAP object for any unread emails. It returns ```None``` if it didn't find any, or if it did, it fetches them from the server and returns them. ### Defining commands For our bot to work, we have to define some actions for it. To do this, we will define functions in ```config.py``` and add them to a commands ```dict``` which will map the command names to the functions. The functions will take 1 argument: the message split by lines. For now, we will make a command that will return "Hello, World! " every time no matter the message content: ```python def hello_world(lines): return "Hello, World! " commands = {"hello" : hello_world} ``` ### Analyzing the message Now we will analyze the message and determine what to do about it. ```python def analyze_msg(raws, a): """ Analyze message. Determine if sender and command are valid. Return values: None: Sender is invalid or no text part False: Invalid command Otherwise: Array: message split by lines :type raws: dict """ print("Analyzing message with uid " + str(a)) msg = pm.factory(raws[a][b'BODY[]']) frm = msg.get_addresses('from') if frm[0][1] != sadr: print("Unread is from %s <%s> skipping" % (frm[0][0], frm[0][1])) return None global subject if not subject.startswith("Re"): subject = "Re: " + msg.get_subject() print("subject is", subject) if msg.text_part is None: print("No text part, cannot parse") return None text = msg.text_part.get_payload().decode(msg.text_part.charset) cmds = text.replace('\r', '').split('\n') # Remove any \r and split on \n if cmds[0] not in commands: print("Command %s is not in commands" % cmds[0]) return False else: return cmds ``` Let's break this down. First we initialize the message object with the data we are given. Next, we make sure it is from our trusted address. Next we set the subject to start with Re: so that it shows up as a reply in the email thread. Next, we make sure we have a ```text_part``` to parse and split it by lines. We check that the requested command is a valid command, and if so, return the line of the message. ### Defining a mail function Defining a mail function will be useful so that we can see in the logs what was mailed to the user, and it will make the sending mail process as easy as passing the function the mail text. ```python def mail(text): """ Print an email to console, then send it """ print("This email will be sent: ") print(text) msg = email.message.EmailMessage() global subject msg["from"] = radr msg["to"] = sadr msg["Subject"] = subject msg.set_content(text) res = s.send_message(msg) print("Sent, res is", res) ``` ### Writing the event loop We will write the loop our bot goes through as it waits for a message to process. We want our bot to run until we interrupt it, so we will put it in an infinite loop. ```python imap_init() smtp_init() while True: # Main loop try: print() # Blank line for clarity msgs = get_unread() while msgs is None: time.sleep(check_freq) msgs = get_unread() for a in msgs.keys(): if type(a) is not int: continue cmds = analyze_msg(msgs, a) if cmds is None: continue elif cmds is False: # Invalid Command t = "The command is invalid. The commands are: \n" for l in commands.keys(): t = t + str(l) + "\n" mail(t) continue else: print("Command received: \n%s" % cmds) r = commands[cmds[0]](cmds) mail(str(r)) print("Command successfully completed! ") except KeyboardInterrupt: i.logout() s.quit() break except OSError: imap_init() continue except smtplib.SMTPServerDisconnected: smtp_init() continue finally: i.logout() s.quit() ``` This is a long code block, so let's break it down. First we check for unread messages and if there are none, then we wil get into a loop waiting for one. Next we loop over each of the unreads we have retrieved and analyze them. (there are a lot of loops in this portion). We then check the value returned by ```analyze_message()``` and if it was ```False``` meaning an invalid comand we send the user a helpful message detailing the available comands. If it was ```None``` meaning it is not from the trusted sender or has an invalid ```text_part``` we will skip it and continue the loop. Here we check the return value from ```analyze_msg()```. If it is None (meaning it is not from the trusted sender or doesn't have a text part) we skip it and continue to the next message. If it is False, meaning it is an invalid command, we helpfully send the user a message reminding them of the available commands. Otherwise, we assume we got the text to fun so we run the corresponding command. When it finishes, we send back the result and print a message to the console. The bot is complete! ### Testing the bot Now we can run the bot and test it! If you run the bot and send a message to the email it is connected to with the first line being ```hello``` then it should reply back "Hello, World! ". If you get any error, make sure you followed each step and if you think the error is on my part, let me know in the comments and I will fix it. ## Example commands: Weather Checker Here I will provide you with one of two example command: a program that gets the weather using the openweathermap.org API for your local area and emails it back to you! ### Setup Before you can use this program, you need to find a couple of values. To find your city's id, search for it at https://openweathermap.org/find and click on it in the results. The city id is the string of numbers at the end of the URL. To get your API key, follow the steps here: https://openweathermap.org/appid. This requires you to create a free account and wait one hour for you API key to be activated. Keep in mind the limit for calls with a free account is one per 10 minutes, so don't check too often or your account could be suspended. Now that you have your values, replace them in the code below. Also, we have one third-party dependency: the ```requests``` module. Don't forget to install it if you haven't already. ### The code ```python import requests from json.decoder import JSONDecodeError def weather(lines): city_id = str(5391959) url = "https://api.openweathermap.org/data/2.5/forecast?id=" api_key = "your api key here" res = requests.get(url + city_id + "&APPID=" + api_key + "&mode=json") if res.status_code != 200: return "Oops, code was " + code try: j = res.json() except JSONDecodeError: return "JSON decode error: \n" + res.text try: main = j["city"]["list"][0]["weather"][0]["main"] description = j["city"]["list"][0]["weather"][0]["description"] except Exception as e: return str(e) + "\n" + r.text return "The weather is: \n%s: %s" % (main, description) ``` Let's break this down. First we define our config values and get the API page using them. Then, we make sure the request was successful (if the status code was 200) and send a message back if it wasn't. Next we attempt to decode the JSON returned by the API and send back if we can't. Finally, we get the weather and the description of it and catch any error that occurs from it (usually the key not existing because of an error with the call) and send back the error and full content of the request so that we can diagnose the error. Finally, if all was successful, we return the weather to the user! ## Example command: Command and Shell Script executor This command won't be useful on repl.it, but rather on your own server. This is really two commands: one that will run a single command, and one that will write the message contents to a file on disk and execute it. (useful if you want to run commands in the same working directory or with variables because if you only run one command at a time, the shell will be reset each time). ### The code: command executor ```python import subprocess as sub def exec_cmd(cmds): param = cmds[2] """exec_cmd: executes a command with subprocess """ try: p = sub.run(param, shell=True, timeout=20, stdout=sub.PIPE, stderr=sub.PIPE) except sub.TimeoutExpired: return "Command timed out. " return '''Command exited with code %s Stdout: %s Stderr: %s ''' % (p.returncode, p.stdout.decode(), p.stderr.decode()) ``` This command isn't super complicated. We take the command to be executed as the second line and run it using ```subprocess``` with a timeout of 20. If the timeout expires, we inform the user. Otherwise, we send the return code, STDOUT, and STDERR. ### The code: shell script executor ```python def runscript(lines): """ Writes input to disk and executes it. Returns any errors. IMPORTANT: Before running, make sure file 'script' exists and is executable. """ try: f = open("script", "w") count = 0 for i in lines: if count is 0 or i is 1: count = count + 1 continue f.write(i + "\n") count = count + 1 f.close() p = sub.run("./script", shell=True, timeout=timeout, stdout=sub.PIPE, stderr=sub.PIPE) r = """Script finished! Return code: %s Stdout: %s Stderr: %s """ % (p.returncode, p.stdout.decode(), p.stderr.decode()) except Exception as e: return "Error: \n" + str(e) return r ``` This code is extremely similar to the single command runner code, except for it loops through the lines for the message (ignoring lines 1 and 2) and writes them to a file named ```script``` which, as mentioned in the docstring, should exist and be executable before this command runs. ### Adding the commands to the bot To add these commands to the bot, and to define you own, paste the functions into ```emailcmd_config.py``` and update the the ```commands``` dictionary at the bottom by putting the key as the alias for the command and the value as the function object. ``` commands = {"weather":weather, "exec":exec_cmd, "script":runscript} ```
7
posted by Scoder12 (23) 3 months ago
โ–ฒ
6
Javascript Games: Lesson 1 -- GuessIt
# Introduction This is the first tutorial in a series designed to teach basic JavaScript game programming. It's the spiritual successor to BASIC Computer Games, in which David H. Ahl provided source code for 100 BASIC games. Today, we would think of those games as "terminal programs", displayed using ASCII text. We will be running our games in a web browser, and while they will initially be text-based, we will quickly move into the realm of canvas graphics. But first things first. In this tutorial, we will cover the boilerplate concepts behind all games: variables, output, input, functions, loops, and flow-of-control. Don't worry if you don't know what any of those mean -- that's what the tutorial is for. Once we have discussed these fundamentals, we'll present the code for our first game: GuessIt. # Fundamentals All games rely on the following foundational concepts. More advanced games involve additional ideas, but *every* game needs these: * variables -- named buckets that hold data * output -- how we show the game to the user * input -- how we accept data from the user * functions -- how we re-use code from at different points in a program * loops -- how we perform repeated tasks efficiently * flow-of-control -- how we make the game choose one path among many **Variables** Variables are just chunks of computer memory that hold data for us. You can think of a variable as a bucket with a name. You can put things in the bucket and take them out again. In some languages, there are different kinds of buckets for different kinds of data. Text goes in one kind of bucket, decimal numbers in another, and integers in yet another. In JavaScript, all buckets can hold anything you want. This simplifies life in some ways, and complicates it in others. To define a new bucket, we use the 'var' keyword, followed by the name of the bucket: ```js var myName; ``` (the semicolon tells JavaScript that it has reached the end of this line of code). Once you have defined a new bucket, you can refer to it by name elsewhere in your code. You can use *operators* like the equals sign to manipulate the data inside the bucket. For example, in JavaScript, the single equals sign (**=**) tells the computer to take whatever is on the right hand side and put it into the thing on the left hand side: ```js myName = "Mark"; ``` takes the text data inside the double quotes and sticks it in the bucket called myName. You can combine the definition of the bucket and the assignment of its value into a single line, if you like: ```js var myAge = 49; ``` Now the variable 'myAge' has a value of 49. Variables can hold text data, numerical data, code, graphics, sound, and any combination of these things wrapped into an 'object' (which we will cover in later tutorials). Variables can also contain lists of data. These lists are often called "Arrays", and you create a list by enclosing the entries in square brackets ('[' and ']') and separating them by commas: ```js var myDogs = ["Hershey", "Koda"]; ``` You can also break this up over multiple lines: ```js var myDogs = [ "Hershey", "Koda" ]; ``` That's a lot to remember, but don't sweat it. The most important thing is: *variables are buckets that hold data*. **Output** All games need to output data to the player(s). Our first games will run on a web page, so we will use HTML for output. We will rely on the "write line" command supplied by the document in which we are running: ```js document.writeln("Your message goes here..."); ``` Note that in HTML, the "string" of letters '<br>' (without quotes) is a "line break" that marks the end of a line of text. Use this to move down to the next line. For example: ```js document.writeln("First line.<br>"); document.writeln("Second line.<br>"); ``` These will appear as two separate lines in the web browser. Without the line breaks, they would appear together on a single line, even though we used two different writeln statements. **Input** Players need a way to affect the games they play, whether it's telling the dealer to "hit me" in Blackjack, or firing a weapon in Doom. In our case, we will be responding to keyboard events. To do this, we will rely on the "event listener" system supplied by the document in which we are running. An "event" is a change in the computer environment that affects the document. This could be many things: a key press, a mouse click, a message telling the browser to resize itself. Web documents supply an "event listener" system to capture and respond to important events. To listen for a particular event, we will call the document's "addEventListener" code: ```js document.addEventListener("keydown", function(event) { // ...your code here... }); ``` The above line tells the document to execute "...your code here..." whenever the user presses key down. Don't worry about the 'function(event)' bit -- we'll discuss that in the next section. For now, let's consider this concrete example: ```js document.addEventListener("keydown", function(event) { document.writeln("Someone pressed a key!"); }); ``` This displays the message, "Someone pressed a key!" each time the user presses a key. There are many other events to which the document can respond, like "keyup", "mousedown", "mouse", and "mousemove", just to name a few. We'll get to those when we need them. For now, "keydown" is good enough. Sometimes, you want your game to ignore events to which it previously responded. You can accomplish this using the "removeEventListener" command: document.removeEventListener("keydown"); This would undo the "addEventListener" we used above. **Functions** Often, you write a chunk of a code that you want to use over and over. For example, suppose you wrote a chess program. Games of chess can last many turns. You would hope that you could write the code to play a single turn, then simply re-use that code for each subsequent turn. *Functions* allow you to do this. A function is a chunk of self-contained code designed to perform a specific task. Functions often require data to complete their task, so when you define the function, you must indicate what data it's expecting as input. In JavaScript, function definitions look like this: function(list of input data) { // Chunk of code to execute using the input data }; If this seems confusing, think of a function as a meat grinder: you put stuff in, you turn the crank, and something useful comes out. The "stuff you put in" is the "list of input data". The "turn the crank" bit is the "chunk of code" part. The "something useful comes out," well...we haven't covered that. It's called the 'return value'. A specific example will clear all this up. Consider a function that computes the area of a rectangle: ```js function(length, width) { var area = length * width; return area; }; ``` You send in a length and a width, it multiplies them together to find the area, then it 'returns' that area back to you. Remember that addEventListener stuff? Recall that the function looked like this: ```js function(event) { document.writeln("Someone pressed a key!"); } ``` Now you know what the 'function' is for -- that tells JavaScript what function to execute when someone presses a key. The 'event' data is passed into the function from the web browser, and it tells the function things like what key was pressed. In fact, we could change the function to display that information as follows: ```js function(event) { document.writeln("Someone pressed the " + event.key + " key!"); } ``` Notice that this function doesn't return anything (there is no 'return' statement at the end). That's fine. Not all functions return a value. Sometimes you use them just to produce output when the crank is turned. One last note: you can store a function inside a variable. Doing so lets you execute (or call) the function using the bucket's name. This is handy shorthand for executing code! Example: ```js var print = function(theMessage) { document.writeln(theMessage + "<br>"); }; ``` Now, any time you want to display a message, you can use this: print("Fred ate bread!"); This will send the data "Fred ate bread" to the above function and print it out, automatically appending a <br> so that subsequent messages appear on subsequent lines in the document. Applying this to our area function, we get: ```js var areaOfRect = function(length, width) { var area = length * width; return area; } ``` And we could write a short program as follows: ```js var area = areaOfRect(3, 4); print("The area is " + area); ``` What do you think would be the output of this program? **Loops** Often, games need to rapidly repeat a task. For example, if you are drawing a square grid, you must print multiple horizontal and vertical lines. For something like tic-tac-toe, this isn't so bad, but for a game like Go, you wouldn't want to draw every line with a new command. Loops allow programmers to efficiently perform duplicate commands. There are several kinds of loops, all of them follow the same philosophy: the programmer defines a *loop condition* during which the program repeats a chunk of code over and over. The programmer follows the loop condition by the code to be repeated: [Loop Condition Goes Here] { // ...code to repeat goes here } Notice that in JavaScript, we use curly bracers ('{' and '}') to "section off" the code that will be repeated. JavaScript supports several different kinds of loops. We will start with just two: the 'while' loop and the 'for' loop (which is really just a 'while' loop in fancy clothes). The *while* loop looks like this: while (...something is true...) { // Do this code } Consider this example: ```js var greets = 0; while (greets < 5) { print("Hey there!"); greets = greets + 1; } ```` This probably looks confusing, especially this bit: greets = greets + 1; Clearly, this is nonsense! There is no number that equals itself plus one. True...but remember -- in JavaScript, the '=' symbol differs from what you learned in Math. In JavaScript, it says, "takes what's on the right-hand side and put it into the thing on the left-hand side." Also remember that 'greets' is a bucket that we initially fill with the value 0. So this: greets = greets + 1; reads as: "Take the value in greets (currently 0), add one to it, and place this back in the greets bucket." So, after executing this line the first time, greets' value changes from 0 to 1. We then hit the bottom curly brace and bounce back up to the 'while' statement, where we check the loop condition. Is the value in greets less than 5? Yep! So we once again execute the code inside the curly braces. While doing so, we again hit the line greets = greets + 1; but *this* time, greets contains '1'. So what happens? We add one to it, and store the resulting value (2) back in greets. Then we hit the bottom curly brace, bounce back up to the top of the loop, and check the conditions. 2 is still less than 5, so we go around again... ...and again... ...and again... ...and again... ...and finally we bounce back to the top, check the condition and see that greets is **equal** to 5, rather than less than 5, so the loop condition is false and we break out of the loop by instantly jumping past the last curly brace. That might seem like a silly example: why would we want to print out the same message 5 times? So let's consider a more useful example. Remember my dogs? ```js var myDogs = ["Hershey", "Koda"]; var count = 0; while (count < myDogs.length) { print(myDogs[count] + " is barking."); count = count + 1; } ``` Now we're using the *while* statement to *iterate* (or step through) a list of data. Notice that we use square brackets ('[' and ']') to access a single element of the list, and that the first element in the list is number 0. In other words: myDogs[0] contains the word "Hershey", and myDogs[1] contains the word "Koda". Also note that JavaScript will tell you how long any list is when you use the ".length" accessor: myDogs.length is 2. *While* loops are handy, but after you've written a few, you'll see that most of them fall into the following pattern: ```js var some_loop_counter = some initial value; while (condition_involving_the_loop_counter) { // ...loop code... some_loop_counter = some_loop_counter + some increment; } ``` Recognizing this pattern, the authors of JavaScript created the *for* loop to make your code more concise. The *for* loop works exactly like the *while* loop, but it lets you compress the syntax: for (initialize the counter; check the condition; update the counter) { // ...loop code... } For example, our doggy-loop above would look like this when written as a *for* loop: ```js for (var count = 0; count < myDogs.length; count = count + 1) { print("My dog " + myDogs[count] + " is barking."); } ``` If you want to compress this even further, you could take advantage of the math operators '+=' or '++': count += 1; is shorthand for count = count + 1; And both count++ and ++count tell JavaScript to increase the value of *count* by 1. There is a difference between these ++count and count++, but we'll save that for another tutorial. So, the super-compressed version of our doggy loop would be this: ```js for (var count = 0; count < myDogs.length; ++count) { print("My dog " + myDogs[count] + " is barking."); } ``` **Flow-of-Control** Finally, let's talk about "flow of control." That's just a fancy way of saying, "my game can execute one of several options at the appropriate time." For example, suppose you are making a dungeon crawler and you have AI monsters that can take one of several actions when encountering the player. You would use flow of control statements to pick one of those actions when an encounter occurs. As with loops, there are several flavors of decision-making in JavaScript. For now, we will restrict ourselves to the "if...then" statement. Generally speaking, that structure looks like this: If (condition 1 is true) { // Do this code } else if (condition 2 is true) { // Do *this* code instead } else if (condition 3 is true) { // etc. } // and so on for as many conditions as you like, until finally else { // Do this if nothing else up to this point was true. } Let's look at an example by modifying our doggy loop: ```js for (var i=0; i<myDogs.length; ++i) { if (myDogs[i] === "Hershey") { print(myDogs[i] + " is barking."); } else if (myDogs[i] === "Koda") { print(myDogs[i] + " is farting."); } else { print("That's not my dog!"); } } ``` Note that the triple-equals ('===') in JavaScript asks the question, "is equal to". So a line like this: if (myDogs[i] === "Hershey") Reads as, "if the ith element in myDogs is equal to the word Hershey". As you can see, this will print out: Hershey is barking. Koda is farting. Which might seem like a fart joke, but is sadly the status quo with my black lab, who is currently practicing his "art" 6 feet from my desk. Ugh. # Conclusion You now have all the building blocks you need to make a simple game. For our first outing, we will write "GuessIt" -- a variation on the card game Acey Ducey. In that game, you flip two cards, then place a bet as to whether a third card, when revealed, will fall between the first two. In GuessIt, we use randomly-generated numbers from 0 to 9, but otherwise, the game is identical. Take a look at the source code. We have broken it up into two files: _definitions.js -- in which we define all the functions and variables used in the game_ _commands.js -- which actually executes the commands defined in definitions.js to start the game_ If you just loaded definitions.js, the game would never start, because the definitions don't do anything but fill buckets with values and functions. If you just loaded commands.js, the program would throw an error because the commands rely on the buckets defined in definitions.js. You don't have to lay out your JavaScript programs this way -- we just did it to drive home the point that defining variables and functions is a *passive* activity, like setting up the game board. It doesn't actually result in the activity of playing the game itself. To do that, you have to execute the commands you created earlier. The code for GuessIt contains a lot of comments to help you understand what we're doing at each point. Some of it repeats what we have covered here, but in a more specific context. Once you are comfortable with what it's doing, try modifying the game. Here are some suggestions: Change the background color by using the command: document.body.style.backgroundColor = "gray"; // Or whatever common color name you want Change the amount of starting cash given to the user Change the payout formula Or, the biggie: Add a new eventListener when the player loses that resets her starting cash and allows her to play again Good luck, and have fun!
7
posted by MarkKreitler (18) 1 month ago
โ–ฒ
25
Tutorial: Building a Discord Bot with Python
Hey all, I published this tutorial on writing chatbots with Python and Discord a few weeks ago and I'm excited to share it with you here now. I'm watching the comments here and over at codementor so let me know if you have any questions. https://www.codementor.io/garethdwyer/building-a-discord-bot-with-python-and-repl-it-miblcwejz
9
posted by GarethDwyer1 (64) 4 months ago
โ–ฒ
19
Writing a blog in go!
# Preface I started this tutorial before the contest opened, but because of school and other complications I was only able to post it today. I hope you see how much effort I put into it and give it an upvote, even though I'm probably submitting too late to win. ### Note The repl at the bottom might not work, for reasons that will become clear to you if you follow the tutorial. However, don't worry, as the code in it is still functional, and if you follow the tutorial in your own repl you'll be able to make it work! Anyway, without further adieu, here it is! ### If you have trouble viewing this on repl.it, I also have it in a [Github Gist](https://gist.github.com/vityavv/be3d8df8063f21051e8b5bbeb844a4aa) # Writing a blog in go! ## [CODE](https://repl.it/@vityavv/BisqueStickyActivemovie) ## About this tutorial * __Why go?__ - *Go is a really fast and underappreciated programming language. It is surprisingly simple, especially compared to other low-level languages like rust and c++, and it's very very easy to make web servers in it* * __What is this tutorial based on?__ - *This is based on [scms](https://github.com/vityavv/scms), a CMS I wrote a while ago that is extremely simple* * __Will this tutorial cover all of the features in scms?__ - *Since this is only a short, one-part tutorial, it will **not** cover the API, SQL database, Markdown (you can't install go packages yet, anyway), or drafts.* ## Before the tutorial Before the tutorial, you should have a basic understanding of Go. [Tour of Go](https://tour.golang.org/welcome/1) should help you out! ## Getting started - managing articles First, let's make a file to manage our articles. Each article will be a *json* file that looks like this: ```json { "title": "The title of the article", "content": "The content of the article" } ``` So, let's make a *struct* in our new file, `articles.go`, to reflect this: ```go type Page struct { Title string `json:"title"` Content string `json:"content"` } ``` You may be wondering "Why can't we just have `title string` and `content string`? Well, `json.Unmarshal` (we'll talk about this function in a bit) only puts values into capitalizd struct values, so we have to make them capitalized and then use *tags* to tell json.Unmarshal what to put there. Anyway, before we begin making our functions, we have to get one more thing out of the way: ```go var pages map[int]Page = make(map[int]Page) ``` One option when making a blog like this is to load the json file each time you get it. However, we're smarter than that. We can store each article in memory beforewards to make the application much faster, and easier to make. As a downside, the more articles you have, the more memory you're going to use. However, articles are just text, which should not take up that much space. Another concern here is why I used `map[int]Page` instead of `[]Page`. I did this because each article will have an ID as it's name... and if an article gets deleted, or if they're somehow out of order in the next function, it will be harder to compensate. Even if you decided to make a slice and just skip the deleted files, imagine this scenario: someone has three articles, 1.json, 2.json, and 3.json, but they delete 2.json. There's a bunch of links to the third article, but they all break because it gets deleted from the slice! However, if map is used, you can avoid this problem. Finally, we can get to making our first function, and the most important of them all: the `FileInit` function. The `FileInit` function should be run at the beginning to load each article into the pages map we made earlier. Let's break it down to it's basic parts. The first couple of lines are: ```go files, err := ioutil.ReadDir("./articles") if err != nil { log.Fatal(err) } ``` This is a new import! Make sure you have your file set up correctly, with `package main` at the top, and then in your imports add `io/ioutil`, used for reading and writing files. Now, let me explain what these lines do: they get a list of the files inside of the `articles` *directory*, or folder. The next three lines are basic error handling, which also use the `log` package. **For the rest of the tutorial, I will be ommitting error handling for sake of brevity. Every time you see a variable called err, assume that following that line is error handling as shown above**. After we get that list, we do this: ```go //for every file in the articles folder for _, file := range files { //The following line reads the file we are on pageFile, err := ioutil.ReadFile("articles/" + file.Name()) //Notice the err above, which means that in the real code I did error handling after it //This line makes a new page, called page var page Page //This line "Unmarshals" the json found in the file we read into the page. There is error handling after this one too err = json.Unmarshal(pageFile, &page) //In this following line, we get the page number. This includes a new import, "strconv", which I use to convert strings to numbers and vice versa. Here, I get the file name, take away the ".json" at the end, and then convert it to a number (int). pageNum, err := strconv.Atoi(file.Name()[:len(file.Name())-len(".json")]) //finally, I add the page to our map pages[pageNum] = page } ``` I've annotated the code so you can read through it, but basically it reads each file and puts it in the pages map. That's pretty much it, for the `FileInit` function! Our next function will be a function called `GetFrontPage`. The front page of our blog will have our five most recent articles on it. Here it is, annotated: ```go //The function doesn't take anything, but it returns a slice of pages. The reason you see (fpPages []Page) in the return is because it already defines fpPages in the begining of the function, and then I can just type "return", without anything, and it will return the fpPages variable. This is a really cool feature of go func GetFrontPage() (fpPages []Page) { //make a new slice of pages, with the *capacity* to hold 5, but a length of zero. This is some weird memory managament wizzard magic I saw online, but lengths and capacities are covered extensibly in the tour of go (have you read that yet? ;) fpPages = make([]Page, 0, 5) //Woah! Where did this getPageNumbers come from? I'll explain, right after this pageNumbers := getPageNumbers() //this for loop counts down backwards from the last element in the page numbers to the fifth-to-last for i := len(pageNumbers) - 1; i > len(pageNumbers) - 6; i-- { //Sometimes there's less than 5 elements, so we have to make sure that the page actually exists if i >= 0 { //this uses the built-in append to add the new page to the fpPages slice fpPages = append(fpPages, pages[pageNumbers[i]]) } else { //in case it doesn't exist, it just adds an empty page struct, which makes everything the nil value. fpPages = append(fpPages, Page{}) } } //as discussed earlier, I just have to type return, since the computer already knows that I'm returning fpPages return } ``` But where did this function, `getPageNumbers`, come from? Well, as discussed earlier, we don't always have the page numbers as 1, 2, 3, 4, and 5. Sometimes they're out of order, and with gaps in them. So, I wrote a helper function to get me the page numbers. Have a look: ```go //As before, using implicit returning by already defining pageNumbers func getPageNumbers() (pageNumbers []int) { //As before, magic wizzardry. This is actually pretty similar to get front page. I make pageNumbers into a slice of ints, the capacity equal to how many pages are there pageNumbers = make([]int, 0, len(pages)) //when using range with maps, you do "key, value := range <map>". Here, I only need the keys, so I can omit the values. for key := range pages { //Pretty simple: I add the key to the pageNumbers slice pageNumbers = append(pageNumbers, key) } //New import! "sort" sorts stuff, as you can probably guess. This basically sorts the numbers. sort.Ints(pageNumbers) //Implicit return! Yay! return } ``` We only a couple of functions left. An imediately obvious one is the function to get a single article: ```go //here, it takes the id of the article and returns two things: the page, and the error. This is standard practice for how go handles errors func GetArticle(id int) (Page, error) { //make sure the page exists with this one simple trick! page, exists := pages[id] if !exists { //Another part of error handling: if there's an error, return the nil value for the first return and the error for the second. This line also includes a new import - "errors" - to make errors extremely easily return Page{}, errors.New("Page not found!") } //finally, we return the page that exists and nil (the null value for an error) as the error, because there's no error return page, nil } ``` Another obvious one is to get all of them, so we can have links to them! However, this one is blatently obvious. ```go func GetAllArticles() map[int]Page { return pages } ``` And finally, we come to our missing links. This function is called `CreateArticle`, and it creates a new article, writes it to the file, and adds it to our pages array. Here it is, annotated: ```go //This function takes the title and the content of the article func CreateArticle(title, content string) { //Here, we create a new page, with the title and content aptly set newPage := Page{ Title: title, Content: content, } //Here, we make a json string out of our new page. Note the "err", which means that I had error handling after this line but omitted it json, err := json.Marshal(newPage) //Here, we get our page numbers, from before. They're already sorted! pageNumbers := getPageNumbers() //Finally, we get our new page number, by taking the last page number and adding one to it. newPageNumber := pageNumbers[len(pageNumbers)-1] + 1 //We add the page to our pages map pages[newPageNumber] = newPage //Finally, we add the json to our article, converting the page number to a string and putting it in the right format. The 0600 you see there is for permissions. It basically means that the person who made the file can read and write to it, and nobody else. This is the same number that the wiki tutorial uses, by the way. Also, notice the "err" err = ioutil.WriteFile("articles/" + strconv.Itoa(newPageNumber) + ".json", json, 0600) } ``` And our final function, much simpler, is to remove an article. Here it is: ```go //Notice here that we return the error type func DeleteArticle(id int) error { //When you assign <map>[<key>] to two values, the second value will contain a boolean saying whether the first one exists or not. We can use this to check if we have our article, and if not, return a new error saying "Article not found" if _, exists := pages[id]; !exists { return errors.New("Article not found") } //delete is built in to go. It is used to delete things from maps. Our pages variable is a map[int]page, so taking the int id and deleting it from pages would delete the article delete(pages, id) //Here, instead of doing the traditional "err := os.Remove(...)", we can instead return it, since we know that os.Remove returns an error. We can let whoever is using the function (which is going to be us, coincedentally) deal with it instead return os.Remove("articles/" + strconv.Itoa(id) + ".json") } ``` And with that, we are done with our articles file! ## Part two: Serving the pages ### Part 2.1: The templates Go is a wonderful programming language for so many reasons, but one of them is that it has built in HTML templates! With that in mind, I created three different templates for the three main pages that will go into our blog, using Go's `html/template` module. In part 2.2, I'll talk about how I use these, but before I do that. By the way, if you're viewing these files in the GitHub Gist, they are just called `blahblahblah.html`, but in the repl, and the final application, all of them are in the `templates` folder. First, we have the front page. As discussed previously, the front page has the five latest articles on it. To do that, I have this code: ```html <!DOCTYPE html> <html> <head> <title>My blog!</title> </head> <body> <h1>My blog!</h1> <hr> <!-- Here, we use range, because we pass a slice of articles to the template. This basically means that for everything between {{range .}} and {{end}}, "." will be defined as the article that we are on. --> {{range .}} <!-- Here, we make sure that the article exists by making sure it has a title. Previously, if we had less than five articles, we would put null articles in there, so this is to make sure that we don't have a bunch of extra space at the end of our page --> {{if ne .Title ""}} <!-- here, "." is defined as an instance of our Page type, so we can just access its properties like this --> <h2>{{.Title}}</h2> <p>{{.Content}}</p> <hr> {{end}} {{end}} <a href="/archive">See all articles</a> </body> </html> ``` Second, is our archive, which lets us see links to every single article. Since we use GetAllArticles() here, and that returns a map, we can use the map keys to provide links to each article. ```html <!DOCTYPE html> <html> <head> <title>My Blog - Archive</title> </head> <body> <h1>My Blog - Archive</h1> <ul> <!-- Here, we have an unordered list using range. The reason we don't just have {{range .}} is because we need the key too, for the link --> {{range $key, $value := .}} <li><a href="/articles/{{$key}}">{{$value.Title}}</a></li> {{end}} </ul> <a href="/">Back home</a> </body> </html> ``` And finally, our simplest page, the article page, which shouldn't really need annotation: ```html <!DOCTYPE html> <html> <head> <title>My Blog!</title> </head> <body> <h1>{{.Title}}</h1> <p>{{.Content}}</p> <a href="/">Back home</a> </body> </html> ``` That's it for our templates! ### Part 2.2: Serving Now that we have our files, we have to serve them to the user through our webpage! We do this with the help of one very special package, `net/http`! In clasical low-level languages, the default http solution is usually either non-existent or very hard to use. However, with go, it is actually quite easy to use `net/http`, even easier than `express` for node.js in some cases (e.g. built-in form parsing). Anyway, it handles a lot like `express`, but if you don't know `express`, don't worry about it, because I will be going through each line of code, step by step. The file we will be writing to is `main.go`. Our first function will be the simplest and most important---the `main` function ```go func main() { //Initialize our files. Covered in part one, we need to put this at the top so it caches (loads) all of the files FileInit() //We add a *handler*, more on this in a sec, for any url that starts with /articles/. This includes /articles/1, /articles/2, and /articles/abacabadabacaba. The handler is articleFunc, a function which we will also discuss shortly http.HandleFunc("/articles/", articleFunc) //We add a handler for anything starting with "/", that doesn't start with "/articles/", and that handler is httpFunc. http.HandleFunc("/", httpFunc) //finally, we open up the server on port 8080. In a real environment, you'd use 80 for http. However, since we are using repl.it (or if you're simply testing this on your computer), we put any number we want above 1000. 8080 is a common testing number, as are 3000 and 8000. We use log.Fatal here (log is an import!) so that if http.ListenAndServe returns an error, we can stop the program and output the error. log.Fatal(http.ListenAndServe(":8080", nil)) } ``` while this is a simple function, it packs a lot of information. Let's look at `http.HandleFunc`. `http.HandleFunc` will set a function as a handler, meaning that it will call that function when the specified url is encountered. Since we have `"/articles/"` set to `articleFunc`, every time the server gets a request for `/articles/...`, it will call articleFunc with its parameters. If the request doesn't start with `/articles/...`, it will use the next one, which in our case is `/`, the catch-all, and it will call `httpFunc`. Here's the two functions: ```go //this func has to take the http.ResponseWriter (the thing we use to respond to the request) and a pointer to http.Request (the thing with all of the information from the request) as arguments, and returns nothing, as defined by http.HandleFunc func articleFunc(w http.ResponseWriter, r *http.Request) { //first, we get the article number. we do this by getting the URL and taking the "/articles/" part away from it num := r.URL.Path[len("/articles/"):] //Then, we see if the last character is "/", and if so, we remove it. We use single quotes here because when we access a single character of a string, it turns into a uint8, and we can convert single characters to uint8s by using single quotes around them. if num[len(num) - 1] == '/' { //Subtracting the last element, a slash num = num[:len(num) - 1] } //here, we convert the string to a number. If you go to /articles/1, you're fine, but if you go to /articles/abc, the function errors, leading us to the next if statement pageNum, err := strconv.Atoi(num) if err != nil { //I chose not to omit this one because here instead of log.Fatal, we use http.NotFound, giving it our w and r. http.NotFound(w, r) } //Get the article from previous page, err := GetArticle(pageNum) if err != nil { //the only error that returns is "Page not found" so we can safely assume that there's a 404 http.NotFound(w, r) } else { //then, we simply execute the templat---wait a sec, executing templates? We didn't talk about this yet! Well, hang on, and in just a sec I'll show you this wizardry. executeTemplate(w, "article.html", page) } } ``` And here's `httpFunc`, the simpler one: ```go func httpFunc(w http.ResponseWriter, r *http.Request) { //first, we make a switch, a more efficient set of if statements switch r.URL.Path { //"/" and "/index.html" are both the same thing, so we do the same thing for them case "/", "/index.html": //oh, there's that pesky executeTemplate function again! I promise I'll get to it, just hang tight! Anyway, our front page uses the GetFrontPage function. executeTemplate(w, "frontPage.html", GetFrontPage()) //finally, we return out of the case, to end the function. return //pretty much the same thing as above case "/archive", "/archive.html": executeTemplate(w, "archive.html", GetAllArticles()) return } //Finally, if we haven't returned, that means that our thing was not found, so that's exactly what we do: error! http.NotFound(w, r) } ``` Now I've got you hooked---surely, you are wondering "What is this `executeTemplate` function? How does it work?!?"---here, we use another wonderful built-in method of Go: the built in HTML Templates! Wait... we've heard this one before, haven't we? Well here we are, putting our wonderful templates to good use. We start with this line, at the beginnning (after all of the imports, of course) ```go //make sure you import "html/template" var templates = template.Must(template.ParseGlob("./templates/*.html")) ``` Basically, with this line, we make a templates variable and set it to all of the templates in our templates folder (ParseGlob). The `template.Must` part is basically just a convinient wrapper around it saying that if there's an error, the app should exit immediately with that error. Since this happens at the very start and at no other time, this is OK! Anyway, let's look at our `executeTemplates` function to see how we managed to pull this off: This function takes three parameters. It needs the http.ResponseWriter from our http funcs so that it can write the response to them. It also needs to know what template is being executed. Finally, it needs the content. Since the content and template are different each time, we use an "interface{}", meaning we don't really know the type. In fact, we don't have to know the type at all, because ExecuteTemplate takes a "interface{}" for its content, so as long as we match everything up when we call the function, we should be fine. ```go //I was going to put the above paragraph right here as a comment but I realized it was getting too long func executeTemplate(w http.ResponseWriter, templ string, content interface{}) { //We use templates.ExecuteTemplate() to execute the specific template we want out of the ones we loaded. You can see how this is used in the useage of the function in previous functions. err := templates.ExecuteTemplate(w, templ, content) if err != nil { //Here, instead of killing the server, we give them the error, and a 500 internal server error. http.Error(w, err.Error(), http.StatusInternalServerError) } } ``` That's pretty much it for our main.go file... *so far...* # Part 3 - Administration! This is our final and hardest part, and that is being able to delete and create articles without booting into the repl. Since you can't get packages for go yet, you have to do some work arounds, which I spent a lot of time finding out, so you're going to have to carefully follow these steps: * Make yourself an explorer ([how](https://repl.it/talk/announcements/Become-an-Explorer/6180)) * Open the command pallete by making sure the editor is in focus and pressing F1 * Type in shell, press enter * Type in: `go get golang.org/x/crypto/bcrypt` * Press enter. You might get an error, ignore it (unless it leads to further issues) Why are we doing all this? Well, we can't just store our password in plain text! We have to make sure that it is protected securely, and the way to do that is to use the bcrypt library (there are some others you can use too, but bcrypt is pretty much the industry standard). Other than the first step, which you only need to do once, you may have to do this every time you load up your repl, because of how repl.it works, unfortunately. Anyway, with that out of the way, let's look at how we're going to do things. We will have a "/dashboard" page, pretty similar to our "/archive" page, but this time we will add buttons to delete articles and to create new ones. This part is pretty simple, so we can add this case to our switch inside of `httpFunc`: ```go case "/dashboard", "/dashboard.html": executeTemplate(w, "dashboard.html", GetAllArticles()) return ``` The dashboard itself, though, will be a little bit more complicated. It utilizes javascript to make a "DELETE" request to the server when articles are deleted, but you don't have to worry about knowing javascript, because I can walk you through it: ```html <!DOCTYPE html> <html> <head> <title>My Blog - Archive</title> </head> <body> <h1>My Blog - Archive</h1> <!-- Button links to the "/new" page, for making new articles --> <a href="/new">New article</a><br> <!-- same as before, with the article, except... --> <ul> {{range $key, $value := .}} <!-- Here, we have a button element, and when it is clicked, it calls the del function in our javascript with the paramater being our key. --> <li><a href="/articles/{{$key}}">{{$value.Title}}</a> | <button onClick="del({{$key}})">Delete</button></li> {{end}} </ul> </form> <a href="/">Back home</a> <script> //Here's our del function! Javascript doesn't care about types, but the equivalent in go would be "func del(key int) {" function del(key) { //The prompt function creates a dialog box asking for a password password = prompt("Please enter your password"); //We create a new formData object to turn our password into formdata that go can then use let formData = new FormData(); formData.append("password", password) //We use fetch to make the request. The key there will be replaced with whatever the key that's passed to the function is fetch(`/delete/${key}`, { //For our options, we set the method to DELETE (as to be fancy), and our body to the formData from earlier method: "DELETE", body: formData //javascript async mumbo jumbo that translates to "get the text from it" }).then(r => r.text()).then(r => { //if it isn't successful then we alert the error, otherwise we reload the page to reflect the change. if (r !== "Article successfully deleted") { alert(r); } else { location.reload(); } //finally, if something goes wrong, we alert that too }).catch(alert); } </script> </body> </html> ``` See? That wasn't so hard. But wait, how do we handle these requests? Well, let me introduce you to the next function in our `main.go` file, `deleteFunc`. This will also introduce us to how go's `bcrypt` library works. To use this function, put `http.HandleFunc("/delete/", deleteFunc)` in your main function, anywhere above the "/" handler. ```go //The function is formatted like a normal http.HandlerFunc func deleteFunc(w http.ResponseWriter, r *http.Request) { //Here, we get the key by removing "/delete/" from the path. strKey := r.URL.Path[len("/delete/"):] //We use strconv to convert the key to an actual key. key, err := strconv.Atoi(strKey) //If, of course, the key isn't an int, we error with a 400, meaning there was a bad request if err != nil { http.Error(w, "That's not a valid key", http.StatusBadRequest) //And stop executing return } //Now, if the method is DELETE (which it should be)... if r.Method == "DELETE" { //We make sure that the form sent has a password value. If not, we error, again with a 400. if r.FormValue("password") == "" { http.Error(w, "Password is missing", http.StatusBadRequest) //Otherwise... } else { //Remember this line. This is how we use bcrypt to check passwords. Also, remember "pwHash," because we'll talk about that in a second. Anyway, as you can probably guess, this converts the two strings to byte slices before comparing them, because that's what bcrypt uses err := bcrypt.CompareHashAndPassword([]byte(pwHash), []byte(r.FormValue("password"))) //Instead of returning a boolean, bcrypt will return an error if they don't match. http.StatusUnauthorized, yet another constant, is 401. if err != nil { http.Error(w, "Password does not match", http.StatusUnauthorized) } else { //Finally, the user has been authenticated, and we can use our DeleteArticle function from earlier to delete the article with that key err = DeleteArticle(key) if err != nil { //If you look back to our DeleteArticle function, you'll remember that we error with Article not found when an article isn't found. Now, we can check this! if err.Error() == "Article not found" { http.Error(w, "Article not found", http.StatusNotFound) } else { http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } else { fmt.Fprint(w, "Article successfully deleted") } } } } else { http.Redirect(w, r, "/", http.StatusFound) } } ``` If you try running that code in it's current state, you will notice that it errors. In fact, it will say that `pwHash` is not defined! So, let's fix that. * First, go to a website that generates bcrypt hashes, and put in the password you want to do. [Here's a site that does it!](https://www.browserling.com/tools/bcrypt) * Then, make a `.env` file in your repl. * Inside of that file, put `PASSWORD=<your bcrypt hash>`, where `<your bcrypt hash>` is replaced with the hash that the website generated * Last two steps! Put `var pwHash = os.Getenv("PASSWORD")` at the top of your file, and... * Put the following code at top of your *main* function ```go if pwHash == "" { log.Fatal("There is no password set! Please create a file called .env and make the contents \"PASSWORD=asdf\", with your password bcrypt hashed instead of asdf ") } ``` Now, if you've done all of these steps correctly, you should have a working dashboard, where you can delete articles! But, there is *one more thing*. We need to be able to add *new* pages as well! Let's first make a page, called `new.html`, where the user can put in a new article: ```html <!DOCTYPE html> <html> <head> <title>My Blog - New!</title> </head> <body> <h1>New Article</h1> <!-- This <form> tag makes it so that when someone clicks the submit button, it makes a POST request to the /new page with the information in the form --> <form method="POST" action="/new"> <label for="title">Title</label> <!-- Here, we use the required attribute to make sure that the user inputs it. However, this is not enough! We also have some checks on the server side which make sure the title (and password) are sent --> <input name="title" type="text" required><br> <label for="content">Content</label><br> <textarea name="content" rows="20" cols="100" required></textarea><br> <label for="password">Password</label> <input name="password" type="password" required><br> <!-- When the user clicks on the following button, the browser will make a POST request to the server to make the new article! --> <button type="submit">Submit</title> </form> </body> </html> ``` Finally, we have to handle the article. However, you might notice that I made the form make a POST request to `/new`. Isn't the page called `/new.html`? When we handle this, we're going to check the request type. If it's a post request, we process it. If it's any other type of request, including a GET request, we will send the page. Here's how we handle it, the final part to our program: ```go //You may notice that we are indented, and it starts with a case statement. This is because this part goes into our main "httpFunc" from earlier. case "/new", "/new.html": //Here's where we make the aforementioned check if r.Method == "POST" { //We have to make sure that the form actually sent over all of the information. r.FormValue("thing that wasn't sent") returns an empty string, so we can check on that if r.FormValue("content") == "" || r.FormValue("title") == "" || r.FormValue("password") == "" { http.Error(w, "Either the content, title, or password are missing", http.StatusBadRequest) } else { //Assuming it passes, we move on to the next step, checking the password, just like last time in our deleteFunc err := bcrypt.CompareHashAndPassword([]byte(pwHash), []byte(r.FormValue("password"))) if err != nil { http.Error(w, "Password does not match", http.StatusUnauthorized) } else { //Finally, we create our article. CreateArticle(r.FormValue("title"), r.FormValue("content")) //And send the user to the front page http.Redirect(w, r, "/", http.StatusFound) } } } else { //If you hop back up to where that { was opened, you'll see this was right after "if r.Method == "POST" {", so this is the part where we serve the page, as the request was *not* a POST but rather a GET (or something else, we don't care) executeTemplate(w, "new.html", []string{})//this last one doesn't matter, we aren't using anything in the template } return ``` And that's it! We are done with our Blog! ## Next Steps I left a challenge in the tutorial! Take a look at `DeleteArticle` and `GetArticle` in our `articles.go` file, and see how they differ from the other functions in that file. They both return an `error` as their last (or only) return value! Your goal is to reformat all of the other functions to return an `error` as well, instead of using `log.Fatal()`, which kills the blog. Finally, every time these functions are used, make it so that if there was an error, it returns an error to the client with an HTTP code 500 (Internal Server Error), like in our `executeTemplate` function (main.go) or our `deleteFunc` function (main.go).
1
posted by vityavv (27) 3 months ago
โ–ฒ
19
Learn the Basics of Ruby! Interactive Program for Anyone New to Ruby :)
## If you're interested in learning the Ruby programming language, this program might help you out with the basics: **printing/putting, delaying code (sleep), variables, some math/methods (to int and to string), arrays, calling random integers, getting user input, loops, and if/elsif/else statements.** ## Other features: * *Interactivity!* The program asks the user for code after teaching topics to check if the user understands. * *Tips & help!* During any question, the user can input "help" or "h" instead of code in order to receive tips with the specific coding problem. * *The program itself...is coded in Ruby!* So if the user wants to see some Ruby in action, they can check out the IDE. Plus, I recently added comments within the IDE that are meant for the user to read. I highly suggest checking this out. * *Not too much at once!* Seeing a ton of code all at once (or even normal text) can be overwhelming, but this program goes at the user's own pace! Simply press the enter key when ready to move on. ## So if you're looking for a new language--this is a sign to try out Ruby (my own preferred language)! Scroll down to the Repl and click run to start learning! :D ![Ruby (1)](https://storage.googleapis.com/replit/images/1541072105686_2ccb62fa15e59d409b7a15e1690a5f95.pn) **Let me know if there's a topic in Ruby that you consider a "basic" that I should cover but didn't in this tutorial! And *please* leave an upvote if you used it at all or just like it!** If you're using Repl.it with light mode (default), the comments in the IDE will be a pale grey. If you're using Repl.it with dark mode, the comments in the IDE will be green. I explain a lot of things in the comments and it's also a great way to see Ruby color-coded and such. #### ๐Ÿ‘ฉโ€๐Ÿ’ป Acknowledgements * Thanks to everyone who helped to test this (both you guys and a few of the members of the Programming Club at my high school). * Also, Kode W/ Klossy for teaching me the basics of Ruby myself! KWK is honestly the reason I like Ruby so much. * Finally--the Repl.it Discord server! I know, I know--Stack Overflow exists--but the Discord server has helped me out of plenty of coding mistakes and other bugs while being incredibly supportive despite my sometimes silly errors. Thank you!
4
posted by 21Miya (24) 3 months ago
โ–ฒ
17
How to create a fairly basic game using Python w/ Turtle Graphics
# How to make a simple game with Python + Turtle ___ Hello! If you're reading this, I can only assume you want to learn about **how to make a fairly basic game using Python with Turtle Graphics**. If that's not the case, why are you here? Go do something else. If you're still here, I'm going to assume you have some basic knowledge of Python (syntax, how to use variables, etc.) and some more in-depth of Turtle Graphics. If you don't, please at least read the first 4 chapters or so of [this](http://interactivepython.org/runestone/static/thinkcspy/index.html) resource. If you need to find more about turtle functions (which you likely will), see [these](https://docs.python.org/3.3/library/turtle.html?highlight=turtle) docs. I know this is long, but if you want to do this right, it's **VERY IMPORTANT** to read through each section!!! If you miss even one thing, your code may not work! ## I - Creating your repl & basic setup ___ OK, so the first thing you need to do is create a Python with Turtle Graphics Repl. If you already have an account, you can do this by clicking the big red plus button, click Search All Languages, searching for Python, and selecting Python (with Turtle). Alternatively, (and anyone can do it this way), you can just click on [this link](https://repl.it/languages/python_turtle)! Now, the next thing you should do is import the turtle graphics module. To import the turtle graphics module, include the following lines at the top of your code: ```python #Import statements import turtle ``` It's as simple as that! The first line a comment - it is very useful in keeping your code organized and readable. The second line is called an import statement, and it does just what it sounds like it does. ## II - Creating the window ___ Now that we have imported the turtle module, we can get started. To make a Python w/ Turtle repl work, we need to have something for the turtle to draw **on**. Now, technically this isn't necessary at this stage, it will be if you want to set a background color, and it will be for later things. To create a window, add a blank line, then the following code: ``` #Creating the window window = turtle.Screen() ``` If you would like to have a background color for the window, also include this line after the following lines: `window.bgcolor("#UVWXYZ")`, where UVWXYZ is the hexadecimal color code of the color you want for the background color. (To get a hex color code, just Google 'color code picker'.) The hex color code has to be surrounded in quotes because it is a string and must be read as such. So, just to recap, your code should now look like this (I have included my own hex color code, feel free to use it or choose your own): ```python #Import statements import turtle #Creating the window window = turtle.Screen() window.bgcolor("#444444") ``` Now comes the fun part - you can press the green 'Run' button and see something visibly happen! Up in the top left corner of the right side of your screen (under the section labeled 'result'), you should see the word "center". If you choose a background color, you should also see that area turn that color. If this worked, great! Continue to step 3. If not - double-check you have entered the code correctly. ## III - Creating the game box ___ ### Part 1 - Creating the turtle Now, we're going to draw the area that the game will be played in. This will also define the grid we're going to use for the game. For this example, I'm going to be using a box size of 500x500 (-250 to 250 on each axis), with a grid size of 50. To do this, we need to do a few things. First, in a new section (separated from the previous sections by an empty line), we need to create a turtle to use to create the box. So, we're going to create a comment detailing what this section of code does, then we're going to create our first turtle. Let's add the following lines of code: ```python #Initializing turtles outer = turtle.Turtle() ``` The second line, `outer = turtle.Turtle()`, creates a new turtle object and calls it `outer`. We will be doing a lot more of this later, so remember this. ___ ### Part 2 - Setting up the turtle Now, we're going to create our play area. First of all, let's make a new section. The first line we're going to do is set `outer`'s speed. To set the speed of a turtle, you use the function `turtle.speed(x)`, where turtle is the name of the turtle object you're using and x is a number from 0 to 10. Speed increases from 1 to 10, and 0 is the maximum speed - going as fast as python can handle. This is what we want to use, because it will keep the user from having to wait as long to start. So, to set the speed of `outer` to the fastest possible, we're going to use `outer.speed(0)`. Next, let's set a border color and fill color for the area. This uses the `turtle.color()` function, which takes up to 2 arguments (the things in the parenthesis). The first is the pen color, or the color that it will draw with. The second is the fill color, which is exactly what it sounds like - the color that is filled in when using fill. For this example, let's use #000000 (black) for the pen color and #FFFFFF (white) for the fill color. So, underneath the outer.speed, we're going to have the following line: `outer.color("#000000","#FFFFFF")`. ___ ### Part 3 - Drawing the area **DISCLAIMER:** From this point on, I won't be explaining in as much detail how each command works, though I will continue giving examples. Please see the docs if you want more info. The first thing to do when drawing the area of the game is to decide how big you want the area to be. In this example, I will be using **500x500**, but you can easily change that. Since your turtle's pen defaults to down, if you want to move it without drawing, you have to send `outer.up()`. Next, you need to go to the coordinates of whatever point you want to be one of the corners of your area - the easiest way to get this is to have your turtle go to (x,y), where x is half of the width of your area and y is half of the height. Since our area is 500x500, use `outer.goto(250,250)`. Next, do start being able to draw, we need to use `outer.down()`. To begin filling the play area, we need to add `outer.begin_fill()`, which will continue tracking the inner area of what you have drawn until you tell it to end the fill. Now, we're finally ready to start drawing. While you can do this with individual turns and forwards, it's much better to use a loop. To make a loop that loops 4 times that will draw us a square of side length 500, we need to use this code: ```python for i in range(4): outer.rt(90) outer.fd(500) ``` This will tell the turtle to make a 90-degree right turn, then go forward 500 pixels 4 times, creating a square. Finally, we need to end the fill by sending `outer.end_fill()`. If you press run now, you should get a full square drawn and filled in. If it doesn't work, check your code against this: ```python #Making the outer border of the game outer.speed(0) outer.color("#000000","#FFFFFF") outer.up() outer.goto(250,250) outer.down() outer.begin_fill() for z in range(4): outer.rt(90) outer.fd(500) outer.end_fill() ``` ## IV - Hiding turtles ___ When you press run, you'll see it the play area, but you'll also see the turtles - the little arrows pointing towards the right. Well, if we're making a game, we can't have that! Let's hide those turtles so that they aren't visible to the user. To do this, we need to make a new section - preferably as close to the top as possible, while still below the section where you initialized the turtles. To hide a turtle, you use `turtle.ht()`, where turtle is the name of the turtle. So, let's quickly make a section and hide the `outer` turtle. Now, if you run it, you won't see the little arrow after the drawing is finished! ## IV.V - Drawing a grid (Optional!) ___ Now, this whole section is **OPTIONAL**! You do **NOT** need to include this section, but I find it helpful especially when creating the game. Now that that's out of the way... ___ ### Part 1 - Creating an array To create a grid for the game, we need to do a few things. First, we need to keep track of each gridline's position. We can do that fairly easily with an **array**. This array should go in its own section, since we're going to be using more arrays later. The increments and decrements from 0 for each value in the array depend on how big you want each grid box to be - for this example, I'm going to use **50x50**. So, we're going to create an array - let's call it `gridpos` - by doing the following: `gridpos = []`. We now have an empty array. Now, to get the values for each grid position, we're going to go from the x position of the bottom left corner of your box - if you're using my numbers, it will be -250 - and add the side length of a grid square - in my case, 50 - to it. This is our first value. Continue until you reach the positive version of the first number. Mine looks like this: `gridpos = [-200, -150, -100, -50, 0, 50, 100, 150, 200]`. Now, we can start to draw it. ___ ### Part 2 - Drawing the grid We're going to need another turtle for the grid, so let's call it... wait for it... `grid`. We're going to want to hide this turtle, so in the hiding turtles section, let's add a line to hide it. Then, set its speed to 0 and its color to whatever you like - for this example, I used #888888. Then, we're going to need to make a loop, and have it loop for 1 fewer times than the number of grid squares you want for the height or the length. So, if you want a 10x10 grid, then it needs to loop 9 times. Inside that loop, we want it to do the following: Go to the leftmost edge of the box and draw a horizontal line for each value in `gridpos`, and go to the bottommost edge of the box and draw a vertical line for each value in `gridpos`. The easiest way to do this is with a large loop. You'll want to loop as many times as items you have in `gridpos` - in my case, 9 times. Let's break this down into 2 parts - **Horizontal Lines** and **Vertical Lines**. ___ #### Part 2A - Horizontal lines Since your turtle will be starting at (0,0), and we want it to draw a horizontal line at each y position in `gridpos`, we need to get it to go to each without drawing off the lines. We can do this by telling it to pick up the pen, then goto the far-left x value and the y value corresponding to the iterator's value in `gridpos`. This is a little hard to explain, so let me show it: ```python for p in range(9): grid.up() grid.goto(-250,gridpos[p]) ``` What this will do is go to (-250, the value of the p'th item in `gridpos`), whatever that may be. From there, it's as simple as putting the pen down and going forward the side length of the area, then turning left 90 degrees. So, your current code for the grid should look like this (Initializing of the grid turtle and gridpos array not shown): ```python #Making the grid grid.speed(0) grid.color("#888888") for p in range(9): grid.up() grid.goto(-250,gridpos[p]) grid.down() grid.fd(500) grid.lt(90) ``` ___ #### Part 2B - Vertical lines Now, it's time to make the vertical lines. Luckily, this part is much easier since we have the first part. All you need to do is copy/paste the code from `grid.up()` to `grid.fd(500)` to immediately below itself, switch -250 and gridpos[p] in the `grid.goto()` statement, and switch the left turn to a right turn. Now, you should have this inside the loop: ```python grid.up() grid.goto(-250,gridpos[p]) grid.down() grid.fd(500) grid.lt(90) grid.up() grid.goto(gridpos[p],-250) grid.down() grid.fd(500) grid.rt(90) ``` If you run it now, you should see it create a complete grid in your play area. ## V - Initializing the boxes ___ Now, the whole point of this game is to collect boxes - you can decide how many. In the example I used 7, but I only am going to detail 3 because it's a very simple, repetitive process to make more. First, we're going to need to create a turtle for each box. Let's go back up to the turtle initialization section, and create 3 turtles - `box1`, `box2`, and `box3` using `x = turtle.Turtle()` and hide them. Now, we probably want some margin between each box and the gridlines - for this example, I'm using a margin of 5 on each side. To make this easier, let's make 2 arrays - an `xlist` and a `ylist`. Each **x value** is going to be 5 **above** the 50 - so -45, 5, 55, 105, etc. Each **y value** is going to be 5 **below** the 50 - so -55, -5, 45, 95, etc. These arrays should go in the same section as the `gridpos` array, for organization's sake. We also need to declare a whole ton of variables - 4 for each box - which will be used later. For each box, we need `boxZx`, `boxZy`, `boxZgridx`, and `boxZgridy`, where Z is the number of each box. We can do that just below the array initialization section. So, to make it simple, for `box1` we can just use `box1x = box1y = box1gridx = box1gridy = 0`, so that each of those variables has been initialized but is equal to 0. This saves line space and some time. ## VI - Placing the boxes ___ This is the part where things start getting repetitive. Each section to place a box is almost exactly the same as the last. First, make a new section for `box1`. First, we want to set its speed to 0 so that it draws as fast as possible. Then, we're going to set its color - in this example, I'm using #8B4513, because it's a nice brownish color. Then, we're going to go up to pick up the pen and go to a set coordinate. For now, let's just go to the top right grid square. To do this, we need to tell `box1` to goto `xlist[9],ylist[9]`. Next, we're going to put down the pen and begin the fill. After that, we need to draw a square of side length 40, because our grid squares are side 50 and we have a margin of 5 on each side. This can be done with the same loop we used to draw the area, but instead of going forward 500 we go forward 40. Finally, we need to end the fill. If you run it now, you should see a brown box appear in the top right corner. That means it's worked! If it hasn't, see this code: ```python #Making the first box box1.speed(0) box1.color("#8B4513") box1.up() box1.goto(xlist[9],ylist[9]) box1.down() box1.begin_fill() for z in range(4): box1.fd(40) box1.rt(90) box1.end_fill() ``` Now, you just need to do the same thing for boxes 2 and 3, but with a different value in the xlist[] and ylist[]. See what experimenting with it does to the position! ## VII - Randomly placing the boxes ___ #### Part 1 - Basic randomization Now, I know what you're thinking. *"We already placed the boxes!"*, you'll say. *"Why do we need to do another section on placing boxes?!"* Well, you don't want to have it be the same position every single game, do you? That takes away some of the fun! Let's make a randomization aspect. First, up in the import section, we need to `import random`. This will let us use a very important function - `random.randint()`. Now, we need to create a new section above the drawing of box1 - let's do it directly below the grid section. This is where we're going to start using those variables we created. First, we need to assign `box1gridx`. So, we're going to use `box1gridx = random.randint(0,9)`. This will tell `box1gridx` to become a random integer between 0 and 9, so that it encompasses the whole of the array. Then, do the same for `box1gridy`. Now, we need to assign `box1x` to a number in `xlist[]` based on `box1gridx`, and the same for `box1y`. This is simple, based on what we know about arrays. Just use `box1x = xlist[box1gridx]`, and do the same for `box1y`. Now, just for debug purposes, let's print the x and y positions to the console using `print(box1x,box1y)`. Finally, we need to change the goto positions when drawing box1 from `xlist[9],ylist[9]` to `box1x,box1y`. If you did the randomization right, it should look like this: ```python #Getting coordinates for box1 box1gridx = random.randint(0,9) box1gridy = random.randint(0,9) box1x = xlist[box1gridx] box1y = ylist[box1gridy] print(box1x,box1y) ``` If you run it now, it should go to a completely different spot each time. ___ #### Part 2 - Specialized randomization Something you may see come up - very rarely, but it could happen - is multiple boxes in the same spot. Now, while this is technically OK, we don't want it because it limits the game's potential. So, for `box2`, we're going to use the same code as for `box1` (slightly modified to apply to `box2`), but we're going to add something to it. We need to check to see if `box2x` is the same as `box1x` and if `box2y` is the same as `box1y`. If they are the same, then we need to give `box2` a new position. We can do this by putting the whole of the randomization code for `box2` in a `while True:` loop (There are better ways to do this, but this is a simple way - you **do NOT** want to run this until you have put a `break` into it), then putting in an if statement. Since we need to check if `box2x` is `box1x` and `box2y` is `box1y`, `if (box2x == box1x and box2y == box1y):` should do the trick. Note that we need to check for both, because just one being the same is OK. Then, under that if it doesn't really matter what we put, so I just put a print statement saying that the placement of this box failed. What is very important is the else. We need to make sure we have an else after the if that contains the line `break`. Otherwise, we will be stuck in this loop forever. So, before running, double check that you have this code for `box2`'s randomization: ```python #Getting coordinates for box2 while True: box2gridx = random.randint(0,9) box2gridy = random.randint(0,9) box2x = xlist[box2gridx] box2y = ylist[box2gridy] print(box2x,box2y) if (box2x == box1x and box2y == box1y): print("Box 2 placement failed, retrying...") else: break ``` Finally, let's fix `box2`'s goto so that it goes to `box2x`, `box2y`. Now, to do this for more boxes it's very similar, but you need to check the position against every single previous box's position. So, for box3, you would need the conditional `(box3x == box1x and box3y == box1y) or (box3x == box2x and box3y == box2y)`. Fill in that randomization and its corresponding goto, and then press run. You should see all 3 boxes go to completely random spots each time! # VIII - Drawing the player ___ Now, it's time to draw our player. This will eventually be moving around on the grid and collecting the boxes, but for now let's just try and draw it. To make it simple and easily distinguishable from the boxes, we're going to choose a different color and shape. In this tutorial, I'm using a circle of diameter 40 and color #ABCDEF (a nice light blue.) To draw the circle, you *could* use `turtle.circle()`, but I'm going to use the more obscure `turtle.dot()`, which draws a dot based on the turtle's position as the center of the circle. Because of this, we need a new array called `playerposlist`. The values in this will be 25 away from each value in `gridpos`, because our grid boxes are 50x50. So, we'll start at -225, then -175, -125, etc. Next, we need to actually create our `player` turtle and hide it, both in their respective sections. Now, we need to create 2 variables to track `player`'s x position and y position in relation to the grid - `playerx` and `playery`. Let's set both of them to 4 for now. Since we're going to need to draw the character a LOT, let's put the drawing code into a function - `drawplayer()`. Since this will be called multiple times, we want our first line to clear any previous drawings using `player.clear()`. Then, set the speed to 0 and have the turtle pick up the pen. Now, we're going to `player.goto(playerposlist[playerx],playerposlist[playery])`. Finally, we need a dot of radius 20 and color #ABCDEF. We can do this with `player.dot(20,"#ABCDEF")`. It should now look like this: ```python player.clear() player.speed(0) player.up() player.goto(playerposlist[playerx],playerposlist[playery]) player.dot(20,"#ABCDEF") ``` Now, just put a call to drawplayer() below the drawplayer() function. If you press play, the boxes should draw and the player should go to the spot that is 4 up and 4 left from the bottom left corner. # IX - Boxcheck ___ Now, if our player has spawned on or moves onto a box, we want it to be collected and disappear. To do that, we're going to need to create a variable tracking the number of `boxes`. Since we have 3 boxes in this example, `boxes` should equal 3. Now, it's time to create a function for checking the boxes called `boxcheck`. The very first line needs to declare that we are using the global versions of `boxes` and of each box's grid coordinate variables (`boxZgridx`, `boxZgridy`). Then, we just need to repeat 5 lines of code for each box (replacing Z with the proper box number): ```python if playerx == boxZgridx and playery == boxZgridy: boxZ.clear() boxes -= 1 boxZgridx = boxZgridy = -1 ``` What this will do is check the player's position against the box's position, and if it's the same it will remove the box and decrement the number of boxes remaining, and then 'put' the box somewhere it can't be counted again. We need to put a call for `boxcheck()` in the beginning of the `drawplayer()` function. After you have this code block for each box, we just need to check `if boxes == 0`, and if it does, then we need to call a new function: # X - `win()` ___ Now, to make our win function, we need one more global variable - `haswon`. Declare it to be 0. Then, create a new turtle called `winner` and hide it. When the player wins, we're going to tell them they won and move their player out of bounds so they stop moving. So, now create the `win()` function. Inside this function, we need to access the global variables `haswon`, `playerx`, and `playery`. Then, we should print something to the console telling the player they have won - something like `"You won!!"`. Then, we need an if to check if the win() function has already run. We do this by checking `if haswon == 0`. Inside this if, we need to first set `haswon` to 1. This prevents it from running this section of the code again if `win()` is called again. Next, we need to set `winner`'s speed to 0 and have it pick up the pen. Then, we're going to draw our 'YOU WIN!' message. To do that, we're going to need an array called `winlist[]`. In it, we need to have one string for each character (we can ignore the leading or ending spaces). Since we want to say 'YOU WIN!', we need 8 strings, each with a character from that in it (so "Y", "O", "U", etc.). Then, we will need a loop that loops 8 times, and each iteration will draw one character. For my loop, I used o as the iterator. Now, let's create the code to draw each letter. We're going to have `winner` goto `xlist[o+1]` (because it's 8 characters on a 10 square grid) for the x position, and `xlist[5]` for the y position. Then, we need to draw each character using `winner.write()`. This function recieves 4 arguments - what to write (`winlist[o]`), whether or not to move (leave this on `False`), the alignment of the text (`align="left"`), and the font information (`font=("Arial",40,"Normal")`). So, the final write command should look like this: ``` winner.write(winlist[o], False, align="left", font=("Arial",40," ``` Finally, out of the loop, we need to set `playerx` and `playery` to 10 and redraw the player. Now, if you run this, if you won, you'd see that Y and W are not centered. The simple fix to this is to put the `winner.goto()` statement inside an else, and have 2 if statements before: one `if o == 0:`, and one `if o == 4:`. Inside the first, we're going to use `winner.goto(xlist[o+1]+2,xlist[5])`, and inside the second, we're going to use `winner.goto(xlist[o+1]-5,xlist[5])`. Now, if you run it and won, you'd see that the Y and W ARE centered. However, we should probably make it so that you can actually... play the game. # XI - User input ___ To get user input, we're going to need to create 4 basic functions. Each one will be very similar, except for a few number changes. The first is `up()`. This will need to declare the `global playery`. Then, you just have a simple if: `if playery < 9:`, which will check to make sure the player isn't at the top of the map. Inside the if, we just need to add one to `playery` and call `drawplayer()` again. The same will be done for all three other methods, with slight changes. `down()` will still `global playery`, but will instead check `if playery > 0 and playery < 10:`. This is because the player gets put at playerx and playery position (10,10) at the end of the game, and we want them to no longer be able to move. Inside the if, we're going to decrement instead of increment the `playery` value, and we're still going to `drawplayer()`. For `left()`, we need to `global playerx` instead of `playery`, and the if will reflect this change. In fact, `left()` is the same as `down()` except you have to replace each occurance of `playery` with `playerx` and the name is changed. The same can be done for `right()`, where you can just copy `up()` and replace every occurance with `playery` with `playerx` and change the name. After you have done this, you still need a way to intercept the user input. We need to add keylisteners for 8 keys: Up, Down, Left, Right, W, A, S, and D. This will be done using the `window.onkey()` function, which takes 2 parameters: the function to call, and the key to listen for. The latter has to be passed to the function as a string. So, we're going to have 8 different onkey statements. To save time explaining, I will just paste them here: ``` window.onkey(up, "Up") window.onkey(up, "W") window.onkey(down, "Down") window.onkey(down, "S") window.onkey(left, "Left") window.onkey(left, "A") window.onkey(right, "Right") window.onkey(right, "D") ``` This will call the appropriate function for each keypress. Finally, to make this work, we just need to add 2 lines to the bottom of the code: `window.listen()` and `windown.mainloop()`. # XII - Conclusion And with that, you should be done! If you press play, you should now have a functioning game. Now, if you want to, you can apply the principle learned in **X - `win()`** to add in an introduction to the game, like in my example, but that is not necesary. If you enjoyed this tutorial, be sure to upvote it. If you want to make a fairly simple random spiral generator with python, go [here](https://repl.it/talk/challenge/Python-Turtle-Graphics-Random-Spirals/7651). If you want to check out my crash course on LOLCODE, go and click on this cat head [(or here)](https://repl.it/talk/challenge/A-Crash-Course-in-LOLCODE/7714) [๐Ÿฑ](https://repl.it/talk/challenge/A-Crash-Course-in-LOLCODE/7714)
0
posted by minermaniac447 (130) 3 months ago
Load more