├── gfx ├── hero.png ├── gooball.png ├── sprites.png ├── arrow_up.png ├── statusbox.png ├── arrow_up_dbg.png ├── empty_heart.png ├── focus_empty.png ├── focus_full.png ├── full_heart.png ├── sprites_bak.png ├── splash │ └── frank_face.png ├── color_tileset_16x16.png └── color_tileset_64x64.png ├── audio └── fx │ ├── heal.ogg │ ├── grunt1.ogg │ ├── grunt2.ogg │ ├── grunt3.ogg │ ├── herodie.ogg │ ├── sword.ogg │ ├── gooballdie.ogg │ ├── heroinjure.ogg │ └── gooballinjure.ogg ├── fonts └── manaspace.regular.ttf ├── monsters.lua ├── conf.lua ├── cursor.lua ├── renderer.lua ├── word_queue.lua ├── credits.md ├── inheritance.lua ├── mode_switcher.lua ├── test ├── lovetest.lua ├── test_collider.lua └── lunatest.lua ├── splash_renderer.lua ├── collider.lua ├── status.lua ├── dungeon_master.lua ├── main.lua ├── character.lua ├── keyboard_handler.lua ├── tiledmap.lua ├── maps └── basic.tmx ├── ProFi.lua └── dictionaries └── basic_english.txt /gfx/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/hero.png -------------------------------------------------------------------------------- /gfx/gooball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/gooball.png -------------------------------------------------------------------------------- /gfx/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/sprites.png -------------------------------------------------------------------------------- /audio/fx/heal.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/heal.ogg -------------------------------------------------------------------------------- /gfx/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/arrow_up.png -------------------------------------------------------------------------------- /gfx/statusbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/statusbox.png -------------------------------------------------------------------------------- /audio/fx/grunt1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/grunt1.ogg -------------------------------------------------------------------------------- /audio/fx/grunt2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/grunt2.ogg -------------------------------------------------------------------------------- /audio/fx/grunt3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/grunt3.ogg -------------------------------------------------------------------------------- /audio/fx/herodie.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/herodie.ogg -------------------------------------------------------------------------------- /audio/fx/sword.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/sword.ogg -------------------------------------------------------------------------------- /gfx/arrow_up_dbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/arrow_up_dbg.png -------------------------------------------------------------------------------- /gfx/empty_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/empty_heart.png -------------------------------------------------------------------------------- /gfx/focus_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/focus_empty.png -------------------------------------------------------------------------------- /gfx/focus_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/focus_full.png -------------------------------------------------------------------------------- /gfx/full_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/full_heart.png -------------------------------------------------------------------------------- /gfx/sprites_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/sprites_bak.png -------------------------------------------------------------------------------- /audio/fx/gooballdie.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/gooballdie.ogg -------------------------------------------------------------------------------- /audio/fx/heroinjure.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/heroinjure.ogg -------------------------------------------------------------------------------- /gfx/splash/frank_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/splash/frank_face.png -------------------------------------------------------------------------------- /audio/fx/gooballinjure.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/audio/fx/gooballinjure.ogg -------------------------------------------------------------------------------- /fonts/manaspace.regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/fonts/manaspace.regular.ttf -------------------------------------------------------------------------------- /gfx/color_tileset_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/color_tileset_16x16.png -------------------------------------------------------------------------------- /gfx/color_tileset_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csaunders/spellsword/master/gfx/color_tileset_64x64.png -------------------------------------------------------------------------------- /monsters.lua: -------------------------------------------------------------------------------- 1 | require('character') 2 | Monsters = {} 3 | 4 | function Monsters.gooBall(x, y) 5 | goo = Character.NewCharacter(x, y, 'gooball') 6 | goo:setHp(1, 1) 7 | goo:setFocus(0, 0) 8 | return goo 9 | end 10 | -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | 2 | function love.conf(t) 3 | t.window = t.window or t.screen 4 | 5 | t.title = "SpellSword" -- The title of the window the game is in (string) 6 | t.author = "csaunders" -- The author of the game (string) 7 | t.window.width = 1024 -- The window width (number) 8 | t.window.height = 768 -- The window height (number) 9 | t.screen.fullscreen = false -- Enable fullscreen (boolean) 10 | t.screen.vsync = false -- Enable vertical sync (boolean) 11 | 12 | t.screen = t.screen or t.window 13 | end 14 | -------------------------------------------------------------------------------- /cursor.lua: -------------------------------------------------------------------------------- 1 | Cursor = {} 2 | Cursor.__index = Cursor 3 | 4 | function Cursor.NewCursor(x, y) 5 | local self = setmetatable({}, Cursor) 6 | 7 | self.startX = x 8 | self.startY = y 9 | self.x = x 10 | self.y = y 11 | 12 | return self 13 | end 14 | 15 | function Cursor:reset() 16 | self.x = self.startX 17 | self.y = self.startY 18 | end 19 | 20 | function Cursor:moveBy(x, y) 21 | self.x = self.x + x 22 | self.y = self.y + y 23 | end 24 | 25 | function Cursor:draw(color) 26 | love.graphics.setColor(color or {0xFF, 0xFF, 0}) 27 | love.graphics.circle('fill', self.x, self.y, 5) 28 | love.graphics.reset() 29 | end 30 | -------------------------------------------------------------------------------- /renderer.lua: -------------------------------------------------------------------------------- 1 | require('inheritance') 2 | Renderer = {} 3 | Renderer.__index = Renderer 4 | 5 | RenderItem = {} 6 | RenderItem.__index = RenderItem 7 | 8 | function RenderItem.NewRenderItem(klass, image, x, y) 9 | local self = setmetatable({}, RenderItem) 10 | self.d = image 11 | self.x = x 12 | self.y = y 13 | 14 | return self 15 | end 16 | 17 | function Renderer:create() 18 | local self = setmetatable({}, Renderer) 19 | self.renderItems = {} 20 | 21 | return self 22 | end 23 | 24 | function Renderer:draw() 25 | for i, renderItem in ipairs(self.renderItems) do 26 | love.graphics.draw(renderItem.d, renderItem.x, renderItem.y) 27 | end 28 | end 29 | 30 | 31 | -------------------------------------------------------------------------------- /word_queue.lua: -------------------------------------------------------------------------------- 1 | WordQueue = {DEFAULT_GRAB = 4} 2 | WordQueue.__index = WordQueue 3 | 4 | function WordQueue.NewQueue(dictionary_file) 5 | local self = setmetatable({}, WordQueue) 6 | 7 | self.words = self:extractWords(love.filesystem.newFile("dictionaries/" .. dictionary_file .. ".txt")) 8 | self.size = table.getn(self.words) 9 | 10 | return self 11 | end 12 | 13 | function WordQueue:grab(number) 14 | accumulator = {} 15 | for i = 1,(number or WordQueue.DEFAULT_GRAB) do 16 | index = math.random(self.size) 17 | table.insert(accumulator, self.words[index]) 18 | end 19 | return accumulator 20 | end 21 | 22 | function WordQueue:extractWords(io) 23 | words = {} 24 | for line in io:lines() do 25 | table.insert(words, line) 26 | end 27 | return words 28 | end 29 | -------------------------------------------------------------------------------- /credits.md: -------------------------------------------------------------------------------- 1 | tiledmap.lua -- ghoulsblade -- http://love2d.org/forums/viewtopic.php?f=5&t=2411&p=25929#p25929 2 | basic_english.txt -- Wikipedia - http://simple.wikipedia.org/wiki/Wikipedia:Basic_English_combined_wordlist 3 | manaspace-regular.ttf -- codeman38 -- http://www.1001fonts.com/manaspace-font.html#styles 4 | SleekBars.png -- Jannax -- http://opengameart.org/content/sleek-bars 5 | gruntsound.wav -- n3b -- http://opengameart.org/content/grunt 6 | yelling_sounds.zip -- HaelDB -- http://opengameart.org/content/male-gruntyelling-sounds 7 | Spell Sounds Starter Pack -- p0ss -- http://opengameart.org/content/spell-sounds-starter-pack 8 | 3 Heal Spells -- DoKashiteru -- http://opengameart.org/content/3-heal-spells 9 | RPG sound pack -- artisticdude -- http://opengameart.org/content/rpg-sound-pack 10 | Audacity -- The Audacity Devs -- http://audacity.sourceforge.net/ 11 | -------------------------------------------------------------------------------- /inheritance.lua: -------------------------------------------------------------------------------- 1 | -- Create a new class that inherits from a base class 2 | -- 3 | function inheritsFrom( baseClass ) 4 | 5 | -- The following lines are equivalent to the SimpleClass example: 6 | 7 | -- Create the table and metatable representing the class. 8 | local new_class = {} 9 | local class_mt = { __index = new_class } 10 | 11 | -- Note that this function uses class_mt as an upvalue, so every instance 12 | -- of the class will share the same metatable. 13 | -- 14 | function new_class:create() 15 | local newinst = {} 16 | setmetatable( newinst, class_mt ) 17 | return newinst 18 | end 19 | 20 | -- The following is the key to implementing inheritance: 21 | 22 | -- The __index member of the new class's metatable references the 23 | -- base class. This implies that all methods of the base class will 24 | -- be exposed to the sub-class, and that the sub-class can override 25 | -- any of these methods. 26 | -- 27 | if baseClass then 28 | setmetatable( new_class, { __index = baseClass } ) 29 | end 30 | 31 | return new_class 32 | end 33 | -------------------------------------------------------------------------------- /mode_switcher.lua: -------------------------------------------------------------------------------- 1 | ModeSwitcher = {} 2 | ModeSwitcher.__index = ModeSwitcher 3 | 4 | function ModeSwitcher.NewModeSwitcher(handlers) 5 | local self = setmetatable({}, ModeSwitcher) 6 | self.modes = {} 7 | for i, handler in ipairs(handlers) do 8 | self:insertHandler(handler) 9 | end 10 | 11 | return self 12 | end 13 | 14 | function ModeSwitcher:insertHandler(handler) 15 | table.insert(self.modes, handler) 16 | end 17 | 18 | function ModeSwitcher:handle(key) 19 | if key == "escape" then 20 | return self:nextMode() 21 | else 22 | return self:handler():handle(key) 23 | end 24 | end 25 | 26 | function ModeSwitcher:inflictsInjuries() 27 | if self:handler().injureOnFailure == nil then return true end 28 | return self.handler().injureOnFailure 29 | end 30 | 31 | function ModeSwitcher:getMode() 32 | return self:handler():name() 33 | end 34 | 35 | function ModeSwitcher:handler() 36 | return self.modes[1] 37 | end 38 | 39 | function ModeSwitcher:tick(response) 40 | end 41 | 42 | function ModeSwitcher:reset() 43 | self:handler():reset() 44 | end 45 | 46 | function ModeSwitcher:nextMode() 47 | previousMode = table.remove(self.modes, 1) 48 | table.insert(self.modes, previousMode) 49 | end 50 | -------------------------------------------------------------------------------- /test/lovetest.lua: -------------------------------------------------------------------------------- 1 | local lovetest = {} 2 | 3 | lovetest._version = "0.1.0" 4 | 5 | -- Search the passed in arguments for either -t or --test 6 | function lovetest.detect(args) 7 | for _, flag in ipairs(args) do 8 | if flag == "-t" or flag == "--test" then 9 | return true 10 | end 11 | end 12 | return false 13 | end 14 | 15 | local function enumerate(dir) 16 | if love.filesystem.enumerate then 17 | return love.filesystem.enumerate(dir) 18 | else 19 | return love.filesystem.getDirectoryItems(dir) 20 | end 21 | end 22 | 23 | -- Run the unit tests, On windows, don't quit. This allows the user to see the 24 | -- test results in the console 25 | function lovetest.run() 26 | require "test/lunatest" 27 | 28 | for _, filename in ipairs(enumerate('test')) do 29 | if filename:match("^test_.*%.lua$") then 30 | local testname = (filename:gsub(".lua", "")) 31 | lunatest.suite("test/" .. testname) 32 | end 33 | end 34 | 35 | local opts = {verbose=true} 36 | local failures = lunatest.run(nil, opts) 37 | 38 | if love._os ~= "Windows" then 39 | if failures > 0 then 40 | os.exit(failures) 41 | else 42 | love.event.push("quit") 43 | end 44 | end 45 | end 46 | 47 | return lovetest 48 | -------------------------------------------------------------------------------- /test/test_collider.lua: -------------------------------------------------------------------------------- 1 | require "../collider" 2 | 3 | local function setup(x, y) 4 | local testColliderObjects = {} 5 | local singleRect = {['x'] = 10, ['y'] = 10, ['width'] = 5, ['height'] = 5} 6 | table.insert(testColliderObjects, singleRect) 7 | local testColliderCharacter = { 8 | ['x'] = x, ['y'] = y 9 | } 10 | return Collider.NewCollider(testColliderObjects), testColliderCharacter 11 | end 12 | 13 | function test_collision_inside_box() 14 | collider, character = setup(13, 13) 15 | assert_equal(true, collider:withinBounds(character)) 16 | end 17 | 18 | function test_collision_outside_box() 19 | collider, character = setup(13, 17) 20 | assert_equal(false, collider:withinBounds(character)) 21 | end 22 | 23 | function test_collision_on_line_of_box() 24 | collider, character = setup(13, 15) 25 | assert_equal(true, collider:withinBounds(character)) 26 | end 27 | 28 | function test_point_caching() 29 | collider, character = setup(17, 13) 30 | assert_equal(false, collider:withinBounds(character)) 31 | assert_equal(true, collider:knownPointWithinBounds(character)) 32 | end 33 | 34 | function test_collisions_with_multiple_binding_boxes() 35 | collider, character = setup(17, 13) 36 | otherRect = { 37 | ['x'] = 15, ['y'] = 13, 38 | ['width'] = 5, ['height'] = 5 39 | } 40 | collider:addObject(otherRect) 41 | assert_equal(true, collider:withinBounds(character)) 42 | end 43 | -------------------------------------------------------------------------------- /splash_renderer.lua: -------------------------------------------------------------------------------- 1 | require('renderer') 2 | 3 | SplashRenderer = inheritsFrom( Renderer ) 4 | SplashRenderer.__index = SplashRenderer 5 | 6 | function SplashRenderer.NewSplash(klass, title) 7 | local self = SplashRenderer:create() 8 | 9 | self.renderItems = {} 10 | self:addBackground() 11 | self:addTitle(title) 12 | 13 | return self 14 | end 15 | 16 | function SplashRenderer:renderItems() 17 | return self.drawables 18 | end 19 | 20 | function SplashRenderer:addRenderItem(d) 21 | table.insert(self.renderItems, d) 22 | end 23 | 24 | function SplashRenderer:addBackground() 25 | local image = love.graphics.newImage('gfx/splash/frank_face.png') 26 | local draw = RenderItem:NewRenderItem(image, 0, 0) 27 | self:addRenderItem(draw) 28 | end 29 | 30 | function SplashRenderer:addTitle(title) 31 | local width, height = love.graphics.getWidth(), love.graphics.getHeight() / 4 32 | local canvas = love.graphics.newCanvas(width, height) 33 | local currentFont = love.graphics.getFont() 34 | love.graphics.setCanvas(canvas) 35 | 36 | -- Draw to local canvas code 37 | local font = love.graphics.newFont('fonts/manaspace.regular.ttf', 50) 38 | love.graphics.setFont(font) 39 | love.graphics.setColor(0x5C,0x5C,0x5C) 40 | local fontWidth, fontHeight = font:getWidth(title), font:getHeight(title) 41 | love.graphics.print(title, (width - fontWidth)/2, (height - fontHeight)/2) 42 | -- /Draw to local canvas code 43 | 44 | if currentFont then love.graphics.setFont(currentFont) end 45 | love.graphics.reset() 46 | love.graphics.setCanvas() 47 | local draw = RenderItem:NewRenderItem(canvas, 0, love.graphics.getHeight() - canvas:getHeight()) 48 | self:addRenderItem(draw) 49 | end 50 | -------------------------------------------------------------------------------- /collider.lua: -------------------------------------------------------------------------------- 1 | Collider = {} 2 | Collider.__index = Collider 3 | 4 | function Collider.NewCollider(objects) 5 | local self = setmetatable({}, Collider) 6 | self.objects = {} 7 | self.colors = {} 8 | self.outsideBounds = {} 9 | 10 | for i, obj in ipairs(objects) do self:addObject(obj) end 11 | 12 | return self 13 | end 14 | 15 | function Collider:cacheBoundCheck(character) 16 | self.outsideBounds[self:cacheKey(character)] = true 17 | end 18 | 19 | function Collider:addObject(object) 20 | table.insert(self.colors, {math.random(255), math.random(255), math.random(255)}) 21 | table.insert(self.objects, object) 22 | end 23 | 24 | function Collider:cacheKey(character) 25 | return character.x .. 'x' .. character.y 26 | end 27 | 28 | function Collider:knownPointWithinBounds(character) 29 | return self.outsideBounds[self:cacheKey(character)] 30 | end 31 | 32 | function Collider:performBoundsCheck(character) 33 | local within = false 34 | for i, bound in ipairs(self.objects) do 35 | local normalizedX = character.x - (bound.x + character.adjustmentX) 36 | local normalizedY = character.y - (bound.y + character.adjustmentY) 37 | 38 | if normalizedX >= 0 and normalizedY >= 0 and normalizedX <= bound.width and normalizedY <= bound.height then 39 | self:cacheBoundCheck(character) 40 | within = true 41 | break 42 | end 43 | end 44 | return within 45 | end 46 | 47 | function Collider:withinBounds(character) 48 | --if self:knownPointWithinBounds(character) then 49 | -- return true 50 | --else 51 | local withinBounds = self:performBoundsCheck(character) 52 | if withinBounds then self:cacheBoundCheck(character) end 53 | return withinBounds 54 | --end 55 | end 56 | 57 | function Collider:draw(x, y) 58 | for i, bound in ipairs(self.objects) do 59 | love.graphics.setColor(self.colors[i]) 60 | love.graphics.rectangle('line', bound.x + adjustmentX, bound.y + adjustmentY, bound.width, bound.height) 61 | love.graphics.setColor(255, 255, 255) 62 | love.graphics.print('('..adjustmentX..','..adjustmentY..')', 50, 50) 63 | end 64 | love.graphics.reset() 65 | end 66 | -------------------------------------------------------------------------------- /status.lua: -------------------------------------------------------------------------------- 1 | require("cursor") 2 | 3 | Status = { 4 | PADDING_X = 46, 5 | PADDING_Y = 46, 6 | FULLHP = love.graphics.newImage('gfx/full_heart.png'), 7 | MISSHP = love.graphics.newImage('gfx/empty_heart.png'), 8 | FULLFOC = love.graphics.newImage('gfx/focus_full.png'), 9 | MISSFOC = love.graphics.newImage('gfx/focus_empty.png'), 10 | BACKGROUND = love.graphics.newImage("gfx/statusbox.png") 11 | } 12 | Status.__index = Status 13 | 14 | function Status.NewStatus(x, y, character) 15 | local self = setmetatable({}, Status) 16 | self.x = x 17 | self.y = y - Status.BACKGROUND:getHeight() 18 | self.cursor = Cursor.NewCursor(self.x + Status.PADDING_X, self.y + Status.PADDING_Y) 19 | self.character = character 20 | 21 | return self 22 | end 23 | 24 | function Status:center() 25 | self.center_x = self.center_x or (self.x + (Status.BACKGROUND:getWidth() / 2)) 26 | self.center_y = self.center_y or (self.y + (Status.BACKGROUND:getHeight() / 2)) 27 | self.center_cursor = self.center_cursor or Cursor.NewCursor(self.center_x, self.center_y) 28 | self.center_cursor:reset() 29 | return self.center_cursor 30 | end 31 | 32 | function Status:draw() 33 | love.graphics.draw(Status.BACKGROUND, self.x, self.y) 34 | self:drawHealth(self.cursor) 35 | self:drawFocus(self.cursor) 36 | love.graphics.reset() 37 | end 38 | 39 | function Status:drawHealth(cursor) 40 | cursor:reset() 41 | width, height = Status.FULLHP:getWidth(), Status.FULLHP:getHeight() 42 | for i=1,self.character.max_hp do 43 | if i <= self.character.current_hp then 44 | image = Status.FULLHP 45 | else 46 | image = Status.MISSHP 47 | end 48 | love.graphics.draw(image, cursor.x, cursor.y) 49 | cursor:moveBy(width, 0) 50 | end 51 | end 52 | 53 | function Status:drawFocus(cursor) 54 | cursor:reset() 55 | width, height = Status.FULLFOC:getWidth(), Status.FULLFOC:getHeight() 56 | cursor:moveBy(Status.BACKGROUND:getWidth() - Status.PADDING_X*3, 0) 57 | for i=1,self.character.max_focus do 58 | if i <= self.character.current_focus then 59 | image = Status.FULLFOC 60 | else 61 | image = Status.MISSFOC 62 | end 63 | love.graphics.draw(image, cursor.x, cursor.y) 64 | cursor:moveBy(-width, 0) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /dungeon_master.lua: -------------------------------------------------------------------------------- 1 | require('monsters') 2 | require('keyboard_handler') 3 | DungeonMaster = {SPRITE_SIZE = 64, DIRECTIONS = {'up', 'down', 'left', 'right'}} 4 | DungeonMaster.__index = DungeonMaster 5 | 6 | function DungeonMaster.NewMaster(character, collider, x, y) 7 | local self = setmetatable({}, DungeonMaster) 8 | 9 | self.monsters = {} 10 | self.character = character 11 | self.collider = collider 12 | self.x = x 13 | self.y = y 14 | 15 | return self 16 | end 17 | 18 | function DungeonMaster:spawnNewMonster() 19 | if table.getn(self.monsters) < 3 then 20 | while true do 21 | local xMult = self:genMult() 22 | local yMult = self:genMult() 23 | local deltaX = math.random(5) * 64 24 | local deltaY = math.random(5) * 64 25 | local startX = self.x + xMult*deltaX 26 | local startY = self.y + yMult*deltaY 27 | local monster = Monsters.gooBall(startX, startY) 28 | monster.adjustmentX = adjustmentX 29 | monster.adjustmentY = adjustmentY 30 | if self.collider:withinBounds(monster) then 31 | self:addMonster(monster) 32 | return 33 | end 34 | end 35 | end 36 | end 37 | 38 | function DungeonMaster:addMonster(monster) 39 | table.insert(self.monsters, monster) 40 | end 41 | 42 | function DungeonMaster:updateMonsterPositions(dx, dy) 43 | local success = true 44 | updater = function(monster) 45 | if character:isBeside(monster) then 46 | character:attack(monster) 47 | success = false 48 | else 49 | monster:applyDelta(dx, dy) 50 | end 51 | end 52 | self:monsterProcess(updater) 53 | return success 54 | end 55 | 56 | function DungeonMaster:genMult() 57 | if math.random(2) == 2 then return 1 else return -1 end 58 | end 59 | 60 | function DungeonMaster:tick(response, direction) 61 | local deadMonsters = {} 62 | if Response == KeyboardHandler.PROCESSING then return end 63 | mover = function(monster) 64 | if monster:dead() then 65 | table.insert(deadMonsters, monster) 66 | elseif monster:isBeside(self.character) then 67 | monster:attack(character) 68 | else 69 | local x, y = monster:move(direction or self:determineDirection(), monster.x, monster.y, self.collider) 70 | monster:setPosition(x, y) 71 | end 72 | end 73 | if response == KeyboardHandler.RESTING and self.character:injured() then self:rollForHeals(self.character) end 74 | self:rollForMonsters() 75 | self:monsterProcess(mover) 76 | self:cullDeadMonsters(deadMonsters) 77 | end 78 | 79 | function DungeonMaster:draw() 80 | drawer = function(monster) 81 | monster:draw() 82 | end 83 | self:monsterProcess(drawer) 84 | end 85 | 86 | function DungeonMaster:cullDeadMonsters(monsters) 87 | for i, monster in ipairs(monsters) do 88 | table.remove(self.monsters, i) 89 | end 90 | end 91 | 92 | function DungeonMaster:monsterProcess(f) 93 | for i, monster in ipairs(self.monsters) do 94 | f(monster) 95 | end 96 | end 97 | 98 | function DungeonMaster:rollForMonsters() 99 | if math.random(15) <= 2 then 100 | self:spawnNewMonster() 101 | end 102 | end 103 | 104 | function DungeonMaster:rollForHeals(char) 105 | if math.random(10) == 1 then 106 | self.heal = self.heal or love.audio.newSource('audio/fx/heal.ogg') 107 | if char == self.character then self.heal:play() end 108 | char:healRandom() 109 | end 110 | end 111 | 112 | function DungeonMaster:determineDirection() 113 | local index = math.random(table.getn(DungeonMaster.DIRECTIONS)) 114 | local direction = DungeonMaster.DIRECTIONS[index] 115 | return direction 116 | end 117 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | love.filesystem.load("tiledmap.lua")() 2 | local lovetest = require("test/lovetest") 3 | --local ProFi = require('ProFi') 4 | require("character") 5 | require("word_queue") 6 | require("keyboard_handler") 7 | require("mode_switcher") 8 | require("status") 9 | require("cursor") 10 | require("collider") 11 | require("dungeon_master") 12 | require('splash_renderer') 13 | 14 | gKeyPressed = {} 15 | gCamX,gCamY = 100,100 16 | character = nil 17 | words = nil 18 | status = nil 19 | modeSwitcher = nil 20 | gameDebug = false 21 | dungeonMaster = nil 22 | collider = nil 23 | -- otherwise the collisions for monsters get all fucked up 24 | -- TODO: need to figure out WHY this is the way it is. 25 | adjustmentX, adjustmentY = 0, 0 26 | timer = nil 27 | 28 | function love.load() 29 | --ProFi:start() 30 | --if arg and arg[#arg] == "-debug" then require("mobdebug").start() end 31 | if lovetest.detect(arg) then lovetest.run() end 32 | 33 | splash = SplashRenderer:NewSplash("Frank Face Games") 34 | 35 | local mapObjects = TiledMap_Load("maps/level0.tmx", 64) 36 | collider = Collider.NewCollider(mapObjects['Floor']) 37 | love.graphics.setNewFont('fonts/manaspace.regular.ttf', 14) 38 | startingPoint = mapObjects['Setup']['StartingPoint'] 39 | gCamX = startingPoint['x'] 40 | gCamY = startingPoint['y'] 41 | adjustmentX = tonumber(startingPoint.properties.adjustX) 42 | adjustmentY = tonumber(startingPoint.properties.adjustY) 43 | character = Character.NewCharacter(love.graphics.getWidth() / 2, love.graphics.getHeight() / 2, 'hero') 44 | status = Status.NewStatus(0, love.graphics.getHeight(), character) 45 | 46 | prepareHandlers() 47 | prepareDungeon(character, collider, gCamX, gCamY) 48 | timer = 0 49 | end 50 | 51 | function love.keyreleased( key ) 52 | gKeyPressed[key] = nil 53 | end 54 | 55 | function love.keypressed(key, unicode) 56 | if timer < 5.0 then return end 57 | gKeyPressed[key] = true 58 | if (key == "escape") then 59 | --ProFi:stop() 60 | --ProFi:writeReport('ProfileResults.txt') 61 | end 62 | if (key == "=") then modeSwitcher:reset() end 63 | if (key == "1" or key == "2" or key == "3" or key == "4") then return end 64 | if (key == "up" or key == "down" or key == "left" or key == "right") then 65 | dungeonMaster:tick(KeyboardHandler.SUCCESS, key) 66 | return 67 | end 68 | response = modeSwitcher:handle(key) 69 | if response == KeyboardHandler.PROCESSING then 70 | return 71 | else 72 | tick(response) 73 | end 74 | end 75 | 76 | function drawImage(cursor, image, rads) 77 | width, height = image:getWidth() / 2, image:getHeight() / 2 78 | multX, multY = 1, 1 79 | if rads == 3.14159 then -- rotated 180deg 80 | -- nothing to 81 | elseif rads > 3.14159 and rads < 2*3.14159 then -- rotated 270deg 82 | multX = -1 83 | elseif rads > 0 and rads < 3.14159 then -- rotated 90deg 84 | multX = 1 85 | multY = -1 86 | else -- rotated 0deg 87 | multX = -1 88 | multY = -1 89 | end 90 | 91 | love.graphics.draw(image, cursor.x + (multX*width), cursor.y + (multY*height), rads) 92 | if gameDebug then cursor:draw() end 93 | end 94 | 95 | function setFontSize(size) 96 | love.graphics.setNewFont('fonts/manaspace.regular.ttf', size or 14) 97 | end 98 | 99 | function handler() 100 | return modeSwitcher:handler() 101 | end 102 | 103 | function tick(response) 104 | if extraTick then 105 | extraTick = false 106 | dungeonMaster:tick(KeyboardHandler.RESTING) 107 | end 108 | 109 | if response == KeyboardHandler.PROCESSING then return end 110 | 111 | if response == KeyboardHandler.SUCCESS then 112 | local updatedX, updatedY, success = character:move(handler():direction(), gCamX, gCamY, collider) 113 | success = success and dungeonMaster:updateMonsterPositions(gCamX-updatedX, gCamY-updatedY) 114 | if success then 115 | gCamX, gCamY = updatedX, updatedY 116 | else 117 | print("could not move that way!") 118 | end 119 | elseif response == KeyboardHandler.FAILURE and modeSwitcher:inflictsInjuries() then 120 | character:injure('focus') 121 | end 122 | modeSwitcher:reset() 123 | dungeonMaster:tick(response) 124 | if character:unfocused() then extraTick = true end 125 | end 126 | 127 | function prepareHandlers() 128 | words = WordQueue.NewQueue('basic_english') 129 | movementHandler = KeyboardHandler.NewHandler(words) 130 | statusHandler = KeyboardHandler.NewHandler(words) 131 | modeSwitcher = ModeSwitcher.NewModeSwitcher({movementHandler, statusHandler}) 132 | end 133 | 134 | function prepareDungeon(character, collider, x, y) 135 | dungeonMaster = DungeonMaster.NewMaster(character, collider, x, y) 136 | dungeonMaster:spawnNewMonster() 137 | end 138 | 139 | function love.update(dt) 140 | timer = timer + dt 141 | local s = 100*dt 142 | if(gKeyPressed['1']) then adjustmentX = adjustmentX - s end 143 | if(gKeyPressed['2']) then adjustmentX = adjustmentX + s end 144 | if(gKeyPressed['3']) then adjustmentY = adjustmentY - s end 145 | if(gKeyPressed['4']) then adjustmentY = adjustmentY + s end 146 | -- if(gKeyPressed.up) then gCamY = gCamY - s end 147 | -- if(gKeyPressed.down) then gCamY = gCamY + s end 148 | -- if(gKeyPressed.left) then gCamX = gCamX - s end 149 | -- if(gKeyPressed.right) then gCamX = gCamX + s end 150 | collectgarbage('collect') -- "Fixes" memory leak 151 | end 152 | 153 | function love.draw() 154 | love.graphics.setBackgroundColor(0x80, 0x80, 0x80) 155 | TiledMap_DrawNearCam(gCamX, gCamY) 156 | --collider:draw(gCamX, gCamY) 157 | dungeonMaster:draw() 158 | character:draw() 159 | status:draw() 160 | handler():draw(status:center()) 161 | if(timer < 3.0) then 162 | splash:draw() 163 | elseif timer > 3.0 and splash ~= nil then 164 | splash = nil 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /character.lua: -------------------------------------------------------------------------------- 1 | Character = {MAX_HP = 5, MAX_FOCUS = 5, SCALE=1, COUNTER=0} 2 | Character.Sprites = { 3 | ['hero'] = love.graphics.newImage('gfx/hero.png'), 4 | ['gooball'] = love.graphics.newImage('gfx/gooball.png') 5 | } 6 | Character.Fx = { 7 | ['hero'] = { 8 | ['injure'] = love.audio.newSource('audio/fx/heroinjure.ogg', 'static'), 9 | ['die'] = love.audio.newSource('audio/fx/herodie.ogg', 'static') 10 | }, 11 | ['gooball'] = { 12 | ['injure'] = love.audio.newSource('audio/fx/gooballinjure.ogg', 'static'), 13 | ['die'] = love.audio.newSource('audio/fx/gooballdie.ogg', 'static') 14 | } 15 | } 16 | Character.__index = Character 17 | 18 | function Character.NewCharacter(x, y, name, scale) 19 | local self = setmetatable({}, Character) 20 | 21 | self.current_hp = Character.MAX_HP 22 | self.max_hp = Character.MAX_HP 23 | self.current_focus = Character.MAX_FOCUS 24 | self.max_focus = Character.MAX_FOCUS 25 | self.sprite = Character.Sprites[name] 26 | self.injurySound = Character.Fx[name]['injure'] 27 | self.deathSound = Character.Fx[name]['die'] 28 | self:setPosition(x, y) 29 | self:setDrawPosition(x, y) 30 | self.adjustmentX = 0 31 | self.adjustmentY = 0 32 | self.scale = scale or Character.SCALE 33 | self.width = self.scale * self.sprite:getWidth() 34 | self.height = self.scale * self.sprite:getHeight() 35 | self.name = name .. Character.COUNTER 36 | Character.COUNTER = Character.COUNTER + 1 37 | 38 | return self 39 | end 40 | 41 | function Character:setHp(current, max) 42 | self.current_hp = current or self.current_hp 43 | self.max_hp = max or self.max_hp 44 | self:correctDiscrepancies() 45 | end 46 | 47 | function Character:setFocus(current, max) 48 | self.current_focus = current or self.current_focus 49 | self.max_focus = max or self.max_focus 50 | self:correctDiscrepancies() 51 | end 52 | 53 | function Character:correctDiscrepancies() 54 | if self.current_hp > self.max_hp then 55 | self.current_hp = self.max_hp 56 | end 57 | if self.current_focus > self.max_focus then 58 | self.current_focus = self.max_focus 59 | end 60 | end 61 | 62 | function Character:injuredHp() 63 | return self.current_hp < self.max_hp 64 | end 65 | 66 | function Character:injuredFocus() 67 | return self.current_focus < self.max_focus 68 | end 69 | 70 | function Character:injured() 71 | return self:injuredHp() or self:injuredFocus() 72 | end 73 | 74 | function Character:unfocused() 75 | return self.current_focus <= 0 76 | end 77 | 78 | function Character:dead() 79 | return self.current_hp <= 0 80 | end 81 | 82 | function Character:move(direction, x, y, collider) 83 | local dx, dy = x or self.x, y or self.y 84 | if direction == "up" then 85 | dy = dy - self.height 86 | elseif direction == "down" then 87 | dy = dy + self.height 88 | elseif direction == "left" then 89 | dx = dx - self.width 90 | elseif direction == "right" then 91 | dx = dx + self.width 92 | end 93 | if collider then 94 | local newPosition = { 95 | ['x'] = dx, ['y'] = dy, 96 | ['name'] = self.name, 97 | ['adjustmentX'] = self.adjustmentX or 0, 98 | ['adjustmentY'] = self.adjustmentY or 0 99 | } 100 | if not collider:withinBounds(newPosition) then return x, y, false end 101 | end 102 | return dx, dy, true 103 | end 104 | 105 | function Character:setPosition(x, y) 106 | local oldX, oldY = (self.x or x), (self.y or y) 107 | self:applyDelta(x-oldX, y - oldY) 108 | self.x, self.y = x, y 109 | end 110 | 111 | function Character:setDrawPosition(x, y) 112 | self.drawX, self.drawY = x, y 113 | end 114 | 115 | function Character:applyDelta(dx, dy) 116 | self.drawX = (self.drawX or 0) + (dx or 0) 117 | self.drawY = (self.drawY or 0) + (dy or 0) 118 | end 119 | 120 | function Character:injure(attribute) 121 | if attribute == "health" then 122 | self.current_hp = self.current_hp - 1 123 | if self:dead() then 124 | self.deathSound:play() 125 | else 126 | self.injurySound:play() 127 | end 128 | elseif attribute == "focus" then 129 | self.current_focus = self.current_focus - 1 130 | end 131 | end 132 | 133 | function Character:heal(attribute, amount) 134 | amount = amount or 1 135 | if attribute == "health" then 136 | self.current_hp = self.current_hp + amount 137 | elseif attribute == "focus" then 138 | self.current_focus = self.current_focus + amount 139 | end 140 | self:correctDiscrepancies() 141 | end 142 | 143 | function Character:healRandom(amount) 144 | attrs = {} 145 | if self:injuredHp() then table.insert(attrs, 'health') end 146 | if self:injuredFocus() then table.insert(attrs, 'focus') end 147 | attribute = attrs[math.random(table.getn(attrs))] 148 | self:heal(attribute, amount) 149 | end 150 | 151 | function Character:center() 152 | return (self.drawX or self.x) + self.height/2, (self.drawY or self.y) + self.width/2 153 | end 154 | 155 | function Character:radius() 156 | if self.height > self.width then 157 | return self.height * self.height 158 | else 159 | return self.width * self.height 160 | end 161 | end 162 | 163 | function Character:attack(other) 164 | other:injure('health') 165 | end 166 | 167 | function Character:isBeside(other) 168 | local cx, cy = self:center() 169 | local ocx, ocy = other:center() 170 | local a = math.abs(ocy - cy) 171 | local b = math.abs(ocx - cx) 172 | local radius = (a * a) + (b * b) 173 | local delta = (3.14159*2*self:radius()) - (3.14159*2*radius) 174 | return delta > 0 175 | end 176 | 177 | function Character:draw() 178 | love.graphics.draw(self.sprite, self.drawX, self.drawY, 0, self.scale, self.scale) 179 | -- love.graphics.circle('fill', self.drawX, self.drawY, 2) 180 | -- local cx, cy = self:center() 181 | -- love.graphics.circle('fill', cx, cy, 2) 182 | end 183 | -------------------------------------------------------------------------------- /keyboard_handler.lua: -------------------------------------------------------------------------------- 1 | KeyboardHandler = { 2 | PUP = 1, 3 | PDOWN = 2, 4 | PLEFT = 3, 5 | PRIGHT = 4, 6 | SUCCESS = 5, 7 | FAILURE = 6, 8 | PROCESSING = 7, 9 | RESTING = 8, 10 | ARROW = love.graphics.newImage('gfx/arrow_up.png'), 11 | ONLYDRAW = 4, 12 | GRUNTS = { 13 | love.audio.newSource('audio/fx/grunt1.ogg', 'static'), 14 | love.audio.newSource('audio/fx/grunt2.ogg', 'static'), 15 | love.audio.newSource('audio/fx/grunt3.ogg', 'static') 16 | }, 17 | MAX_ERRORS = 3 18 | } 19 | 20 | KeyboardHandler.__index = KeyboardHandler 21 | 22 | function KeyboardHandler.NewHandler(dictionary, words, injureOnFailure) 23 | if gameDebug then 24 | KeyboardHandler.ARROW = love.graphics.newImage('gfx/arrow_up_dbg.png') 25 | end 26 | 27 | local self = setmetatable({}, KeyboardHandler) 28 | self.errorCount = 1 29 | self.status = KeyboardHandler.PROCESSING 30 | self.dictionary = dictionary 31 | self.injureOnFailure = injureOnFailure 32 | self:setWords(words or dictionary:grab()) 33 | 34 | return self 35 | end 36 | 37 | function KeyboardHandler:reset() 38 | self:setWords(self.dictionary:grab()) 39 | end 40 | 41 | function KeyboardHandler:setWords(words) 42 | self.wordStrings = words 43 | self.staticWords = {} 44 | self.words = {} 45 | self.chosenWord = nil 46 | for i = 1,table.getn(words) do 47 | word = {} 48 | staticWord = {} 49 | for c in words[i]:gmatch(".") do 50 | table.insert(word, c) 51 | table.insert(staticWord, c) 52 | end 53 | table.insert(self.words, word) 54 | table.insert(self.staticWords, staticWord) 55 | end 56 | end 57 | 58 | function KeyboardHandler:chosenDirection() 59 | return self.chosenWord 60 | end 61 | 62 | function KeyboardHandler:getWord(position) 63 | return self.staticWords[postion] 64 | end 65 | 66 | function KeyboardHandler:handle(key) 67 | if key == " " then return KeyboardHandler.RESTING end 68 | if self.chosenWord == nil then 69 | for i=1,table.getn(self.words) do 70 | if self.words[i][1] == key then 71 | self.chosenWord = i 72 | end 73 | end 74 | end 75 | self.status = self:handleChosenWord(key) 76 | return self.status 77 | end 78 | 79 | function KeyboardHandler:handleChosenWord(key) 80 | if self.chosenWord == nil and not self:withinErrorThreshold() then 81 | self.errorCount = 0 82 | return KeyboardHandler.FAILURE 83 | end 84 | 85 | word = self.words[self.chosenWord] 86 | 87 | if word and word[1] == key then 88 | self.errorCount = 0 89 | table.remove(word, 1) 90 | elseif self:withinErrorThreshold() then 91 | self:playGrunt() 92 | else 93 | self.errorCount = 0 94 | return KeyboardHandler.FAILURE 95 | end 96 | 97 | if word and table.getn(word) == 0 then 98 | return KeyboardHandler.SUCCESS 99 | else 100 | return KeyboardHandler.PROCESSING 101 | end 102 | end 103 | 104 | function KeyboardHandler:direction() 105 | d = self.chosenWord 106 | dw = nil 107 | if d == KeyboardHandler.PUP then 108 | dw = "up" 109 | elseif d == KeyboardHandler.PDOWN then 110 | dw = "down" 111 | elseif d == KeyboardHandler.PLEFT then 112 | dw = "left" 113 | elseif d == KeyboardHandler.PRIGHT then 114 | dw = "right" 115 | end 116 | return dw 117 | end 118 | 119 | function KeyboardHandler:draw(cursor) 120 | image = KeyboardHandler.ARROW 121 | setFontSize(22) 122 | 123 | local width, height = image:getWidth(), image:getHeight() 124 | for i=1,table.getn(self.words) do 125 | if gameDebug and i ~= KeyboardHandler.ONLYDRAW then 126 | -- do nothing 127 | else 128 | cursor:reset() 129 | if i == KeyboardHandler.PUP then 130 | cursor:moveBy(0, -height/2) 131 | drawImage(cursor, image, 0) 132 | cursor:moveBy(0, -height/2) 133 | elseif i == KeyboardHandler.PDOWN then 134 | cursor:moveBy(0, height/2) 135 | drawImage(cursor, image, 3.14159) 136 | cursor:moveBy(0, height/2) 137 | elseif i == KeyboardHandler.PLEFT then 138 | cursor:moveBy(-width/2, 0) 139 | drawImage(cursor, image, 3.14149 * 1.5) 140 | cursor:moveBy(-width/2, 0) 141 | else 142 | cursor:moveBy(width/2, 0) 143 | drawImage(cursor, image, 3.14159 / 2) 144 | cursor:moveBy(width/2, 0) 145 | end 146 | self:drawWord(cursor, i) 147 | -- cursor:draw() 148 | end 149 | end 150 | cursor:reset() 151 | -- cursor:draw({0xFF, 0, 0}) 152 | end 153 | 154 | function KeyboardHandler:drawWord(cursor, i) 155 | local word = self.wordStrings[i] 156 | local font = love.graphics.getFont() 157 | local static = self.staticWords[i] 158 | local actual = self.words[i] 159 | local width = font:getWidth(word) 160 | local height = font:getHeight(word) 161 | local sidx, idx = 1, 1 162 | while static[sidx] ~= actual[idx] do 163 | sidx = sidx + 1 164 | end 165 | self:compensateForLocation(word, cursor, i, font) 166 | cursor:moveBy(-width/2, -height/2) 167 | if sidx ~= idx then 168 | love.graphics.setColor(0xFF, 0, 0) 169 | message = string.sub(word, 1, sidx - 1) 170 | love.graphics.print(message, cursor.x, cursor.y) 171 | cursor:moveBy(font:getWidth(message), 0) 172 | end 173 | love.graphics.setColor(0xFF, 0xFF, 0xFF) 174 | message = string.sub(word, sidx) 175 | love.graphics.print(message, cursor.x, cursor.y) 176 | love.graphics.reset() 177 | end 178 | 179 | function KeyboardHandler:compensateForLocation(word, cursor, i, font) 180 | if i == KeyboardHandler.PUP then 181 | cursor:moveBy(0, -5) 182 | elseif i == KeyboardHandler.PDOWN then 183 | cursor:moveBy(0, 5) 184 | elseif i == KeyboardHandler.PLEFT then 185 | cursor:moveBy(-font:getWidth(word)/2,0) 186 | else 187 | cursor:moveBy(font:getWidth(word)/2, 0) 188 | end 189 | end 190 | 191 | function KeyboardHandler:withinErrorThreshold() 192 | return self.errorCount < KeyboardHandler.MAX_ERRORS 193 | end 194 | 195 | function KeyboardHandler:playGrunt() 196 | self.errorCount = self.errorCount + 1 197 | print(self.errorCount) 198 | local index = math.random(table.getn(KeyboardHandler.GRUNTS)) 199 | KeyboardHandler.GRUNTS[index]:play() 200 | end 201 | -------------------------------------------------------------------------------- /tiledmap.lua: -------------------------------------------------------------------------------- 1 | -- loader for "tiled" map editor maps (.tmx,xml-based) http://www.mapeditor.org/ 2 | -- supports multiple layers 3 | -- NOTE : function ReplaceMapTileClass (tx,ty,oldTileType,newTileType,fun_callback) end 4 | -- NOTE : function TransmuteMap (from_to_table) end -- from_to_table[old]=new 5 | -- NOTE : function GetMousePosOnMap () return gMouseX+gCamX-gScreenW/2,gMouseY+gCamY-gScreenH/2 end 6 | 7 | kTileSize = 32 8 | kMapTileTypeEmpty = 0 9 | scale = 1 10 | local floor = math.floor 11 | local ceil = math.ceil 12 | 13 | function TiledMap_Load (filepath,tilesize,spritepath_removeold,spritepath_prefix) 14 | spritepath_removeold = spritepath_removeold or "../" 15 | spritepath_prefix = spritepath_prefix or "" 16 | kTileSize = tilesize or 32 17 | gTileGfx = {} 18 | 19 | local tiletype,layers, objects = TiledMap_Parse(filepath) 20 | gMapLayers = layers 21 | for first_gid,path in pairs(tiletype) do 22 | path = spritepath_prefix .. string.gsub(path,"^"..string.gsub(spritepath_removeold,"%.","%%."),"") 23 | local raw = love.image.newImageData(path) 24 | local w,h = raw:getWidth(),raw:getHeight() 25 | local gid = first_gid 26 | local e = kTileSize 27 | for y=0,floor(h/kTileSize)-1 do 28 | for x=0,floor(w/kTileSize)-1 do 29 | local sprite = love.image.newImageData(kTileSize,kTileSize) 30 | sprite:paste(raw,0,0,x*e,y*e,e,e) 31 | gTileGfx[gid] = love.graphics.newImage(sprite) 32 | gid = gid + 1 33 | end 34 | end 35 | end 36 | return objects 37 | end 38 | 39 | function TiledMap_GetMapTile (tx,ty,layerid) -- coords in tiles 40 | local row = gMapLayers[layerid][ty] 41 | return row and row[tx] or kMapTileTypeEmpty 42 | end 43 | 44 | function TiledMap_Project(camx, camy) -- convert camera coordinates to tile coordinates 45 | return camx * kTileSize, camy * kTileSize 46 | end 47 | 48 | function TiledMap_DrawNearCam (camx,camy) 49 | camx,camy = floor(camx),floor(camy) 50 | local screen_w = love.graphics.getWidth() 51 | local screen_h = love.graphics.getHeight() 52 | local minx,maxx = floor((camx-screen_w/2)/kTileSize),ceil((camx+screen_w/2)/kTileSize) 53 | local miny,maxy = floor((camy-screen_h/2)/kTileSize),ceil((camy+screen_h/2)/kTileSize) 54 | for z = 1,#gMapLayers do 55 | for x = minx,maxx do 56 | for y = miny,maxy do 57 | local gfx = gTileGfx[TiledMap_GetMapTile(x,y,z)] 58 | if (gfx) then 59 | local sx = (x*kTileSize - camx + screen_w/2) * scale 60 | local sy = (y*kTileSize - camy + screen_h/2) * scale 61 | love.graphics.draw(gfx,sx,sy,0,scale,scale) -- x, y, r, sx, sy, ox, oy 62 | end 63 | end 64 | end 65 | end 66 | end 67 | 68 | 69 | -- ***** ***** ***** ***** ***** xml parser 70 | 71 | 72 | -- LoadXML from http://lua-users.org/wiki/LuaXml 73 | function LoadXML(s) 74 | local function LoadXML_parseargs(s) 75 | local arg = {} 76 | string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) 77 | arg[w] = a 78 | end) 79 | return arg 80 | end 81 | local stack = {} 82 | local top = {} 83 | table.insert(stack, top) 84 | local ni,c,label,xarg, empty 85 | local i, j = 1, 1 86 | while true do 87 | ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i) 88 | if not ni then break end 89 | local text = string.sub(s, i, ni-1) 90 | if not string.find(text, "^%s*$") then 91 | table.insert(top, text) 92 | end 93 | if empty == "/" then -- empty element tag 94 | table.insert(top, {label=label, xarg=LoadXML_parseargs(xarg), empty=1}) 95 | elseif c == "" then -- start tag 96 | top = {label=label, xarg=LoadXML_parseargs(xarg)} 97 | table.insert(stack, top) -- new level 98 | else -- end tag 99 | local toclose = table.remove(stack) -- remove top 100 | top = stack[#stack] 101 | if #stack < 1 then 102 | error("nothing to close with "..label) 103 | end 104 | if toclose.label ~= label then 105 | error("trying to close "..toclose.label.." with "..label) 106 | end 107 | table.insert(top, toclose) 108 | end 109 | i = j+1 110 | end 111 | local text = string.sub(s, i) 112 | if not string.find(text, "^%s*$") then 113 | table.insert(stack[#stack], text) 114 | end 115 | if #stack > 1 then 116 | error("unclosed "..stack[stack.n].label) 117 | end 118 | return stack[1] 119 | end 120 | 121 | 122 | -- ***** ***** ***** ***** ***** parsing the tilemap xml file 123 | 124 | local function getTilesets(node) 125 | local tiles = {} 126 | for k, sub in ipairs(node) do 127 | if (sub.label == "tileset") then 128 | tiles[tonumber(sub.xarg.firstgid)] = sub[1].xarg.source 129 | end 130 | end 131 | return tiles 132 | end 133 | 134 | local function getLayers(node) 135 | local layers = {} 136 | for k, sub in ipairs(node) do 137 | if (sub.label == "layer") then -- and sub.xarg.name == layer_name 138 | local layer = {} 139 | table.insert(layers,layer) 140 | width = tonumber(sub.xarg.width) 141 | i = 1 142 | j = 1 143 | for l, child in ipairs(sub[1]) do 144 | if (j == 1) then 145 | layer[i] = {} 146 | end 147 | layer[i][j] = tonumber(child.xarg.gid) 148 | j = j + 1 149 | if j > width then 150 | j = 1 151 | i = i + 1 152 | end 153 | end 154 | end 155 | end 156 | return layers 157 | end 158 | 159 | local function getProperties(node) 160 | local properties = {} 161 | for k, sub in ipairs(node) do 162 | if sub.label == 'properties' then 163 | for l, prop in ipairs(sub) do 164 | properties[prop.xarg.name] = prop.xarg.value 165 | end 166 | end 167 | end 168 | return properties 169 | end 170 | 171 | local function getObjects(node) 172 | local objects = {} 173 | for k, sub in ipairs(node) do 174 | if sub.label == 'objectgroup' then 175 | local objgroup = {} 176 | for l, obj in ipairs(sub) do 177 | if obj.label == "object" and obj.xarg.name then 178 | local object = {} 179 | object['x'] = tonumber(obj.xarg.x) 180 | object['y'] = tonumber(obj.xarg.y) 181 | object['width'] = tonumber(obj.xarg.width) 182 | object['height'] = tonumber(obj.xarg.height) 183 | object['properties'] = getProperties(obj) 184 | table.insert(objgroup, object) 185 | objgroup[obj.xarg.name] = object 186 | end 187 | objects[sub.xarg.name] = objgroup 188 | end 189 | end 190 | end 191 | return objects 192 | end 193 | 194 | function TiledMap_Parse(filename) 195 | local xml = LoadXML(love.filesystem.read(filename)) 196 | local tiles = getTilesets(xml[2]) 197 | local layers = getLayers(xml[2]) 198 | local objects = getObjects(xml[2]) 199 | return tiles, layers, objects 200 | end 201 | 202 | -------------------------------------------------------------------------------- /maps/basic.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | -------------------------------------------------------------------------------- /ProFi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ProFi v1.3, by Luke Perkin 2012. MIT Licence http://www.opensource.org/licenses/mit-license.php. 3 | 4 | Example: 5 | ProFi = require 'ProFi' 6 | ProFi:start() 7 | some_function() 8 | another_function() 9 | coroutine.resume( some_coroutine ) 10 | ProFi:stop() 11 | ProFi:writeReport( 'MyProfilingReport.txt' ) 12 | 13 | API: 14 | *Arguments are specified as: type/name/default. 15 | ProFi:start( string/once/nil ) 16 | ProFi:stop() 17 | ProFi:checkMemory( number/interval/0, string/note/'' ) 18 | ProFi:writeReport( string/filename/'ProFi.txt' ) 19 | ProFi:reset() 20 | ProFi:setHookCount( number/hookCount/0 ) 21 | ProFi:setGetTimeMethod( function/getTimeMethod/os.clock ) 22 | ProFi:setInspect( string/methodName, number/levels/1 ) 23 | ]] 24 | 25 | ----------------------- 26 | -- Locals: 27 | ----------------------- 28 | 29 | local ProFi = {} 30 | local onDebugHook, sortByDurationDesc, sortByCallCount, getTime 31 | local DEFAULT_DEBUG_HOOK_COUNT = 0 32 | local FORMAT_HEADER_LINE = "| %-50s: %-40s: %-20s: %-12s: %-12s: %-12s|\n" 33 | local FORMAT_OUTPUT_LINE = "| %s: %-12s: %-12s: %-12s|\n" 34 | local FORMAT_INSPECTION_LINE = "> %s: %-12s\n" 35 | local FORMAT_TOTALTIME_LINE = "| TOTAL TIME = %f\n" 36 | local FORMAT_MEMORY_LINE = "| %-20s: %-16s: %-16s| %s\n" 37 | local FORMAT_HIGH_MEMORY_LINE = "H %-20s: %-16s: %-16sH %s\n" 38 | local FORMAT_LOW_MEMORY_LINE = "L %-20s: %-16s: %-16sL %s\n" 39 | local FORMAT_TITLE = "%-50.50s: %-40.40s: %-20s" 40 | local FORMAT_LINENUM = "%4i" 41 | local FORMAT_TIME = "%04.3f" 42 | local FORMAT_RELATIVE = "%03.2f%%" 43 | local FORMAT_COUNT = "%7i" 44 | local FORMAT_KBYTES = "%7i Kbytes" 45 | local FORMAT_MBYTES = "%7.1f Mbytes" 46 | local FORMAT_MEMORY_HEADER1 = "\n=== HIGH & LOW MEMORY USAGE ===============================\n" 47 | local FORMAT_MEMORY_HEADER2 = "=== MEMORY USAGE ==========================================\n" 48 | local FORMAT_BANNER = [[ 49 | ############################################################################################################### 50 | ##### ProFi, a lua profiler. This profile was generated on: %s 51 | ##### ProFi is created by Luke Perkin 2012 under the MIT Licence, www.locofilm.co.uk 52 | ##### Version 1.3. Get the most recent version at this gist: https://gist.github.com/2838755 53 | ############################################################################################################### 54 | 55 | ]] 56 | 57 | ----------------------- 58 | -- Public Methods: 59 | ----------------------- 60 | 61 | --[[ 62 | Starts profiling any method that is called between this and ProFi:stop(). 63 | Pass the parameter 'once' to so that this methodis only run once. 64 | Example: 65 | ProFi:start( 'once' ) 66 | ]] 67 | function ProFi:start( param ) 68 | if param == 'once' then 69 | if self:shouldReturn() then 70 | return 71 | else 72 | self.should_run_once = true 73 | end 74 | end 75 | self.has_started = true 76 | self.has_finished = false 77 | self:resetReports( self.reports ) 78 | self:startHooks() 79 | self.startTime = getTime() 80 | end 81 | 82 | --[[ 83 | Stops profiling. 84 | ]] 85 | function ProFi:stop() 86 | if self:shouldReturn() then 87 | return 88 | end 89 | self.stopTime = getTime() 90 | self:stopHooks() 91 | self.has_finished = true 92 | end 93 | 94 | function ProFi:checkMemory( interval, note ) 95 | local time = getTime() 96 | local interval = interval or 0 97 | if self.lastCheckMemoryTime and time < self.lastCheckMemoryTime + interval then 98 | return 99 | end 100 | self.lastCheckMemoryTime = time 101 | local memoryReport = { 102 | ['time'] = time; 103 | ['memory'] = collectgarbage('count'); 104 | ['note'] = note or ''; 105 | } 106 | table.insert( self.memoryReports, memoryReport ) 107 | self:setHighestMemoryReport( memoryReport ) 108 | self:setLowestMemoryReport( memoryReport ) 109 | end 110 | 111 | --[[ 112 | Writes the profile report to a file. 113 | Param: [filename:string:optional] defaults to 'ProFi.txt' if not specified. 114 | ]] 115 | function ProFi:writeReport( filename ) 116 | if #self.reports > 0 or #self.memoryReports > 0 then 117 | filename = filename or 'ProFi.txt' 118 | self:sortReportsWithSortMethod( self.reports, self.sortMethod ) 119 | self:writeReportsToFilename( filename ) 120 | print( string.format("[ProFi]\t Report written to %s", filename) ) 121 | end 122 | end 123 | 124 | --[[ 125 | Resets any profile information stored. 126 | ]] 127 | function ProFi:reset() 128 | self.reports = {} 129 | self.reportsByTitle = {} 130 | self.memoryReports = {} 131 | self.highestMemoryReport = nil 132 | self.lowestMemoryReport = nil 133 | self.has_started = false 134 | self.has_finished = false 135 | self.should_run_once = false 136 | self.lastCheckMemoryTime = nil 137 | self.hookCount = self.hookCount or DEFAULT_DEBUG_HOOK_COUNT 138 | self.sortMethod = self.sortMethod or sortByDurationDesc 139 | self.inspect = nil 140 | end 141 | 142 | --[[ 143 | Set how often a hook is called. 144 | See http://pgl.yoyo.org/luai/i/debug.sethook for information. 145 | Param: [hookCount:number] if 0 ProFi counts every time a function is called. 146 | if 2 ProFi counts every other 2 function calls. 147 | ]] 148 | function ProFi:setHookCount( hookCount ) 149 | self.hookCount = hookCount 150 | end 151 | 152 | --[[ 153 | Set how the report is sorted when written to file. 154 | Param: [sortType:string] either 'duration' or 'count'. 155 | 'duration' sorts by the time a method took to run. 156 | 'count' sorts by the number of times a method was called. 157 | ]] 158 | function ProFi:setSortMethod( sortType ) 159 | if sortType == 'duration' then 160 | self.sortMethod = sortByDurationDesc 161 | elseif sortType == 'count' then 162 | self.sortMethod = sortByCallCount 163 | end 164 | end 165 | 166 | --[[ 167 | By default the getTime method is os.clock (CPU time), 168 | If you wish to use other time methods pass it to this function. 169 | Param: [getTimeMethod:function] 170 | ]] 171 | function ProFi:setGetTimeMethod( getTimeMethod ) 172 | getTime = getTimeMethod 173 | end 174 | 175 | --[[ 176 | Allows you to inspect a specific method. 177 | Will write to the report a list of methods that 178 | call this method you're inspecting, you can optionally 179 | provide a levels parameter to traceback a number of levels. 180 | Params: [methodName:string] the name of the method you wish to inspect. 181 | [levels:number:optional] the amount of levels you wish to traceback, defaults to 1. 182 | ]] 183 | function ProFi:setInspect( methodName, levels ) 184 | if self.inspect then 185 | self.inspect.methodName = methodName 186 | self.inspect.levels = levels or 1 187 | else 188 | self.inspect = { 189 | ['methodName'] = methodName; 190 | ['levels'] = levels or 1; 191 | } 192 | end 193 | end 194 | 195 | ----------------------- 196 | -- Implementations methods: 197 | ----------------------- 198 | 199 | function ProFi:shouldReturn( ) 200 | return self.should_run_once and self.has_finished 201 | end 202 | 203 | function ProFi:getFuncReport( funcInfo ) 204 | local title = self:getTitleFromFuncInfo( funcInfo ) 205 | local funcReport = self.reportsByTitle[ title ] 206 | if not funcReport then 207 | funcReport = self:createFuncReport( funcInfo ) 208 | self.reportsByTitle[ title ] = funcReport 209 | table.insert( self.reports, funcReport ) 210 | end 211 | return funcReport 212 | end 213 | 214 | function ProFi:getTitleFromFuncInfo( funcInfo ) 215 | local name = funcInfo.name or 'anonymous' 216 | local source = funcInfo.short_src or 'C_FUNC' 217 | local linedefined = funcInfo.linedefined or 0 218 | linedefined = string.format( FORMAT_LINENUM, linedefined ) 219 | return string.format(FORMAT_TITLE, source, name, linedefined) 220 | end 221 | 222 | function ProFi:createFuncReport( funcInfo ) 223 | local name = funcInfo.name or 'anonymous' 224 | local source = funcInfo.source or 'C Func' 225 | local linedefined = funcInfo.linedefined or 0 226 | local funcReport = { 227 | ['title'] = self:getTitleFromFuncInfo( funcInfo ); 228 | ['count'] = 0; 229 | ['timer'] = 0; 230 | } 231 | return funcReport 232 | end 233 | 234 | function ProFi:startHooks() 235 | debug.sethook( onDebugHook, 'cr', self.hookCount ) 236 | end 237 | 238 | function ProFi:stopHooks() 239 | debug.sethook() 240 | end 241 | 242 | function ProFi:sortReportsWithSortMethod( reports, sortMethod ) 243 | if reports then 244 | table.sort( reports, sortMethod ) 245 | end 246 | end 247 | 248 | function ProFi:writeReportsToFilename( filename ) 249 | local file, err = io.open( filename, 'w' ) 250 | assert( file, err ) 251 | self:writeBannerToFile( file ) 252 | if #self.reports > 0 then 253 | self:writeProfilingReportsToFile( self.reports, file ) 254 | end 255 | if #self.memoryReports > 0 then 256 | self:writeMemoryReportsToFile( self.memoryReports, file ) 257 | end 258 | file:close() 259 | end 260 | 261 | function ProFi:writeProfilingReportsToFile( reports, file ) 262 | local totalTime = self.stopTime - self.startTime 263 | local totalTimeOutput = string.format(FORMAT_TOTALTIME_LINE, totalTime) 264 | file:write( totalTimeOutput ) 265 | local header = string.format( FORMAT_HEADER_LINE, "FILE", "FUNCTION", "LINE", "TIME", "RELATIVE", "CALLED" ) 266 | file:write( header ) 267 | for i, funcReport in ipairs( reports ) do 268 | local timer = string.format(FORMAT_TIME, funcReport.timer) 269 | local count = string.format(FORMAT_COUNT, funcReport.count) 270 | local relTime = string.format(FORMAT_RELATIVE, (funcReport.timer / totalTime) * 100 ) 271 | local outputLine = string.format(FORMAT_OUTPUT_LINE, funcReport.title, timer, relTime, count ) 272 | file:write( outputLine ) 273 | if funcReport.inspections then 274 | self:writeInpsectionsToFile( funcReport.inspections, file ) 275 | end 276 | end 277 | end 278 | 279 | function ProFi:writeMemoryReportsToFile( reports, file ) 280 | file:write( FORMAT_MEMORY_HEADER1 ) 281 | self:writeHighestMemoryReportToFile( file ) 282 | self:writeLowestMemoryReportToFile( file ) 283 | file:write( FORMAT_MEMORY_HEADER2 ) 284 | for i, memoryReport in ipairs( reports ) do 285 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_MEMORY_LINE ) 286 | file:write( outputLine ) 287 | end 288 | end 289 | 290 | function ProFi:writeHighestMemoryReportToFile( file ) 291 | local memoryReport = self.highestMemoryReport 292 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_HIGH_MEMORY_LINE ) 293 | file:write( outputLine ) 294 | end 295 | 296 | function ProFi:writeLowestMemoryReportToFile( file ) 297 | local memoryReport = self.lowestMemoryReport 298 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_LOW_MEMORY_LINE ) 299 | file:write( outputLine ) 300 | end 301 | 302 | function ProFi:formatMemoryReportWithFormatter( memoryReport, formatter ) 303 | local time = string.format(FORMAT_TIME, memoryReport.time) 304 | local kbytes = string.format(FORMAT_KBYTES, memoryReport.memory) 305 | local mbytes = string.format(FORMAT_MBYTES, memoryReport.memory/1024) 306 | local outputLine = string.format(formatter, time, kbytes, mbytes, memoryReport.note) 307 | return outputLine 308 | end 309 | 310 | function ProFi:writeBannerToFile( file ) 311 | local banner = string.format(FORMAT_BANNER, os.date()) 312 | file:write( banner ) 313 | end 314 | 315 | function ProFi:writeInpsectionsToFile( inspections, file ) 316 | local inspectionsList = self:sortInspectionsIntoList( inspections ) 317 | file:write('\n==^ INSPECT ^======================================================================================================== COUNT ===\n') 318 | for i, inspection in ipairs( inspectionsList ) do 319 | local line = string.format(FORMAT_LINENUM, inspection.line) 320 | local title = string.format(FORMAT_TITLE, inspection.source, inspection.name, line) 321 | local count = string.format(FORMAT_COUNT, inspection.count) 322 | local outputLine = string.format(FORMAT_INSPECTION_LINE, title, count ) 323 | file:write( outputLine ) 324 | end 325 | file:write('===============================================================================================================================\n\n') 326 | end 327 | 328 | function ProFi:sortInspectionsIntoList( inspections ) 329 | local inspectionsList = {} 330 | for k, inspection in pairs(inspections) do 331 | inspectionsList[#inspectionsList+1] = inspection 332 | end 333 | table.sort( inspectionsList, sortByCallCount ) 334 | return inspectionsList 335 | end 336 | 337 | function ProFi:resetReports( reports ) 338 | for i, report in ipairs( reports ) do 339 | report.timer = 0 340 | report.count = 0 341 | report.inspections = nil 342 | end 343 | end 344 | 345 | function ProFi:shouldInspect( funcInfo ) 346 | return self.inspect and self.inspect.methodName == funcInfo.name 347 | end 348 | 349 | function ProFi:getInspectionsFromReport( funcReport ) 350 | local inspections = funcReport.inspections 351 | if not inspections then 352 | inspections = {} 353 | funcReport.inspections = inspections 354 | end 355 | return inspections 356 | end 357 | 358 | function ProFi:getInspectionWithKeyFromInspections( key, inspections ) 359 | local inspection = inspections[key] 360 | if not inspection then 361 | inspection = { 362 | ['count'] = 0; 363 | } 364 | inspections[key] = inspection 365 | end 366 | return inspection 367 | end 368 | 369 | function ProFi:doInspection( inspect, funcReport ) 370 | local inspections = self:getInspectionsFromReport( funcReport ) 371 | local levels = 5 + inspect.levels 372 | local currentLevel = 5 373 | while currentLevel < levels do 374 | local funcInfo = debug.getinfo( currentLevel, 'nS' ) 375 | if funcInfo then 376 | local source = funcInfo.short_src or '[C]' 377 | local name = funcInfo.name or 'anonymous' 378 | local line = funcInfo.linedefined 379 | local key = source..name..line 380 | local inspection = self:getInspectionWithKeyFromInspections( key, inspections ) 381 | inspection.source = source 382 | inspection.name = name 383 | inspection.line = line 384 | inspection.count = inspection.count + 1 385 | currentLevel = currentLevel + 1 386 | else 387 | break 388 | end 389 | end 390 | end 391 | 392 | function ProFi:onFunctionCall( funcInfo ) 393 | local funcReport = ProFi:getFuncReport( funcInfo ) 394 | funcReport.callTime = getTime() 395 | funcReport.count = funcReport.count + 1 396 | if self:shouldInspect( funcInfo ) then 397 | self:doInspection( self.inspect, funcReport ) 398 | end 399 | end 400 | 401 | function ProFi:onFunctionReturn( funcInfo ) 402 | local funcReport = ProFi:getFuncReport( funcInfo ) 403 | if funcReport.callTime then 404 | funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime) 405 | end 406 | end 407 | 408 | function ProFi:setHighestMemoryReport( memoryReport ) 409 | if not self.highestMemoryReport then 410 | self.highestMemoryReport = memoryReport 411 | else 412 | if memoryReport.memory > self.highestMemoryReport.memory then 413 | self.highestMemoryReport = memoryReport 414 | end 415 | end 416 | end 417 | 418 | function ProFi:setLowestMemoryReport( memoryReport ) 419 | if not self.lowestMemoryReport then 420 | self.lowestMemoryReport = memoryReport 421 | else 422 | if memoryReport.memory < self.lowestMemoryReport.memory then 423 | self.lowestMemoryReport = memoryReport 424 | end 425 | end 426 | end 427 | 428 | ----------------------- 429 | -- Local Functions: 430 | ----------------------- 431 | 432 | getTime = os.clock 433 | 434 | onDebugHook = function( hookType ) 435 | local funcInfo = debug.getinfo( 2, 'nS' ) 436 | if hookType == "call" then 437 | ProFi:onFunctionCall( funcInfo ) 438 | elseif hookType == "return" then 439 | ProFi:onFunctionReturn( funcInfo ) 440 | end 441 | end 442 | 443 | sortByDurationDesc = function( a, b ) 444 | return a.timer > b.timer 445 | end 446 | 447 | sortByCallCount = function( a, b ) 448 | return a.count > b.count 449 | end 450 | 451 | ----------------------- 452 | -- Return Module: 453 | ----------------------- 454 | 455 | ProFi:reset() 456 | return ProFi -------------------------------------------------------------------------------- /dictionaries/basic_english.txt: -------------------------------------------------------------------------------- 1 | able 2 | about 3 | absence 4 | absorption 5 | acceleration 6 | acceptance 7 | accessory 8 | accident 9 | account 10 | acid 11 | across 12 | act 13 | acting 14 | active 15 | actor 16 | addition 17 | address 18 | adjacent 19 | adjustment 20 | adventure 21 | advertisement 22 | advice 23 | after 24 | afterthought 25 | again 26 | against 27 | age 28 | agency 29 | agent 30 | ago 31 | agreement 32 | air 33 | airplane 34 | alcohol 35 | algebra 36 | all 37 | allowance 38 | almost 39 | along 40 | also 41 | alternative 42 | aluminium 43 | always 44 | ambition 45 | ammonia 46 | among 47 | amount 48 | amplitude 49 | amusement 50 | anchor 51 | and 52 | anesthetic 53 | angle 54 | angry 55 | animal 56 | ankle 57 | another 58 | answer 59 | ant 60 | any 61 | anybody 62 | anyhow 63 | anyone 64 | anything 65 | anywhere 66 | apparatus 67 | appendage 68 | apple 69 | application 70 | approval 71 | approximation 72 | april 73 | arbitrary 74 | arbitration 75 | arc 76 | arch 77 | area 78 | argument 79 | arithmetic 80 | arm 81 | army 82 | arrangement 83 | art 84 | as 85 | asbestos 86 | ash 87 | asset 88 | assistant 89 | at 90 | attack 91 | attempt 92 | attention 93 | attraction 94 | august 95 | authority 96 | autobus 97 | automatic 98 | automobile 99 | average 100 | awake 101 | awkward 102 | axis 103 | baby 104 | back 105 | backbone 106 | backwoods 107 | bad 108 | bag 109 | balance 110 | balcony 111 | bale 112 | ball 113 | ballet 114 | band 115 | bang 116 | bank 117 | bankrupt 118 | bar 119 | bark 120 | barrel 121 | base 122 | based 123 | basin 124 | basing 125 | basket 126 | bath 127 | be 128 | beak 129 | beaker 130 | beard 131 | beat 132 | beautiful 133 | because 134 | become 135 | bed 136 | bedroom 137 | bee 138 | beef 139 | beer 140 | beeswax 141 | before 142 | behavior 143 | behind 144 | belief 145 | bell 146 | belt 147 | bent 148 | berry 149 | bet 150 | between 151 | bill 152 | biology 153 | bird 154 | birefringence 155 | birth 156 | birthday 157 | birthright 158 | bit 159 | bite 160 | bitter 161 | black 162 | blackberry 163 | blackbird 164 | blackboard 165 | blade 166 | blame 167 | blanket 168 | blood 169 | bloodvessel 170 | blow 171 | blue 172 | bluebell 173 | board 174 | boat 175 | body 176 | boiling 177 | bomb 178 | bone 179 | book 180 | bookkeeper 181 | boot 182 | both 183 | bottle 184 | bottom 185 | box 186 | boy 187 | brain 188 | brake 189 | branch 190 | brass 191 | brave 192 | bread 193 | break 194 | breakfast 195 | breast 196 | breath 197 | brick 198 | bridge 199 | bright 200 | broken 201 | broker 202 | brother 203 | brown 204 | brush 205 | brushwood 206 | bubble 207 | bucket 208 | bud 209 | budget 210 | builder 211 | building 212 | bulb 213 | bunch 214 | buoyancy 215 | burial 216 | burn 217 | burned 218 | burner 219 | burning 220 | burst 221 | business 222 | busy 223 | but 224 | butter 225 | buttercup 226 | button 227 | by 228 | cafe 229 | cake 230 | calculation 231 | calendar 232 | call 233 | camera 234 | canvas 235 | capacity 236 | capital 237 | card 238 | cardboard 239 | care 240 | carefree 241 | caretaker 242 | carpet 243 | carriage 244 | cart 245 | carter 246 | cartilage 247 | case 248 | cast 249 | cat 250 | catarrh 251 | category 252 | cause 253 | cave 254 | cavity 255 | cell 256 | centi- 257 | ceremony 258 | certain 259 | certificate 260 | chain 261 | chair 262 | chalk 263 | champagne 264 | chance 265 | change 266 | character 267 | charge 268 | chauffeur 269 | cheap 270 | check 271 | cheese 272 | chemical 273 | chemist 274 | chemistry 275 | chest 276 | chief 277 | child 278 | chimney 279 | chin 280 | china 281 | chocolate 282 | choice 283 | chorus 284 | church 285 | cigarette 286 | circle 287 | circuit 288 | circulation 289 | circumference 290 | circus 291 | citron 292 | civilization 293 | claim 294 | claw 295 | clay 296 | clean 297 | clear 298 | cleavage 299 | clever 300 | client 301 | climber 302 | clip 303 | clock 304 | clockwork 305 | cloth 306 | clothier 307 | clothing 308 | cloud 309 | club 310 | coal 311 | coat 312 | cocktail 313 | code 314 | coffee 315 | cognac 316 | coil 317 | cold 318 | collar 319 | collection 320 | college 321 | collision 322 | colony 323 | color 324 | column 325 | comb 326 | combination 327 | combine 328 | come 329 | comfort 330 | committee 331 | common 332 | commonsense 333 | communications 334 | company 335 | comparison 336 | competition 337 | complaint 338 | complete 339 | complex 340 | component 341 | compound 342 | concept 343 | concrete 344 | condition 345 | conductor 346 | congruent 347 | connection 348 | conscious 349 | conservation 350 | consignment 351 | constant 352 | consumer 353 | continuous 354 | contour 355 | control 356 | convenient 357 | conversion 358 | cook 359 | cooked 360 | cooker 361 | cooking 362 | cool 363 | copper 364 | copy 365 | copyright 366 | cord 367 | cork 368 | corner 369 | correlation 370 | corrosion 371 | cost 372 | cotton 373 | cough 374 | country 375 | court 376 | cover 377 | cow 378 | crack 379 | credit 380 | creeper 381 | crime 382 | crop 383 | cross 384 | cruel 385 | crush 386 | cry 387 | crying 388 | cunning 389 | cup 390 | cupboard 391 | current 392 | curtain 393 | curve 394 | cushion 395 | cusp 396 | customs 397 | cut 398 | damage 399 | damping 400 | dance 401 | dancer 402 | dancing 403 | danger 404 | dark 405 | date 406 | daughter 407 | day 408 | daylight 409 | dead 410 | dear 411 | death 412 | debit 413 | debt 414 | december 415 | deci- 416 | decision 417 | deck 418 | decrease 419 | deep 420 | defect 421 | deficiency 422 | deflation 423 | degenerate 424 | degree 425 | degree 426 | delicate 427 | delivery 428 | demand 429 | denominator 430 | density 431 | department 432 | dependent 433 | deposit 434 | desert 435 | design 436 | designer 437 | desire 438 | destruction 439 | detail 440 | determining 441 | development 442 | dew 443 | diameter 444 | difference 445 | different 446 | difficulty 447 | digestion 448 | dike 449 | dilution 450 | dinner 451 | dip 452 | direct 453 | direction 454 | dirty 455 | disappearance 456 | discharge 457 | disclaimers 458 | discount 459 | discovery 460 | discussion 461 | disease 462 | disgrace 463 | disgust 464 | dislike 465 | dissipation 466 | distance 467 | distribution 468 | disturbance 469 | ditch 470 | dive 471 | division 472 | divisor 473 | divorce 474 | do 475 | dog 476 | doll 477 | domesticating 478 | dominion 479 | door 480 | doubt 481 | down 482 | downfall 483 | drain 484 | drawer 485 | dreadful 486 | dream 487 | dress 488 | dressing 489 | drift 490 | drink 491 | driver 492 | driving 493 | drop 494 | dropped 495 | dropper 496 | dry 497 | duct 498 | dull 499 | dust 500 | duster 501 | duty 502 | dynamite 503 | each 504 | ear 505 | early 506 | earring 507 | earth 508 | earthwork 509 | east 510 | easy 511 | economy 512 | edge 513 | education 514 | effect 515 | efficiency 516 | effort 517 | egg 518 | eight 519 | either 520 | elastic 521 | electric 522 | electricity 523 | eleven 524 | elimination 525 | embassy 526 | empire 527 | employer 528 | empty 529 | encyclopedia 530 | end 531 | enemy 532 | engine 533 | engineer 534 | enough 535 | envelope 536 | environment 537 | envy 538 | equal 539 | equation 540 | erosion 541 | error 542 | eruption 543 | evaporation 544 | even 545 | evening 546 | event 547 | ever 548 | evergreen 549 | every 550 | everybody 551 | everyday 552 | everyone 553 | everything 554 | everywhere 555 | exact 556 | example 557 | exchange 558 | excitement 559 | exercise 560 | existence 561 | expansion 562 | experience 563 | experiment 564 | expert 565 | explanation 566 | explosion 567 | export 568 | expression 569 | extinction 570 | eye 571 | eyeball 572 | eyebrow 573 | eyelash 574 | face 575 | fact 576 | factor 577 | failure 578 | fair 579 | fall 580 | false 581 | family 582 | famous 583 | fan 584 | far 585 | farm 586 | farmer 587 | fastening 588 | fat 589 | father 590 | fatherland 591 | fault 592 | fear 593 | feather 594 | february 595 | feeble 596 | feeling 597 | female 598 | ferment 599 | fertile 600 | fertilizing 601 | fever 602 | fiber 603 | fiction 604 | field 605 | fifteen 606 | fifth 607 | fifty 608 | fight 609 | figure 610 | fin 611 | financial 612 | finger 613 | fingerprint 614 | fire 615 | fire-engine 616 | firearm 617 | fired 618 | firefly 619 | fireman 620 | fireplace 621 | firework 622 | firing 623 | first 624 | first-rate 625 | fish 626 | fisher 627 | fisherman 628 | five 629 | fixed 630 | flag 631 | flame 632 | flash 633 | flask 634 | flat 635 | flesh 636 | flight 637 | flint 638 | flood 639 | flood 640 | floor 641 | flour 642 | flow 643 | flower 644 | fly 645 | focus 646 | fold 647 | folder 648 | foliation 649 | food 650 | foolish 651 | foot 652 | football 653 | footlights 654 | footman 655 | footnote 656 | footprint 657 | footstep 658 | for 659 | force 660 | forecast 661 | forehead 662 | foreign 663 | forgiveness 664 | fork 665 | form 666 | forty 667 | forward 668 | four 669 | fourteen 670 | fourth 671 | fowl 672 | fraction 673 | fracture 674 | frame 675 | free 676 | frequent 677 | fresh 678 | friction 679 | friday 680 | friend 681 | from 682 | front 683 | frost 684 | frozen 685 | fruit 686 | full 687 | fume 688 | funnel 689 | funny 690 | fur 691 | furnace 692 | furniture 693 | fusion 694 | future 695 | garden 696 | gardener 697 | gas 698 | gasworks 699 | gate 700 | general 701 | generation 702 | geography 703 | geology 704 | geometry 705 | germ 706 | germinating 707 | get 708 | gill 709 | girl 710 | give 711 | glacier 712 | gland 713 | glass 714 | glossary 715 | glove 716 | glycerin 717 | go 718 | goat 719 | god 720 | gold 721 | goldfish 722 | good 723 | good-morning 724 | goodlooking 725 | goodnight 726 | government 727 | grain 728 | gram 729 | grand 730 | grass 731 | grateful 732 | grating 733 | gravel 734 | gray 735 | grease 736 | great 737 | green 738 | grey 739 | grief 740 | grip 741 | grocery 742 | groove 743 | gross 744 | ground 745 | group 746 | growth 747 | guarantee 748 | guard 749 | guess 750 | guide 751 | gum 752 | gun 753 | gun-carriage 754 | gunboat 755 | gunmetal 756 | gunpowder 757 | habit 758 | hair 759 | half 760 | hammer 761 | hand 762 | handbook 763 | handkerchief 764 | handle 765 | handwriting 766 | hanger 767 | hanging 768 | happy 769 | harbor 770 | hard 771 | harmony 772 | hat 773 | hate 774 | have 775 | he 776 | head 777 | headdress 778 | headland 779 | headstone 780 | headway 781 | healthy 782 | hearing 783 | heart 784 | heat 785 | heated 786 | heater 787 | heating 788 | heavy 789 | hedge 790 | help 791 | help 792 | here 793 | hereafter 794 | herewith 795 | high 796 | highlands 797 | highway 798 | hill 799 | himself 800 | hinge 801 | hire 802 | hiss 803 | history 804 | hold 805 | hole 806 | holiday 807 | hollow 808 | home 809 | honest 810 | honey 811 | hoof 812 | hook 813 | hope 814 | horn 815 | horse 816 | horseplay 817 | horsepower 818 | hospital 819 | host 820 | hotel 821 | hour 822 | hourglass 823 | house 824 | houseboat 825 | housekeeper 826 | how 827 | however 828 | human 829 | humor 830 | hundred 831 | hunt 832 | hurry 833 | hurt 834 | husband 835 | hyena 836 | hygiene 837 | hysteria 838 | ice 839 | idea 840 | if 841 | igneous 842 | ill 843 | image 844 | imagination 845 | imperial 846 | import 847 | important 848 | impulse 849 | impurity 850 | in 851 | inasmuch 852 | inclusion 853 | income 854 | increase 855 | index 856 | individual 857 | indoors 858 | industry 859 | inferno 860 | infinity 861 | inflation 862 | influenza 863 | inheritance 864 | ink 865 | inland 866 | inlet 867 | inner 868 | innocent 869 | input 870 | insect 871 | inside 872 | instep 873 | institution 874 | instrument 875 | insulator 876 | insurance 877 | integer 878 | intelligent 879 | intercept 880 | interest 881 | international 882 | interpretation 883 | intersection 884 | into 885 | intrusion 886 | invention 887 | inverse 888 | investigation 889 | investment 890 | invitation 891 | iron 892 | island 893 | itself 894 | jam 895 | january 896 | jaw 897 | jazz 898 | jealous 899 | jelly 900 | jerk 901 | jewel 902 | jeweler 903 | join 904 | joiner 905 | joint 906 | journey 907 | judge 908 | jug 909 | juice 910 | july 911 | jump 912 | june 913 | jury 914 | justice 915 | keep 916 | keeper 917 | kennel 918 | kettle 919 | key 920 | kick 921 | kidney 922 | kilo- 923 | kind 924 | king 925 | kiss 926 | kitchen 927 | knee 928 | knife 929 | knock 930 | knot 931 | knowledge 932 | lace 933 | lag 934 | lake 935 | lame 936 | lamp 937 | land 938 | landmark 939 | landslip 940 | language 941 | large 942 | last 943 | late 944 | latitude 945 | latitude 946 | laugh 947 | laughing 948 | lava 949 | law 950 | lawyer 951 | layer 952 | lazy 953 | lead 954 | leaf 955 | learner 956 | learning 957 | least 958 | leather 959 | lecture 960 | left 961 | leg 962 | legal 963 | length 964 | lens 965 | less 966 | lesson 967 | let 968 | letter 969 | level 970 | lever 971 | lever 972 | liability 973 | library 974 | license 975 | lid 976 | life 977 | lift 978 | light 979 | lighthouse 980 | like 981 | lime 982 | limestone 983 | limit 984 | line 985 | linen 986 | link 987 | lip 988 | liqueur 989 | liquid 990 | list 991 | liter 992 | little 993 | liver 994 | living 995 | load 996 | loan 997 | local 998 | lock 999 | locker 1000 | locking 1001 | locus 1002 | long 1003 | longitude 1004 | longitude 1005 | look 1006 | looking-glass 1007 | loose 1008 | loss 1009 | loud 1010 | love 1011 | low 1012 | luck 1013 | lump 1014 | lunch 1015 | lung 1016 | macaroni 1017 | machine 1018 | madam 1019 | magic 1020 | magnetic 1021 | magnitude 1022 | make 1023 | malaria 1024 | male 1025 | man 1026 | manager 1027 | manhole 1028 | mania 1029 | manner 1030 | many 1031 | map 1032 | marble 1033 | march 1034 | margin 1035 | mark 1036 | marked 1037 | market 1038 | marriage 1039 | married 1040 | mass 1041 | mast 1042 | match 1043 | material 1044 | mathematics 1045 | mattress 1046 | mature 1047 | may 1048 | may 1049 | meal 1050 | mean 1051 | meaning 1052 | measure 1053 | meat 1054 | medical 1055 | medicine 1056 | medium 1057 | meeting 1058 | melt 1059 | member 1060 | memory 1061 | meow 1062 | mess 1063 | message 1064 | metabolism 1065 | metal 1066 | meter 1067 | micro- 1068 | microscope 1069 | middle 1070 | military 1071 | milk 1072 | mill 1073 | milli- 1074 | million 1075 | mind 1076 | mine 1077 | miner 1078 | mineral 1079 | minute 1080 | minute 1081 | mist 1082 | mixed 1083 | mixture 1084 | model 1085 | modern 1086 | modest 1087 | momentum 1088 | monday 1089 | money 1090 | monkey 1091 | monopoly 1092 | month 1093 | mood 1094 | moon 1095 | moral 1096 | more 1097 | morning 1098 | most 1099 | mother 1100 | motion 1101 | mountain 1102 | moustache 1103 | mouth 1104 | move 1105 | much 1106 | mud 1107 | multiple 1108 | multiplication 1109 | murder 1110 | muscle 1111 | museum 1112 | music 1113 | myself 1114 | nail 1115 | name 1116 | narrow 1117 | nasty 1118 | nation 1119 | natural 1120 | nature 1121 | navy 1122 | near 1123 | nearer 1124 | neat 1125 | necessary 1126 | neck 1127 | need 1128 | needle 1129 | neglect 1130 | neighbor 1131 | nerve 1132 | nest 1133 | net 1134 | network 1135 | neutron 1136 | new 1137 | news 1138 | newspaper 1139 | next 1140 | nice 1141 | nickel 1142 | nicotine 1143 | night 1144 | nine 1145 | no 1146 | nobody 1147 | node 1148 | noise 1149 | normal 1150 | north 1151 | nose 1152 | nostril 1153 | not 1154 | note 1155 | noted 1156 | nothing 1157 | november 1158 | now 1159 | nowhere 1160 | nucleus 1161 | number 1162 | numerator 1163 | nurse 1164 | nut 1165 | obedient 1166 | observation 1167 | october 1168 | of 1169 | off 1170 | offer 1171 | office 1172 | officer 1173 | offspring 1174 | oil 1175 | old 1176 | olive 1177 | omelet 1178 | on 1179 | once 1180 | oncoming 1181 | one 1182 | oneself 1183 | onlooker 1184 | only 1185 | onto 1186 | open 1187 | opera 1188 | operation 1189 | opinion 1190 | opium 1191 | opposite 1192 | or 1193 | orange 1194 | orchestra 1195 | orchestra 1196 | order 1197 | ore 1198 | organ 1199 | organism 1200 | organization 1201 | origin 1202 | ornament 1203 | other 1204 | out 1205 | outburst 1206 | outcome 1207 | outcrop 1208 | outcry 1209 | outdoor 1210 | outer 1211 | outgoing 1212 | outhouse 1213 | outlaw 1214 | outlet 1215 | outlier 1216 | outline 1217 | outlook 1218 | output 1219 | outside 1220 | outskirts 1221 | outstretched 1222 | oval 1223 | oven 1224 | over 1225 | overacting 1226 | overall 1227 | overbalancing 1228 | overbearing 1229 | overcoat 1230 | overcome 1231 | overdo 1232 | overdressed 1233 | overfull 1234 | overhanging 1235 | overhead 1236 | overland 1237 | overlap 1238 | overleaf 1239 | overloud 1240 | overseas 1241 | overseer 1242 | overshoe 1243 | overstatement 1244 | overtake 1245 | overtaxed 1246 | overtime 1247 | overturned 1248 | overuse 1249 | overvalued 1250 | overweight 1251 | overworking 1252 | own 1253 | owner 1254 | oxidation 1255 | packing 1256 | pad 1257 | page 1258 | pain 1259 | paint 1260 | painter 1261 | painting 1262 | pair 1263 | pajamas 1264 | pan 1265 | paper 1266 | paradise 1267 | paraffin 1268 | paragraph 1269 | parallel 1270 | parcel 1271 | parent 1272 | park 1273 | part 1274 | particle 1275 | parting 1276 | partner 1277 | party 1278 | passage 1279 | passport 1280 | past 1281 | paste 1282 | patent 1283 | path 1284 | patience 1285 | payment 1286 | peace 1287 | pedal 1288 | pen 1289 | pencil 1290 | pendulum 1291 | penguin 1292 | pension 1293 | people 1294 | perfect 1295 | person 1296 | petal 1297 | petroleum 1298 | phonograph 1299 | physical 1300 | physics 1301 | physiology 1302 | piano 1303 | picture 1304 | pig 1305 | pin 1306 | pincushion 1307 | pipe 1308 | piston 1309 | place 1310 | plain 1311 | plan 1312 | plane 1313 | plant 1314 | plaster 1315 | plate 1316 | platinum 1317 | play 1318 | played 1319 | playing 1320 | plaything 1321 | please 1322 | pleased 1323 | pleasure 1324 | plough 1325 | plow 1326 | plug 1327 | pocket 1328 | poetry 1329 | point 1330 | pointer 1331 | pointing 1332 | poison 1333 | police 1334 | policeman 1335 | polish 1336 | political 1337 | pollen 1338 | pool 1339 | poor 1340 | population 1341 | porcelain 1342 | porter 1343 | position 1344 | possible 1345 | post 1346 | postman 1347 | postmark 1348 | postmaster 1349 | postoffice 1350 | pot 1351 | potash 1352 | potato 1353 | potter 1354 | powder 1355 | power 1356 | practice 1357 | praise 1358 | prayer 1359 | present 1360 | president 1361 | pressure 1362 | price 1363 | prick 1364 | priest 1365 | prime 1366 | prince 1367 | princess 1368 | print 1369 | printer 1370 | prison 1371 | prisoner 1372 | private 1373 | probability 1374 | probable 1375 | process 1376 | produce 1377 | producer 1378 | product 1379 | profit 1380 | program 1381 | progress 1382 | projectile 1383 | projection 1384 | promise 1385 | proof 1386 | propaganda 1387 | property 1388 | prose 1389 | protest 1390 | proud 1391 | psychology 1392 | public 1393 | pull 1394 | pulley 1395 | pump 1396 | punishment 1397 | pupil 1398 | purchase 1399 | pure 1400 | purpose 1401 | purr 1402 | push 1403 | put 1404 | pyramid 1405 | quack 1406 | quality 1407 | quantity 1408 | quarter 1409 | queen 1410 | question 1411 | quick 1412 | quiet 1413 | quinine 1414 | quite 1415 | quotient 1416 | race 1417 | radiation 1418 | radio 1419 | radium 1420 | rail 1421 | rain 1422 | raining 1423 | range 1424 | rat 1425 | rate 1426 | ratio 1427 | ray 1428 | reaction 1429 | reader 1430 | reading 1431 | reading 1432 | ready 1433 | reagent 1434 | real 1435 | reason 1436 | receipt 1437 | receiver 1438 | reciprocal 1439 | record 1440 | rectangle 1441 | recurring 1442 | red 1443 | reference 1444 | referendum 1445 | reflux 1446 | regret 1447 | regular 1448 | reinforcement 1449 | relation 1450 | relative 1451 | religion 1452 | remark 1453 | remedy 1454 | rent 1455 | repair 1456 | representative 1457 | reproduction 1458 | repulsion 1459 | request 1460 | residue 1461 | resistance 1462 | resolution 1463 | respect 1464 | responsible 1465 | rest 1466 | restaurant 1467 | result 1468 | retail 1469 | revenge 1470 | reversible 1471 | reward 1472 | rheumatism 1473 | rhythm 1474 | rice 1475 | rich 1476 | right 1477 | rigidity 1478 | ring 1479 | rise 1480 | rival 1481 | river 1482 | road 1483 | rock 1484 | rod 1485 | roll 1486 | roller 1487 | roof 1488 | room 1489 | root 1490 | rot 1491 | rotation 1492 | rough 1493 | round 1494 | royal 1495 | rub 1496 | rubber 1497 | rude 1498 | rule 1499 | ruler 1500 | rum 1501 | run 1502 | runaway 1503 | rust 1504 | sac 1505 | sad 1506 | safe 1507 | sail 1508 | sailor 1509 | salad 1510 | sale 1511 | salt 1512 | same 1513 | sample 1514 | sand 1515 | sardine 1516 | satisfaction 1517 | saturated 1518 | saturday 1519 | saucer 1520 | saving 1521 | say 1522 | scale 1523 | scale 1524 | scarp 1525 | schist 1526 | school 1527 | science 1528 | scissors 1529 | scratch 1530 | screen 1531 | screw 1532 | sea 1533 | seal 1534 | seaman 1535 | search 1536 | seat 1537 | second 1538 | second 1539 | secondhand 1540 | secret 1541 | secretary 1542 | secretion 1543 | section 1544 | security 1545 | sedimentary 1546 | see 1547 | seed 1548 | seem 1549 | selection 1550 | self 1551 | selfish 1552 | send 1553 | sense 1554 | sensitivity 1555 | sentence 1556 | sepal 1557 | separate 1558 | september 1559 | serious 1560 | serum 1561 | servant 1562 | service 1563 | set 1564 | seven 1565 | sex 1566 | shade 1567 | shadow 1568 | shake 1569 | shale 1570 | shame 1571 | share 1572 | sharp 1573 | shave 1574 | shear 1575 | sheep 1576 | sheet 1577 | shelf 1578 | shell 1579 | ship 1580 | shirt 1581 | shock 1582 | shocked 1583 | shocking 1584 | shoe 1585 | shore 1586 | short 1587 | shortcut 1588 | shorthand 1589 | shoulder 1590 | show 1591 | shut 1592 | side 1593 | sideboard 1594 | sidewalk 1595 | sight 1596 | sign 1597 | silk 1598 | sill 1599 | silver 1600 | similarity 1601 | simple 1602 | since 1603 | sir 1604 | sister 1605 | six 1606 | sixteen 1607 | size 1608 | skin 1609 | skirt 1610 | skull 1611 | sky 1612 | slate 1613 | sleep 1614 | sleeve 1615 | slide 1616 | slip 1617 | slope 1618 | slow 1619 | small 1620 | smash 1621 | smell 1622 | smile 1623 | smoke 1624 | smooth 1625 | snake 1626 | sneeze 1627 | snow 1628 | snowing 1629 | so 1630 | soap 1631 | social 1632 | society 1633 | sock 1634 | soft 1635 | soil 1636 | soldier 1637 | solid 1638 | solution 1639 | solvent 1640 | some 1641 | somebody 1642 | someday 1643 | somehow 1644 | someone 1645 | something 1646 | sometime 1647 | somewhat 1648 | somewhere 1649 | son 1650 | song 1651 | sorry 1652 | sort 1653 | sound 1654 | soup 1655 | south 1656 | space 1657 | spade 1658 | spark 1659 | special 1660 | specialization 1661 | specimen 1662 | speculation 1663 | spirit 1664 | spit 1665 | splash 1666 | sponge 1667 | spoon 1668 | sport 1669 | spot 1670 | spring 1671 | square 1672 | stable 1673 | stage 1674 | stain 1675 | stair 1676 | stalk 1677 | stamen 1678 | stamp 1679 | star 1680 | start 1681 | statement 1682 | station 1683 | statistics 1684 | steady 1685 | steam 1686 | steamer 1687 | steel 1688 | stem 1689 | step 1690 | stick 1691 | sticky 1692 | stiff 1693 | still 1694 | stimulus 1695 | stitch 1696 | stocking 1697 | stomach 1698 | stone 1699 | stop 1700 | stopper 1701 | stopping 1702 | store 1703 | storm 1704 | story 1705 | straight 1706 | strain 1707 | strange 1708 | straw 1709 | stream 1710 | street 1711 | strength 1712 | stress 1713 | stretch 1714 | stretcher 1715 | strike 1716 | string 1717 | strong 1718 | structure 1719 | study 1720 | subject 1721 | substance 1722 | substitution 1723 | subtraction 1724 | success 1725 | successive 1726 | such 1727 | suchlike 1728 | sucker 1729 | sudden 1730 | sugar 1731 | suggestion 1732 | sum 1733 | summer 1734 | sun 1735 | sunburn 1736 | sunday 1737 | sunlight 1738 | sunshade 1739 | supply 1740 | support 1741 | surface 1742 | surgeon 1743 | surprise 1744 | suspension 1745 | suspicious 1746 | sweet 1747 | sweetheart 1748 | swelling 1749 | swim 1750 | swing 1751 | switch 1752 | sympathetic 1753 | system 1754 | table 1755 | tail 1756 | tailor 1757 | take 1758 | talk 1759 | talk 1760 | talking 1761 | tall 1762 | tame 1763 | tap 1764 | tapioca 1765 | taste 1766 | tax 1767 | taxi 1768 | tea 1769 | teacher 1770 | teaching 1771 | tear 1772 | telegram 1773 | telephone 1774 | ten 1775 | tendency 1776 | tent 1777 | term 1778 | terrace 1779 | test 1780 | texture 1781 | than 1782 | that 1783 | the 1784 | theater 1785 | then 1786 | theory 1787 | there 1788 | thermometer 1789 | thick 1790 | thickness 1791 | thief 1792 | thimble 1793 | thin 1794 | thing 1795 | third 1796 | thirteen 1797 | thirty 1798 | this 1799 | thorax 1800 | though 1801 | thought 1802 | thousand 1803 | thread 1804 | threat 1805 | three 1806 | throat 1807 | through 1808 | thrust 1809 | thumb 1810 | thunder 1811 | thursday 1812 | ticket 1813 | tide 1814 | tie 1815 | tight 1816 | till 1817 | time 1818 | tin 1819 | tired 1820 | tissue 1821 | to 1822 | toast 1823 | tobacco 1824 | today 1825 | toe 1826 | together 1827 | tomorrow 1828 | tongs 1829 | tongue 1830 | tonight 1831 | too 1832 | tooth 1833 | top 1834 | torpedo 1835 | total 1836 | touch 1837 | touching 1838 | towel 1839 | tower 1840 | town 1841 | trade 1842 | trader 1843 | tradesman 1844 | traffic 1845 | tragedy 1846 | train 1847 | trainer 1848 | training 1849 | transmission 1850 | transparent 1851 | transport 1852 | trap 1853 | travel 1854 | tray 1855 | treatment 1856 | tree 1857 | triangle 1858 | trick 1859 | trouble 1860 | troubled 1861 | troubling 1862 | trousers 1863 | truck 1864 | true 1865 | tube 1866 | tuesday 1867 | tune 1868 | tunnel 1869 | turbine 1870 | turn 1871 | turning 1872 | twelve 1873 | twenty 1874 | twenty-one 1875 | twice 1876 | twin 1877 | twist 1878 | two 1879 | typist 1880 | ugly 1881 | umbrella 1882 | unconformity 1883 | under 1884 | underclothing 1885 | undercooked 1886 | undergo 1887 | undergrowth 1888 | undermined 1889 | undersigned 1890 | undersized 1891 | understanding 1892 | understatement 1893 | undertake 1894 | undervalued 1895 | undo 1896 | unit 1897 | universe 1898 | university 1899 | unknown 1900 | up 1901 | upkeep 1902 | uplift 1903 | upon 1904 | upright 1905 | uptake 1906 | use 1907 | used 1908 | valency 1909 | valley 1910 | value 1911 | valve 1912 | vanilla 1913 | vapor 1914 | variable 1915 | vascular 1916 | vegetable 1917 | velocity 1918 | verse 1919 | very 1920 | vessel 1921 | vestigial 1922 | victim 1923 | victory 1924 | view 1925 | viewpoint 1926 | violent 1927 | violin 1928 | visa 1929 | vitamin 1930 | vodka 1931 | voice 1932 | volt 1933 | volume 1934 | vortex 1935 | vote 1936 | waiter 1937 | waiting 1938 | waiting 1939 | walk 1940 | wall 1941 | war 1942 | warm 1943 | wash 1944 | waste 1945 | wasted 1946 | watch 1947 | water 1948 | waterfall 1949 | wave 1950 | wax 1951 | way 1952 | weak 1953 | weather 1954 | wedge 1955 | wednesday 1956 | week 1957 | weekend 1958 | weight 1959 | welcome 1960 | well 1961 | well-being 1962 | well-off 1963 | west 1964 | wet 1965 | what 1966 | whatever 1967 | wheel 1968 | when 1969 | whenever 1970 | where 1971 | whereas 1972 | whereby 1973 | wherever 1974 | whether 1975 | which 1976 | whichever 1977 | while 1978 | whip 1979 | whisky 1980 | whistle 1981 | white 1982 | whitewash 1983 | who 1984 | whoever 1985 | wholesale 1986 | why 1987 | wide 1988 | widow 1989 | wife 1990 | wild 1991 | will 1992 | wind 1993 | window 1994 | windpipe 1995 | wine 1996 | wing 1997 | winter 1998 | wire 1999 | wise 2000 | with 2001 | within 2002 | without 2003 | woman 2004 | wood 2005 | woodwork 2006 | wool 2007 | word 2008 | work 2009 | worker 2010 | workhouse 2011 | working 2012 | working 2013 | working 2014 | world 2015 | worm 2016 | wound 2017 | wreck 2018 | wrist 2019 | writer 2020 | writing 2021 | wrong 2022 | x-ray 2023 | yawn 2024 | year 2025 | yearbook 2026 | yellow 2027 | yes 2028 | yesterday 2029 | you 2030 | young 2031 | yourself 2032 | zebra 2033 | zinc 2034 | zookeeper 2035 | zoology 2036 | -------------------------------------------------------------------------------- /test/lunatest.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- 3 | -- Copyright (c) 2009-12 Scott Vokes 4 | -- 5 | -- Permission is hereby granted, free of charge, to any person 6 | -- obtaining a copy of this software and associated documentation 7 | -- files (the "Software"), to deal in the Software without 8 | -- restriction, including without limitation the rights to use, 9 | -- copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | -- copies of the Software, and to permit persons to whom the 11 | -- Software is furnished to do so, subject to the following 12 | -- conditions: 13 | -- 14 | -- The above copyright notice and this permission notice shall be 15 | -- included in all copies or substantial portions of the Software. 16 | -- 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | -- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | -- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | -- OTHER DEALINGS IN THE SOFTWARE. 25 | -- 26 | ------------------------------------------------------------------------ 27 | -- 28 | -- This is a library for randomized testing with Lua. 29 | -- For usage and examples, see README and the test suite. 30 | -- 31 | ------------------------------------------------------------------------ 32 | 33 | ------------ 34 | -- Module -- 35 | ------------ 36 | 37 | -- standard libraries used 38 | local debug, io, math, os, string, table = 39 | debug, io, math, os, string, table 40 | 41 | -- required core global functions 42 | local assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber = 43 | assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber 44 | local fmt, tostring, type, unpack = string.format, tostring, type, unpack 45 | local getmetatable, rawget, setmetatable, xpcall = 46 | getmetatable, rawget, setmetatable, xpcall 47 | local exit, next, require = os.exit, next, require 48 | 49 | -- Get containing env, Lua 5.1-style 50 | local getenv = getfenv 51 | 52 | ---Use lhf's random, if available. It provides an RNG with better 53 | -- statistical properties, and it gives consistent values across OSs. 54 | -- http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lrandom 55 | pcall(require, "random") 56 | local random = random 57 | 58 | -- Use the debug API to get line numbers, if available. 59 | pcall(require, "debug") 60 | local debug = debug 61 | 62 | -- Use luasocket's gettime(), luaposix' gettimeofday(), or os.date for 63 | -- timestamps 64 | local now = pcall(require, "socket") and socket.gettime or 65 | pcall(require, "posix") and posix.gettimeofday and 66 | function () 67 | local s, us = posix.gettimeofday() 68 | return s + us / 1000000 69 | end or 70 | function () return tonumber(os.date("%s")) end 71 | 72 | -- Get env immediately wrapping module, to put assert_ tests there. 73 | local _importing_env = getenv() 74 | 75 | -- Check command line arguments: 76 | -- -v / --verbose, default to verbose_hooks. 77 | -- -s or --suite, only run the named suite(s). 78 | -- -t or --test, only run tests matching the pattern. 79 | local lt_arg = arg 80 | 81 | -- ##################### 82 | -- # Utility functions # 83 | -- ##################### 84 | 85 | local function printf(...) print(string.format(...)) end 86 | 87 | local function result_table(name) 88 | return { name=name, pass={}, fail={}, skip={}, err={} } 89 | end 90 | 91 | local function combine_results(to, from) 92 | local s_name = from.name 93 | for _,set in ipairs{"pass", "fail", "skip", "err" } do 94 | local fs, ts = from[set], to[set] 95 | for name,val in pairs(fs) do 96 | ts[s_name .. "." .. name] = val 97 | end 98 | end 99 | end 100 | 101 | local function is_func(v) return type(v) == "function" end 102 | 103 | local function count(t) 104 | local ct = 0 105 | for _ in pairs(t) do ct = ct + 1 end 106 | return ct 107 | end 108 | 109 | 110 | -- ########### 111 | -- # Results # 112 | -- ########### 113 | 114 | local function msec(t) 115 | if t and type(t) == "number" then 116 | return fmt(" (%.2fms)", t * 1000) 117 | else 118 | return "" 119 | end 120 | end 121 | 122 | 123 | local RPass = {} 124 | local passMT = {__index=RPass} 125 | function RPass:tostring_char() return "." end 126 | function RPass:add(s, name) s.pass[name] = self end 127 | function RPass:type() return "pass" end 128 | function RPass:tostring(name) 129 | return fmt("PASS: %s%s%s", 130 | name or "(unknown)", msec(self.elapsed), 131 | self.msg and (": " .. tostring(self.msg)) or "") 132 | end 133 | 134 | 135 | local RFail = {} 136 | local failMT = {__index=RFail} 137 | function RFail:tostring_char() return "F" end 138 | function RFail:add(s, name) s.fail[name] = self end 139 | function RFail:type() return "fail" end 140 | function RFail:tostring(name) 141 | return fmt("FAIL: %s%s: %s%s%s", 142 | name or "(unknown)", 143 | msec(self.elapsed), 144 | self.reason or "", 145 | self.msg and (" - " .. tostring(self.msg)) or "", 146 | self.line and (" (%d)"):format(self.line) or "") 147 | end 148 | 149 | 150 | local RSkip = {} 151 | local skipMT = {__index=RSkip} 152 | function RSkip:tostring_char() return "s" end 153 | function RSkip:add(s, name) s.skip[name] = self end 154 | function RSkip:type() return "skip" end 155 | function RSkip:tostring(name) 156 | return fmt("SKIP: %s()%s", name or "unknown", 157 | self.msg and (" - " .. tostring(self.msg)) or "") 158 | end 159 | 160 | 161 | local RError = {} 162 | local errorMT = {__index=RError} 163 | function RError:tostring_char() return "E" end 164 | function RError:add(s, name) s.err[name] = self end 165 | function RError:type() return "error" end 166 | function RError:tostring(name) 167 | return self.msg or 168 | fmt("ERROR (in %s%s, couldn't get traceback)", 169 | msec(self.elapsed), name or "(unknown)") 170 | end 171 | 172 | 173 | local function Pass(t) return setmetatable(t or {}, passMT) end 174 | local function Fail(t) return setmetatable(t, failMT) end 175 | local function Skip(t) return setmetatable(t, skipMT) end 176 | local function Error(t) return setmetatable(t, errorMT) end 177 | 178 | 179 | -- ############## 180 | -- # Assertions # 181 | -- ############## 182 | 183 | ---Renamed standard assert. 184 | local checked = 0 185 | local TS = tostring 186 | 187 | local function wraptest(flag, msg, t) 188 | checked = checked + 1 189 | t.msg = msg 190 | if debug then 191 | local info = debug.getinfo(3, "l") 192 | t.line = info.currentline 193 | end 194 | if not flag then error(Fail(t)) end 195 | end 196 | 197 | ---Fail a test. 198 | -- @param no_exit Unless set to true, the presence of any failures 199 | -- causes the test suite to terminate with an exit status of 1. 200 | function fail(msg, no_exit) 201 | local line 202 | if debug then 203 | local info = debug.getinfo(2, "l") 204 | line = info.currentline 205 | end 206 | error(Fail { msg=msg, reason="(Failed)", no_exit=no_exit, line=line }) 207 | end 208 | 209 | 210 | ---Skip a test, with a note, e.g. "TODO". 211 | function skip(msg) error(Skip { msg=msg }) end 212 | 213 | 214 | ---got == true. 215 | -- (Named "assert_true" to not conflict with standard assert.) 216 | -- @param msg Message to display with the result. 217 | function assert_true(got, msg) 218 | wraptest(got, msg, { reason=fmt("Expected success, got %s.", TS(got)) }) 219 | end 220 | 221 | ---got == false. 222 | function assert_false(got, msg) 223 | wraptest(not got, msg, 224 | { reason=fmt("Expected false, got %s", TS(got)) }) 225 | end 226 | 227 | --got == nil 228 | function assert_nil(got, msg) 229 | wraptest(got == nil, msg, 230 | { reason=fmt("Expected nil, got %s", TS(got)) }) 231 | end 232 | 233 | --got ~= nil 234 | function assert_not_nil(got, msg) 235 | wraptest(got ~= nil, msg, 236 | { reason=fmt("Expected non-nil value, got %s", TS(got)) }) 237 | end 238 | 239 | local function tol_or_msg(t, m) 240 | if not t and not m then return 0, nil 241 | elseif type(t) == "string" then return 0, t 242 | elseif type(t) == "number" then return t, m 243 | else error("Neither a numeric tolerance nor string") 244 | end 245 | end 246 | 247 | 248 | ---exp == got. 249 | function assert_equal(exp, got, tol, msg) 250 | tol, msg = tol_or_msg(tol, msg) 251 | if type(exp) == "number" and type(got) == "number" then 252 | wraptest(math.abs(exp - got) <= tol, msg, 253 | { reason=fmt("Expected %s +/- %s, got %s", 254 | TS(exp), TS(tol), TS(got)) }) 255 | else 256 | wraptest(exp == got, msg, 257 | { reason=fmt("Expected %q, got %q", TS(exp), TS(got)) }) 258 | end 259 | end 260 | 261 | ---exp ~= got. 262 | function assert_not_equal(exp, got, msg) 263 | wraptest(exp ~= got, msg, 264 | { reason="Expected something other than " .. TS(exp) }) 265 | end 266 | 267 | ---val > lim. 268 | function assert_gt(lim, val, msg) 269 | wraptest(val > lim, msg, 270 | { reason=fmt("Expected a value > %s, got %s", 271 | TS(lim), TS(val)) }) 272 | end 273 | 274 | ---val >= lim. 275 | function assert_gte(lim, val, msg) 276 | wraptest(val >= lim, msg, 277 | { reason=fmt("Expected a value >= %s, got %s", 278 | TS(lim), TS(val)) }) 279 | end 280 | 281 | ---val < lim. 282 | function assert_lt(lim, val, msg) 283 | wraptest(val < lim, msg, 284 | { reason=fmt("Expected a value < %s, got %s", 285 | TS(lim), TS(val)) }) 286 | end 287 | 288 | ---val <= lim. 289 | function assert_lte(lim, val, msg) 290 | wraptest(val <= lim, msg, 291 | { reason=fmt("Expected a value <= %s, got %s", 292 | TS(lim), TS(val)) }) 293 | end 294 | 295 | ---#val == len. 296 | function assert_len(len, val, msg) 297 | wraptest(#val == len, msg, 298 | { reason=fmt("Expected #val == %d, was %d", 299 | len, #val) }) 300 | end 301 | 302 | ---#val ~= len. 303 | function assert_not_len(len, val, msg) 304 | wraptest(#val ~= len, msg, 305 | { reason=fmt("Expected length other than %d", len) }) 306 | end 307 | 308 | ---Test that the string s matches the pattern exp. 309 | function assert_match(pat, s, msg) 310 | s = tostring(s) 311 | wraptest(type(s) == "string" and s:match(pat), msg, 312 | { reason=fmt("Expected string to match pattern %s, was %s", 313 | pat, 314 | (s:len() > 30 and (s:sub(1,30) .. "...")or s)) }) 315 | end 316 | 317 | ---Test that the string s doesn't match the pattern exp. 318 | function assert_not_match(pat, s, msg) 319 | wraptest(type(s) ~= "string" or not s:match(pat), msg, 320 | { reason=fmt("Should not match pattern %s", pat) }) 321 | end 322 | 323 | ---Test that val is a boolean. 324 | function assert_boolean(val, msg) 325 | wraptest(type(val) == "boolean", msg, 326 | { reason=fmt("Expected type boolean but got %s", 327 | type(val)) }) 328 | end 329 | 330 | ---Test that val is not a boolean. 331 | function assert_not_boolean(val, msg) 332 | wraptest(type(val) ~= "boolean", msg, 333 | { reason=fmt("Expected type other than boolean but got %s", 334 | type(val)) }) 335 | end 336 | 337 | ---Test that val is a number. 338 | function assert_number(val, msg) 339 | wraptest(type(val) == "number", msg, 340 | { reason=fmt("Expected type number but got %s", 341 | type(val)) }) 342 | end 343 | 344 | ---Test that val is not a number. 345 | function assert_not_number(val, msg) 346 | wraptest(type(val) ~= "number", msg, 347 | { reason=fmt("Expected type other than number but got %s", 348 | type(val)) }) 349 | end 350 | 351 | ---Test that val is a string. 352 | function assert_string(val, msg) 353 | wraptest(type(val) == "string", msg, 354 | { reason=fmt("Expected type string but got %s", 355 | type(val)) }) 356 | end 357 | 358 | ---Test that val is not a string. 359 | function assert_not_string(val, msg) 360 | wraptest(type(val) ~= "string", msg, 361 | { reason=fmt("Expected type other than string but got %s", 362 | type(val)) }) 363 | end 364 | 365 | ---Test that val is a table. 366 | function assert_table(val, msg) 367 | wraptest(type(val) == "table", msg, 368 | { reason=fmt("Expected type table but got %s", 369 | type(val)) }) 370 | end 371 | 372 | ---Test that val is not a table. 373 | function assert_not_table(val, msg) 374 | wraptest(type(val) ~= "table", msg, 375 | { reason=fmt("Expected type other than table but got %s", 376 | type(val)) }) 377 | end 378 | 379 | ---Test that val is a function. 380 | function assert_function(val, msg) 381 | wraptest(type(val) == "function", msg, 382 | { reason=fmt("Expected type function but got %s", 383 | type(val)) }) 384 | end 385 | 386 | ---Test that val is not a function. 387 | function assert_not_function(val, msg) 388 | wraptest(type(val) ~= "function", msg, 389 | { reason=fmt("Expected type other than function but got %s", 390 | type(val)) }) 391 | end 392 | 393 | ---Test that val is a thread (coroutine). 394 | function assert_thread(val, msg) 395 | wraptest(type(val) == "thread", msg, 396 | { reason=fmt("Expected type thread but got %s", 397 | type(val)) }) 398 | end 399 | 400 | ---Test that val is not a thread (coroutine). 401 | function assert_not_thread(val, msg) 402 | wraptest(type(val) ~= "thread", msg, 403 | { reason=fmt("Expected type other than thread but got %s", 404 | type(val)) }) 405 | end 406 | 407 | ---Test that val is a userdata (light or heavy). 408 | function assert_userdata(val, msg) 409 | wraptest(type(val) == "userdata", msg, 410 | { reason=fmt("Expected type userdata but got %s", 411 | type(val)) }) 412 | end 413 | 414 | ---Test that val is not a userdata (light or heavy). 415 | function assert_not_userdata(val, msg) 416 | wraptest(type(val) ~= "userdata", msg, 417 | { reason=fmt("Expected type other than userdata but got %s", 418 | type(val)) }) 419 | end 420 | 421 | ---Test that a value has the expected metatable. 422 | function assert_metatable(exp, val, msg) 423 | local mt = getmetatable(val) 424 | wraptest(mt == exp, msg, 425 | { reason=fmt("Expected metatable %s but got %s", 426 | TS(exp), TS(mt)) }) 427 | end 428 | 429 | ---Test that a value does not have a given metatable. 430 | function assert_not_metatable(exp, val, msg) 431 | local mt = getmetatable(val) 432 | wraptest(mt ~= exp, msg, 433 | { reason=fmt("Expected metatable other than %s", 434 | TS(exp)) }) 435 | end 436 | 437 | ---Test that the function raises an error when called. 438 | function assert_error(f, msg) 439 | local ok, err = pcall(f) 440 | local got = ok or err 441 | wraptest(not ok, msg, 442 | { exp="an error", got=got, 443 | reason=fmt("Expected an error, got %s", TS(got)) }) 444 | end 445 | 446 | 447 | ---Run a test case with randomly instantiated arguments, 448 | -- running the test function f opt.count (default: 100) times. 449 | -- @param opt A table with options, or just a test name string.
450 | -- opt.count: how many random trials to perform
451 | -- opt.seed: Start the batch of trials with a specific seed
452 | -- opt.always: Always test these seeds (for regressions)
453 | -- opt.show_progress: Whether to print a . after every opt.tick trials.
454 | -- opt.seed_limit: Max seed to allow.
455 | -- opt.max_failures, max_errors, max_skips: Give up after X of each.
456 | -- @param f A test function, run as f(unpack(randomized_args(...))) 457 | -- @param ... the arg specification. For each argument, creates a 458 | -- random instance of that type.
459 | -- boolean: return true or false
460 | -- number n: returns 0 <= x < n, or -n <= x < n if negative. 461 | -- If n has a decimal component, so will the result.
462 | -- string: Specifiedd as "(len[,maxlen]) (pattern)".
463 | -- "10 %l" means 10 random lowercase letters.
464 | -- "10,30 [aeiou]" means between 10-30 vowels.
465 | -- function: Just call (as f()) and return result.
466 | -- table or userdata: Call v.__random() and return result.
467 | -- @usage 468 | function assert_random(opt, f, ...) 469 | -- Stub. Exported to the same namespace, but code appears below. 470 | end 471 | 472 | 473 | -- #################### 474 | -- # Module beginning # 475 | -- #################### 476 | 477 | ---Unit testing module, with extensions for random testing. 478 | module("lunatest") 479 | 480 | VERSION = "0.94" 481 | 482 | 483 | -- ######### 484 | -- # Hooks # 485 | -- ######### 486 | 487 | local dot_ct = 0 488 | local cols = 70 489 | 490 | local iow = io.write 491 | 492 | -- Print a char ([.fEs], etc.), wrapping at 70 columns. 493 | local function dot(c) 494 | c = c or "." 495 | io.write(c) 496 | dot_ct = dot_ct + 1 497 | if dot_ct > cols then 498 | io.write("\n ") 499 | dot_ct = 0 500 | end 501 | io.stdout:flush() 502 | end 503 | 504 | local function print_totals(r) 505 | local ps, fs = count(r.pass), count(r.fail) 506 | local ss, es = count(r.skip), count(r.err) 507 | if checked == 0 then return end 508 | local el, unit = r.t_post - r.t_pre, "s" 509 | if el < 1 then unit = "ms"; el = el * 1000 end 510 | local elapsed = fmt(" in %.2f %s", el, unit) 511 | local buf = {"\n---- Testing finished%s, ", 512 | "with %d assertion(s) ----\n", 513 | " %d passed, %d failed, ", 514 | "%d error(s), %d skipped."} 515 | printf(table.concat(buf), elapsed, checked, ps, fs, es, ss) 516 | end 517 | 518 | 519 | ---Default behavior. 520 | default_hooks = { 521 | begin = false, 522 | begin_suite = function(s_env, tests) 523 | iow(fmt("\n-- Starting suite %q, %d test(s)\n ", 524 | s_env.name, count(tests))) 525 | end, 526 | end_suite = false, 527 | pre_test = false, 528 | post_test = function(name, res) 529 | dot(res:tostring_char()) 530 | end, 531 | done = function(r) 532 | print_totals(r) 533 | for _,ts in ipairs{ r.fail, r.err, r.skip } do 534 | for name,res in pairs(ts) do 535 | printf("%s", res:tostring(name)) 536 | end 537 | end 538 | end, 539 | } 540 | 541 | 542 | ---Default verbose behavior. 543 | verbose_hooks = { 544 | begin = function(res, suites) 545 | local s_ct = count(suites) 546 | if s_ct > 0 then 547 | printf("Starting tests, %d suite(s)", s_ct) 548 | end 549 | end, 550 | begin_suite = function(s_env, tests) 551 | dot_ct = 0 552 | printf("-- Starting suite %q, %d test(s)", 553 | s_env.name, count(tests)) 554 | end, 555 | end_suite = 556 | function(s_env) 557 | local ps, fs = count(s_env.pass), count(s_env.fail) 558 | local ss, es = count(s_env.skip), count(s_env.err) 559 | dot_ct = 0 560 | printf(" Finished suite %q, +%d -%d E%d s%d", 561 | s_env.name, ps, fs, es, ss) 562 | end, 563 | pre_test = false, 564 | post_test = function(name, res) 565 | printf("%s", res:tostring(name)) 566 | dot_ct = 0 567 | end, 568 | done = function(r) print_totals(r) end 569 | } 570 | 571 | setmetatable(verbose_hooks, {__index = default_hooks }) 572 | 573 | 574 | -- ################ 575 | -- # Registration # 576 | -- ################ 577 | 578 | local suites = {} 579 | local failed_suites = {} 580 | 581 | ---Check if a function name should be considered a test key. 582 | -- Defaults to functions starting or ending with "test", with 583 | -- leading underscores allowed. 584 | function is_test_key(k) 585 | return type(k) == "string" and (k:match("^test.*") or k:match("test$")) 586 | end 587 | 588 | local function get_tests(mod) 589 | local ts = {} 590 | if type(mod) == "table" then 591 | for k,v in pairs(mod) do 592 | if is_test_key(k) and type(v) == "function" then 593 | ts[k] = v 594 | end 595 | end 596 | ts.setup = rawget(mod, "setup") 597 | ts.teardown = rawget(mod, "teardown") 598 | ts.ssetup = rawget(mod, "suite_setup") 599 | ts.steardown = rawget(mod, "suite_teardown") 600 | return ts 601 | end 602 | return {} 603 | end 604 | 605 | ---Add a file as a test suite. 606 | -- @param modname The module to load as a suite. The file is 607 | -- interpreted in the same manner as require "modname". 608 | -- Which functions are tests is determined by is_test_key(name). 609 | function suite(modname) 610 | local ok, err = pcall( 611 | function() 612 | local mod, r_err = require(modname) 613 | suites[modname] = get_tests(mod) 614 | end) 615 | if not ok then 616 | print(fmt(" * Error loading test suite %q:\n%s", 617 | modname, tostring(err))) 618 | failed_suites[#failed_suites+1] = modname 619 | end 620 | end 621 | 622 | 623 | -- ########### 624 | -- # Running # 625 | -- ########### 626 | 627 | local ok_types = { pass=true, fail=true, skip=true } 628 | 629 | local function err_handler(name) 630 | return function (e) 631 | if type(e) == "table" and e.type and ok_types[e.type()] then return e end 632 | local msg = fmt("ERROR in %s():\n\t%s", name, tostring(e)) 633 | msg = debug.traceback(msg, 3) 634 | return Error { msg=msg } 635 | end 636 | end 637 | 638 | 639 | local function run_test(name, test, suite, hooks, setup, teardown) 640 | local result 641 | if is_func(hooks.pre_test) then hooks.pre_test(name) end 642 | local t_pre, t_post, elapsed --timestamps. requires luasocket. 643 | local ok, err 644 | 645 | if is_func(setup) then 646 | ok, err = xpcall(function() setup(name) end, err_handler(name)) 647 | else 648 | ok = true 649 | end 650 | 651 | if ok then 652 | t_pre = now() 653 | ok, err = xpcall(test, err_handler(name)) 654 | t_post = now() 655 | elapsed = t_post - t_pre 656 | 657 | if is_func(teardown) then 658 | if ok then 659 | ok, err = xpcall(function() teardown(name, elapsed) end, 660 | err_handler(name)) 661 | else 662 | xpcall(function() teardown(name, elapsed) end, 663 | function(info) 664 | print "\n===============================================" 665 | local msg = fmt("ERROR in teardown handler: %s", info) 666 | print(msg) 667 | os.exit(1) 668 | end) 669 | end 670 | end 671 | end 672 | 673 | if ok then err = Pass() end 674 | result = err 675 | result.elapsed = elapsed 676 | 677 | -- TODO: log tests w/ no assertions? 678 | result:add(suite, name) 679 | 680 | if is_func(hooks.post_test) then hooks.post_test(name, result) end 681 | end 682 | 683 | 684 | local function cmd_line_switches(arg) 685 | arg = arg or {} 686 | local opts = {} 687 | for i=1,#arg do 688 | local v = arg[i] 689 | if v == "-v" or v == "--verbose" then opts.verbose=true 690 | elseif v == "-s" or v == "--suite" then 691 | opts.suite_pat = arg[i+1] 692 | elseif v == "-t" or v == "--test" then 693 | opts.test_pat = arg[i+1] 694 | end 695 | end 696 | return opts 697 | end 698 | 699 | 700 | local function failure_or_error_count(r) 701 | local t = 0 702 | for k,f in pairs(r.err) do 703 | t = t + 1 704 | end 705 | for k,f in pairs(r.fail) do 706 | if not f.no_exit then t = t + 1 end 707 | end 708 | return t 709 | end 710 | 711 | local function run_suite(hooks, opts, results, sname, tests) 712 | local ssetup, steardown = tests.ssetup, tests.steardown 713 | tests.ssetup, tests.steardown = nil, nil 714 | 715 | if not opts.suite_pat or sname:match(opts.suite_pat) then 716 | local run_suite = true 717 | local res = result_table(sname) 718 | 719 | if ssetup then 720 | local ok, err = pcall(ssetup) 721 | if not ok or (ok and err == false) then 722 | run_suite = false 723 | local msg = fmt("Error in %s's suite_setup: %s", sname, tostring(err)) 724 | failed_suites[#failed_suites+1] = sname 725 | results.err[sname] = Error{msg=msg} 726 | end 727 | end 728 | 729 | if run_suite and count(tests) > 0 then 730 | local setup, teardown = tests.setup, tests.teardown 731 | tests.setup, tests.teardown = nil, nil 732 | 733 | if hooks.begin_suite then hooks.begin_suite(res, tests) end 734 | res.tests = tests 735 | for name, test in pairs(tests) do 736 | if not opts.test_pat or name:match(opts.test_pat) then 737 | run_test(name, test, res, hooks, setup, teardown) 738 | end 739 | end 740 | if steardown then pcall(steardown) end 741 | if hooks.end_suite then hooks.end_suite(res) end 742 | combine_results(results, res) 743 | end 744 | end 745 | end 746 | 747 | ---Run all known test suites, with given configuration hooks. 748 | -- @param hooks Override the default hooks. 749 | -- @param opts Override command line arguments. 750 | -- @usage If no hooks are provided and arg[1] == "-v", the verbose_hooks will 751 | -- be used. opts is expected to be a table of command line arguments. 752 | function run(hooks, opts) 753 | -- also check the namespace it's run in 754 | local opts = opts and cmd_line_switches(opts) or cmd_line_switches(lt_arg) 755 | 756 | -- Make stdout line-buffered for better interactivity when the output is 757 | -- not going to the terminal, e.g. is piped to another program. 758 | --io.stdout:setvbuf("line") 759 | 760 | if hooks == true or opts.verbose then 761 | hooks = verbose_hooks 762 | else 763 | hooks = hooks or {} 764 | end 765 | 766 | setmetatable(hooks, {__index = default_hooks}) 767 | 768 | local results = result_table("main") 769 | results.t_pre = now() 770 | 771 | -- If it's all in one test file, check its environment, too. 772 | local env = getenv(3) 773 | if env then suites.main = get_tests(env) end 774 | 775 | if hooks.begin then hooks.begin(results, suites) end 776 | 777 | for sname,suite in pairs(suites) do 778 | run_suite(hooks, opts, results, sname, suite) 779 | end 780 | results.t_post = now() 781 | if hooks.done then hooks.done(results) end 782 | 783 | local failures = failure_or_error_count(results) 784 | return failures + #failed_suites 785 | end 786 | 787 | 788 | -- ######################## 789 | -- # Randomization basics # 790 | -- ######################## 791 | 792 | local _r 793 | if random then 794 | _r = random.new() 795 | end 796 | 797 | ---Set random seed. 798 | function set_seed(s) _r:seed(s) end 799 | 800 | ---Get a random value low <= x <= high. 801 | function random_int(low, high) 802 | if not high then high = low; low = 0 end 803 | return _r:value(low, high) 804 | end 805 | 806 | ---Get a random bool. 807 | function random_bool() return random_int(0, 1) == 1 end 808 | 809 | ---Get a random float low <= x < high. 810 | function random_float(low, high) 811 | return random_int(low, high - 1) + _r:value() 812 | end 813 | 814 | 815 | if not random then 816 | set_seed = math.randomseed 817 | random_bool = function() return math.random(0, 1) == 1 end 818 | random_float = function(l, h) 819 | return random_int(l, h - 1) + math.random() 820 | end 821 | random_int = function(l, h) 822 | if not h then h = l; l = 0 end 823 | return math.random(l, h) 824 | end 825 | end 826 | 827 | -- Lua_number's bits of precision. IEEE 754 doubles have 52. 828 | local function determine_accuracy() 829 | for i=1,128 do 830 | if 2^i == (2^i + 1) then return i - 1 end 831 | end 832 | return 128 --long long ints? 833 | end 834 | local bits_of_accuracy = determine_accuracy() 835 | 836 | 837 | -- ################## 838 | -- # Random strings # 839 | -- ################## 840 | 841 | 842 | -- For valid char classes, see Lua Reference Manual 5.1, p. 77 843 | -- or http://www.lua.org/manual/5.1/manual.html#5.4.1 . 844 | local function charclass(pat) 845 | local m = {} 846 | 847 | local match, char = string.match, string.char 848 | for i=0,255 do 849 | local c = char(i) 850 | if match(c, pat) then m[#m+1] = c end 851 | end 852 | 853 | return table.concat(m) 854 | end 855 | 856 | 857 | -- Return a (() -> random char) iterator from a pattern. 858 | local function parse_pattern(pattern) 859 | local cs = {} --charset 860 | local idx = 1 861 | local len = string.len(pattern) 862 | assert(len > 0, "Cannot generate pattern from empty string.") 863 | 864 | local function at_either_end() return #cs == 0 or #cs == len end 865 | local function slice(i) return string.sub(pattern, i, i) end 866 | 867 | while idx <= len do 868 | local c = slice(idx) 869 | 870 | if c == "-" then 871 | if at_either_end() then 872 | cs[#cs+1] = c --literal - at start or end 873 | else --range 874 | local low = string.byte(slice(idx-1)) + 1 875 | local high = string.byte(slice(idx+1)) 876 | assert(low < high, "Invalid character range: " .. pattern) 877 | for asc=low,high do 878 | cs[#cs+1] = string.char(asc) 879 | end 880 | idx = idx + 1 881 | end 882 | 883 | elseif c == "%" then 884 | local nextc = slice(idx + 1) 885 | cs[#cs+1] = charclass("%" .. nextc) 886 | idx = idx + 1 887 | 888 | else 889 | cs[#cs+1] = c 890 | end 891 | idx = idx + 1 892 | end 893 | 894 | cs = table.concat(cs) 895 | local len = string.len(cs) 896 | assert(len > 0, "Empty charset") 897 | 898 | return function() 899 | local idx = random_int(1, len) 900 | return string.sub(cs, idx, idx) 901 | end 902 | end 903 | 904 | 905 | -- Read a random string spec, return a config table. 906 | local function parse_randstring(s) 907 | local low, high, rest = string.match(s, "([0-9]+),?([0-9]*) (.*)") 908 | if low then --any match 909 | if high == "" then high = low end 910 | return { low = tonumber(low), 911 | high = tonumber(high), 912 | gen = parse_pattern(rest) } 913 | else 914 | local err = "Invalid random string spec: " .. s 915 | error(err, 2) 916 | end 917 | end 918 | 919 | 920 | -- Generate a random string. 921 | -- @usage e.g. "20 listoftwentycharstogenerate" or "10,20 %l". 922 | function random_string(spec) 923 | local info = parse_randstring(spec) 924 | local ct, diff 925 | diff = info.high - info.low 926 | if diff == 0 then ct = info.low else 927 | ct = random_int(diff) + info.low 928 | end 929 | 930 | local acc = {} 931 | for i=1,ct do 932 | acc[i] = info.gen(self) 933 | end 934 | local res = table.concat(acc) 935 | assert(res:len() == ct, "Bad string gen") 936 | return res 937 | end 938 | 939 | 940 | -- ######################### 941 | -- # General random values # 942 | -- ######################### 943 | 944 | -- Generate a random number, according to arg. 945 | local function gen_number(arg) 946 | arg = arg or math.huge 947 | local signed = (arg < 0) 948 | local float 949 | if signed then float = (math.ceil(arg) ~= arg) else 950 | float = (math.floor(arg) ~= arg) 951 | end 952 | 953 | local f = float and random_float or random_int 954 | if signed then 955 | return f(arg, -arg) 956 | else 957 | return f(0, arg) 958 | end 959 | end 960 | 961 | 962 | -- Create an arbitrary instance of a value. 963 | local function generate_arbitrary(arg) 964 | local t = type(arg) 965 | if t == "number" then 966 | return gen_number(arg) 967 | elseif t == "function" then 968 | return arg(gen_number()) -- assume f(number) -> val 969 | elseif t == "string" then 970 | return random_string(arg) 971 | elseif t == "table" or t == "userdata" then 972 | assert(arg.__random, t .. " has no __random method") 973 | -- assume arg.__random(number) -> val 974 | return arg.__random(gen_number()) 975 | elseif t == "boolean" then 976 | return random_bool() 977 | else 978 | error("Cannot randomly generate values of type " .. t .. ".") 979 | end 980 | end 981 | 982 | 983 | local random_test_defaults = { 984 | count = 100, 985 | max_failures = 10, 986 | max_errors = 5, 987 | max_skips = 50, 988 | random_bound = 2^bits_of_accuracy, 989 | seed_limit = math.min(1e13, 2^bits_of_accuracy), 990 | always = {}, 991 | seed = nil, 992 | show_progress = true 993 | } 994 | 995 | 996 | local function random_args(args) 997 | local as = {} 998 | for i=1,#args do 999 | as[i] = generate_arbitrary(args[i]) 1000 | end 1001 | return as 1002 | end 1003 | 1004 | 1005 | local function new_seed(limit) 1006 | limit = limit or 1e13 1007 | return random_int(0, limit) 1008 | end 1009 | 1010 | 1011 | local function get_seeds_and_args(t) 1012 | local ss = {} 1013 | for _,r in ipairs(t) do 1014 | if r.seed then 1015 | ss[#ss+1] = fmt("%s %s\n Seed: %s", 1016 | r.reason or "", r.msg and ("\n " .. r.msg) or "", r.seed) 1017 | end 1018 | if r.args then 1019 | for i,arg in ipairs(r.args) do 1020 | ss[#ss+1] = " * " .. arg 1021 | end 1022 | end 1023 | ss[#ss+1] = "" 1024 | end 1025 | return ss 1026 | end 1027 | 1028 | 1029 | local function run_randtest(seed, f, args, r, limit) 1030 | local try_ct = 0 1031 | while r.tried[seed] and try_ct < 50 do 1032 | seed = new_seed(limit) 1033 | try_ct = try_ct + 1 1034 | end 1035 | if try_ct >= 50 then 1036 | error(Fail { reason = "Exhausted all seeds" }) 1037 | end 1038 | set_seed(seed) 1039 | r.tried[seed] = true 1040 | 1041 | local result 1042 | local r_args = random_args(args) 1043 | local ok, err = pcall(function() f(unpack(r_args)) end) 1044 | if ok then 1045 | result = Pass() 1046 | result.seed = seed 1047 | r.ps[#r.ps+1] = result 1048 | else 1049 | -- So errors in the suite itself get through... 1050 | if type(err) == "string" then error(err) end 1051 | result = err 1052 | result.seed = seed 1053 | local rt = result:type() 1054 | if rt == "pass" then r.ps[#r.ps+1] = result 1055 | elseif rt == "fail" then r.fs[#r.fs+1] = result 1056 | elseif rt == "error" then r.es[#r.es+1] = result 1057 | elseif rt == "skip" then r.ss[#r.ss+1] = result 1058 | else error("unmatched") 1059 | end 1060 | end 1061 | 1062 | seed = new_seed(limit) 1063 | r.ts = r.ts + 1 1064 | local str_args = {} 1065 | -- Convert args to strs (for display) and add to result. 1066 | for i,v in ipairs(r_args) do 1067 | str_args[i] = tostring(v) 1068 | end 1069 | result.args = str_args 1070 | return seed 1071 | end 1072 | 1073 | 1074 | local function report_trial(r, opt) 1075 | if #r.es > 0 then 1076 | local seeds = get_seeds_and_args(r.es) 1077 | error(Fail { reason = fmt("%d tests, %d error(s).\n %s", 1078 | r.ts, #r.es, 1079 | table.concat(seeds, "\n ")), 1080 | seeds = seeds}) 1081 | elseif #r.fs > 0 then 1082 | local seeds = get_seeds_and_args(r.fs) 1083 | error(Fail { reason = fmt("%d tests, %d failure(s).\n %s", 1084 | r.ts, #r.fs, 1085 | table.concat(seeds, "\n ")), 1086 | seeds = seeds}) 1087 | elseif #r.ss >= opt.max_skips then 1088 | error(Fail { reason = fmt("Too many cases skipped.")}) 1089 | else 1090 | error(Pass { reason = fmt(": %d cases passed.", #r.ps) }) 1091 | end 1092 | end 1093 | 1094 | 1095 | local function assert_random(opt, f, ...) 1096 | local args = { ... } 1097 | if type(opt) == "string" then 1098 | opt = { name=opt } 1099 | elseif type(opt) == "function" then 1100 | table.insert(args, 1, f) 1101 | f = opt 1102 | opt = {} 1103 | end 1104 | 1105 | setmetatable(opt, { __index=random_test_defaults }) 1106 | 1107 | local seed = opt.seed or os.time() 1108 | local r = { ps={}, fs={}, es={}, ss={}, ts=0, tried={} } 1109 | 1110 | -- Run these seeds every time, for easy regression testing. 1111 | for _,s in ipairs(opt.always) do 1112 | run_randtest(s, f, args, r, opt.seed_limit) 1113 | end 1114 | 1115 | set_seed(seed) 1116 | local tick = opt.tick or opt.count / 10 1117 | 1118 | for i=1,opt.count do 1119 | seed = run_randtest(seed, f, args, r, opt.seed_limit) 1120 | if #r.ss >= opt.max_skips or 1121 | #r.fs >= opt.max_failures or 1122 | #r.es >= opt.max_errors then break 1123 | end 1124 | if opt.show_progress and i % tick == 0 then 1125 | dot(".") 1126 | end 1127 | end 1128 | local overall_status = (passed == count and "PASS" or "FAIL") 1129 | 1130 | report_trial(r, opt) 1131 | end 1132 | 1133 | 1134 | -- Put it in the same namespace as the other assert_ functions. 1135 | _importing_env.assert_random = assert_random 1136 | 1137 | --------------------------------------------------------------------------------