lib/gamnit: intro the asteronit example
[nit.git] / lib / gamnit / examples / 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 else
87 object.destroy
88 end
89
90 asteroid.destroy
91 break
92 end
93 end
94 end
95 end
96 end
97
98 # Physical object in space physics
99 abstract class SpacialObject
100
101 # World in which this object belongs
102 var world: World
103
104 # Current position
105 var center = new Point3d[Float](0.0, 0.0, 0.0)
106
107 # Position inertia, applied on `center` at each `do_turn`
108 var inertia = new Point3d[Float](0.0, 0.0, 0.0)
109
110 # Current rotation
111 var rotation = 0.0
112
113 # Rotation inertia, applied on `rotation` at each `do_turn`
114 var rotation_inertia = 0.0
115
116 # Rotation force, currently applied by the pilot
117 var applied_rotation = 0.0 is writable
118
119 # Thrust force, currently applied by the pilot
120 var applied_thrust = 0.0 is writable
121
122 # Radius of this object for collision detection
123 var radius: Float is noinit
124
125 # New instance copying the data from `other` with an optional `variation`
126 init copy(other: SpacialObject, variation: nullable Float)
127 do
128 init other.world
129
130 if variation == null then variation = 0.0
131
132 center.x = other.center.x
133 center.y = other.center.y
134 center.z = other.center.z
135
136 inertia.x = other.inertia.x + variation - 2.0*variation.rand
137 inertia.y = other.inertia.y + variation - 2.0*variation.rand
138
139 rotation = other.rotation
140 if variation != 0.0 then rotation = 2.0 * pi.rand
141 end
142
143 # Apply `thrust` forward on this object
144 fun apply_thrust(thrust: Float)
145 do
146 inertia.x += thrust * rotation.cos
147 inertia.y += thrust * rotation.sin
148 end
149
150 # Execute a turn that took `dt` seconds
151 fun do_turn(dt: Float)
152 do
153 # Forces to inertia
154 var t = applied_thrust * 5.0
155 inertia.x += t * rotation.cos
156 inertia.y += t * rotation.sin
157
158 # Arcade rotation
159 var r = applied_rotation * 0.05
160 rotation += r
161
162 # Realistic rotation, kept for reference and reality minded individuals
163 #var r = applied_rotation * 0.2
164 #rotation_inertia += r
165 #rotation_inertia = rotation_inertia.min(2.0).max(-2.0)
166
167 # Inertia to position
168 rotation += rotation_inertia * dt
169 center.x += inertia.x * dt
170 center.y += inertia.y * dt
171 center.z += inertia.z * dt
172
173 # Wrap objects so they stay in the screen
174 while center.x < -world.half_width do center.x += 2.0 * world.half_width
175 while center.x > world.half_width do center.x -= 2.0 * world.half_width
176 while center.y < -world.half_height do center.y += 2.0 * world.half_height
177 while center.y > world.half_height do center.y -= 2.0 * world.half_height
178 end
179
180 # Destroy this object
181 fun destroy do world.objects.remove self
182 end
183
184 # Player's ship
185 class Ship
186 super SpacialObject
187
188 init do radius = 20.0
189
190 # Open fire forward
191 fun fire
192 do
193 var bullet = new Bullet.copy(world.ship)
194 bullet.center.z = -1.0 # in the background
195 bullet.apply_thrust 500.0 # give a boost
196
197 world.objects.add bullet
198 world.bullets.add bullet
199 end
200 end
201
202 # Asteroid, the main obstacle in this game
203 class Asteroid
204 super SpacialObject
205
206 # Size of this asteroid, should be greater than 0
207 var size: Int
208
209 # Color, or type, on this asteroid
210 var color: Int = 2.rand
211
212 init
213 do
214 rotation_inertia = 0.5 - 1.0.rand
215 radius = 22.5 * size.to_f
216 end
217
218 # New asteroid breaking off from `other`
219 init break_off(other: Asteroid)
220 do
221 size = other.size - 1
222 color = other.color
223
224 copy(other, 60.0)
225 end
226
227 # Explode and break off this asteroid
228 redef fun destroy
229 do
230 super
231
232 world.asteroids.remove self
233
234 if size == 1 then return # Do not break off
235
236 for p in world.n_asteroid_parts.times do
237 var asteroid = new Asteroid.break_off(self)
238 asteroid.size = size - 1
239 asteroid.color = color
240
241 asteroid.inertia.x += 1.0 - 2.0.rand
242 asteroid.inertia.y += 1.0 - 2.0.rand
243
244 world.objects.add asteroid
245 world.asteroids.add asteroid
246 end
247 end
248 end
249
250 # Bullet or beam fired from a `Ship`
251 class Bullet
252 super SpacialObject
253
254 # Time left before this bullet expires
255 var ttl = 5.0
256
257 init do radius = 0.0
258
259 redef fun do_turn(dt)
260 do
261 super
262
263 ttl -= dt
264 if ttl <= 0.0 then destroy
265 end
266
267 redef fun destroy
268 do
269 super
270 world.bullets.remove self
271 end
272 end