Move some stuff to constants. Add QUIET flag for release
[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.logger import Logger, LoggerHistory
11 from kivy.uix.relativelayout import RelativeLayout
12 from kivy.uix.scrollview import ScrollView
13 from kivy.graphics import Color, Rectangle
14 from kivy.utils import platform
15
16 from erdslangetjie.level import LevelList
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, 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     title = "Peter's thread snake"
228
229     def __init__(self):
230         self.levels = LevelList()
231         super(GameApp, self).__init__()
232
233     def build(self):
234         root = ScrollView(size_hint=(None, None))
235         return root
236
237     def on_start(self):
238         from kivy.base import EventLoop
239         window = EventLoop.window
240         if platform() == 'android':
241             window.fullscreen = True
242         self.root.size = window.size
243         game = GameWindow(self.levels, self.root)
244         game.build()
245         self.root.add_widget(game)
246         # Ensure the player is visible
247         self.root.scroll_x = 0
248         self.root.scroll_y = 0
249         game.draw_player()
250         game.draw_nemesis()
251
252
253 def main():
254     """ Erdslangetjie, a maze game of eluding nemesis
255     """
256     if QUIET:
257         for hdlr in Logger.handlers[:]:
258             if not isinstance(hdlr, LoggerHistory):
259                 Logger.removeHandler(hdlr)
260     GameApp().run()