{"id":596,"date":"2010-03-03T23:51:32","date_gmt":"2010-03-03T13:51:32","guid":{"rendered":"http:\/\/www.erisian.com.au\/wordpress\/?p=596"},"modified":"2010-03-03T23:51:32","modified_gmt":"2010-03-03T13:51:32","slug":"pygame","status":"publish","type":"post","link":"https:\/\/www.erisian.com.au\/wordpress\/2010\/03\/03\/pygame","title":{"rendered":"PyGame"},"content":{"rendered":"<p>At this year&#8217;s linux.conf.au I decided it was high time I learnt how to program simple graphics. The use case I had in mind in particular was simulating\/visualising resource transportation in a grid based real-time strategy game like Widelands, but really I&#8217;ve been a bit annoyed that I haven&#8217;t been able to do basic graphics on Linux at a similar level to what I used to be able to do with <a href=\"http:\/\/en.wikipedia.org\/wiki\/AMOS_%28programming_language%29\">AMOS Basic<\/a> on the Amiga or <a href=\"http:\/\/en.wikipedia.org\/wiki\/QuickBASIC\">QuickBASIC<\/a> on the PC back in the early &#8217;90s. I used to be able to write a clone of <a href=\"http:\/\/en.wikipedia.org\/wiki\/QBasic\">QBasic<\/a>&#8216;s &#8220;nibbles&#8221; in a couple of hours, with just &#8220;print&#8221; and &#8220;locate&#8221; and a bunch of logic; doing the same today would mean figuring out all the stuff required to setup ncurses, and well, augh.<\/p>\n<p>Or doing it as a webapp, of course, and that tends to involve even more setup.<\/p>\n<p>So with <a href=\"http:\/\/cworth.org\/blog\/\">Carl<\/a> and <a href=\"http:\/\/keithp.com\/blog\">Keith<\/a> in attendance I figured <a href=\"http:\/\/www.cairographics.org\/\">Cairo<\/a> would be a good bet, but the documentation that&#8217;s out there for python-cairo didn&#8217;t seem very helpful as far as making it trivially easy (probably in part because cairo&#8217;s kinda multi-targetable and kinda powerful). Carl&#8217;s suggestion was python-gtk, which uses cairo as it&#8217;s drawing engine; I didn&#8217;t actually try that though &#8212; gtk is a bit more than I want to have to think about, in theory anyway. I thought about trying out nickle&#8217;s support for cairo, but it still seemed to involve a bit more setup work than I really wanted, and, ultimately, Python&#8217;s my play language of choice, and it seemed a bit overwrought switching languages just for pretty pictures.<\/p>\n<p>Ultimately I went with <a href=\"http:\/\/www.pygame.org\/wiki\/about\">PyGame<\/a>, which seemed to have the right emphasis on &#8220;simple&#8221;, &#8220;works with python&#8221;, and &#8220;popular enough I&#8217;d heard of it before&#8221;. It&#8217;s possibly a bit kludgy in some respects &#8212; but it&#8217;s also pretty easy. I was a bit surprised that it doesn&#8217;t seem to actually use Cairo for its drawing, instead relying on SDL which seems to hit X natively. At some point, that might annoy me enough to revisit the topic, but then again, by that point it&#8217;ll probably be using Cairo one way or another anyway, the way things are going.<\/p>\n<p>My first program was to switch from an example bouncing ball in a window to a bunch of lines whose end points bounce around a window. The code:<\/p>\n<blockquote><p><code><\/p>\n<pre>#!\/usr\/bin\/env python\r\nimport pygame\r\n\r\nsize = (640,480)\r\ncolour = (40, 50, 0)\r\nbgcolour = (255,255,255)\r\n\r\nn_lines = 13\r\n\r\nscreen = pygame.display.set_mode(size)\r\npygame.display.set_caption(\"not a game\")\r\nrunning = 1\r\n\r\nclass BouncePoint:\r\n    def __init__(self, start, velocity, colour, bbox):\r\n        self.coord = tuple(start)\r\n        self.speed = list(velocity)\r\n        self.colour = colour\r\n        self.bbox = bbox\r\n\r\n    def update(self, time):\r\n        self.coord = tuple( a+b*time\r\n                 for a,b in zip( self.coord, self.speed ) )\r\n        for d in [0,1]:\r\n            if self.coord[d] &lt; 0:\r\n                self.speed[d] = abs(self.speed[d])\r\n            if self.coord[d] &gt; self.bbox[d]:\r\n                self.speed[d] = -abs(self.speed[d])\r\n\r\npts = []\r\nfor i in range(n_lines):\r\n    pts.append( (BouncePoint( (i*10,i*10), (30*i+45,50*i+12), colour, size),\r\n                 BouncePoint( (i*50,i*50), (i*65+23,i*13+125), colour, size ))\r\n    )\r\n    colour = (colour[1], colour[2], colour[0])\r\n\r\nclock = pygame.time.Clock()\r\nclock.tick()\r\n\r\nwhile running:\r\n    event = pygame.event.poll()\r\n    if event.type == pygame.QUIT:\r\n        running = 0\r\n    time = clock.tick()\/1000.0\r\n    for i in range(n_lines):\r\n        pts[i][0].update(time)\r\n        pts[i][1].update(time)\r\n\r\n    screen.fill(bgcolour)\r\n    for i in range(n_lines):\r\n        pygame.draw.line(screen, pts[i][0].colour, pts[i][0].coord, pts[i][1].coord, i+1)\r\n    pygame.display.flip()\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>Works adequately &#8212; I mean, it pegs the CPU, redraws the whole window every refresh, and flickers the cursor when you move it across the window; but it displays what I wanted to see, and doesn&#8217;t have too much excess code. Really, there&#8217;s just a call to &#8220;pygame.display.setmode&#8221; to create a window, &#8220;pygame.event.poll&#8221; to watch for events like the window close button, and &#8220;pygame.display.flip&#8221; to let me write my code to redraw the entire screen from scratch without the display actually flickering due to partially complete updates being visible.<\/p>\n<p>There&#8217;s also a bit of boilerplate around the class declarations for the lines; I forget whether that&#8217;s there because the example I copied from did it that way, or because what I wanted to end up with would need separate objects for most of the drawable items, but either way it seemed like a reasonable approach. Even if it is a major departure from what you might see in QuickBASIC in &#8217;93.<\/p>\n<p>I suspect I&#8217;m abusing modern CPU speeds and such to get away with that code &#8212; like having a personal bodybuilder on hand just to unscrew a stuck jar or something; but equally it runs fine on my netbook, and I can always add a &#8220;time.sleep(0.1)&#8221; in here and there if I only actually want, say, ten frames per second. <\/p>\n<p>(As far as I can pick, the right way to throttle graphics apps is by frames per second; and adding a sleep in there lets you do that directly &#8212; and if you make it sleep for &#8220;max(0, secs_per_frame &#8211; elapsed_time)&#8221;, you get it throttled on quick enough CPUs, and pegged out when your hardware isn&#8217;t as quick as your hopes. Maybe there&#8217;s better criteria out there, but that seems much closer to perfect than I&#8217;d expected, given how easy I&#8217;m insisting this be)<\/p>\n<p>So yeah, PyGame: recommended. AAA+++ module, will do more braindead hacks with it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At this year&#8217;s linux.conf.au I decided it was high time I learnt how to program simple graphics. The use case I had in mind in particular was simulating\/visualising resource transportation in a grid based real-time strategy game like Widelands, but really I&#8217;ve been a bit annoyed that I haven&#8217;t been able to do basic graphics [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[16],"tags":[20],"_links":{"self":[{"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/posts\/596"}],"collection":[{"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/comments?post=596"}],"version-history":[{"count":4,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/posts\/596\/revisions"}],"predecessor-version":[{"id":600,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/posts\/596\/revisions\/600"}],"wp:attachment":[{"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/media?parent=596"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/categories?post=596"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.erisian.com.au\/wordpress\/wp-json\/wp\/v2\/tags?post=596"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}