src: transform all old writable in annotations
[nit.git] / examples / mnit_dino / src / game_logic.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Entire game logic for the Dino game
18 # Depends only on Nit standard library
19 module game_logic
20
21 interface Turnable
22 fun do_turn( turn : Turn ) is abstract
23 end
24
25 class Game
26 var nbr_wanted_cavemen : Int
27
28 var dino = new Dino
29 var cavemen = new Array[Caveman]
30 var javelins_in_air = new Array[Javelin]
31 var items_on_ground = new Array[Entity]
32 var entities = new Array[Entity].with_items(dino)
33 var turn_nbr = 0
34
35 var over_since = 0
36
37 var score: Container[Int]
38
39 var random_radius_min = 200
40 var random_radius_max = 400
41 protected var random_radius_diff : Int =
42 random_radius_max - random_radius_min
43
44 var entities_sorter = new EntitiesSorter
45
46 init( cavemen_nbr : Int, score: Container[Int] )
47 do
48 srand_from(cavemen_nbr)
49
50 self.score = score
51
52 nbr_wanted_cavemen = cavemen_nbr
53
54 for n in [0..nbr_wanted_cavemen[ do
55 var man = new Caveman
56 cavemen.add( man )
57 entities.add( man )
58
59 var radius = (random_radius_min + random_radius_diff.rand).to_f
60 var angle = (2.0*pi).rand
61 man.pos.x = ( angle.cos * radius ).to_i
62 man.pos.y = ( angle.sin * radius ).to_i
63 end
64
65 for b in 24.times do
66 var bush = new Bush
67 bush.pos = new GamePos([-300..300[.rand, [-400..400[.rand)
68 items_on_ground.add bush
69 entities.add bush
70 end
71 end
72
73 fun do_turn : Turn
74 do
75 turn_nbr += 1
76 var turn = new Turn( self )
77
78 dino.do_turn( turn )
79 for man in cavemen do
80 man.do_turn( turn )
81 if not man.is_alive then
82 cavemen.remove( man )
83 score.item += 1
84 end
85 end
86
87 for j in javelins_in_air do
88 j.do_turn( turn )
89 if j.hit_dino then
90 javelins_in_air.remove( j )
91 entities.remove( j )
92 else if j.hit_ground then
93 javelins_in_air.remove( j )
94 items_on_ground.add( j )
95 end
96 end
97
98 if over and over_since == 0 then
99 over_since = turn.nbr
100 end
101
102 # sort for blitting, firsts and in the back
103 entities_sorter.sort entities
104
105 return turn
106 end
107
108 fun add_javelin( j : Javelin )
109 do
110 javelins_in_air.add( j )
111 entities.add( j )
112 end
113
114 fun over : Bool do return dino.life <= 0 or cavemen.is_empty
115 fun won : Bool do return cavemen.is_empty and dino.is_alive
116 fun lost : Bool do return not won
117
118 fun ready_to_start_over : Bool do return over_since + 80 < turn_nbr
119 end
120
121 class Turn
122 var game : Game
123 var nbr : Int
124
125 init ( g : Game )
126 do
127 game = g
128 nbr = game.turn_nbr
129 end
130 end
131
132 class GamePos
133 var x : Int
134 var y : Int
135
136 init ( x, y : Int )
137 do
138 self.x = x
139 self.y = y
140 end
141
142 init copy( src : GamePos )
143 do
144 x = src.x
145 y = src.y
146 end
147
148 fun squared_dist_with( other : GamePos ) : Int
149 do
150 var dx = other.x - x
151 var dy = other.y - y
152
153 return dx*dx + dy*dy
154 end
155
156 redef fun to_s do return "<{x}|{y}>"
157 end
158
159 class Entity
160 super Turnable
161
162 fun run_over_distance_x: Int do return 50
163 fun run_over_distance_y: Int do return 16
164
165 var pos = new GamePos( 0, 0 )
166
167 fun squared_dist_with_dino( game : Game ) : Int
168 do
169 return pos.squared_dist_with( game.dino.pos )
170 end
171
172 fun under_dino(game: Game): Bool
173 do
174 var dy = pos.y - game.dino.pos.y
175 if dy.abs > run_over_distance_y then return false
176
177 var dx = pos.x - game.dino.pos.x
178 return dx.abs <= run_over_distance_x
179 end
180 end
181
182 class MovingEntity
183 super Entity
184
185 var going_to : nullable GamePos = null is writable
186
187 fun speed : Int is abstract
188
189 var going_left = false
190 var going_right = false
191
192 redef fun do_turn( t )
193 do
194 if going_to != null then
195 var ds = pos.squared_dist_with( going_to.as(not null) )
196 if ds < speed*speed then
197 going_to = null # is there
198 else
199 var dx = going_to.x - pos.x
200 var dy = going_to.y - pos.y
201 var a = atan2( dy.to_f, dx.to_f )
202 var mx = a.cos*speed.to_f
203 var my = a.sin*speed.to_f
204
205 pos.x += mx.to_i
206 pos.y += my.to_i
207
208 going_left = mx < 0.0
209 going_right = mx > 0.0
210 end
211 end
212 end
213 end
214
215 class MortalEntity
216 super Entity
217
218 fun is_alive : Bool is abstract
219 end
220
221 class Dino
222 super MovingEntity
223 super MortalEntity
224
225 #var running_until = 0
226 var total_life = 20
227 var life : Int = total_life
228
229 redef fun speed do return 8
230
231 redef fun is_alive do return life > 0
232
233 fun hit( hitter : Entity, damage : Int )
234 do
235 if is_alive then
236 life -= damage
237 end
238 end
239
240 redef fun do_turn( t )
241 do
242 if is_alive then
243 super
244 end
245
246 for i in t.game.items_on_ground do
247 if i.under_dino(t.game) then
248 t.game.items_on_ground.remove i
249 t.game.entities.remove i
250 end
251 end
252 end
253 end
254
255 class Caveman
256 super MovingEntity
257 super MortalEntity
258
259 var afraid_until = 0
260 var cannot_throw_until = 0
261
262 var throw_distance : Int = 400*40+10.rand
263 var fear_distance : Int = 300*20+8.rand
264 var flee_distance : Int = 600*60+16.rand
265
266 var fear_duration : Int = 80+40.rand
267 var throw_period : Int = 40+8.rand
268
269 var variance_angle: Float = 2.0*pi.rand
270 var variance_dist: Int = throw_distance.to_f.sqrt.to_i-4
271 var variance_x: Int = (variance_angle.cos*variance_dist.to_f).to_i
272 var variance_y: Int = (variance_angle.sin*variance_dist.to_f).to_i
273
274 redef var is_alive : Bool = true
275
276 redef var speed: Int = 3+3.rand
277
278 fun is_afraid( turn : Turn ) : Bool do return turn.nbr < afraid_until
279 fun can_throw( turn : Turn ) : Bool do return cannot_throw_until < turn.nbr
280 fun die(turn: Turn) do is_alive = false
281
282 redef fun do_turn( t )
283 do
284 if is_alive then
285 if under_dino(t.game) then
286 if t.game.dino.is_alive then die(t)
287 return
288 else if is_afraid( t ) then
289 # going to destination
290 else if t.game.dino.life <= 0 then
291 # dino is dead, chill
292 else
293 var dwd = squared_dist_with_dino( t.game )
294 if dwd < fear_distance then
295 afraid_until = t.nbr + fear_duration
296
297 var dino_pos = t.game.dino.pos
298 var dx = dino_pos.x - pos.x
299 var dy = dino_pos.y - pos.y
300 var a = atan2( dy.to_f, dx.to_f )
301 a += pi # get opposite
302 a += [-100..100[.rand.to_f*pi/3.0/100.0
303 var x = a.cos*flee_distance.to_f
304 var y = a.sin*flee_distance.to_f
305 going_to = new GamePos( x.to_i, y.to_i )
306 else if dwd < throw_distance then
307 if can_throw( t ) then
308 cannot_throw_until = t.nbr + throw_period
309 var javelin = new Javelin( pos, t.game.dino.pos )
310 t.game.add_javelin( javelin )
311 going_to = null
312 end
313 else
314 # get closer to dino
315 var dino_pos = t.game.dino.pos
316 going_to = new GamePos(dino_pos.x+variance_x, dino_pos.y+variance_y)
317 end
318 end
319
320 super
321 end
322 end
323 end
324
325 class Javelin
326 super Entity
327
328 var z = 400
329 var angle : Float = pi/2.0
330
331 var thrown_angle_xy : Float
332 #var thrown_angle_z
333 var speed_z = 60
334
335 var hit_ground = false
336 var hit_dino = false
337
338 var speed : Int = 10+2.rand
339 var hit_dino_distance = 128
340 var hit_damage = 1
341 var gravity : Int = -3
342
343 init ( from : GamePos, to : GamePos )
344 do
345 var dx = to.x-from.x
346 var dy = to.y-from.y
347 thrown_angle_xy = atan2( dy.to_f, dx.to_f )
348 pos = new GamePos.copy( from )
349 end
350
351 redef fun do_turn( t )
352 do
353 var dwd = squared_dist_with_dino( t.game )
354 if dwd < hit_dino_distance and t.game.dino.is_alive then
355 t.game.dino.hit( self, hit_damage )
356 hit_dino = true
357 else
358 if z <= 0 then
359 hit_ground = true
360 if thrown_angle_xy.cos > 0.0 then
361 angle = pi*5.0/8.0+(pi/4.0).rand
362 else
363 # left of the screen
364 angle = pi*9.0/8.0+(pi/4.0).rand
365 end
366 else
367 # in the air
368 speed_z += gravity
369 z += speed_z
370
371 var mx = (thrown_angle_xy.cos * speed.to_f).to_i
372 pos.x += mx
373 pos.y += (thrown_angle_xy.sin * speed.to_f).to_i
374
375 if mx > 0 then
376 angle = atan2( (speed_z/10).to_f, -1.0*speed.to_f )-pi/2.0
377 else
378 angle = atan2( (speed_z/10).to_f, speed.to_f )-pi/2.0
379 end
380 end
381 end
382 end
383 end
384
385 class Bush super Entity end
386
387 # Sort entities on screen in order of Y, entities in the back are drawn first
388 class EntitiesSorter
389 super AbstractSorter[Entity]
390
391 redef fun compare(a, b) do return b.pos.y <=> a.pos.y
392 end