contrib/asteronits: add sound effects
[nit.git] / contrib / asteronits / src / game_logic.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Pure game logic, independent of gamnit and other display concerns
16 module game_logic
17
18 import geometry::points_and_lines
19
20 # Root of all entities of a single play
21 class World
22
23 # Number of original asteroids per play
24 var n_asteroids: Int
25
26 # Number of parts created when an asteroid explodes
27 var n_asteroid_parts: Int
28
29 # Ratio of the world: height / width
30 var ratio_height_width: Float
31
32 # Minimum half size of the world, applied either to `half_width` of `half_height`
33 private var min_half_size = 500.0
34
35 # Width of the world
36 var half_width: Float = if ratio_height_width <= 1.0 then
37 min_half_size
38 else min_half_size * ratio_height_width is lazy
39
40 # Height of the world
41 var half_height: Float = if ratio_height_width >= 1.0 then
42 min_half_size
43 else min_half_size / ratio_height_width is lazy
44
45 # Player's ship
46 var ship = new Ship(self)
47
48 # All live spacial objects
49 var objects = new Array[SpacialObject].with_items(ship)
50
51 # All live asteroids
52 var asteroids = new Array[Asteroid]
53
54 # All live bullets
55 var bullets = new Array[SpacialObject]
56
57 init
58 do
59 for a in n_asteroids.times do
60 var asteroid = new Asteroid(self, 3)
61 asteroid.center.x = half_width - 2.0*half_width.rand
62 asteroid.center.y = half_height - 2.0*half_height.rand
63 asteroid.rotation_inertia = 0.5 - 1.0.rand
64
65 objects.add asteroid
66 asteroids.add asteroid
67 end
68 end
69
70 # Execute a turn that took `dt` seconds
71 fun do_turn(dt: Float)
72 do
73 for object in objects do object.do_turn dt
74
75 for object in objects.to_a do if not object isa Asteroid then
76
77 for asteroid in asteroids.to_a do
78 var d2 = object.center.dist2(asteroid.center)
79
80 var r = object.radius + asteroid.radius
81 if d2 < r*r then
82 # Boom
83 if object == ship then
84 # The ship is invincible
85 # TODO health and losing
86 ship.hit
87 else
88 object.destroy
89 end
90
91 asteroid.destroy
92 break
93 end
94 end
95 end
96 end
97 end
98
99 # Physical object in space physics
100 abstract class SpacialObject
101
102 # World in which this object belongs
103 var world: World
104
105 # Current position
106 var center = new Point3d[Float](0.0, 0.0, 0.0)
107
108 # Position inertia, applied on `center` at each `do_turn`
109 var inertia = new Point3d[Float](0.0, 0.0, 0.0)
110
111 # Current rotation
112 var rotation = 0.0
113
114 # Rotation inertia, applied on `rotation` at each `do_turn`
115 var rotation_inertia = 0.0
116
117 # Rotation force, currently applied by the pilot
118 var applied_rotation = 0.0 is writable
119
120 # Thrust force, currently applied by the pilot
121 var applied_thrust = 0.0 is writable
122
123 # Radius of this object for collision detection
124 var radius: Float is noinit
125
126 # New instance copying the data from `other` with an optional `variation`
127 init copy(other: SpacialObject, variation: nullable Float)
128 do
129 init other.world
130
131 if variation == null then variation = 0.0
132
133 center.x = other.center.x
134 center.y = other.center.y
135 center.z = other.center.z
136
137 inertia.x = other.inertia.x + variation - 2.0*variation.rand
138 inertia.y = other.inertia.y + variation - 2.0*variation.rand
139
140 rotation = other.rotation
141 if variation != 0.0 then rotation = 2.0 * pi.rand
142 end
143
144 # Apply `thrust` forward on this object
145 fun apply_thrust(thrust: Float)
146 do
147 inertia.x += thrust * rotation.cos
148 inertia.y += thrust * rotation.sin
149 end
150
151 # Execute a turn that took `dt` seconds
152 fun do_turn(dt: Float)
153 do
154 # Forces to inertia
155 var t = applied_thrust * 5.0
156 inertia.x += t * rotation.cos
157 inertia.y += t * rotation.sin
158
159 # Arcade rotation
160 var r = applied_rotation * 0.05
161 rotation += r
162
163 # Realistic rotation, kept for reference and reality minded individuals
164 #var r = applied_rotation * 0.2
165 #rotation_inertia += r
166 #rotation_inertia = rotation_inertia.min(2.0).max(-2.0)
167
168 # Inertia to position
169 rotation += rotation_inertia * dt
170 center.x += inertia.x * dt
171 center.y += inertia.y * dt
172 center.z += inertia.z * dt
173
174 # Wrap objects so they stay in the screen
175 while center.x < -world.half_width do center.x += 2.0 * world.half_width
176 while center.x > world.half_width do center.x -= 2.0 * world.half_width
177 while center.y < -world.half_height do center.y += 2.0 * world.half_height
178 while center.y > world.half_height do center.y -= 2.0 * world.half_height
179 end
180
181 # Destroy this object
182 fun destroy do world.objects.remove self
183 end
184
185 # Player's ship
186 class Ship
187 super SpacialObject
188
189 init do radius = 20.0
190
191 # Open fire forward
192 fun fire
193 do
194 var bullet = new Bullet.copy(world.ship)
195 bullet.center.z = -1.0 # in the background
196 bullet.apply_thrust 500.0 # give a boost
197
198 world.objects.add bullet
199 world.bullets.add bullet
200 end
201
202 # Something hits the ship
203 fun hit do end
204 end
205
206 # Asteroid, the main obstacle in this game
207 class Asteroid
208 super SpacialObject
209
210 # Size of this asteroid, should be greater than 0
211 var size: Int
212
213 # Color, or type, on this asteroid
214 var color: Int = 2.rand
215
216 init
217 do
218 rotation_inertia = 0.5 - 1.0.rand
219 radius = 22.5 * size.to_f
220 end
221
222 # New asteroid breaking off from `other`
223 init break_off(other: Asteroid)
224 do
225 size = other.size - 1
226 color = other.color
227
228 copy(other, 60.0)
229 end
230
231 # Explode and break off this asteroid
232 redef fun destroy
233 do
234 super
235
236 world.asteroids.remove self
237
238 if size == 1 then return # Do not break off
239
240 for p in world.n_asteroid_parts.times do
241 var asteroid = new Asteroid.break_off(self)
242 asteroid.size = size - 1
243 asteroid.color = color
244
245 asteroid.inertia.x += 1.0 - 2.0.rand
246 asteroid.inertia.y += 1.0 - 2.0.rand
247
248 world.objects.add asteroid
249 world.asteroids.add asteroid
250 end
251 end
252 end
253
254 # Bullet or beam fired from a `Ship`
255 class Bullet
256 super SpacialObject
257
258 # Time left before this bullet expires
259 var ttl = 5.0
260
261 init do radius = 0.0
262
263 redef fun do_turn(dt)
264 do
265 super
266
267 ttl -= dt
268 if ttl <= 0.0 then destroy
269 end
270
271 redef fun destroy
272 do
273 super
274 world.bullets.remove self
275 end
276 end