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