-
Notifications
You must be signed in to change notification settings - Fork 0
/
gamelib.py
210 lines (167 loc) · 6.7 KB
/
gamelib.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import tkinter as tk
import tkinter.ttk as ttk
# use ImageTk for improved PhotoImage class
from PIL import ImageTk
class GameCanvasElement:
"""An element on the game canvas, with attributes:
x = x-coord of image
y = y-coord of image
canvas = reference to the game canvas
canvas_object_id = id of the object, used to manipulate it using canvas
is_visible = boolean flag if element is visible.
By default the (x,y) coordinate are at the center of the image,
but this can be changed by subclasses or calls to canvas.itemconfigure().
"""
def __init__(self, canvas, x=0, y=0, **kwargs):
self.x = x
self.y = y
# Modified: save reference to game_app instead of game_app.canvas
#self.app = game_app
self._canvas = canvas
self.is_visible = True
self.canvas_object_id = self.init_canvas_object(**kwargs)
self.init_element()
def init_canvas_object(self, **kwargs) -> int:
"""Initialize a graphical object to show on self.canvas.
This method must return the canvas_object_id of the object.
"""
return 0
def init_element(self):
"""Initialize the state of the element.
Use this method for things other than creating the canvas object.
"""
pass
@property
def canvas(self):
"""Return a reference to the game app's canvas."""
return self._canvas
def show(self):
self.is_visible = True
self.canvas.itemconfigure(self.canvas_object_id, state=tk.NORMAL)
def hide(self):
self.is_visible = False
self.canvas.itemconfigure(self.canvas_object_id, state=tk.HIDDEN)
def render(self):
if self.is_visible:
self.canvas.coords(self.canvas_object_id, self.x, self.y)
def update(self):
pass
def contains(self, x, y):
"""Test if the game element contains point x,y in its image or its 'space'.
Returns: True if (x,y) is inside the object's image or region.
"""
(xl,yl, xr,yr) = self.canvas.bbox(self.canvas_object_id)
return xl <= x <= xr and min(yl,yr) <= y <= max(yl,yr)
class Text(GameCanvasElement):
"""Some text displayed on the game canvas."""
def __init__(self, canvas, text, x=0, y=0, **kwargs):
self.text = text
super().__init__(canvas, x, y, **kwargs)
def init_canvas_object(self, **kwargs):
object_id = self.canvas.create_text(
self.x,
self.y,
text=self.text,
**kwargs)
return object_id
def set_text(self, text):
self.text = text
self._canvas.itemconfigure(self.canvas_object_id, text=text)
def append_text(self, text):
self.set_text(self.text + text)
def set_color(self, color):
self._canvas.itemconfigure(self.canvas_object_id, color=color)
class Sprite(GameCanvasElement):
"""A canvas element with an image."""
def __init__(self, canvas, image_filename, x=0, y=0):
self.image_filename = image_filename
super().__init__(canvas, x, y)
def init_canvas_object(self):
#self.image = tk.PhotoImage(file=self.image_filename)
self.image = ImageTk.PhotoImage(file=self.image_filename)
object_id = self.canvas.create_image(
self.x,
self.y,
image=self.image)
return object_id
@property
def height(self):
"""Return the height of the Sprite's image."""
return self.image.height() if self.image else 0
@property
def width(self):
"""Return the width of the Sprite's image."""
return self.image.width() if self.image else 0
class GameApp(ttk.Frame):
"""Base class for a game. This class creates a canvas
and provides several call-back methods for initializing elements
on the canvas, start/stop animation, and running the animation loop.
"""
def __init__(self, parent, canvas_width, canvas_height, update_delay=33):
super().__init__(parent, width=canvas_width, height=canvas_height)
self.parent = parent
self.update_delay = update_delay
# row 0 is the canvas, row 1 for controls and text
self.rowconfigure(0, weight=4)
self.rowconfigure(1, weight=1)
self.grid(sticky=tk.NSEW)
self.canvas = self.create_canvas(canvas_width, canvas_height)
# The timer_id keeps a reference to the animation timer.
# It is empty string if timer is stopped.
self.timer_id = ""
self.elements = []
self.init_game()
# bind callback for event handlers
self.parent.bind('<KeyPress>', self.on_key_pressed)
self.parent.bind('<KeyRelease>', self.on_key_released)
def create_canvas(self, canvas_width, canvas_height) -> tk.Canvas:
canvas = tk.Canvas(self,
borderwidth=0,
width=canvas_width,
height=canvas_height,
highlightthickness=0)
canvas.grid(row=0, sticky=tk.NSEW)
return canvas
def add_element(self, element):
"""Add an element to list of animated elements.
Element should be an object with update() and render() methods,
such as a GameCanvasElement.
"""
if not element in self.elements:
self.elements.append(element)
def remove_element(self, element):
"""Remove an element from the canvas and list of animated elements."""
if element in self.elements:
self.elements.remove(element)
self.canvas.delete(element.canvas_object_id)
def animate(self):
"""Animate each element on the canvas.
A subclass may override this to provide it's own animation.
A subclass should be careful to set self.timer_id to the value
returned by animate().
"""
for element in self.elements:
element.update()
element.render()
self.timer_id = self.after(self.update_delay, self.animate)
def start(self):
"""Start the animation loop if not already running."""
if not self.timer_id:
self.timer_id = self.after(0, self.animate)
def stop(self):
"""Stop the animation loop."""
if self.timer_id:
self.after_cancel(self.timer_id)
self.timer_id = ""
def stopped(self) -> bool:
"""Test if the animation loop has been stopped."""
return not self.timer_id
def running(self) -> bool:
"""Return True if animation is running, False otherwise."""
return self.timer_id != ""
def init_game(self):
pass
def on_key_pressed(self, event):
pass
def on_key_released(self, event):
pass