├── .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 |
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
--------------------------------------------------------------------------------