"better" auto-scrolling support
[erdslangetjie.git] / erdslangetjie / __main__.py
1 import sys
2 import kivy
3 import pygame
4
5 kivy.require('1.6.0')
6
7 from kivy.app import App
8 from kivy.core.window import Window
9 from kivy.uix.widget import Widget
10 from kivy.uix.relativelayout import RelativeLayout
11 from kivy.uix.scrollview import ScrollView
12 from kivy.graphics import Color, Rectangle
13 from kivy.utils import platform
14
15 from erdslangetjie.level import LevelList
16 from erdslangetjie.player import ThePlayer, Nemesis
17
18 TILE_SIZE = 48
19
20
21 class GameWindow(RelativeLayout):
22
23     def __init__(self, level_list, view):
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 = view
29
30         cols, rows = self.level_obj.get_size()
31
32         super(GameWindow, self).__init__(
33                 size=(cols * TILE_SIZE, rows * TILE_SIZE),
34                 size_hint=(None, None))
35
36         self.x_scroll_margin = float(TILE_SIZE) / self.view.size[0]
37         self.y_scroll_margin = float(TILE_SIZE) / self.view.size[1]
38
39         self.mouse_move = False
40
41         self.player = ThePlayer()
42         self.nemesis = Nemesis()
43         if not self.level_obj.enter_pos:
44             raise RuntimeError('No entry point')
45         self.player_tile = None
46         self.nemesis_tile = None
47
48         self.player.pos = self.level_obj.enter_pos
49         if platform() != 'android':
50             # Very hack'ish
51             self.keyboard = Window.request_keyboard(self._closed, self)
52             self.keyboard.bind(on_key_down=self._on_key_down)
53
54     def build(self):
55         self.clear_widgets()
56         self.tiles = {}
57         tiles = self.level_obj.get_tiles()
58         bx, by = 0, 0
59         for tile_line in tiles:
60             bx = 0
61             for tile in tile_line:
62                 node = Widget(size=(TILE_SIZE, TILE_SIZE),
63                         pos=(bx, by),
64                         size_hint=(None, None))
65                 self.add_widget(node)
66                 with node.canvas:
67                     Color(1, 1, 1)
68                     Rectangle(pos=node.pos, size=node.size,
69                             texture=tile.texture)
70                 self.tiles[(bx, by)] = node
71                 bx += TILE_SIZE
72             by += TILE_SIZE
73
74     def draw_player(self):
75         if self.player_tile:
76             self.remove_widget(self.player_tile)
77         sprite_pos = (self.player.pos[0] * TILE_SIZE,
78                 self.player.pos[1] * TILE_SIZE)
79         self.player_tile = Widget(size=(TILE_SIZE, TILE_SIZE),
80                 pos=sprite_pos)
81         with self.player_tile.canvas:
82             Color(1, 1, 1)
83             Rectangle(pos=sprite_pos, size=self.player_tile.size,
84                     texture=self.player.get_texture())
85         self.add_widget(self.player_tile)
86         for offset in [(TILE_SIZE - 1, TILE_SIZE - 1),
87                 (-TILE_SIZE + 1, TILE_SIZE - 1),
88                 (TILE_SIZE - 1, -TILE_SIZE + 1),
89                 (-TILE_SIZE + 1, -TILE_SIZE + 1),
90                 (0, 2 * TILE_SIZE - 2),
91                 (-2 * TILE_SIZE + 2, 0),
92                 (2 * TILE_SIZE - 2, 0),
93                 (0, -2 * TILE_SIZE + 2),
94                 (0, 0)]:
95             # Aim is to ensure a 'neighbourhood' around the player
96             # is visible if possible
97             check_point = (sprite_pos[0] + offset[0] + TILE_SIZE / 2,
98                     sprite_pos[1] + offset[1] + TILE_SIZE / 2)
99             true_point = self.to_parent(*check_point)
100             if check_point[0] < 0:
101                 continue
102             if check_point[1] < 0:
103                 continue
104             if check_point[0] >= self.size[0]:
105                 continue
106             if check_point[1] >= self.size[1]:
107                 continue
108             while not self.included(true_point, 0):
109                 # Scroll ourselves
110                 if true_point[0] >= self.view.size[0]:
111                     self.view.scroll_x += self.x_scroll_margin
112                     true_point = self.to_parent(*check_point)
113                     #print '-x', self.view.scroll_x, self.view.scroll_y
114                 elif true_point[0] < 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[1] >= self.view.size[1]:
119                     self.view.scroll_y += self.y_scroll_margin
120                     true_point = self.to_parent(*check_point)
121                     #print '+y', self.view.scroll_x, self.view.scroll_y
122                 elif true_point[1] < 0:
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                 #print true_point, self.view.size
127
128     def included(self, point, margin):
129         if point[0] < margin:
130             return False
131         if point[0] >= self.view.size[0] - margin:
132             return False
133         if point[1] < margin:
134             return False
135         if point[1] >= self.view.size[1] - margin:
136             return False
137         return True
138
139     def draw_nemesis(self):
140         if not self.nemesis.on_board():
141             return
142         if self.nemesis_tile:
143             self.remove_widget(self.nemesis_tile)
144         sprite_pos = (self.nemesis.pos[0] * TILE_SIZE,
145                 self.nemesis.pos[1] * TILE_SIZE)
146         self.nemesis_tile = Widget(size=(TILE_SIZE, TILE_SIZE),
147                 pos=sprite_pos)
148         with self.nemesis_tile.canvas:
149             Color(1, 1, 1)
150             Rectangle(pos=sprite_pos, size=self.nemesis_tile.size,
151                     texture=self.nemesis.get_texture())
152         self.add_widget(self.nemesis_tile)
153
154     def _closed(self):
155         self.keyboard.unbind(on_key_down=self._on_key_down)
156
157     def _on_key_down(self, keyboard, keycode, text, modifiers):
158         # FIXME - likely portablity issues
159         direction = None
160         if keycode[0] == pygame.K_UP:
161             direction = (0, 1)
162         elif keycode[0] == pygame.K_DOWN:
163             direction = (0, -1)
164         elif keycode[0] == pygame.K_LEFT:
165             direction = (-1, 0)
166         elif keycode[0] == pygame.K_RIGHT:
167             direction = (1, 0)
168         if direction:
169             self.do_move(direction)
170
171     def do_move(self, direction):
172         self.nemesis.move(self.level_obj)
173         self.draw_nemesis()
174         self.player.move(direction, self.level_obj)
175         self.draw_player()
176         self.check_state()
177
178     def check_state(self):
179         if self.level_obj.at_exit(self.player.pos):
180             # Jump to next level
181             self.level_obj = self.level_list.advance_to_next_level()
182             self.remove_widget(self.nemesis_tile)
183             self.nemesis.reset_pos()
184             if self.level_obj:
185                 self.level_obj.load_tiles()
186                 self.player.pos = self.level_obj.enter_pos
187                 self.remove_widget(self.player_tile)
188                 self.view.scroll_x = 0
189                 self.view.scroll_y = 0
190                 self.build()
191                 self.draw_nemesis()
192                 self.draw_player()
193             else:
194                 print 'You won!'
195                 sys.exit(1)
196         elif self.nemesis.pos == self.player.pos:
197             # Caught
198             print 'You lost!'
199             sys.exit(1)
200
201     def _calc_mouse_pos(self, pos):
202         pos = self.to_local(*pos)
203         return (int(pos[0] / TILE_SIZE), int(pos[1] / TILE_SIZE))
204
205     def on_touch_down(self, touch):
206         pos = self._calc_mouse_pos(touch.pos)
207         if pos == self.player.pos:
208             self.mouse_move = True
209             self.mouse_start = pos
210
211     def on_touch_up(self, touch):
212         self.mouse_move = False
213
214     def on_touch_move(self, touch):
215         if self.mouse_move:
216             pos = self._calc_mouse_pos(touch.pos)
217             if (pos[0] - self.mouse_start[0] != 0) or (
218                     pos[1] - self.mouse_start[1] != 0):
219                 direction = (pos[0] - self.mouse_start[0],
220                         pos[1] - self.mouse_start[1])
221                 self.do_move(direction)
222                 self.mouse_start = pos
223
224
225 class GameApp(App):
226
227     def __init__(self):
228         self.levels = LevelList()
229         super(GameApp, self).__init__()
230
231     def build(self):
232         root = ScrollView(size=(960, 640), size_hint=(None, None))
233         game = GameWindow(self.levels, root)
234         game.build()
235         root.add_widget(game)
236         # Ensure the player is visible
237         root.scroll_x = 0
238         root.scroll_y = 0
239         game.draw_player()
240         game.draw_nemesis()
241         return root
242
243
244 def main():
245     """ Erdslangetjie, a maze game of eluding nemesis
246     """
247     GameApp().run()