logo
468x60-2-495


  • Home
  • Privacy Policy
  • About
search
May 21, 2012
The Math and ActionScript of...
We see lines used in a lot of scenarios; curves are also used but perhaps not as frequently – but that doesn’t undermine their importance! In this tutorial we shall take a closer look at...
read more
May 20, 2012
Weekend Lecture: Understandi...
Interested in game design? This weekend, we feature a set of four interactive lectures: games that are about game design, by Pixelate. Play the Games Bub and Bob, two little 8-bit guys, will talk...
read more
top
Mar 25, 2012 Posted on Mar 25, 2012 in Hints and Tips | 10 comments

How to Build a Python Bot That Can Play Web Games

In this tutorial we’ll explore the ins and outs of building a Computer Vision-based game bot in Python, which will be able to to play the popular Flash game Sushi Go Round. You can use the techniques taught in this tutorial to create bots for automatically testing your own web games.


Final Result Preview

Let’s take a look at the final result we will be working towards:


Prerequisites

This tutorial, and all the code within it, requires that a few additional Python libraries be installed. They provide a nice Python wrapping to a bunch of low-level C code which greatly eases the process and speed of bot scripting.

Some of the code and libraries are Windows-specific. There may be Mac or Linux equivalents, but we won’t be covering them in this tutorial.

You’ll need to download and install the following libraries:

  • The Python Imaging Library
  • Numpy
  • PyWin
  • All of the above have self installers; Running them will automatically install the modules into your \lib\site-packages directory and, in theory, adjust your pythonPath accordingly. However in practice this doesn’t always happen. Should you begin receiving any Import Error messages after installation, you’ll probably need to manually adjust your Environment Variables. More information on adjusting Path Variables may be found here.

The final tool we’ll need is a decent paint program. I suggest Paint.NET as an excellent free option, but any program with rulers that display their measurements in pixels can be used.

We’ll use a few games as examples along the way


Introduction

This tutorial is written to gave a basic introduction to the process of building bots that play browser-based games. The approach we’re going to take is likely slightly different than what most would expect when they think of a bot. Rather than making a program that sits between the client and server injecting code (like a Quake or C/S bot), our bot will sit purely on the ‘outside’. We’ll rely on Computer Vision-esque techniques and Windows API calls to gather needed information and generate movement.

With this approach we lose a bit of refined detail and control, but make up for it in shortened dev time and ease of use. Automating a specific game function can be done in a few short lines of code, and a full-fledged, start-to-finish bot (for a simple game) can be cranked out in a few hours.

The joys of this fast approach are such that once you get familiar with what the computer can easily ‘see’, you’ll begin to view games slightly differently. A good example is found in puzzle games. A common construct involves exploiting human speed limitations to force you into a less than optimal solution. It’s fun (and pretty easy) to ‘break’ these games by scripting in movements that could never be accomplished by a human.

These bots are also very useful for testing simple games – unlike a human, a bot won’t get bored playing the same scenario over and over again.

Source code for all of the tutorial examples, as well as for one of the completed example bots, may be found here.

Have fun!


Step 1: Create a New Python Project

In a new folder, right-click and select New > Text Document.

Python_Snapshot_of_entire_screen_area

Once made, rename the file from ‘New Text Document’ to ‘quickGrab.py’ (without the quotes) and confirm that you want to change the file name extension.

Python_Snapshot_of_entire_screen_area

Finally, right-click on our newly created file and select “Edit with IDLE” from the context menu to launch the editor

Python_Snapshot_of_entire_screen_area

Step 2: Setting Up Your First Screen Grab

We’ll begin work on our bot by exploring the basic screen grab function. Once up and running, we’ll step through it line by line, as this function (and its many iterations) will serve as the backbone of our code.

In quickgrab.py enter the following code:

import ImageGrab
import os
import time

def screenGrab():
    box = ()
    im = ImageGrab.grab()
    im.save(os.getcwd() + '\\full_snap__' + str(int(time.time())) +
'.png', 'PNG')

def main():
    screenGrab()

if __name__ == '__main__':
    main()

Running this program should give you a full snapshot of the screen area:

Python_Snapshot_of_entire_screen_area

The current code grabs the full width and height of your screen area and stores it as a PNG in your current working directory.

Now let’s step through the code to see exactly how it works.

The first three lines:

import ImageGrab
import os
import time

…are the aptly named ‘import statements’. These tell Python to load in the listed modules at runtime. This gives us access to their methods via the Modeule.attribute syntax.

The first module is part of the Python Image Library we installed earlier. As its name suggests, it gives us the basic screen gabbing functionality our bot will rely on.

The second line imports the OS (Operating System) Module. This gives us the ability to easily navigate around our operating system’s directories. It’ll come in handy once we begin organizing assets into different folders.

This final import is the built-in Time module. Well use this mostly for stamping the current time onto snapshots, but it can be very useful as a timer for bots that need events triggered over a given number of seconds.

The next four lines make up the heart of our screenGrab() function.

def screenGrab():
    box = ()
    im = ImageGrab.grab()
    im.save(os.getcwd() + '\\full_snap__' + str(int(time.time())) +
'.png', 'PNG')

The first line def screenGrab() defines the name of our function. The empty parentheses mean it expects no arguments.

Line 2, box=() assigns an empty tuple to a variable named “box”. We’ll fill this with arguments in the next step.

Line 3, im = ImageGrab.grab() creates a full snapshot of your screen and returns an RGB image to the instance im

Line 4 can be a little tricky if you’re unfamiliar with how the Time module works. The first part im.save( calls the “save” method from the Image class. It expects two arguments. The first is the location in which to save the file, and the second is the file format.

Here we set the location by first calling os.getcwd(). This gets the current directory the code is being run from and returns it as a string. We next add a +. This will be used in between each new argument to concatenate all of the strings together.

The next piece '\\full_snap__ give our file a simple descriptive name. (Because the backslash is an escape character in Python, we have to add two of them to avoid cancelling out one of our letters).

Next is the hairy bit: str(int(time.time())) . This takes advantage of Python’s built-in Type methods. We’ll explain this piece by working from the inside out:

time.time() returns the number of seconds since Epoch, which is given as a type Float. Since we’re creating a file name we can’t have the decimal in there, so we first convert it to an integer by wrapping it in int(). This gets us close, but Python can’t concatenate type Int with type String, so the last step is to wrap everything in the str() function to give us a nice usable timestamp for the file name. From here, all that remains is adding the extension as part of the string: + '.png' and passing the second argument which is again the extension’s type: "PNG".

The last part of our code defines the function main(), and tells it to call the screenGrab() function whenever it’s run.

And here, at the end, is a Python convention that checks whether the script is top level, and if so allows it to run. Translated, it simply means that that it only executes main() if it is run by itself. Otherwise – if, for instance, it is loaded as a module by a different Python script – it only supplies its methods instead of executing its code.

def main():
    screenGrab()

if __name__ == '__main__':
    main()

Step 3: The Bounding Box

The ImageGrab.grab() function accepts one argument which defines a bounding box. This is a tuple of coordinates following the pattern of (x,y,x,y) where,

  1. The first pair of values (x,y.. defines the top left corner of the box
  2. The second pair ..x,y) defines the bottom right.

Combining these allows us to only copy the part of the screen we need.

Let’s put this into practice.

For this example, we’re going to use a game called Sushi Go Round. (Quite addicting. You’ve been warned.) Open the game in a new tab and take a snapshot using our existing screenGrab() code:

Python_Snapshot_of_sushi_game_full_screen

A snapshot of the full screen area.


Step 4: Getting Coordinates

Now it’s time to start mining some coordinates for our bounding box.

Open up your most recent snapshot in an image editor.

Python_Snapshot_of_sushi_game_full_screen

The (0,0) position is always located at the top left corner of the image. We want to pad the x and y coordinates so that our new snapshot function sets (0,0) to the leftmost corner of the game’s play area.

The reasons for this are two-fold. First, it makes finding in-game coordinates much easier when we only need to adjust values in relation to the play area versus the entire area of your screen resolution. Second, grabbing a smaller portion of the screen reduces the processing overhead required. Full screen grabs produce quite a bit of data, which can make it tough to traverse it multiple times per second.

looking_at_xy

If not done already, enable the ruler display in your editor and zoom in on the top corner of the play area until you can see the pixels in detail:

looking_at_x_y

Hover your cursor over the first pixel of the play area and check the coordinates displayed on the ruler. These will be the first two values of our Box tuple. On my specific machine these values are 157, 162.

Navigate to the lower edge of the play area to get the bottom pair of coordinates.

looking_at_x_y

This shows coordinates of 796 and 641. Combining these with our previous pair gives a box with the coordinates of (157,162,796,641).

Let’s add this to our code.

import ImageGrab
import os
import time

def screenGrab():
    box = (157,346,796,825)
    im = ImageGrab.grab(box)
    im.save(os.getcwd() + '\\full_snap__' + str(int(time.time())) +
'.png', 'PNG')

def main():
    screenGrab()

if __name__ == '__main__':
    main()

In line 6 we’ve updated the tuple to hold the coordinates of the play area.

Save and run the code. Open up the newly saved image and you should see:

play_area_snapshot.png

Success! A perfect grab of the play area. We won’t always need to do this kind of intensive hunt for coordinates. Once we get into the win32api we’ll go over some faster methods for setting coordinates when we don’t need pixel perfect accuracy.


Step 5: Planning Ahead for Flexibility

As it stands now, we’ve hard-coded the coordinates in relation to our current setup, assuming our browser, and our resolution. It’s generally a bad idea to hard-code coordinates in this way. If, for instance, we want to run the code on a different computer – or say, a new ad on the website shifts the position of the play area slightly – we would have to manually and painstakingly fix all of our coordinate calls.

So we’re going to create two new variables: x_pad and y_pad. These will be used to store the relationship between the game area and the rest of the screen. This will make it very easy to port the code from place to place since every new coordinate will be relative to the two global variables we’re going to create, and to adjust for changes in screen area, all that’s required is to reset these two variables.

Since we’ve already done the measurements, setting the pads for our current system is very straightforward. We’re going to set the pads to store the location of the first pixel outside of the play area. From the first pair of x,y coordinates in our box tuple, subtract a 1 from each value. So 157 becomes 156, and 346 becomes 345.

Let’s add this to our code.

# Globals
# ------------------

x_pad = 156
y_pad = 345

Now that these are set, we’ll begin to adjust the box tuple to be in relation to these values.

def screenGrab():
    box = (x_pad+1, y_pad+1, 796, 825)
    im = ImageGrab.grab()
    im.save(os.getcwd() + '\\full_snap__' + str(int(time.time())) +
'.png', 'PNG')

For the second pair, we’re going to first subtract the values of the pads (156 and 345) from the coordinates (796, 825), and then use those values in the same Pad + Value format.

def screenGrab():
    box = (x_pad+1, y_pad+1, x_pad+640, y_pad+479)
    im = ImageGrab.grab()
    im.save(os.getcwd() + '\\full_snap__' + str(int(time.time())) +
'.png', 'PNG')

Here the x coordinate becomes 640 (769-156), and the y becomes 480 (825-345)

It may seem a little redundant at first, but doing this extra step ensures easy maintenance in the future.


Step 6: Creating a Docstring

Before we go any further, we’re going to create a docstring at the top of our project. Since most of our code will be based around specific screen coordinates and relationships to coordinates, it’s important to know the circumstances under which everything will line up correctly. For instance, things such as current resolution, browser, toolbars enabled (since they change the browser area), and any adjustments needed to center the play area on screen, all affect the relative position of the coordinates. Having all of this documented greatly helps the troubleshooting process when running your code across multiple browsers and computers.

One last thing to be aware of is the ever-changing ad space on popular gaming sites. If all of your grab calls suddenly stop behaving as expected, a new add slightly shifting things on screen is a good bet.

As an example, I usually have the following comments at the top of my Python code:

"""

All coordinates assume a screen resolution of 1280x1024, and Chrome
maximized with the Bookmarks Toolbar enabled.
Down key has been hit 4 times to center play area in browser.
x_pad = 156
y_pad = 345
Play area =  x_pad+1, y_pad+1, 796, 825
"""

Dropping all of this information at the beginning of your Python file makes it quick and easy to double check all of your settings and screen alignment without having to pore over your code trying to remember where you stored that one specific x-coordinate.


Step 7: Turning quickGrab.py Into a Useful Tool

We’re going to fork our project at this point, creating two files: one to hold all of our bot’s code, and the other to act as a general screen shot utility. We’re going to be taking a lot of screen shots as we hunt for coordinates, so having a separate module ready to go will make things a lot speedier.

Save and close our current project.

In your folder, right-click on quickGrab.py and select ‘copy’ from the menu.

play_area_snapshot.png

Now right-click and select ‘paste’ from the menu

play_area_snapshot.png

Select the copied file and rename it to ‘code.py’

play_area_snapshot.png

From now on all new code additions and changes will be made in code.py. quickGrab.py will now function purely as a snapshot tool. We just need to make one final modification:

Change the file extension from .py, to .pyw and confirm the changes.

play_area_snapshot.png

This extension tells python to run the script without launching the console. So now, quickGrab.pyw lives up to its name. Double click on the file and it will quietly execute its code in the background and save a snapshot to your working directory.

Keep the game open in the background (be sure to mute it before the looped music drives you to madness); we’ll return to it shortly. We have a few more concepts/tools to introduce before we get into controlling things on-screen.


Step 8: Win32api – A Brief Overview

Working with the win32api can be a little daunting initially. It wraps the low-level Windows C code – which is thankfully very well documented here, but a little like a labyrinth to navigate through your first couple of go-arounds.

Before we start scripting any useful actions, we’re going to take a close look at some of the API functions upon which we’ll be relying. Once we have a clear understanding of each parameter it will be easy to adjust them to serve whatever purposes we need in-game.

The win32api.mouse_event():


win32api.mouse_event(
	dwFlags,
	dx,
	dy,
	dwData
	)

The first parameter dwFlags defines the “action” of the mouse. It controls things such as movement, clicking, scrolling, etc..

The following list shows the most common parameters used while scripting movement.

dwFlags:

  • win32con.MOUSEEVENTF_LEFTDOWN
  • win32con.MOUSEEVENTF_LEFTUP
  • win32con.MOUSEEVENTF_MIDDLEDOWN
  • win32con.MOUSEEVENTF_MIDDLEUP
  • win32con.MOUSEEVENTF_RIGHTDOWN
  • win32con.MOUSEEVENTF_RIGHTUP
  • win32con.MOUSEEVENTF_WHEEL

Each name is self explanatory. If you wanted to send a virtual right-click, you would pass win32con.MOUSEEVENTF_RIGHTDOWN to the dwFlags parameter.

The next two parameters, dx and dy, describe the mouse’s absolute position along the x and y axis. While we could use these parameters for scripting mouse movement, they use a coordinate system different than the one we’ve been using. So, we’ll leave them set to zero and rely on a different part of the API for our mouse moving needs.

The fourth parameter is dwData . This function is used if (and only if) dwFlags contains MOUSEEVENTF_WHEEL. Otherwise is can be omitted or set to zero. dwData specifies the amount of movement on your mouse’s scroll wheel.

A quick example to solidify these techniques:

If we imagine a game with a weapon selection system similar to Half-Life 2 – where weapons can be selected by rotating the mouse wheel – we would come up with the following function to sift through the weapons list:

def browseWeapons():
	weaponList = ['crowbar','gravity gun','pistol'...]
	for i in weaponList:
		win32api.mouse_event(win32con.MOUSEEVENTF_MOUSEEVENTF_WHEEL,0,0,120)

Here we want to simulate scrolling the mouse wheel to navigate our theoretical weapon listing, so we passed the ...MOUSEEVENTF_WHEEL ‘action’ to the dwFlag. We don’t need dx or dy, positional data, so we left those set to zero, and we wanted to scroll one click in the forward direction for each ‘weapon’ in the list, so we passed the integer 120 to dwData (each wheel click equals 120).

As you can see, working with mouse_event is simply a matter of plugging the right arguments into the right spot. Let’s now move onto some more usable functions


Step 5: Basic Mouse Clicking

We’re going to make three new functions. One general left-click function, and two that handle the specific down and up states.

Open code.py with IDLE and add the following to our list of import statements:

import win32api, win32con

As before, this gives us access to module’s contents via the module.attribute syntax.

Next we’ll make our first mouse click function.

def leftClick():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
    time.sleep(.1)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
    print "Click." 			#completely optional. But nice for debugging purposes.

Recall that all we’re doing here is assigning an ‘action’ to the first argument of mouse_event. We don’t need to pass any positional information, so we’re leaving the coordinate parameters at (0,0), and we don’t need to send any additional info, so dwData is being omitted. The time.sleep(.1) function tells Python to halt execution for the time specified in parentheses. We’ll add these through out our code, usually for very short amount of times. Without these, the ‘clicking’ can get ahead of itself and fire before menus have a chance to update.

So what we’ve made here is a general left-click. One press, one release. We’ll spend most of our time with this one, but we’re going to make two more variations.

The next two are the exact same thing, but now each step is split into its own function. These will be used when we need to hold down the mouse for a length of time (for dragging, shooting, etc..).

def leftDown():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
    time.sleep(.1)
    print 'left Down'

def leftUp():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
    time.sleep(.1)
    print 'left release'

Step 9: Basic Mouse Movement

With clicking out of the way all that’s left is moving the mouse around on screen.

Add the following functions to code.py:

def mousePos(cord):
	win32api.SetCursorPos((x_pad + cord[0], y_pad + cord[1])

def get_cords():
	x,y = win32api.GetCursorPos()
	x = x - x_pad
	y = y - y_pad
	print x,y

These two functions serve distinctly different purposes. The first will be used for scripting movement in the program. Thanks to excellent naming conventions, the body of the function does exactly as SetCursorPos() implies. Calling this function sets the mouse to the coordinates passed to it as an x,y tuple. Notice that we’ve added in our x and y pads; it’s important to do this anywhere a coordinate is called.

The second is a simple tool that we’ll use while running Python interactively. It prints to the console the current position of the mouse as an x,y tuple. This greatly speeds up the process of navigating through menus without having to take a snapshot and break out a ruler. We won’t always be able to use it, as some mouse activities will need to be pixel-specific, but when we can, it’s a fantastic time saver.

In the next step we’ll put some of these new techniques to use and start navigating in-game menus. But before we do, delete the current contents of main() in code.py and replace it with pass. We’re going to be working with the interactive prompt for the next step, so we won’t be needing the screenGrab() function.


Step 10: Navigating Game Menus

In this, and the next few steps, we’re going to attempt to gather as many event coordinates as we can using our get_cords() method. Using it we’ll be able to quickly build up the code for things like navigating menus, clearing tables, and making food. Once we have these set, it will just be a matter of hooking them into the bot’s logic.

Let’s get started. Save and run your code to bring up the Python shell. Since we replaced the body of main() with pass in the last step, you should see a blank shell upon running.

play_area_snapshot.png

Now, before we even get to the playable part of the game there are four initial menus we need to get through. They are as follows:

  1. Initial “play” button
    play_button.png
  2. iPhone “continue” button
  3. Tutorial “Skip” button
  4. Today’s goal “Continue” button
    .png

We’ll need to get the coordinates for each of these and the add them to a new function called startGame(). Position the IDLE shell so you can see both it and the play area. Type in the get_cords() function but don’t press return yet; move your mouse over the button for which you need coordinates. Be sure not to click yet, as we want focus to remain in the shell. Hover your mouse over the menu item and now press the return key. This will grab the current location of the mouse and print to the console a tuple containing the x,y values. Repeat this for the remaining three menus.

Leave the shell open and arrange it so you can see it as well as the IDLE editor. We’re now going to now add our startGame() function and fill it with our newly acquired coordinates.

def startGame():
	#location of first menu
	mousePos((182, 225))
    leftClick()
    time.sleep(.1)

	#location of second menu
    mousePos((193, 410))
    leftClick()
    time.sleep(.1)

	#location of third menu
    mousePos((435, 470))
    leftClick()
    time.sleep(.1)

	#location of fourth menu
    mousePos((167, 403))
    leftClick()
    time.sleep(.1)

We now have a nice compact function to call at the start of each game. It sets the cursor position to each of the menu locations we previously defined, and then tells the mouse to click. time.sleep(.1) tells Python to halt execution for 1/10 of a second between each click, which gives the menus enough time to update in between.

Save and run your code and you should see a result similar to this:

As a feeble human it takes me slightly longer than a second to navigate all of the menus by hand, but our bot can now do it in about .4 seconds. Not bad at all!


Step 11: Getting Food Coordinates

Now we’re going to repeat the same process for each of these buttons:

play_button.png

Once again, in the Python shell, type in get_cords(), hover you mouse over the food box you need, and press the Enter key to execute the command.

As an option to further speed things along, if you have a second monitor, or are able to arrange the python shell in a way that you can see it as well as the game area, rather than typing in and running get_cords() each time we need it, we can set up a simple for loop. Use a time.sleep() method to halt execution just long enough for you to move the mouse to the next location needing coordinates.

Here’s the for loop in action:

We’re going to create a new class called Cord and use it to store all of the coordinate values we gather. Being able to call Cord.f_rice offers a huge readability advantage over passing the coordinates directly to mousePos(). As an option, you could also store everything in a dictionary, but I find the class syntax more enjoyable.

class Cord:

	f_shrimp = (54,700)
	f_rice = (119 701)
	f_nori = (63 745)
	f_roe = (111 749)
	f_salmon = (54 815)
	f_unagi = (111 812)

We’re going to store a lot of our coordinates in this class, and there will be some overlap, so adding the ‘f_‘ prefix lets us know that we referring to the food locations, rather than, say, a location in the phone menu.

We’ll return to these in a bit. There is a bit more coordinate hunting to do!


Step 12: Getting Empty Plate Coordinates

Each time a customer finishes eating, they leave behind a plate that needs to be clicked on to be removed. So we need to get the location of the empty plates as well.

play_button.png

I’ve noted their position with a giant red ‘X’. Repeat the same pattern as in the last two steps to get their coordinates. Store them in the comment string for now.

"""

Plate cords:

    108, 573
    212, 574
    311, 573
    412, 574
    516, 575
    618, 573
"""

We’re getting close. Only a few more steps of preliminary setup before we get into the really fun stuff.


Step 13: Getting Phone Coordinates

Ok, this will be the final set of coordinates we have to mine in this specific manner.

This one has a lot more to keep track of so you may want to do it by manually calling the get_cords() function rather than the previously used for loop method. Either way, we’re going to go through all of the phone menus to get the coordinates for each item.

This one is a bit more involved as to reach one of the purchase screens we need, you need to have enough money to actually purchase something. So you’ll need to make a few pieces of sushi before you go about the business of coordinate hunting. At the most, you’ll have to make two sushi rolls, I believe. That will get you enough to buy some rice, which will get us to the screen we need.

There are six menus we have to get through:

  1. The Phone
    play_button.png
  2. Initial Menu
  3. Toppings
  4. Rice
    .png
  5. Shipping
    .png

We need to get coordinates for everything but Sake (although you can if you want. I found the bot worked fine without it. I was willing to sacrifice the occasional bad in-game review for not having to code in the logic.)

Getting the coordinates:

We’re going to add all of these to our Cord class. We’ll use the prefix ‘t_‘ to denote that food types are phone>toppings menu items.

class Cord:

	f_shrimp = (54,700)
	f_rice = (119 701)
	f_nori = (63 745)
	f_roe = (111 749)
	f_salmon = (54 815)
	f_unagi = (111 812)

#-----------------------------------	

	phone = (601, 730)

    menu_toppings = (567, 638)

    t_shrimp = (509, 581)
    t_nori = (507, 645)
    t_roe = (592, 644)
    t_salmon = (510, 699)
    t_unagi = (597, 585)
    t_exit = (614, 702)

    menu_rice = (551, 662)
    buy_rice = 564, 647

    delivery_norm = (510, 664)

Alright! We’ve finally mined all the coordinate values we need. So let’s start making something useful!


Step 14: Clearing Tables

We’re going to take our previously recorded coordinates and use them to fill a function called clear_tables().

def clear_tables():
    mousePos((108, 573))
    leftClick()

    mousePos((212, 574))
    leftClick()

    mousePos((311, 573))
    leftClick()

    mousePos((412, 574))
    leftClick()

    mousePos((516, 575))
    leftClick()

    mousePos((618, 573))
    leftClick()
    time.sleep(1)

As you can see, this looks more or less exactly like our earlier startGame() function. A few small differences:

We have no time.sleep() functions in between the different click events. We don’t have to wait for any menus to update, so we don’t have to throttle our click speeds.

We do, however, have one long time.sleep() at the very end. While not strictly required, it is nice to add these occasional pauses in execution to our code, something just long enough to give us time to manually break out of the bot’s main loop if necessary (which we’ll get to). Otherwise, the thing will continue to steal your mouse position over and over, and you won’t be able to shift focus to the shell long enough to stop the script – which can funny the first two or three times as you struggle against a mouse, but it quickly loses its charm.

So be sure to add in some reliable pauses in your own bots!


Step 15: Making Sushi

The first thing we need to do is learn how to make the sushi. Click the recipe book to open the instruction manual. All sushi types encountered throughout the game will be found within its pages. I’ll note the first three below, but I leave it to you to catalog the rest.

play_button.png
'''
Recipes:

	onigiri
		2 rice, 1 nori

	caliroll:
		1 rice, 1 nori, 1 roe

	gunkan:
		1 rice, 1 nori, 2 roe
'''

Now we’re going to set up a function that will accept an argument for “sushi type” and then assemble the proper ingredients based on the passed value.

def makeFood(food):
    if food == 'caliroll':
        print 'Making a caliroll'
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

    elif food == 'onigiri':
        print 'Making a onigiri'
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(.05)

        time.sleep(1.5)

    elif food == 'gunkan':
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

This functions just as all the others but with one small change: rather than passing the coordinates directly, we’re calling them as attributes from our Cord class.

The function foldMat() is called at the end of each sushi making process. This clicks the mat to roll the sushi we just assembled. Let’s define that function now:

def foldMat():
    mousePos((Cord.f_rice[0]+40,Cord.f_rice[1]))
    leftClick()
    time.sleep(.1)

Let’s briefly walk though this mousePos() call as it’s a bit cobbled together. We access the first value of the f_rice tuple by adding [0] on the end of the attribute. Recall that this is our x value. To click on the mat we only need to adjust our x values by a handful of pixels, so we add 40 to the current x coordinate, and the then pass f_rice[1] to the y. This shifts our x position just enough to the right to allow us to trigger the mat.

Notice that after the foldMat() call we have a long time.sleep(). The Mat takes quite a while to roll, and food items can’t be clicked while their animations are running, so you just have to wait.


Step 16: Navigating the Phone Menu

In this step we’ll set all of the mousePos() to point to the appropriate menu items, but we’ll leave it there for now. This is part of the program that will be wrapped in and controlled by the bot’s logic. We’ll revisit this function after getting a few new techniques under our belt.

def buyFood(food):

	mousePos(Cord.phone)

	mousePos(Cord.menu_toppings)

	mousePos(Cord.t_shrimp)
	mousePos(Cord.t_nori)
	mousePos(Cord.t_roe)
	mousePos(Cord.t_salmon)
	mousePos(Cord.t_unagi)
	mousePos(Cord.t_exit)

	mousePos(Cord.menu_rice)
	mousePos(Cord.buy_rice)

	mousePos(Cord.delivery_norm)

That’s it for this step. We’ll do more with this later.


Brief Intro: Making the Computer See

We’re now getting to the very interesting bits. We’re going to start looking at how to make the computer ‘see’ on-screen events. This is a very exciting part of the process, and one that’s easy to get wrapped up thinking about.

Another neat part of bot building is that eventually the bot can provide us, the programmers, with enough information that further vision work is not required. For instance, in the case of the Sushi bot, once we get the first level running, the bot is spitting out accurate enough data about what’s happening on screen that all we have to do from that point on is take that data it’s “seeing” and simply tell it how to react to it.

Another large part of bot building is learning the game, knowing what values you need to keep track of versus which you can ignore. For instance, we’ll make no effort to track cash on hand. It’s just something that ended up being irrelevant to the bot. All it needs to know is if it has enough food to continue working. So rather than keeping tabs on the total money, it simply checks to see if it can afford something, regardless of price, because as it works out in the game, it’s only a matter of a few seconds before you can afford to replenish something. So if it can’t afford it now, it just tries again in a few seconds.

Which brings me to my final point. That of the brute force method versus the elegant one. Vision algorithms take valuable processing time. Checking multiple points in many different regions of the play area can quickly eat away your bot performance, so it comes down to a question of “does the bot need to know whether _______ has happened or not?”.

As an example, a customer in the Sushi game could be thought of as having four states: not present, waiting, eating, and finished eating. When finished, they leave a flashing empty plate behind. I could expend the processing power on checking all plate locations by snapping all six plate locations and then checking against an expected value (which is prone to failure since the plates flash on and off, making a false negative a big possibility), or… I could just brute force my way through by clicking each plate location every few seconds. In practice this is every bit as effective as the ‘elegant’ solution of letting the bot determine the state of the customer. Clicking six locations takes a fraction of a second where as grabbing and processing six different images is comparatively slow. We can use the time we saved on other more important image processing tasks.


Step 17: Importing Numpy and ImageOps

Add the following to your list of import statements.

import ImageOps
from numpy import *

ImageOps is another PIL module. It is used to perform operations (such as grayscaling) on an Image.

I’ll briefly explain the second for those who aren’t familiar with Python. Our standard import statements loads the module’s namespace (a collection of variable names and functions). So, to access items in a module’s scope, we have to employ the module.attribute sytax. However, by using a from ___ import statement we inherit the names into our local scope. Meaning, the module.attribute syntax is no longer needed. They are not top level, so we use them as we would any other Python built-in function, like str() or list(). By importing Numpy in this manner, it allows us to simply call array(), instead of numpy.array().

The wildcard * means import everything from the module.


Step 18: Making the Computer See

The first method we’ll explore is that of checking a specific RGB value of a pixel against an expected value. This method is good for static things such as menus. Since it deals with specific pixels, it’s usually a little too fragile for moving objects. however, its varies from case to case. Sometimes it’s the perfect technique, other time you’ll have to sort out a different method.

Open Sushi Go Round in your browser and start a new game. Ignore your customers and open the phone menu. You start off with no money in the bank, so everything should be greyed out as below. These will be the RGB values we’ll check.

play_button.png

In code.py, scroll to your screenGrab() function. We’re going to make the following changes:

def screenGrab():
    b1 = (x_pad + 1,y_pad+1,x_pad+640,y_pad+480)
    im = ImageGrab.grab()

    ##im.save(os.getcwd() + '\\Snap__' + str(int(time.time())) +'.png', 'PNG')
    return im

We’ve made two small changes. In line 5 we commented out our save statement. In line 6 we now return the Image object for use outside of the function.

Save and run the code. We’re going to do some more interactive work.

With the Toppings menu open and all items greyed out, run the following code:

>>>im = screenGrab()
>>>

This assigns the snap shot we take in screenGrab() to the instance im. For here, we can call the getpixel(xy) method to grab specific pixel data.

Now we need to get RGB values for each of the greyed out items. These will make up our ‘expected value’ that the bot will test against when it makes its own getpixel() calls.

We already have the coordinates we need from the previous steps, so all we have to do is pass them as arguments to getpixel() and note the output.

Output from our interactive session:

>>> im = screenGrab()
>>> im.getpixel(Cord.t_nori)
(33, 30, 11)
>>> im.getpixel(Cord.t_roe)
(127, 61, 0)
>>> im.getpixel(Cord.t_salmon)
(127, 71, 47)
>>> im.getpixel(Cord.t_shrimp)
(127, 102, 90)
>>> im.getpixel(Cord.t_unagi)
(94, 49, 8) >>> im.getpixel(Cord.buy_rice)
(127, 127, 127)
>>>

We need to add these values to our buyFood() function in way that allows it to know whether or not something is available.

def buyFood(food):

    if food == 'rice':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_rice)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        if s.getpixel(Cord.buy_rice) != (127, 127, 127):
            print 'rice is available'
            mousePos(Cord.buy_rice)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'rice is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

    if food == 'nori':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.t_nori) != (33, 30, 11):
            print 'nori is available'
            mousePos(Cord.t_nori)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'nori is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

    if food == 'roe':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()

        time.sleep(.1)
        if s.getpixel(Cord.t_roe) != (127, 61, 0):
            print 'roe is available'
            mousePos(Cord.t_roe)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'roe is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

Here we pass a ingredient name to the buyFood() function. A series of if/elif statements is used to catch the passed parameter and respond accordingly. Each fork follows the exact same logic, so we’ll just explore the first one.

 if food == 'rice':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_rice)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        time.sleep(.1)
 

The first thing we do after the if fork is click on the phone and open up the proper menu item – in this case the Rice menu.

 s = screenGrab()
 if s.getpixel(Cord.buy_rice) != (127, 127, 127):
 

Next we take a quick snapshot of the screen area and call getpixel() to get an RGB value for the pixel at the coordinates of Cord.buy_rice. We then test this against our previously established RGB value for when the item is greyed out. If it evaluates to True, we know that the item is not longer greyed out, and we have enough money to buy it. Consequently, if it evaluated to False, we can’t afford it.

print 'rice is available'
mousePos(Cord.buy_rice)
time.sleep(.1)
leftClick()
mousePos(Cord.delivery_norm)
time.sleep(.1)
leftClick()
time.sleep(2.5)

Providing we can afford the ingredient, we simply navigate through the remaining boxes required to purchase the food.

else:
            print 'rice is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

Finally, if we cannot afford the food, we tell Python to close the menu, wait one second, and then try the process again. It is usually only a matter of seconds between being able to afford something versus not being able to afford something. We won’t do it in this tutorial, but it is fairly straightforward to add additional logic to this function to let the bot decide whether it needs to continue waiting until it can afford something, or if it’s free to do other tasks and return at a later time.


Step 19: Keeping Track of Ingredients

All right, now we’re going to slowly, little by little, start replacing areas where we, the external entity, provide input and decision making with logic that can run by itself.

We need to device a way of keeping track of how many ingredients we currently have on hand. We could do this by pinging the screen in certain areas, or by averaging each ingredient box (we’ll get to this technique later), but by far, the simplest and fastest method is to just store all of the on hand items in a dictionary.

The amount of each ingredient stays constant throughout each level. You will always begin with 10 of the ‘common’ items (rice, nori, roe), and 5 of the ‘premium’ items (shrimp, salmon, unagi).

play_button.png

Let’s add this information to a dictionary.

foodOnHand = {'shrimp':5,
              'rice':10,
              'nori':10,
              'roe':10,
              'salmon':5,
              'unagi':5}

Our dictionary keys hold the name of the ingredient, and we’ll be able to get current amount by exploring the values.


Step 20: Adding Tracking to Code

Now that we have our dictionary of values. Let’s work it into the code. Every time we make something, we’ll subtract the ingredients used. Every time we shop, we’ll add them back in.

Let’s begin by expanding the makeFood() function

def makeFood(food):
    if food == 'caliroll':
        print 'Making a caliroll'
        foodOnHand['rice'] -= 1
        foodOnHand['nori'] -= 1
        foodOnHand['roe'] -= 1
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

    elif food == 'onigiri':
        print 'Making a onigiri'
        foodOnHand['rice'] -= 2
        foodOnHand['nori'] -= 1
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(.05)

        time.sleep(1.5)

    elif food == 'gunkan':
        print 'Making a gunkan'
        foodOnHand['rice'] -= 1
        foodOnHand['nori'] -= 1
        foodOnHand['roe'] -= 2
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

Now each time we make a piece of Sushi, we reduce the values in our foodOnHand dictionary by the appropriate amount. Next we’ll adjust buyFood() to add values.

def buyFood(food):

    if food == 'rice':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_rice)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.buy_rice) != (127, 127, 127):
            print 'rice is available'
            mousePos(Cord.buy_rice)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['rice'] += 10
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'rice is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

    if food == 'nori':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.t_nori) != (33, 30, 11):
            print 'nori is available'
            mousePos(Cord.t_nori)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['nori'] += 10
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'nori is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

    if food == 'roe':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()

        time.sleep(.1)
        if s.getpixel(Cord.t_roe) != (127, 61, 0):
            print 'roe is available'
            mousePos(Cord.t_roe)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['roe'] += 10
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'roe is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

Now each time an ingredient is purchased, we add the quantity to the appropriate dictionary value.


Step 21: Checking Food on Hand

Now that we have our makeFood() and buyFood() functions set up to modify the foodOnHand dictionary, we need to create a new function to monitor all the changes and check whether an ingredient has fallen below a certain threshold.

def checkFood():
    for i, j in foodOnHand.items():
        if i == 'nori' or i == 'rice' or i == 'roe':
            if j <= 4:
                print '%s is low and needs to be replenished' % i
                buyFood(i)

Here we set up a for loop to iterate through the key and value pairs of our foodOnHand dictionary. For each value, it checks whether the name equals one of the ingredients we need; if so, it then checks to see if its value is less than or equal to 3; and finally, providing it is less than 3, it calls buyFood() with the ingredient type as the parameter.

Let’s test this out a bit.

Everything seems to be working fairly well, so let’s move on to some more image recognition tasks.


Step 22: Traversing RGB Values – Setup

To go any further with our bot, we need to gather information about which sushi type is in which customer’s bubble. Doing this with the getpixel() method would be very painstaking as you would need to find an area in each thought bubble that has a unique RGB value not shared by any other sushi type/thought bubble. Given the pixel style art, which by its very nature has a limited color palette, you would have to fight tons of color overlap in the sushi types. Furthermore, for each new sushi type introduced through out the game, you would have to manually inspect it to see if it has a unique RGB not found in any of the other sushi types. Once found, it would certainly be at a different coordinate than the others so that means storing ever more coordinate values – 8 sushi types per bubble times 6 seat locations means 48 unique needed coordinates!

So, in summary, we need a better method.

Enter method two: Image summing/averaging. This version works off of a list of RGB values instead of one specific pixel. For each snapshot we take, the image is grayscaled, loaded into an array, and then summed. This sum is treated the same as the RGB value in the getpixel() method. We will use it to test and compare multiple images.

The flexibility of this method is such that once it is set up, in the case of our sushi bot, not more work is required on our part. As new sushi types are introduced their unique RGB values are summed and printed to the screen for our use. There’s no need to chase down any more specific coordinates like with getpixel().

That said, there is still a bit of setup required for this technique. We’ll need to create a few new bounding boxes so we process just the area of the screen we need rather than the entire play area.

Let get started. Navigate to your screenGrab() function and make a second copy of it. Rename the copy to grab() and make the following changes:

def screenGrab():
    box = (x_pad + 1,y_pad+1,x_pad+640,y_pad+480)
    im = ImageGrab.grab(box)

    ##im.save(os.getcwd() + '\\Snap__' + str(int(time.time())) + '.png', 'PNG')
    return im

def grab():
    box = (x_pad + 1,y_pad+1,x_pad+640,y_pad+480)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    return a

Line 2: We’re taking a screengrab just as we have before, but now we’re converting it to grayscale before we assign it to the instance im. Converting to grayscale makes traversing all of the color values much faster; instead of each pixel having a Red, Green, and Blue value, it only has one value ranging from 0-255.

Line 3: We create an array of the image’s color values using the PIL method getcolors() and assign them to the variable a

Line 4: We sum all the values of the array and print them to the screen. These are the numbers we’ll use when we compare two images.


Step 23: Setting New Bounding Boxes

Start a new game and wait for all of the customers to fill up. Double click on quickGrab.py to take a snapshot of the play area.

play_button.png

We’ll need to set bounding boxes inside of each of those bubbles.

Zoom in till you can see the fine detail of the pixels

play_button.png

For each bubble, we need to make sure the top left of our bounding box starts in the same location. To do so, count up two ‘edges’ from the inner left of the bubble. We want the white pixel at the second ‘edge’ to mark our first x,y location.

play_button.png

To get the bottom pair, add 63 to the x position, and 16 to the y. This will give you a box similar to the one below:

play_button.png

Don’t worry that we’re not getting the entire picture of the Sushi type. Since we’re summing all of the values, even a small change in one pixel will change the total and let us know something new is on screen.

We’re going to create six new functions, each a specialized version of our general grab() one, and fill their bounding arguments with the coordinates of all the bubbles. Once those are made, we’ll make a simple function to call everything at once, just for testing purposes.

def get_seat_one():
    box = (45,427,45+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_one__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_seat_two():
    box = (146,427,146+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_two__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_seat_three():
    box = (247,427,247+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_three__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_seat_four():
    box = (348,427,348+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_four__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_seat_five():
    box = (449,427,449+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_five__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_seat_six():
    box = (550,427,550+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\\seat_six__' + str(int(time.time())) + '.png', 'PNG')
    return a

def get_all_seats():
    get_seat_one()
    get_seat_two()
    get_seat_three()
    get_seat_four()
    get_seat_five()
    get_seat_six()

Okay! Lots of code, but it’s all just specialised versions of previously defined functions. Each defines a bounding box, and passes it to ImageGrab.Grab. From there, we convert to an array of RGB values and print the sum to the screen.

Go ahead and run this a few times while playing the game. Be sure to verify that every sushi type, regardless of which bubble it’s in, displays the same sum each time.


Step 24: Create a Sushi Types Dictionary

Once you’ve verified that each of the sushi types is always displaying the same value, record their sums into a dictionary as follows:

sushiTypes = {2670:'onigiri',
              3143:'caliroll',
              2677:'gunkan',}

Having the numbers as the key and the strings as the values will make it easy to shuffle things from function to function without loosing track of everything.


Step 25: Create a No Bubble Class

The final step in our bubble gathering is getting the sums for when there are no bubbles present. We’ll use these to check when customers have come and gone.

Start a new game and quickly run get_all_seats() before anyone has a chance to show up. The numbers it prints out we’ll place into a class called Blank. As before, you could use a dictionary if you prefer.

class Blank:
    seat_1 = 8119
    seat_2 = 5986
    seat_3 = 11598
    seat_4 = 10532
    seat_5 = 6782
    seat_6 = 9041

We’re almost there now! One final step and we’ll have a simple, working bot!


Step 26: Putting It All Together

Time to finally hand off control to our bot. We’ll script in the basic logic that will let it respond to customers, make their orders, and replenish its ingredients when the begin to run low.

The basic flow will follow this: Check seats > if customer, make order > check food > if low, buy food > clear tables > repeat.

This is a long one; let’s get started.

def check_bubs():

    checkFood()
    s1 = get_seat_one()
    if s1 != Blank.seat_1:
        if sushiTypes.has_key(s1):
            print 'table 1 is occupied and needs %s' % sushiTypes[s1]
            makeFood(sushiTypes[s1])
        else:
            print 'sushi not found!\n sushiType = %i' % s1

    else:
        print 'Table 1 unoccupied'

    clear_tables()
    checkFood()
    s2 = get_seat_two()
    if s2 != Blank.seat_2:
        if sushiTypes.has_key(s2):
            print 'table 2 is occupied and needs %s' % sushiTypes[s2]
            makeFood(sushiTypes[s2])
        else:
            print 'sushi not found!\n sushiType = %i' % s2

    else:
        print 'Table 2 unoccupied'

    checkFood()
    s3 = get_seat_three()
    if s3 != Blank.seat_3:
        if sushiTypes.has_key(s3):
            print 'table 3 is occupied and needs %s' % sushiTypes[s3]
            makeFood(sushiTypes[s3])
        else:
            print 'sushi not found!\n sushiType = %i' % s3

    else:
        print 'Table 3 unoccupied'

    checkFood()
    s4 = get_seat_four()
    if s4 != Blank.seat_4:
        if sushiTypes.has_key(s4):
            print 'table 4 is occupied and needs %s' % sushiTypes[s4]
            makeFood(sushiTypes[s4])
        else:
            print 'sushi not found!\n sushiType = %i' % s4

    else:
        print 'Table 4 unoccupied'

    clear_tables()
    checkFood()
    s5 = get_seat_five()
    if s5 != Blank.seat_5:
        if sushiTypes.has_key(s5):
            print 'table 5 is occupied and needs %s' % sushiTypes[s5]
            makeFood(sushiTypes[s5])
        else:
            print 'sushi not found!\n sushiType = %i' % s5

    else:
        print 'Table 5 unoccupied'

    checkFood()
    s6 = get_seat_six()
    if s6 != Blank.seat_6:
        if sushiTypes.has_key(s6):
            print 'table 1 is occupied and needs %s' % sushiTypes[s6]
            makeFood(sushiTypes[s6])
        else:
            print 'sushi not found!\n sushiType = %i' % s6

    else:
        print 'Table 6 unoccupied'

    clear_tables()

The very first thing we do is check food on hand. from there, we take a snapshot of position one and assign the sum to s1. After that we check to see that s1 does NOT equal Blank.seat_1. If it doesn’t, we have a customer. We check our sushiTypes dictionary to see it has a sum the same as our s1. If it does, we then call makeFood() and pass the sushiType as an argument.

Clear_tables() is called every two seats.

Only one final piece remaining: setting up the loop.


Step 27: Main Loop

We’re going to set up a very simple while loop to play the game. We didn’t make any sort of break mechanism, so to stop execution, click in the shell and hit Ctrl+C to send a keyboard interrupt.

def main():
	startGame()
    while True:
        check_bubs()

And that’s it! Refresh the page, load the game, and set your bot loose!

So, it’s a bit clunky and in need of refinement, but it stands as a decent skeleton for you to iterate upon.

A more complete version of the bot can be found here. It has several fixes such as keeping track of what’s being made, not getting stuck in the phone menus, and other general optimizations.


Conclusion

You now have all of the tools you need to go about building your own simple bots. The techniques we used in this tutorial are quite primitive in the world of Computer Vision, but still, with enough persistence, you can create many cool things with them – even outside the realm of game bots. We, for instance, run several scripts based on these techniques to automate repetitive software tasks around the office. It’s pretty satisfying to remove a human task with just a few lines of code.

Thanks for reading, and if you have any issues or comments, be sure to leave a note below. Good luck, have fun.



View full post on Activetuts+

Mar 25, 2012 Posted on Mar 25, 2012 in Hints and Tips | 10 comments

Build an Image Editor With EaselJS, jQuery, and the HTML5 File API – Tuts+ Premium

In this Premium tutorial, using the HTML5 Canvas and File APIs, we’ll create a full-blown picture editor, with features on par with some desktop applications – including simple transformations like skewing and rotating, and more complicated filters like Gaussian blurring, embossing, and edge detection. For this, we will use the EaselJS library and jQuery.


Premium Preview

Let’s take a look at the final result we will be working towards:

HTML5 EaselJS jQuery Image Editor
Click to try the demo.

Note the huge set of features:

  • Ability to load and save image files from and to your hard drive
  • Built-in print capability (that doesn’t print the UI elements)
  • Undo/redo memory
  • Multiple layers, including text layers
  • Simple transformations: scale, rotate, skew, flip
  • Complex filters: brightness, colorify, blur, emboss, sharpen, and more
  • Basic scripting capabilities

…all coded in JavaScript, using jQuery, HTML5, and EaselJS.

Rather than explaining how to build this app line-by-line from scratch, the tutorial starts from the finished source code and explains how each section works, as well as the theory behind concepts such as the Gaussian blur filter. The full source code is also available to download and modify.


Read the Full Tutorial

Premium members can access the full tutorial right away!

If you’re not yet a Premium member, you can still read the first few steps for free.


Tuts+ Premium Membership

We run a Premium membership system which periodically gives members access to extra tutorials, like this one, from across the whole Tuts+ network. If you’re a Premium member, you can log in and read the tutorial. If you’re not a member, you can of course join today!

Also, don’t forget to follow @envatoactive on twitter, circle us on Google+, like us on Facebook, and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.



View full post on Activetuts+

Mar 12, 2012 Posted on Mar 12, 2012 in Hints and Tips | 10 comments

Create a Colorful Spinning Wheel in Flash With AS3

In this tutorial you’ll learn how to create a spinning wheel using Flash and AS3, with an interface that’s suitable for both mouse- and touch-based devices.


Final Result Preview

Let’s take a look at the final result we will be working towards:

Click and drag your mouse vertically to spin the wheel; the longer the line you drag, the faster the wheel will spin! Once it stops, the colored bar at the bottom will display the color the wheel landed on.


Step 1: Brief Overview

Using pre-made graphic elements we’ll create a colorful interface that will be powered by several ActionScript 3 classes.

The user will be able to spin the wheel using a dragging gesture represented by a line on the screen; a taller line will make a faster spin.


Step 2: Flash Document Settings

Open Flash and create a 500x300px document. Set the frame rate to 24fps.


Step 3: Interface

A colorful nice looking interface will be displayed, made up of multiple shapes, MovieClips and more.
The simple shapes were created using the Flash Pro drawing tools, and since they’re easy to duplicate I won’t explain their creation. Make sure the wheel’s rotation point is in the center.

You can always look at the FLA in the source download files.


Step 4: Instance Names

The image above shows the Instance Names of the various MovieClips. Pay special attention to the wheel.p MovieClips; these are the little black lines that divide the colors in the wheel, and are inside the wheel MovieClip. They are named p1 to p10, going clockwise.


Step 5: TweenMax

We’ll use a different tween engine than the default one included in Flash; this will make the color transition of the colorMC symbol a lot easier.

You can download TweenMax from the Greensock website.


Step 6: Set Main Class

Add the class name, Main, to the Class field in the Publish section of the Properties panel to associate the FLA with the Main document class.


Step 7: Create a new ActionScript Class

Create a new (Cmd + N) ActionScript 3.0 Class and save it as Main.as in your class folder.


Step 8: Class Structure

Create your basic class structure to begin writing your code.

package
{
	import flash.display.Sprite;

	public class Main extends Sprite
	{
		public function Main():void
		{
			// constructor code
		}
	}
}

Step 9: Required Classes

These are the classes we’ll need to import for our class to work. The import directive makes externally defined classes and packages available to your code.

import flash.display.Sprite;
import flash.display.Shape;
import flash.events.MouseEvent;
import flash.events.Event;
import com.greensock.TweenMax;

Step 10: Variables

These are the variables we’ll use; read the comments in the code to know more about them:

private var speed:Number = 0; //the current speed of the wheel
private var paddles:Vector.<Sprite> = new Vector.<Sprite>(); //a vector that holds the p1, p2 etc MCs in the stage
private var line:Shape; //the line drawn as the gesture to move the wheel
private var lastPaddle:String; //will detect the current value of the wheel

Step 11: Constructor

The constructor is a function that runs when an object is created from a class, and is the first to execute when you make an instance of an object. Since this is our document class, it’ll run as soon as the SWF loads.

public final function Main():void
{
	//code...
}

Step 12: Paddles Vector

First we add the various paddle MovieClips to the vector, and add the listeners – we’ll write the listeners() function next.

public final function Main():void
{
	paddles.push(wheel.p1, wheel.p2, wheel.p3, wheel.p4, wheel.p5, wheel.p6, wheel.p7, wheel.p8, wheel.p9, wheel.p10);
	listeners('add');
}

Step 13: Listeners

This function will add or remove the listeners according to the parameter. Mouse Listeners are set to draw the line that will control the wheel.

private final function listeners(action:String):void
{
	if(action == 'add')
	{
		stage.addEventListener(MouseEvent.MOUSE_DOWN, startDraw);
		stage.addEventListener(MouseEvent.MOUSE_UP, spinWheel);
	}
	else
	{
		stage.removeEventListener(MouseEvent.MOUSE_DOWN, startDraw);
		stage.removeEventListener(MouseEvent.MOUSE_UP, spinWheel);
	}
}

Step 14: Movement Line

The next function starts to create a line based on the current mouse position, and places it on the stage. It’s triggered when the mouse is clicked.

private final function startDraw(e:MouseEvent):void
{
	line = new Shape();
	addChild(line);

	line.graphics.moveTo(mouseX, mouseY);
	line.graphics.lineStyle(8, 0x000000, 0.3);//you can change the line color and style here
	stage.addEventListener(MouseEvent.MOUSE_MOVE, drawLine);
}

Step 15: Draw Line

While the mouse is moved, the line continues in that direction.

private final function drawLine(e:MouseEvent):void
{
	line.graphics.lineTo(mouseX, mouseY);
}

Step 16: Spin the Wheel

The next code runs when the mouse button is released, finishing the line. The drawing listeners are removed to avoid drawing multiple lines and the speed is calculated according to the height of the line. Finally, an EnterFrame event is called to actually rotate the wheel.

private final function spinWheel(e:MouseEvent):void
{
	stage.removeEventListener(MouseEvent.MOUSE_MOVE, drawLine);
	listeners('rm');

	speed = line.height * 0.1;
	removeChild(line);
	line = null;

	stage.addEventListener(Event.ENTER_FRAME, spin);
}

Step 17: Rotate the Wheel

This is the function that will spin the wheel and detect what value it lands on:

private final function spin(e:Event):void
{
	/* Rotate Wheel */

	wheel.rotationZ += speed;

Step 18: Detect Value

Here we detect the current value of the wheel based on the last paddle it touched.

/* Detect Value */

for(var i:int = 0; i < 10; i++)
{
	if(indicator.hArea.hitTestObject(paddles[i]))
	{
		lastPaddle = paddles[i].name;
	}
}

Step 19: Decrease Speed

The wheel’s speed is reduced every frame to eventually stop the spinning.

/* Decrease speed */

speed -= 0.1;

Step 20: Reset Wheel

All values are reset when the wheel stops. A function that will run an action according to the final value is called.

	/* Remove listener and reset speed when wheel stops */

	if(speed <= 0)
	{
		stage.removeEventListener(Event.ENTER_FRAME, spin);
		speed = 0;
		setBarColor(lastPaddle);
		listeners('add');
	}
}

Step 21: Set Bar Color

This function will run a custom action according to the last value of the wheel. In this case it changes the color of the bottom bar, but you could make it do anything else.

function setBarColor(action:String):void
{
	switch(action)
	{
		case 'p1':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xF15D5D, tintAmount:1}});
			break;
		case 'p2':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xC06CA8, tintAmount:1}});
			break;
		case 'p3':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x644D9B, tintAmount:1}});
			break;
		case 'p4':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x5E98C6, tintAmount:1}});
			break;
		case 'p5':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x4789C2, tintAmount:1}});
			break;
		case 'p6':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x55C4CB, tintAmount:1}});
			break;
		case 'p7':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x57BC80, tintAmount:1}});
			break;
		case 'p8':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x90CC6C, tintAmount:1}});
		break;
		case 'p9':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xEBE666, tintAmount:1}});
		break;
		case 'p10':
			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xF29C69, tintAmount:1}});
			break;
	}
}

Conclusion

Change the code to perform your own actions!

I hope you liked this tutorial, thank you for reading!



View full post on Activetuts+

Mar 11, 2012 Posted on Mar 11, 2012 in Hints and Tips | 10 comments

Build a Classic Snake Game in AS3

In this tutorial I would like to show you how easy it is to create a classic “Snake” game in Flash. I will try to explain everything easily, step by step, so that you can develop the game further to your needs! The Game will be developed in AS3 and I will use the FlashDevelop IDE.


Introduction

The game won’t be complex. Whenever we hit a wall, it will restart the game. After eating an apple the snake will grow, and a ‘new’ Apple will appear. (Actually, it will be the same apple, but I’ll explain this later.)

One of the most important aspects of the game is the code’s reaction to KEY_DOWN events. The snake will only then change its direction after a tick has passed, not immediately after a keypress. This means that, if the snake is going right, and you press down and left very fast, the snake will go down, not down AND left. Without this ‘feature’ the snake would allow us to go left while we are going right, which would mean it hit itself.


Let’s Look at the Game Already!

Let’s take a look at the final result we will be working towards:


Step 1: Creating the Project

In FlashDevelop, create a new Project, and inside the ‘src’ folder create a ‘com’ folder. In the ‘com’ folder create a new class, and call it ‘Element.as’.

Set the dimensions of the project to 600x600px.

The FlashDevelop project structure

Step 2: Wait… What’s an Element?

The snake is make up of blue squares, which I call elements. We will create an Element Class, which draws the element. The red apple is going to be an element too, so we will extend the code with a few more lines.

Therefore we won’t create a new class for the apple. (But if you really want to, you can.)


Step 3: Writing the Element Class

The Element class creates a square. It doesn’t draw it on the stage, it just creates it. The registration point of the element – the position referred to by its x- and y-coordinates – is in the top-left.

After opening the Element.as you will see something like this:

package com
{
	/**
	 * ...
	 * @author Fuszenecker Zsombor
	 */
	public class Element
	{

		public function Element()
		{

		}

	}
}

First we need this to extend the Shape class, so we can use the graphics object to draw the square. After this, create two variables: one for the direction (if it’s part of the snake), and one for the score value (if it’s an apple), and then change the parameters of the constructor function:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{

		}
	}
}

Now fill the function with some code:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{
			graphics.lineStyle(0, _c, _a);
			graphics.beginFill(_c, _a);
			graphics.drawRect(0, 0, _w, _h);
			graphics.endFill();

			_catchValue = 0;
		}
	}
}

Now, whenever we create an element, it will draw a rectangle and set the score value of the element to 0 by default. (It won’t put the rectangle on stage, it just draws it within itself. Notice that we have not called the addChild() function.)

Let’s finish this class and then we can finally test how much we have done already:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{
			graphics.lineStyle(0, _c, _a);
			graphics.beginFill(_c, _a);
			graphics.drawRect(0, 0, _w, _h);
			graphics.endFill();

			_catchValue = 0;
		}

		//ONLY USED IN CASE OF A PART OF THE SNAKE
		public function set direction(value:String):void
		{
			_direction = value;
		}
		public function get direction():String
		{
			return _direction;
		}

		//ONLY USED IN CASE OF AN APPLE
		public function set catchValue(value:Number):void
		{
			_catchValue = value;
		}
		public function get catchValue():Number
		{
			return _catchValue;
		}
	}

}

We created four functions to change the directions and the value of the apple. We achieved this by using setters and getters. More about Setters/Getters in this article!


Step 4: Testing the Element Class

Open Main.as now.

Import the com.Element class and create an Element in the init() function:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import com.Element;

	public class Main extends Sprite
	{
		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			var testElement:Element = new Element(0x00AAFF, 1, 10, 10);
			testElement.x = 50;
			testElement.y = 50;
			this.addChild(testElement);

		}

	}
}

First we create the testElement variable which holds our element. We create a new Element and assign that to our testElement variable. Note the arguments we passed: first we give it a color, then the alpha, width and height. If you look in the Element class’s Element function, you can see how it uses this data to draw the rectangle.

After creating the Element, we position it and put it on the stage!


Step 5: Setting Up the Variables

Look at the following code. I wrote the functions of the variables next to them (notice that we imported the necessary classes too):

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THESE VARS A VALUE HERE!
		//Give them their values in the init() function.
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts the snake should have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake's parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2; //There will be 2px space between every Element
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired! This will set the SPEED of the snake
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1, 10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious - the score of the apple
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);
		}
	}
}

The most important variable is the snake_vector. We will put every Element of the snake in this Vector.

Then there is the markers_vector. We will use markers to set the direction of the snake’s parts. Each object in this Vector will have a position and a type. The type will tell us whether the snake should go right, left, up, or down after ‘hitting’ the object. (They won’t collide, only the position of the markers and the snake’s parts will be checked.)

As an example, if we press DOWN, an object will be created. The x and y of this object will be the snake’s head’s x and y coordinates, and the type will be “Down”. Whenever the position of one of the snake’s Elements is the same as this object’s, the snakes elements direction will be set to “Down”.

Please read the comments next to the variables to understand what the other variables do!


Step 6: Writing the attachElement() Function

The attachElement() function will take four parameters: the new snake element, the x and y coordinates, and the direction of the last part of the snake.

private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
{

}

Before we put the element on the stage we should position it. But for this we need the direction of the snake’s last element, to know whether the new element has to be above, under, or next to this.

After checking the direction and setting the position, we can add it to the stage.

private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
{
	if (dirOfLast == "R")
	{
		who.x = lastXPos - snake_vector[0].width - space_value;
		who.y = lastYPos;
	}
	else if(dirOfLast == "L")
	{
		who.x = lastXPos + snake_vector[0].width + space_value;
		who.y = lastYPos;
	}
	else if(dirOfLast == "U")
	{
		who.x = lastXPos;
		who.y = lastYPos + snake_vector[0].height + space_value;
	}
	else if(dirOfLast == "D")
	{
		who.x = lastXPos;
		who.y = lastYPos - snake_vector[0].height - space_value;
	}
	this.addChild(who);
}

Now we can use this function in the init() function:

for(var i:int=0;i<min_elements;++i)
{
	snake_vector[i] = new Element(0x00AAFF,1,10,10);
	snake_vector[i].direction = "R"; //The starting direction of the snake
	if (i == 0)//first snake element
	{
		//you have to place the first element on a GRID. (now: 0,0)
		//[possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
		attachElement(snake_vector[i],0,0,snake_vector[i].direction)
		snake_vector[0].alpha = 0.7;
	}
	else
	{
		attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
	}
}

We create the first 10 Elements, and set the direction of them to ‘R’ (right). If it is the first element, we call attachElement() and we change its alpha a bit (so the “head” is a slightly lighter color).

If you wish to set the position somewhere else, then please keep the following in mind: the snake has to be placed on a grid, otherwise it would look bad and would not work. If you wish to change the x and y position you can do it the following way:

Setting the x position: (snake_vector[0].width+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Setting the y position: (snake_vector[0].height+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Let’s change it to this:

if (i == 0)//first snake element
{
	//you have to place the first element on a GRID. (now: 0,0)
	//[possible x positions: (snake_vector[0].width+space_value)*<UINT>]
	attachElement(
		snake_vector[i],
		(snake_vector[0].width+space_value)*20,
		(snake_vector[0].height+space_value)*10,
		snake_vector[i].direction
	);
	snake_vector[0].alpha = 0.7;
}

And the snake’s first element is set onto the 20th space in the x-grid and 10th space in the y-grid.

This is what we’ve got so far:

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);

			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)//first snake element
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}
		}	

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}
	}
}


Step 7: Writing the placeApple() Function

This function does the following:

  1. It checks whether the apple was caught. For this we will use the caught parameter, and set its default value to true, in case we don’t pass any value as parameters in the future. If it was caught, it adds 10 to the apple’s score value (so the next apple is worth more).
  2. After this the apple has to be repositioned (we don’t create new apples) at a random grid position.
  3. If it is placed on the snake, we should place it somewhere else.
  4. If it is not on the stage yet, we place it there.

private function placeApple(caught:Boolean = true):void
{
	if (caught)
		apple.catchValue += 10;

	var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
	var randomX:Number = Math.floor(Math.random()*boundsX);

	var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
	var randomY:Number = Math.floor(Math.random()*boundsY);

	apple.x = randomX * (apple.width + space_value);
	apple.y = randomY * (apple.height + space_value);

	for(var i:uint=0;i<snake_vector.length-1;i++)
	{
		if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
			placeApple(false);
	}
	if (!apple.stage)
		this.addChild(apple);
}

There will be some math here, but if you think it through you should understand why it is so. Just draw it out on some paper if necessary.

  • boundsX will hold how many elements could be drawn in one row.
  • randomX takes this boundsX, multiplies it with a Number between zero and one, and floors it. If boundsX is 12 and the random Number is 0.356, then floor(12*0.356) is 4, so the apple will be placed on the 4th spot on the x-grid.
  • boundsY will hold how many elements can be drawn in one column.
  • randomY takes this boundsY, multiplies it with a Number between zero and one, and floors it.
  • Then we set the x and y position to these numbers.

In the for loop, we check whether the apple’s new x and y positions are identical to any of the snake_vectors elements. If so, we call the placeApple() function again (recursive function), and set the parameter of it to false. (Meaning that the apple was not caught, we just need to reposition it)

(apple.stage) returns true if the apple is on the stage. we use the ‘!’ operator to invert that value, so if it is NOT on the stage, we place it there.

The last thing we need to do is call the placeApple() function at the end of the init() function.

private function init(e:Event = null):void
{
    /*
	.
	.
	.
	*/

	placeApple(false);
}

Notice that we pass false as the parameter. It’s logical, because we didn’t catch the apple in the init() function yet. We will only catch it in the moveIt() function.

Now there are only three more functions to write: the directionChanged(), moveIt() and the gameOver() functions.


Step 8: Starting the moveIt() Function

The moveIt() function is responsible for all of the movement. This function will check the boundaries and check whether there is an object at the x and y position of the snake’s head. It will also look for the apple at this position.

For all of this, we will use our timer variable.

Add two more lines in the end of the init() function:

timer.addEventListener(TimerEvent.TIMER,moveIt);
timer.start();

Look at the comments in the sourcecode, to see which block of code does what.

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				//This block runs if the snakes head is out of the stage (hitting the walls)
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run (Checking the collision with itself).
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					//Sets the new position of the snakes part
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

Now we need to code the movement. For this we jump into the switch block, which will run on every snake part, because of the for loop.

First we need to check the direction of the current element.

				switch (DIRECTION)
				{
					case "R" :
						//Here we need to set the new x position for the current part
						break;
					case "L" :
						//Here we need to set the new x position for the current part
						break;
					case "D" :
						//Here we need to set the new y position for the current part
						break;
					case "U" :
						//Here we need to set the new y position for the current part
						break;
				}

When the direction of the part is set to “R”, for instance, we need to add something to its current X position (the space_value plus the width of the snake part).

With this in mind, we can fill it out:

				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

After testing the code, you should see that the snake is moving, and going off the stage and never stops. (You may need to refresh the page – or just click here to load it in a new window.)

So we need to stop the snake


Step 9: Writing the gameOver() Function

This function is going to be the shortest. We just clear the stage and restart it:

private function gameOver():void
{
	dead = true;
	timer.stop();
	while (this.numChildren)
		this.removeChildAt(0);
	timer.removeEventListener(TimerEvent.TIMER,moveIt);
	init();
}

That’s it. We set the dead variable to true, stop the movement with the timer, remove every child of the class and call the init() function, like we just started the game.

Now, let’s get back to the moveIt() function.


Step 10: Continuing the moveIt() Function

We will use the gameOver() function in two places. The first is when we check if the head is out of bounds, and the second is when the snake hits itself:

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run
					gameOver();
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

This is the code we have now:

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);

			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)//first snake element
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}

			placeApple(false);
			timer.addEventListener(TimerEvent.TIMER, moveIt);
			timer.start();
		}	

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}

		private function placeApple(caught:Boolean = true):void
		{
			if (caught)
				apple.catchValue += 10;

			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
			var randomX:Number = Math.floor(Math.random()*boundsX);

			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
			var randomY:Number = Math.floor(Math.random()*boundsY);

			apple.x = randomX * (apple.width + space_value);
			apple.y = randomY * (apple.height + space_value);

			for(var i:uint=0;i<snake_vector.length-1;i++)
			{
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
					placeApple(false);
			}
			if (!apple.stage)
				this.addChild(apple);
		}		

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run
					gameOver();
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

		private function gameOver():void
		{
			dead = true;
			timer.stop();
			while (this.numChildren)
				this.removeChildAt(0);
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
			//stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			init();
		}

	}
}


Step 11: The directionChanged() Function

We want to listen to the keyboard, so we can actually control the snake. For this we need to put some code into the init() function and the gameOver() function.

Put this at the end of the init() function (setting up the listener function):

stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

And this at the end of the gameOver() function:

stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

Now create a new function:

private function directionChanged(e:KeyboardEvent):void
{
	var m:Object = new Object(); //MARKER OBJECT
	//this will be added to the markers_vector, and have the properties x,y, and type
	//the type property will show us the direction. if it is set to right, whenever a snake's part hits it,
	//the direction of that snake's part will be set to right also

	if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
	{
		//If we pressed the LEFT arrow,
		//and it was not the last key we pressed,
		//and the last key pressed was not the RIGHT arrow either...
		//Then this block of code will run
	}
	markers_vector.push(m); //we push the object into a vector, so we can acces to it later (in the moveIt() function)
}

What goes into the if block?

  • The direction of the head should be rewritten.
  • The marker object has to be set correctly.
  • The last_button variable should be set to the last button pressed.

if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
{
	snake_vector[0].direction = "L";
	m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
	last_button_down = Keyboard.LEFT;
}

Repeat this three more times, and we will have this:

private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
			}
			markers_vector.push(m);
		}

We need one more thing to test it. In the moveIt() function we have something like this:

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

Here we need another for loop, to check every snake’s part against every marker on the stage, and check whether they collide. If they do, we need to set the snake’s part’s direction to the marker’s type. If it’s the last snake part which collides with the marker, we need to remove the marker from the markers_vector, too, so the snake parts don’t collide with it any more.

				if (markers_vector.length > 0)
				{
					for(var j:uint=0;j < markers_vector.length;j++)
					{
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
						{
							//setting the direction
							snake_vector[i].direction = markers_vector[j].type;
							if(i == snake_vector.length-1)
							{
								//if its the last snake_part
								markers_vector.splice(j, 1);
							}
						}
					}
				}

Now if you play with it it looks okay, but there is a bug in there. Remember what i said at the beginning of the tutorial?

For instance, if the snake is going to the right and you press the down-left combo very fast, it will hit itself and restart the game.

How do we correct this? Well it’s easy. We have our flag variable, and we will use that for this. We will only be able to change the directions of the snake when this is set to true (Default is false, check the init() function for that).

So we need to change the directionChanged() function a little. The if blocks’ heads should be changed: add a && flag clause at the end of every ‘if’.

		private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
				flag = false;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
				flag = false;
			}
			markers_vector.push(m);
		}

If you test it now, it won’t work because the flag is always false.

When do we need to set it to true then?

After a move/tick we can allow the users to change directions, we just don’t want to change it twice in one tick. So put this at the very end of the moveIt() function:

flag = true;

Now test it, and there is no bug any more.


Step 12: Finishing the Game

Now the only thing we need to do is the ‘apple-check’

Remember this at the very beginning of the moveIt() function?

			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snake's head's position and the apple's position are the same
			}

This is what we need to do in there:

  • Call the placeApple() function. (We don’t set the parameter to false; we leave it as it is. The default is true.)
  • Show the current score
  • Attach a new element to the snake’s last part.

			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//calling the placeApple() function
				placeApple();
				//show the current Score
				score += apple.catchValue;
				score_tf.text = "Score:" + String(score);
				//Attach a new snake Element
				snake_vector.push(new Element(0x00AAFF,1,10,10));
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
				//attachElement(who,lastXPos,lastYPos,lastDirection)
				attachElement(snake_vector[snake_vector.length-1],
									  (snake_vector[snake_vector.length-2].x),
									  snake_vector[snake_vector.length-2].y,
									  snake_vector[snake_vector.length-2].direction);
			}

Now everything should work fine. Try it out:

Here is the whole Main class again:

			package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{
		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 1;
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The starting direction of the snake (only change it if you change the 'for cycle' too.)
			score = 0;
			score_tf = new TextField();
			this.addChild(score_tf);

			//Create the first <min_elements> Snake parts
			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i],0,0,snake_vector[i].direction)
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}

			placeApple(false);
			timer.addEventListener(TimerEvent.TIMER,moveIt);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			timer.start();
		}

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}

		private function placeApple(caught:Boolean = true):void
		{
			if (caught)
				apple.catchValue += 10;

			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
			var randomX:Number = Math.floor(Math.random()*boundsX);

			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
			var randomY:Number = Math.floor(Math.random()*boundsY);

			apple.x = randomX * (apple.width + space_value);
			apple.y = randomY * (apple.height + space_value);

			for(var i:uint=0;i<snake_vector.length-1;i++)
			{
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
					placeApple(false);
			}
			if (!apple.stage)
				this.addChild(apple);
		}

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				placeApple();
				//show the current Score
				score += apple.catchValue;
				score_tf.text = "Score:" + String(score);
				//Attach a new snake Element
				snake_vector.push(new Element(0x00AAFF,1,10,10));
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
				attachElement(snake_vector[snake_vector.length-1],
									  (snake_vector[snake_vector.length-2].x),
									  snake_vector[snake_vector.length-2].y,
									  snake_vector[snake_vector.length-2].direction);
			}
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				if (markers_vector.length > 0)
				{
					for(var j:uint=0;j < markers_vector.length;j++)
					{
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
						{
							snake_vector[i].direction = markers_vector[j].type;
							if(i == snake_vector.length-1)
							{
								markers_vector.splice(j, 1);
							}
						}
					}
				}
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					gameOver();
				}

				//Move the boy
				var DIRECTION:String = snake_vector[i].direction;
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

			}

			flag = true;
		}

		private function gameOver():void
		{
			dead = true;
			timer.stop();
			while (this.numChildren)
				this.removeChildAt(0);
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
			stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			init();
		}

		private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
				flag = false;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
				flag = false;
			}
			markers_vector.push(m);
		}

	}

}


Step 13: Summing It All Up

Congratulations! You have just created a nice game. Now you can develop it further, and create a super apple or something. For that I recommend using another function called placeSuperApple() and a new class named SuperApple. Whenever you catch a super apple, the snakes parts could lengthen by three elements, perhaps. This could be set with setters/getters in the SuperApple class.

If you wish to do this, and you get stuck somewhere, just leave me a comment here.

Thank you for your time!



View full post on Activetuts+

Mar 11, 2012 Posted on Mar 11, 2012 in Hints and Tips | 10 comments

Best of Tuts+ in February 2012

Each month, we bring together a selection of the best tutorials and articles from across the whole Tuts+ network. Whether you’d like to read the top posts from your favourite site, or would like to start learning something completely new, this is the best place to start!


Psdtuts+ — Photoshop Tutorials

  • Create a Baseball-Inspired Text Effect in Photoshop

    Create a Baseball-Inspired Text Effect in Photoshop

    Applying texture to a text effect can be a lot of fun. In this tutorial we will explain how to create a baseball-inspired text effect using layer styles, patterns, and brushes. Let’s get started!

    Visit Article

  • Create a Mini Planet Using Photoshop’s 3D Capabilities

    Create a Mini Planet Using Photoshop’s 3D Capabilities

    When most people think about Photoshop, they probably don’t think about 3D. What most people don’t realize, however, is that Photoshop CS5 Extended includes some powerful tools to help you render your artwork in 3D. In this tutorial we will demonstrate how to create a mini planet using Photoshop’s 3D capabilities. Let’s get started!

    Visit Article

  • Create a Coffee Cake Photo Manipulation – Tuts+ Premium Tutorial

    Create a Coffee Cake Photo Manipulation – Tuts+ Premium Tutorial

    In this Tuts+ Premium tutorial, author Stephen Petrany will demonstrate how to take pieces from multiple photos and seamlessly blend them into a "coffee cake" photo manipulation. This tutorial will also explore unique ways to work with paths and smart objects. If you are looking to take your photo manipulation skills to the next level then Log in or Join Now to get started!

    Visit Article


  • Nettuts+ — Web Development Tutorials

  • The Largest jQuery Class in the World

    The Largest jQuery Class in the World

    A couple weeks ago, Tuts+ Premium launched a free new real-time course, called “30 Days to Learn jQuery.” After signing up, each member receives an email, linking to a new video lesson for an entire month.

    Visit Article

  • How to Customize Your Command Prompt

    How to Customize Your Command Prompt

    Lately, I’ve been getting this question a lot: “how did you get your terminal to look the way it does?” If you’ve noticed my terminal and are curious about how I set it up, this is the tutorial for you! Of course, what you learn here will be enough to get you started on creating your own custom command prompt, as well!

    Visit Article

  • Attention Developers: NewRelic is your Secret Weapon

    Attention Developers: NewRelic is your Secret Weapon

    While the title of this article may sound like a cliche, hatched in the bowels of PR hell, I’m serious when I say that NewRelic is your secret weapon.

    Visit Article


  • Vectortuts+ — Illustrator Tutorials

  • How to Create a Vintage Type Postcard

    How to Create a Vintage Type Postcard

    Follow this in-depth look at the process of designing type for a vintage style postcard in Adobe Illustrator CS5. Harken back to an era when postcards were all the rage with this friendly type style. The tutorial will delve into clipping masks, using bitmap images, working with layers and type effects.

    Visit Article

  • Create a Block Game Interface in Illustrator

    Create a Block Game Interface in Illustrator

    In the following tutorial you will learn how to create a block game interface in Adobe Illustrator CS5. Vector game graphics allow for versatile artwork. The workflow presented in this tutorial will teach you how to create game graphics in Illustrator. These techniques can be applied to multiple interface design and game design projects. It’s time to jump in, learn to create these shapes, and give them colorful graphic depth.

    Visit Article

  • 25+ Illustrator Tutorials for Creating Vintage Graphics and Retro Illustration

    Illustrator Tutorials for Creating Vintage Graphics and Retro Illustration

    If youre looking to improve your vector design skills, learn how to use Illustrator on a deeper level, and discover how to create vintage vector graphics, then you’ve landed on the right article. We’ve assembled a collection of tutorials that show you how to create vintage illustrations, and retro graphics using Illustrator effects and a variety of professional workflows.

    Visit Article


  • Webdesigntuts+ — Web Design Tutorials

  • Principles for Successful Button Design

    Principles for Successful Button Design

    There are a thousand ways to design and create buttons today and you only need to spend a small amount of time looking through work on dribbble to get a sense of them. A great deal of these examples are exactly the same, but occasionally there are the odd few that feel like they’ve had a little more care and attention in their making.

    Visit Article

  • Orman Clark’s Vertical Navigation Menu: The CSS3 Version

    Orman Clark’s Vertical Navigation Menu: The CSS3 Version

    Next in the Orman Clark’s coded PSD series is his awesome looking Vertical Navigation Menu. We’ll recreate it with CSS3 and jQuery while using the minimal amount of images possible.

    Visit Article

  • Coding the SimpleAdmin Theme: Login Page

    Coding the SimpleAdmin Theme: Login Page

    It’s time to translate our admin layout into a working template. We’ll begin by setting out the markup for our Login page, then we’ll hit the stylesheets..

    Visit Article


  • Phototuts+ — Photography Tutorials

  • The Stock Market: Exploring Stock Photography

    The Stock Market: Exploring Stock Photography

    Creative professionals all over the world frequently require high quality images, but often don’t have the budget to hire a photographer for small projects. Enter stock photography: an industry where awesome photographs are out there and ripe for the using. Today, we’ll be taking a look at the wild world of the stock market – stock photography, that is.

    Visit Article

  • Lightroom 4 Beta: Packed with New Features

    Lightroom 4 Beta: Packed with New Features

    In six short years, Adoble Lightroom has changed the way many photographers manage their images. With powerful cataloging and developing features, Lightroom offers photographers the ability to customize their photo management workflow and manage the thousands of images more efficiently than ever before. Adobe’s innovation continues with Lightroom 4, which is currently in Beta. Today, we’ll be taking a look at some of the new features of the latest iteration of Lightroom.

    Visit Article

  • A Primer to Digital Medium Format Camera

    A Primer to Digital Medium Format Camera

    Over the last few months, I’ve observed a trend among several well known photographers. No longer satisfied with crop factor cameras or even 35mm equivalent full frame digital cameras, more and more photographers are jumping to digital medium format. What are the advantages offered by digital medium formats, and will you be using one anytime soon? Read on to find out.

    Visit Article


  • Cgtuts+ — Computer Graphics Tutorials

  • Achieving 3D Realism: Reception Area Render With 3D Studio Max & V-Ray, Part 1

    Achieving 3D Realism: Reception Area Render With 3D Studio Max & V-Ray, Part 1

    The following tutorial is based on a real project. This unique tutorial will take users through the real process of creating shaders with bespoke physical properties and applying textures based on real photo references.

    Visit Article

  • Create And Render A Still Life Scene In Blender, Using Cycles

    Create And Render A Still Life Scene In Blender, Using Cycles

    Today, we’ll have a brief introduction to Blender’s new rendering engine – Cycles. This tutorial will cover modeling a small and easy still life scene, setting up different types of materials used in cycles and then finally lighting and rendering the scene.

    Visit Article

  • An Introduction To UVMapping In 3d Studio Max Using The Unwrap UVW Modifier

    An Introduction To UVMapping In 3d Studio Max Using The Unwrap UVW Modifier

    So UVMapping… you hate it, I hate it. But unfortunately it’s a necessary step in the process of completing most cg projects. In this tutorial we’ll look at creating uvs using the ‘Unwrap UVW’ modifier in 3D Studio Max, and discuss what uv mapping is, why it’s necessary and some ways to approach it.

    Visit Article


  • Aetuts+ — After Effects Tutorials

  • Create The Amazing Spider-Man Title Sequence Entirely In After Effects

    Create The Amazing Spider-Man Title Sequence Entirely In After Effects

    Nancy will show us how to create the title sequence for the Amazing Spider-Man entirely in After Effects using ShapeShifter AE. She shows us how to combine Shape Layers + Layer Masks to model and animate Spideys symbol. Download the free project file and follow along. You’ll be amazed at how easy it is to create!

    Visit Article

  • 3D Transforming Text With ShapeShifter AE

    D Transforming Text With ShapeShifter AE

    In this tutorial, we will be taking a look at how to build this 3D transforming text animation using Mettle’s ShapeShifter AE plug-in. We will also be enhancing some of the elements using 3rd party plug-ins such as Trapcode Shine (CC Light Burst alternative), Frischluft’s Out of Focus (Lens Blur alternative), and RE:Vision RSMB (CC Force Motion Blur alternative).

    Visit Article

  • Create An Awesome Array Of Shattering Strings

    Create An Awesome Array Of Shattering Strings

    We’ll be starting in Cinema 4d to create text fragments and use XPresso to export the Mograph positional data to After Effects. From there, we’ll jump over into After Effects and use expressions to connect 3d nulls to 2d data points… We’ll also be using a macro in Microsoft Word to edit multiple lines of expressions. It doesn’t matter if you’re a Cinema 4d user or strictly an After Effects user, today’s tutorial should be something helpful for everyone!

    Visit Article


  • Audiotuts+ — Audio & Production Tutorials

  • The 15 Minute Mix

    The 15 Minute Mix

    Consider this your challenge for today. Take a song that you just recorded, or have been working on and mix it in 15 minutes. Shut off everything, pull the faders up and follow the following tutorial. Use a stopwatch to keep track of time and when you should be switching tasks.

    If you don’t have any sessions to try, you can use any of these 50 different multi-tracks.

    Visit Article

  • 35 Audio Tutorial Sites That Will Keep You Learning

    Audio Tutorial Sites That Will Keep You Learning

    The reason you’re here on our site is because you’re interested in audio tutorials. I think we do a great job: we have a huge number of excellent tuts – both free and premium. But we know we haven’t cornered the market. There are an amazing number of audio tut sites out there, and the number seems to grow every year. Here are 35 of the best.

    Visit Article

  • Quick Tip: Creating Skrillex Style Tech Basslines in NI Massive

    Quick Tip: Creating Skrillex Style Tech Basslines in NI Massive

    This series of quick tips will outline how you can use the ever powerful NI Massive synth to create techy basslines used by artists such as Skrillex. In this example I have used Cubase but the same principles will translate to pretty much any other DAW. Here is an example of the kind of sound you can expect to end up with at the end of this series:

    Visit Article


  • Activetuts+ — Flash, Flex & ActionScript Tutorials

  • Review: Construct 2, a Drag and Drop HTML5 Game Maker

    Review: Construct 2, a Drag and Drop HTML5 Game Maker

    Construct 2 is an HTML5 game making tool that doesn’t require any programming knowledge. You just drag and drop items around, add behaviors to them, and make them come alive with “events”.

    Visit Article

  • Number Systems: An Introduction to Binary, Hexadecimal, and More

    Number Systems: An Introduction to Binary, Hexadecimal, and More

    Ever see crazy binary numbers and wonder what they meant? Ever see numbers with letters mixed in and wonder what is going on? You’ll find out all of this and more in this article. Hexadecimal doesn’t have to be scary.

    Visit Article

  • Understanding Affine Transformations With Matrix Mathematics

    Understanding Affine Transformations With Matrix Mathematics

    Inspired by Prof. Wildberger in his lecture series on linear algebra, I intend to implement his mathematical ideas with Flash. We shall not delve into the mathematical manipulation of matrices through linear algebra: just through vectors. This understanding, although diluting the elegance of linear algebra, is enough to launch us into some interesting possibilities of 2×2 matrix manipulation. In particular, we’ll use it to apply various shearing, skewing, flipping, and scaling effects to images at runtime.

    Visit Article


  • Wptuts+ — WordPress Tutorials

  • Creating a Filterable Portfolio with WordPress and jQuery

    Creating a Filterable Portfolio with WordPress and jQuery

    Learn in this tutorial how to make a filterable Portfolio with jQuery integrated with WordPress, remember that this portfolio kind can make a big difference on your themes!

    Visit Article

  • How to Include JavaScript and CSS in Your WordPress Themes and Plugins

    How to Include JavaScript and CSS in Your WordPress Themes and Plugins

    Knowing the proper way to include JavaScript and CSS files in your WordPress themes and plugins is very important for designers and developers. If you don’t adhere to best practices, you run the risk of conflicting with other themes and plugins, and potentially creating problems that could have been easily avoided. This article is intended as a reference for playing nicely with others.

    Visit Article

  • How to Create a Simple Post Rating System With WordPress and jQuery

    How to Create a Simple Post Rating System With WordPress and jQuery

    There already are many post rating system plugins out there. Surprisingly, no one fits my needs, they either are too complicated or with too many built-in options. So, in this tutorial, you’ll learn how to build your own simple post rating functionality, directly within your theme files. There’s no need for plugin!

    Visit Article


  • Mobiletuts+ — Mobile Development Tutorials

  • Getting Started With RenderScript on Android

    Getting Started With RenderScript on Android

    RenderScript is a scripting language on Android that allows you to write high performance graphic rendering and raw computational code. Learn more about RenderScript and write your first graphics app that leverages RenderScript in this tutorial.

    Visit Article

  • PhoneGap From Scratch: Twitter & Maps

    PhoneGap From Scratch: Twitter & Maps

    Want to learn how to use PhoneGap, but don’t know where to get started? Join us as we put together ’Sculder”, not only a tribute to an excellent science fiction TV series, but a fully-fledged native mobile application for the believer in you!

    Visit Article

  • Supplementing iAd Placement with AdMob

    Supplementing iAd Placement with AdMob

    Click-based advertising within a mobile application is a great way to make some money off of your free or inexpensive applications. While there are many choices out there, many iOS developers tend to go with the iAds platform for a variety of reasons including simplicity, aesthetics, and a high CPM.

    Visit Article



View full post on Activetuts+

Page 12 of 253« First«...1011121314...203040...»Last »
search search search search search
Find an Article
Categories
  • Flash Video Training
  • Hints and Tips
  • Recommended
Please Support Our Sponsors
Recent Posts
  • The Math and ActionScript of Curves: Drawing Quadratic and Cubic Curves
  • Weekend Lecture: Understanding Games, a Flash Game About Game Design
  • Weekend Lecture: Understanding Games, a Flash Game About Game Design
  • Workshop Coding Challenge: Fix This Breakout Game
  • Enable the Latest AIR SDK in Flash Professional CS5.5+
Tag Cloud
2011 ActionScript Active Activetuts+ Adobe animation Basic Basix Best Build Button Character Create Creating Critique Custom design Effect Effects Files Flash from Game Guide HTML5 Introduction Macromedia Motion Muzzle part Player Premium Professional Quick Silverlight Simple Text Tool Tutorial Tuts+ Tween Using Video website Workshop
About Our Site:

Hey there and welcome to "Flash Video Training Source", a resource for anybody interested in learning more about Adobe's great tool. We feature educational videos, which will help you master Adobe Flash and help you get to know all of its features. We at "Flash Video Training Source" believe that video training and video... more

Why don't you follow us on Twitter and get the latest video tutorials twitted to your account. Just click on the floating twitter bar to your right!

Go Back In Time
May 2012
M T W T F S S
« Apr    
 123456
78910111213
14151617181920
21222324252627
28293031  
Pretty Blank Box
top

Blogroll

  • Development Blog
  • Documentation
  • Plugins
  • Suggest Ideas
  • Support Forum
  • Themes
  • WordPress Planet

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Archives

  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • August 2011
  • July 2011
  • June 2011
  • May 2011
  • April 2011
  • March 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • August 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
Powered by WordPress  |  Designed by Elegant Themes  |  Lightning Fast Hosting by Site 5 Hosting