Use labels, to make things look better
[erdslangetjie.git] / erdslangetjie / __main__.py
1 import kivy
2 import pygame
3
4 kivy.require('1.6.0')
5
6 from kivy.app import App
7 from kivy.uix.widget import Widget
8 from kivy.logger import Logger, LoggerHistory
9 from kivy.uix.relativelayout import RelativeLayout
10 from kivy.uix.scrollview import ScrollView
11 from kivy.uix.label import Label
12 from kivy.graphics import Color, Rectangle
13 from kivy.utils import platform
14
15 from erdslangetjie.level import LevelList
16 from erdslangetjie.data import load_image
17 from erdslangetjie.player import ThePlayer, Nemesis
18 from erdslangetjie.constants import TILE_SIZE, QUIET
19
20
21 class GameWindow(RelativeLayout):
22
23     def __init__(self, level_list, app):
24         self.level_list = level_list
25         self.level_obj = self.level_list.get_current_level()
26         self.level_obj.load_tiles()
27         self.tiles = {}
28         self.view = app.root
29         self.app = app
30
31         cols, rows = self.level_obj.get_size()
32
33         super(GameWindow, self).__init__(
34                 size=(cols * TILE_SIZE, rows * TILE_SIZE),
35                 size_hint=(None, None))
36
37         self.x_scroll_margin = float(TILE_SIZE) / self.view.size[0]
38         self.y_scroll_margin = float(TILE_SIZE) / self.view.size[1]
39
40         self.mouse_move = False
41
42         self.player = ThePlayer()
43         self.nemesis = Nemesis()
44         if not self.level_obj.enter_pos:
45             raise RuntimeError('No entry point')
46         self.player_tile = None
47         self.nemesis_tile = None
48
49         self.player.pos = self.level_obj.enter_pos
50         if platform() != 'android':
51             # Very hack'ish
52             # We need to delay this import until after the window creation by
53             # the app, else our size config doesn't work
54             from kivy.core.window import Window
55             self.keyboard = Window.request_keyboard(self._closed, self)
56             self.keyboard.bind(on_key_down=self._on_key_down)
57
58     def build(self):
59         self.clear_widgets()
60         self.tiles = {}
61         tiles = self.level_obj.get_tiles()
62         bx, by = 0, 0
63         for tile_line in tiles:
64             bx = 0
65             for tile in tile_line:
66                 node = Widget(size=(TILE_SIZE, TILE_SIZE),
67                         pos=(bx, by),
68                         size_hint=(None, None))
69                 self.add_widget(node)
70                 with node.canvas:
71                     Color(1, 1, 1)
72                     Rectangle(pos=node.pos, size=node.size,
73                             texture=tile.texture)
74                 self.tiles[(bx, by)] = node
75                 bx += TILE_SIZE
76             by += TILE_SIZE
77
78     def draw_player(self):
79         if self.player_tile:
80             self.remove_widget(self.player_tile)
81         sprite_pos = (self.player.pos[0] * TILE_SIZE,
82                 self.player.pos[1] * TILE_SIZE)
83         self.player_tile = Widget(size=(TILE_SIZE, TILE_SIZE),
84                 pos=sprite_pos)
85         with self.player_tile.canvas:
86             Color(1, 1, 1)
87             Rectangle(pos=sprite_pos, size=self.player_tile.size,
88                     texture=self.player.get_texture())
89         self.add_widget(self.player_tile)
90         for offset in [(TILE_SIZE - 1, TILE_SIZE - 1),
91                 (-TILE_SIZE + 1, TILE_SIZE - 1),
92                 (TILE_SIZE - 1, -TILE_SIZE + 1),
93                 (-TILE_SIZE + 1, -TILE_SIZE + 1),
94                 (0, 2 * TILE_SIZE - 2),
95                 (-2 * TILE_SIZE + 2, 0),
96                 (2 * TILE_SIZE - 2, 0),
97                 (0, -2 * TILE_SIZE + 2),
98                 (0, 0)]:
99             # Aim is to ensure a 'neighbourhood' around the player
100             # is visible if possible
101             check_point = (sprite_pos[0] + offset[0] + TILE_SIZE / 2,
102                     sprite_pos[1] + offset[1] + TILE_SIZE / 2)
103             true_point = self.to_parent(*check_point)
104             if check_point[0] < 0:
105                 continue
106             if check_point[1] < 0:
107                 continue
108             if check_point[0] >= self.size[0]:
109                 continue
110             if check_point[1] >= self.size[1]:
111                 continue
112             while not self.included(true_point, 0):
113                 # Scroll ourselves
114                 if true_point[0] >= self.view.size[0]:
115                     self.view.scroll_x += self.x_scroll_margin
116                     true_point = self.to_parent(*check_point)
117                     #print '-x', self.view.scroll_x, self.view.scroll_y
118                 elif true_point[0] < 0:
119                     self.view.scroll_x -= self.x_scroll_margin
120                     true_point = self.to_parent(*check_point)
121                     #print '+x', self.view.scroll_x, self.view.scroll_y
122                 elif true_point[1] >= self.view.size[1]:
123                     self.view.scroll_y += self.y_scroll_margin
124                     true_point = self.to_parent(*check_point)
125                     #print '+y', self.view.scroll_x, self.view.scroll_y
126                 elif true_point[1] < 0:
127                     self.view.scroll_y -= self.y_scroll_margin
128                     true_point = self.to_parent(*check_point)
129                     #print '-y', self.view.scroll_x, self.view.scroll_y
130                 #print true_point, self.view.size
131
132     def included(self, point, margin):
133         if point[0] < margin:
134             return False
135         if point[0] >= self.view.size[0] - margin:
136             return False
137         if point[1] < margin:
138             return False
139         if point[1] >= self.view.size[1] - margin:
140             return False
141         return True
142
143     def draw_nemesis(self):
144         if not self.nemesis.on_board():
145             return
146         if self.nemesis_tile:
147             self.remove_widget(self.nemesis_tile)
148         sprite_pos = (self.nemesis.pos[0] * TILE_SIZE,
149                 self.nemesis.pos[1] * TILE_SIZE)
150         self.nemesis_tile = Widget(size=(TILE_SIZE, TILE_SIZE),
151                 pos=sprite_pos)
152         with self.nemesis_tile.canvas:
153             Color(1, 1, 1)
154             Rectangle(pos=sprite_pos, size=self.nemesis_tile.size,
155                     texture=self.nemesis.get_texture())
156         self.add_widget(self.nemesis_tile)
157
158     def _closed(self):
159         self.keyboard.unbind(on_key_down=self._on_key_down)
160
161     def _on_key_down(self, keyboard, keycode, text, modifiers):
162         # FIXME - likely portablity issues
163         direction = None
164         if keycode[0] == pygame.K_UP:
165             direction = (0, 1)
166         elif keycode[0] == pygame.K_DOWN:
167             direction = (0, -1)
168         elif keycode[0] == pygame.K_LEFT:
169             direction = (-1, 0)
170         elif keycode[0] == pygame.K_RIGHT:
171             direction = (1, 0)
172         if direction:
173             self.do_move(direction)
174
175     def do_move(self, direction):
176         if not self.level_obj:
177             return
178         self.nemesis.move(self.level_obj)
179         self.draw_nemesis()
180         self.player.move(direction, self.level_obj)
181         self.draw_player()
182         self.check_state()
183         self.reset_timer()
184
185     def timed_move(self):
186         if not self.level_obj:
187             return
188         self.nemesis.move(self.level_obj)
189         self.draw_nemesis()
190         self.check_state()
191         self.reset_timer()
192
193     def reset_timer(self):
194         pass
195
196     def check_state(self):
197         if self.level_obj.at_exit(self.player.pos):
198             # Jump to next level
199             self.level_obj = self.level_list.advance_to_next_level()
200             self.remove_widget(self.nemesis_tile)
201             self.nemesis.reset_pos()
202             if self.level_obj:
203                 self.level_obj.load_tiles()
204                 self.player.pos = self.level_obj.enter_pos
205                 self.remove_widget(self.player_tile)
206                 self.view.scroll_x = 0
207                 self.view.scroll_y = 0
208                 self.build()
209                 self.draw_nemesis()
210                 self.draw_player()
211             else:
212                 self.app.game_over(True)
213         elif self.nemesis.pos == self.player.pos:
214             # Caught
215             self.app.game_over(False)
216
217     def _calc_mouse_pos(self, pos):
218         pos = self.to_local(*pos)
219         return (int(pos[0] / TILE_SIZE), int(pos[1] / TILE_SIZE))
220
221     def on_touch_down(self, touch):
222         pos = self._calc_mouse_pos(touch.pos)
223         if pos == self.player.pos:
224             self.mouse_move = True
225             self.mouse_start = pos
226
227     def on_touch_up(self, touch):
228         self.mouse_move = False
229
230     def on_touch_move(self, touch):
231         if self.mouse_move:
232             pos = self._calc_mouse_pos(touch.pos)
233             if (pos[0] - self.mouse_start[0] != 0) or (
234                     pos[1] - self.mouse_start[1] != 0):
235                 direction = (pos[0] - self.mouse_start[0],
236                         pos[1] - self.mouse_start[1])
237                 self.do_move(direction)
238                 self.mouse_start = pos
239
240
241 class Screen(Widget):
242
243     BACKGROUND = None
244     START = 'Start'
245
246     def __init__(self, app):
247         super(Screen, self).__init__()
248         self.image = load_image(self.BACKGROUND)
249         self.app = app
250         with self.canvas:
251             Rectangle(pos=(0, 0), size=(1026, 760),
252                     texture=self.image.texture)
253
254         self.stop_button = Label(
255                 text='[ref=quit][color=ff0066]Quit[/color][/ref]',
256                 font_size=30,
257                 markup=True,
258                 size=(200, 40),
259                 pos=((1026 - 200) / 2 - 100, 100))
260         self.stop_button.bind(on_ref_press=self.app.stop_app)
261         self.start_button = Label(
262                 text="[ref=start][color=00ff66]" + self.START + "[/color][/ref]",
263                 font_size=30,
264                 markup = True, size=(200, 40),
265                 pos=((1026 - 200) / 2 + 100, 100))
266         self.start_button.bind(on_ref_press=self.app.start_game)
267         self.add_widget(self.stop_button)
268         self.add_widget(self.start_button)
269
270
271 class IntroScreen(Screen):
272
273     BACKGROUND = 'screens/intro_screen.png'
274     START = 'Start the Game'
275
276
277 class WonScreen(Screen):
278
279     BACKGROUND = 'screens/won.png'
280     START = 'Play again?'
281
282
283 class LostScreen(Screen):
284
285     BACKGROUND = 'screens/lost.png'
286     START = 'Retry?'
287
288
289 class GameApp(App):
290
291     title = "Peter's thread snake"
292
293     def __init__(self):
294         self.levels = LevelList()
295         super(GameApp, self).__init__()
296
297     def build(self):
298         root = ScrollView(size_hint=(None, None))
299         return root
300
301     def on_start(self):
302         from kivy.base import EventLoop
303         window = EventLoop.window
304         if platform() == 'android':
305             window.fullscreen = True
306         self.root.size = window.size
307         self.make_intro()
308
309     def make_intro(self):
310         self.root.clear_widgets()
311         screen = IntroScreen(self)
312         self.root.add_widget(screen)
313
314     def stop_app(self, label, ref):
315         self.stop()
316
317     def start_game(self, label, ref):
318         """Start the game"""
319         game = GameWindow(self.levels, self)
320         game.build()
321         self.root.clear_widgets()
322         self.root.add_widget(game)
323         # Ensure the player is visible
324         self.root.scroll_x = 0
325         self.root.scroll_y = 0
326         game.draw_player()
327         game.draw_nemesis()
328
329     def game_over(self, won):
330         if won:
331             screen = WonScreen(self)
332             self.levels.reset()
333         else:
334             screen = LostScreen(self)
335         self.root.clear_widgets()
336         self.root.add_widget(screen)
337
338
339 def main():
340     """ Erdslangetjie, a maze game of eluding nemesis
341     """
342     if QUIET:
343         for hdlr in Logger.handlers[:]:
344             if not isinstance(hdlr, LoggerHistory):
345                 Logger.removeHandler(hdlr)
346     GameApp().run()