Added TODO
[dvx.git] / DeathValley.py
blobe613d5c737f4723fae88f27136a7ad74fc00fc72
1 #!/usr/bin/env python
2 """
3 Death Valley X
5 (C) 2008 Gergely Imreh <imrehg@gmail.com>
6 --- using:
7 Accelerometer code: Martin Senkerik <martinsenkerik@gmail.com>
8 (BSD licence)
10 GPLv3 or later
11 """
13 import pygtk
14 pygtk.require('2.0')
15 import gtk, gobject
16 import cairo
17 import pango
18 import random
19 import threading, time
20 import accelero
21 from math import pi, ceil
23 ############
25 # Accelerometer thread
26 amthread = None
28 # Lots of output
29 debug = False
31 __version__ = '0.1'
33 ############
37 def main_quit(obj):
38 ## Shut down accelerometer thread when finished
39 amthread.bRun = False
41 def progress_timeout(object):
42 """ Repeated event to redraw graphics """
43 ## Area to redraw
44 x, y, w, h = object.allocation
45 object.window.invalidate_rect((0,0,w,h),False)
46 ## Every segment survived worth 1 point
47 object.score = object.score+1
48 ## If crashed, game over
49 return object.checkcrash()
51 class Field(gtk.DrawingArea):
53 def __init__(self):
54 gtk.DrawingArea.__init__(self)
55 self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
56 gtk.gdk.BUTTON_RELEASE_MASK |
57 gtk.gdk.BUTTON1_MOTION_MASK |
58 gtk.gdk.SCROLL_MASK
60 ## Implement later: Pause on touch of screen
61 # self.connect("button_press_event", self.on_button_press_event)
64 self.connect("expose_event", self.do_expose_event)
65 ## Timer to redraw screen: every 170ms
66 ## Right now: go faster -> slugish graphics, go slower -> slugish everything
67 self.timer = gobject.timeout_add (170, progress_timeout, self)
69 ## Starting path
70 self.path = [200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
71 self.pwidth = [180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180]
72 ## Max path segments
73 self.maxindex = 12
75 ## Probabilities of road direction changes
76 ## 0,1 : probability of UP,AHEAD, when going UP now
77 ## 2,3 : probability of UP,AHEAD, when going AHEAD now
78 ## 4,5 : probability of UP,AHEAD, when going DOWN now
79 ## (third probability is always 1-p0-p1 for them to add up to 1)
80 self.probs = [0.5, 0.4, 0.2, 0.6, 0.2, 0.4]
81 ## Probabilities of width changes:
82 ## 0,1 : getting NARROWER or STAYING SAME.
83 ## probability of getting WIDER is just 1-p0-p1
84 self.wprobs = [0.1, 0.8]
86 ## Starting by going ahead
87 self.pstate = 0
88 ## Unit of changes in road segment UP or DOWN in any given step
89 self.stepsize = 15
90 ## Units of change in width
91 self.wstep = 10
92 ## Defines the ship's speed: max step in one redraw either up or down.
93 self.shipspeed = 30
94 ## Starting shipposition
95 self.shippos = 290
96 ## Start going ahead
97 ## Ratio is how much the speed changes compared to the .shipspeed
98 self.shipratio = 0;
99 ## We are healthy first
100 self.crash = False
101 ## Reset score
102 self.score = 0
105 def do_expose_event(self, w, event):
106 # Handle the expose-event by drawing
107 try:
108 self.cr = self.window.cairo_create()
109 except AttributeError:
110 #return self._expose_gdk(event)
111 raise
112 return self._expose_cairo(event)
115 def _expose_cairo(self, event):
116 """ What to do on expose """
117 # Create the cairo context
118 self.cr = self.window.cairo_create()
120 # Restrict Cairo to the exposed area; avoid extra work
121 self.cr.rectangle(event.area.x, event.area.y,
122 event.area.width, event.area.height)
123 self.cr.clip()
124 ## Drawing background
125 self.redrawfield()
126 ## Draw ship
127 self.drawship()
130 def refresh(self):
131 """ Refresh screen """
132 print "do_refresh"
133 x, y, w, h = self.allocation
134 self.window.invalidate_rect((0,0,w,h),False)
136 def drawship(self):
137 """ Call ship position update and draw ship - in pieces of crashed """
138 ## New position
139 self.updateshippos()
140 if debug: print "DEBUG:","Draw ship at ",self.shippos
141 ## Draw ship in pieces (with a little randomness) if crashed
142 if self.crash:
143 self.cr.set_source_rgba(1, 0, 0, 0.6)
144 self.cr.arc(20+10*random.random(), self.shippos+10*random.random(), 3.0, 0, 2*pi)
145 self.cr.fill()
146 self.cr.arc(20+10*random.random(), self.shippos-10*random.random(), 4.0, 0, 2*pi)
147 self.cr.fill()
148 self.cr.arc(20-10*random.random(), self.shippos-10*random.random(), 5.0, 0, 2*pi)
149 self.cr.fill()
150 self.cr.arc(20-10*random.random(), self.shippos+10*random.random(), 5.0, 0, 2*pi)
151 self.cr.fill()
152 else:
153 ## Draw ship in one piece still, yippie....
154 self.cr.set_source_rgba(1, 0.8, 0.2, 0.6)
155 self.cr.arc(20, self.shippos, 10.0, 0, 2*pi)
156 self.cr.fill()
158 def checkcrash(self):
159 """ Routine to check every update, whether the ship crashed and to vibration if it did """
160 ## Check for crash - taking ship size of r=5 into account
161 if (self.shippos <= self.path[0]+5 ) or (self.shippos >= self.path[0]+self.pwidth[0]-5):
162 if debug: print "DEBUG:","Crashed!!"
163 ## Good vibrations!
164 intensity = 255
165 outfile = open("/sys/class/leds/neo1973:vibrator/brightness", "w", 1)
166 outfile.write("%s\n" % str(intensity))
167 time.sleep(0.5)
168 outfile.write("0\n")
169 outfile.close()
170 self.crash = True
171 return False
172 else:
173 ## No crash, carry on
174 return True
178 def updateshippos(self):
179 """ Updating the ship position, according to tilt~ship-speed """
180 self.shippos = self.shippos+self.shipspeed*self.shipratio
181 if debug: print "DEBUG:","Updateship: Pos:",self.shippos,"Speed:",self.shipratio,"New Pos:",self.shippos
184 def redrawfield(self):
185 """ Drawing background, score, valley walls """
186 ## Get window size
187 width, height = self.window.get_size()
188 ## Fill the background with gray
189 self.cr.set_source_rgb(0, 0, 0)
190 self.cr.rectangle(0, 0, width, height)
191 self.cr.fill()
193 ## Display score: rotated, in corner
194 self.cr.set_source_rgb(1, 0.0, 0.0)
195 self.scorefield = self.create_pango_layout(str(self.score))
196 self.scorefield.set_font_description(pango.FontDescription("sans serif 10"))
197 self.cr.move_to(440,30)
198 self.cr.rotate( 90 / (180.0 / pi));
199 self.cr.update_layout(self.scorefield)
200 self.cr.show_layout(self.scorefield)
201 self.cr.rotate( -90 / (180.0 / pi));
203 ########## Drawing lines##########
205 ## Change probabilities: close to edges, too wide or too narrow paths
206 inprob,inwprob = self.changeprob(self.path[-1],self.pwidth[-1],self.probs,self.wprobs)
207 ## Where should the road go next?
208 self.pstate = self.newstate(self.pstate,inprob)
211 ## Calculate position of next road segment
212 newpos = self._newpath(self.path,self.pstate)
213 if debug: print "DEBUG:","New Road segment:",newpos
214 self.path.append(newpos)
216 ## Calculate new width
217 wy = self._newwidth(self.pwidth[-1],inwprob)
218 if debug: print "DEBUG:","New Width :",wy
219 self.pwidth.append(wy)
221 ## Set color of valley walls
222 self.cr.set_source_rgb(0.9, 0.9, 0.9)
224 ## Index of staring positions
225 to = 0
226 ## length of elementary segments
227 tos = ceil(width/self.maxindex)
228 ## Loop through segments
229 for index, item in enumerate(self.path):
230 if index < self.maxindex:
231 self.cr.move_to(to,item)
232 to += tos
234 ## Curves to sloooow, using line_to only....
235 #### self.cr.curve_to(to,item,to-tos,self.path[index+1],to,self.path[index+1])
236 #### self.cr.move_to(to-tos,item+self.pwidth[index])
237 #### self.cr.curve_to(to,item+self.pwidth[index],to-tos,self.path[index+1]+self.pwidth[index+1],to,self.path[index+1]+self.pwidth[index+1])
239 ## Drawing lines
240 # Upper line
241 self.cr.line_to(to,self.path[index+1])
242 self.cr.move_to(to-tos,item+self.pwidth[index])
243 # Lower line
244 self.cr.line_to(to,self.path[index+1]+self.pwidth[index+1])
246 ## Draw on screen
247 self.cr.stroke()
249 ## Throw away used path segments
250 self.path.pop(0)
251 self.pwidth.pop(0)
253 ##### End of redraw()
257 def _newwidth(self,width,wprob):
258 """ Use probabilities to change path width """
259 change = random.random()
260 if change < wprob[0]:
261 ## Narrower
262 return width-self.wstep
263 elif change < wprob[0]+wprob[1]:
264 ## Stay the same
265 return width
266 else:
267 ## Wider
268 return width+self.wstep
271 def _newpath(self,path,pstate):
272 """ Change path direction according to status """
273 if pstate == -1:
274 ## Move road up (compared to the last piece of road already generated)
275 return path[-1]-self.stepsize
276 elif pstate == 0:
277 ## Keep ahead
278 return path[-1]
279 else:
280 ## Move road down
281 return path[-1]+self.stepsize
285 def newstate(self,state,probs):
286 """ Use Markov probabilities to change direction of path """
287 ## Now: Going up
288 if state == -1:
289 sprob = probs[0:2]
290 ## Now: Going straight
291 elif state == 0:
292 sprob = probs[2:4]
293 ## Now: Going down
294 else:
295 sprob = probs[4:6]
297 ## Choose new direction
298 change = random.random()
299 if change < sprob[0]:
300 ## Go up
301 return -1
302 elif change < sprob[0]+sprob[1]:
303 ## Stay the same
304 return 0
305 else:
306 ## Go down
307 return 1
309 def changeprob(self,pos,width,probs,wprobs):
310 """ Change probabilities of directions of winding path """
311 if debug: print "DEBUG:","Before next: Pos:",pos,"Width:",width
312 outprobs = probs[:]
313 outwprobs = wprobs[:]
315 ## Path direction
316 if pos <= 10:
317 if debug: print "DEBUG:","Too HIGH"
318 outprobs[0] = 0
319 outprobs[2] = 0
320 elif pos >= 470-width:
321 if debug: print "DEBUG:","Too LOW"
322 norm = outprobs[2]+outprobs[3]
323 outprobs[2] = outprobs[2]/norm
324 outprobs[3] = outprobs[3]/norm
325 norm = outprobs[4]+outprobs[5]
326 outprobs[4] = outprobs[4]/norm
327 outprobs[5] = outprobs[5]/norm
328 ## Path width: min 70, max 180
329 if width <= 70:
330 if debug: print "DEBUG:","Too Narrow"
331 outwprobs[0] = 0
332 elif width >= 180:
333 if debug: print "DEBUG:","Too Wide"
334 norm = outwprobs[0]+outwprobs[1]
335 outwprobs[0] = outwprobs[0]/norm
336 outwprobs[1] = outwprobs[1]/norm
338 return outprobs,outwprobs
341 class Form:
342 """ Form to display game area """
344 ## accelerometer thread
345 t = None
346 gtk.gdk.threads_init()
348 def __init__(self):
349 """ Draw window: buttons and drawing arrea """
350 self.ready_semaphore = threading.Semaphore()
351 self.ready_semaphore.acquire()
353 ## Create window
354 self.window = gtk.Window()
355 self.window.connect("delete-event", main_quit)
356 self.window.connect('destroy', main_quit)
357 self.window.set_title("DeathValleyX")
360 ## vertical box to align buttons and drawing screen
361 ## input Homogeneous, Spacing
362 box1 = gtk.VBox(False,0)
364 ## exit button
365 button = gtk.Button("eXit")
366 button.connect_object("clicked", main_quit, self.window)
367 box1.pack_start(button,True,True,0)
368 button.show()
370 ## Drawing area
371 self.drawing_area = Field()
372 self.drawing_area.set_size_request(480, 480)
373 box1.pack_start(self.drawing_area, False, False, 0)
374 self.drawing_area.show()
376 ## Level-down and level-up buttons
377 ## All in horizontal box
378 box2 = gtk.HBox(False,0)
379 button = gtk.Button("|")
380 # DOES NOT WORK YET
381 button.Sensitive = False
382 #### button.connect_object("clicked", main_quit, self.window)
383 box2.pack_start(button,True,True,0)
384 button.show()
386 button = gtk.Button("+")
387 #DOES NOT WORK YET
388 #### button.connect_object("clicked", main_quit, self.window)
389 box2.pack_start(button,True,True,0)
390 button.show()
392 box2.show()
393 box1.pack_start(box2,True,True,0)
395 self.window.add(box1)
396 box1.show()
397 self.window.show()
398 ## Finish drawing window
400 ## start accelerometer thread
401 self.ready_semaphore.release()
405 def main():
406 """ Main function - create window and accelerometer thread """
407 ## Accelerometer thread
408 global amthread
410 ## Draw windows
411 form = Form()
413 ## start accelerometer
414 amthread = accelero.Worker(form)
415 amthread.start()
417 gtk.main()
419 ## when exit
420 return 0
424 if __name__ == "__main__":
425 main()