Move turnip growth and related updates to the end of the night
[tabakrolletjie.git] / tabakrolletjie / scenes / night.py
1 """ In the night, the mould attacks. """
2
3 import pygame.surface
4 import pygame.locals as pgl
5
6 import pymunk
7
8 from .base import BaseScene
9 from ..battery import BatteryManager
10 from ..lights import LightManager
11 from ..infobar import InfoBar, CountDownBar
12 from ..obstacles import ObstacleManager
13 from ..enemies import Boyd
14 from ..events import SceneChangeEvent
15 from ..utils import debug_timer, shadowed_text
16 from ..loader import loader
17 from ..sound import sound
18 from ..transforms import Overlay
19 from ..turnip import Turnip
20 from ..widgets import ImageButton
21 from ..constants import (
22     NIGHT_LENGTH, NIGHT_HOURS_PER_TICK, DEBUG, FONTS,
23     SCREEN_SIZE, FPS)
24
25
26 class NightScene(BaseScene):
27
28     DARKNESS = Overlay(colour=(0, 0, 0, 150))
29
30     def enter(self, gamestate):
31         self._space = pymunk.Space()
32         self._obstacles = ObstacleManager(self._space, gamestate)
33         self._lights = LightManager(self._space, gamestate)
34         self._battery = BatteryManager(gamestate)
35         self._battery_dead = False
36         self.check_battery()
37         self._infobar = InfoBar("day", battery=self._battery, scene=self)
38         self._countdownbar = CountDownBar("h")
39         self._mould = Boyd(gamestate, self._space)
40         self._turnips = []
41         for turnip_data in gamestate.turnips:
42             turnip = Turnip(space=self._space, **turnip_data)
43             self._turnips.append(turnip)
44         self._soil = loader.load_image(
45             "textures", "soil.png", transform=self.DARKNESS)
46         self._tools = self.create_tools(gamestate)
47         self._total_ticks = 0
48         self._do_ticks = True
49         self._paused = False
50         self._eaten_tonight = 0
51         self._night_over_text = []
52         self._ending = False
53
54     def create_tools(self, gamestate):
55         tools = []
56         y = SCREEN_SIZE[1] - 40
57         tools.append(ImageButton(
58             '32', 'pause.png', name='pause play',
59             pos=(SCREEN_SIZE[0] - 150, y)))
60         tools.append(ImageButton(
61             '32', 'exit.png', name='exit', pos=(SCREEN_SIZE[0] - 50, y)))
62         return tools
63
64     def add_day_button(self):
65         y = SCREEN_SIZE[1] - 40
66         self._tools.append(ImageButton(
67             '32', 'day.png', name='day', pos=(SCREEN_SIZE[0] - 200, y)))
68
69     @property
70     def turnip_count(self):
71         return len(self._turnips)
72
73     @property
74     def power_usage(self):
75         power = self._lights.total_power_usage()
76         power = power / (FPS * NIGHT_HOURS_PER_TICK)
77         return int(round(power))
78
79     def remaining_hours(self):
80         return int(round(
81             (NIGHT_LENGTH - self._total_ticks) * NIGHT_HOURS_PER_TICK))
82
83     @debug_timer("night.render")
84     def render(self, surface, gamestate):
85         surface.blit(self._soil, (0, 0))
86
87         self._mould.render(surface)
88
89         for turnip in self._turnips[:]:
90             if turnip.eaten:
91                 self._turnips.remove(turnip)
92                 turnip.remove()
93                 gamestate.eaten += 1
94                 self._eaten_tonight += 1
95             else:
96                 turnip.render(surface)
97
98         self._lights.render_light(surface)
99         self._obstacles.render(surface)
100         self._lights.render_fittings(surface)
101         self._infobar.render(surface, gamestate)
102         self._countdownbar.render(surface, self.remaining_hours())
103
104         for tool in self._tools:
105             tool.render(surface)
106
107         for text, text_pos in self._night_over_text:
108             surface.blit(text, text_pos, None)
109
110     def event(self, ev, gamestate):
111         if self._ending:
112             return
113         if ev.type == pgl.KEYDOWN:
114             if not self._do_ticks:
115                 # Any keypress exits
116                 self._to_day(gamestate)
117             if ev.key in (pgl.K_q, pgl.K_ESCAPE):
118                 self._ending = True
119                 from .menu import MenuScene
120                 SceneChangeEvent.post(scene=MenuScene())
121             elif ev.key == pgl.K_e and DEBUG:
122                 self._end_night()
123             elif ev.key == pgl.K_SPACE:
124                 self.toggle_pause()
125         elif ev.type == pgl.MOUSEBUTTONDOWN:
126             if not self._do_ticks:
127                 # Any mouse press exits
128                 self._to_day(gamestate)
129             if ev.button == 1:
130                 self._lights.toggle_nearest(ev.pos, surfpos=True)
131
132                 # Check tools
133                 for tool in self._tools:
134                     if tool.pressed(ev):
135                         if tool.name == 'pause play':
136                             self.toggle_pause()
137                         elif tool.name == 'exit':
138                             self._ending = True
139                             from .menu import MenuScene
140                             SceneChangeEvent.post(scene=MenuScene())
141                         elif tool.name == 'day':
142                             self._to_day(gamestate)
143
144     def toggle_pause(self):
145         self._paused = not self._paused
146         pause_img = "play.png" if self._paused else "pause.png"
147         for tool in self._tools:
148             if tool.name == 'pause play':
149                 tool.update_image("32", pause_img)
150
151     def _to_day(self, gamestate):
152         # End the night
153         if self._ending:
154             return
155         gamestate.update_lights(self._lights)
156         # Turnip
157         self.grow_turnips(gamestate)
158         turnip_data = [turnip.serialize() for turnip in self._turnips]
159         gamestate.turnips = turnip_data
160         gamestate.days += 1
161         self._mould.update_resistances(gamestate)
162         self._ending = True
163         from .day import DayScene
164         SceneChangeEvent.post(scene=DayScene())
165
166     def _end_night(self):
167         self._do_ticks = False
168         self._night_over_text = []
169         overlay = pygame.surface.Surface(
170             (SCREEN_SIZE[0], 240), pgl.SWSURFACE).convert_alpha()
171         overlay.fill((0, 0, 0, 172))
172         self._night_over_text.append((overlay, (0, 40)))
173         self._night_over_text.append(
174             (shadowed_text("The Night is Over", FONTS["bold"], 48), (300, 50)))
175         self._night_over_text.append(
176             (shadowed_text("Turnips eaten tonight: %d" % self._eaten_tonight,
177                            FONTS["sans"], 32), (300, 130)))
178         self._night_over_text.append(
179             (shadowed_text("Surviving turnips: %d" % len(self._turnips),
180                            FONTS["sans"], 32), (300, 170)))
181         self._night_over_text.append(
182             (shadowed_text("Press any key to continue", FONTS["sans"], 24),
183              (350, 240)))
184
185     def check_battery(self):
186         if self._battery.current == 0 and not self._battery_dead:
187             self._battery_dead = True
188             sound.play_sound("battery_dying.ogg")
189             self._lights.battery_dead()
190
191     @debug_timer("night.tick")
192     def tick(self, gamestate):
193         if self._do_ticks and not self._paused:
194             if self._total_ticks < NIGHT_LENGTH:
195                 self._mould.tick(gamestate, self._space, self._lights)
196                 self._lights.tick()
197                 if self._total_ticks % FPS == 0:
198                     self._battery.current -= int(
199                         self._lights.total_power_usage())
200                     self.check_battery()
201                 self._total_ticks += 1
202             else:
203                 self._end_night()
204             if not self._mould.alive():
205                 self._end_night()
206             if not self.turnip_count:
207                 self.add_day_button()
208             if not self.turnip_count and not self._battery.current:
209                 self._end_night()
210
211     def grow_turnips(self, gamestate):
212         """ Turnips grow at the end of the night """
213         for turnip in self._turnips[:]:
214             # Turnips grow at dawn
215             seeds = turnip.grow()
216             if seeds:
217                 gamestate.seeds += seeds
218                 gamestate.harvested += 1
219                 self._turnips.remove(turnip)
220                 # We ignore the body cleanup, since the space is going away