├── .gitignore ├── README.md ├── classic-games ├── README.md └── hello_class │ ├── README.md │ ├── classes.py │ └── game.py └── tools ├── README.md └── pquiz ├── README └── pquiz.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Udacity student projects 2 | ============ 3 | 4 | Projects created by students of Udacity, the XXI century university. 5 | 6 | 7 | classic-games 8 | ============= 9 | 10 | Student created games, written while learning object oriented programming at Udacity 11 | 12 | 13 | tools 14 | ============= 15 | 16 | Tools and addons that improve usability 17 | 18 | -------------------------------------------------------------------------------- /classic-games/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytonc/udacity/766bef50ac9bfe74410ad3a898d8654b319a5c82/classic-games/README.md -------------------------------------------------------------------------------- /classic-games/hello_class/README.md: -------------------------------------------------------------------------------- 1 | Simple game to learn Object Oriented programming concepts 2 | 3 | Run by issuing a command : python game.py 4 | 5 | There are currently several development branches that you can check out: 6 | Some of them need additional graphic libraries (PyGame and PIL) 7 | 8 | GUI mods: 9 | -------- 10 | 11 | git checkout kaprice 12 | python gfx.py 13 | 14 | git checkout gdimike 15 | python game.py 16 | 17 | git checkout piffio 18 | python game.py -g sdl # or '-g txt' - choice of gui 19 | 20 | Functionality mods: 21 | ------------------ 22 | 23 | git checkout heinzema # added Equipable Items and Chests 24 | python game.py 25 | 26 | git checkout alexk # magic and AI related improvements and some additions, 27 | # which make the game nicer(Castle, Buttrefly, Fountains, Trees etc.). 28 | python game_Tkinter.py # ANCII and PyGame versions aren't supported yet, but Tkinter looks and works pretty nice. 29 | 30 | -------------------------------------------------------------------------------- /classic-games/hello_class/classes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # game.py - simple game to demonstrate classes and objects 3 | import random 4 | 5 | CHR_PLAYER = "S" 6 | CHR_ENEMY = "B" 7 | CHR_WIZARD = "W" 8 | CHR_ARCHER = "A" 9 | CHR_DEAD = "X" 10 | 11 | class StatusBar(object): 12 | def __init__(self, character = None): 13 | self.character = character 14 | self.msg = '' 15 | 16 | def set_character(self, character): 17 | self.character = character 18 | self.set_status() 19 | self.show() 20 | 21 | def set_status(self, msg = ''): 22 | self.msg = (msg, '::'.join((self.msg, msg)))[len(self.msg) > 0] 23 | status = "HP: %i/%i" % (self.character.hp, self.character.max_hp) 24 | msgs = self.msg.split('::') 25 | 26 | self.line1 = "%s + %s" % (status, msgs[0]) 27 | if len(msgs) > 1: 28 | self.line2 = "%s + %s" % (' ' * len(status), msgs[1]) 29 | else: 30 | self.line2 = "%s + %s" % (' ' * len(status), ' ' * len(msgs[0])) 31 | 32 | def format_line(self, txt, width): 33 | line = "+ %s" % txt 34 | line += " " * (width - (len(line))) + " +" 35 | return line 36 | 37 | def show(self): 38 | self.set_status() 39 | print "+" * (world.width + 2) 40 | print self.format_line(self.line1, world.width) 41 | print self.format_line(self.line2, world.width) 42 | self.msg = '' 43 | 44 | statusbar = StatusBar() 45 | 46 | class WorldMap(object): 47 | def __init__(self, width, height): 48 | self.width = width 49 | self.height = height 50 | self.map = [[None for y in range(self.height)] for x in range(self.width)] 51 | 52 | def is_occupied(self, x, y): 53 | ''' Checks if a given space on the map and returns True if occupied. ''' 54 | return self.map[x][y] is not None 55 | 56 | def print_map(self): 57 | print '+' * (self.width + 2) 58 | for y in range(self.height - 1, 0, -1): 59 | line = '+' 60 | for x in range(self.width): 61 | cell = self.map[x][y] 62 | if cell is None: 63 | line += ' ' 64 | else: 65 | line += cell.image 66 | print line + '+' 67 | print '+' * (self.width + 2) 68 | 69 | world = WorldMap(60, 22) 70 | 71 | #world = [[None for x in range(100)] for y in range(100)] 72 | 73 | class Entity: 74 | def __init__(self, x, y, image): 75 | self.x = x 76 | self.y = y 77 | world.map[x][y] = self 78 | self.image = image 79 | 80 | def occupy(self, x, y): 81 | world.map[x][y] = self 82 | 83 | def remove(self): 84 | world.map[self.x][self.y] = None 85 | 86 | def distance(self, other): 87 | return abs(other.x - self.x), abs(other.y - self.y) 88 | 89 | class Character(Entity): 90 | def __init__(self, x, y, image, hp, damage = 10): 91 | Entity.__init__(self, x, y, image) 92 | self.hp, self.max_hp = hp, hp 93 | self.damage = damage 94 | self.items = [] 95 | 96 | def _direction_to_dxdy(self, direction): 97 | """Convert a string representing movement direction into a tuple 98 | (dx, dy), where 'dx' is the size of step in the 'x' direction and 99 | 'dy' is the size of step in the 'y' direction.""" 100 | dx, dy = 0, 0 101 | if direction == 'left': 102 | dx = -1 103 | elif direction == 'right': 104 | dx = 1 105 | elif direction == 'up': 106 | dy = 1 107 | elif direction == 'down': 108 | dy = -1 109 | return dx, dy 110 | 111 | def new_pos(self, direction): 112 | ''' 113 | Calculates a new position given a direction. Takes as input a 114 | direction 'left', 'right', 'up' or 'down'. Allows wrapping of the 115 | world map (eg. moving left from x = 0 moves you to x = -1) 116 | ''' 117 | dx, dy = self._direction_to_dxdy(direction) 118 | new_x = (self.x + dx) % world.width 119 | new_y = (self.y + dy) % world.height 120 | return new_x, new_y 121 | 122 | def move(self, direction): 123 | """ 124 | Moves the character to the new position. 125 | """ 126 | new_x, new_y = self.new_pos(direction) 127 | if world.is_occupied(new_x, new_y): 128 | statusbar.set_status('Position is occupied, try another move.') 129 | else: 130 | self.remove() 131 | self.x, self.y = new_x, new_y 132 | self.occupy(self.x, self.y) 133 | 134 | def attack(self, enemy): 135 | dist = self.distance(enemy) 136 | if dist == (0, 1) or dist == (1, 0): 137 | if not enemy.hp: 138 | msgs = [ 139 | "This body doesn't look delicious at all.", 140 | "You really want me to do this?", 141 | "Yeah, whatever!", 142 | "I killed it! What did you make me do!" 143 | ] 144 | statusbar.set_status(random.choice(msgs)) 145 | else: 146 | # Possible damage is depending on physical condition 147 | worst = int((self.condition() * 0.01) ** (1/2.) * self.damage + 0.5) 148 | best = int((self.condition() * 0.01) ** (1/4.) * self.damage + 0.5) 149 | damage = (worst == best) and best or random.randrange(worst, best) 150 | 151 | # Possible damage is also depending on sudden adrenaline 152 | # rushes and aiming accuracy or at least butterfly flaps 153 | damage = random.randrange( 154 | (damage-1, 0)[not damage], 155 | (damage+1, self.damage)[damage == self.damage]) 156 | enemy.harm(damage) 157 | 158 | if enemy.image == CHR_PLAYER: 159 | statusbar.set_status("You are being attacked: %i damage." % damage) 160 | elif self.image == CHR_PLAYER: 161 | if enemy.image == CHR_DEAD: 162 | statusbar.set_status("You make %i damage: your enemy is dead." % damage) 163 | else: 164 | statusbar.set_status("You make %i damage: %s has %i/%i hp left." % \ 165 | (damage, enemy.image, enemy.hp, enemy.max_hp)) 166 | else: 167 | msgs = [ 168 | "Woah! Kicking air really is fun!", 169 | "This would be totally ineffective!", 170 | "Just scaring the hiding velociraptors..." 171 | ] 172 | statusbar.set_status(random.choice(msgs)) 173 | 174 | 175 | def condition(self): 176 | return (self.hp * 100) / self.max_hp 177 | 178 | def harm(self, damage): 179 | self.hp -= damage 180 | if self.hp <= 0: 181 | self.image = CHR_DEAD 182 | self.hp = 0 183 | 184 | def get_all_enemies_at_distance(self, dist): 185 | """Return a list of all enemies that are exactly 'dist' cells away 186 | either horizontally or vertically. 187 | """ 188 | coords = [((self.x + dist) % world.width, self.y % world.height), 189 | ((self.x - dist) % world.width, self.y % world.height), 190 | (self.x % world.width, (self.y + dist) % world.height), 191 | (self.x % world.width, (self.y - dist) % world.height)] 192 | enemies = [] 193 | for x, y in coords: 194 | if world.is_occupied(x, y) and isinstance(world.map[x][y], Enemy): 195 | enemies.append(world.map[x][y]) 196 | return enemies 197 | 198 | def get_all_enemies(self, max_dist=1): 199 | """Return a list of all enemies that are at most 'max_dist' cells away 200 | either horizontally or vertically. 201 | """ 202 | enemies = [] 203 | for dist in range(1, max_dist+1): 204 | enemies.extend(self.get_all_enemies_at_distance(dist)) 205 | return enemies 206 | 207 | def get_alive_enemies_at_distance(self, dist): 208 | """Return a list of alive enemies that are exactly 'dist' cells away 209 | either horizontally or vertically. 210 | """ 211 | enemies = self.get_all_enemies_at_distance(dist) 212 | return [enemy for enemy in enemies if enemy.hp > 0] 213 | 214 | def get_alive_enemies(self, max_dist=1): 215 | """Return a list of alive enemies that are at most 'max_dist' cells away 216 | either horizontally or vertically. 217 | """ 218 | enemies = self.get_all_enemies(max_dist) 219 | return [enemy for enemy in enemies if enemy.hp > 0] 220 | 221 | class Player(Character): 222 | def __init__(self, x, y, hp): 223 | Character.__init__(self, x, y, CHR_PLAYER, hp) 224 | 225 | class Enemy(Character): 226 | def __init__(self, x, y, hp): 227 | Character.__init__(self, x, y, CHR_ENEMY, hp) 228 | 229 | # not used 230 | def challenge(self, other): 231 | print "Let's fight!" 232 | 233 | def act(self, character, directions): 234 | # No action if dead X-( 235 | if not self.hp: 236 | return False 237 | 238 | choices = [0, 1] 239 | 240 | dist = self.distance(character) 241 | if dist == (0, 1) or dist == (1, 0): 242 | choices.append(2) 243 | choice = random.choice(choices) 244 | 245 | if choice == 1: 246 | # Running away 247 | while (True): 248 | goto = directions[random.choice(directions.keys())] 249 | new_x, new_y = self.new_pos(goto) 250 | if not world.is_occupied(new_x, new_y): 251 | self.move(goto) 252 | break 253 | elif choice == 2: 254 | # Fighting back 255 | self.attack(character) 256 | 257 | class Wizard(Character): 258 | def __init__(self, x, y, hp): 259 | Character.__init__(self, x, y, CHR_WIZARD, hp) 260 | 261 | def cast_spell(self, name, target): 262 | """Cast a spell on the given target.""" 263 | if name == 'remove': 264 | self._cast_remove(target) 265 | elif name == 'hp-stealer': 266 | self._cast_hp_stealer(target) 267 | else: 268 | print "The wizard does not know the spell '{0}' yet.".format(name) 269 | 270 | def _cast_remove(self, enemy): 271 | dist = self.distance(enemy) 272 | if dist == (0, 1) or dist == (1, 0): 273 | enemy.remove() 274 | 275 | def _cast_hp_stealer(self, enemy): 276 | dist = self.distance(enemy) 277 | if dist == (0, 3) or dist == (3, 0): 278 | enemy.harm(3) 279 | self.hp += 3 280 | 281 | class Archer(Character): 282 | def __init__(self, x, y, hp): 283 | Character.__init__(self, x, y, CHR_ARCHER, hp) 284 | 285 | def range_attack(self, enemy): 286 | dist = self.distance(enemy) 287 | if (dist[0] <= 5 and dist[1] == 0) or (dist[0] == 0 and dist[1] <= 5): 288 | enemy.harm(5) 289 | -------------------------------------------------------------------------------- /classic-games/hello_class/game.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # game.py - simple game to demonstrate classes and objects 3 | from classes import * 4 | 5 | DIRECTIONS = { 6 | "r": "right", 7 | "l": "left", 8 | "d": "down", 9 | "u": "up" 10 | } 11 | 12 | if __name__ == '__main__': 13 | 14 | print """Welcome to 'Hello, Class' game 15 | Available commands are: 16 | r - move right 17 | l - move left 18 | u - move up 19 | d - move down 20 | a - attack 21 | gps - print location 22 | x - exit 23 | 24 | There is a Bug 2 steps to the right from you. 25 | You should probably do something about it! 26 | """ 27 | 28 | # initializing some entities 29 | 30 | #campus = World(100, 100) 31 | student = Player(10, 10, 100) 32 | engineer = Wizard(35, 14, 100) 33 | bug1 = Enemy(12, 10, 100) 34 | bug2 = Enemy(11, 11, 100) 35 | 36 | statusbar.set_character(student) 37 | world.print_map() 38 | 39 | while True: 40 | c = raw_input("You > ") 41 | 42 | if c == "x": 43 | break 44 | elif c in DIRECTIONS: 45 | student.move(DIRECTIONS[c]) 46 | bug1.act(student, DIRECTIONS) 47 | elif c == "gps": 48 | statusbar.set_status("Your GPS location: %i %i" % (student.x, student.y)) 49 | statusbar.set_status("Bug GPS location: %i %i" % (bug1.x, bug1.y)) 50 | elif c == "a": 51 | enemies = student.get_alive_enemies(1) 52 | if enemies: 53 | student.attack(enemies[0]) 54 | enemies[0].act(student, DIRECTIONS) 55 | else: 56 | statusbar.set_status("Unknown command. 'x' to exit game") 57 | 58 | statusbar.show() 59 | world.print_map() 60 | 61 | 62 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytonc/udacity/766bef50ac9bfe74410ad3a898d8654b319a5c82/tools/README.md -------------------------------------------------------------------------------- /tools/pquiz/README: -------------------------------------------------------------------------------- 1 | PQUIZ - programming quiz downloader, submitter. 2 | 3 | 4 | == OVERVIEW == 5 | 6 | https://github.com/udacians/udacity 7 | by Karl-Aksel Puulmann aka macobo 8 | 9 | A small command-line application to make downloading programming quizzes and submitting solutions for Udacitys Computer Science classes easier. 10 | 11 | 12 | == 2. EXAMPLES == 13 | 14 | Downloading all programming quizzes for course CS262 (Downloaded files will be placed as ./Course/UnitName/Part.py where . is current folder) 15 | 16 | python pquiz.py --download --course cs262 17 | 18 | 19 | Submitting solution to CS262 Problem Set 5 Quiz 5 (you will be asked to log in) 20 | 21 | python pquiz.py --submit --file "./cs262/Problem Set 5/5.py" 22 | 23 | OR (if the file wasn't downloaded using this tool) 24 | 25 | python pquiz.py --submit --file "./cs262/Problem Set 5/5.py" --course cs262 --unit "Problem Set 5" --part 5 26 | 27 | 28 | Downloading all programming quizzes for course CS262 Problem set 5 29 | 30 | python pquiz.py --download --course cs262 --unit "Problem Set 5" 31 | 32 | 33 | Downloading Part 5 of CS262 Problem set 5 34 | 35 | python pquiz.py --download --course cs262 --unit "Problem Set 5" --part 5 36 | -------------------------------------------------------------------------------- /tools/pquiz/pquiz.py: -------------------------------------------------------------------------------- 1 | # Udacity tool to submit and download programming quizzes 2 | # by: Karl-Aksel Puulmann, macobo@ut.ee 3 | 4 | import cookielib 5 | import urllib 6 | import urllib2 7 | import json 8 | import re 9 | import getpass 10 | import os 11 | import time 12 | 13 | rootURL = r"http://www.udacity.com" 14 | ajaxRoot = r"http://www.udacity.com/ajax" 15 | cookieFile = r".\cookie.lwp" 16 | 17 | coursePath = { 18 | # Intro to Computer Science. Building a Search Engine 19 | "cs101": r"Course/cs101/CourseRev/apr2012", 20 | # Web Application Engineering. How to Build a Blog 21 | "cs253": r"Course/cs253/CourseRev/apr2012", 22 | # Programming Languages. Building a Web Browser 23 | "cs262": r'Course/cs262/CourseRev/apr2012', 24 | # Artificial Intelligence. Programming a Robotic Car 25 | "cs373": r"Course/cs373/CourseRev/apr2012", 26 | # Design of Computer Programs. Programming Principles 27 | "cs212": r"Course/cs212/CourseRev/apr2012", 28 | # Algorithms. Crunching Social Networks 29 | "cs215": r"Course/cs215/CourseRev/1", 30 | # Applied Cryptography. Science of Secrets 31 | "cs387": r"Course/cs387/CourseRev/apr2012", 32 | # Software Testing. How to Make Software Fail 33 | "cs258": r"Course/cs258/CourseRev/1", 34 | # Statistics 101 35 | "st101": r"Course/st101/CourseRev/1" 36 | } 37 | 38 | courseCache = {} 39 | csrf_token = None 40 | uVersion = None 41 | logged_in = False 42 | cookie_jar = None 43 | 44 | def log_in(): 45 | """ Logs you in so you can submit a solution. Saves 46 | the cookie on disk. """ 47 | global logged_in, cookie_jar 48 | email = raw_input("Email: ") 49 | # Try to ask for password in a way that shoulder-surfers can't handle 50 | pw = getpass.getpass("Password: ") 51 | print ("") # Empty line for clarity 52 | data = {"data": 53 | {"email":email, 54 | "password":pw}, 55 | "method":"account.sign_in", 56 | "version":uVersion, 57 | "csrf_token":csrf_token} 58 | try: 59 | answer = jsonFromURL(ajaxRoot, json.dumps(data)) 60 | except urllib2.HTTPError, error: 61 | contents = error.read() 62 | print(contents) 63 | raise 64 | if 'error' in answer['payload']: 65 | raise ValueError("Failed to log in!") 66 | 67 | cookie_jar.save() 68 | print("Logged in successfully!\n") 69 | logged_in = True 70 | 71 | def setSessionHandler(): 72 | """ Gets information from udacity home page to successfully 73 | query courses. 74 | If user has saved a cookie on disk, tries to use that. """ 75 | global uVersion, csrf_token, cookie_jar, logged_in 76 | 77 | cookie_jar = cookielib.LWPCookieJar(cookieFile) 78 | if os.access(cookieFile, os.F_OK): 79 | logged_in = True 80 | cookie_jar.load() 81 | print("Found a cookie!") 82 | 83 | opener = urllib2.build_opener( 84 | urllib2.HTTPCookieProcessor(cookie_jar)) 85 | urllib2.install_opener(opener) 86 | 87 | print("Accessing udacity main page...\n") 88 | uMainSiteHTML = urllib2.urlopen(rootURL).read() 89 | # get uVersion - used when querying 90 | uVersion = re.findall(r"js/udacity.js[?]([0-9]+)", uMainSiteHTML)[0] 91 | uVersion = "dacity-"+uVersion 92 | # get csrf_token - used for logging in 93 | csrf_token = re.findall(r'csrf_token = "([^"]+)', uMainSiteHTML)[0] 94 | 95 | # -- Utility functions -- 96 | def jsonFromURL(url, data=None): 97 | return json.loads(urllib2.urlopen(url, data).read()) 98 | 99 | def findPair(key, value, json_array): 100 | """ Finds first dictionary that where key corresponds to value 101 | in an array of dictionaries """ 102 | for x in json_array: 103 | if x is not None and key in x and x[key] == value: 104 | return x 105 | raise ValueError("(key, value) pair not in list") 106 | 107 | def ajaxURL(query): 108 | return ajaxRoot + "?" + urllib.quote(json.dumps(query)) 109 | 110 | def sanitize(path): 111 | """ Sanitizes unit names so they can be used as folder paths """ 112 | illegalChars = r'<>:"/\|?*' 113 | for ch in illegalChars: 114 | path = path.replace(ch, "") 115 | return path 116 | 117 | def stripHTML(text): 118 | return re.sub(r"<.*?>", "", text) 119 | 120 | # -- Functions related to getting course-related info -- 121 | def courseJSON(courseID): 122 | """ Returns the JSON-formatted info about this course """ 123 | if courseID not in courseCache: 124 | query = {"data":{"path":coursePath[courseID]}, 125 | "method": "course.get", 126 | "version": uVersion} 127 | print("Getting course info...") 128 | url = ajaxURL(query) 129 | courseCache[courseID] = jsonFromURL(url)['payload'] 130 | return courseCache[courseID] 131 | 132 | 133 | def unitJSON(courseID, unitName): 134 | """ Returns the JSON of this unit from the API """ 135 | courseJS = courseJSON(courseID) 136 | for unit in courseJS['course_rev']['units']: 137 | if unit['name'] == unitName: 138 | return unit 139 | raise ValueError("No unit named {0} found!".format(unitName)) 140 | 141 | def programPath(unitJSON, n): 142 | """ Given the JSON covering the unit, returns the nth part programming quiz path """ 143 | partKeys = unitJSON['nuggetLayout'][n-1] # The keys to parts of part n 144 | nuggets = unitJSON['nuggets'] 145 | for v in partKeys: # one of the parts should be a programming quiz 146 | if v is not None: 147 | part = findPair('key', v['nugget_key'], nuggets) 148 | type_of_lecture = part['nuggetType'] 149 | if type_of_lecture == "program": 150 | return part['path'] 151 | raise ValueError("Found no programming quiz for this part") 152 | 153 | def programmingQuiz(courseID, unit, part): 154 | """ Returns the program text for Udacity cs-courseID unit part quiz """ 155 | print("Getting default program text...") 156 | path = programPath(unitJSON(courseID, unit), part) 157 | query = {"data":{"path": path}, 158 | "method":"assignment.ide.get", 159 | "version": uVersion} 160 | url = ajaxURL(query) 161 | queryAnswer = jsonFromURL(url) 162 | return queryAnswer['payload']['nugget']['suppliedCode'] 163 | 164 | 165 | def downloadProgram(courseID, unit, part): 166 | """ Downloads the specific program and places it as 167 | ./courseID/Unit/part.py in the file tree 168 | (./ means current folder) 169 | Places a token on the first line to identify the file. """ 170 | text = programmingQuiz(courseID, unit, part) 171 | coursePath = os.path.join(os.curdir, str(courseID)) 172 | if not os.path.exists(coursePath): 173 | os.mkdir(coursePath) 174 | 175 | unitSanitized = sanitize(unit) 176 | unitPath = os.path.join(coursePath, unitSanitized) 177 | 178 | if not os.path.exists(unitPath): 179 | os.mkdir(unitPath) 180 | fileName = "{0}.py".format(part) 181 | filePath = os.path.join(unitPath, fileName) 182 | if os.path.exists(filePath): 183 | raise ValueError("File already exists") 184 | with open(filePath, "w") as out: 185 | # Add info to help identify file 186 | out.write("# {0} ; {1} ; {2}\n".format(courseID, unit, part)) 187 | out.write('\n'.join(text.split("\r\n"))) 188 | 189 | def downloadUnit(courseID, unit): 190 | unitJS = unitJSON(courseID, unit) 191 | parts = len(unitJS['nuggetLayout']) 192 | for part in range(1, parts+1): 193 | print('{0}: {1} part {2}'.format(courseID, unit, part)) 194 | try: 195 | downloadProgram(courseID, unit, part) 196 | except ValueError: 197 | pass 198 | 199 | def downloadCourse(courseID): 200 | """ Downloads all units in this course """ 201 | courseJS = courseJSON(courseID) 202 | for unit in courseJS['course_rev']['units']: 203 | unitName = unit['name'] 204 | print('{0}: {1}'.format(courseID, unitName)) 205 | downloadUnit(courseID, unitName) 206 | 207 | 208 | # -- Functions related to submitting a file -- 209 | def identifyFile(first_line): 210 | """ Tries to identify file by its first line, which must 211 | be in the following form: "# CourseID ; Unit ; Part" """ 212 | if first_line[:2] != '# ': 213 | raise ValueError("First line doesn't identify file") 214 | try: 215 | course, unit, part = first_line[2:].strip().split(' ; ') 216 | except: 217 | raise ValueError("First line doesn't identify file") 218 | return course, unit, int(part) 219 | 220 | def submit(program_file): 221 | """ Submits a file, trying to identify it by its first line """ 222 | with open(program_file) as f: 223 | first_line = f.readline() # identifier line 224 | program_text = f.read() 225 | course, unit, part = identifyFile(first_line) 226 | status = submitSolution(program_text, course, unit, part) 227 | 228 | def submitSolution(program_text, courseID, unit, part): 229 | print("Submitting your solution for {0} {1} Part {2}\n".format( 230 | courseID, unit, part)) 231 | global logged_in 232 | if not logged_in: 233 | log_in() 234 | path = programPath(unitJSON(courseID, unit), part) 235 | # Send the program as a query 236 | print("Sending sending your program to the servers...\n") 237 | query = {"data":{"usercode":program_text, 238 | "op":"submit", 239 | "path":path}, 240 | "method":"assignment.ide.exe", 241 | "version":uVersion, 242 | "csrf_token":csrf_token} 243 | req = urllib2.Request(ajaxRoot, json.dumps(query)) 244 | response1 = json.loads(urllib2.urlopen(req).read()) 245 | # Ask from the server how we did 246 | query = {"data": {"ps_key": response1['payload']['ps_key']}, 247 | "method":"assignment.ide.result", 248 | "version":uVersion} 249 | queryURL = ajaxURL(query) 250 | for _ in range(20): 251 | specifics = jsonFromURL(queryURL) 252 | if specifics['payload']['status'] != 'queued': 253 | print("\nThe server responded:") 254 | print(stripHTML(specifics['payload']['comment'])) 255 | return specifics['payload'] 256 | print("Your program is still being graded. Trying again in 1 second") 257 | time.sleep(1) 258 | print("Your program didn't recieve a response in 20 tries. :(") 259 | 260 | def main(): 261 | import argparse 262 | 263 | epilog = """Example: 264 | pquiz.py --submit --file 1.py OR 265 | pquiz.py -s -f 1.py 266 | pquiz.py --download --course 212""" 267 | parser = argparse.ArgumentParser(description= \ 268 | "Tool to help download and upload programming quizzes "+ 269 | "from Udacity's CS courses", 270 | epilog = epilog, 271 | formatter_class = argparse.RawDescriptionHelpFormatter) 272 | parser.add_argument("-s", "--submit", 273 | action='store_true', 274 | help="submit a file") 275 | parser.add_argument("-d", "--download", 276 | action='store_true', 277 | help="download a programming quiz") 278 | parser.add_argument("-c", "--course", 279 | metavar="CID", 280 | help="Course ID (eg cs262, st101)") 281 | parser.add_argument("-u", "--unit", 282 | help='Unit title (eg "Unit 5", "Homework 2")') 283 | parser.add_argument("-p", "--part", 284 | type=int, 285 | help="part number") 286 | parser.add_argument("-f", "--file", 287 | help="path to file") 288 | 289 | args = parser.parse_args() 290 | 291 | if args.course and args.course not in coursePath: 292 | print "Course " + args.course + " is not supported!" 293 | return 294 | 295 | if args.submit and not args.download: 296 | setSessionHandler() 297 | if args.course and args.unit and args.part and args.file: 298 | program_text = open(args.file).read() 299 | submitSolution(program_text, args.course, args.unit, args.part) 300 | elif args.file: 301 | submit(args.file) 302 | else: 303 | parser.print_help() 304 | elif args.download and not args.submit: 305 | setSessionHandler() 306 | if args.course and args.unit and args.part: 307 | downloadProgram(args.course, args.unit, args.part) 308 | elif args.course and args.unit: 309 | downloadUnit(args.course, args.unit) 310 | elif args.course: 311 | downloadCourse(args.course) 312 | else: 313 | parser.print_help() 314 | else: 315 | parser.print_help() 316 | 317 | if __name__ == "__main__": 318 | main() 319 | --------------------------------------------------------------------------------