Merge branch 'killclosures' into killnitc
[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 javelins_on_ground = new Array[Javelin]
32 var entities = new Array[Entity].with_items(dino)
33 var turn_nbr = 0
34
35 var over_since = 0
36
37 var random_radius_min = 200
38 var random_radius_max = 400
39 protected var random_radius_diff : Int =
40 random_radius_max - random_radius_min
41
42 init( cavemen_nbr : Int )
43 do
44 srand
45
46 nbr_wanted_cavemen = cavemen_nbr
47
48 for n in [0..nbr_wanted_cavemen[ do
49 var man = new Caveman
50 cavemen.add( man )
51 entities.add( man )
52
53 var radius = (random_radius_min + random_radius_diff.rand).to_f
54 var angle = (2.0*pi).rand
55 man.pos.x = ( angle.cos * radius ).to_i
56 man.pos.y = ( angle.sin * radius ).to_i
57 end
58 end
59
60 fun do_turn : Turn
61 do
62 turn_nbr += 1
63 var turn = new Turn( self )
64
65 dino.do_turn( turn )
66 for man in cavemen do
67 man.do_turn( turn )
68 if not man.is_alive then
69 cavemen.remove( man )
70 end
71 end
72
73 for j in javelins_in_air do
74 j.do_turn( turn )
75 if j.hit_dino then
76 javelins_in_air.remove( j )
77 entities.remove( j )
78 else if j.hit_ground then
79 javelins_in_air.remove( j )
80 javelins_on_ground.add( j )
81 end
82 end
83
84 if over and over_since == 0 then
85 over_since = turn.nbr
86 end
87
88 # sort for blitting, firsts and in the back
89 # FIXME: remove closure
90 entities.sort !cmp( a, b ) = b.pos.y <=> a.pos.y
91
92 return turn
93 end
94
95 fun add_javelin( j : Javelin )
96 do
97 javelins_in_air.add( j )
98 entities.add( j )
99 end
100
101 fun over : Bool do return dino.life <= 0 or cavemen.is_empty
102 fun won : Bool do return cavemen.is_empty and dino.is_alive
103 fun lost : Bool do return not won
104
105 fun ready_to_start_over : Bool do return over_since + 80 < turn_nbr
106 end
107
108 class Turn
109 var game : Game
110 var nbr : Int
111
112 init ( g : Game )
113 do
114 game = g
115 nbr = game.turn_nbr
116 end
117 end
118
119 class GamePos
120 var x : Int
121 var y : Int
122
123 init ( x, y : Int )
124 do
125 self.x = x
126 self.y = y
127 end
128
129 init copy( src : GamePos )
130 do
131 x = src.x
132 y = src.y
133 end
134
135 fun squared_dist_with( other : GamePos ) : Int
136 do
137 var dx = other.x - x
138 var dy = other.y - y
139
140 return dx*dx + dy*dy
141 end
142
143 redef fun to_s do return "<{x}|{y}>"
144 end
145
146 class Entity
147 super Turnable
148
149 var pos = new GamePos( 0, 0 )
150
151 fun squared_dist_with_dino( game : Game ) : Int
152 do
153 return pos.squared_dist_with( game.dino.pos )
154 end
155 end
156
157 class MovingEntity
158 super Entity
159
160 var going_to : nullable GamePos writable = null
161
162 fun speed : Int is abstract
163
164 var going_left = false
165 var going_right = false
166
167 redef fun do_turn( t )
168 do
169 if going_to != null then
170 var ds = pos.squared_dist_with( going_to.as(not null) )
171 if ds < speed*speed then
172 going_to = null # is there
173 else
174 var dx = going_to.x - pos.x
175 var dy = going_to.y - pos.y
176 var a = atan2( dy.to_f, dx.to_f )
177 var mx = a.cos*speed.to_f
178 var my = a.sin*speed.to_f
179
180 pos.x += mx.to_i
181 pos.y += my.to_i
182
183 going_left = mx < 0.0
184 going_right = mx > 0.0
185 end
186 end
187 end
188 end
189
190 class MortalEntity
191 super Entity
192
193 fun is_alive : Bool is abstract
194 end
195
196 class Dino
197 super MovingEntity
198 super MortalEntity
199
200 #var running_until = 0
201 var total_life = 20
202 var life : Int = total_life
203
204 redef fun speed do return 8
205
206 redef fun is_alive do return life > 0
207
208 fun hit( hitter : Entity, damage : Int )
209 do
210 if is_alive then
211 life -= damage
212 end
213 end
214
215 redef fun do_turn( t )
216 do
217 if is_alive then
218 super
219 end
220 end
221 end
222
223 class Caveman
224 super MovingEntity
225 super MortalEntity
226
227 var afraid_until = 0
228 var cannot_throw_until = 0
229
230 var throw_distance : Int = 400*40+10.rand
231 var fear_distance : Int = 300*20+8.rand
232 var flee_distance : Int = 600*60+16.rand
233 var run_over_distance = 500
234
235 var fear_duration : Int = 80+40.rand
236 var throw_period : Int = 40+8.rand
237
238 redef var is_alive : Bool = true
239
240 redef fun speed do return 4
241
242 fun is_afraid( turn : Turn ) : Bool do return turn.nbr < afraid_until
243 fun can_throw( turn : Turn ) : Bool do return cannot_throw_until < turn.nbr
244
245 redef fun do_turn( t )
246 do
247 if is_alive then
248 var dwd = squared_dist_with_dino( t.game )
249
250 if dwd < run_over_distance then
251 is_alive = false
252 return
253 else if is_afraid( t ) then
254 # going to destination
255 else if t.game.dino.life <= 0 then
256 # dino is dead, chill
257 else
258 if dwd < fear_distance then
259 afraid_until = t.nbr + fear_duration
260
261 var dino_pos = t.game.dino.pos
262 var dx = dino_pos.x - pos.x
263 var dy = dino_pos.y - pos.y
264 var a = atan2( dy.to_f, dx.to_f )
265 a += pi # get opposite
266 var x = a.cos*flee_distance.to_f
267 var y = a.sin*flee_distance.to_f
268 going_to = new GamePos( x.to_i, y.to_i )
269 else if dwd < throw_distance then
270 if can_throw( t ) then
271 cannot_throw_until = t.nbr + throw_period
272 var javelin = new Javelin( pos, t.game.dino.pos )
273 t.game.add_javelin( javelin )
274 going_to = null
275 end
276 else
277 # get closer to dino
278 going_to = t.game.dino.pos
279 end
280 end
281
282 super
283 end
284 end
285 end
286
287 class Javelin
288 super Entity
289
290 var z = 400
291 var angle : Float = pi/2.0
292
293 var thrown_angle_xy : Float
294 #var thrown_angle_z
295 var speed_z = 60
296
297 var hit_ground = false
298 var hit_dino = false
299
300 var speed : Int = 10+2.rand
301 var hit_dino_distance = 128
302 var hit_damage = 1
303 var gravity : Int = -3
304
305 init ( from : GamePos, to : GamePos )
306 do
307 var dx = to.x-from.x
308 var dy = to.y-from.y
309 thrown_angle_xy = atan2( dy.to_f, dx.to_f )
310 pos = new GamePos.copy( from )
311 end
312
313 redef fun do_turn( t )
314 do
315 var dwd = squared_dist_with_dino( t.game )
316 if dwd < hit_dino_distance and t.game.dino.is_alive then
317 t.game.dino.hit( self, hit_damage )
318 hit_dino = true
319 else
320 if z <= 0 then
321 hit_ground = true
322 else
323 # in the air
324 speed_z += gravity
325 z += speed_z
326
327 var mx = (thrown_angle_xy.cos * speed.to_f).to_i
328 pos.x += mx
329 pos.y += (thrown_angle_xy.sin * speed.to_f).to_i
330
331 if mx > 0 then
332 angle = atan2( (speed_z/10).to_f, -1.0*speed.to_f )-pi/2.0
333 else
334 angle = atan2( (speed_z/10).to_f, speed.to_f )-pi/2.0
335 end
336 end
337 end
338 end
339 end