├── .gitignore ├── LICENSE ├── README ├── glade └── piedit.glade ├── piedit.py ├── piedit ├── __init__.py ├── colors.py ├── debug.py ├── getchr.py ├── interpreter.py ├── interpreter_floodfill.py ├── ui.py └── unionfind.py └── programs ├── 99bottles.png ├── Piet-1.png ├── Piet_hello2.png ├── alpha_filled.png ├── hello.png ├── japh.png ├── new.png ├── piet_pi.png ├── primetest.png └── test.png /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Steven Anderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Piedit is a Graphical IDE and integrated interpreter for the Piet programming language http://www.dangermouse.net/esoteric/piet.html. 2 | 3 | Piet is an esoteric programming language inspired by Piet Mondrian - http://en.wikipedia.org/wiki/Piet_Mondrian, where the programs look like abstract art. The colours and arrangement of the pixels control the program flow and operation. For a more detailed explanation, see the Piet Homepage (link above). 4 | 5 | Written in Python with a GTK interface designed in Glade. 6 | 7 | Features 8 | 9 | * Create, edit and save Piet programs easily using the UI 10 | * Run programs using the command line interpreter 11 | * Step through/run programs in the UI 12 | * There seemed like a lot more features when I was writing it! 13 | 14 | Requirements 15 | 16 | * Python (with PIL) 17 | * GTK+ 2.0 18 | * PyGTK bindings -------------------------------------------------------------------------------- /glade/piedit.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | True 9 | GTK_WIN_POS_CENTER 10 | 500 11 | 500 12 | 13 | 14 | 15 | 16 | 17 | True 18 | 19 | 20 | True 21 | 22 | 23 | True 24 | _File 25 | True 26 | 27 | 28 | True 29 | 30 | 31 | True 32 | gtk-new 33 | True 34 | True 35 | 36 | 37 | 38 | 39 | 40 | True 41 | gtk-open 42 | True 43 | True 44 | 45 | 46 | 47 | 48 | 49 | True 50 | gtk-save 51 | True 52 | True 53 | 54 | 55 | 56 | 57 | 58 | True 59 | gtk-save-as 60 | True 61 | True 62 | 63 | 64 | 65 | 66 | 67 | True 68 | 69 | 70 | 71 | 72 | True 73 | gtk-quit 74 | True 75 | True 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | True 87 | _Run 88 | True 89 | 90 | 91 | True 92 | 93 | 94 | True 95 | _Run 96 | True 97 | 98 | 99 | 100 | True 101 | gtk-media-play 102 | 103 | 104 | 105 | 106 | 107 | 108 | True 109 | 110 | 111 | 112 | 113 | True 114 | _Debug 115 | True 116 | 117 | 118 | 119 | True 120 | gtk-media-forward 121 | 122 | 123 | 124 | 125 | 126 | 127 | True 128 | False 129 | _Next Step 130 | True 131 | 132 | 133 | 134 | True 135 | gtk-media-forward 136 | 137 | 138 | 139 | 140 | 141 | 142 | True 143 | 144 | 145 | 146 | 147 | True 148 | False 149 | _Stop 150 | True 151 | 152 | 153 | 154 | True 155 | gtk-stop 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | True 167 | _Help 168 | True 169 | 170 | 171 | True 172 | 173 | 174 | True 175 | gtk-help 176 | True 177 | True 178 | 179 | 180 | 181 | 182 | True 183 | 184 | 185 | 186 | 187 | True 188 | gtk-about 189 | True 190 | True 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | BONOBO_DOCK_ITEM_BEH_EXCLUSIVE | BONOBO_DOCK_ITEM_BEH_NEVER_VERTICAL | BONOBO_DOCK_ITEM_BEH_LOCKED 203 | 204 | 205 | 206 | 207 | True 208 | 209 | 210 | True 211 | True 212 | 213 | 214 | True 215 | New 216 | gtk-new 217 | 218 | 219 | 220 | True 221 | 222 | 223 | 224 | 225 | True 226 | Open 227 | gtk-open 228 | 229 | 230 | 231 | True 232 | 233 | 234 | 235 | 236 | True 237 | Save 238 | gtk-save 239 | 240 | 241 | 242 | True 243 | 244 | 245 | 246 | 247 | True 248 | 249 | 250 | 251 | 252 | True 253 | Run 254 | gtk-media-play 255 | 256 | 257 | 258 | True 259 | 260 | 261 | 262 | 263 | True 264 | Debug 265 | gtk-media-forward 266 | 267 | 268 | 269 | True 270 | 271 | 272 | 273 | 274 | True 275 | False 276 | Step 277 | gtk-media-forward 278 | 279 | 280 | 281 | True 282 | 283 | 284 | 285 | 286 | True 287 | False 288 | Stop 289 | gtk-cancel 290 | 291 | 292 | 293 | True 294 | 295 | 296 | 297 | 298 | True 299 | 300 | 301 | 302 | 303 | True 304 | Help 305 | gtk-help 306 | 307 | 308 | 309 | True 310 | 311 | 312 | 313 | 314 | False 315 | False 316 | 317 | 318 | 319 | 320 | True 321 | 0 322 | GTK_SHADOW_NONE 323 | 324 | 325 | True 326 | 12 327 | 12 328 | 329 | 330 | True 331 | GDK_EXTENSION_EVENTS_ALL 332 | 3 333 | 7 334 | 1 335 | 1 336 | True 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | True 407 | <b>Codel Colours</b> 408 | True 409 | 410 | 411 | label_item 412 | 413 | 414 | 415 | 416 | False 417 | 1 418 | 419 | 420 | 421 | 422 | True 423 | 0 424 | GTK_SHADOW_NONE 425 | 426 | 427 | True 428 | 12 429 | 430 | 431 | True 432 | 3 433 | 3 434 | 435 | 436 | 437 | 438 | 439 | True 440 | 441 | 442 | 443 | 444 | 2 445 | 2 446 | 447 | 448 | 449 | 450 | True 451 | True 452 | True 453 | \/ 454 | 0 455 | 456 | 457 | 458 | 2 459 | 3 460 | 461 | 462 | 463 | 464 | 465 | True 466 | True 467 | True 468 | /\ 469 | -1 470 | 471 | 472 | 473 | 1 474 | 2 475 | 2 476 | 3 477 | 478 | 479 | 480 | 481 | 482 | True 483 | True 484 | True 485 | > 486 | 0 487 | 488 | 489 | 490 | 2 491 | 3 492 | 493 | 494 | 495 | 496 | 497 | True 498 | True 499 | True 500 | < 501 | 0 502 | 503 | 504 | 505 | 2 506 | 3 507 | 1 508 | 2 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | True 519 | <b>Program</b> 520 | True 521 | 522 | 523 | label_item 524 | 525 | 526 | 527 | 528 | 2 529 | 530 | 531 | 532 | 533 | 534 | 535 | True 536 | True 537 | 538 | 539 | 540 | 541 | True 542 | 4 543 | True 544 | True 545 | 546 | 547 | 1 548 | 549 | 550 | 551 | 552 | -------------------------------------------------------------------------------- /piedit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Piet IDE, written in Python. It's badass.""" 4 | 5 | import sys 6 | import os.path 7 | import threading 8 | import signal 9 | import pygtk 10 | import gtk 11 | import gtk.glade 12 | import gnome 13 | import piedit.ui 14 | import piedit.interpreter 15 | pygtk.require("2.0") 16 | 17 | __author__ = "Steven Anderson" 18 | __copyright__ = "Steven Anderson 2008" 19 | __credits__ = ["Steven Anderson"] 20 | __license__ = "GPL" 21 | __version__ = "0.0.1" 22 | __maintainer__ = "Steven Anderson" 23 | __email__ = "steven.james.anderson@googlemail.com" 24 | __status__ = "Production" 25 | 26 | 27 | class Program: 28 | """Class that runs the main program""" 29 | 30 | def __init__(self): 31 | """Loads the glade XML and creates the ui object""" 32 | gnome.init("Piedit", "0.1") 33 | gladeui = gtk.glade.XML(os.path.join('glade', 'piedit.glade')) 34 | ui = piedit.ui.UI(gladeui) 35 | 36 | class GuiThread(threading.Thread): 37 | def run(self): 38 | gtk.main() 39 | 40 | def key_interrupt(num,frame): 41 | print "Termieffnated" 42 | raise SystemExit 43 | 44 | if __name__ == "__main__": 45 | signal.signal(signal.SIGINT,key_interrupt) 46 | program = Program() 47 | gtk.gdk.threads_init() 48 | gui_thread = GuiThread() 49 | gui_thread.start() -------------------------------------------------------------------------------- /piedit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/piedit/__init__.py -------------------------------------------------------------------------------- /piedit/colors.py: -------------------------------------------------------------------------------- 1 | """Class to access information about piet colors""" 2 | import sys 3 | 4 | __author__ = "Steven Anderson" 5 | __copyright__ = "Steven Anderson 2008" 6 | __credits__ = ["Steven Anderson"] 7 | __license__ = "GPL" 8 | __version__ = "0.0.1" 9 | __maintainer__ = "Steven Anderson" 10 | __email__ = "steven.james.anderson@googlemail.com" 11 | __status__ = "Production" 12 | 13 | colors = ( 14 | "#FFC0C0", 15 | "#FF0000", 16 | "#C00000", 17 | "#FFFFC0", 18 | "#FFFF00", 19 | "#C0C000", 20 | "#C0FFC0", 21 | "#00FF00", 22 | "#00C000", 23 | "#C0FFFF", 24 | "#00FFFF", 25 | "#00C0C0", 26 | "#C0C0FF", 27 | "#0000FF", 28 | "#0000C0", 29 | "#FFC0FF", 30 | "#FF00FF", 31 | "#C000C0", 32 | "#FFFFFF", 33 | "#000000", 34 | ) 35 | 36 | color_mappings = {} 37 | for index,color in enumerate(colors): 38 | color_mappings[color] = index 39 | 40 | white = "#FFFFFF" 41 | black = "#000000" 42 | 43 | num_hues = 6 44 | num_lights = 3 45 | 46 | def all_colors(): 47 | """Generator to return all piet colors""" 48 | for color in colors: 49 | yield color 50 | 51 | def rgb_to_hex(rgb): 52 | """Converts an rgb tuple to a hex string""" 53 | if len(rgb) <3: 54 | return rgb 55 | return '#%02x%02x%02x'.upper() % rgb 56 | 57 | def hex_to_rgb(hex): 58 | """Converts a hex string to an rgb tuple""" 59 | return (int(hex[1:3],16),int(hex[3:5],16),int(hex[5:7],16)) 60 | 61 | def is_white(hex): 62 | if hex == white: 63 | return True 64 | else: 65 | try: 66 | color_mappings[hex] 67 | return False 68 | except KeyError: 69 | return True 70 | 71 | def is_black(hex): 72 | if hex == black: 73 | return True 74 | else: 75 | return False 76 | 77 | def hue_light_diff(from_color,to_color): 78 | """Gets the difference in hue and light between two colors. 79 | Expects a from_color and to_color in rgb tuple form""" 80 | from_hue, from_light = divmod(color_mappings[from_color],num_lights) 81 | to_hue, to_light = divmod(color_mappings[to_color],num_lights) 82 | 83 | #print "From Hue Light: %s,%s,%s, To Hue Light: %s,%s,%s" % (from_color,from_hue,from_light,to_color,to_hue,to_light) 84 | 85 | hue_diff, light_diff = to_hue - from_hue, to_light - from_light 86 | if hue_diff <0: 87 | hue_diff = hue_diff + num_hues 88 | if light_diff <0: 89 | light_diff = light_diff + num_lights 90 | 91 | return (hue_diff, light_diff) 92 | -------------------------------------------------------------------------------- /piedit/debug.py: -------------------------------------------------------------------------------- 1 | class Debug(object): 2 | 3 | def __init__(self, doit): 4 | self.doit = doit 5 | 6 | def writeln(self,msg=''): 7 | if self.doit: 8 | print msg 9 | -------------------------------------------------------------------------------- /piedit/getchr.py: -------------------------------------------------------------------------------- 1 | """Module to get a character from STDIN across platforms""" 2 | import sys 3 | 4 | __author__ = "Steven Anderson" 5 | __copyright__ = "Steven Anderson 2008" 6 | __credits__ = ["Steven Anderson"] 7 | __license__ = "GPL" 8 | __version__ = "0.0.1" 9 | __maintainer__ = "Steven Anderson" 10 | __email__ = "steven.james.anderson@googlemail.com" 11 | __status__ = "Production" 12 | 13 | def get_chr_unix(): 14 | """Gets a character from STDIN in unix""" 15 | import tty, termios 16 | fd = sys.stdin.fileno() 17 | old_settings = termios.tcgetattr(fd) 18 | try: 19 | tty.setraw(sys.stdin.fileno()) 20 | ch = sys.stdin.read(1) 21 | finally: 22 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 23 | return ch 24 | 25 | def get_chr_windows(): 26 | """Gets a character from STDIN in windows""" 27 | import msvcrt 28 | return msvcrt.getch() 29 | 30 | def get_chr(): 31 | """Gets a character from STDIN""" 32 | try: 33 | return get_chr_unix() 34 | except ImportError: 35 | return get_chr_windows() -------------------------------------------------------------------------------- /piedit/interpreter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Interpreter for the Piet programming language. Can be run directly or 4 | imported and used by the GUI.""" 5 | 6 | import sys 7 | import getopt 8 | import PIL.Image 9 | import colors 10 | import unionfind 11 | import getchr 12 | import debug 13 | 14 | __author__ = "Steven Anderson" 15 | __copyright__ = "Steven Anderson 2008" 16 | __credits__ = ["Steven Anderson"] 17 | __license__ = "GPL" 18 | __version__ = "0.0.1" 19 | __maintainer__ = "Steven Anderson" 20 | __email__ = "steven.james.anderson@googlemail.com" 21 | __status__ = "Production" 22 | 23 | 24 | class Interpreter(object): 25 | """The Piet interpreter class""" 26 | def __init__(self, max_steps=1000000, thread=None): 27 | """Initalizes new Interpreter.""" 28 | self.current_pixel = None 29 | self.dp = 0 30 | self.cc = 0 31 | self.switch_cc = True 32 | self.step = 0 #0 for just moved into color block, 1 for moved to edge 33 | self.times_stopped = 0 34 | self.max_steps = max_steps 35 | self.current_step = 0 36 | self.stack = [] 37 | self.color_blocks = {} 38 | self.finished = False 39 | self.thread = thread 40 | self.debug = debug.Debug(False) 41 | #Indexed by hue and light change 42 | self.operations = { 43 | (1,0):("Add",self.op_add), 44 | (2,0):("Divide",self.op_divide), 45 | (3,0):("Greater",self.op_greater), 46 | (4,0):("Duplicate",self.op_duplicate), 47 | (5,0):("IN(char)",self.op_in_char), 48 | (0,1):("Push",self.op_push), 49 | (1,1):("Subtract",self.op_subtract), 50 | (2,1):("Mod",self.op_mod), 51 | (3,1):("Pointer",self.op_pointer), 52 | (4,1):("Roll",self.op_roll), 53 | (5,1):("OUT(Number)",self.op_out_number), 54 | (0,2):("Pop",self.op_pop), 55 | (1,2):("Multiply",self.op_multiply), 56 | (2,2):("Not",self.op_not), 57 | (3,2):("Switch",self.op_switch), 58 | (4,2):("IN(Number)",self.op_in_number), 59 | (5,2):("OUT(Char)",self.op_out_char), 60 | } 61 | 62 | def init(self): 63 | self.__init__() 64 | 65 | def set_opt(self,o,a): 66 | """Sets an option from the command line.""" 67 | if o in ["-d", "--debug"]: 68 | self.debug.DEBUG = True 69 | elif o in ["-m","--maxsteps"]: 70 | self.max_steps = int(a) 71 | 72 | def run_program(self,path=None,pixels=None,width=None,height=None,start=True): 73 | """Runs a program at the given path.""" 74 | self.debug.writeln("---LOADING IMAGE %s...---" % (path)) 75 | if pixels != None: 76 | self.width = width 77 | self.height = height 78 | self.pixels = [[Pixel(x,y,pixels[y*(self.width)+x]) for y in xrange(self.height)] for x in xrange(self.width)] 79 | self.current_pixel = self.pixels[0][0] 80 | else: 81 | self.load_image(path) 82 | self.debug.writeln("---IMAGE LOADED---\n") 83 | self.debug.writeln("---SCANNING COLOR BLOCKS---") 84 | self.find_color_blocks() 85 | self.debug.writeln("---COLOR BLOCKS SCANNED---\n") 86 | self.debug.writeln("---STARTING EXECUTION---") 87 | self.debug.writeln("AT (%s,%s), COLOR=%s, DP=%d, CC=%s"\ 88 | % (self.current_pixel.x,self.current_pixel.y,self.current_pixel.color,\ 89 | self.dp, self.cc)) 90 | if start: 91 | self.start_execution() 92 | else: 93 | pass 94 | 95 | def load_image(self,path): 96 | """Loads an image and puts pixel data into self.pixels.""" 97 | try: 98 | self.image = PIL.Image.open(path) 99 | if self.image.mode != "RGB": 100 | self.image = self.image.convert("RGB") 101 | except IOError: 102 | raise IOError, "IMAGE_NOT_LOADED" 103 | 104 | (self.width, self.height) = self.image.size 105 | rawpixels = self.image.getdata() 106 | self.pixels = [[Pixel(x,y,colors.rgb_to_hex(rawpixels[y*(self.width)+x])) for y in xrange(self.height)] for x in xrange(self.width)] 107 | self.current_pixel = self.pixels[0][0] 108 | 109 | def find_color_blocks(self): 110 | """Uses the connected component algorithm to build the program color blocks.""" 111 | next_label = 0 112 | #Pass 1 113 | for y in xrange(self.height): 114 | for x in xrange(self.width): 115 | pixel = self.pixels[x][y] 116 | if not self.is_background(pixel.color): 117 | neighbours = self.neighbours(pixel) 118 | 119 | if neighbours == []: 120 | pixel.parent = self.pixels[x][y] 121 | pixel.set_label = next_label 122 | next_label = next_label + 1 123 | else: 124 | for n in neighbours: 125 | unionfind.union(n,pixel) 126 | 127 | #Pass 2 128 | for y in xrange(self.height): 129 | for x in xrange(self.width): 130 | pixel = self.pixels[x][y] 131 | if not self.is_background(pixel.color): 132 | root = unionfind.find(pixel) 133 | pixel.set_size = root.set_size 134 | pixel.set_label = root.set_label 135 | #Build color block object 136 | if not self.color_blocks.has_key(pixel.set_label): 137 | self.color_blocks[pixel.set_label] = ColorBlock(pixel.set_size) 138 | self.color_blocks[pixel.set_label].update_boundaries(pixel) 139 | 140 | #Debug 141 | for i,color_block in self.color_blocks.items(): 142 | bounds = color_block.boundary_pixels 143 | self.debug.writeln("Color Block %s: Size=%s, \n\tmaxRL=(%s,%s), maxRR=(%s,%s), \n\tmaxDL=(%s,%s), maxDR=(%s,%s), \n\tmaxLL=(%s,%s), maxLR=(%s,%s), \n\tmaxUL=(%s,%s), maxUR=(%s,%s)" \ 144 | % (i, color_block.size, 145 | bounds[0][0].x,bounds[0][0].y, bounds[0][1].x, bounds[0][1].y, 146 | bounds[1][0].x,bounds[1][0].y, bounds[1][1].x, bounds[1][1].y, 147 | bounds[2][0].x,bounds[2][0].y, bounds[2][1].x, bounds[2][1].y, 148 | bounds[3][0].x,bounds[3][0].y, bounds[3][1].x, bounds[3][1].y)) 149 | 150 | def is_background(self,color): 151 | """Tells us if the given color is black or white.""" 152 | if colors.is_white(color) or colors.is_black(color): 153 | return True 154 | else: 155 | return False 156 | 157 | def neighbours(self,pixel): 158 | """Finds the neighbours of the given pixel with the same label.""" 159 | neighbours = [] 160 | index = 0; 161 | x = pixel.x 162 | y = pixel.y 163 | 164 | if y !=0 and self.pixels[x][y-1].color == pixel.color: 165 | #Add above pixel 166 | index = index + 1 167 | neighbours.append(self.pixels[x][y-1]) 168 | 169 | if x != 0 and self.pixels[x-1][y].color == pixel.color: 170 | #Add left pixel 171 | neighbours.append(self.pixels[x-1][y]) 172 | index = index + 1 173 | 174 | return neighbours 175 | 176 | def start_execution(self): 177 | """Starts the execution of the program.""" 178 | if self.max_steps == -1: 179 | while not self.finished: 180 | self.do_next_step() 181 | else: 182 | for i in xrange(self.max_steps): 183 | self.do_next_step() 184 | if self.finished: 185 | return 186 | self.debug.writeln("---EXECUTION FINISHED (Max Steps Reached)---") 187 | 188 | def do_next_debug_step(self): 189 | if self.max_steps == -1: 190 | self.do_next_step() 191 | else: 192 | if self.current_step < self.max_steps: 193 | self.do_next_step() 194 | else: 195 | return False 196 | self.debug.writeln("---EXECUTION FINISHED (Max Steps Reached)---") 197 | if self.finished: 198 | return False 199 | else: 200 | return True 201 | 202 | def do_next_step(self,step=None): 203 | """Executes a step in the program.""" 204 | if self.thread != None: 205 | if self.thread.should_stop: 206 | self.debug.writeln() 207 | self.debug.writeln("---EXECUTION FINISHED (Thread was stopped)---") 208 | self.finished = True 209 | return 210 | self.current_step = self.current_step + 1 211 | if self.step == 0: 212 | self.debug.writeln(" -> Moving within color block...") 213 | self.step = 1 214 | self.move_within_block() 215 | elif self.step == 1: 216 | self.debug.writeln(" -> Moving out of color block...") 217 | self.step = 0 218 | self.move_out_of_block() 219 | else: 220 | error_handler.handle_error("The step wasn't 0 or 1. That should never happen. This must be a bug in my code. Sorry") 221 | if not self.finished: 222 | self.debug.writeln() 223 | self.debug.writeln("AT (%s,%s), COLOR=%s, DP=%d, CC=%s"\ 224 | % (self.current_pixel.x,self.current_pixel.y,self.current_pixel.color,\ 225 | self.dp, self.cc)) 226 | 227 | def move_within_block(self): 228 | """Moves to the border pixel within the current color block.""" 229 | if colors.is_white(self.current_pixel.color): 230 | self.move_within_white() 231 | else: 232 | self.move_within_color() 233 | 234 | def move_within_white(self): 235 | """Slides through a white block until an obstruction or a 236 | new color block is found.""" 237 | x,y = self.next_pixel_coords() 238 | if not self.is_pixel_obstruction(x,y): 239 | return 240 | next_pixel = self.pixels[x][y] 241 | 242 | while colors.is_white(next_pixel.color): 243 | self.current_pixel = next_pixel 244 | x,y = self.next_pixel_coords() 245 | if not self.is_pixel_obstruction(x,y): 246 | return 247 | next_pixel = self.pixels[x][y] 248 | 249 | def is_pixel_obstruction(self,x,y): 250 | """Tells us whether the pixel at the given x and y is an obstruction.""" 251 | if (x<0 or y<0 or x>=self.width or y>=self.height): 252 | self.hit_obstruction() 253 | return False 254 | return True 255 | 256 | def move_within_color(self): 257 | """Moves within a color block to the required pixel 258 | at the max dp/cc direction.""" 259 | self.current_pixel = self.color_blocks\ 260 | [self.current_pixel.set_label].boundary_pixels\ 261 | [self.dp][self.cc] 262 | 263 | def move_out_of_block(self): 264 | """Moves out of a color block and into the next color block, performing 265 | the operation if necessary.""" 266 | x,y = self.current_pixel.x, self.current_pixel.y 267 | n_x,n_y = self.next_pixel_coords() 268 | 269 | self.debug.writeln(" -> Trying to cross from (%s,%s) to (%s,%s)"\ 270 | %(x,y,n_x,n_y)) 271 | 272 | #If we're at a wall 273 | if (self.dp == 0 and x >= self.width-1)\ 274 | or (self.dp == 1 and y >= self.height-1)\ 275 | or (self.dp == 2 and x <= 0)\ 276 | or (self.dp == 3 and y <= 0): 277 | self.hit_obstruction() 278 | return 279 | 280 | current_pixel = self.current_pixel 281 | next_pixel = self.pixels[n_x][n_y] 282 | #If we're at a black pixel 283 | if colors.is_black(next_pixel.color): 284 | self.hit_obstruction() 285 | return 286 | 287 | if colors.is_white(current_pixel.color)\ 288 | or colors.is_white(next_pixel.color): 289 | pass 290 | else: 291 | #Get the operation to do 292 | hue_light_diff = colors.hue_light_diff(current_pixel.color,next_pixel.color) 293 | op_name, op = self.operations[hue_light_diff] 294 | self.debug.writeln(" -> Crossing from (%s,%s), color=%s to (%s,%s), color=%s"\ 295 | % (current_pixel.x, current_pixel.y, current_pixel.color,\ 296 | next_pixel.x, next_pixel.y, next_pixel.color)) 297 | self.debug.writeln(" -> Stack before %s = %s" % (op_name.upper(),self.stack)) 298 | self.debug.writeln(" -> Performing %s" % (op_name.upper())) 299 | op() 300 | self.debug.writeln(" -> Stack after %s = %s" % (op_name.upper(),self.stack)) 301 | self.current_pixel = next_pixel 302 | self.times_stopped = 0 303 | self.switch_cc = True 304 | 305 | def next_pixel_coords(self): 306 | """Returns the coordinates of the next pixel in the direction of the dp.""" 307 | x,y = self.current_pixel.x, self.current_pixel.y 308 | if self.dp == 0: 309 | return (x+1,y) 310 | elif self.dp == 1: 311 | return (x,y+1) 312 | elif self.dp == 2: 313 | return (x-1,y) 314 | elif self.dp == 3: 315 | return (x,y-1) 316 | else: 317 | error_handler.handle_error("The DP managed to become none of 0,1,2,3. This is a bug. Sorry") 318 | 319 | def hit_obstruction(self): 320 | """Handles the case when an obstruction is the next pixel.""" 321 | self.debug.writeln(" -> Hit an obstruction") 322 | self.times_stopped = self.times_stopped + 1 323 | self.debug.writeln(" -> Obstructions Hit = %i" % (self.times_stopped)) 324 | self.step = 0 325 | if (self.times_stopped >= 8): 326 | self.stop_execution() 327 | else: 328 | if self.switch_cc: 329 | self.toggle_cc() 330 | self.switch_cc = False 331 | else: 332 | self.rotate_dp(1) 333 | self.switch_cc = True 334 | 335 | def stop_execution(self): 336 | """Cancels execution of the program.""" 337 | self.debug.writeln("---EXECUTION FINISHED---") 338 | self.finished = True 339 | 340 | def toggle_cc(self): 341 | """Toggles the cc.""" 342 | self.debug.writeln(" -> Toggling CC") 343 | div,mod = divmod(1-self.cc,1) 344 | self.cc = div 345 | 346 | def rotate_dp(self,times=1): 347 | """Rotates the dp by the given number of times.""" 348 | self.debug.writeln(" -> Rotating DP by %s" % times) 349 | div,mod = divmod(self.dp+times,4) 350 | self.dp = mod 351 | 352 | #Below are the actual operation methods for the piet language. 353 | def op_add(self): 354 | """Piet Add operation.""" 355 | if len(self.stack) >= 2: 356 | item1 = self.stack.pop() 357 | item2 = self.stack.pop() 358 | self.stack.append(item1+item2) 359 | 360 | def op_divide(self): 361 | """Piet Divide operation.""" 362 | if len(self.stack) >= 2: 363 | top_item = self.stack.pop() 364 | second_item = self.stack.pop() 365 | self.stack.append(second_item/top_item) 366 | 367 | def op_greater(self): 368 | """Piet Greater operation.""" 369 | if len(self.stack) >= 2: 370 | top_item = self.stack.pop() 371 | second_item = self.stack.pop() 372 | self.stack.append(int(second_item>top_item)) 373 | 374 | def op_duplicate(self): 375 | """Piet Duplicate operation.""" 376 | if len(self.stack) >=1: 377 | item = self.stack[-1] 378 | self.stack.append(item) 379 | 380 | def op_in_char(self): 381 | """Piet IN(CHAR) operation.""" 382 | chr = getchr.get_chr() 383 | self.stack.append(ord(chr)) 384 | 385 | def op_push(self): 386 | """Piet Push operation.""" 387 | self.stack.append(self.current_pixel.set_size) 388 | 389 | def op_subtract(self): 390 | """Piet Subtract operation.""" 391 | if len(self.stack) >= 2: 392 | top_item = self.stack.pop() 393 | second_item = self.stack.pop() 394 | self.stack.append(second_item-top_item) 395 | 396 | def op_mod(self): 397 | """Piet Mod operation.""" 398 | if len(self.stack) >= 2: 399 | top_item = self.stack.pop() 400 | second_item = self.stack.pop() 401 | self.stack.append(second_item % top_item) 402 | 403 | def op_pointer(self): 404 | """Piet Pointer operation.""" 405 | if len(self.stack) >= 1: 406 | item = self.stack.pop() 407 | self.rotate_dp(item) 408 | 409 | def op_roll(self): 410 | """Piet Roll operation.""" 411 | if len(self.stack) >= 2: 412 | num_rolls = self.stack.pop() 413 | depth = self.stack.pop() 414 | if depth >0: 415 | for i in xrange(abs(num_rolls)): 416 | self.roll(depth,num_rolls<0) 417 | 418 | def roll(self,depth,reverse): 419 | """Does a single roll.""" 420 | if depth > len(self.stack): 421 | depth = len(self.stack) 422 | 423 | if reverse: 424 | bottom_item = self.stack[0] 425 | index = depth 426 | for i in xrange(index): 427 | self.stack[i] = self.stack[i+1] 428 | self.stack[index] = bottom_item 429 | else: 430 | top_item = self.stack[-1] 431 | index = len(self.stack)-depth 432 | for i in xrange(len(self.stack)-1,index,-1): 433 | self.stack[i] = self.stack[i-1] 434 | self.stack[index] = top_item 435 | 436 | def op_out_number(self): 437 | """Piet OUT(NUM) operation.""" 438 | if len(self.stack) >=1: 439 | item = self.stack.pop() 440 | sys.stdout.write(str(item)) 441 | sys.stdout.flush() 442 | 443 | def op_pop(self): 444 | """Piet Pop operation.""" 445 | if len(self.stack) >=1: 446 | self.stack.pop() 447 | 448 | def op_multiply(self): 449 | """Piet Multiply operation.""" 450 | if len(self.stack) >= 2: 451 | item1 = self.stack.pop() 452 | item2 = self.stack.pop() 453 | self.stack.append(item1*item2) 454 | 455 | def op_not(self): 456 | """Piet Not operation.""" 457 | if len(self.stack) >= 1: 458 | item = self.stack.pop() 459 | self.stack.append(int(not item)) 460 | 461 | def op_switch(self): 462 | """Piet Switch operation.""" 463 | if len(self.stack) >=1: 464 | item = self.stack.pop() 465 | for i in xrange(item): 466 | self.toggle_cc() 467 | 468 | def op_in_number(self): 469 | """Piet IN(NUM) operation.""" 470 | char = getchr.get_chr() 471 | try: 472 | self.stack.append(int(char)) 473 | except ValueError: 474 | pass 475 | 476 | def op_out_char(self): 477 | """Piet OUT(CHAR) operation.""" 478 | if len(self.stack) >=1: 479 | item = self.stack.pop() 480 | sys.stdout.write(chr(item)) 481 | sys.stdout.flush() 482 | 483 | 484 | class ColorBlock(object): 485 | """Class that represents a color block in a Piet program.""" 486 | def __init__(self,size): 487 | """Initializes new ColorBlock.""" 488 | self.size = size 489 | #boundary_pixels = [[DPR_CCL,DPR_CCR],[DPD_CCL,DPD,CCR] ... etc. 490 | self.boundary_pixels = [[None,None] for i in xrange(4)] 491 | 492 | def update_boundaries(self,pixel): 493 | """Updates the boundary pixels of the current color block given a new pixel.""" 494 | #If a new maximum (right, left) 495 | if self.boundary_pixels[0][0] == None or pixel.x > self.boundary_pixels[0][0].x: 496 | self.boundary_pixels[0][0] = pixel 497 | 498 | #If a new maximum (right, right) 499 | if self.boundary_pixels[0][1] == None or pixel.x >= self.boundary_pixels[0][1].x: 500 | self.boundary_pixels[0][1] = pixel 501 | 502 | #If a new maximum (down, right) 503 | if self.boundary_pixels[1][1] == None or pixel.y > self.boundary_pixels[1][1].y: 504 | self.boundary_pixels[1][1]= pixel 505 | 506 | #If a new maximum (down, left) 507 | if self.boundary_pixels[1][0] == None or pixel.y >= self.boundary_pixels[1][0].y: 508 | self.boundary_pixels[1][0] = pixel 509 | 510 | #If a new maximum (left, right) 511 | if self.boundary_pixels[2][1] == None or pixel.x < self.boundary_pixels[2][1].x: 512 | self.boundary_pixels[2][1] = pixel 513 | 514 | #If a new maximum (left, left) 515 | if self.boundary_pixels[2][0] == None or pixel.x <= self.boundary_pixels[2][0].x: 516 | self.boundary_pixels[2][0] = pixel 517 | 518 | #If a new maximum (up,left) 519 | if self.boundary_pixels[3][0] == None: 520 | self.boundary_pixels[3][0] = pixel 521 | 522 | #If a new maximum (up,right) 523 | if self.boundary_pixels[3][1] == None or pixel.y == self.boundary_pixels[3][1].y: 524 | self.boundary_pixels[3][1] = pixel 525 | 526 | 527 | class Pixel(object): 528 | """Class that represents a pixel in a Piet program (stricly a codel, but 529 | the convention is 1 pixel per codel.""" 530 | 531 | def __init__(self,x,y,color): 532 | """Initializes new Pixel.""" 533 | self.x = x 534 | self.y = y 535 | self.color = color 536 | self.parent = self 537 | self.set_size = 1 538 | self.set_label = -1 539 | 540 | 541 | class ErrorHandler(object): 542 | """Class that handles errors for the interpreter. Does it differently 543 | for UI and command line modes.""" 544 | 545 | def __init__(self, isGUI=False): 546 | """Initalizes new ErrorHandler.""" 547 | self.isGUI = isGUI 548 | 549 | def handle_error(self,message): 550 | """Handles an error with the given message""" 551 | if not self.isGUI: 552 | raise SystemExit("\nError: "+message) 553 | else: 554 | pass 555 | 556 | 557 | def print_usage(): 558 | """Prints usage string for command line.""" 559 | print "Piedit v0.0.1 - Python Piet IDE\n" 560 | print "Usage: interpreter.py [] " 561 | print "options:" 562 | print "\t-h (--help)\t- Prints this help" 563 | print "\t-d (--debug)\t- Prints debug information" 564 | print "\t-m (--maxsteps)\t- Sets maximum steps to execute. This is 10^6 by default. Set to -1 for infinite." 565 | 566 | def getopts(): 567 | """Parses the command line options.""" 568 | try: 569 | return getopt.getopt(sys.argv[1:], "hdm:", ["help","debug","maxsteps="]) 570 | except getopt.GetoptError, err: 571 | print str(err) 572 | print_usage() 573 | sys.exit(2) 574 | 575 | #Run the program if on command line 576 | if __name__ == "__main__": 577 | try: 578 | error_handler = ErrorHandler(False) 579 | interpreter = Interpreter() 580 | if len(sys.argv)>1: 581 | 582 | opts,args = getopts() 583 | for o,a in opts: 584 | if o in ["-h","--help"]: 585 | print_usage() 586 | sys.exit(1) 587 | else: 588 | interpreter.set_opt(o,a) 589 | if len(args) != 1: 590 | print_usage() 591 | sys.exit(2) 592 | interpreter.run_program(args[0]) 593 | else: 594 | print_usage() 595 | except KeyboardInterrupt: 596 | print "\n\nTerminated" 597 | -------------------------------------------------------------------------------- /piedit/interpreter_floodfill.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Interpreter for the Piet programming language. Can be run directly or 4 | imported and used by the GUI""" 5 | 6 | import sys 7 | import gtk 8 | import PIL.Image 9 | import colors 10 | import unionfind 11 | import getchr 12 | 13 | __author__ = "Steven Anderson" 14 | __copyright__ = "Steven Anderson 2008" 15 | __credits__ = ["Steven Anderson"] 16 | __license__ = "GPL" 17 | __version__ = "0.0.1" 18 | __maintainer__ = "Steven Anderson" 19 | __email__ = "steven.james.anderson@googlemail.com" 20 | __status__ = "Production" 21 | 22 | def print_usage(): 23 | """Prints usage string for command line""" 24 | print "Usage: interpreter.py image" 25 | 26 | 27 | class Interpreter(object): 28 | """The Piet interpreter class""" 29 | def __init__(self): 30 | """Sets up object properties""" 31 | self.current_pixel_coords = None 32 | self.dp = 0 33 | self.cc = 0 34 | self.switch_cc = True 35 | self.step = 0 #0 for just moved into color block, 1 for moved to edge 36 | self.times_stopped = 0 37 | self.max_steps = 1000000 38 | self.stack = [] 39 | self.block_size = 0 40 | self.boundary_pixel_coords = None 41 | #Indexed by hue and light change 42 | self.operations = { 43 | (1,0):("Add",self.op_add), 44 | (2,0):("Divide",self.op_divide), 45 | (3,0):("Greater",self.op_greater), 46 | (4,0):("Duplicate",self.op_duplicate), 47 | (5,0):("IN(char)",self.op_in_char), 48 | (0,1):("Push",self.op_push), 49 | (1,1):("Subtract",self.op_subtract), 50 | (2,1):("Mod",self.op_mod), 51 | (3,1):("Pointer",self.op_pointer), 52 | (4,1):("Roll",self.op_roll), 53 | (5,1):("OUT(Number)",self.op_out_number), 54 | (0,2):("Pop",self.op_pop), 55 | (1,2):("Multiply",self.op_multiply), 56 | (2,2):("Not",self.op_not), 57 | (3,2):("Switch",self.op_switch), 58 | (4,2):("IN(Number)",self.op_in_number), 59 | (5,2):("OUT(Char)",self.op_out_char), 60 | } 61 | 62 | def run_program(self,path): 63 | """Runs a program at the given path""" 64 | print "Loading image" 65 | self.load_image(path) 66 | print "Starting execution" 67 | self.start_execution() 68 | 69 | def load_image(self,path): 70 | """Loads an image and puts pixel data into self.pixels""" 71 | try: 72 | self.image = PIL.Image.open(path) 73 | if self.image.mode != "RGB": 74 | self.image = self.image.convert("RGB") 75 | except IOError: 76 | raise IOError, "IMAGE_NOT_LOADED" 77 | 78 | (self.width, self.height) = self.image.size 79 | rawpixels = self.image.getdata() 80 | self.pixels = dict([((x,y),colors.rgb_to_hex(rawpixels[y*(self.width)+x])) for x in range(self.width) for y in range(self.height)]) 81 | #for x in range(self.width): 82 | # for y in range(self.height): 83 | # print "Pixel: (%s,%s) - %s" % (x,y,self.pixels[(x,y)]) 84 | self.current_pixel_coords = (0,0) 85 | 86 | def start_execution(self): 87 | """Starts the execution of the program""" 88 | for i in range(self.max_steps): 89 | self.do_next_step() 90 | 91 | def do_next_step(self): 92 | """Executes a step in the program.""" 93 | if self.step == 0: 94 | self.step = 1 95 | self.move_within_block() 96 | #print "DP: %s, CC:%s" % (self.dp, self.cc) 97 | #print "Moved within block to %s,%s" % (self.current_pixel_coords[0],self.current_pixel_coords[1]) 98 | elif self.step == 1: 99 | self.step = 0 100 | self.move_out_of_block() 101 | #print "DP: %s, CC:%s" % (self.dp, self.cc) 102 | #print "Moved out of block to %s,%s" % (self.current_pixel_coords[0],self.current_pixel_coords[1]) 103 | else: 104 | error_handler.handle_error("The step wasn't 0 or 1. That should never happen. This must be a bug in my code. Sorry") 105 | 106 | def move_within_block(self): 107 | """Moves to the border pixel within the current color block""" 108 | if colors.is_white(self.pixels[self.current_pixel_coords]): 109 | self.move_within_white() 110 | else: 111 | self.move_within_color() 112 | 113 | def move_within_white(self): 114 | """Slides through a white block until an obstruction or a 115 | new color block is found""" 116 | next_pixel = self.pixels[self.get_next_pixel_coords()] 117 | 118 | while colors.is_white(next_pixel): 119 | self.current_pixel_coords = self.get_next_pixel_coords() 120 | x,y = self.get_next_pixel_coords() 121 | 122 | if (x<0 or y<0 or x>=self.width or y>=self.height): 123 | self.hit_obstruction() 124 | return 125 | 126 | next_pixel = self.pixels[(x,y)] 127 | 128 | def move_within_color(self): 129 | """Moves within a color block to the required pixel 130 | at the max dp/cc direction""" 131 | coords = self.current_pixel_coords 132 | color = self.pixels[coords] 133 | #print "Start - looking for ",color 134 | self.block_size = 0 135 | self.boundary_pixel_coords = None 136 | self.floodfill(coords,color,"WTF") 137 | self.block_size = 0 138 | self.boundary_pixel_coords = None 139 | self.floodfill(coords,"WTF",color) 140 | self.current_pixel_coords = self.boundary_pixel_coords 141 | #print self.boundary_pixel_coords 142 | 143 | def floodfill(self,pixel_coords,from_color,to_color): 144 | """Recurively fills a color block with another color""" 145 | #print "Checking ",pixel_coords 146 | if pixel_coords[0] < 0\ 147 | or pixel_coords[0] >= self.width\ 148 | or pixel_coords[1] < 0\ 149 | or pixel_coords[1] >= self.height: 150 | return 151 | 152 | pixel = self.pixels[pixel_coords] 153 | #print "Looking at ",pixel_coords,"Color = ",pixel,"From = ", from_color,"Target = ", to_color 154 | if pixel == to_color\ 155 | or pixel != from_color: 156 | #print "Nope" 157 | return 158 | else: 159 | #print "Yep" 160 | self.pixels[pixel_coords] = to_color 161 | self.block_size = self.block_size + 1 162 | self.update_boundary_pixel(pixel_coords) 163 | #print "Boundary: ",self.boundary_pixel_coords 164 | self.floodfill((pixel_coords[0]-1,pixel_coords[1]),from_color,to_color) 165 | self.floodfill((pixel_coords[0]+1,pixel_coords[1]),from_color,to_color) 166 | self.floodfill((pixel_coords[0],pixel_coords[1]-1),from_color,to_color) 167 | self.floodfill((pixel_coords[0],pixel_coords[1]+1),from_color,to_color) 168 | 169 | def update_boundary_pixel(self,pixel_coords): 170 | """Modifies the boundary pixel for the current dp and cc""" 171 | if self.boundary_pixel_coords == None: 172 | self.boundary_pixel_coords = pixel_coords 173 | return 174 | 175 | x,y = pixel_coords 176 | c_x,c_y = self.boundary_pixel_coords 177 | if self.dp == 0: 178 | if x > c_x: 179 | self.boundary_pixel_coords = (x,y) 180 | elif x == c_x: 181 | if (self.cc == 0 and y < c_y)\ 182 | or (self.cc == 1 and y > c_y): 183 | self.boundary_pixel_coords = (x,y) 184 | elif self.dp == 1: 185 | if y > c_y: 186 | self.boundary_pixel_coords = (x,y) 187 | elif y == c_y: 188 | if (self.cc == 0 and x > c_x)\ 189 | or (self.cc == 1 and x < c_x): 190 | self.boundary_pixel_coords = (x,y) 191 | elif self.dp == 2: 192 | if x < c_x: 193 | self.boundary_pixel_coords = (x,y) 194 | elif x == c_x: 195 | if (self.cc == 0 and y > c_y)\ 196 | or (self.cc == 1 and y < c_y): 197 | self.boundary_pixel_coords = (x,y) 198 | elif self.dp == 3: 199 | if y < c_y: 200 | self.boundary_pixel_coords = (x,y) 201 | elif y == c_y: 202 | if (self.cc == 0 and x < c_x)\ 203 | or (self.cc == 1 and x > c_x): 204 | self.boundary_pixel_coords = (x,y) 205 | 206 | def move_out_of_block(self): 207 | """Moves out of a color block and into the next color block, performing 208 | the operation if necessary""" 209 | 210 | #If we're at a wall 211 | x,y = self.current_pixel_coords 212 | if (self.dp == 0 and x >= self.width-1)\ 213 | or (self.dp == 1 and y >= self.height-1)\ 214 | or (self.dp == 2 and x <= 0)\ 215 | or (self.dp == 3 and y <= 0): 216 | self.hit_obstruction() 217 | return 218 | 219 | current_pixel = self.pixels[(x,y)] 220 | next_pixel_coords = self.get_next_pixel_coords() 221 | next_pixel = self.pixels[next_pixel_coords] 222 | #If we're at a black pixel 223 | if colors.is_black(next_pixel): 224 | self.hit_obstruction() 225 | return 226 | 227 | if colors.is_white(current_pixel)\ 228 | or colors.is_white(next_pixel): 229 | pass 230 | else: 231 | #Get the operation to do 232 | hue_light_diff = colors.hue_light_diff(current_pixel,next_pixel) 233 | op_name, op = self.operations[hue_light_diff] 234 | #print op_name 235 | op() 236 | self.current_pixel_coords = next_pixel_coords 237 | self.times_stopped = 0 238 | self.switch_cc = True 239 | 240 | 241 | def get_next_pixel_coords(self): 242 | """Returns the next pixel in the direction of the dp""" 243 | x,y = self.current_pixel_coords 244 | if self.dp == 0: 245 | return (x+1,y) 246 | elif self.dp == 1: 247 | return (x,y+1) 248 | elif self.dp == 2: 249 | return (x-1,y) 250 | elif self.dp == 3: 251 | return (x,y-1) 252 | else: 253 | error_handler.handle_error("The DP managed to become none of 0,1,2,3. This is a bug. Sorry") 254 | 255 | 256 | def hit_obstruction(self): 257 | """Handles the case when an obstruction is the next pixel.""" 258 | self.times_stopped = self.times_stopped + 1 259 | self.step = 0 260 | if (self.times_stopped >= 8): 261 | self.stop_execution() 262 | else: 263 | if self.switch_cc: 264 | self.toggle_cc() 265 | self.switch_cc = False 266 | else: 267 | self.rotate_dp(1) 268 | self.switch_cc = True 269 | 270 | def stop_execution(self): 271 | """Cancels execution of the program""" 272 | print "\nExecution finished" 273 | sys.exit(1) 274 | 275 | def toggle_cc(self): 276 | """Toggles the cc""" 277 | #print "Toggling cc" 278 | div,mod = divmod(1-self.cc,1) 279 | self.cc = div 280 | 281 | def rotate_dp(self,times=1): 282 | """Rotates the dp by the given number of times""" 283 | #print "Rotating dp" 284 | div,mod = divmod(self.dp+times,4) 285 | self.dp = mod 286 | 287 | #Below are the actual operation methods for the piet language. 288 | def op_add(self): 289 | """Piet Add operation""" 290 | if len(self.stack) >= 2: 291 | item1 = self.stack.pop() 292 | item2 = self.stack.pop() 293 | self.stack.append(item1+item2) 294 | 295 | def op_divide(self): 296 | """Piet Divide operation""" 297 | if len(self.stack) >= 2: 298 | top_item = self.stack.pop() 299 | second_item = self.stack.pop() 300 | self.stack.append(second_item//top_item) 301 | 302 | def op_greater(self): 303 | """Piet Greater operation""" 304 | if len(self.stack) >= 2: 305 | top_item = self.stack.pop() 306 | second_item = self.stack.pop() 307 | self.stack.append(int(second_item>top_item)) 308 | 309 | def op_duplicate(self): 310 | """Piet Duplicate operation""" 311 | if len(self.stack) >=1: 312 | item = self.stack[-1] 313 | self.stack.append(item) 314 | 315 | def op_in_char(self): 316 | """Piet IN(CHAR) operation""" 317 | chr = getchr.get_chr() 318 | self.stack.append(ord(chr)) 319 | 320 | def op_push(self): 321 | """Piet Push operation""" 322 | self.stack.append(self.block_size) 323 | 324 | def op_subtract(self): 325 | """Piet Subtract operation""" 326 | if len(self.stack) >= 2: 327 | top_item = self.stack.pop() 328 | second_item = self.stack.pop() 329 | self.stack.append(second_item-top_item) 330 | 331 | def op_mod(self): 332 | """Piet Mod operation""" 333 | if len(self.stack) >= 2: 334 | top_item = self.stack.pop() 335 | second_item = self.stack.pop() 336 | self.stack.append(second_item % top_item) 337 | 338 | def op_pointer(self): 339 | """Piet Pointer operation""" 340 | if len(self.stack) >= 1: 341 | item = self.stack.pop() 342 | self.rotate_dp(item) 343 | 344 | def op_roll(self): 345 | """Piet Roll operation""" 346 | if len(self.stack) >= 2: 347 | num_rolls = self.stack.pop() 348 | depth = self.stack.pop() 349 | if depth >0: 350 | for i in range(abs(num_rolls)): 351 | self.roll(depth,num_rolls<0) 352 | 353 | def roll(self,depth,reverse): 354 | """Does a single roll""" 355 | if depth > len(self.stack): 356 | depth = len(self.stack) 357 | 358 | if reverse: 359 | bottom_item = self.stack[0] 360 | index = depth 361 | for i in range(index): 362 | self.stack[i] = self.stack[i+1] 363 | self.stack[index] = bottom_item 364 | else: 365 | top_item = self.stack[-1] 366 | index = len(self.stack)-depth 367 | for i in range(len(self.stack)-1,index,-1): 368 | self.stack[i] = self.stack[i-1] 369 | self.stack[index] = top_item 370 | 371 | def op_out_number(self): 372 | """Piet OUT(NUM) operation""" 373 | if len(self.stack) >=1: 374 | item = self.stack.pop() 375 | sys.stdout.write(str(item)) 376 | 377 | def op_pop(self): 378 | """Piet Pop operation""" 379 | if len(self.stack) >=1: 380 | self.stack.pop() 381 | 382 | def op_multiply(self): 383 | """Piet Multiply operation""" 384 | if len(self.stack) >= 2: 385 | item1 = self.stack.pop() 386 | item2 = self.stack.pop() 387 | self.stack.append(item1*item2) 388 | 389 | def op_not(self): 390 | """Piet Not operation""" 391 | if len(self.stack) >= 1: 392 | item = self.stack.pop() 393 | self.stack.append(int(not item)) 394 | 395 | def op_switch(self): 396 | """Piet Switch operation""" 397 | if len(self.stack) >=1: 398 | item = self.stack.pop() 399 | for i in range(item): 400 | self.toggle_cc() 401 | 402 | def op_in_number(self): 403 | """Piet IN(NUM) operation""" 404 | char = getchr.get_chr() 405 | try: 406 | self.stack.append(int(char)) 407 | except ValueError: 408 | pass 409 | 410 | def op_out_char(self): 411 | """Piet OUT(CHAR) operation""" 412 | if len(self.stack) >=1: 413 | item = self.stack.pop() 414 | sys.stdout.write(chr(item)) 415 | 416 | 417 | class ErrorHandler(object): 418 | """Class that handles errors for the interpreter. Does it differently 419 | for UI and command line modes""" 420 | 421 | def __init__(self, isGUI=False): 422 | """Sets object properties""" 423 | self.isGUI = isGUI 424 | 425 | def handle_error(self,message): 426 | """Handles an error with the given message""" 427 | if not self.isGUI: 428 | raise SystemExit("\nError: "+message) 429 | else: 430 | pass 431 | 432 | #Run the program if on command line 433 | if __name__ == "__main__": 434 | error_handler = ErrorHandler(False) 435 | interpreter = Interpreter() 436 | if len(sys.argv)>1: 437 | interpreter.run_program(sys.argv[1]) 438 | else: 439 | print_usage() -------------------------------------------------------------------------------- /piedit/ui.py: -------------------------------------------------------------------------------- 1 | """UI classes for the piedit project""" 2 | 3 | import sys 4 | import os 5 | import threading 6 | import time 7 | import pygtk 8 | import gtk 9 | import gnome.ui 10 | import string 11 | import PIL.Image 12 | import piedit.colors 13 | import piedit.interpreter 14 | import piedit.debug 15 | pygtk.require("2.0") 16 | 17 | __author__ = "Steven Anderson" 18 | __copyright__ = "Steven Anderson 2008" 19 | __credits__ = ["Steven Anderson"] 20 | __license__ = "GPL" 21 | __version__ = "0.0.1" 22 | __maintainer__ = "Steven Anderson" 23 | __email__ = "steven.james.anderson@googlemail.com" 24 | __status__ = "Production" 25 | 26 | 27 | class InterpreterThread(threading.Thread): 28 | def __init__(self,pixels,width,height,callback=None,debug=False): 29 | self.should_stop = False 30 | self.interpreter = piedit.interpreter.Interpreter(thread=self) 31 | self.interpreter.debug.DEBUG = debug 32 | self.pixels = pixels 33 | self.width = width 34 | self.height = height 35 | self.callback = callback 36 | threading.Thread.__init__(self) 37 | 38 | def run(self): 39 | self.interpreter.run_program(pixels=self.pixels,width=self.width,height=self.height) 40 | self.callback(self.should_stop) 41 | 42 | def stop(self): 43 | self.should_stop = True 44 | 45 | class Handlers(object): 46 | """Defines the signal handlers for the ui""" 47 | 48 | def __init__(self,ui): 49 | """Sets up object properties""" 50 | self._ui = ui 51 | self.file_filter = gtk.FileFilter() 52 | self.file_filter.add_pattern("*.png") 53 | self.file_filter.set_name("PNG Files") 54 | 55 | def on_mainApp_delete_event(self, *args): 56 | """Handler for application close""" 57 | if self._ui.save_changes(): 58 | try: 59 | self.interpreter_thread.stop() 60 | except: 61 | pass 62 | gtk.main_quit() 63 | 64 | #File Menu 65 | def on_fileNewMenuItem_activate(self, *args): 66 | """Handler for File|New menu item""" 67 | if self._ui.save_changes(): 68 | self._ui.clear_image(self._ui.default_width,self._ui.default_height) 69 | self._ui.draw_program_table() 70 | 71 | def on_fileOpenMenuItem_activate(self, *args): 72 | """Handler for File|Open menu item""" 73 | if self._ui.save_changes(): 74 | fileChooser = gtk.FileChooserDialog( 75 | title="Open File", 76 | action=gtk.FILE_CHOOSER_ACTION_OPEN, 77 | buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) 78 | fileChooser.add_filter(self.file_filter) 79 | response = fileChooser.run() 80 | if response == gtk.RESPONSE_OK: 81 | path = fileChooser.get_filename() 82 | self._ui.load_image(path) 83 | fileChooser.destroy() 84 | 85 | def on_fileSaveMenuItem_activate(self, *args): 86 | """Handler for File|Save menu item""" 87 | if self._ui.current_file is None: 88 | return self.on_fileSaveAsMenuItem_activate(args) 89 | else: 90 | self._ui.save_image(self._ui.current_file) 91 | return True 92 | 93 | def on_fileSaveAsMenuItem_activate(self, *args): 94 | """Handler for File|Save As menu item""" 95 | fileChooser = gtk.FileChooserDialog( 96 | title="Save File", 97 | action=gtk.FILE_CHOOSER_ACTION_SAVE, 98 | buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) 99 | fileChooser.add_filter(self.file_filter) 100 | fileChooser.set_do_overwrite_confirmation(True) 101 | response = fileChooser.run() 102 | if response == gtk.RESPONSE_OK: 103 | filename = fileChooser.get_filename() 104 | if (not filename == None) and (not filename.endswith(".png")): 105 | filename = filename + ".png" 106 | self._ui.save_image(filename) 107 | fileChooser.destroy() 108 | return True 109 | else: 110 | fileChooser.destroy() 111 | return False 112 | 113 | def on_fileQuitMenuItem_activate(self, *args): 114 | """Handler for File|Quit menu item""" 115 | if self._ui.save_changes(): 116 | gtk.main_quit() 117 | 118 | #Run Menu 119 | def on_runRunMenuItem_activate(self,*args): 120 | """Handler for Run|Run menu item""" 121 | self.run_mode = "Run" 122 | self.set_run_menu(running=True,status="Running...") 123 | self.interpreter_thread = InterpreterThread(pixels=self._ui.pixels,width=self._ui.width,height=self._ui.height,callback=self.thread_end_callback,debug=False) 124 | self.interpreter_thread.start() 125 | 126 | def on_runDebugMenuItem_activate(self,*args): 127 | """Handler for Run|Debug menu item""" 128 | self.run_mode = "Debug" 129 | self.set_run_menu(running=True,status="Debugging...",debug=True) 130 | self._ui.interpreter = piedit.interpreter.Interpreter() 131 | self._ui.interpreter.debug.DEBUG = True 132 | self._ui.interpreter.run_program(pixels=self._ui.pixels,width=self._ui.width,height=self._ui.height,start=False) 133 | self._ui.highlight_pixel(0,0) 134 | 135 | def on_runStepMenuItem_activate(self,*args): 136 | if self._ui.interpreter.do_next_debug_step(): 137 | self._ui.highlight_pixel(self._ui.interpreter.current_pixel.x,self._ui.interpreter.current_pixel.y) 138 | else: 139 | self.set_run_menu(running=False,status="Complete") 140 | 141 | def on_runStopMenuItem_activate(self,*args): 142 | if self.run_mode == "Run": 143 | self.interpreter_thread.stop() 144 | elif self.run_mode == "Debug": 145 | self.thread_end_callback(self._ui.interpreter.debug.DEBUG) 146 | 147 | def set_run_menu(self,running,status,debug=False): 148 | if running: 149 | self._ui.gladeui.get_widget("runRunMenuItem").set_sensitive(False) 150 | self._ui.gladeui.get_widget("runDebugMenuItem").set_sensitive(False) 151 | self._ui.gladeui.get_widget("runStopMenuItem").set_sensitive(True) 152 | self._ui.gladeui.get_widget("runStepMenuItem").set_sensitive(debug) 153 | 154 | self._ui.gladeui.get_widget("toolbarRun").set_sensitive(False) 155 | self._ui.gladeui.get_widget("toolbarDebug").set_sensitive(False) 156 | self._ui.gladeui.get_widget("toolbarStop").set_sensitive(True) 157 | self._ui.gladeui.get_widget("toolbarStep").set_sensitive(debug) 158 | else: 159 | self._ui.gladeui.get_widget("runStopMenuItem").set_sensitive(False) 160 | self._ui.gladeui.get_widget("runStepMenuItem").set_sensitive(False) 161 | self._ui.gladeui.get_widget("runRunMenuItem").set_sensitive(True) 162 | self._ui.gladeui.get_widget("runDebugMenuItem").set_sensitive(True) 163 | 164 | self._ui.gladeui.get_widget("toolbarStop").set_sensitive(False) 165 | self._ui.gladeui.get_widget("toolbarStep").set_sensitive(False) 166 | self._ui.gladeui.get_widget("toolbarRun").set_sensitive(True) 167 | self._ui.gladeui.get_widget("toolbarDebug").set_sensitive(True) 168 | 169 | self._ui.gladeui.get_widget("statusBar").set_status(status) 170 | 171 | def thread_end_callback(self,was_cancelled): 172 | """Function to be called when the thread stops executing.""" 173 | if was_cancelled: 174 | self.set_run_menu(running=False,status="Cancelled") 175 | else: 176 | self.set_run_menu(running=False,status="Complete") 177 | #Help Menu 178 | def on_helpHelpMenuItem_activate(self,*args): 179 | print "Help | Help" 180 | 181 | def on_helpAboutMenuItem_activate(self,*args): 182 | """Handler for Help|About menu item""" 183 | print "Help About" 184 | 185 | #Toolbar 186 | def on_toolbarNew_clicked(self,*args): 187 | return self.on_fileNewMenuItem_activate(*args) 188 | 189 | def on_toolbarOpen_clicked(self,*args): 190 | return self.on_fileOpenMenuItem_activate(*args) 191 | 192 | def on_toolbarSave_clicked(self,*args): 193 | return self.on_fileSaveMenuItem_activate(*args) 194 | 195 | def on_toolbarRun_clicked(self,*args): 196 | return self.on_runRunMenuItem_activate(*args) 197 | 198 | def on_toolbarDebug_clicked(self,*args): 199 | return self.on_runDebugMenuItem_activate(*args) 200 | 201 | def on_toolbarStep_clicked(self,*args): 202 | return self.on_runStepMenuItem_activate(*args) 203 | 204 | def on_toolbarStop_clicked(self,*args): 205 | return self.on_runStopMenuItem_activate(*args) 206 | 207 | def on_toolbarHelp_clicked(self,*args): 208 | return self.on_helpHelpMenuItem_activate(*args) 209 | 210 | #Other handlers 211 | def on_programTable_button_press_event(self, widget, event): 212 | self._ui.set_pixel_color(int(event.x),int(event.y)) 213 | 214 | def on_codelColorEventBox_clicked(self, widget, event): 215 | """Handler for clicking a codel color event box""" 216 | self._ui.set_selected_color(widget) 217 | 218 | def on_programTable_expose_event(self, widget, event): 219 | #Add event boxes to program table 220 | self._ui.draw_program_table() 221 | 222 | def on_increaseWidthButton_clicked(self,*args): 223 | self._ui.increase_width() 224 | 225 | def on_decreaseWidthButton_clicked(self,*args): 226 | self._ui.decrease_width() 227 | 228 | def on_increaseHeightButton_clicked(self,*args): 229 | self._ui.increase_height() 230 | 231 | def on_decreaseHeightButton_clicked(self,*args): 232 | self._ui.decrease_height() 233 | 234 | 235 | class UI(object): 236 | """Wrapper class for the UI. Provides functions to do things in the UI""" 237 | 238 | def __init__(self,gladeui): 239 | """Sets up the object properties""" 240 | self.changes_made = False 241 | self.selected_color = None 242 | self.current_file = None 243 | self.gladeui = gladeui 244 | self.selected_color_widget = None 245 | 246 | self.default_height = 10 247 | self.default_width = 10 248 | self.width = self.default_width 249 | self.height = self.default_height 250 | self.max_width = 1000 251 | self.max_height = 1000 252 | self.current_pixel = None 253 | 254 | self.handlers = Handlers(self) 255 | self.gladeui.signal_autoconnect(self.handlers) 256 | self.message_handler = MessageHandler(self) 257 | self.initialise_ui() 258 | 259 | def save_image(self,path): 260 | """Saves the current program table to an image""" 261 | image = PIL.Image.new("RGB",(self.width,self.height)) 262 | image.putdata([piedit.colors.hex_to_rgb(p) for p in self.pixels]) 263 | image.save(path, "PNG") 264 | self.message_handler.handle_message("FILE_SAVED") 265 | self.set_current_file(path) 266 | self.set_changes_made(False) 267 | self.set_window_title(os.path.basename(path)) 268 | 269 | def load_image(self,path): 270 | """Loads an image from file and displays it in the program table""" 271 | try: 272 | image = PIL.Image.open(path) 273 | if image.mode != "RGB": 274 | image = image.convert("RGB") 275 | except IOError: 276 | self.message_handler.handle_error("FILE_NOT_LOADED") 277 | (self.width, self.height) = image.size 278 | if self.width>self.max_width or self.height>self.max_height: 279 | self.message_handler.handle_error("IMAGE_TOO_BIG") 280 | else: 281 | self.clear_image(self.width,self.height) 282 | self.pixels = [piedit.colors.rgb_to_hex(rgb) for rgb in image.getdata()] 283 | self.draw_program_table() 284 | self.set_current_file(path) 285 | self.set_changes_made(False) 286 | self.set_window_title(os.path.basename(path)) 287 | 288 | def clear_image(self,width,height): 289 | """Clears the program table, i.e. fills with all whites""" 290 | self.height=height 291 | self.width=width 292 | self.gladeui.get_widget("programTable").window.clear() 293 | self.pixels = [piedit.colors.white for y in xrange(self.height) for x in xrange(self.width)] 294 | self.current_pixel=None 295 | self.set_current_file(None) 296 | self.set_window_title("Untitled.png") 297 | self.set_changes_made(False) 298 | 299 | def set_pixel_color(self,x,y): 300 | """Sets the color of a program table pixel to the currently selected color""" 301 | program_table = self.gladeui.get_widget("programTable").window 302 | pt_width, pt_height = program_table.get_size() 303 | width_per_pixel = pt_width/self.width 304 | height_per_pixel = pt_height/self.height 305 | extra_width_cutoff = self.width-(pt_width%(width_per_pixel*self.width)) 306 | extra_height_cutoff = self.height-(pt_height%(height_per_pixel*self.height)) 307 | 308 | x_sum = 0 309 | x_counter = 0 310 | while x_sumextra_width_cutoff)*(x-extra_width_cutoff) 455 | w = width_per_pixel + int(x>=extra_width_cutoff) 456 | t = y*height_per_pixel + int(y>extra_height_cutoff)*(y-extra_height_cutoff) 457 | h = height_per_pixel + int(y>=extra_height_cutoff) 458 | 459 | if (x,y) == self.current_pixel: 460 | gc.set_line_attributes(2,gtk.gdk.LINE_SOLID,gtk.gdk.CAP_BUTT,gtk.gdk.JOIN_MITER) 461 | l=l+1 462 | w=w-2 463 | t=t+1 464 | h=h-2 465 | else: 466 | gc.set_line_attributes(1,gtk.gdk.LINE_SOLID,gtk.gdk.CAP_BUTT,gtk.gdk.JOIN_MITER) 467 | program_table.draw_rectangle(gc,False,l,t,w,h) 468 | try: 469 | pixel = self.pixels[y*self.width+x] 470 | fg = colormap.alloc_color(pixel) 471 | gc.set_foreground(fg) 472 | except AttributeError: 473 | gc.set_foreground(white) 474 | program_table.draw_rectangle(gc,True,l+1,t+1,w-1,h-1) 475 | 476 | def highlight_pixel(self,x,y): 477 | if self.current_pixel == None: 478 | old_x,old_y = 0,0 479 | else: 480 | old_x,old_y = self.current_pixel 481 | self.current_pixel = (x,y) 482 | self.draw_program_table([old_x,x],[old_y,y]) 483 | self.draw_program_table([x],[y]) 484 | 485 | def increase_width(self): 486 | for i,y in enumerate(xrange(self.height)): 487 | self.pixels.insert((y*self.width+i)+self.width,piedit.colors.white) 488 | self.width = self.width+1 489 | self.draw_program_table() 490 | 491 | def decrease_width(self): 492 | if self.width > 1: 493 | for i,y in enumerate(xrange(self.height)): 494 | del self.pixels[(y*self.width)+self.width-1-i] 495 | self.width = self.width-1 496 | self.draw_program_table() 497 | 498 | def increase_height(self): 499 | self.pixels.extend([piedit.colors.white for x in xrange(self.width)]) 500 | self.height = self.height+1 501 | self.draw_program_table() 502 | 503 | def decrease_height(self): 504 | if self.height > 1: 505 | self.pixels[self.width*self.height-self.width:] = [] 506 | self.height = self.height-1 507 | self.draw_program_table() 508 | 509 | 510 | class MessageHandler(object): 511 | """Class to handle errors and display them to the user""" 512 | def __init__(self,ui): 513 | """Sets up error messages""" 514 | self._ui = ui 515 | self.error_messages = { 516 | "IMAGE_TOO_BIG":"The image couldn't be loaded because it's too big.\n" 517 | +"At present we only support images up to 10x10 pixels.\n" 518 | +"Sorry :(", 519 | "FILE_NOT_LOADED":"The image could not be loaded.\n" 520 | +"Either the file doesn't exist, or it wasn't\n" 521 | +"recognised as an image"} 522 | self.messages = { 523 | "FILE_SAVED":"File saved successfully", 524 | "SAVE_CHANGES":"Would you like to save changes to the current file?"} 525 | 526 | def handle_error(self,error_type): 527 | """Handles an error. Displays an error dialog to the user""" 528 | msgbox = gtk.MessageDialog( 529 | parent=self._ui.gladeui.get_widget("mainWindow"), 530 | flags=gtk.DIALOG_MODAL, 531 | type=gtk.MESSAGE_ERROR, 532 | buttons=gtk.BUTTONS_OK, 533 | message_format=self.error_messages[error_type]) 534 | msgbox.run() 535 | msgbox.destroy() 536 | 537 | def handle_message(self,message_type): 538 | """Handles a message. Displays an information dialog to the user""" 539 | msgbox = gtk.MessageDialog( 540 | parent=self._ui.gladeui.get_widget("mainWindow"), 541 | flags=gtk.DIALOG_MODAL, 542 | type=gtk.MESSAGE_INFO, 543 | buttons=gtk.BUTTONS_OK, 544 | message_format=self.messages[message_type]) 545 | msgbox.run() 546 | msgbox.destroy() 547 | 548 | def handle_save_msgbox(self): 549 | """Presents the save changes prompt to the user""" 550 | msgbox = gtk.MessageDialog( 551 | parent=self._ui.gladeui.get_widget("mainWindow"), 552 | flags=gtk.DIALOG_MODAL, 553 | type=gtk.MESSAGE_QUESTION, 554 | buttons=gtk.BUTTONS_YES_NO, 555 | message_format=self.messages["SAVE_CHANGES"]) 556 | response = msgbox.run() 557 | msgbox.destroy() 558 | if response == gtk.RESPONSE_YES: 559 | return self._ui.handlers.on_fileSaveMenuItem_activate() 560 | elif response == gtk.RESPONSE_NO: 561 | return True 562 | else: 563 | return False 564 | -------------------------------------------------------------------------------- /piedit/unionfind.py: -------------------------------------------------------------------------------- 1 | """Module for the union-find functions for the piet interpreter""" 2 | 3 | import sys 4 | 5 | __author__ = "Steven Anderson" 6 | __copyright__ = "Steven Anderson 2008" 7 | __credits__ = ["Steven Anderson"] 8 | __license__ = "GPL" 9 | __version__ = "0.0.1" 10 | __maintainer__ = "Steven Anderson" 11 | __email__ = "steven.james.anderson@googlemail.com" 12 | __status__ = "Production" 13 | 14 | def union(parent,child): 15 | """Performs a union by attaching the root node of the smaller set to the 16 | root node of the larger set""" 17 | if parent.set_size < child.set_size: 18 | child, parent = parent, child 19 | 20 | parent_head = find(parent) 21 | child_head = find(child) 22 | 23 | if parent_head == child_head: 24 | return 25 | 26 | child_head.parent = parent_head 27 | child_head.set_label = parent_head.set_label 28 | parent_head.set_size = parent_head.set_size + child_head.set_size 29 | 30 | def find(item): 31 | """Finds the root node of a given item, attaching it directly to its root node 32 | on the way up""" 33 | if item.parent == item: 34 | return item 35 | else: 36 | item.parent = find(item.parent) 37 | return item.parent -------------------------------------------------------------------------------- /programs/99bottles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/99bottles.png -------------------------------------------------------------------------------- /programs/Piet-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/Piet-1.png -------------------------------------------------------------------------------- /programs/Piet_hello2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/Piet_hello2.png -------------------------------------------------------------------------------- /programs/alpha_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/alpha_filled.png -------------------------------------------------------------------------------- /programs/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/hello.png -------------------------------------------------------------------------------- /programs/japh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/japh.png -------------------------------------------------------------------------------- /programs/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/new.png -------------------------------------------------------------------------------- /programs/piet_pi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/piet_pi.png -------------------------------------------------------------------------------- /programs/primetest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/primetest.png -------------------------------------------------------------------------------- /programs/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whilefalse/Piedit/d6398df0e3aba5b738fc5473e35359e79fcd5a8f/programs/test.png --------------------------------------------------------------------------------