1#
2# The contents of this file are subject to the Mozilla Public
3# License Version 1.1 (the "License"); you may not use this file
4# except in compliance with the License. You may obtain a copy of
5# the License at http://www.mozilla.org/MPL/
6#
7# Software distributed under the License is distributed on an "AS
8# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9# implied. See the License for the specific language governing
10# rights and limitations under the License.
11#
12# The Original Code is State Machine Compiler (SMC).
13#
14# The Initial Developer of the Original Code is Charles W. Rapp.
15# Portions created by Charles W. Rapp are
16# Copyright (C) 2000 - 2003 Charles W. Rapp.
17# All Rights Reserved.
18#
19# Contributor(s):
20#       Port to Python by Francois Perrad, francois.perrad@gadz.org
21#
22# Vehicle --
23#
24#  Draws a generic vehicle on the map (a black square) which
25#  moves in straight lines along the road and obeys the stop light.
26#
27# RCS ID
28# $Id: Vehicle.py,v 1.1 2005/05/28 17:48:30 cwrapp Exp $
29#
30# CHANGE LOG
31# $Log: Vehicle.py,v $
32# Revision 1.1  2005/05/28 17:48:30  cwrapp
33# Added Python examples 1 - 4 and 7.
34#
35#
36
37from Tkinter import *
38
39import Vehicle_sm
40
41class Vehicle:
42
43	_speed = 2
44
45	def __init__(self, stoplight, direction, canvas):
46		self._fsm = Vehicle_sm.Vehicle_sm(self)
47
48		# The canvas to draw on and the direction this vehicle is
49		# moving.
50		self._canvas = canvas
51		self._direction = direction
52
53		# The stoplight object is responsible knowing the road
54		# layout. Ask it for all relevant information.
55		self._stoplight = stoplight
56
57		# This vehicle is initially at the road's outside edge.
58		# Figure out the road's length.
59		XLength = stoplight.getRoadLengthX()
60		YLength = stoplight.getRoadLengthY()
61		LaneWidth = stoplight.getRoadWidth() / 2
62
63		# The vehicle is 12 pixels x 12 pixels.
64		self._vehicleSize = 6
65
66		# A 3 pixel separation is to be maintained between vehicles.
67		self._vehicleSeparation = 3
68
69		# How far away the vehicle is from the curb.
70		CurbOffset = (LaneWidth - self._vehicleSize) / 2
71
72		# The vehicle's current canvas location. This is the
73		# square's upper left hand corner.
74		if      direction == 'north':
75			self._xpos = (XLength / 2) + CurbOffset
76			self._ypos = YLength - self._vehicleSize
77		elif direction == 'south':
78			self._xpos = (XLength / 2) - LaneWidth + CurbOffset
79			self._ypos = 0
80		elif direction == 'east':
81			self._xpos = 0
82			self._ypos = (YLength / 2) + CurbOffset
83		elif direction == 'west':
84			self._xpos = XLength - self._vehicleSize
85			self._ypos = (YLength / 2) - LaneWidth + CurbOffset
86
87		# Put the vehicle on display.
88		self._canvasID = canvas.create_rectangle(
89				self._xpos,
90				self._ypos,
91				self._xpos + self._vehicleSize,
92				self._ypos + self._vehicleSize,
93				fill='black',
94				outline='white',
95		)
96
97		# Move this vehicle along at near movie-refresh rate.
98		self._redrawRate = 1000 / 60
99
100		# Store the after's timer ID here.
101		self._timerID = -1
102
103		# Set this flag to true when the vehicle has
104		# completed its trip.
105		self._isDoneFlag = False
106
107		# Uncomment to see debug output.
108		#self._fsm.setDebugFlag(True)
109
110	def Delete(self):
111		if self._timerID >= 0:
112			self._canvas.after_cancel(self._timerID)
113			self._timerID = -1
114		self._canvas.delete(self._canvasID)
115
116	# timeout --
117	#
118	#   If the vehicle has driven off the canvas, then
119	#   delete the vehicle.
120	#   Check if the vehicle is at the intersection and the
121	#   light is either yellow or red. If yes, then issue a
122	#   "LightRed" transition. If all is go, then keep on
123	#   truckin.
124	#
125	# Arugments:
126	#   None.
127
128	def timeout(self):
129		self._timerID = -1
130		if self.OffCanvas():
131			self._fsm.TripDone()
132		elif self.AtIntersection() and self.getLight() != 'green':
133			self._fsm.LightRed()
134		else:
135			self._fsm.KeepGoing()
136
137	def getLight(self):
138		return self._stoplight.getLight(self._direction)
139
140	# lightGreen --
141	#
142	#   The light has turned green. Time to get moving again.
143	#
144	# Arguments:
145	#   None
146
147	def lightGreen(self):
148		self._fsm.LightGreen()
149
150	# setSpeed --
151	#
152	#   Set speed for all vehicles.
153	#
154	# Arguments:
155	#   speed   In pixels.
156
157	def setSpeed(klass, speed):
158		if speed < 1 or speed > 10:
159			print "Invalid speed (%d).\n" % speed
160		else:
161			klass._speed = speed
162
163	setSpeed = classmethod(setSpeed)
164
165	# isDone --
166	#
167	#   Has this vehicle completed its trip?
168	#
169	# Arguments:
170	#   None.
171	#
172	# Results:
173	#   Returns true if the trip is done and false
174	#   otherwise.
175
176	def isDone(self):
177		return self._isDoneFlag
178
179	# start --
180	#
181	#   Start this vehicle running.
182	#
183	# Arguments:
184	#   None.
185
186	def Start(self):
187		self._fsm.Start()
188
189	# pause --
190	#
191	#   Pause this vehicles' running.
192	#
193	# Arguments:
194	#   None.
195
196	def Pause(self):
197		self._fsm.Pause()
198
199	# continue --
200	#
201	#   Continue this vehicles' running.
202	#
203	# Arguments:
204	#   None.
205
206	def Continue(self):
207		self._fsm.Continue()
208
209	# stop --
210	#
211	#   Stop this vehicles' running.
212	#
213	# Arguments:
214	#   None.
215	#
216
217	def Stop(self):
218		self._fsm.Stop()
219		self.Delete()
220
221	# State Machine Actions
222	#
223	# The following methods are called by the state machine.
224
225	# SetTimer --
226	#
227	#   Set the timer for the next move.
228	#
229	# Arguments:
230	#   None.
231
232	def SetTimer(self):
233		self._timerID = self._canvas.after(self._redrawRate, self.timeout)
234
235	# StopTimer --
236	#
237	#   Stop the vehicle's timer.
238	#
239	# Arguments:
240	#   None.
241
242	def StopTimer(self):
243		if self._timerID >= 0:
244			self._canvas.after_cancel(self._timerID)
245			self._timerID = -1
246
247	# Move --
248	#
249	#   1. Calculate the vehicle's new position.
250	#   2. Remove the vehicle from the canvas.
251	#   3. Draw the vehicles new position.
252	#
253	# Arguments:
254	#   None.
255	#
256	# Results:
257	#   None returned. Side affect of redrawing vehicle.
258
259	def Move(self):
260		if      self._direction == 'north':
261			Xmove = 0
262			Ymove = - self._speed
263		elif self._direction == 'south':
264			Xmove = 0
265			Ymove = self._speed
266		elif self._direction == 'east':
267			Xmove = self._speed
268			Ymove = 0
269		elif self._direction == 'west':
270			Xmove = - self._speed
271			Ymove = 0
272
273		self._canvas.move(self._canvasID, Xmove, Ymove)
274
275		self._xpos += Xmove
276		self._ypos += Ymove
277
278	# RegisterWithLight --
279	#
280	#   When the light turns green, it will inform us.
281	#
282	# Arguments:
283	#   None.
284
285	def RegisterWithLight(self):
286		self._stoplight.registerVehicle(self, self._direction)
287
288	# SelfDestruct --
289	#
290	#   Remove the vehicle from the canvas.
291	#
292	# Arguments:
293	#   None.
294
295	def SelfDestruct(self):
296		self._canvas.delete(self._canvasID)
297		self._canvasID = -1
298		self._isDoneFlag = True
299
300	# OffCanvas --
301	#
302	#   Figure out if the vehicle has driven off the map.
303	#
304	# Arguments:
305	#   None.
306	#
307	# Results:
308	#   Returns true if the vehicle is off the map; otherwise
309	#   false.
310
311	def OffCanvas(self):
312		if      self._direction == 'north':
313			return (self._ypos - self._speed) <= 0
314		elif self._direction == 'south':
315			YLength = self._stoplight.getRoadLengthY()
316			return (self._ypos + self._speed) >= YLength
317		elif self._direction == 'east':
318			XLength = self._stoplight.getRoadLengthX()
319			return (self._xpos + self._speed) >= XLength
320		elif self._direction == 'west':
321			return (self._xpos - self._speed) <= 0
322
323	# AtIntersection --
324	#
325	#   Figure out whether this vehicile is at the intersection
326	#   or not.
327	#
328	# Arguments:
329	#   None.
330	#
331	# Results:
332	#   Returns true if the vehicle is at the intersection;
333	#   otherwise, false.
334
335	def AtIntersection(self):
336		# The vehicle is not at the intersection until proven
337		# otherwise.
338		Retval = False
339
340		XLength = self._stoplight.getRoadLengthX()
341		YLength = self._stoplight.getRoadLengthY()
342		LaneWidth = self._stoplight.getRoadWidth() / 2
343
344		# Calculate the intersections coordinates based on
345		# the vehicle's direction. Then calculate where the
346		# vehicle will end up this move. If the vehicle will
347		# move beyond the intersection stop line, then the
348		# vehicle is at the intersection.
349		#
350		# Also take into account the vehicles already waiting
351		# at the intersection.
352		#
353		# By the way, once the vehicle moves past the intersection,
354		# ignore the light.
355		NumVehicles = self._stoplight.getQueueSize(self._direction)
356		LenVehicles = (self._vehicleSize + self._vehicleSeparation) * NumVehicles
357		if      self._direction == 'north':
358			YIntersection = (YLength / 2) + LaneWidth + (self._vehicleSize / 2) + LenVehicles
359			Retval = (self._ypos > YIntersection) and (self._ypos - self._speed <= YIntersection)
360		elif self._direction == 'south':
361			YIntersection = (YLength / 2) - LaneWidth - (self._vehicleSize / 2) - LenVehicles
362			Retval = (self._ypos < YIntersection) and (self._ypos + self._speed >= YIntersection)
363		elif self._direction == 'east':
364			XIntersection = (XLength / 2) - LaneWidth - (self._vehicleSize / 2) - LenVehicles
365			Retval = (self._xpos < XIntersection) and (self._xpos + self._speed >= XIntersection)
366		elif self._direction == 'west':
367			XIntersection = (XLength / 2) + LaneWidth + (self._vehicleSize / 2) + LenVehicles
368			Retval = (self._xpos > XIntersection) and (self._xpos - self._speed <= XIntersection)
369		return Retval
370