1-- This file is a part of Avoision. 2-- 3-- Copyright (C) 2011 Jared Krinke. 4-- 5-- Avoision is free software; you can redistribute it and/or 6-- modify it under the terms of the GNU General Public License 7-- as published by the Free Software Foundation; either version 2 8-- of the License, or (at your option) any later version. 9-- 10-- This program is distributed in the hope that it will be useful, 11-- but WITHOUT ANY WARRANTY; without even the implied warranty of 12-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13-- GNU General Public License for more details. 14-- 15-- You should have received a copy of the GNU General Public License 16-- along with this program; if not, write to the Free Software 17-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 19require("Avoision/Common.lua") 20require("Avoision/Enemy.lua") 21require("Avoision/Seeker.lua") 22require("Effects/Ghost.lua") 23require("Avoision/Goal.lua") 24require("Avoision/Player.lua") 25 26local transitionPeriod = 1000 27local boardWidth = 400 28local boardHeight = 400 29local boardTimeout = 19000 30local boardPlayerMinimumDistance = 0.15 31local enemyWidth = 1 / 30 32local enemyWidthMin = 1 / 90 33local enemyWidthMax = 1 / 15 34local enemySpeed = 0.2 / 1000 35local enemySpeedMin = 0.1 / 1000 36local enemySpeedMax = 0.6 / 1000 37local enemyGhostPeriod = 500 38local enemyGhostScaleMax = 0 39local goalWidth = 1 / 30 40local goalHeight = 1 / 30 41local goalSpeed = 0.1 / 1000 42local goalDirectionPeriod = 3000 43 44local pointProgression = { 30, 20, 15, 10, 8, 7, 6, 5, 4, 3, 2, 1, 0 } 45 46Board = { } 47 48function Board.new(layer, lost, ended) 49 local board = Entity.new(0, 0, 0, boardWidth, boardHeight) 50 51 board.paused = false 52 board.finishTimer = 0 53 board.difficulty = Common.difficultyLevel.Easy 54 board.score = 0 55 board.elements:add(Element.Image.new("Images/Background.png", 0, 0, 0, 1, 1, 0)) 56 board.player = Player.new() 57 board.goal = Goal.new(0, 0, goalWidth, goalHeight, 0, 0) 58 board.cd = layer:createCollisionDetector() 59 60 -- TODO: Test this more thoroughly 61 function board.getSafePosition(self, width, height) 62 local ax1 = max(-0.5 + width / 2, self.player.x - (self.player.width / 2 + width / 2 + boardPlayerMinimumDistance)) 63 local ax2 = min(0.5 - width / 2, self.player.x + (self.player.width / 2 + width / 2 + boardPlayerMinimumDistance)) 64 local ay1 = max(-0.5 + height / 2, self.player.y - (self.player.height / 2 + height / 2 + boardPlayerMinimumDistance)) 65 local ay2 = min(0.5 - height / 2, self.player.y + (self.player.height / 2 + height / 2 + boardPlayerMinimumDistance)) 66 67 repeat 68 local x = (random() - 0.5) * (1 - width) 69 local y = (random() - 0.5) * (1 - height) 70 71 local bx1 = x - width / 2 72 local bx2 = x + width / 2 73 local by1 = y - height / 2 74 local by2 = y + height / 2 75 76 local valid = true 77 78 if ((ax1 >= bx1 and ax1 <= bx2) or (ax2 >= bx1 and ax2 <= bx2) or (bx1 >= ax1 and bx1 <= ax2)) 79 and ((ay1 >= by1 and ay1 <= by2) or (ay2 >= by1 and ay2 <= by2) or (by1 >= ay1 and by1 <= ay2)) 80 then 81 valid = false 82 else 83 return x, y 84 end 85 until valid 86 end 87 88 function board.resetGoal(self) 89 self:setPoints(pointProgression[1] * self.difficulty) 90 self.timer = 0 91 92 self.goal.x, self.goal.y = self:getSafePosition(self.goal.width, self.goal.height) 93 94 -- The goal moves on hard 95 if self.difficulty >= Common.difficultyLevel.Hard then 96 self.goal.axisSpeed = goalSpeed 97 else 98 self.goal.axisSpeed = 0 99 end 100 101 -- Update goal speeds as needed 102 self:resetGoalDirection() 103 end 104 105 function board.addEnemy(self) 106 local size = enemyWidth 107 local speed = enemySpeed 108 109 -- Enemy speeds vary on normal 110 if self.difficulty >= Common.difficultyLevel.Normal then 111 speed = enemySpeedMin + (enemySpeedMax - enemySpeedMin) * random() 112 end 113 114 -- Enemies are faster on extreme 115 if self.difficulty >= Common.difficultyLevel.Extreme then 116 speed = speed * 1.1 117 end 118 119 -- Enemy sizes vary on hard (and speed varies more) 120 if self.difficulty >= Common.difficultyLevel.Hard then 121 size = enemyWidthMin + (enemyWidthMax - enemyWidthMin) * random() 122 end 123 124 -- Seekers may appear on extreme 125 local x = random() 126 local seekerProbability = 0 127 128 if self.difficulty >= Common.difficultyLevel.Extreme then 129 seekerProbability = 0.15 130 end 131 132 local e = nil 133 134 if x >= seekerProbability then 135 local speedX = 0 136 local speedY = 0 137 138 -- Direction 139 if random() > 0.5 then 140 speed = -speed 141 end 142 143 -- Axis 144 if random() > 0.5 then 145 speedX = speed 146 else 147 speedY = speed 148 end 149 150 -- On extreme, blocks may move diagonally 151 if self.difficulty >= Common.difficultyLevel.Extreme and random() > 0.67 then 152 local otherSpeed = enemySpeedMin + (enemySpeedMax - enemySpeedMin) * random() 153 154 if speedX == 0 then 155 speedX = otherSpeed 156 elseif speedY == 0 then 157 speedY = otherSpeed 158 end 159 end 160 161 e = Enemy.new(0, 0, size, size, speedX, speedY) 162 else 163 e = Seeker.new(0, 0, size, size, speed, self.player) 164 end 165 166 e.x, e.y = self:getSafePosition(e.width, e.height) 167 168 Ghost.new(e, enemyGhostPeriod, enemyGhostScaleMax, false, function() 169 self:addChild(e) 170 self.cd:addChild(e) 171 end):setParent(self) 172 end 173 174 function board.resetGoalDirection(self) 175 local speed = self.goal.axisSpeed 176 177 -- Direction 178 if random() > 0.5 then 179 speed = -speed 180 end 181 182 -- Axis 183 if random() > 0.5 then 184 self.goal.speed.x = speed 185 self.goal.speed.y = 0 186 else 187 self.goal.speed.x = 0 188 self.goal.speed.y = speed 189 end 190 191 self.goalDirectionTimer = self.timer + goalDirectionPeriod * random() 192 end 193 194 function board.update(self, ms) 195 -- Update children first 196 self:updateChildren(ms) 197 198 -- Update timer 199 self.timer = self.timer + ms 200 201 if not self.paused then 202 local done = false 203 204 -- Check for goal intersection 205 if self.cd:checkCollision(self.player, self.goal) then 206 self:captureGoal() 207 else 208 -- Update goal direction 209 if self.difficulty >= Common.difficultyLevel.Hard and self.timer >= self.goalDirectionTimer then 210 self:resetGoalDirection() 211 end 212 213 local newPoints = self.difficulty * pointProgression[max(1, min(#pointProgression, 1 + floor(self.timer / boardTimeout * #pointProgression)))] 214 215 if newPoints ~= self.points then 216 self:setPoints(newPoints) 217 end 218 end 219 220 -- Check for enemy intersection 221 self.cd:forEachCollision(function(p, e) 222 done = true 223 end, Common.group.player, Common.group.enemy) 224 225 if done then 226 self:lose() 227 end 228 else 229 if self.finishTimer > 0 and self.timer >= self.finishTimer then 230 ended() 231 end 232 end 233 end 234 235 function board.setPoints(self, points) 236 self.points = points 237 end 238 239 function board.getDifficulty(self) 240 return self.difficulty 241 end 242 243 function board.setDifficulty(self, difficulty) 244 self.difficulty = difficulty 245 end 246 247 function board.getScore(self) 248 return self.score 249 end 250 251 function board.setScore(self, score) 252 self.score = score 253 end 254 255 function board.getPaused(self) 256 return self.paused 257 end 258 259 function board.addToScore(self, points) 260 self:setScore(self.score + points) 261 end 262 263 function board.captureGoal(self) 264 self:addToScore(self.points) 265 266 -- Create a ghost and move the goal 267 self.goal:createGhost():setParent(self) 268 self:resetGoal() 269 270 Audio.play("Sounds/Score.wav") 271 272 -- Add another enemy 273 self:addEnemy() 274 end 275 276 function board.lose(self) 277 self.player:clearMovingStates() 278 self.player:createGhost():setParent(self) 279 self:removeChild(self.player) 280 self.paused = true 281 self.finishTimer = self.timer + transitionPeriod 282 283 Audio.play("Sounds/Boom.wav") 284 285 lost() 286 end 287 288 function board.reset(self) 289 self.paused = false 290 self.finishTimer = 0 291 self:setScore(0) 292 293 self:clearChildren() 294 self.cd:clearChildren() 295 296 self:addChild(self.player) 297 self:addChild(self.goal) 298 self.cd:addChild(self.player) 299 self.cd:addChild(self.goal) 300 301 self:resetGoal() 302 end 303 304 return board 305end 306 307