NOTICE: update dates and authors.
[nit.git] / lib / bucketed_game.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2011-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 # Provides basic game logic utilities using buckets to coordinate and
18 # optimize actions on game turn ends. Supports both action at each
19 # end of turn as well as actions on some end of turns.
20 #
21 # Allows for fast support of a large number of entities with rare actions,
22 # such as a forest with many individual trees.
23 module bucketed_game
24
25 # Something acting on the game
26 class Turnable[G: Game]
27 fun do_turn(turn: GameTurn[G]) is abstract
28 end
29
30 # Something acting on the game from time to time
31 class Bucketable[G: Game]
32 super Turnable[G]
33 private var act_at: nullable Int = null
34
35 # Cancel the previously registered acting turn
36 #
37 # Once called, `self.do_turn` will not be invoked until `GameTurn::act_next`
38 # or `GameTurn::act_in` are called again.
39 fun cancel_act do act_at = null
40 end
41
42 # Optiomized organization of `Bucketable` instances
43 class Buckets[G: Game]
44 super Turnable[G]
45 type BUCKET: HashSet[Bucketable[G]]
46
47 private var buckets: Array[BUCKET] is noinit
48
49 private var next_bucket: nullable BUCKET = null
50 private var current_bucket_key: Int = -1
51
52 init
53 do
54 var n_buckets = 100
55 buckets = new Array[BUCKET].with_capacity(n_buckets)
56
57 for b in [0 .. n_buckets [do
58 buckets[b] = new HashSet[Bucketable[G]]
59 end
60 end
61
62 fun add_at(e: Bucketable[G], at_tick: Int)
63 do
64 var at_key = key_for_tick(at_tick)
65
66 if at_key == current_bucket_key then
67 next_bucket.as(not null).add(e)
68 else
69 buckets[at_key].add(e)
70 end
71
72 e.act_at = at_tick
73 end
74
75 private fun key_for_tick(at_tick: Int): Int
76 do
77 return at_tick % buckets.length
78 end
79
80 redef fun do_turn(turn: GameTurn[G])
81 do
82 current_bucket_key = key_for_tick(turn.tick)
83 var current_bucket = buckets[current_bucket_key]
84
85 var next_bucket = new HashSet[Bucketable[G]]
86
87 for e in current_bucket do
88 var act_at = e.act_at
89 if act_at != null then
90 if turn.tick == act_at then
91 e.do_turn(turn)
92 else if act_at > turn.tick and
93 key_for_tick(act_at) == current_bucket_key
94 then
95 next_bucket.add(e)
96 end
97 end
98 end
99
100 self.next_bucket = next_bucket
101 buckets[current_bucket_key] = next_bucket
102 end
103 end
104
105 # Game related event
106 interface GameEvent
107 fun apply( game : ThinGame ) is abstract
108 end
109
110 # Event raised at the first turn
111 class FirstTurnEvent
112 super GameEvent
113 end
114
115 # Game logic on the client
116 class ThinGame
117 var tick: Int = 0 is protected writable
118 end
119
120 # Game turn on the client
121 class ThinGameTurn[G: ThinGame]
122 var tick: Int = 0 is protected writable
123
124 var events: List[GameEvent] = new List[GameEvent] is protected writable
125
126 init (t: Int) do tick = t
127 end
128
129 # Game turn on the full logic
130 class GameTurn[G: Game]
131 super ThinGameTurn[G]
132 var game: G
133
134 init (g: G)
135 do
136 super(g.tick)
137 game = g
138 end
139
140 fun act_next(e: Bucketable[G])
141 do
142 game.buckets.add_at(e, tick + 1)
143 end
144
145 fun act_in(e: Bucketable[G], t: Int)
146 do
147 game.buckets.add_at(e, tick + t)
148 end
149
150 fun add_event( event : GameEvent )
151 do
152 event.apply( game )
153 events.add( event )
154 end
155 end
156
157 # Full game logic
158 class Game
159 super ThinGame
160 type G: Game
161
162 var buckets: Buckets[G] = new Buckets[G]
163
164 # Last turn executed in this game
165 # Can be used to consult the latest events (by the display for example),
166 # but cannot be used to add new Events.
167 var last_turn: nullable ThinGameTurn[G] = null
168
169 init do end
170
171 fun do_turn: GameTurn[G]
172 do
173 var turn = new GameTurn[G](self)
174
175 do_pre_turn(turn)
176 buckets.do_turn(turn)
177 do_post_turn(turn)
178
179 last_turn = turn
180
181 tick += 1
182
183 return turn
184 end
185
186 fun do_pre_turn(turn: GameTurn[G]) do end
187 fun do_post_turn(turn: GameTurn[G]) do end
188 end