1 module game;
2 
3 import cloud;
4 import birdo;
5 import player;
6 import cactus;
7 import ground;
8 import counter;
9 import helpers;
10 import resourcemanager;
11 
12 import dsfml.graphics;
13 import dsfml.window;
14 import dsfml.system;
15 import dsfml.audio;
16 
17 import std.conv;
18 import std.random;
19 import std.algorithm;
20 import std.datetime;
21 import std.datetime.stopwatch : StopWatch;
22 
23 static this() {
24   assert(resource_manager.register!Texture("assets/gameover.png", "gameover"));
25   assert(resource_manager.register!SoundBuffer("assets/sound_button.mp3", "button"));
26   assert(resource_manager.register!SoundBuffer("assets/sound_hit.mp3", "hit"));
27   assert(resource_manager.register!SoundBuffer("assets/sound_score100.mp3", "score"));
28 }
29 
30 /++
31  + Dino game,
32  +
33  + Creates a properly sized window and runs the whole simulation
34  +/
35 class Game {
36   private RenderWindow window;
37   private Player player;
38   private Ground ground;
39   private Birdo[] birdos;
40   private Cloud[] clouds;
41   private Cactus[] cactuses;
42   private float seconds_to_next_birdo;
43   private float seconds_to_next_cloud;
44   private float seconds_to_next_cactus;
45   private StopWatch birdo_stopwatch;
46   private StopWatch cloud_stopwatch;
47   private StopWatch cactus_stopwatch;
48   private Counter counter;
49 
50   private Sound button_sound;
51   private Sound hit_sound;
52   private Sound score_sound;
53 
54   /++ Width/height ratio +/
55   static immutable float width_to_height_ratio = 4;
56 
57   private void birdo_generation() {
58     if(birdo_stopwatch.peek.asSeconds > seconds_to_next_birdo) {
59       birdos ~= new Birdo(window.getSize.x, Birdo.random_level);
60       birdos[$-1].window_height = window.getSize.y;
61       seconds_to_next_birdo = uniform01!float * 8 + 1;
62       birdo_stopwatch.reset();
63 
64       if(seconds_to_next_cactus - cactus_stopwatch.peek.asSeconds < .6) {
65         seconds_to_next_cactus += .6;
66       }
67     }
68   }
69 
70   private void cactus_generation() {
71     if(cactus_stopwatch.peek.asSeconds > seconds_to_next_cactus) {
72       cactuses ~= random_cactus_at(600, window.getSize.y);
73       seconds_to_next_cactus = uniform01!float * 1 + .6;
74       cactus_stopwatch.reset();
75 
76       if(seconds_to_next_birdo - birdo_stopwatch.peek.asSeconds < .6) {
77         seconds_to_next_birdo += .6;
78       }
79     }
80   }
81 
82   private void cloud_generation() {
83     if(cloud_stopwatch.peek.asSeconds > seconds_to_next_cloud) {
84       clouds ~= new Cloud(window.getSize.x + 100, uniform01!float * 50 + 20);
85       seconds_to_next_cloud = uniform01!float * 8 + 1;
86       cloud_stopwatch.reset();
87     }
88   }
89 
90   private void on_key_pressed(Keyboard.Key code) {
91     if(code == Keyboard.Key.Space || code == Keyboard.Key.Up) {
92       const jumped = player.jump();
93       if(jumped && button_sound.status != Sound.Status.Playing) {
94         button_sound.play();
95       }
96     }
97     if(code == Keyboard.Key.Down && player.vert_velocity > 0) {
98       player.vert_velocity = 0;
99     }
100   }
101 
102   private bool update_simulation() {
103     bool close;
104 
105     birdo_generation();
106     cloud_generation();
107     cactus_generation();
108 
109     foreach(cactus; cactuses) {
110       if(player.collider.intersects(cactus.collider)) {
111         close = true;
112         player.dead = true;
113       }
114     }
115     foreach(birdo; birdos) {
116       if(player.collider.intersects(birdo.collider)) {
117         close = true;
118         player.dead = true;
119       }
120     }
121 
122     player.update();
123     foreach(cactus; cactuses) {
124       cactus.move(player.displacement);
125     }
126     foreach(birdo; birdos) {
127       birdo.move(player.displacement);
128     }
129     foreach(cloud; clouds) {
130       cloud.move(player.displacement / 12);
131     }
132     ground.move(player.displacement);
133 
134     // Remove cactuses that moved outside of the screen
135     cactuses = remove!"a.horizontal_offset < -100"(cactuses);
136 
137     counter.num = player.displacement_tot.to!uint / 45;
138     if(counter.num % 100 == 0 && counter.num != 0 && score_sound.status != Sound.Status.Playing) {
139       score_sound.play();
140     }
141 
142     return close;
143   }
144 
145   private void draw_objects() {
146     window.clear(Color(247, 247, 247));
147 
148     window.draw(ground);
149 
150     foreach(cactus; cactuses) {
151       window.draw(cactus);
152     }
153     foreach(cloud; clouds) {
154       window.draw(cloud);
155     }
156     foreach(birdo; birdos) {
157       window.draw(birdo);
158     }
159 
160     window.draw(player);
161     window.draw(counter);
162     window.display();
163   }
164 
165   private void session() {
166     bool close;
167     while(!close) {
168       foreach(ev; window.events)
169       switch(ev.type) {
170         case Event.EventType.Closed:     window.close(); close = true; break;
171         case Event.EventType.KeyPressed: on_key_pressed(ev.key.code);  break;
172         default: break;
173       }
174 
175       close = update_simulation();
176       draw_objects();
177     }
178   }
179 
180   private void endscreen() {
181     const tex = window.screenshot;
182     auto screenshot = new Sprite(tex);
183     auto gameover_sprite = new Sprite(resource_manager.get!Texture("gameover"));
184     gameover_sprite.position((window.middle - resource_manager.get!Texture("gameover").middle).changeType!float);
185 
186     bool open = true;
187     bool key_released = true, key_pressed;
188 
189     import std.traits : EnumMembers;
190     foreach(key; EnumMembers!(Keyboard.Key))
191     if(Keyboard.isKeyPressed(key)) {
192       key_released = false;
193     }
194 
195     while(open) {
196       foreach(ev; window.events) {
197         switch(ev.type) {
198           case Event.EventType.Closed:      window.close(); open = false; break;
199           case Event.EventType.KeyReleased: key_released = true;          break;
200           case Event.EventType.KeyPressed:  key_pressed = key_released;   break;
201           default: break;
202         }
203       }
204       if(key_pressed && key_released) {
205         open = false;
206       }
207 
208       window.draw(screenshot);
209       window.draw(gameover_sprite);
210       window.display();
211     }
212   }
213 
214   private void initialize(uint size) {
215     player = new Player();
216     player.window_height = size;
217 
218     cactus_stopwatch.start();
219     cloud_stopwatch.start();
220     birdo_stopwatch.start();
221 
222     ground = new Ground();
223 
224     button_sound = new Sound;
225     button_sound.setBuffer(resource_manager.get!SoundBuffer("button"));
226 
227     hit_sound = new Sound;
228     hit_sound.setBuffer(resource_manager.get!SoundBuffer("hit"));
229 
230     score_sound = new Sound;
231     score_sound.setBuffer(resource_manager.get!SoundBuffer("score"));
232 
233     cactuses.length = 0;
234     birdos.length = 0;
235 
236     counter = new Counter;
237 
238     seconds_to_next_birdo = 30;
239     seconds_to_next_cloud = 0;
240     seconds_to_next_cactus = 3;
241   }
242 
243   /++ Creates a Dino game instance, size refers to height of the window +/
244   this(uint size) {
245     window = new RenderWindow(VideoMode(cast(uint) width_to_height_ratio * size, size), "dino");
246     window.setVerticalSyncEnabled(true);
247 
248     initialize(size);
249   }
250 
251   /++ Runs the game +/
252   void run() {
253     while(window.isOpen) {
254       session();
255       window.display();
256       hit_sound.play();
257 
258       if(!window.isOpen) {
259         break;
260       }
261 
262       endscreen();
263       button_sound.play();
264 
265       initialize(window.size.y);
266     }
267   }
268 }