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