first commit

This commit is contained in:
2021-12-18 20:10:30 +01:00
parent 4d04f63b99
commit 215bed9faf
108 changed files with 3630 additions and 0 deletions

57
src/Animation.lua Normal file
View File

@@ -0,0 +1,57 @@
--[[
GD50
Legend of Zelda
-- Animation Class --
Author: Colton Ogden
cogden@cs50.harvard.edu
]]
Animation = Class{}
function Animation:init(def)
self.frames = def.frames
self.interval = def.interval
self.texture = def.texture
self.looping = def.looping or true
self.timer = 0
self.currentFrame = 1
-- used to see if we've seen a whole loop of the animation
self.timesPlayed = 0
end
function Animation:refresh()
self.timer = 0
self.currentFrame = 1
self.timesPlayed = 0
end
function Animation:update(dt)
-- if not a looping animation and we've played at least once, exit
if not self.looping and self.timesPlayed > 0 then
return
end
-- no need to update if animation is only one frame
if #self.frames > 1 then
self.timer = self.timer + dt
if self.timer > self.interval then
self.timer = self.timer % self.interval
self.currentFrame = math.max(1, (self.currentFrame + 1) % (#self.frames + 1))
-- if we've looped back to the beginning, record
if self.currentFrame == 1 then
self.timesPlayed = self.timesPlayed + 1
end
end
end
end
function Animation:getCurrentFrame()
return self.frames[self.currentFrame]
end

69
src/Ball.lua Normal file
View File

@@ -0,0 +1,69 @@
Ball = Class{}
function Ball:init(x, y, dx, dy)
self.x = x
self.y = y
self.width = 8
self.height = 8
-- these variables are for keeping track of our velocity on both the
-- X and Y axis, since the ball can move in two dimensions
self.dy = dx
self.dx = dy
self.remove = false
self.hitPlayer = false
end
function Ball:reset()
self.x = VIRTUAL_WIDTH / 2 - 2
self.y = VIRTUAL_HEIGHT / 2 - 2
self.dx = 0
self.dy = 0
end
function Ball:update(dt,level)
local tempX = self.x + self.dx * dt
local tempY = self.y + self.dy * dt
local trailSeg = level:pointOnEdge(tempX,tempY,level.player.trailSegments)
if trailSeg then
self.hitPlayer = true
gSounds['hit']:play()
end
local i, segment = level:getTouchingSegment(tempX,tempY)
if segment then
local direction = self:getCollisionDirection(segment, tempX, tempY)
if direction == 'up' or direction == 'down' then
self.dy = - self.dy
elseif direction == 'right' or direction == 'left' then
self.dx = -self.dx
elseif level:pointOnEdge(tempX,tempY) then
self.dx = -self.dx
self.dy = -self.dy
elseif not level:insideBounds(tempX,tempY) then
self.dx = -self.dx
self.dy = -self.dy
end
gSounds['blip']:stop()
gSounds['blip']:play()
end
if not (level:insideBounds(tempX,tempY) or level:pointOnEdge(tempX,tempY)) then
self.remove = true
end
self.x = self.x + self.dx * dt
self.y = self.y + self.dy * dt
end
function Ball:render()
love.graphics.setColor(255,255,255,255)
love.graphics.circle('fill', self.x, self.y, self.width/2)
end
function Ball:getCollisionDirection(segment,x,y)
return segment:sideOfSegment(x,y)
end

64
src/Dependencies.lua Normal file
View File

@@ -0,0 +1,64 @@
--
-- libraries
--
Class = require 'lib/class'
Event = require 'lib/knife.event'
push = require 'lib/push'
Timer = require 'lib/knife.timer'
require 'src/Animation'
require 'src/constants'
require 'src/StateMachine'
require 'src/Util'
require 'src/Level'
require 'src/Segment'
require 'src/Player'
require 'src/Ball'
require 'src/entity/entity_defs'
require 'src/entity/Entity'
require 'src/states/BaseState'
require 'src/states/StateStack'
require 'src/states/entity/EntityBaseState'
require 'src/states/entity/EntityIdleState'
require 'src/states/entity/EntityWalkState'
require 'src/states/entity/EntityWalkEdgeState'
require 'src/states/game/StartState'
require 'src/states/game/FadeInState'
require 'src/states/game/FadeOutState'
require 'src/states/game/PlayState'
require 'src/states/game/GameOverState'
gTextures = {
['entities'] = love.graphics.newImage('graphics/entities.png')
}
gFrames = {
['entities'] = GenerateQuads(gTextures['entities'], 16, 16)
}
gFonts = {
['small'] = love.graphics.newFont('fonts/font.ttf', 8),
['medium'] = love.graphics.newFont('fonts/font.ttf', 16),
['large'] = love.graphics.newFont('fonts/font.ttf', 32)
}
gSounds = {
['music'] = love.audio.newSource('sounds/music.wav', 'static'),
['field-music'] = love.audio.newSource('sounds/field_music.wav', 'static'),
['battle-music'] = love.audio.newSource('sounds/battle_music.mp3', 'static'),
['blip'] = love.audio.newSource('sounds/blip.wav', 'static'),
['powerup'] = love.audio.newSource('sounds/powerup.wav', 'static'),
['hit'] = love.audio.newSource('sounds/hit.wav', 'static'),
['run'] = love.audio.newSource('sounds/run.wav', 'static'),
['heal'] = love.audio.newSource('sounds/heal.wav', 'static'),
['exp'] = love.audio.newSource('sounds/exp.wav', 'static'),
['levelup'] = love.audio.newSource('sounds/levelup.wav', 'static'),
['victory-music'] = love.audio.newSource('sounds/victory.wav', 'static'),
['intro-music'] = love.audio.newSource('sounds/intro.mp3', 'static')
}

728
src/Level.lua Normal file
View File

@@ -0,0 +1,728 @@
-- require('mobdebug').start()
Level = Class{}
function Level:init()
self.segments = self:createLevel()
self.polygon = self:createPolygon()
self.player = {}
end
function Level:update(dt)
end
function Level:render()
self:renderOuterSegments()
end
function Level:renderOuterSegments()
for k,segment in pairs(self.segments) do
segment:render()
end
end
function Level:createLevel()
local level = {
[1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5,'down'),
[2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'left'),
[3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'up'),
[4] = Segment({LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5,'right')
}
-- local level = {
-- [1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5),
-- [2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5),
-- [3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5),
-- [4] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},.5),
-- [5] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},.5),
-- [6] = Segment({LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5)
-- }
return level
end
function Level:insideBounds2(x,y)
local shape = love.physics.newPolygonShape(self.polygonPoints)
return shape:testPoint(x,y)
end
function Level:insideBounds(x,y, segments)
if not segments then
segments = self.segments
end
local up,down,left,right = false
local upSeg, downSeg, leftSeg, rightSeg = nil
-- find closest segments
for i, segment in pairs(segments) do
-- check raycast on y axis
if segment.vertical then
if y <= math.max(segment.firstPointY, segment.secondPointY) and
y >= math.min(segment.firstPointY, segment.secondPointY) then
if x <= segment.firstPointX then
if not rightSeg then
rightSeg = segment
elseif math.abs(segment.firstPointX - x) < math.abs(rightSeg.firstPointX - x) then
rightSeg = segment
end
else
if not leftSeg then
leftSeg = segment
elseif math.abs(segment.firstPointX - x) < math.abs(leftSeg.firstPointX - x) then
leftSeg = segment
end
end
end
end
if segment.horizontal then
if x <= math.max(segment.firstPointX, segment.secondPointX) and
x >= math.min(segment.firstPointX, segment.secondPointX) then
if y <= segment.firstPointY then
if not downSeg then
downSeg = segment
elseif math.abs(segment.firstPointY - y) < math.abs(downSeg.firstPointY - y) then
downSeg = segment
end
else
if not upSeg then
upSeg = segment
elseif math.abs(segment.firstPointY - y) < math.abs(upSeg.firstPointY - y) then
upSeg = segment
end
end
end
end
end
if not rightSeg or not leftSeg or not upSeg or not downSeg then
return false
end
if rightSeg.face ~= 'left' or leftSeg.face ~= 'right' or upSeg.face ~= 'down' or downSeg.face ~= 'up' then
return false
end
return true
end
function Level:insideBounds3(x,y)
x = math.floor(x)
y = math.floor(y)
local oddNodes = false
local j = #self.polygon
local polygon = self.polygon
for i = 1, #polygon do
if (polygon[i].y < y and polygon[j].y >= y or polygon[j].y < y and polygon[i].y >= y) then
if (polygon[i].x + ( y - polygon[i].y ) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < x) then
oddNodes = not oddNodes
end
end
j = i
end
return oddNodes
end
function Level:createPolygon()
local polygon = {}
local polygonPoints = {}
local j = 1
for i,segment in ipairs(self.segments) do
polygon[i] = {}
polygon[i+1] = {}
polygon[i].x, polygon[i].y, polygon[i+1].x, polygon[i+1].y = segment:segmentToPoints()
polygonPoints[j] = polygon[i].x
polygonPoints[j+1] = polygon[i].y
polygonPoints[j+2] = polygon[i+1].x
polygonPoints[j+3] = polygon[i+1].y
j = j + 4
i = i + 1
end
self.polygonPoints = polygonPoints
return polygon
end
function Level:pointOnEdge(x,y,segments)
if not segments then
segments = self.segments
end
local margin = 2
for i,segment in pairs(segments) do
if segment.vertical then --vertical line
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
y <= math.max(segment.firstPointY, segment.secondPointY) and
y >= math.min(segment.firstPointY, segment.secondPointY)
then
return true
end
elseif segment.horizontal then --horizontal line
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
x <= math.max(segment.firstPointX, segment.secondPointX) and
x >= math.min(segment.firstPointX, segment.secondPointX)
then
return true
end
end
end
return false
end
function Level:getTouchingSegment(x,y)
local margin = 2
for i,segment in pairs(self.segments) do
if segment.vertical then --vertical line
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
y <= math.max(segment.firstPointY, segment.secondPointY) and
y >= math.min(segment.firstPointY, segment.secondPointY)
then
return i, segment
end
elseif segment.horizontal then --horizontal line
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
x <= math.max(segment.firstPointX, segment.secondPointX) and
x >= math.min(segment.firstPointX, segment.secondPointX)
then
return i, segment
end
end
end
return nil
end
function Level:cutLevel()
local newSegs = self.player.trailSegments
local startSegi, startSeg = self.player.trailStartSegment[1],self.player.trailStartSegment[2]
local endSegi, endSeg = self.player.trailFinishSegment[1],self.player.trailFinishSegment[2]
local first = 0
local last = 0
local firstFace = ''
--check if it is same start and finish segment
if startSegi == endSegi then
-- print("cutlevel same start and end segment: "..tostring(startSegi)..' - '..tostring(endSegi))
local s = {}
-- split the start/end segment
local new = {}
local split = self.segments[startSegi]:copy()
newSegs[1]:joinPerpendicular(split)
newSegs[#newSegs]:joinPerpendicular(split)
-- print("-- joined perpendicular start and end segment from new segments")
newSegs[1]:debug()
newSegs[#newSegs]:debug()
local previous = #self.segments
if startSegi > 1 and startSegi < #self.segments then
previous = startSegi - 1
end
local part1, part2, temp = {}
local inOrder = false
local j,k = 1,1
local insertedNewSegs = false
-- print("-- Create new segments table")
-- create the new set of segments
while k <= #self.segments do
-- print(" segment to be inserted "..tostring(k))
if k == startSegi and not insertedNewSegs then
-- print("Reached the segment being cut")
-- print_r(self.segments[k])
-- print("split the segment")
part1, temp, part2 = self.segments[k]:splitInThreeWithSegments(newSegs[1],newSegs[#newSegs])
-- print("part1")
part1:debug()
-- print("part2")
part2:debug()
-- print("insert first part")
new[j] = part1
-- print_r(new)
j = j + 1
-- insert new segs
-- print("-- insert new segments into new set")
for i, segment in pairs(newSegs) do
if i + 1 < #newSegs then
newSegs[i+1]:joinPerpendicular(segment)
end
new[j] = segment:copy()
j = j + 1
end
insertedNewSegments = true
new[j] = part2
j = j + 1
-- print_r(new)
-- print("-- finished inserting new segments and parts")
else
new[j] = self.segments[k]:copy()
j = j + 1
end
k = k + 1
end
-- print("order segments in cutlevel same start and end segment")
new = self:orderSegments(new)
-- print_r(new)
-- print("calculate faces")
self:calculateSegmentFaces(new,new[1].face)
-- print_r(new)
self.segments = new
else
-- create two set of segments, one to one side of the cut, one to the other
local board1 = {}
local board2 = {}
-- create first board
local k = 1
local j = 1
local last = #self.segments
local insertedNewSegments = false
local savedStart,savedEnd = {}
local board2StartSegi,board2EndSegi = 0
-- create first board
while k <= #self.segments do
-- print("-----start Loop for "..tostring(k).."-----")
if k == startSegi and not insertedNewSegments then
board2StartSegi = k
-- print("-- this segment is the start segment and not inserted new segments yet")
-- print("newsegs[1] is joined perpendicular to k segment:")
newSegs[1]:debug()
newSegs[1]:joinPerpendicular(self.segments[k])
newSegs[1]:debug()
-- print("newSegs[1] is joined -- finished")
local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1])
-- print("-- Segment k split into 2 segments by newsegs[1]: ")
startPart1:debug()
startPart2:debug()
if self.segments[last]:endEqualsStartOf(self.segments[k]) or
self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one
--keep first part of segment
-- print("-- keep first part of the k segment")
self.segments[k] = startPart1
savedStart = startPart2
else -- keep second part of the segment (which is the first one touching the last segment)
-- print("-- keep second part of the k segment")
self.segments[k] = startPart2
savedStart = startPart1
end
board1[j] = self.segments[k]
j = j + 1
-- print("-- before checking for inserted new segments - Board1")
-- print_r(board1)
if not insertedNewSegments then
k = endSegi -- skip to last segment to cut and insert it as well
last = k - 1
board2EndSegi = k
newSegs[#newSegs]:joinPerpendicular(self.segments[k])
-- print("-- not inserted items, proceed to insert in order new segments")
for i, newseg in ipairs(newSegs) do
board1[j] = newseg:copy()
j = j + 1
end
-- print_r(board1)
local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs])
-- print("-- proceed to insert the second split segment")
if self.segments[last]:endEqualsEndOf(self.segments[k]) or
self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one
self.segments[k] = endPart1
savedEnd = endPart2
-- print("-- keep first part of the k segment")
else
self.segments[k] = endPart2
savedEnd = endPart1
-- print("-- keep second part of the k segment")
end
board1[j] = self.segments[k]:copy()
j = j + 1
-- print_r(board1)
insertedNewSegments = true
end
elseif k == endSegi and not insertedNewSegments then
board2StartSegi = k
-- print("-- this segment is the end segment and not inserted new segments yet")
-- print("newsegs[#newSegs] is joined perpendicular to k segment:")
newSegs[#newSegs]:debug()
newSegs[#newSegs]:joinPerpendicular(self.segments[k])
newSegs[#newSegs]:debug()
-- print("newSegs[#newSegs] is joined -- finished")
local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs])
-- print("-- Segment k split into 2 segments by newsegs[#newSegs]: ")
endPart1:debug()
endPart2:debug()
if self.segments[last]:endEqualsStartOf(self.segments[k]) or
self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one
-- keep first part of segment
self.segments[k] = endPart1
savedEnd = endPart2
else -- keep second part of the segment (which is the first one touching the last segment)
self.segments[k] = endPart2
savedEnd = endPart1
end
board1[j] = self.segments[k]
j = j + 1
if not insertedNewSegments then
k = startSegi -- skip to last segment to cut and insert it as well
last = k - 1
board2EndSegi = k
newSegs[1]:joinPerpendicular(self.segments[k])
-- print("-- not inserted items, proceed to insert in reverse order new segments")
local i = #newSegs
while i >= 1 do
board1[j] = newSegs[i]:copy()
board1[j]:switchDirection()
j = j + 1
i = i - 1
end
-- print_r(board1)
local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1])
-- print("-- Segment k split into 2 segments by newsegs[1]: ")
startPart1:debug()
startPart2:debug()
if self.segments[last]:endEqualsEndOf(self.segments[k]) or
self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one
self.segments[k] = startPart1
savedstart = startPart2
else
self.segments[k] = startPart2
savedStart= startPart1
end
board1[j] = self.segments[k]:copy()
j = j + 1
-- print_r(board1)
insertedNewSegments = true
end
elseif k ~= startSegi and k ~= endSegi then
board1[j] = self.segments[k]:copy()
j = j + 1
end
last = k
k = k + 1
end
-- print("order segments in cutlevel board1")
board1 = self:orderSegments(board1)
self:calculateSegmentFaces(board1,board1[1].face)
-- print("PREPARE BOARD 1 -- FINAL")
-- print_r(board1)
-- create second board
-- print('---- Create Second board from '..tostring(board2StartSegi)..' to '..tostring(board2EndSegi))
j = 1
if board2StartSegi < board2EndSegi then
board2[j] = savedStart
j = j + 1
-- print_r(board2)
-- print('-- inserted first segment, proceed with skipped segments in first board, start < end')
k = board2StartSegi + 1
while k < board2EndSegi do
board2[j] = self.segments[k]:copy()
k = k + 1
j = j + 1
end
-- print_r(board2)
-- print('-- inserted original skipped segments')
board2[j] = savedEnd
-- print('-- inserted saved end segment')
-- print_r(board2)
j = j + 1
-- insert new segments in order
-- print("-- proceed to insert in order new segments")
for i, newseg in ipairs(newSegs) do
board2[j] = newseg:copy()
j = j + 1
end
-- print_r(board2)
else
-- print('-- inserted first segment, proceed with skipped segments in first board, end < start')
board2[j] = savedEnd
j = j + 1
-- print('inserted saved end')
-- print_r(board2)
k = board2EndSegi + 1
-- print('-- insert the skipped segments')
while k < board2StartSegi do
board2[j] = self.segments[k]:copy()
k = k + 1
j = j + 1
end
-- print_r(board2)
-- print('insert the saved start segment')
-- insert new segments reverse order
board2[j] = savedStart
j = j + 1
-- print_r(board2)
-- print('-- insert the new segments in reverse order')
local i = #newSegs
while i >= 1 do
board2[j] = newSegs[i]:copy()
board2[j]:switchDirection()
j = j + 1
i = i - 1
end
-- print('-- inserted new segments, final board2 before ordering')
-- print_r(board2)
end
-- print("order segments in cutlevel board2")
board2 = self:orderSegments(board2)
self:calculateSegmentFaces(board2,board2[1].face)
-- print("PREPARE BOARD 2 -- FINAL")
-- print_r(board2)
if self:containsBalls(board1) == 0 then
self.segments = board2
elseif self:containsBalls(board2) == 0 then
self.segments = board1
else
self.segments = self:getPerimeter(board1) > self:getPerimeter(board2) and board1 or board2
end
end
end
function Level:orderSegments(segs)
--returns the list of segments in the level linked to each other in order and perpendicular to each other
local new = {}
-- -- print('----- order Segments : ')
-- print_s(segs)
new[1] = segs[1]
table.remove(segs,1)
local found = false
local k = #segs
local i = 1
while i <= k do
-- -- print('----- segment i = '..tostring(i) .. '#segs '..tostring(#segs))
-- print_s(new)
-- -- print(' --new search')
-- print_s(segs)
for j, s in ipairs(segs) do
-- -- print("test if j is next segment to i, j = "..tostring(j))
if new[i]:endEqualsStartOf(s) then
found = true
-- -- print("new[i] end equals start of ")
-- s:debug()
if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(j))
new[i]:joinContiguous(s)
table.remove(segs,j)
i = i - 1
k = k - 1
break
else
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j))
s:joinPerpendicular(new[i])
new[i+1] = s
table.remove(segs,j)
break
end
elseif new[i]:endEqualsEndOf(s) then
found = true
-- -- print("new[i] end equals end of ")
-- s:debug()
s:switchDirection()
if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(j))
new[i]:joinContiguous(s)
table.remove(segs,j)
i = i - 1
k = k - 1
break
else
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j))
s:joinPerpendicular(new[i])
new[i+1] = s
table.remove(segs,j)
break
end
end
end
local fi, fs = 0,{}
if not found then
-- -- print("search didnt yield any segment")
local margin = 2
while not found and margin < 5 do
fi, fs = self:findNearestSegment(new[i].secondPointX,new[i].secondPointY,segs,margin)
if not fs then
fi, fs = self:findNearestSegment(new[i].firstPointX,new[i].firstPointY,segs,margin)
if not fs then
margin = margin + 1
else
found = true
end
else
found = true
end
end
if found then
if new[i].vertical == fs.vertical then
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(fi))
new[i]:joinContiguous(fs)
table.remove(segs,fi)
i = i - 1
k = k - 1
else
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(fi))
fs:joinPerpendicular(new[i])
new[i+1] = fs
table.remove(segs,fi)
end
end
end
if not found then
-- print("ERROR -- order segments does not find next segment to ")
new[i]:debug("number: " ..tostring(i))
break
else
found = false
end
i = i + 1
end
-- -- print("finished ordering: #new " ..tostring(#new))
return new
end
function Level:calculateSegmentFaces(segments, firstFace)
-- start segment, end segment, list of segments to calculate faces for (when cutting the map)
local face = firstFace
for k, s in ipairs(segments) do
s.face = face
if k + 1 <= #segments then
if face == 'up' then
if s.direction == 'right' then
if segments[k+1].direction == 'up' then
face = 'left'
else
face = 'right'
end
else
if segments[k+1].direction == 'up' then
face = 'right'
else
face = 'left'
end
end
elseif face == 'down' then
if s.direction == 'right' then
if segments[k+1].direction == 'up' then
face = 'right'
else
face = 'left'
end
else
if segments[k+1].direction == 'up' then
face = 'left'
else
face = 'right'
end
end
elseif face == 'right' then
if s.direction == 'up' then
if segments[k+1].direction == 'right' then
face = 'down'
else
face = 'up'
end
else
if segments[k+1].direction == 'right' then
face = 'up'
else
face = 'down'
end
end
elseif face == 'left' then
if s.direction == 'up' then
if segments[k+1].direction == 'right' then
face = 'up'
else
face = 'down'
end
else
if segments[k+1].direction == 'right' then
face = 'down'
else
face = 'up'
end
end
end
end
end
end
function Level:getPerimeter(segments)
local p = 0
if not segments then
segments = self.segments
end
for k, s in pairs(segments) do
p = p + s:length()
end
return p
end
function Level:findNearestSegment(x,y,segments,margin)
if not segments then
segments = self.segments
end
if not margin then
margin = 2
end
-- find a touching segment within margins
for i,segment in pairs(segments) do
if segment.vertical then --vertical line
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
y <= math.max(segment.firstPointY, segment.secondPointY) and
y >= math.min(segment.firstPointY, segment.secondPointY)
then
return i, segment
end
elseif segment.horizontal then --horizontal line
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
x <= math.max(segment.firstPointX, segment.secondPointX) and
x >= math.min(segment.firstPointX, segment.secondPointX)
then
return i, segment
end
end
end
return nil, nil
end
function Level:containsBalls(segments)
-- returns numbere of balls inside the bounds passed in or self.segments
if not segments then
segments = self.segments
end
local counter = 0
for k,b in pairs(self.balls) do
if self:insideBounds(b.x + 4, b.y + 4,segments) or self:pointOnEdge(b.x+4, b.y+4, seegments) then
counter = counter + 1
end
end
return counter
end

213
src/Player.lua Normal file
View File

@@ -0,0 +1,213 @@
-- require('mobdebug').start()
Player = Class{__includes = Entity}
function Player:init(def, level )
Entity.init(self,def)
self.x = 1
self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1
self.trail = {}
self.level = level
self.trailSegments = {}
self.trailStartSegment = {}
self.trailFinishSegment = {}
self.score = 0
self.multiplier = 1
Event.on('on-edge',function()
self:onEdge()
-- self:playerDebug("after on-edge event ")
end)
Event.on('walking',function()
self:onWalking()
-- self:playerDebug("after walking event ")
end)
Event.on('change-direction', function()
self:onChangeDirection()
-- self:playerDebug("after change-direction event ")
end)
Event.on('back-to-wall', function ()
self:onBackToWall()
-- self:playerDebug("after back-to-wall event ")
end)
end
function Player:changeState(state)
Entity.changeState(self,state)
end
function Player:update(dt)
Entity.update(self, dt)
end
function Player:createAnimations(animations)
return Entity.createAnimations(self,animations)
end
function Player:changeAnimation(name)
Entity.changeAnimation(self,name)
end
function Player:render()
Entity.render(self)
self:renderTrailSegments()
-- love.graphics.setColor(255, 0, 255, 255)
-- love.graphics.rectangle('line', self.x, self.y, self.width, self.height)
-- love.graphics.setColor(255, 255, 255, 255)
end
function Player:renderTrailSegments()
for i,segment in pairs (self.trailSegments) do
segment:render()
-- print_r(segment)
end
end
function Player:insideBounds()
return self.level:insideBounds(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
end
function Player:onEdge()
self.trail = {}
self.trailSegments = {}
self.trailFinishSegment = {}
self.trailStartSegment = {}
self.trailStartSegment[1], self.trailStartSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
table.insert(self.trail,
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
)
end
function Player:onWalking()
if #self.trail > 1 then
table.remove(self.trail)
table.remove(self.trailSegments)
end
table.insert(self.trail,
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
)
if #self.trail == 2 then
table.insert(self.trailSegments,
Segment(
{self.trail[1].x, self.trail[1].y},
{self.trail[2].x, self.trail[2].y},
.5,'',{r=250,g=150,b=150}
)
)
end
end
function Player:onChangeDirection()
if self:insideBounds() then
if #self.trail > 1 then
table.remove(self.trail)
table.remove(self.trailSegments)
end
local k = #self.trailSegments
local j = #self.trail + 1
table.insert(self.trail,
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
)
if #self.trail == 2 then
table.insert(self.trailSegments,
Segment(
{self.trail[1].x, self.trail[1].y},
{self.trail[2].x, self.trail[2].y},
.5,'',{r=250,g=150,b=150}
)
)
end
self.trail = {}
table.insert(self.trail,
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
)
end
end
function Player:onBackToWall()
if self:onTheEdge() and #self.trail > 0 then
self.trailFinishSegment[1], self.trailFinishSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
local k = #self.trailSegments
local j = #self.trail + 1
if #self.trail > 1 then
table.remove(self.trail)
end
table.insert(self.trail,
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
)
if #self.trail == 2 then
table.remove(self.trailSegments)
table.insert(self.trailSegments,
Segment(
{self.trail[1].x, self.trail[1].y},
{self.trail[2].x, self.trail[2].y},
.5,'',{r=250,g=150,b=150}
)
)
end
self:resetTrail()
end
end
function Player:reset()
self.x = 1
self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1
self.trail = {}
self.trailSegments = {}
self.trailStartSegment = {}
self.trailFinishSegment = {}
end
function Player:resetTrail()
self:playerDebug('Player:resetTrail')
if #self.trailStartSegment > 0 and #self.trailFinishSegment > 0 then
self.level:cutLevel()
end
self.trail = {}
self.trailStartSegment = {}
self.trailFinishSegment = {}
self.trailSegments = {}
end
function Player:onTheEdge()
return self.level:pointOnEdge(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
end
function Player:playerDebug(msg)
-- print('------------------------------------')
if msg then
-- print(msg)
end
-- print("Player Position x,y = " ..tostring(self.x)..','..tostring(self.y))
-- print("Player Center x,y = " ..tostring(self.x+8)..','..tostring(self.y+8))
-- print("Player direction : "..self.direction .. ' previous: '..self.previousDirection)
-- print('......................')
-- print("Player trail points: ")
-- print_r(self.trail)
-- print('......................')
-- print("Player segments: ")
-- print_s(self.trailSegments)
-- print('......................')
-- print("Trail Start")
if self.trailStartSegment[2] ~= nil then
self.trailStartSegment[2]:debug(tostring(self.trailStartSegment[1]))
end
-- print('......................')
-- print("Trail Finish")
if self.trailFinishSegment[2] ~= nil then
self.trailFinishSegment[2]:debug(tostring(self.trailFinishSegment[1]))
end
-- print('------------------------------------')
end

294
src/Segment.lua Normal file
View File

@@ -0,0 +1,294 @@
-- require('mobdebug').start()
Segment = Class{}
local symbols = {['up'] = 207, ['down'] = 209, ['right'] = 199, ['left'] = 182}
function Segment:init(firstPoint, secondPoint,lineWidth, face, color)
self.firstPointX = firstPoint[1]
self.firstPointY = firstPoint[2]
self.secondPointX = secondPoint[1]
self.secondPointY = secondPoint[2]
self.lineWidth = lineWidth or 3
self.vertical = self.firstPointX == self.secondPointX
self.horizontal = self.firstPointY == self.secondPointY
self.face = face or ''
self.direction = self:calculateDirection()
self.color = color or {r=255,g=255,b=255}
end
function Segment:render()
love.graphics.setColor(self.color.r,self.color.g, self.color.b,255)
love.graphics.setLineWidth(self.lineWidth)
love.graphics.line(self.firstPointX,self.firstPointY,self.secondPointX, self.secondPointY)
love.graphics.setColor(255,255, 255, 255)
-- debug
-- love.graphics.setFont(gFonts['small'])
-- love.graphics.printf(self.face,self.firstPointX + 2, self.firstPointY + 2 / 2,25,'left' )
end
function Segment:segmentToPoints()
return self.firstPointX, self.firstPointY, self.secondPointX, self.secondPointY
end
function Segment:length()
return math.sqrt( (self.secondPointX - self.firstPointX) * (self.secondPointX - self.firstPointX) + (self.secondPointY - self.firstPointY) * (self.secondPointY - self.firstPointY) )
end
function Segment:sideOfSegment(x,y)
if y < self.firstPointY and y < self.secondPointY then -- above
return 'up'
end
if y > self.firstPointY and y > self.secondPointY then -- below
return 'down'
end
if x < self.firstPointX and x < self.secondPointX then -- left
return 'left'
end
if x > self.firstPointX and x > self.secondPointX then -- right
return 'right'
end
end
function Segment:debug(msg)
if not msg then
msg = ''
end
-- print("Segment:debug ".. msg .." ...............")
-- print("FirstPoint: "..tostring(self.firstPointX)..','..tostring(self.firstPointY))
-- print("SecondPoint: "..tostring(self.secondPointX)..','..tostring(self.secondPointY))
-- print("face: "..self.face.. ' - Direction: '..self.direction)
end
function Segment:copy()
local copy = {}
copy.firstPointX = self.firstPointX
copy.firstPointY = self.firstPointY
copy.secondPointX = self.secondPointX
copy.secondPointY = self.secondPointY
copy.lineWidth = self.lineWidth
copy.face = self.face
return Segment({copy.firstPointX,copy.firstPointY},{copy.secondPointX,copy.secondPointY},copy.lineWidth, copy.face)
end
function Segment:endEqualsStartOf(s)
-- segments are linked at the end of this one
if self.secondPointX == s.firstPointX and
self.secondPointY == s.firstPointY then
return true
end
return false
end
function Segment:endEqualsEndOf(s)
-- segments are linked at the end of this one
if self.secondPointX == s.secondPointX and self.secondPointY == s.secondPointY then
return true
end
return false
end
function Segment:startEqualsEndOf(s)
-- segments are linked at the beginning of this one
if self.firstPointX == s.secondPointX and self.firstPointY == s.secondPointY then
return true
end
return false
end
function Segment:startEqualsStartOf(s)
-- segments are linked at the beginning of this one
if self.firstPointX == s.firstPointX and self.firstPointY == s.PointY then
return true
end
return false
end
function Segment:calculateDirection()
local dir = ''
if self.vertical then
if self.firstPointY < self.secondPointY then
dir = 'down'
else
dir = 'up'
end
else
if self.firstPointX < self.secondPointX then
dir = 'right'
else
dir = 'left'
end
end
return dir
end
function Segment:splitInTwoWithPoint(point) -- number
local s1, s2 = {}
if point > 0 then
if self.vertical then
s1 = Segment(
{self.firstPointX, self.firstPointY},
{self.secondPointX, point},
self.lineWidth, self.face
)
s2 = Segment(
{self.firstPointX, point},
{self.secondPointX,self.secondPointY},
self.lineWidth, self.face
)
else
s1 = Segment(
{self.firstPointX, self.firstPointY},
{point, self.secondPointY},
self.lineWidth, self.face
)
s2 = Segment(
{point, self.firstPointY},
{self.secondPointX,self.secondPointY},
self.lineWidth, self.face
)
end
else
return nil, nil
end
return s1, s2
end
function Segment:splitInTwoWithSegment(segment)
local point = 0
if self.vertical then
if self.firstPointY == segment.firstPointY then
point = segment.firstPointY
else
point = segment.secondPointY
end
elseif self.horizontal then
if self.firstPointX == segment.firstPointX then
point = segment.firstPointX
else
point = segment.secondPointX
end
end
return self:splitInTwoWithPoint(point)
end
function Segment:joinPerpendicular(s)
-- changes the self, not the parameter
if s.vertical then
local sx = s.firstPointX
if math.abs(self.firstPointX - sx) <
math.abs(self.secondPointX - sx) then
self.firstPointX = sx
else
self.secondPointX = sx
end
else
local sy = s.firstPointY
if math.abs(self.firstPointY - sy) <
math.abs(self.secondPointY - sy) then
self.firstPointY = sy
else
self.secondPointY = sy
end
end
end
function Segment:switchDirection()
local temp = {}
temp.firstPointX = self.firstPointX
temp.firstPointY = self.firstPointY
self.firstPointX = self.secondPointX
self.firstPointY = self.secondPointY
self.secondPointX = temp.firstPointX
self.secondPointY = temp.firstPointY
self:notDirection()
end
function Segment:notDirection()
if self.direction == 'down' then
self.direction = 'up'
elseif self.direction == 'up' then
self.direction = 'down'
elseif self.direction == 'right' then
self.direction = 'left'
elseif self.direction == 'left' then
self.direction = 'right'
end
end
function Segment:notFace()
if self.face == 'up' then
self.face = 'down'
elseif self.face == 'down' then
self.face = 'up'
elseif self.face == 'right' then
self.face = 'left'
elseif self.face == 'left' then
self.face = 'right'
end
end
function Segment:joinContiguous(s)
-- second segment becomes part of first one
if self.vertical then
self.secondPointY = s.secondPointY
else
self.secondPointX = s.secondPointX
end
end
function Segment:compare(s)
-- returns >0 if self is to the right or below
-- returns <0 if self is to the left or above
-- returns 0 if both are same point
if self.vertical and s.vertical then
if self.firstPointX == s.firstPointX then
return 0
else
return self.firstPointX - s.firstPointX
end
elseif self.horizontal and s.horizontal then
if self.firstPointY == s.firstPointY then
return 0
else
return self.firstPointY - s.firstPointY
end
else
return (self.firstPointX + self.firstPointY +
self.secondPointX + self.secondPointY) - (
s.firstPointX + s.firstPointY + s.secondPointX
+s.secondPointY)
end
end
function Segment:splitInThreeWithSegments(s1,s2)
local p1, p2, p3, t = {}
if self.direction == 'down' or self.direction == 'right' then
if s1:compare(s2) < 0 then
p1, t = self:splitInTwoWithSegment(s1)
p2, p3 = t:splitInTwoWithSegment(s2)
else
p1, t = self:splitInTwoWithSegment(s2)
p2, p3 = t:splitInTwoWithSegment(s1)
end
else
if s1:compare(s2) < 0 then
p1, t = self:splitInTwoWithSegment(s2)
p2, p3 = t:splitInTwoWithSegment(s1)
else
p1, t = self:splitInTwoWithSegment(s1)
p2, p3 = t:splitInTwoWithSegment(s2)
end
end
return p1,p2,p3
end
function Segment:insert(list)
end

37
src/StateMachine.lua Normal file
View File

@@ -0,0 +1,37 @@
-- require('mobdebug').start()
StateMachine = Class{}
function StateMachine:init(states)
self.empty = {
render = function() end,
update = function() end,
processAI = function() end,
enter = function() end,
exit = function() end
}
self.states = states or {} -- [name] -> [function that returns states]
self.current = self.empty
end
function StateMachine:change(stateName, enterParams)
assert(self.states[stateName]) -- state must exist!
self.current:exit()
self.current = self.states[stateName]()
self.current:enter(enterParams)
end
function StateMachine:update(dt)
self.current:update(dt)
end
function StateMachine:render()
self.current:render()
end
--[[
Used for states that can be controlled by the AI to influence update logic.
]]
function StateMachine:processAI(params, dt)
self.current:processAI(params, dt)
end

73
src/Util.lua Normal file
View File

@@ -0,0 +1,73 @@
-- require('mobdebug').start()
--[[
Given an "atlas" (a texture with multiple sprites), as well as a
width and a height for the tiles therein, split the texture into
all of the quads by simply dividing it evenly.
]]
function GenerateQuads(atlas, tilewidth, tileheight)
local sheetWidth = atlas:getWidth() / tilewidth
local sheetHeight = atlas:getHeight() / tileheight
local sheetCounter = 1
local spritesheet = {}
for y = 0, sheetHeight - 1 do
for x = 0, sheetWidth - 1 do
spritesheet[sheetCounter] =
love.graphics.newQuad(x * tilewidth, y * tileheight, tilewidth,
tileheight, atlas:getDimensions())
sheetCounter = sheetCounter + 1
end
end
return spritesheet
end
--[[
Recursive table printing function.
https://coronalabs.com/blog/2014/09/02/tutorial-printing-table-contents/
]]
function print_r ( t )
local print_r_cache={}
local function sub_print_r(t,indent)
if (print_r_cache[tostring(t)]) then
print(indent.."*"..tostring(t))
else
print_r_cache[tostring(t)]=true
if (type(t)=="table") then
for pos,val in pairs(t) do
if (type(val)=="table") then
print(indent.."["..pos.."] => "..tostring(t).." {")
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
print(indent..string.rep(" ",string.len(pos)+6).."}")
elseif (type(val)=="string") then
print(indent.."["..pos..'] => "'..val..'"')
else
print(indent.."["..pos.."] => "..tostring(val))
end
end
else
print(indent..tostring(t))
end
end
end
if (type(t)=="table") then
print(tostring(t).." {")
sub_print_r(t," ")
print("}")
else
sub_print_r(t," ")
end
print()
end
function math.round(x,precision)
return x + precision - (x + precision) % 1
end
function print_s(segments)
for k, s in ipairs(segments) do
s:debug(tostring(k))
end
end

12
src/constants.lua Normal file
View File

@@ -0,0 +1,12 @@
-- require('mobdebug').start()
VIRTUAL_WIDTH = 384
VIRTUAL_HEIGHT = 216
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
LEVEL_RENDER_OFFSET_TOP = 30
LEVEL_RENDER_OFFSET = 10
TILE_SIZE = 16

57
src/entity/Entity.lua Normal file
View File

@@ -0,0 +1,57 @@
Entity = Class{}
function Entity:init(def)
self.direction = 'down'
self.previousDirection = ''
self.walkingSpeed = def.walkingSpeed
self.animations = self:createAnimations(def.animations)
self.currentAnimation = self.animations['idle-down']
self.stateMachine = StateMachine {
['walk'] = function() return EntityWalkState(self) end,
['edge'] = function() return EntityWalkEdgeState(self) end,
['idle'] = function() return EntityIdleState(self) end
}
self.stateMachine:change('idle')
self.width = def.width
self.height = def.height
end
function Entity:changeState(name)
self.stateMachine:change(name)
-- printChangeState to "..name)
end
function Entity:changeAnimation(name)
self.currentAnimation = self.animations[name]
end
function Entity:createAnimations(animations)
local animationsReturned = {}
for k, animationDef in pairs(animations) do
animationsReturned[k] = Animation {
texture = animationDef.texture or 'entities',
frames = animationDef.frames,
interval = animationDef.interval
}
end
return animationsReturned
end
function Entity:processAI(params, dt)
self.stateMachine:processAI(params, dt)
end
function Entity:update(dt)
self.currentAnimation:update(dt)
self.stateMachine:update(dt)
end
function Entity:render()
self.stateMachine:render()
end

View File

@@ -0,0 +1,47 @@
ENTITY_DEFS = {
['player'] = {
width = 16,
height = 16,
walkingSpeed = 100,
animations = {
['walk-left'] = {
frames = {67, 68, 69},
interval = 0.15,
texture = 'entities'
},
['walk-right'] = {
frames = {79, 80, 81},
interval = 0.15,
texture = 'entities'
},
['walk-down'] = {
frames = {55, 56, 57},
interval = 0.15,
texture = 'entities'
},
['walk-up'] = {
frames = {91, 92, 93},
interval = 0.15,
texture = 'entities'
},
['idle-left'] = {
frames = {67},
texture = 'entities'
},
['idle-right'] = {
frames = {79},
texture = 'entities'
},
['idle-down'] = {
frames = {55},
texture = 'entities'
},
['idle-up'] = {
frames = {91},
texture = 'entities'
}
}
}
}

9
src/states/BaseState.lua Normal file
View File

@@ -0,0 +1,9 @@
-- require('mobdebug').start()
BaseState = Class{}
function BaseState:init() end
function BaseState:enter() end
function BaseState:exit() end
function BaseState:update(dt) end
function BaseState:render() end

36
src/states/StateStack.lua Normal file
View File

@@ -0,0 +1,36 @@
-- require('mobdebug').start()
StateStack = Class{}
function StateStack:init()
self.states = {}
end
function StateStack:update(dt)
self.states[#self.states]:update(dt)
end
function StateStack:processAI(params, dt)
self.states[#self.states]:processAI(params, dt)
end
function StateStack:render()
for i, state in ipairs(self.states) do
state:render()
end
end
function StateStack:clear()
self.states = {}
end
function StateStack:push(state)
table.insert(self.states, state)
state:enter()
end
function StateStack:pop()
-- print("pop state: " .. self.states[#self.states].name)
self.states[#self.states]:exit()
table.remove(self.states)
end

View File

@@ -0,0 +1,18 @@
-- require('mobdebug').start()
EntityBaseState = Class{}
function EntityBaseState:init(entity)
self.entity = entity
end
function EntityBaseState:update(dt) end
function EntityBaseState:enter() end
function EntityBaseState:exit() end
function EntityBaseState:processAI(params, dt) end
function EntityBaseState:render()
local anim = self.entity.currentAnimation
love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],
math.floor(self.entity.x), math.floor(self.entity.y))
end

View File

@@ -0,0 +1,32 @@
-- require('mobdebug').start()
EntityIdleState = Class{__includes = BaseState}
function EntityIdleState:init(entity)
self.entity = entity
self.entity:changeAnimation('idle-' .. self.entity.direction)
end
function EntityIdleState:update(dt)
if love.keyboard.isDown('left') or love.keyboard.isDown('right') or
love.keyboard.isDown('up') or love.keyboard.isDown('down') then
if not self.entity:onTheEdge() then
Event.dispatch('walking')
self.entity:changeState('walk')
else
Event.dispatch('on-edge')
self.entity:changeState('edge')
end
end
end
function EntityIdleState:render()
local anim = self.entity.currentAnimation
love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y)
-- love.graphics.setColor(255, 0, 255, 255)
-- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height)
-- love.graphics.setColor(255, 255, 255, 255)
end

View File

@@ -0,0 +1,100 @@
-- require('mobdebug').start()
EntityWalkEdgeState = Class{__includes = BaseState}
function EntityWalkEdgeState:init(entity)
self.entity = entity
self.entity:changeAnimation('walk-'..self.entity.direction)
end
function EntityWalkEdgeState:update(dt)
local dy = self.entity.walkingSpeed * dt
local dx = self.entity.walkingSpeed * dt
if self.entity.direction == 'left' then
if self.entity.level:pointOnEdge(self.entity.x - dx + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2) then
self.entity.x = self.entity.x - dx
Event.dispatch('on-edge')
else
if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2 - dx, self.entity.y + TILE_SIZE / 2) then
self.entity:changeState('walk')
Event.dispatch('walking')
self.entity.x = self.entity.x - dx
end
end
elseif self.entity.direction == 'right' then
if self.entity.level:pointOnEdge(self.entity.x + dx + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2) then
self.entity.x = self.entity.x + dx
Event.dispatch('on-edge')
else
if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2 + dx, self.entity.y + TILE_SIZE / 2) then
self.entity:changeState('walk')
Event.dispatch('walking')
self.entity.x = self.entity.x + dx
end
end
elseif self.entity.direction == 'up' then
if self.entity.level:pointOnEdge(self.entity.x + TILE_SIZE / 2, self.entity.y - dy + TILE_SIZE / 2) then
self.entity.y = self.entity.y - dy
Event.dispatch('on-edge')
else
if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2 - dy) then
self.entity:changeState('walk')
Event.dispatch('walking')
self.entity.y = self.entity.y - dy
end
end
elseif self.entity.direction == 'down' then
if self.entity.level:pointOnEdge(self.entity.x + TILE_SIZE / 2, self.entity.y + dy + TILE_SIZE / 2) then
self.entity.y = self.entity.y + dy
Event.dispatch('on-edge')
else
if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2 + dy) then
self.entity:changeState('walk')
Event.dispatch('walking')
self.entity.y = self.entity.y + dy
end
end
end
if love.keyboard.isDown('left') then
if self.entity.direction ~= 'left' then
self.entity.previousDirection = self.entity.direction
end
self.entity.direction = 'left'
self.entity:changeAnimation('walk-left')
elseif love.keyboard.isDown('right') then
if self.entity.direction ~= 'right' then
self.entity.previousDirection = self.entity.direction
end
self.entity.direction = 'right'
self.entity:changeAnimation('walk-right')
elseif love.keyboard.isDown('up') then
if self.entity.direction ~= 'up' then
self.entity.previousDirection = self.entity.direction
end
self.entity.direction = 'up'
self.entity:changeAnimation('walk-up')
elseif love.keyboard.isDown('down') then
if self.entity.direction ~= 'down' then
self.entity.previousDirection = self.entity.direction
end
self.entity.direction = 'down'
self.entity:changeAnimation('walk-down')
else
self.entity:changeState('idle')
end
end
function EntityWalkEdgeState:render()
local anim = self.entity.currentAnimation
love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y)
-- love.graphics.setColor(255, 0, 255, 255)
-- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height)
-- love.graphics.setColor(255, 255, 255, 255)
end

View File

@@ -0,0 +1,93 @@
-- require('mobdebug').start()
EntityWalkState = Class{__includes = BaseState}
function EntityWalkState:init(entity)
self.entity = entity
self.entity:changeAnimation('walk-'..self.entity.direction)
end
function EntityWalkState:update(dt)
if self.entity.direction == 'up' then
self.entity.y = self.entity.y - self.entity.walkingSpeed * dt
if self.entity.y + TILE_SIZE / 2 < LEVEL_RENDER_OFFSET_TOP - 2 then
self.entity.y = self.entity.y + self.entity.walkingSpeed * dt
Event.dispatch('back-to-wall')
self.entity:changeState('edge')
end
elseif self.entity.direction == 'down' then
self.entity.y = self.entity.y + self.entity.walkingSpeed * dt
if self.entity.y + TILE_SIZE / 2 > VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET + 2 then
self.entity.y = self.entity.y - self.entity.walkingSpeed * dt
Event.dispatch('back-to-wall')
self.entity:changeState('edge')
end
elseif self.entity.direction == 'right' then
self.entity.x = self.entity.x + self.entity.walkingSpeed * dt
if self.entity.x + TILE_SIZE / 2 > VIRTUAL_WIDTH - TILE_SIZE / 2 + 2 or self.entity.previousdirection == 'left' then
self.entity.x = self.entity.x - self.entity.walkingSpeed * dt
Event.dispatch('back-to-wall')
self.entity:changeState('edge')
end
elseif self.entity.direction == 'left' then
self.entity.x = self.entity.x - self.entity.walkingSpeed * dt
if self.entity.x < 0 or self.entity.previousdirection == 'right' then
self.entity.x = self.entity.x + self.entity.walkingSpeed * dt
Event.dispatch('back-to-wall')
self.entity:changeState('edge')
end
end
if love.keyboard.isDown('left') then
if self.entity.direction ~= 'left' then
self.entity.previousDirection = self.entity.direction
Event.dispatch('change-direction')
else
Event.dispatch('walking')
end
self.entity.direction = 'left'
self.entity:changeAnimation('walk-left')
elseif love.keyboard.isDown('right') then
if self.entity.direction ~= 'right' then
self.entity.previousDirection = self.entity.direction
Event.dispatch('change-direction')
else
Event.dispatch('walking')
end
self.entity.direction = 'right'
self.entity:changeAnimation('walk-right')
elseif love.keyboard.isDown('up') then
if self.entity.direction ~= 'up' then
self.entity.previousDirection = self.entity.direction
Event.dispatch('change-direction')
else
Event.dispatch('walking')
end
self.entity.direction = 'up'
self.entity:changeAnimation('walk-up')
elseif love.keyboard.isDown('down') then
if self.entity.direction ~= 'down' then
self.entity.previousDirection = self.entity.direction
Event.dispatch('change-direction')
else
Event.dispatch('walking')
end
self.entity.direction = 'down'
self.entity:changeAnimation('walk-down')
else
self.entity:changeState('idle')
end
if not self.entity:insideBounds() then
Event.dispatch('back-to-wall')
self.entity:changeState('edge')
end
end
function EntityWalkState:render()
local anim = self.entity.currentAnimation
love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y)
-- love.graphics.setColor(255, 0, 255, 255)
-- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height)
-- love.graphics.setColor(255, 255, 255, 255)
end

View File

@@ -0,0 +1,33 @@
--[[
GD50
Pokemon
Author: Colton Ogden
cogden@cs50.harvard.edu
]]
FadeInState = Class{__includes = BaseState}
function FadeInState:init(color, time, onFadeComplete)
self.name = 'FadeInState'
self.r = color.r
self.g = color.g
self.b = color.b
self.opacity = 0
self.time = time
Timer.tween(self.time, {
[self] = {opacity = 255}
})
:finish(function()
gStateStack:pop()
onFadeComplete()
end)
end
function FadeInState:render()
love.graphics.setColor(self.r, self.g, self.b, self.opacity)
love.graphics.rectangle('fill', 0, 0, VIRTUAL_WIDTH, VIRTUAL_HEIGHT)
love.graphics.setColor(255, 255, 255, 255)
end

View File

@@ -0,0 +1,37 @@
--[[
GD50
Pokemon
Author: Colton Ogden
cogden@cs50.harvard.edu
]]
FadeOutState = Class{__includes = BaseState}
function FadeOutState:init(color, time, onFadeComplete)
self.name = 'FadeOutState'
self.opacity = 255
self.r = color.r
self.g = color.g
self.b = color.b
self.time = time
Timer.tween(self.time, {
[self] = {opacity = 0}
})
:finish(function()
gStateStack:pop()
onFadeComplete()
end)
end
function FadeOutState:update(dt)
end
function FadeOutState:render()
love.graphics.setColor(self.r, self.g, self.b, self.opacity)
love.graphics.rectangle('fill', 0, 0, VIRTUAL_WIDTH, VIRTUAL_HEIGHT)
love.graphics.setColor(255, 255, 255, 255)
end

View File

@@ -0,0 +1,44 @@
-- require('mobdebug').start()
GameOverState = Class{__includes = BaseState}
function GameOverState:init(score)
self.name = 'GameOverState'
gSounds['intro-music']:play()
self.score = score or 0
end
function GameOverState:update(dt)
if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then
gStateStack:push(FadeInState({
r = 0, g = 0, b = 0
}, 1,
function()
gSounds['intro-music']:stop()
gStateStack:push(StartState(),function()end, true)
gStateStack:push(FadeOutState({
r = 0, g = 0, b = 0
}, 1,
function() end))
end))
end
end
function GameOverState:render()
love.graphics.clear(0, 0, 0, 255)
love.graphics.setColor(56, 56, 56, 255)
love.graphics.setFont(gFonts['large'])
love.graphics.printf('GAME OVER', 0, VIRTUAL_HEIGHT / 2 - 72, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(gFonts['medium'])
love.graphics.printf('Score: '..tostring(self.score), 0, VIRTUAL_HEIGHT / 2, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Try keeping away from the balls but do not lose them all!!!', 0, VIRTUAL_HEIGHT / 2 + 48, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Press Enter', 0, VIRTUAL_HEIGHT / 2 + 88, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(gFonts['small'])
love.graphics.setColor(255, 255, 255, 255)
-- love.graphics.draw(gTextures[self.sprite], self.spriteX, self.spriteY)
end

View File

@@ -0,0 +1,115 @@
-- require('mobdebug').start()
PlayState = Class{__includes = BaseState}
function PlayState:init()
self.name = 'PlayState'
self.stage = 1
self.level = Level()
self.player = Player ( ENTITY_DEFS['player'] , self.level)
self.level.player = self.player
self.balls = {}
self:createBalls()
self.level.balls = self.balls
gSounds['music']:setLooping(true)
gSounds['music']:play()
end
function PlayState:enter()
end
function PlayState:update(dt)
-- self.level:update(dt)
self.player:update(dt)
self:updateBalls(dt, self.level)
end
function PlayState:render()
love.graphics.clear(0, 0, 0, 255)
love.graphics.setColor(40, 45, 52,255)
love.graphics.setFont(gFonts['small'])
love.graphics.printf('Score '..tostring(self.player.score),
0, 5, VIRTUAL_WIDTH, 'left')
love.graphics.setFont(gFonts['medium'])
love.graphics.printf('Stage '..tostring(self.stage),
0, 5, VIRTUAL_WIDTH, 'center')
self.level:render()
self.player:render()
self:renderBalls(dt)
end
function PlayState:createBalls()
self.balls = {}
for i = 1, self.stage do
table.insert(self.balls,Ball(
math.random(LEVEL_RENDER_OFFSET, VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET), --X
math.random(LEVEL_RENDER_OFFSET_TOP, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET), --Y
math.random(20,90),math.random(20,90) -- speed
))
end
end
function PlayState:updateBalls(dt, level)
for k,ball in pairs(self.balls) do
ball:update(dt,level)
end
-- remove balls outside bounds
for k,ball in pairs(self.balls) do
if ball.remove then
table.remove(self.balls,k)
self.player.score = self.player.score - 1
elseif ball.hitPlayer then
-- check if any ball hit the player trail segments
self:gameOver()
end
end
if #self.balls <= 0 then
-- game over - reset level
self:gameOver()
end
-- check perimeter of level, if low enough, create new level move on to the next stage
if level:getPerimeter() < 500 then
self:nextStage()
end
end
function PlayState:renderBalls(dt)
for k,ball in pairs(self.balls) do
ball:render()
end
end
function PlayState:gameOver()
gStateStack:pop()
gSounds['music']:stop()
gStateStack:push(GameOverState(self.player.score),function() end)
end
function PlayState:nextStage()
gStateStack:push(FadeOutState({
r = 255, g = 255, b = 255}, 1,function()
self.stage = self.stage + 1
self.level = Level()
self.player:reset()
self.player.score = self.player.score + self.player.multiplier * #self.balls
self.player.multiplier = self.player.multiplier + #self.balls
self.balls = {}
self:createBalls()
self.level.player = self.player
self.player.level = self.level
self.level.balls = self.balls
gSounds['music']:stop()
gSounds['heal']:play()
gStateStack:push(FadeOutState({r = 255, g = 255, b = 255}, 1,function()
gSounds['music']:play()
end))
end))
end

View File

@@ -0,0 +1,40 @@
-- require('mobdebug').start()
StartState = Class{__includes = BaseState}
function StartState:init()
self.name = 'StartState'
gSounds['intro-music']:play()
end
function StartState:update(dt)
if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then
gStateStack:push(FadeInState({
r = 255, g = 255, b = 255
}, 1,
function()
gSounds['intro-music']:stop()
gStateStack:push(PlayState(),function()end, true)
-- gStateStack:push(FadeOutState({
-- r = 255, g = 255, b = 255
-- }, 1,
-- function() end))
end))
end
end
function StartState:render()
-- print("StartSate : setting colors")
love.graphics.clear(0, 0, 0, 255)
love.graphics.setColor(56, 56, 56, 255)
love.graphics.setFont(gFonts['large'])
love.graphics.printf('Spider Cut!', 0, VIRTUAL_HEIGHT / 2 - 72, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(gFonts['medium'])
love.graphics.printf('Press Enter', 0, VIRTUAL_HEIGHT / 2 + 68, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(gFonts['small'])
-- print("StartState : end")
-- love.graphics.setColor(255, 255, 255, 255)
-- love.graphics.draw(gTextures[self.sprite], self.spriteX, self.spriteY)
end