Interactive Programming in Python
"Mini-projects" in "Python", serving to reinforce "interactive programming" concepts taught in "Introduction to Interactive Programming in Python" on-line class offered by "Coursera" (https://www.coursera.org/course/interactivepython | September - November 2014).
"GitHub" repository of these scripts can be found at : https://github.com/aristotelis-metsinis/ArcadeGames (source code along with line by line comments).
game description
Just Click on the "run" upper left button on "CodeSkulptor" to start playing. -- click to show/hide source code # # Mini-project # 8 - "RiceRocks" (Asteroids). # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 8: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 16 Nov 2014 # Version: 10.0 # Based on Mini-project # 7 - "Spaceship", ver 8.0 # # Implementation of a 2D space game "RiceRocks" that is # inspired by the classic arcade game "Asteroids". # In the game, the player controls a spaceship via four # buttons: # * two buttons that rotate the spaceship clockwise or # counterclockwise (independent of its current "velocity"). # * a "thrust" button that accelerates the ship in its # "forward" direction. # * a "fire" button that shoots missiles. # Large asteroids spawn randomly on the screen with # random "velocities". # The player's goal is to destroy these asteroids before # they strike the player's ship. # # In Mini-project # 7, a working spaceship plus a # single asteroid and a single missile was implemented # only. The ship did not die if it hit a rock. # Missiles "lifespan" was ignored. A single missile # was allowed; not yet blowing up rocks. # The number of "lives" remaining and the "score" were # simply shown; neither of those elements was changed. # # In Mini-project # 8, the game has multiple # rocks and multiple missiles. A life is lost if the ship # collides with a rock and points are scored if the # missile collides with a rock. The program keeps track of # the score and lives remaining and ends the game at the # proper time. Animated explosions have been added when # there is a collision. # # Note: we highly recommend using "Chrome". # "Chrome" typically has better performance on games # with more substantial drawing requirements and # standardization on a common browser. # Chrome's superior performance becomes apparent when # program attempts to draw dozens of "Sprites". # Unfortunately, no audio format is supported by all # major browsers. The provided sounds are in the "mp3" # format, which is supported by "Chrome" (but not by # "Firefox" on some systems). # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui # Import module, which contains functions that involve # randomness. import random # Import module that contains additional mathematical # operations. import math #--------------------------------------------------------- # Define and initialize global constants. # Globals for user interface. # Initialize global constants that will hold the "width" # and "height" of the "canvas". WIDTH = 800 HEIGHT = 600 # Motion keys. # The "left" and "right" arrows should control the # orientation of the ship. # The "up arrow" should control the thrusters of the ship. # Shoot when the "spacebar" is pressed. MOTION_KEYS = ["left", "right", "up", "space"] # To get the "acceleration" right, we need to multiply # the "forward" vector of the ship by this constant; # small fraction of the "forward" acceleration vector # so that the ship does not accelerate too fast. So, # scale each component of ship's "acceleration" vector # by this factor to generate a reasonable "acceleration". THRUST = 0.1 # Update ship's "angular velocity" by this constant # whenever "left" and "right" arrow keys are pressed. So, # a reasonable "angular velocity" at which ship should # turn. ANGLE_VELOCITY = 0.05 # If "thrusters" are off, "friction" will bring # the ship to a stop by introducing a deceleration # in the opposite direction to the "velocity" of # the ship. Constant factor less than one. # So, the "velocity" should always be multiplied by a # constant factor less than one to slow the ship down. FRICTION = 0.01 # Rock's "angular velocity"; rotate once per second # approximately. ROCK_ANGLE_VELOCITY = 0.1 # Rock's min and max "angular velocity". ROCK_MIN_ANGLE_VELOCITY = -0.15 ROCK_MAX_ANGLE_VELOCITY = 0.15 # Rock's min and max "velocity". ROCK_MIN_VELOCITY = -1 ROCK_MAX_VELOCITY = 1 # Use this scaling factor to vary the "velocity" of rocks # based on the score to make game play more difficult # as the game progresses. In practice, divide score by # this factor and compute the smallest integral value # greater than or equal to the result of the division. # Then the "velocity" of a rock is multipled by the # outcome. VELOCITY_SCALING_FACTOR = 10 # Limit the total number of rocks in the game at any one # time. With too many rocks the game becomes less fun and # the animation slows down significantly. MAX_NUMBER_OF_ROCKS = 12 # Missile speed; used for the computation of the "velocity" # of a missile, i.e. the combination (sum) of the ship's # current "velocity" and a - by this factor - multiple # of the ship's "forward" vector. MISSILE_SPEED = 8 # Tiled explosion image that can be used to create # animated explosions consisting of this number of # images (number of sub-images of the "Sprite"). EXPLOSION_ANIMATIONS = 24 # Volume for the sound to be the given level on a 0 # (silent) - 1.0 (maximum) scale. Default is 1. VOLUME = 0.5 # This is the max remaining lives. MAX_LIVES = 3 # Add these units in a case of a (missile - rock) hit. # A point for each hit, scaled to these units. SCORE_SCALING_FACTOR = 10 # Initialize global constants that will hold general # "draw" properties. FONT_SIZE = 25 FONT_COLOR = 'White' FONT_FACE = 'sans-serif' #--------------------------------------------------------- # Define and initialize global variables. # Initialize global variable that will hold the "score" # of the game. score = 0 # Initialize global variable that will hold the number # of "lives" remaining. lives = MAX_LIVES # Initialize global variable used for "timing" and # "animation" purposes. time = 0 # Boolean flag indicating whether the game has started # (True) or not (False). started = False # Initialize global variable that will be assigned # the "rock" (Sprite) group object (an empty set). rock_group = set([]) # Initialize global variable that will be assigned # the "missile" (Sprite) group object (an empty set). missile_group = set([]) # Initialize global variable that will be assigned # the "explosion" (Sprite) group object (an empty set). explosion_group = set([]) #--------------------------------------------------------- class ImageInfo: """ Define "Image Info" class. """ #----------------------------------------------------- def __init__(self, center, size, radius = 0, lifespan = None, animated = False): """ Create and initialize a "Image Info" object. Stores information about the image. """ # Centre of the image. self.center = center # Size of the image. self.size = size # The radius of a circle that completely encloses # the image; use this circle to detect collisions # with the ship instead of having to worry about # the exact shape of the image. self.radius = radius # The lifespan of the image; not needed for the # ship - other objects like missiles have a # lifespan, i.e. they should be drawn only for a # certain period of time. if lifespan: self.lifespan = lifespan else: self.lifespan = float('inf') # Whether or not image is animated; not needed for # the ship as being a static image - other objects # like explosions are animated images. self.animated = animated #----------------------------------------------------- def get_center(self): """ Get the centre of the image. """ return self.center #----------------------------------------------------- def get_size(self): """ Get the size of the image. """ return self.size #----------------------------------------------------- def get_radius(self): """ Get the radius of a circle that completely encloses the image. """ return self.radius #----------------------------------------------------- def get_lifespan(self): """ Get the lifespan of the image. """ return self.lifespan #----------------------------------------------------- def get_animated(self): """ Get whether or not image is animated. """ return self.animated #--------------------------------------------------------- # Art assets created by "Kim Lathrop"; may be freely # re-used in non-commercial projects, please credit Kim. # Debris images - debris1_brown.png, debris2_brown.png, # debris3_brown.png, debris4_brown.png # debris1_blue.png, debris2_blue.png, # debris3_blue.png, debris4_blue.png, # debris_blend.png debris_info = ImageInfo([320, 240], [640, 480]) debris_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png") # Nebula images - nebula_brown.png, nebula_blue.png nebula_info = ImageInfo([400, 300], [800, 600]) nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_blue.f2014.png") # Splash image. splash_info = ImageInfo([200, 150], [400, 300]) splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png") # Ship image. ship_info = ImageInfo([45, 45], [90, 90], 35) ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png") # Missile images - shot1.png, shot2.png, shot3.png missile_info = ImageInfo([5,5], [10, 10], 3, 50) missile_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/shot2.png") # Asteroid images - asteroid_blue.png, asteroid_brown.png, # asteroid_blend.png asteroid_info = ImageInfo([45, 45], [90, 90], 40) asteroid_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png") # Animated explosion - explosion_orange.png, # explosion_blue.png, # explosion_blue2.png, # explosion_alpha.png explosion_info = ImageInfo([64, 64], [128, 128], 17, 24, True) explosion_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/explosion_alpha.png") # Sound assets purchased from "sounddogs.com"; # please do not redistribute. soundtrack = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/soundtrack.mp3") missile_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/missile.mp3") missile_sound.set_volume(VOLUME) ship_thrust_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/thrust.mp3") explosion_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/explosion.mp3") #--------------------------------------------------------- def angle_to_vector(ang): """ Helper function to handle transformation. When thrusting, the ship should accelerate in the direction of its "forward" vector. This vector can be computed from the orientation/angle of the ship using this "helper" function. """ return [math.cos(ang), math.sin(ang)] #--------------------------------------------------------- def dist(p,q): """ Helper function to compute the "distance" of two 2D points. """ return math.sqrt((p[0] - q[0]) ** 2+(p[1] - q[1]) ** 2) #--------------------------------------------------------- class Ship: """ Define "Ship" class. """ #----------------------------------------------------- def __init__(self, pos, vel, angle, image, info): """ Create and initialize a "Ship" object of a specific "position", "velocity", "angle", "image" and "image - info". """ # "Position" of the ship on the "canvas" (vector / # pair of floats). self.pos = [pos[0], pos[1]] # "Velocity" of the ship; the direction the ship # is moving does not have to be in the direction # it is facing (vector / pair of floats). self.vel = [vel[0], vel[1]] # Make the ship accelerate in the # direction it is facing (boolean). # Initially set to "false" indicating that we are # not thrusting in the beginning; engines are # turned off - we should not draw the thrust and # we should not accelerate. self.thrust = False # Ship's orientation (scalar / float); the angle # between the horizontal axis and the direction the # ship is pointing (in radians; not degrees). self.angle = angle # Ship's angular velocity (scalar / float); control # how fast the ship will actually rotate whenever # "left" and "right" arrow keys are pressed. # Initially set to "0" indicating that the ship # should not be rotating in the beginning. self.angle_vel = 0 # The image of the ship (tiled image; the first # tile is the ship without thrusters - the second # tile is the ship with thrusters). self.image = image # Get information about this image. self.image_center = info.get_center() self.image_size = info.get_size() self.radius = info.get_radius() #----------------------------------------------------- def draw(self, canvas): """ Draw ship's image by incorporating ship's "position" and "angle". Draw the "thrust" image when thrusters are on. Note: the angle should be in radians, not degrees; one radian is equal to "180/pi" degrees. """ # Draw ship's image with or without thrusters # depending on whether or not the ship is currently # thrusting. The ship should also be drawn rotated # to the proper "angle" so that it is # facing in the proper way. if not self.thrust: canvas.draw_image(self.image, (self.image_center[0], self.image_center[1]), (self.image_size[0], self.image_size[1]), (self.pos[0], self.pos[1]), (self.image_size[0], self.image_size[1]), self.angle) else: canvas.draw_image(self.image, (3 * self.image_center[0], self.image_center[1]), (self.image_size[0], self.image_size[1]), (self.pos[0], self.pos[1]), (self.image_size[0], self.image_size[1]), self.angle) return None #----------------------------------------------------- def update(self): """ Modify and update ship's "angle", "position" and "velocity". It gets called every time the "draw" handler for the system is called. """ # Update ship's "angle" (orientation) based on its # "angular velocity". self.angle += self.angle_vel # Update the "position" of the ship based on its # "velocity". # Note: as seen below, while the ship accelerates # in its "forward" direction, the ship always # moves in the direction of its "velocity" vector. self.pos[0] += self.vel[0] self.pos[1] += self.vel[1] # Ship's "position" wraps around the screen when # it goes off the edge (by using modular # arithmetic). self.pos[0] %= WIDTH self.pos[1] %= HEIGHT # If "thrusters" are off, "friction" will bring # the ship to a stop by introducing a deceleration # in the opposite direction to the "velocity" of # the ship (constant factor slightly less than one). # Without a "friction" the # ship will move as fast as we want by keeping # "thrusters" continually on. "Friction" actually # caps the "velocity" of the ship. # So, the "velocity" should always be multiplied by a # constant factor less than one to slow the ship # down. self.vel[0] *= (1 - FRICTION) self.vel[1] *= (1 - FRICTION) # When thrusting, update the "velocity" of the # ship in terms of its "acceleration" in the # direction of the "forward" vector. if self.thrust: # However previously we need to figure out # what direction the ship is accelerating in; # the "forward" vector that # corresponds to the direction that should # accelerate in based on ship's angle; by # making use of the "angle_to_vector()" # function. # Also, to get the acceleration right, we # need to multiply the "forward" vector by a # constant (small fraction of the "forward" # "acceleration" vector so that the ship does # not accelerate too fast). forward = angle_to_vector(self.angle) self.vel[0] += forward[0] * THRUST self.vel[1] += forward[1] * THRUST return None #----------------------------------------------------- def adjust_orientation(self, angle_vel): """ Increment and decrement the "angular velocity" by the (fixed in practice) "angle_vel" amount. """ self.angle_vel += angle_vel return None #----------------------------------------------------- def set_thrust(self, on_off): """ Turn the thrusters on/off depending on the "on_off" boolean argument. Rewind and Play the "thrust" sound when the "thrust" is on. Pause/Stop the sound when the "thrust" turns off. """ self.thrust = on_off if self.thrust: # Stop playing the sound in any case, and # make it so the next "sound.play()" will # start playing the sound at the # beginning. ship_thrust_sound.rewind() ship_thrust_sound.play() else: # Stop playing the sound. ship_thrust_sound.pause() return None #----------------------------------------------------- def shoot(self): """ Spawn a new missile (an instance of the "Sprite" class) and add it to the "missile_group". Call this method when the "spacebar" is pressed. """ # The missile spawns from the tip of the ship; # so missile's initial "position" should be the # tip of ship's "cannon". # Also the missile is travelling in the direction # the ship is facing. So, the "velocity" of a # missile is the combination (sum) of the ship's # current "velocity" and a multiple of the ship's # "forward" vector. Furthermore, missiles will # always have a zero "angular velocity" and a # "lifespan". Finally, missile sound is passed to # the "Sprite" initializer so that the shooting # sound is played whenever a missile spawns. forward = angle_to_vector(self.angle) missile_pos = [self.pos[0] + self.radius * forward[0], self.pos[1] + self.radius * forward[1]] missile_vel = [self.vel[0] + MISSILE_SPEED * forward[0], self.vel[1] + MISSILE_SPEED * forward[1]] a_missile = Sprite(missile_pos, missile_vel, self.angle, 0, missile_image, missile_info, missile_sound) # Keep a set of missiles and spawn this new # missile into this set when firing using the # space bar. global missile_group missile_group.add(a_missile) return None #----------------------------------------------------- def get_position(self): """ Get the position of the ship. """ return self.pos #----------------------------------------------------- def get_radius(self): """ Get the radius of a circle that completely encloses the (image of the) ship. """ return self.radius #--------------------------------------------------------- class Sprite: """ Define "Sprite" class. "Sprite" is a 2D image or animation overlaid on top of a game to add visual complexity. """ #----------------------------------------------------- def __init__(self, pos, vel, ang, ang_vel, image, info, sound = None): """ Create and initialize a "Sprite" object; implements rocks and missiles. Note: "velocity" and "rotation" are not anymore controlled by keys as in the case of the ship. Rock "Sprites" have set "velocity", "position", and "angular velocity" randomly when they are created. Also, missiles will always have a zero "angular velocity" and a certain "lifespan". """ # "Position" of the "Sprite" on the "canvas" # (vector / pair of floats). self.pos = [pos[0], pos[1]] # "Velocity" of the "Sprite". self.vel = [vel[0], vel[1]] # "Sprite's" orientation (scalar / float); the # angle between the horizontal axis and the # direction the "Sprite" is pointing (in radians; # not degrees). self.angle = ang # "Sprite's" angular velocity (scalar / float); # control how fast the "Sprite" will actually # rotate. self.angle_vel = ang_vel # The image of the "Sprite". self.image = image # Get information about this image. self.image_center = info.get_center() self.image_size = info.get_size() self.radius = info.get_radius() # The lifespan of the image. self.lifespan = info.get_lifespan() # Whether or not image is animated. self.animated = info.get_animated() # Age of the "Sprite"; if the "age" is greater than # or equal to the "lifespan" of the "Sprite", then # it should be removed. self.age = 0 # Option of giving "Sprite" a sound. if sound: sound.rewind() sound.play() #----------------------------------------------------- def draw(self, canvas): """ Draw the "Sprite". Check if "self.animated" attribute is "True". If so, then choose the correct tile in the image based on the "age". The image is tiled horizontally. If "self.animated" is "False", it should continue to draw the "Sprite" as before. """ if not self.animated: canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle) else: # Tiled explosion image that can be used to # create animated explosions. # Modular arithmetic; so the number coming back # will be always within the range from zero to # the number of the (sub)images of the "Sprite". # Integer division; to avoid index by a fraction # (rather index by a whole number) - index # inside the tiled image. image_index = (self.age % EXPLOSION_ANIMATIONS) // 1 # Computation of the sub-image centre. image_center = [self.image_center[0] + (self.image_size[0] * image_index), self.image_center[1]] canvas.draw_image(self.image, image_center, self.image_size, self.pos, self.image_size, self.angle) return None #----------------------------------------------------- def update(self): """ Update "Sprite" every time this is called inside the "draw" handler; move and rotate "Sprite". Return "False" (i.e. keep "Sprite") if the "age" is less than the "lifespan" or "True" (i.e. remove "Sprite") otherwise. Note: "velocity" and "rotation" are not anymore controlled by keys as in the case of the ship. Rock "Sprites" have set "velocity", "position", and "angular velocity" randomly when they are created. Rocks do not accelerate or experience friction. Also, missiles will always have a zero "angular velocity" and a certain "lifespan". """ # Update Sprite's "angle" (orientation) based on # its "angular velocity". self.angle += self.angle_vel # Update the "position" of the "Sprite" based on # its "velocity". self.pos[0] += self.vel[0] self.pos[1] += self.vel[1] # Sprite's position wraps around the screen when # it goes off the edge (by using modular # arithmetic). self.pos[0] %= WIDTH self.pos[1] %= HEIGHT # Increment the "age" of the "Sprite" every time # "update()" is called. self.age += 1 # If the "age" is greater than or equal to the # "lifespan" of the "Sprite", then it should be # removed (i.e. return "True", else "False"). if self.age >= self.lifespan: return True else: return False #----------------------------------------------------- def collide(self, other_object): """ Take an "other_object" as an argument and return "True" if there is a collision with a (rock) "Sprite" or "False" otherwise. This "other object" may be either the "ship" or a "missile" object in practice. """ # Compute the distance between the centres of the # two objects. If less that the sum of their # radius then assume collision (the circles that # enclose them overlap). if dist(self.pos, other_object.get_position()) < ( self.radius + other_object.get_radius()): return True else: return False #----------------------------------------------------- def get_position(self): """ Get the position of the "Sprite". """ return self.pos #----------------------------------------------------- def get_radius(self): """ Get the radius of a circle that completely encloses the (image of the) "Sprite". """ return self.radius #--------------------------------------------------------- def draw(canvas): """ Event handler that is responsible for all drawing. It receives the "canvas" object and draws the animated background, the ship, the rock and missile "Sprites" as well as game's score and remaining lives. If the number of lives becomes 0, the game is reset and the "splash" screen appears. """ global time # Animate background. time += 1 wtime = (time / 4) % WIDTH center = debris_info.get_center() size = debris_info.get_size() canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT]) canvas.draw_image(debris_image, center, size, (wtime - WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) canvas.draw_image(debris_image, center, size, (wtime + WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) global lives # Draw remaining "lives". canvas.draw_text("Lives: " + str(lives), [WIDTH // 8, HEIGHT // 8], FONT_SIZE, FONT_COLOR, FONT_FACE) global score # Draw "score"; # but first get the width of the "score" text in # pixels; useful in (later) computing the # position to draw the "score" text - right justified # on the "canvas". score_text = "Score: " + str(score) score_textwidth_in_px = frame.get_canvas_textwidth(score_text, FONT_SIZE, FONT_FACE) score_point_x = WIDTH - (WIDTH // 8) - (score_textwidth_in_px) score_point_y = HEIGHT // 8 canvas.draw_text(score_text, [score_point_x, score_point_y], FONT_SIZE, FONT_COLOR, FONT_FACE) global rock_group, missile_group, explosion_group # Draw ship and "Sprites". my_ship.draw(canvas) if rock_group: # Take the group of rocks and the "canvas" and # call the "draw" method for each # rock "Sprite" in the group. process_sprite_group(canvas, rock_group, "draw") if missile_group: # Take the group of missiles and the "canvas" and # call the "draw" method for each # missile "Sprite" in the group. process_sprite_group(canvas, missile_group, "draw") if explosion_group: # Take the group of explosions and the "canvas" # and call the "draw" method for each # explosion "Sprite" in the group. process_sprite_group(canvas, explosion_group, "draw") # Update ship and "Sprites". my_ship.update() if rock_group: # Take the group of rocks and the "canvas" and # call the "update" method for each rock "Sprite" # in the group. process_sprite_group(None, rock_group, "update") if missile_group: # Take the group of missiles and the "canvas" and # call the "update" method for each # missile "Sprite" in the group. process_sprite_group(None, missile_group, "update") if explosion_group: # Take the group of explosions and the "canvas" # and call the "update" method for each # explosion "Sprite" in the group. process_sprite_group(None, explosion_group, "update") # Determine if the ship hits any of the rocks. # If so, decrease the number of lives by one. if group_collide(rock_group, my_ship): lives -= 1 global started # If the number of lives becomes 0, the game is # reset and the "splash" screen shall appear. if lives == 0: started = False # Destroy all "Sprites". rock_group = set([]) #missile_group = set([]) #explosion_group = set([]) # Detect missile / rock collisions and # increment the "score" by the number of missile # collisions (and a "scaling" factor). score += group_group_collide(rock_group, missile_group) * SCORE_SCALING_FACTOR # Draw "splash" screen if game not started; dismissed # with a mouse click before starting this game. if not started: canvas.draw_image(splash_image, splash_info.get_center(), splash_info.get_size(), [WIDTH / 2, HEIGHT / 2], splash_info.get_size()) return None #--------------------------------------------------------- def rock_spawner(): """ Timer handler that spawns a rock; new rock on every tick; once every second. Note: "velocity" and "rotation" are not anymore controlled by keys as in the case of the ship. Rock "Sprites" have set "velocity", "position", and "angular velocity" randomly when they are created. Last but not least, the total number of rock "Sprites" in the game is limited at any one time. """ # Prevent any (more) rocks for spawning until the # game is (re)started. if not started: return None global rock_group # Limit the total number of rocks in the game at any # one time. if len(rock_group) >= MAX_NUMBER_OF_ROCKS: return None # Rock's "velocity". # Generate random floating-point number "n" in an # arbitrary range: "min <= n < max". velocity_range = ROCK_MAX_VELOCITY - ROCK_MIN_VELOCITY rock_vel = [random.random() * velocity_range + ROCK_MIN_VELOCITY, random.random() * velocity_range + ROCK_MIN_VELOCITY] # Vary the velocity of rocks based on the score to # make game play more difficult as the game progresses. # In practice, multiply "velocity" by 1, 2, 3, etc, as # the score gets higher. if score > 0: rock_vel = [rock_vel[0] * math.ceil(float(score) / (VELOCITY_SCALING_FACTOR * SCORE_SCALING_FACTOR)), rock_vel[1] * math.ceil(float(score) / (VELOCITY_SCALING_FACTOR * SCORE_SCALING_FACTOR))] # Rock's "angular velocity". # Generate random floating-point number "n" in an # arbitrary range: "min <= n < max". angle_velocity_range = ROCK_MAX_ANGLE_VELOCITY - ROCK_MIN_ANGLE_VELOCITY rock_angle_vel = random.random() * angle_velocity_range + ROCK_MIN_ANGLE_VELOCITY # Rock's "position". # Generate random integer "n" such that # "min <= n < max". rock_pos = [random.randrange(0, WIDTH - 1), random.randrange(0, HEIGHT - 1)] # Only spawn new rock if collision with ship is # "false", taking into considaration also a "safe" # distance "margin" by making sure they are some # distance away from the ship. Otherwise, ship can be # destroyed when a rock spawns on top of it. So, # "skip" a rock spawn event if the spawned rock is too # close to the ship. ship_radius = ship_info.get_radius() rock_radius = asteroid_info.get_radius() while dist(rock_pos, my_ship.pos) < ( (2 * ship_radius) + rock_radius): rock_pos = [random.randrange(0, WIDTH - 1), random.randrange(0, HEIGHT - 1)] # So generate rocks that spin in both directions # and, likewise, move in all directions. a_rock = Sprite(rock_pos, rock_vel, 0, rock_angle_vel, asteroid_image, asteroid_info) # Spawn new rocks into the set of rocks by # adding the just created rock (an instance # of a "Sprite" object). rock_group.add(a_rock) return None #--------------------------------------------------------- def process_sprite_group(canvas, sprite_group, method): """ Helper function to take a "set" and a "canvas" and call the "update" and "draw" methods for each "Sprite" in the group. Check also the return value of "update" for "Sprites". If it returns "True", remove the "Sprite" from the group. Note: iterate over a copy of the "sprite group" to avoid deleting (if necessary) from the same set over which we are iterating. """ group_copy = set(sprite_group) for sprite in group_copy: if method == "draw": sprite.draw(canvas) else: remove_sprite = sprite.update() if remove_sprite: sprite_group.remove(sprite) return None #--------------------------------------------------------- def group_collide(group, other_object): """ Helper function to take a set "group" and an a "Sprite" "other_object" and check for collisions between "other_object" and elements of the group. In practice, collisions between a single "Sprite" (e.g. ship or a missile) and any "Sprite" in the "group" (e.g. group of rocks) will be detected. This function should return "True" or "False" depending on whether there was a collision. "True" if there was a collision with anything in the "group", "False" if "other_object" collided with nothing in the "group". """ collision = False # Upon a collision, the rock should be # destroyed and the player should lose a life. # If there is a collision, the colliding object # should will be removed from the group. To avoid # removing an object from a set that we are iterating # over (which can cause a serious debugging headache), # we iterate over a copy of the set created via # "set(group)". group_copy = set(group) for object in group_copy: if object.collide(other_object): group.remove(object) collision = True # Create a new "explosion" (an instance of # the "Sprite" class) and add it to the # "explosion_group". # Make sure that each explosion plays # the explosion sound. explosion = Sprite(object.get_position(), [0, 0], 0, 0, explosion_image, explosion_info, explosion_sound) explosion_group.add(explosion) return collision #--------------------------------------------------------- def group_group_collide(group_1, group_2): """ Helper function that essentially checks for collisions between two groups by taking two groups of objects as input and iterating through the elements of a copy of the first group using a for-loop. Then calls "group_collide()" with each of these elements on the second group. Returns the number of elements in the first group that collide with the second group as well as deletes these elements in the first group. """ number_of_collisions = 0 group_copy_1 = set(group_1) for object in group_copy_1: collision = group_collide(group_2, object) if collision: # Essentially, we want to destroy rocks when # they are hit by a missile. group_1.discard(object) # Keep track the number of times # "group_collide()" returns "True". number_of_collisions += 1 # Return the number of collisions between "group_1" # (e.g. rocks) and "group-2" (e.g. missiles); for # example if the number is 6, then 6 rocks were # destroyed and we can update score accordingly. return number_of_collisions #--------------------------------------------------------- def keydown_handler(key): """ Event key handler. Update ship's "angular velocity" and so control how the ship rotates whenever "left" and "right" arrow keys are pressed. While the "left arrow" is held down, ship should turn counter-clockwise. While the "right arrow" is down, ship should turn clockwise. When neither key is down, ship should maintain its orientation. Also, the "up arrow" should control the thrusters of the ship. The thrusters should be on when the "up arrow" is down and off when it is up. Shoot when the "spacebar" is pressed. """ # Enable this handler only when game has started # in practice. #if not started: # return None # The "left" and "right" arrows should control the # orientation of the ship. # The "up arrow" should control the thrusters of the ship. # Call "shoot()" method when the "spacebar" is pressed. if key == simplegui.KEY_MAP[MOTION_KEYS[0]]: # If the "left arrow" key is pressed, turn ship # counter-clockwise. # Decrement the "angular velocity" by a # fixed amount in practice. my_ship.adjust_orientation(-ANGLE_VELOCITY) if key == simplegui.KEY_MAP[MOTION_KEYS[1]]: # If the "right arrow" key is pressed, turn ship # clockwise. # Increment the "angular velocity" by a # fixed amount in practice. my_ship.adjust_orientation(ANGLE_VELOCITY) if key == simplegui.KEY_MAP[MOTION_KEYS[2]]: # If the "up arrow" key is pressed, thrusters # should be on. my_ship.set_thrust(True) if key == simplegui.KEY_MAP[MOTION_KEYS[3]]: # If the "spacebar" key is pressed, call "shoot()" # method. my_ship.shoot() return None #--------------------------------------------------------- def keyup_handler(key): """ Event key handler. Update ship's "angular velocity" and so control how the ship rotates whenever "left" and "right" arrow keys are pressed. While the "left arrow" is held down, ship should turn counter-clockwise. While the "right arrow" is down, ship should turn clockwise. When neither key is down, ship should maintain its orientation. Also, the "up arrow" should control the thrusters of the ship. The thrusters should be on when the "up arrow" is down and off when it is up. """ # The "left" and "right" arrows should control the # orientation of the ship. # The "up arrow" should control the thrusters of the ship. if key == simplegui.KEY_MAP[MOTION_KEYS[0]]: # If the "left arrow" key is released, stop turning # ship counter-clockwise. # Maintain ship's orientation by "canceling" # the last change of the "angular velocity". my_ship.adjust_orientation(ANGLE_VELOCITY) if key == simplegui.KEY_MAP[MOTION_KEYS[1]]: # If the "right arrow" key is released, stop turning # ship clockwise. # Maintain ship's orientation by "canceling" # the last change of the "angular velocity". my_ship.adjust_orientation(-ANGLE_VELOCITY) if key == simplegui.KEY_MAP[MOTION_KEYS[2]]: # If the "up arrow" key is released, thrusters # should be off. my_ship.set_thrust(False) return None #--------------------------------------------------------- def click(pos): """ Mouse click handler that resets UI and conditions whether "splash" image is drawn. """ center = [WIDTH / 2, HEIGHT / 2] size = splash_info.get_size() inwidth = (center[0] - size[0] / 2) < pos[0] < (center[0] + size[0] / 2) inheight = (center[1] - size[1] / 2) < pos[1] < (center[1] + size[1] / 2) global started, lives, score, my_ship if (not started) and inwidth and inheight: started = True # Make sure lives and score are properly # initialized. lives = MAX_LIVES score = 0 # Stop playing the sound, and # make it so the next "sound.play()" will # start playing the background music at the # beginning. soundtrack.rewind() soundtrack.play() # Initialize ship. #my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], # 0, ship_image, ship_info) return None #--------------------------------------------------------- # Create and initialize frame. frame = simplegui.create_frame("Asteroids", WIDTH, HEIGHT) # Initialize ship (and two "Sprites"). my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 0, ship_image, ship_info) # In the template of Mini-project # 7, the global variable # "a_rock" was created at the start with zero "velocity". # Instead, we wanted to create version of "a_rock" once # every second in the "timer" handler. #a_rock = Sprite([WIDTH / 3, HEIGHT / 3], [0, 0], # 0, ROCK_ANGLE_VELOCITY, # asteroid_image, asteroid_info) # In the template of Mini-project # 7, the global variable # "a_missile" was created at the start. Instead, we wanted # to create version of "a_missile" whenever "spacebar" key # was pressed in the "keydown" handler. #a_missile = Sprite([2 * WIDTH / 3, 2 * HEIGHT / 3], [-1,1], # 0, 0, missile_image, # missile_info, missile_sound) # Register the "event handler" that is responsible # for all drawing. frame.set_draw_handler(draw) # Register "event handlers" for "control" elements. frame.set_keydown_handler(keydown_handler) frame.set_keyup_handler(keyup_handler) frame.set_mouseclick_handler(click) # Create a "Timer" repeatedly calling the proper "event # handler" every 1 second. timer = simplegui.create_timer(1000.0, rock_spawner) # Get things rolling. # Start the "Timer". timer.start() # Start frame. frame.start() # Start playing the background music. #soundtrack.play() #--------------------------------------------------------- -- click to play "RiceRocks" @ http://www.codeskulptor.org/#user38_ZS5c5CDjLVDH3kS.py
game description
The program keeps track of wins and losses for Blackjack's session (wins minus losses) and contains: Just Click on the "run" upper left button on "CodeSkulptor" to start playing. -- click to show/hide source code # # Mini-project # 6: "Blackjack". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 6: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 2 Nov 2014 # Version: 13.0 # # Implementation of card game: "Blackjack". # # "Cards" in "Blackjack" have the following values: an "ace" # may be valued as either 1 or 11 (player's choice), "face" # cards (kings, queens and jacks) are valued at 10 and the # value of the remaining cards corresponds to their number. # # During a round of "Blackjack", the players plays against # a "Dealer" with the goal of building a "Hand" (a # collection of "Cards") whose cards have a total value # that is higher than the value of the dealer's "Hand", but # not over 21. A round of "Blackjack" is also sometimes # referred to as a "Hand". # # The game logic for this simplified version of "Blackjack" # is as follows: # * The "Player" and the "Dealer" are each dealt two "Cards" # initially with one of the dealer's cards being dealt # faced down (his hole card). # * The "Player" may then ask for the "Dealer" to # repeatedly "hit" his "Hand" by dealing him another "Card". # * If, at any point, the value of the player's "Hand" # exceeds 21, the "Player" is "busted" and loses # immediately. # * At any point prior to busting, the "Player" may "stand" # and the "Dealer" will then hit his "Hand" until the # value of his "Hand" is 17 or more. For the "Dealer", "aces" # count as 11 unless it causes the dealer's "Hand" to bust. # * If the "Dealer" busts, the "Player" wins. Otherwise, # the "Player" and "Dealer" then compare the values of # their "Hands" and the "Hand" with the higher value wins. The # "Dealer" wins ties in this version. # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui # Import module, which contains functions that involve # randomness. import random #--------------------------------------------------------- # Define and initialize global constants. # Moad "Card" sprite: 936 x 384 - source: jfitz.com CARD_SIZE = (72, 96) CARD_CENTER = (36, 48) card_images = simplegui.load_image("http://storage.googleapis.com/codeskulptor-assets/cards_jfitz.png") CARD_BACK_SIZE = (72, 96) CARD_BACK_CENTER = (36, 48) card_back = simplegui.load_image("http://storage.googleapis.com/codeskulptor-assets/card_jfitz_back.png") # Define globals for "Cards". SUITS = ('C', 'S', 'H', 'D') RANKS = ('A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K') VALUES = {'A':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':10, 'Q':10, 'K':10} # Initialize global constant that will hold the "title" # of the game. GAME_TITLE = "Blackjack" # Initialize global constants that will hold the "width" # and "height" of the "canvas" ("deck of cards"). CANVAS_WIDTH = 600 CANVAS_HEIGHT = 600 # Initialize global constants that will hold general # "draw" properties. X_MARGIN = 10 Y_MARGIN = 20 Y_MARGIN_PLUS = 30 FONT_SIZE = 25 TITLE_FONT_SIZE = 50 FONT_COLOR = 'White' TITLE_FONT_COLOR = 'Orange' OUTCOME_FONT_COLOR = 'Yellow' FONT_FACE = 'sans-serif' CANVAS_BACKGROUND = "Green" BUTTON_WIDTH = 200 # Define game messages. INVALID_CARD = "Invalid card: " HAND_CONTAINS = "Hand contains " DECK_CONTAINS = "Deck contains " HIT_OR_STAND = "Player, hit or stand ?" NEW_DEAL = "Player, new deal ?" PLAYER_WINS = "Player, you win." PLAYER_LOSES = "Player, you lose." PLAYER_BUSTED = "Player, you have busted." DEALER_BUSTED = "Dealer busted." EARLY_DEAL_1 = "Deal during the middle of" EARLY_DEAL_2 = "the round." # Player wins or loses these points. SCORE_POINTS = 1 #--------------------------------------------------------- # Define and initialize global variables. # Initialize global variable that will keep track of # whether the player's "Hand" is still being played. in_play = False # Used for drawing text messages on the "canvas". outcome = outcome_plus = outcome_plus_plus = "" # These messages should prompt the "Player" to take some # required action. action = "" # Initialize global variable that will keep track of # wins and losses for Blackjack's session (wins minus # losses). score = 0 #--------------------------------------------------------- class Card: """ Define "Card" class. """ #----------------------------------------------------- def __init__(self, suit, rank): """ Create and initialize a "Card" object of a specific "suit" and "rank". """ if (suit in SUITS) and (rank in RANKS): self.suit = suit self.rank = rank else: self.suit = None self.rank = None global outcome outcome = INVALID_CARD, suit, rank #----------------------------------------------------- def __str__(self): """ Return a string representation of a "Card" object in a human-readable form. """ return self.suit + self.rank #----------------------------------------------------- def get_suit(self): """ Return "Card's" suit. """ return self.suit #----------------------------------------------------- def get_rank(self): """ Return "Card's" rank. """ return self.rank #----------------------------------------------------- def draw(self, canvas, pos): """ Draw a "Card" object on the "canvas" at the specific "pos" (position of the upper left corner). """ card_loc = (CARD_CENTER[0] + CARD_SIZE[0] * RANKS.index(self.rank), CARD_CENTER[1] + CARD_SIZE[1] * SUITS.index(self.suit)) canvas.draw_image(card_images, card_loc, CARD_SIZE, [pos[0] + CARD_CENTER[0], pos[1] + CARD_CENTER[1]], CARD_SIZE) return None #--------------------------------------------------------- class Hand: """ Define "Hand" class. """ #----------------------------------------------------- def __init__(self): """ Create and initialize the "Hand" object to have an empty list of "Card" objects. """ self.hand = [] #----------------------------------------------------- def __str__(self): """ Return a string representation of a "Hand" object in a human-readable form. """ string_representation = HAND_CONTAINS for card in self.hand: string_representation += str(card) + " " return string_representation #----------------------------------------------------- def add_card(self, card): """ Append a "Card" object to a "Hand" object (list of "Card" objects). """ self.hand.append(card) return None #----------------------------------------------------- def get_value(self): """ Compute the value of the "Hand" object. Use the provided "VALUE" dictionary to look up the value of a single "Card" to compute the value of a "Hand". Count "aces" as 1, if the "Hand" has an "ace", then add 10 to "Hand" value if it doesn't bust. Note: in practice never count two "aces" as "11". """ value = 0 ace = False for card in self.hand: value += VALUES[card.get_rank()] if (card.get_rank() == 'A'): ace = True if not ace: return value else: if (value + 10) <= 21: return (value + 10) else: return value #----------------------------------------------------- def draw(self, canvas, pos): """ Draw a "Hand" object on the "canvas" by using the "draw" method for "Cards". """ # Draw a "Hand" as a horizontal sequence of "Cards" # where the parameter "pos" is the position of the # upper left corner of the leftmost "Card". # Note: assume generally that only the first five # "Cards" of a player's "Hand" need to be visible # on the "canvas". for card in self.hand: card.draw(canvas, pos) pos[0] += CARD_SIZE[0] + X_MARGIN return None #--------------------------------------------------------- class Deck: """ Define "Deck" class. """ #----------------------------------------------------- def __init__(self): """ Create and initialize a "Deck" object as list of "Cards" generated by a "list" comprehension method and using the "Card" initializer to create the "Cards". """ self.deck = [Card(suit,rank) for suit in SUITS for rank in RANKS] #----------------------------------------------------- def shuffle(self): """ Shuffle the "Deck". """ random.shuffle(self.deck) return None #----------------------------------------------------- def deal_card(self): """ Deal a "Card" object from the "Deck". """ return self.deck.pop() #----------------------------------------------------- def __str__(self): """ Return a string representation of a "Deck" object in a human-readable form. """ string_representation = DECK_CONTAINS for card in self.deck: string_representation += str(card) + " " return string_representation #--------------------------------------------------------- def deal(): """ Event handler for "Deal" button that shuffles the "Deck" and "deals" the two "Cards" to both the "Dealer" and the "Player". """ # Update messages, score and the player's "Hand" status # as global variables. global outcome, outcome_plus, outcome_plus_plus, in_play, score, action outcome = outcome_plus = outcome_plus_plus = "" action = HIT_OR_STAND # If the "Deal" button is clicked during the middle of # a round the program reports that the "Player" lost # the round and updates the "score" appropriately. if in_play: outcome = PLAYER_LOSES outcome_plus = EARLY_DEAL_1 outcome_plus_plus = EARLY_DEAL_2 score -= SCORE_POINTS else: in_play = True # Create and shuffle the "Deck" (stored as a global # variable). Avoids the situation where the "Deck" # becomes empty during play. global deck_of_cards deck_of_cards = Deck() deck_of_cards.shuffle() # Create new "Player" and "Dealer" Hands (stored as # global variables). global player, dealer player = Hand() dealer = Hand() # Add two "Cards" to each "Hand". To transfer a "Card" # from the "Deck" to a "Hand", the "deal_card()" # method of the "Deck" class and the "add_card()" # method of "Hand" class are being used in # combination. player.add_card(deck_of_cards.deal_card()) dealer.add_card(deck_of_cards.deal_card()) player.add_card(deck_of_cards.deal_card()) dealer.add_card(deck_of_cards.deal_card()) # Print resulting "Hands" to the console with an # appropriate message indicating which "Hand" is which. # Remove comments if in DEBUG mode. #print "Player: " + str(player) #print "Dealer: " + str(dealer) return None #--------------------------------------------------------- def hit(): """ Event handler for "Hit" button. If the value of the "Hand" is less than or equal to 21, clicking this button adds an extra card to player's "Hand". If the value exceeds 21 after being hit, "You have busted" is get printed. """ # Update messages, score and the player's "Hand" status # as global variables. global outcome, outcome_plus, outcome_plus_plus, in_play, score, action # If the "Hand" is in play, hit the "player". if in_play: outcome = outcome_plus = outcome_plus_plus = "" player.add_card(deck_of_cards.deal_card()) else: return None # If busted, update messages, score and the player's # "Hand" status. if player.get_value() > 21: outcome = PLAYER_BUSTED outcome_plus = outcome_plus_plus = "" action = NEW_DEAL score -= SCORE_POINTS in_play = False return None #--------------------------------------------------------- def stand(): """ Event handler for "Stand" button. If the "Player" has busted, remind the "Player" that they have busted. Otherwise, repeatedly hit the "Dealer" until his "Hand" has value 17 or more. If the "Dealer" busts, let the "Player" know. Otherwise, compare the value of the player's and dealer's "Hands". If the value of the player's "Hand" is less than or equal to the dealer's "Hand", the "Dealer" wins. Otherwise the "Player" has won. Note: the "Dealer" wins ties in this game version. """ # Update message, score and the player's "Hand" status # as global variables. global outcome, outcome_plus, outcome_plus_plus, in_play, score, action # If the "Player" has busted, remind the "Player" that # they have busted. if player.get_value() > 21: outcome = PLAYER_BUSTED outcome_plus = outcome_plus_plus = "" action = NEW_DEAL elif in_play: # If the "Hand" is in play, repeatedly hit "Dealer" # until his "Hand" has value 17 or more. while dealer.get_value() < 17: dealer.add_card(deck_of_cards.deal_card()) # If busted, update messages, score and the # player's "Hand" status. if dealer.get_value() > 21: outcome = PLAYER_WINS outcome_plus = DEALER_BUSTED outcome_plus_plus = "" action = NEW_DEAL score += SCORE_POINTS in_play = False # Else compare the value of the # player's and dealer's "Hands". If the value of # the player's "Hand" is less than or equal to # the dealer's "Hand", the "dealer" wins. # Otherwise the "player" has won. Again, # update messages, score and the player's "Hand" # status. else: in_play = False action = NEW_DEAL outcome_plus = outcome_plus_plus = "" if player.get_value() > dealer.get_value(): outcome = PLAYER_WINS score += SCORE_POINTS else: outcome = PLAYER_LOSES score -= SCORE_POINTS return None #--------------------------------------------------------- def draw(canvas): """ Event handler that is responsible for all drawing. It receives the "canvas" object and draws message(s) to the "Player", the "title" and the "score" of the game as well as draws the resulting "Hands" with appropriate titles indicating which "Hand" is which. """ canvas.draw_text(outcome, (CANVAS_WIDTH // 2, CANVAS_HEIGHT // 5), FONT_SIZE, OUTCOME_FONT_COLOR, FONT_FACE) canvas.draw_text(outcome_plus, (CANVAS_WIDTH // 2, CANVAS_HEIGHT // 4), FONT_SIZE, OUTCOME_FONT_COLOR, FONT_FACE) canvas.draw_text(outcome_plus_plus, (CANVAS_WIDTH // 2, (CANVAS_HEIGHT // 4) + Y_MARGIN_PLUS), FONT_SIZE, OUTCOME_FONT_COLOR, FONT_FACE) canvas.draw_text(GAME_TITLE, (CANVAS_WIDTH // 10, CANVAS_HEIGHT // 10), TITLE_FONT_SIZE, TITLE_FONT_COLOR, FONT_FACE) canvas.draw_text("Score: " + str(score), (CANVAS_WIDTH // 10 , CANVAS_HEIGHT // 5), FONT_SIZE, FONT_COLOR, FONT_FACE) canvas.draw_text("Dealer", (CANVAS_WIDTH // 10 , CANVAS_HEIGHT // 3), FONT_SIZE, FONT_COLOR, FONT_FACE) dealer.draw(canvas, [CANVAS_WIDTH // 10, CANVAS_HEIGHT // 3 + Y_MARGIN]) canvas.draw_text("Player", (CANVAS_WIDTH // 10 , CANVAS_HEIGHT // 3 + 3 * Y_MARGIN + CARD_SIZE[1]), FONT_SIZE, FONT_COLOR, FONT_FACE) player.draw(canvas, [CANVAS_WIDTH // 10, CANVAS_HEIGHT // 3 + 4 * Y_MARGIN + CARD_SIZE[1]]) canvas.draw_text(action, (CANVAS_WIDTH // 2, CANVAS_HEIGHT // 3 + 3 * Y_MARGIN + CARD_SIZE[1]), FONT_SIZE, TITLE_FONT_COLOR, FONT_FACE) # If the round is still in play, an image of the back # of a "Card" should be drawn over the dealer's first # (hole) "Card" to hide it. Once the round is over, # the dealer's hole "Card" should be displayed. if in_play: card_loc = (CARD_CENTER[0], CARD_CENTER[1]) canvas.draw_image(card_back, card_loc, CARD_SIZE, [CANVAS_WIDTH // 10 + CARD_CENTER[0], CANVAS_HEIGHT // 3 + Y_MARGIN + CARD_CENTER[1]], CARD_SIZE) return None #--------------------------------------------------------- # Create and initialize frame. frame = simplegui.create_frame("Blackjack", CANVAS_WIDTH, CANVAS_HEIGHT) frame.set_canvas_background(CANVAS_BACKGROUND) # Register event handlers for "control" elements and # frame buttons. # Create and shuffle a new "Deck" (or restock and shuffle # an existing deck) each time the "Deal" button is clicked. # This change avoids the situation where the "Deck" becomes # empty during play. frame.add_button("Deal", deal, BUTTON_WIDTH) frame.add_label("") frame.add_button("Hit", hit, BUTTON_WIDTH) frame.add_label("") frame.add_button("Stand", stand, BUTTON_WIDTH) # Register the "event handler" that is responsible # for all drawing. frame.set_draw_handler(draw) # Get things rolling. # A "Hand" is automatically dealt to the "Player" and # "Dealer" when the program starts via a call to the # "deal()" function during initialization. deal() # Start frame. frame.start() ########################################################## # Testing templates for classes. # # Uncomment each sequence of calls and check whether the # output to the console matches that provided in the # comments below. #--------------------------------------------------------- #--------------------------------------------------------- # 1. Testing template for the "Card" class. # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-card_template.py #c1 = Card("S", "A") #print c1 #print c1.get_suit(), c1.get_rank() #print type(c1) #c2 = Card("C", "2") #print c2 #print c2.get_suit(), c2.get_rank() #print type(c2) #c3 = Card("D", "T") #print c3 #print c3.get_suit(), c3.get_rank() #print type(c3) #--------------------------------------------------------- # Output to console. #SA #S A #<class '__main__.Card'> #C2 #C 2 #<class '__main__.Card'> #DT #D T #<class '__main__.Card'> #--------------------------------------------------------- #--------------------------------------------------------- # 2. Testing template for the "Hand" class. # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-hand_template.py #c1 = Card("S", "A") #c2 = Card("C", "2") #c3 = Card("D", "T") #print c1, c2, c3 #print type(c1), type(c2), type(c3) #test_hand = Hand() #print test_hand #test_hand.add_card(c1) #print test_hand #test_hand.add_card(c2) #print test_hand #test_hand.add_card(c3) #print test_hand #print type(test_hand) #--------------------------------------------------------- # Output to console. # Note that the string representation of a "Hand" will # vary based on how the "__str__" method has been # implemented. #SA C2 DT #<class '__main__.Card'> <class '__main__.Card'> <class '__main__.Card'> #Hand contains #Hand contains SA #Hand contains SA C2 #Hand contains SA C2 DT #<class '__main__.Hand'> #--------------------------------------------------------- #--------------------------------------------------------- # 3. Testing template for the "Deck" class. # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-deck_template.py #test_deck = Deck() #print test_deck #print type(test_deck) #c1 = test_deck.deal_card() #print c1 #print type(c1) #print test_deck #c2 = test_deck.deal_card() #print c2 #print type(c2) #print test_deck #test_deck = Deck() #print test_deck #test_deck.shuffle() #print test_deck #print type(test_deck) #c3 = test_deck.deal_card() #print c3 #print type(c3) #print test_deck #--------------------------------------------------------- # Output to console. # Output of string method for decks depends on the # implementation of "__str__" method. # Note the output of shuffling is randomized so the exact # order of "Cards" need not match. #Deck contains CA C2 C3 C4 C5 C6 C7 C8 C9 CT CJ CQ CK SA S2 S3 S4 S5 S6 S7 S8 S9 ST SJ SQ SK HA H2 H3 H4 H5 H6 H7 H8 H9 HT HJ HQ HK DA D2 D3 D4 D5 D6 D7 D8 D9 DT DJ DQ DK #<class '__main__.Deck'> #DK #<class '__main__.Card'> #Deck contains CA C2 C3 C4 C5 C6 C7 C8 C9 CT CJ CQ CK SA S2 S3 S4 S5 S6 S7 S8 S9 ST SJ SQ SK HA H2 H3 H4 H5 H6 H7 H8 H9 HT HJ HQ HK DA D2 D3 D4 D5 D6 D7 D8 D9 DT DJ DQ #DQ #<class '__main__.Card'> #Deck contains CA C2 C3 C4 C5 C6 C7 C8 C9 CT CJ CQ CK SA S2 S3 S4 S5 S6 S7 S8 S9 ST SJ SQ SK HA H2 H3 H4 H5 H6 H7 H8 H9 HT HJ HQ HK DA D2 D3 D4 D5 D6 D7 D8 D9 DT DJ #Deck contains CA C2 C3 C4 C5 C6 C7 C8 C9 CT CJ CQ CK SA S2 S3 S4 S5 S6 S7 S8 S9 ST SJ SQ SK HA H2 H3 H4 H5 H6 H7 H8 H9 HT HJ HQ HK DA D2 D3 D4 D5 D6 D7 D8 D9 DT DJ DQ DK #Deck contains CT H6 C4 H9 D6 HJ D2 S5 D8 H2 ST H4 HQ HK S8 D3 CJ D5 DK DQ DA S9 S6 S2 DJ C8 SJ C9 D4 C7 SK CK S3 CA SA S4 CQ S7 HA H3 C5 D9 DT H7 HT C2 SQ H8 C6 D7 C3 H5 #<class '__main__.Deck'> #H5 #<class '__main__.Card'> #Deck contains CT H6 C4 H9 D6 HJ D2 S5 D8 H2 ST H4 HQ HK S8 D3 CJ D5 DK DQ DA S9 S6 S2 DJ C8 SJ C9 D4 C7 SK CK S3 CA SA S4 CQ S7 HA H3 C5 D9 DT H7 HT C2 SQ H8 C6 D7 C3 #--------------------------------------------------------- #--------------------------------------------------------- # 4. Testing template for the "get_value" method for # "Hands". # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-getvalue_template.py #c1 = Card("S", "A") #c2 = Card("C", "2") #c3 = Card("D", "T") #c4 = Card("S", "K") #c5 = Card("C", "7") #c6 = Card("D", "A") #test_hand = Hand() #print test_hand #print test_hand.get_value() #test_hand.add_card(c2) #print test_hand #print test_hand.get_value() #test_hand.add_card(c5) #print test_hand #print test_hand.get_value() #test_hand.add_card(c3) #print test_hand #print test_hand.get_value() #test_hand.add_card(c4) #print test_hand #print test_hand.get_value() #test_hand = Hand() #print test_hand #print test_hand.get_value() #test_hand.add_card(c1) #print test_hand #print test_hand.get_value() #test_hand.add_card(c6) #print test_hand #print test_hand.get_value() #test_hand.add_card(c4) #print test_hand #print test_hand.get_value() #test_hand.add_card(c5) #print test_hand #print test_hand.get_value() #test_hand.add_card(c3) #print test_hand #print test_hand.get_value() #--------------------------------------------------------- # Output to console. # Note that the string representation of a "Hand" may vary # based on the implementation of the "__str__" method. #Hand contains #0 #Hand contains C2 #2 #Hand contains C2 C7 #9 #Hand contains C2 C7 DT #19 #Hand contains C2 C7 DT SK #29 #Hand contains #0 #Hand contains SA #11 #Hand contains SA DA #12 #Hand contains SA DA SK #12 #Hand contains SA DA SK C7 #19 #Hand contains SA DA SK C7 DT #29 ########################################################## -- click to play "Blackjack" @ http://www.codeskulptor.org/#user38_Sre1SIq1cuvP3al.py
game description
-- click to show/hide source code # # Mini-project # 5: "Memory". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 5: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 26 Oct 2014 # Version: 10.0 # # Implementation of card game: "Memory". # # Two game "modes": play with "textual numbers" or # "images. # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui # Import module, which contains functions that involve # randomness. import random # Import module that contains additional mathematical # operations. import math #--------------------------------------------------------- # Define and initialize global constants. # Initialize global constants that will hold the "width" # and "height" of the "canvas" ("deck of cards" - grid of # 16 "cards"). CANVAS_WIDTH = 800 CANVAS_HEIGHT = 140 # "Memory" game of 16 "cards" (as global constant). CARDS_NUMBER = 16 # Compute the "width" of a single cell of this grid; # "placeholder" for a single "card" (cells distributed # evently). CARD_PLACEHOLDER_WIDTH = (CANVAS_WIDTH // CARDS_NUMBER) # Set general "draw" properties. FONT_SIZE = 50 FONT_FACE = 'sans-serif' FONT_COLOR = 'White' MARGIN_Y = 19 # Compute the (global constant) "vertical" position to # draw a "card", presenting a "textual number" and taking # into consideration the height of the "deck of cards" # plus a "margin". CARD_VALUE_POINT_Y = (CANVAS_HEIGHT // 2) + MARGIN_Y # More general "draw" properties. CARD_PLACEHOLDER_LINE_COLOR = 'Black' CARD_PLACEHOLDER_FILL_COLOR = 'Green' CARD_PLACEHOLDER_LINE_WIDTH = 2 # Initialize a "dictionary" as global constant, mapping # numbers from 0-7 (acting as "keys") to "urls" (acting # as "values"). In practice, the business logic of the # program models generally the "deck of cards" as a # "shuffled" list consisting of 16 numbers with each # number lying in the range [0,8) and appearing twice. # The following "urls" (links to images) # are just being used at the "presentation" layer, # drawing the proper "image" instead of "number" (text). IMAGES = {} IMAGES[0] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/riemann.jpg') IMAGES[1] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/aristotle.jpg') IMAGES[2] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/euler.jpg') IMAGES[3] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/gauss.jpg') IMAGES[4] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/newton.jpg') IMAGES[5] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/einstein.jpg') IMAGES[6] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/hilbert.jpg') IMAGES[7] = simplegui.load_image('http://aristotelis-metsinis.github.io/img/lagrange.jpg') #--------------------------------------------------------- # Define and initialize global variables. # Boolean flag: play the game with "images" (True) or # with "textual numbers" (False). play_with_images = False #--------------------------------------------------------- def new_game(): """ Helper function that starts and restarts the game, initializing global variables; reshuffle the "cards", reset the "turn" counter and restart the game. All "cards" should start the game hidden. """ # Initialize global variable that will hold the "deck # of cards"; we model the "deck of cards" as a list # consisting of 16 numbers with each number lying in # the range [0,8) and appearing twice. The list is # created by concatenating two lists with range [0,8) # together. Although Player can play the game with # "textual numbers" or "images", the above mentioned # technique is being used modeling the game in both # game "modes". global deck_of_cards deck_of_cards = range(CARDS_NUMBER // 2) + range(CARDS_NUMBER // 2) # Shuffle the "deck". random.shuffle(deck_of_cards) # Remove comment if in DEBUG mode. #print deck_of_cards # Initialize global variable that will hold the a list, # with size equal to the size of the "deck of cards" # consisting of boolean values. The boolean value # at a certain list index indicates whether the "card" # is "exposed" (True) or not (False). Particularly, # the ith entry should be "True" if the ith card is # face up and its value is visible or "False" if the # ith card is face down and it's value is hidden. global deck_of_cards_exposed deck_of_cards_exposed = [False] * CARDS_NUMBER # Initialize global variable that will hold the game # state (0,1 and 2), i.e. beginning of the game, single # "exposed" unpaired "card" and end of a "turn" # respectively (have a look at the comments of # "mouseclick()" for a detailed description # concerning this variable). global state state = 0 # Initialize global variable that will hold the number # of "turns" playing the game. global turn turn = 0 label.set_text("Turns = " + str(turn)) # Initialize global variable that will hold a "helper" # list, keeping the index of the cards "exposed" in # a single "turn". global index_of_cards_exposed_in_a_turn index_of_cards_exposed_in_a_turn = [-1, -1] return None #--------------------------------------------------------- def mouseclick(pos): """ Define "mouse click" event handler; implements game "state" logic. It receives a parameter; pair of screen coordinates, i.e. a tuple of two non-negative integers - the position of the mouse click. """ # User clicks on a "card" of the "deck" (grid of # evenly distributed cells - cards placeholders). # Compute the index of this "card", i.e. determine # which card have been clicked on with the mouse. # Recall that the sequence of cards entirely fills # the "canvas". clicked_card_index = int(math.floor(float(pos[0]) / CARD_PLACEHOLDER_WIDTH)) # If user clicks on a card already "exposed"; ignore # event and "return" function immediately. if deck_of_cards_exposed[clicked_card_index]: return None # The counter of "turns" playing the game will be # updated as a global variable. global turn # The following block implements the game logic for # selecting two "cards" and determining if they match. # State 0 corresponds to the start of the game. # In state 0, if you click on a card, that card is # exposed, and you switch to state 1. # State 1 corresponds to a single exposed unpaired # card. # In state 1, if you click on an unexposed card, that # card is exposed and you switch to state 2. # State 2 corresponds to the end of a turn. # In state 2, if you click on an unexposed card, that # card is exposed and you switch to state 1. global state if state == 0: # Set the "status" of the clicked "card" # as "exposed". deck_of_cards_exposed[clicked_card_index] = True # Store the "index" of the "exposed" card. # This is the first card "exposed" in this "turn" # of the game. index_of_cards_exposed_in_a_turn[0] = clicked_card_index # Update "turn" counter; incremented after the # first "card" is flipped during a turn. turn += 1 label.set_text("Turns = " + str(turn)) # Switch to the next game "state". state = 1 elif state == 1: # Set the "status" of the clicked "card" # as "exposed". deck_of_cards_exposed[clicked_card_index] = True # Store the "index" of the "exposed" card. # This is the second card "exposed" in this "turn" # of the game. index_of_cards_exposed_in_a_turn[1] = clicked_card_index # Switch to the next game "state". state = 2 else: # Set the "status" of the clicked "card" # as "exposed". deck_of_cards_exposed[clicked_card_index] = True # Get the value of the cards exposed in the previous # "turn" of the game (taking advantage of the # "indexes" stored). Then determine if the previous # two "exposed" cards are paired or unpaired. # If unpaired then switch the "status" of these # cards back to "unexposed"; i.e. flip them back # over so that they are hidden before moving to # state 1. if deck_of_cards[index_of_cards_exposed_in_a_turn[0]] != deck_of_cards[index_of_cards_exposed_in_a_turn[1]]: deck_of_cards_exposed[index_of_cards_exposed_in_a_turn[0]] = False deck_of_cards_exposed[index_of_cards_exposed_in_a_turn[1]] = False # Store the "index" of the "exposed" card. # This is the first card "exposed" in this "turn" # of the game, i.e. replace the "index" of the # first card "exposed" in the previous "turn" of # the game. index_of_cards_exposed_in_a_turn[0] = clicked_card_index # Update "turn" counter; incremented after the # first "card" is flipped during a turn. turn += 1 label.set_text("Turns = " + str(turn)) # Switch to the next game "state". state = 1 return None #--------------------------------------------------------- def draw(canvas): """ Event handler that is responsible for all drawing. It receives the "canvas" object and draws the "deck of cards" (grid) as a horizontal sequence of 16 evently distributed cells - "card" placeholders. It also draws the "exposed" cards (if any) taking into consideration the "mode" of the game, i.e either drawing "textual numbers" or "images" in the "cells" of the "exposed" cards (placeholders). "Cards" are logically 50 x 140 pixels in size based on the configurations set for the purposes of this program. """ # Iterate through the "Memory deck" and draw all 16 # "card" placeholders. for index in range(CARDS_NUMBER): # Store the position of the left and right border # of this cell (card placeholder). card_placeholder_left_x = CARD_PLACEHOLDER_WIDTH * index card_placeholder_right_x = CARD_PLACEHOLDER_WIDTH * (index + 1) # Check if the "card" of this cell has an "exposed" # (already) status. if deck_of_cards_exposed[index]: # Compute the position at the middle of this # cell. card_placeholder_middle_x = (card_placeholder_right_x + card_placeholder_left_x) // 2 # Play the game with "textual numbers" instead # of "images". if not play_with_images: # Use the "index" of this "cell" as the # "index" in the list of the "deck of # cards" extracting the "card value". # Get the width of the "card value" text # in pixels; useful in (later) computing # the position to draw the "card value" # text - centered justified in the "cell" # of each "card" (placeholder). card_value_textwidth_in_px = frame.get_canvas_textwidth(str(deck_of_cards[index]), FONT_SIZE, FONT_FACE) card_value_point_x = card_placeholder_middle_x - (card_value_textwidth_in_px // 2) # Draw the "textual number" associated # with each "card" on the "canvas". canvas.draw_text(str(deck_of_cards[index]), (card_value_point_x, CARD_VALUE_POINT_Y), FONT_SIZE, FONT_COLOR, FONT_FACE) # Play the game with "images" in place of # "textual numbers". else: # Use the "index" of this "cell" as the # "index" in the list of the "deck of # cards" extracting the "card value". # Later use this "card value" as the "key" # loading the corresponding "image". image = IMAGES[deck_of_cards[index]] # Draw the "image" associated with each # "card" on the "canvas". canvas.draw_image(image, (image.get_width() // 2,image.get_height() // 2), (image.get_width(), image.get_height()), (card_placeholder_middle_x, CANVAS_HEIGHT // 2), (image.get_width(), image.get_height())) # "Card" of this cell is not "exposed" (already); # simply draw a cell ("card" placeholder). else: card_placeholder_points = [[card_placeholder_left_x, 0], [card_placeholder_right_x, 0], [card_placeholder_right_x, CANVAS_HEIGHT], [card_placeholder_left_x, CANVAS_HEIGHT]] # Just draw a blank green rectangle. canvas.draw_polygon(card_placeholder_points, CARD_PLACEHOLDER_LINE_WIDTH, CARD_PLACEHOLDER_LINE_COLOR, CARD_PLACEHOLDER_FILL_COLOR) return None #--------------------------------------------------------- def switch_game_mode(): """ Button event handler that updates properly the boolean flag, which "keeps" the "mode" of the game. The game has two modes: play with "textual numbers" (False) or "images" (True). Each time button is pressed the value of this variable changes from "True" to "False" and vice versa. The button text is updated accordingly. """ # The boolean flag will be updated as a global # variable. If already "True", will be "False" (and # vice versa). global play_with_images play_with_images = not play_with_images if play_with_images: # User will play this game with "images". Update # button text informing the user that he/she will # reset the on-going game and play the next # game with "textual numbers". switch_game_mode_button.set_text("Reset and Play with numbers") else: # User will play this game with "textual numbers". # Update button text informing the user that # he/she will reset the on-going game and play # the next game with "images". switch_game_mode_button.set_text("Reset and Play with images") # Reset on-going game. new_game() return None #--------------------------------------------------------- # Create frame. frame = simplegui.create_frame("Memory", CANVAS_WIDTH, CANVAS_HEIGHT) # Register event handlers for "control" elements and # frame buttons to "restart" and if necessary "switch" # the mode of the game. Once the game is over, you should # hit the "Reset" button to restart the game. frame.add_button("Reset", new_game) frame.add_label("") label = frame.add_label("Turns = 0") frame.add_label("") switch_game_mode_button = frame.add_button("Reset and Play with images", switch_game_mode, 200) # Register "event handler" that is responsible for the # management of the mouse clicks on the "canvas". frame.set_mouseclick_handler(mouseclick) # Register the "event handler" that is responsible # for all drawing. frame.set_draw_handler(draw) # Call "new_game()" ensuring that all variables are # always initialized when the program starts running. new_game() # Start frame. frame.start() #--------------------------------------------------------- -- click to play "Memory" @ http://www.codeskulptor.org/#user41_yoFTVqEW70FhBKE.py
game description
-- click to show/hide source code # # Mini-project # 4: "Pong". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 4: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 19 Oct 2014 # Version: 11.0 # # Implementation of the classic arcade game "Pong". # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui # Import module, which contains functions that involve # randomness. import random #--------------------------------------------------------- # Define and initialize global constants. # Initialize global constants that will hold the "width" # and "height" of the "canvas" ("Pong" table). WIDTH = 600 HEIGHT = 400 # Initialize global constant that will hold the "radius" # of the ball. BALL_RADIUS = 20 # Initialize global constants that will hold the "width" # and "height" of the "paddles". PAD_WIDTH = 8 PAD_HEIGHT = 80 # as well as compute the "half" of those values. HALF_PAD_WIDTH = PAD_WIDTH / 2 HALF_PAD_HEIGHT = PAD_HEIGHT / 2 # Initialize global constants that will determine the # (horizontal) "direction" of the ball at the beginning # of a new game. LEFT = False RIGHT = True # Initialize global constants that will hold the "beginning" # and the "end" limits of "speed" ranges concerning the # horizontal and vertical "velocities", which will be # generated as random numbers within those boundaries # (pixels per update; 1/60 seconds) and according to the # guidelines of this project. BALL_VEL_x_RANGE_START = 120 BALL_VEL_x_RANGE_STOP = 240 BALL_VEL_y_RANGE_START = 60 BALL_VEL_y_RANGE_STOP = 180 # Initialize global constant that will hold the "acceleration" # of the (horizontal) ball "velocity". # Increase the difficulty of the game, by increasing the # "velocity" of the ball by 10% each time it strikes a # "paddle". BALL_VELOCITY_ACCELERATION = 1.1 # "Gutter" points. # Whenever ball touches "gutter", "Player" gets "points". POINTS = 1 # In this version of "Pong", the left and right "paddles" # move up and down respectively at a constant "velocity". VERTICAL_VELOCITY = 4 # Motion keys. # If the "w" or "s" key is pressed, move up or down left # "paddle". If the "up arrow" or "down arrow" key is # pressed, move up or down right "paddle". MOTION_KEYS = ["w", "s", "up", "down"] # Each new game should start after these seconds from the # time the "frame" opens or the time the "previous" game # ends. NEW_GAME_DELAY = 3 # Set general "draw" properties. COLOR = 'White' FONT_FACE = 'sans-serif' FONT_SIZE = 50 LINE_WIDTH = 1 #--------------------------------------------------------- # Define and initialize global variables. # Initialize global variable that will hold the horizontal # and vertical "position" as well as "velocity" for the # ball. Note: "velocity" = pixels per update; 1/60 seconds). ball_pos = [WIDTH / 2, HEIGHT / 2] ball_vel = [random.randrange(BALL_VEL_x_RANGE_START, BALL_VEL_x_RANGE_STOP) // 60, random.randrange(BALL_VEL_y_RANGE_START, BALL_VEL_y_RANGE_STOP) // 60] # Initialize global variables that will hold the vertical # "positions" of the two "paddles", i.e. the vertical # distance of the left and right "paddles" (centre of the # "paddles") from the top of the "canvas" (table). paddle1_pos = HEIGHT / 2 paddle2_pos = paddle1_pos # Initialize global variables that will hold the vertical # "velocities" of the "paddles". paddle1_vel = 0 paddle2_vel = 0 # Initialize global variables that will hold the scores # for each "Player". score1 = 0 score2 = 0 # Initialize global variable that will keep # track of the time in "seconds". seconds = 0 #--------------------------------------------------------- def spawn_ball(direction): """ Initialize ball "position" and "velocity" for new ball in the middle of the table. If "direction" is "RIGHT", the ball's "velocity" is upper right, else upper left. """ # These are vectors stored as (global) "[x,y]" lists; # ball "position" and "velocity". global ball_pos, ball_vel # Set ball "position" at the middle of the table. ball_pos = [WIDTH / 2, HEIGHT / 2] # Randomization to the "velocity". The exact values for # the horizontal and vertical components of this # "velocity" should be generated using "random.randrange()". # This function returns a random integer "n" such that # "start <= n < stop". For the horizontal and vertical # "velocities", we generate a random number within the # suggested "speed" limits (pixels per update; # 1/60 seconds). ball_vel[0] = random.randrange(BALL_VEL_x_RANGE_START, BALL_VEL_x_RANGE_STOP) // 60 ball_vel[1] = random.randrange(BALL_VEL_y_RANGE_START, BALL_VEL_y_RANGE_STOP) // 60 # The velocity of the ball should be upwards and towards # the right. if direction == RIGHT: ball_vel = [ball_vel[0], -ball_vel[1]] # The velocity of the ball should be upwards and towards # the left. else: ball_vel = [-ball_vel[0], -ball_vel[1]] return None #--------------------------------------------------------- def new_game(): """ Initialize a new game by reseting the vertical "positions" and "velocities" of the "paddles" as well as the "score" of each "Player". Call "spawn_ball()" to initialize "position" and "velocity" for new ball. Start also a timer, which will "postpone" the beginning of a new game by the configured ammount of time. """ # These are (global) numbers; vertical "position" of # each "paddle". global paddle1_pos, paddle2_pos # These are (global) numbers; vertical "velocity" of # each "paddle". global paddle1_vel, paddle2_vel # These are (global) numbers; "score" of each # "Player". global score1, score2 # Reset vertical positions of the two "paddles" # (as global variables). paddle1_pos = HEIGHT / 2 paddle2_pos = paddle1_pos # Reset vertical "velocities" of the two "paddles" # (as global variables). paddle1_vel = 0 paddle2_vel = 0 # Reset "Player" scores (as global variables). score1 = 0 score2 = 0 # Check if "Timer" is Running; if not, start the "Timer". if not timer.is_running(): timer.start() # Start a game of "Pong". spawn_ball(RIGHT) return None #--------------------------------------------------------- def draw_handler(canvas): """ Event handler that is responsible for all drawing. It receives "canvas" object and draws the "Pong" table, the "moving" ball and the scores of each "Player". It is also responsible for testing whether the ball touches/collides with the "gutters" or the "paddles". """ # These are (global) numbers; vertical "position" of # each "paddle". global paddle1_pos, paddle2_pos # These are (global) numbers; "score" of each "Player". global score1, score2 # These are vectors stored as (global) "[x,y]" lists; # ball "position" and "velocity". global ball_pos, ball_vel # This is (global) number; keeps track of the time in # "seconds". global seconds # Draw middle line and "gutters" of "Pong" table. canvas.draw_line([WIDTH / 2, 0], [WIDTH / 2, HEIGHT], LINE_WIDTH, COLOR) canvas.draw_line([PAD_WIDTH, 0], [PAD_WIDTH, HEIGHT], LINE_WIDTH, COLOR) canvas.draw_line([WIDTH - PAD_WIDTH, 0], [WIDTH - PAD_WIDTH, HEIGHT], LINE_WIDTH, COLOR) # "Postpone" the beginning of new game if "Timer" is # already running by "reseting" ball "position" at the # middle of the table. if timer.is_running(): ball_pos = [WIDTH / 2, HEIGHT / 2] # Print message about the remaining time until the # beginning of the new game by referencing the # global "seconds" counter. canvas.draw_text("new game will start in " + str(NEW_GAME_DELAY - seconds) + " seconds" + ("." * (NEW_GAME_DELAY - seconds)), [WIDTH // 12, 3 * HEIGHT // 4], 3 * FONT_SIZE // 10, COLOR, FONT_FACE) else: # "Timer" has expired; update ball "position" for # the new game. ball_pos[0] += ball_vel[0] ball_pos[1] += ball_vel[1] # Test whether the ball touches/collides with the left # "gutter" (offset from the left edge of the "canvas" # by the width of the "paddle"). if ball_pos[0] <= (BALL_RADIUS + PAD_WIDTH): # Check whether the ball is actually striking left # "paddle" when it touches left "gutter". If so, # reflect the ball back into play; ball's "velocity" # increased by the "acceleration" configured. if ((paddle1_pos - HALF_PAD_HEIGHT) <= ball_pos[1] <= (paddle1_pos + HALF_PAD_HEIGHT)): ball_vel[0] = -ball_vel[0] * BALL_VELOCITY_ACCELERATION else: # Ball touched "gutter". Respawn the ball in # the center of the table headed towards the # opposite "gutter" and of course update score # of "Player" 2 (right) by the "points" # configured. score2 += POINTS # Start a game of "Pong". Start also a "Timer" # to "postpone" the beginning of the new game. if not timer.is_running(): timer.start() spawn_ball(RIGHT) # Test whether the ball touches/collides with the right # "gutter" (offset from the right edge of the "canvas" # by the width of the "paddle"). elif ball_pos[0] >= ((WIDTH - 1) - BALL_RADIUS - PAD_WIDTH): # Check whether the ball is actually striking right # "paddle" when it touches right "gutter". If so, # reflect the ball back into play; ball's "velocity" # increased by the "acceleration" configured. if ((paddle2_pos - HALF_PAD_HEIGHT) <= ball_pos[1] <= (paddle2_pos + HALF_PAD_HEIGHT)): ball_vel[0] = -ball_vel[0] * BALL_VELOCITY_ACCELERATION else: # Ball touched "gutter". Respawn the ball in # the center of the table headed towards the # opposite "gutter" and of course update score # of "Player" 1 (left) by the "points" # configured. score1 += POINTS # Start a game of "Pong". Start also a "Timer" # to "postpone" the beginning of the new game. if not timer.is_running(): timer.start() spawn_ball(LEFT) # Collide and reflect off of top side of the "canvas". elif ball_pos[1] <= BALL_RADIUS: ball_vel[1] = -ball_vel[1] # Collide and reflect off of bottom side of the "canvas". elif ball_pos[1] >= ((HEIGHT - 1) - BALL_RADIUS): ball_vel[1] = -ball_vel[1] # Draw a ball moving across the "Pong" table. canvas.draw_circle(ball_pos, BALL_RADIUS, 2 * LINE_WIDTH, COLOR, COLOR) # Update paddle's vertical "position", by # referencing the two global variables that contain the # vertical "velocities" of the "paddle". Keep "paddle" # on the screen by calling the proper "helper" function. if keep_paddle_on_screen(paddle1_pos, paddle1_vel): paddle1_pos += paddle1_vel if keep_paddle_on_screen(paddle2_pos, paddle2_vel): paddle2_pos += paddle2_vel # Draw left and right "paddles" in their respective # "gutters". canvas.draw_polygon([[0, paddle1_pos - HALF_PAD_HEIGHT], [PAD_WIDTH, paddle1_pos - HALF_PAD_HEIGHT], [PAD_WIDTH, paddle1_pos + HALF_PAD_HEIGHT], [0, paddle1_pos + HALF_PAD_HEIGHT]], LINE_WIDTH, COLOR, COLOR) canvas.draw_polygon([[WIDTH - PAD_WIDTH, paddle2_pos - HALF_PAD_HEIGHT], [WIDTH , paddle2_pos - HALF_PAD_HEIGHT], [WIDTH, paddle2_pos + HALF_PAD_HEIGHT], [WIDTH - PAD_WIDTH, paddle2_pos + HALF_PAD_HEIGHT]], LINE_WIDTH, COLOR, COLOR) # Draw scores; # but first get the width of the "score" text in pixels # for each "Player"; useful in (later) computing the # position to draw the "score" text - centered justified # on the "canvas field" of each player. score_textwidth_in_px = frame.get_canvas_textwidth(str(score1), FONT_SIZE, FONT_FACE) score_point_x = (WIDTH // 4) - (score_textwidth_in_px // 2) score_point_y = (HEIGHT // 4) canvas.draw_text(str(score1), [score_point_x, score_point_y], FONT_SIZE, COLOR, FONT_FACE) score_textwidth_in_px = frame.get_canvas_textwidth(str(score2), FONT_SIZE, FONT_FACE) score_point_x = (3 * WIDTH // 4) - (score_textwidth_in_px // 2) score_point_y = (HEIGHT // 4) canvas.draw_text(str(score2), [score_point_x, score_point_y], FONT_SIZE, COLOR, FONT_FACE) return None #--------------------------------------------------------- def keep_paddle_on_screen(paddle_pos, paddle_vel): """ Helper function that restrict "paddle" to stay entirely on the "canvas" by testing whether the current update for a paddle's "position" will move part of the "paddle" off of the screen. If it does, don't allow the update by returning a "False" boolean value; else return "True". Function accepts current paddle's "vertical position" and "vertical velocity". """ # Compute updated (future) position of the "paddle". paddle_pos_updated = paddle_pos + paddle_vel # "Paddle" will be "off" (False) or "on" (True) screen. if (HALF_PAD_HEIGHT <= paddle_pos_updated <= (HEIGHT - HALF_PAD_HEIGHT)): return True else: return False #--------------------------------------------------------- def keydown_handler(key): """ Event key handler. Update the values of the two vertical "velocities" using this "key" handler. If key is pressed, "paddle" will start to move up or down at a constant "velocity". When key is released, "paddle" will stop moving. """ # These are (global) numbers; vertical "velocity" of # each "paddle". global paddle1_vel, paddle2_vel # The "w" and "s" keys should control the vertical # "velocity" of the left "paddle" while the "Up arrow" # and "Down arrow" key should control the "velocity" of # the right "paddle". if key == simplegui.KEY_MAP[MOTION_KEYS[0]]: # If the "w" key is pressed, move up left # "paddle". paddle1_vel -= VERTICAL_VELOCITY if key == simplegui.KEY_MAP[MOTION_KEYS[1]]: # If the "s" key is pressed, move down left # "paddle". paddle1_vel += VERTICAL_VELOCITY if key == simplegui.KEY_MAP[MOTION_KEYS[2]]: # If the "Up arrow" key is pressed, move up right # "paddle". paddle2_vel -= VERTICAL_VELOCITY if key == simplegui.KEY_MAP[MOTION_KEYS[3]]: # If the "Down arrow" key is pressed, move down # right "paddle". paddle2_vel += VERTICAL_VELOCITY # else motionless if none of the above keys is pressed. return None #--------------------------------------------------------- def keyup_handler(key): """ Event key handler. Update the values of the two vertical "velocities" using this "key" handler. If key is pressed, "paddle" will start to move up or down at a constant "velocity". When key is released, "paddle" will stop moving. """ # These are (global) numbers; vertical "velocity" of # each "paddle". global paddle1_vel, paddle2_vel # The "w" and "s" keys should control the vertical # "velocity" of the left "paddle" while the "Up arrow" # and "Down arrow" key should control the "velocity" of # the right "paddle". if key == simplegui.KEY_MAP[MOTION_KEYS[0]]: # If the "w" key is released, stop moving up left # "paddle". paddle1_vel = 0 if key == simplegui.KEY_MAP[MOTION_KEYS[1]]: # If the "s" key is released, stop moving down left # "paddle". paddle1_vel = 0 if key == simplegui.KEY_MAP[MOTION_KEYS[2]]: # If the "Up arrow" key is released, stop moving up # right "paddle". paddle2_vel = 0 if key == simplegui.KEY_MAP[MOTION_KEYS[3]]: # If the "Down arrow" key is released, stop moving # down right "paddle". paddle2_vel = 0 return None #--------------------------------------------------------- # def button_restart_hander(): """ Event button handler. Call "new_game()" to reset the "paddles", "score" and relaunch the ball. Reset also a "Timer", which will "postpone" the beginning of a new game by the configured ammount of time. """ # This is (global) number; keeps track of the time in # "seconds". global seconds # Check if "Timer" is Running; if yes, stop the "Timer" # and reset global "seconds" counter. if timer.is_running(): seconds = 0 timer.stop() # Reset the "paddles", "score", relaunch the ball and # start the "Timer". new_game() return None #--------------------------------------------------------- def timer_handler(): """ Event handler for "Timer" with 1 sec interval, which increments or resets a global "time" counter. """ # Increment "seconds" (as global variable) # by one each time "Timer" calls this "event handler"; # i.e. once per 1 second. global seconds seconds += 1 # In case where "seconds" counter gets greater than the # number of seconds configured as the "delay" # between two successive games, reset counter and stop # the "Timer". if seconds > NEW_GAME_DELAY: seconds = 0 timer.stop() return None #--------------------------------------------------------- # Create frame. frame = simplegui.create_frame("Pong", WIDTH, HEIGHT) # Create a "Timer" by repeatedly calling the proper # "event handler" every 1 second. timer = simplegui.create_timer(1000, timer_handler) # Register the "event handler" that is responsible # for all drawing. frame.set_draw_handler(draw_handler) # Register "event handlers" for "control" elements. frame.set_keydown_handler(keydown_handler) frame.set_keyup_handler(keyup_handler) frame.add_button("Restart", button_restart_hander, 200) # Get a game going (almost) immediately. new_game() # Start frame. frame.start() #--------------------------------------------------------- -- click to play "Pong" @ http://www.codeskulptor.org/#user38_vH3nXtvB8jHAOdH.py
game description
-- click to show/hide source code # # Mini-project # 3: "Stopwatch". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 3: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 12 Oct 2014 # Version: 4.0 # # A simple digital "stopwatch" that keeps track of the "time" # in "tenths of a second". The "stopwatch" contains "Start", # "Stop" and "Reset" buttons. # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui #--------------------------------------------------------- # Define and initialize global variables. # Initialize global variables that will hold the "width" # and "height" of the "canvas". canvas_width = 300 canvas_heigth = 200 # Initialize global integer variable that will keep # track of the time in "tenths of seconds". The "stopwatch" # should be stopped when the frame opens. tenths_of_seconds = 0 # Initialize global variable that will keep track of the # number of times the "watch" stopped on a whole second # "successfully" (1.0, 2.0, 3.0, etc.). successful_stops = 0 # Initialize global variable that will keep track of the # "total" number of times the "watch" stopped. total_stops = 0 #--------------------------------------------------------- def format(time): """ Helper function that converts "time" in "tenths of seconds" into a formatted string in the following form: "A:BC.D", where "A", "C" and "D" are digits in the range "0-9" and "B" is in the range "0-5". Note: "stopwatch" need only work correctly up to 10 minutes. """ # Compute "tenths of a second", "seconds" and # "minutes" by using integer division and remainder # (modular arithmetic) to extract various digits for # the formatted "time" from the global integer "Timer". # Convert them into strings finally. tenths_of_seconds = str(time % 10) seconds = str((time // 10) % 60) minutes = str((time // 600) % 60) # The string returned must always correctly include # leading zeros (if necessary). For example: # format(0) = 0:00.0 | format(11) = 0:01.1 # format(321) = 0:32.1 | format(613) = 1:01.3 if len(seconds) == 1: seconds = "0" + seconds # Return the formatted "time" string. return str(minutes) + ":" + str(seconds) + "." + str(tenths_of_seconds) #--------------------------------------------------------- def button_start_hander(): """ Event handler for button "Start"; start the "Timer". """ # Check if "Timer" is Running; if not, start the "Timer". if not timer.is_running(): timer.start() return None #--------------------------------------------------------- def button_stop_hander(): """ Event handler for button "Stop"; stop the "Timer" and update the "stop-counters", that keep track of the number of "successful stops" and the "total" number of those ones. """ # Check if "Timer" is Running; if yes, stop the "Timer". if timer.is_running(): timer.stop() # Update "stop-counters" (as global variables); i.e # increment "total stops" by one as well as the # number of "successful stops" if and only if # "watch" stopped on a whole second, by checking # if the "tenths of the current second" is "0" # in the formatted "time" string. global total_stops, successful_stops total_stops += 1 if (format(tenths_of_seconds)[-1] == "0"): successful_stops += 1 return None #--------------------------------------------------------- def button_reset_hander(): """ Event handler for button "Reset"; stop the "Timer" and reset the current "time" to zero. Reset also the "stop-counters", that keep track of the number of "successful stops" and the "total" number of those ones. """ # Call "event handler" for button "Stop", stopping the # "Timer". button_stop_hander() # Reset the "Timer", i.e. in practice reset the # variable that keeps track of the time in "tenths of # seconds" as well as the "stop-counters" # (as global variables). global tenths_of_seconds, total_stops, successful_stops tenths_of_seconds = 0 total_stops = 0 successful_stops = 0 return None #--------------------------------------------------------- def timer_handler(): """ Event handler for "Timer" with 0.1 sec interval, which "simply" increments a global integer. """ # Increment "tenths_of_seconds" (as global variable) # by one each time "Timer" calls this "event handler"; i.e # once per 0.1 seconds. global tenths_of_seconds tenths_of_seconds += 1 return None #--------------------------------------------------------- def draw_handler(canvas): """ Event handler that is responsible for all drawing. It receives "canvas" object and draws the current "time" in the middle of the "canvas" as well as the "stop- counters" in the upper right-hand part of the "stopwatch canvas". """ # Set general "draw" properties. color = 'White' font_face = 'sans-serif' # Call function "format()" to convert "time" in "tenths # of seconds" into a formatted string in the following # form: "A:BC.D". time = format(tenths_of_seconds) # Set specific "draw" properties for the "time" text. font_size = 50 margin_y = 14 # Get the width of the "time" text in pixels; useful # in (later) computing the position to draw the "time" # text - centered justified on "canvas". time_textwidth_in_px = frame.get_canvas_textwidth(time, font_size, font_face) time_point_x = (canvas_width // 2) - (time_textwidth_in_px // 2) time_point_y = (canvas_heigth // 2) + margin_y # Draw "time". canvas.draw_text(time, [time_point_x, time_point_y], font_size, color, font_face) # Build string representing the "success" rate of # watch "stops" by concatenating the number of "successful # stops" and the number of "total stops". These counters # will be drawn in the form "x/y", where "x" is the # number of "successful stops" and "y" is number of # "total stops". stops = str(successful_stops) + "/" + str(total_stops) # Set specific "draw" properties for the "stop-counters". font_size = 25 margin_x = 25 margin_y = 30 # Get the width of the "stops" text in pixels; useful # in (later) computing the position to draw the text of # the "success" rate of "stops" on "canvas". stops_textwidth_in_px = frame.get_canvas_textwidth(stops, font_size, font_face) stops_point_x = canvas_width - stops_textwidth_in_px - margin_x stops_point_y = margin_y # Draw "success" rate of "stops". canvas.draw_text(stops, [stops_point_x, stops_point_y], font_size, color, font_face) return None #--------------------------------------------------------- # Create frame. frame = simplegui.create_frame('Stopwatch', canvas_width, canvas_heigth) # Create a timer repeatedly calling the proper "event # handler" every 0.1 seconds. timer = simplegui.create_timer(100, timer_handler) # Register the "event handler" that is responsible # for all drawing. frame.set_draw_handler(draw_handler) # Register "event handlers" for "control" elements. frame.add_button("Start", button_start_hander, 200) frame.add_button("Stop", button_stop_hander, 200) frame.add_button("Reset", button_reset_hander, 200) # Start frame. frame.start() ########################################################## # Test code by calling the function "format()" # repeatedly in the program with different "time" (tenths # of seconds) as its input arguments. # Uncomment each sequence of calls and check whether the # output in the console matches that provided in the # comments below. # Note that function should always return a string with # six characters. # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-format_template.py #--------------------------------------------------------- # Test. #print format(0) #print format(7) #print format(17) #print format(60) #print format(63) #print format(214) #print format(599) #print format(600) #print format(602) #print format(667) #print format(1325) #print format(4567) #print format(5999) #--------------------------------------------------------- # Output from test. #0:00.0 #0:00.7 #0:01.7 #0:06.0 #0:06.3 #0:21.4 #0:59.9 #1:00.0 #1:00.2 #1:06.7 #2:12.5 #7:36.7 #9:59.9 ########################################################## -- click to play "Stopwatch" @ http://www.codeskulptor.org/#user38_HNRcSRMHWTveYXi.py
game description
Computer's responses will be printed in the console. Just click on the "run" upper left button on "CodeSkulptor" to start the program. -- click to show/hide source code # # Mini-project # 2: "Guess the number". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 2: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 5 Oct 2014 # Version: 6.0 # # Two-player game: the first player thinks of a "secret" # number in some known "range", while the second player # attempts to "guess" the number. After each "guess", the # first player answers either "Higher", "Lower" or # "Correct" depending on whether the "secret" number is # higher, lower or equal to the guess. Player is restricted # to a limited number of guesses. # In this project, an interactive program has been built # where the "computer" takes the role of the first # player. A "user" plays as the second player, # interacting with the program using an "input" field and # several buttons. Computer's responses will be printed # in the console. # #--------------------------------------------------------- # Import the "simple gui" module. import simplegui # Import module that contains functions, which involve # randomness. import random # Import module that contains additional mathematical # operations. import math #--------------------------------------------------------- # Initialize global variables. # Initialize global variable that will hold the upper # limit of the "range" of numbers of the game in progress. # Note: when program starts, the game should immediately # begin in range [0, 100). num_range = 100 # Initialize global variable that will hold the input # "guess" of the game in progress. guess_number = -1 # Initialize global variable that will hold the random # number in the "range" of numbers of the game in progress. secret_number = 0 # Initialize global variable that will hold the "remaining # guesses" of the game in progress. remaining_guesses = 0 #--------------------------------------------------------- def new_game(): """ Helper function that starts and restarts the game. """ # Initialize global variable "secret_number" # to be a random number in the range [0, <num_range>). # Function "random.randrange()" returns a random # integer "n" such that "start <= n < stop". global secret_number secret_number = random.randrange(0, num_range) # Player is restricted to a limited number of guesses. # Compute global variable "remaining_guesses" based upon # the desired "num_range" of the game in progress; # once the player has used up those guesses, they lose. # Note: a "binary search" strategy for playing # the game, approximately halves the range of possible # "secret" numbers after each guess. So any "secret" # number in the range [low, high) can always be found in # at most "n" guesses, where "n" is the smallest integer # such that "2 ^ n >= high - low + 1". global remaining_guesses remaining_guesses = math.log(num_range - 0 + 1) / math.log(2) remaining_guesses = int(math.ceil(remaining_guesses)) # Print-out a blank line to separate consecutive games. print "" # Print-out proper message announcing the beginning of # a new game, the desired "range" of numbers and the # number of "remaining guesses". print "New game. Range is from 0 to " + str(num_range) print "Number of remaining guesses is " + str(remaining_guesses) # Enable this if in "Debug" mode. #print "Secret number is " + str(secret_number) return None #--------------------------------------------------------- def range100(): """ Event handler for "button" that changes the "range" to [0,100) and starts a new game. Whenever Player clicks this "range" button, the current game stops and a new game with the selected range begins. """ # Set the desired range for the "secret" number (as a # global variable). global num_range num_range = 100 # Call "new_game()" to reset the "secret" number in the # desired range. new_game() return None #--------------------------------------------------------- def range1000(): """ Event handler for "button" that changes the "range" to [0,1000) and starts a new game. Whenever Player clicks this "range" button, the current game stops and a new game with the selected range begins. """ # Set the desired range for the "secret" number (as a # global variable). global num_range num_range = 1000 # Call "new_game()" to reset the "secret" number in the # desired range. new_game() return None #--------------------------------------------------------- def input_guess(guess): """ Event hanlder for the "input" field; used to enter "guesses". It takes the input string "guess", converts it to an integer, and prints out a message of the form "Guess was <guess>". It also compares the entered number to "secret_number" and prints out appropriate message. Finally, it may begin a new game if Player has either won or run out of guesses. """ # Initialize boolean variable; if player either wins or # runs out of guesses, reset variable to "True"; # i.e. start a new game. start_new_game = False # Store input "guess" into "guess_number" (as a # global variable) and print-out proper message. # Note: no need to validate that the "input" number is # in the correct "range" for the purposes of this project; # that responsibility falls on the player. global guess_number guess_number = int(guess) print "" print "Guess was " + guess # Recompute "remaining_guesses" (as a global variable) and # print-out proper message. global remaining_guesses remaining_guesses = remaining_guesses - 1 print "Number of remaining guesses is " + str(remaining_guesses) # Once player has used up all guesses, a new game # begins "immediately" after this try. if (remaining_guesses == 0): start_new_game = True # Compare the entered number to "secret_number" # and print out an appropriate message such as # "Higher", "Lower", or "Correct", etc. if (secret_number < guess_number): if (remaining_guesses > 0): print "Lower!" else: print "You ran out of guesses. The number was " + str(secret_number) elif (secret_number > guess_number): if (remaining_guesses > 0): print "Higher!" else: print "You ran out of guesses. The number was " + str(secret_number) else: print "Correct!" # Player has won this game; start a new one # "immediately" after this try. start_new_game = True # Player has either won or run out of guesses; # a new game with the same "range" as the last one # immediately begins by calling "new_game()". if (start_new_game): new_game() return None #--------------------------------------------------------- # Create frame. frame = simplegui.create_frame("Guess the number", 200, 200) # Register event handlers for control elements and start # frame buttons to restart the game allowing the player to # choose two different ranges for the "secret" number. frame.add_button("Range: 0 - 100", range100, 200) frame.add_button("Range: 0 - 1000", range1000, 200) frame.add_input("Enter a guess", input_guess, 200) # Start frame. frame.start() # Call "new_game()" ensuring that "secret_number" variable # is always initialized when the program starts running. new_game() ########################################################## # Test code by calling the function "input_guess()" # repeatedly in the program with different player choices # as its input arguments. Uncomment each sequence of calls # and check whether the output in the console matches that # provided in the comments below. # Ref: testing template found at the following URL: # http://www.codeskulptor.org/#examples-gtn_testing_template.py #--------------------------------------------------------- #--------------------------------------------------------- # Test # 1. #secret_number = 74 #input_guess("50") #input_guess("75") #input_guess("62") #input_guess("68") #input_guess("71") #input_guess("73") #input_guess("74") #--------------------------------------------------------- # Output from test # 1. #New game. Range is from 0 to 100 #Number of remaining guesses is 7 # #Guess was 50 #Number of remaining guesses is 6 #Higher! # #Guess was 75 #Number of remaining guesses is 5 #Lower! # #Guess was 62 #Number of remaining guesses is 4 #Higher! # #Guess was 68 #Number of remaining guesses is 3 #Higher! # #Guess was 71 #Number of remaining guesses is 2 #Higher! # #Guess was 73 #Number of remaining guesses is 1 #Higher! # #Guess was 74 #Number of remaining guesses is 0 #Correct! # #New game. Range is from 0 to 100 #Number of remaining guesses is 7 #--------------------------------------------------------- #--------------------------------------------------------- # Test # 2. #range1000() #secret_number = 375 #input_guess("500") #input_guess("250") #input_guess("375") #--------------------------------------------------------- # Output from test # 2. #New game. Range is from 0 to 100 #Number of remaining guesses is 7 # #New game. Range is from 0 to 1000 #Number of remaining guesses is 10 # #Guess was 500 #Number of remaining guesses is 9 #Lower! # #Guess was 250 #Number of remaining guesses is 8 #Higher! # #Guess was 375 #Number of remaining guesses is 7 #Correct! # #New game. Range is from 0 to 1000 #Number of remaining guesses is 10 #--------------------------------------------------------- #--------------------------------------------------------- # Test # 3. #secret_number = 28 #input_guess("50") #input_guess("50") #input_guess("50") #input_guess("50") #input_guess("50") #input_guess("50") #input_guess("50") #--------------------------------------------------------- # Output from test # 3. #New game. Range is from 0 to 100 #Number of remaining guesses is 7 # #Guess was 50 #Number of remaining guesses is 6 #Lower! # #Guess was 50 #Number of remaining guesses is 5 #Lower! # #Guess was 50 #Number of remaining guesses is 4 #Lower! # #Guess was 50 #Number of remaining guesses is 3 #Lower! # #Guess was 50 #Number of remaining guesses is 2 #Lower! # #Guess was 50 #Number of remaining guesses is 1 #Lower! # #Guess was 50 #Number of remaining guesses is 0 #You ran out of guesses. The number was 28 # #New game. Range is from 0 to 100 #Number of remaining guesses is 7 ########################################################## -- click to play "Guess the Number" @ http://www.codeskulptor.org/#user38_qUSUQsJHQTbf1Yf.py
game description
Each choice wins against the preceding two choices and loses against the following two choices (if "rock" and "scissors" are thought of as being adjacent using "modular arithmetic"). how to play Just click on the "run" upper left button on "CodeSkulptor" and the program will then simulate playing a round of "Rock-paper-scissors-lizard-Spock" for each of the five possible player choices, generating its own (computer) random choice from any of the above mentioned five alternatives and then determining the winner using a simple rule (as described in the above mentioned description of the game). -- click to show/hide source code # # Mini-project # 1: "Rock-paper-scissors-lizard-Spock". # # Author: Aristotelis Metsinis # Email: aristotelis.metsinis@gmail.com # Mini-project # 1: An Introduction to Interactive Programming in Python # @ https://www.coursera.org/course/interactivepython # Date: 28 Sep 2014 # Version: 6.0 # # "Rock-paper-scissors-lizard-Spock" (RPSLS) is a variant of # "Rock-paper-scissors" that allows five choices. Each choice wins # against two other choices, loses against two other choices and # ties against itself. # # The key idea of this program is to equate the strings # "rock", "paper", "scissors", "lizard", "Spock" to numbers # as follows: # # 0 - rock # 1 - Spock # 2 - paper # 3 - lizard # 4 - scissors # # Each choice wins against the preceding two choices and loses against # the following two choices (if "rock" and "scissors" are thought of as # being adjacent using "modular arithmetic"). # #--------------------------------------------------------- # Import module that contains functions, which involve # randomness. import random #--------------------------------------------------------- def name_to_number(name): """ Helper function that converts the string "name" into a "number" between "0" and "4" in the way described below. 0 - rock 1 - Spock 2 - paper 3 - lizard 4 - scissors """ # Define a variable that will hold the computed "number" # and initialise it. Normally, after the execution of this # function, "number" should have one of the following # values: 0,1,2,3,4 depending on player's choice, or "-1" # case of an invalid input. number = -1 # A sequence of "if/elif/else" clauses should be used # checking for conditions of the form # name == '<player_choice>' to distinguish the cases. # A final "else" clause catches cases when "name" # does not match any of the five correct input strings. if ( name == 'rock' ): number = 0 elif ( name == 'Spock' ): number = 1 elif ( name == 'paper' ): number = 2 elif ( name == 'lizard' ): number = 3 elif ( name == 'scissors' ): number = 4 else: number = -1 # Return the computed "number". return number #--------------------------------------------------------- def number_to_name(number): """ Helper function that converts a "number" in the range "0" to "4" into its corresponding (string) "name" in the way described below. 0 - rock 1 - Spock 2 - paper 3 - lizard 4 - scissors """ # Define a variable that will hold the "name" # and initialise it. Normally, after the execution of # this function, "name" should have one of the # following values: "rock", "Spock", "paper", "lizard", "scissors" # depending on the input "number", or "" in case of # a "number" not in the correct range. name = "" # A sequence of "if/elif/else" clauses should be used # checking for conditions of the form # number == <input_value> to distinguish the cases. # A final "else" clause catches cases when "number" is # not in the correct range. if ( number == 0 ): name = "rock" elif ( number == 1 ): name = "Spock" elif ( number == 2 ): name = "paper" elif ( number == 3 ): name = "lizard" elif ( number == 4 ): name = "scissors" else: name = "" # Return "name". return name #--------------------------------------------------------- def rpsls(player_choice): """ Main function that takes as input the string "player_choice", which normally should be one of "rock", "paper", "scissors", "lizard", or "Spock". The function then simulates playing a round of "Rock-paper-scissors-lizard-Spock" by generating its own random choice from these alternatives and then determining the winner using a simple rule (as described in the comments found at the top of this program). """ # Print-out a blank line to separate consecutive games. print "" # Print-out appropriate message describing player's choice. print "Player chooses " + player_choice # Compute the number "player_number" between "0" and "4" # corresponding to player's choice by calling the # helper function "name_to_number() using "player_choice". player_number = name_to_number( player_choice ) # Confirm that player's choice is valid (i.e. within the [0,4] # range); if not print proper "error" message. if (player_number >= 0) and (player_number <= 4): # Generate computer's guess by computing a random number # "comp_number" between "0" and "4" that corresponds to # computer's guess using function "random.randrange()" that # returns a random integer "n" such that "start <= n < stop". comp_number = random.randrange(0,5) # Confirm that computers's choice is valid (i.e. within the # [0,4] range); if not print proper "error" message. if (comp_number >= 0) and (comp_number <= 4): # Print-out appropriate message for that guess by # computing the name "comp_name" corresponding to # computer's number using the function "number_to_name()" comp_name = number_to_name(comp_number) print "Computer chooses " + comp_name # Determine the winner by computing the # difference between "player_number" and "comp_number" # taken modulo five and using "if/elif/else" statement # whose conditions test the various possible values of this # difference. Then print an appropriate message # concerning the winner. diff = ( player_number - comp_number ) % 5 # "diff" is either "1" or "2" then "player" wins. if (diff == 1) or (diff == 2): print "Player wins!" # else if "diff" is either "3" or "4" then "computer" wins. elif (diff == 3) or (diff == 4): print "Computer wins!" # else "tie" betwen "player" and "computer". else: print "Player and computer tie!" else: print "Error: invalid computer's choice!" else: print "Error: invalid player's choice!" return None #--------------------------------------------------------- # Test code by calling the main function "rpsls()" repeatedly in the program # with different player choices as its input arguments, generating different # computer guesses and different winners each time. rpsls("rock") rpsls("Spock") rpsls("paper") rpsls("lizard") rpsls("scissors") #--------------------------------------------------------- |