a9e18444095a645077cfc9cedd5756872c06c23b
1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Physical world logic
16 module world
is serialize
19 import more_collections
24 # The physical world in which this game happens
25 var world
= new World(self)
28 # A terrain features (a rock, a tree, etc.)
31 redef type R
: FeatureRule
33 # Top-left corner of this feature
37 # Metadata for a `Feature`
41 # Strength to resist on `World::explode`
45 # The physical world of the `game`
51 var blast_sites
= new Array[Pos]
53 private var mountain_map
: Noise do
54 var map
= new PerlinNoise
59 private var forest_map
: Noise do
60 var map
= new PerlinNoise
65 # Cache of discovered features, also keeps tracks of changes
66 private var features_cache
= new FeatureMap
68 # Get the `Feature` at `x, y`, if any
69 fun [](x
, y
: Int): nullable Feature
71 if features_cache
.has
(x
, y
) then return features_cache
[x
, y
]
73 # Generate a feature from the noise map
74 var pos
= new Pos(x
.to_f
+0.5, y
.to_f
+0.5)
77 if mountain_map
[x
.to_f
, y
.to_f
] > 0.55 then
78 feature
= new Feature(game
.story
.rock
, pos
)
79 else if forest_map
[x
.to_f
, y
.to_f
] > 0.5 then
80 feature
= new Feature(game
.story
.tree
, pos
)
84 features_cache
[x
, y
] = feature
89 # Detect the first collision with a `Feature` from `src` to `dst`
91 # This is the main collision detection method used by tanks shots, and at tank movement.
92 # The idea is to check all cases between `src` and `dst` and return the first feature found.
93 # Returns `null` if there is no obstacle features.
95 # Example of the cases that would be checked between `s` and `d`:
104 fun first_collision
(src
, dst
: Pos): nullable Feature
106 var going_left
= dst
.x
< src
.x
107 var angle
= src
.atan2
(dst
)
108 var slope
= angle
.tan
110 # Soften slopes approaching infinity
111 if slope
> 100.0 then slope
= 100.0
112 if slope
< -100.0 then slope
= -100.0
114 # For each column (over x) from src.x to dst.x
115 var x0
= src
.x
.floor
.to_i
116 var x1
= dst
.x
.floor
.to_i
117 for x
in [x0
.. x1
].smart_step
do
118 var dx
= x
.to_f
- src
.x
119 var y0
= src
.y
+ dx
*slope
120 var y1
= src
.y
+ (dx
+1.0)*slope
122 var first
= y0
.floor
.to_i
123 var last
= y1
.floor
.to_i
125 # Invert the first and last element of the range
131 # For each row (over y)
132 # from where the line enters the column to where it leaves it
133 for y
in [first
.. last
].smart_step
do
134 if not y
.in_between_floats
(src
.y
, dst
.y
) then continue
136 var feature
= self[x
.to_i
, y
]
137 if feature
!= null then return feature
144 # Apply an explosion at `center` of the given `power`
145 fun explode
(turn
: TTurn, center
: Pos, power
: Int)
147 var x
= center
.x
.floor
.to_i
148 var y
= center
.y
.floor
.to_i
149 var range
= [-power
.. power
]
150 var features
= new Array[Feature]
154 var f
= self[x
+dx
, y
+dy
]
155 var force
= (power-dx
.abs
) + (power-dy
.abs
)
156 if f
!= null and f
.rule
.strength
<= force
then features
.add f
160 turn
.add
new ExplosionEvent(center
, power
, features
)
164 # Map of features organized by their coordinates
166 # The naive implementation is using a `HashMap2`.
167 # This class can be redefed with optimizations as needed.
169 super HashMap2[Int, Int, nullable Feature]
174 var tree
= new FeatureRule(self, 2)
177 var rock
= new FeatureRule(self, 3)
180 var debris
= new FeatureRule(self, 4)
187 # Center of the explosion
193 # All the features this explosion destroys
194 var destroyed_features
: Array[Feature]
196 redef fun apply
(game
)
198 for feature
in destroyed_features
do
199 game
.world
.features_cache
[feature
.pos
.x
.floor
.to_i
, feature
.pos
.y
.floor
.to_i
] = null
202 game
.world
.blast_sites
.add pos
203 if game
.world
.blast_sites
.length
> 100 then game
.world
.blast_sites
.shift
207 # The feature at `pos` changes to `feature`
208 class FeatureChangeEvent
211 # New `Feature`, if any
212 var feature
: nullable Feature
214 # `Pos` of this change
217 redef fun apply
(game
)
219 game
.world
.features_cache
[pos
.x
.floor
.to_i
, pos
.y
.floor
.to_i
] = feature
226 # Position in the world
230 # Add `self` to `other` and return the new position
231 fun +(other
: Point[Float]): Pos
233 var nx
= other
.x
.add
(x
)
234 var ny
= other
.y
.add
(y
)
235 return new Pos(x
.value_of
(nx
), y
.value_of
(ny
))
240 # Is `self` in between `a` and `b`?
243 # assert 1.in_between_floats(0.0, 2.0)
244 # assert 1.in_between_floats(2.0, 0.0)
245 # assert not 1.in_between_floats(2.0, 4.0)
247 fun in_between_floats
(a
, b
: Float): Bool
250 if a
< b
then return a
.floor
- 1.0 < f
and f
< b
.ceil
251 return a
.ceil
> f
and f
> b
.floor
- 1.0
255 redef universal Float
256 # Get the vector with `self` as direction and the given `magnitude`
257 fun to_vector
(magnitude
: Float): Pos
259 return new Pos(cos
*magnitude
, sin
*magnitude
)
264 # Step appropriately to go from `first` to `last`
267 # assert [1..3].smart_step.to_a == [1, 2, 3]
268 # assert [3..1].smart_step.to_a == [3, 2, 1]
270 fun smart_step
: Iterator[E
]
273 if first
> last
then step
= -1
274 return self.step
(step
)