From 04034ef5e29a8f0ea61f027b5e4033317807de26 Mon Sep 17 00:00:00 2001 From: Jaidyn Ann Date: Sat, 19 Sep 2020 01:53:57 -0500 Subject: [PATCH] Use middleclass for OOP --- art/sprites/dad | Bin 898 -> 0 bytes art/sprites/dad-1.png | Bin 549 -> 0 bytes art/sprites/dad-2.png | Bin 878 -> 0 bytes art/sprites/dad-3.png | Bin 543 -> 0 bytes lib/baton/baton.lua | 368 ------------------ lib/baton/license.md | 21 - lib/baton/main.lua | 100 ----- lib/baton/readme.md | 142 ------- lib/center/LICENSE | 21 - lib/center/README.md | 94 ----- lib/center/center.lua | 119 ------ lib/center/screenshots/center.png | Bin 21309 -> 0 bytes lib/center/screenshots/img1.png | Bin 3034 -> 0 bytes lib/center/screenshots/img2.png | Bin 1827 -> 0 bytes lib/center/screenshots/img3.png | Bin 2322 -> 0 bytes lib/middleclass/.travis.yml | 36 ++ lib/middleclass/CHANGELOG.md | 55 +++ lib/middleclass/MIT-LICENSE.txt | 20 + lib/middleclass/README.md | 80 ++++ lib/middleclass/UPDATING.md | 69 ++++ lib/middleclass/init.lua | 183 +++++++++ lib/middleclass/performance/run.lua | 43 ++ lib/middleclass/performance/time.lua | 13 + .../rockspecs/middleclass-3.0-0.rockspec | 21 + .../rockspecs/middleclass-3.1-0.rockspec | 21 + .../rockspecs/middleclass-3.2-0.rockspec | 21 + .../rockspecs/middleclass-4.0-0.rockspec | 21 + .../rockspecs/middleclass-4.1-0.rockspec | 21 + .../rockspecs/middleclass-4.1.1-0.rockspec | 21 + lib/middleclass/spec/class_spec.lua | 28 ++ lib/middleclass/spec/classes_spec.lua | 138 +++++++ lib/middleclass/spec/default_methods_spec.lua | 236 +++++++++++ lib/middleclass/spec/instances_spec.lua | 65 ++++ lib/middleclass/spec/metamethods_lua_5_2.lua | 85 ++++ lib/middleclass/spec/metamethods_lua_5_3.lua | 106 +++++ lib/middleclass/spec/metamethods_spec.lua | 317 +++++++++++++++ lib/middleclass/spec/mixins_spec.lua | 53 +++ main.lua | 295 +++++++------- 38 files changed, 1807 insertions(+), 1006 deletions(-) delete mode 100644 art/sprites/dad delete mode 100644 art/sprites/dad-1.png delete mode 100644 art/sprites/dad-2.png delete mode 100644 art/sprites/dad-3.png delete mode 100644 lib/baton/baton.lua delete mode 100644 lib/baton/license.md delete mode 100644 lib/baton/main.lua delete mode 100644 lib/baton/readme.md delete mode 100644 lib/center/LICENSE delete mode 100644 lib/center/README.md delete mode 100644 lib/center/center.lua delete mode 100644 lib/center/screenshots/center.png delete mode 100644 lib/center/screenshots/img1.png delete mode 100644 lib/center/screenshots/img2.png delete mode 100644 lib/center/screenshots/img3.png create mode 100644 lib/middleclass/.travis.yml create mode 100644 lib/middleclass/CHANGELOG.md create mode 100644 lib/middleclass/MIT-LICENSE.txt create mode 100644 lib/middleclass/README.md create mode 100644 lib/middleclass/UPDATING.md create mode 100644 lib/middleclass/init.lua create mode 100644 lib/middleclass/performance/run.lua create mode 100644 lib/middleclass/performance/time.lua create mode 100644 lib/middleclass/rockspecs/middleclass-3.0-0.rockspec create mode 100644 lib/middleclass/rockspecs/middleclass-3.1-0.rockspec create mode 100644 lib/middleclass/rockspecs/middleclass-3.2-0.rockspec create mode 100644 lib/middleclass/rockspecs/middleclass-4.0-0.rockspec create mode 100644 lib/middleclass/rockspecs/middleclass-4.1-0.rockspec create mode 100644 lib/middleclass/rockspecs/middleclass-4.1.1-0.rockspec create mode 100644 lib/middleclass/spec/class_spec.lua create mode 100644 lib/middleclass/spec/classes_spec.lua create mode 100644 lib/middleclass/spec/default_methods_spec.lua create mode 100644 lib/middleclass/spec/instances_spec.lua create mode 100644 lib/middleclass/spec/metamethods_lua_5_2.lua create mode 100644 lib/middleclass/spec/metamethods_lua_5_3.lua create mode 100644 lib/middleclass/spec/metamethods_spec.lua create mode 100644 lib/middleclass/spec/mixins_spec.lua diff --git a/art/sprites/dad b/art/sprites/dad deleted file mode 100644 index 5a51fe545bcd818ca7bf00dd26ca90778943cb97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 898 zcmeAS@N?(olHy`uVBq!ia0vp^3xHUGg&9bu2j(3EQY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP(mWWC&cwX!+&FA+gDlgkZLHbJzJU{W!ht`zHqbJDI;aB@TZ%(lFza%K=|JY7Cz4uUHm}-wE^4ix#nTTeDMEv+7`Y%Q@#u#>Y7#CDQvP zKNlW7X>P%lT<{^w?bI`N?u_Y-;V-IAyuZj5wC5_j@d9R((+;_EHFnc?e2e*bZA(Gb z*{gL6BH3OoQj71)m3VOT&-|0K_Q$eK_`C7qAMZH!`;Tt^5jtq|J9UBao>RxCZaDhc z(pF~e&)ca7WY_0q%#y!8KWfjPk`K??bLJn_sGFW`H%&3_>=VwDcH8}ao@=k{xW2*m zL2*_;V^I3nwzSw7)d;(phh%gTXO^{8%s(3#X6$s&eTBJ}xZJ%Fk8sFG$UEPs~kqEGhvR zq{zZss19_AD##@%K>Mwd!OmbXGB7gGHL%n*vNSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgAsXNl;`*QAzp=4#adEMa zk55`!T0=v_|NsBDl`|g!Dqu|Vc6VV*OxsZlt(0Jna-4-Y$keujO z2BZ%Fu>%k<0Ahv*Dg2E&IT|5oC-NLsRXPIUT+~V-u;5Y3~fN;Rv`SJmOTTWg&#gNg$&cc`6+DjOpVk9<`D=E>b`%>q=ZBN|N()b5rw57@Uhz6H8K4 z6p|_xeDgE2eN%IC5;GJuGD=Dctn~GBfkL@pA-(*fbWJXw%;eO(VxaQUyp+@;1!ren zr%K&mM_pq*11^{R^1Ph<#1w^+jLc$%#Jm)R-29?cg|wplTm}Dv)I9gZ+*HS+5|BZP zEWCy4K$ob3T#^E`-zpjG3tv zg+4Niay1*n75@MK|CDpf0-yrMByV>Yw#2j@wLlJgiKnkC`x8!K1_O;Je&20@LITN& zeq})V01!I>F$gHE*4=O)sC9#Z@AY%J`DSOnN_ML9xp#&<;!{+S)wr6VIOVj!gq1$ls>M64f^NGi@>sXz#n>>I zGkRDBsdMaouq~DK(cBzv9Asu6 z;jo)$htqZEy8f^UkJj4FV3b`D$(Hr`!RMVv);{mrzO40g^}27&XN>~HO@9{U9A-HG zEPj${{cawG-^q)AUf;vJ@95^As!f&OPCKMmOzxLX?0#Nc6}9H)?bHLZ>+>>Z$#0(@ zwdYUCr)TXs^N(uOP0zNQrWkkj3Fk?>?S4PcwO4jr-(dToIIEvADE(_&TI?N_2)mhw zWONc|^0idVKO1Of>}2P*!dy#oyVJXPv-SgEV;wi`cA&VS-#@Sy#3Pa z!+z}l$Qrml@S|GRM^9iJ)|I$MlqBcp=BDPAFgO>bCYGe8C?r)X_~vJ3`=;jPBxWdR zWR#Q?Sn2EM0)=wHLVEc{>6%z zi75&t8JWcjiFqjsx%ow@3TZ|8xeERTsd?^+xv7ptB_M+oS$GT8fi6)6xg-T>zg05W z84N}SMh3bDmb!)(Ax4H)rj}L)7TN{|Rt5%3?5m%kXvob^$xN$6(_ms^Wn==;kTU(3 QB2WW^r>mdKI;Vst0L;Qu)c^nh diff --git a/art/sprites/dad-3.png b/art/sprites/dad-3.png deleted file mode 100644 index 4d54447adf52c93333c13125bfd37c136f019087..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 543 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgAr#;f;`*QAf1sJj#3YqM zADPB*h5!Hm3&%cA0m?EadAqx?{>Xm`vWC6H)7O>#38yfFfyNWR@3uf8f#gKLG9Y~b zh#i1<0T3(P=F;~B(kY%Ujv*T7w_ZFgctC-JCE$faCohAULWscef7xE^UaPXJSJ$-5 z+PVC1?c{2>vWjCR!yCgz3QH6$nwBJ=%Hfe_0D-R|tV{*78mUnvd zW7{3)dEd1;wDQj{n8AK+6Az2ngblSo`|3(uBTACU8hRjU`JhJ zJp(S6{PMh<{KOQ6l8nq^g~YrRh1~q2RE4yn{9Fb9g48_s#N1TJq7slniY&Z^>OhyM zf?SdUwBITj>QO Zu`)7&Xh@m 'button', 'a' -local function parseSource(source) - return source:match '(.+):(.+)' -end - --- splits an axis value into axis and direction --- example: 'leftx-' -> 'leftx', '-' -local function parseAxis(value) - return value:match '(.+)([%+%-])' -end - --- splits a joystick hat value into hat number and direction --- example: '2rd' -> '2', 'rd' -local function parseHat(value) - return value:match '(%d)(.+)' -end - ---[[ - -- source functions -- - - each source function checks the state of one type of input - and returns a value from 0 to 1. for binary controls, such - as keyboard keys and gamepad buttons, they return 1 if the - input is held down and 0 if not. for analog controls, such - as "leftx+" (the left analog stick held to the right), they - return a number from 0 to 1. - - source functions are split into keyboard/mouse functions - and joystick/gamepad functions. baton treats these two - categories slightly differently. -]] - -local sourceFunction = {keyboardMouse = {}, joystick = {}} - --- checks whether a keyboard key is down or not -function sourceFunction.keyboardMouse.key(key) - return love.keyboard.isDown(key) and 1 or 0 -end - --- checks whether a keyboard key is down or not, --- but it takes a scancode as an input -function sourceFunction.keyboardMouse.sc(sc) - return love.keyboard.isScancodeDown(sc) and 1 or 0 -end - --- checks whether a mouse buttons is down or not. --- note that baton doesn't detect mouse movement, just the buttons -function sourceFunction.keyboardMouse.mouse(button) - return love.mouse.isDown(tonumber(button)) and 1 or 0 -end - --- checks the position of a joystick axis -function sourceFunction.joystick.axis(joystick, value) - local axis, direction = parseAxis(value) - -- "a and b or c" is ok here because b will never be boolean - value = tonumber(axis) and joystick:getAxis(tonumber(axis)) - or joystick:getGamepadAxis(axis) - if direction == '-' then value = -value end - return value > 0 and value or 0 -end - --- checks whether a joystick button is held down or not --- can take a number or a GamepadButton string -function sourceFunction.joystick.button(joystick, button) - -- i'm intentionally not using the "a and b or c" idiom here - -- because joystick.isDown returns a boolean - if tonumber(button) then - return joystick:isDown(tonumber(button)) and 1 or 0 - else - return joystick:isGamepadDown(button) and 1 or 0 - end -end - --- checks the direction of a joystick hat -function sourceFunction.joystick.hat(joystick, value) - local hat, direction = parseHat(value) - return joystick:getHat(hat) == direction and 1 or 0 -end - ---[[ - -- player class -- - - the player object takes a configuration table and handles input - accordingly. it's called a "player" because it makes sense to use - multiple of these for each player in a multiplayer game, but - you can use separate player objects to organize inputs - however you want. -]] - -local Player = {} -Player.__index = Player - --- internal functions -- - --- sets the player's config to a user-defined config table --- and sets some defaults if they're not already defined -function Player:_loadConfig(config) - if not config then - error('No config table provided', 4) - end - if not config.controls then - error('No controls specified', 4) - end - config.pairs = config.pairs or {} - config.deadzone = config.deadzone or .5 - config.squareDeadzone = config.squareDeadzone or false - self.config = config -end - --- initializes a control object for each control defined in the config -function Player:_initControls() - self._controls = {} - for controlName, sources in pairs(self.config.controls) do - self._controls[controlName] = { - sources = sources, - rawValue = 0, - value = 0, - down = false, - downPrevious = false, - pressed = false, - released = false, - } - end -end - --- initializes an axis pair object for each axis pair defined in the config -function Player:_initPairs() - self._pairs = {} - for pairName, controls in pairs(self.config.pairs) do - self._pairs[pairName] = { - controls = controls, - rawX = 0, - rawY = 0, - x = 0, - y = 0, - down = false, - downPrevious = false, - pressed = false, - released = false, - } - end -end - -function Player:_init(config) - self:_loadConfig(config) - self:_initControls() - self:_initPairs() - self._activeDevice = 'none' -end - ---[[ - detects the active device (keyboard/mouse or joystick). - if the keyboard or mouse is currently being used, joystick - inputs will be ignored. this is to prevent slight axis movements - from adding errant inputs when someone's using the keyboard. - - the active device is saved to player._activeDevice, which is then - used throughout the rest of the update loop to check only - keyboard or joystick inputs. -]] -function Player:_setActiveDevice() - for _, control in pairs(self._controls) do - for _, source in ipairs(control.sources) do - local type, value = parseSource(source) - if sourceFunction.keyboardMouse[type] then - if sourceFunction.keyboardMouse[type](value) > self.config.deadzone then - self._activeDevice = 'kbm' - return - end - elseif self.config.joystick and sourceFunction.joystick[type] then - if sourceFunction.joystick[type](self.config.joystick, value) > self.config.deadzone then - self._activeDevice = 'joy' - end - end - end - end -end - ---[[ - gets the value of a control by running the appropriate source functions - for all of its sources. does not apply deadzone. -]] -function Player:_getControlRawValue(control) - local rawValue = 0 - for _, source in ipairs(control.sources) do - local type, value = parseSource(source) - if sourceFunction.keyboardMouse[type] and self._activeDevice == 'kbm' then - if sourceFunction.keyboardMouse[type](value) == 1 then - return 1 - end - elseif sourceFunction.joystick[type] and self._activeDevice == 'joy' then - rawValue = rawValue + sourceFunction.joystick[type](self.config.joystick, value) - if rawValue >= 1 then - return 1 - end - end - end - return rawValue -end - ---[[ - updates each control in a player. saves the value with and without deadzone - and the down/pressed/released state. -]] -function Player:_updateControls() - for _, control in pairs(self._controls) do - control.rawValue = self:_getControlRawValue(control) - control.value = control.rawValue >= self.config.deadzone and control.rawValue or 0 - control.downPrevious = control.down - control.down = control.value > 0 - control.pressed = control.down and not control.downPrevious - control.released = control.downPrevious and not control.down - end -end - ---[[ - updates each axis pair in a player. saves the value with and without deadzone - and the down/pressed/released state. -]] -function Player:_updatePairs() - for _, pair in pairs(self._pairs) do - -- get raw x and y - local l = self._controls[pair.controls[1]].rawValue - local r = self._controls[pair.controls[2]].rawValue - local u = self._controls[pair.controls[3]].rawValue - local d = self._controls[pair.controls[4]].rawValue - pair.rawX, pair.rawY = r - l, d - u - - -- limit to 1 - local len = math.sqrt(pair.rawX^2 + pair.rawY^2) - if len > 1 then - pair.rawX, pair.rawY = pair.rawX / len, pair.rawY / len - end - - -- deadzone - if self.config.squareDeadzone then - pair.x = math.abs(pair.rawX) > self.config.deadzone and pair.rawX or 0 - pair.y = math.abs(pair.rawY) > self.config.deadzone and pair.rawY or 0 - else - pair.x = len > self.config.deadzone and pair.rawX or 0 - pair.y = len > self.config.deadzone and pair.rawY or 0 - end - - -- down/pressed/released - pair.downPrevious = pair.down - pair.down = pair.x ~= 0 or pair.y ~= 0 - pair.pressed = pair.down and not pair.downPrevious - pair.released = pair.downPrevious and not pair.down - end -end - --- public API -- - --- checks for changes in inputs -function Player:update() - self:_setActiveDevice() - self:_updateControls() - self:_updatePairs() -end - --- gets the value of a control or axis pair without deadzone applied -function Player:getRaw(name) - if self._pairs[name] then - return self._pairs[name].rawX, self._pairs[name].rawY - elseif self._controls[name] then - return self._controls[name].rawValue - else - error('No control with name "' .. name .. '" defined', 3) - end -end - --- gets the value of a control or axis pair with deadzone applied -function Player:get(name) - if self._pairs[name] then - return self._pairs[name].x, self._pairs[name].y - elseif self._controls[name] then - return self._controls[name].value - else - error('No control with name "' .. name .. '" defined', 3) - end -end - --- gets whether a control or axis pair is "held down" -function Player:down(name) - if self._pairs[name] then - return self._pairs[name].down - elseif self._controls[name] then - return self._controls[name].down - else - error('No control with name "' .. name .. '" defined', 3) - end -end - --- gets whether a control or axis pair was pressed this frame -function Player:pressed(name) - if self._pairs[name] then - return self._pairs[name].pressed - elseif self._controls[name] then - return self._controls[name].pressed - else - error('No control with name "' .. name .. '" defined', 3) - end -end - --- gets whether a control or axis pair was released this frame -function Player:released(name) - if self._pairs[name] then - return self._pairs[name].released - elseif self._controls[name] then - return self._controls[name].released - else - error('No control with name "' .. name .. '" defined', 3) - end -end - ---[[ - gets the currently active device (either "kbm", "joy", or "none"). - this is useful for displaying instructional text. you may have - a menu that says "press ENTER to confirm" or "press A to confirm" - depending on whether the player is using their keyboard or gamepad. - this function allows you to detect which they used most recently. -]] -function Player:getActiveDevice() - return self._activeDevice -end - --- main functions -- - --- creates a new player with the user-provided config table -function baton.new(config) - local player = setmetatable({}, Player) - player:_init(config) - return player -end - -return baton diff --git a/lib/baton/license.md b/lib/baton/license.md deleted file mode 100644 index 1a39666..0000000 --- a/lib/baton/license.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Andrew Minnich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/baton/main.lua b/lib/baton/main.lua deleted file mode 100644 index 1fc1297..0000000 --- a/lib/baton/main.lua +++ /dev/null @@ -1,100 +0,0 @@ -local baton = require 'baton' - -local player = baton.new { - controls = { - left = {'key:left', 'axis:leftx-', 'button:dpleft'}, - right = {'key:right', 'axis:leftx+', 'button:dpright'}, - up = {'key:up', 'axis:lefty-', 'button:dpup'}, - down = {'key:down', 'axis:lefty+', 'button:dpdown'}, - action = {'key:x', 'button:a', 'mouse:1'}, - }, - pairs = { - move = {'left', 'right', 'up', 'down'} - }, - joystick = love.joystick.getJoysticks()[1], - deadzone = .33, -} - -local pairDisplayAlpha = 0 -local pairDisplayTargetAlpha = 0 -local buttonDisplayAlpha = 0 -local buttonDisplayTargetAlpha = 0 - -local updates = 0 -local updateTime = 0 - -function love.update(dt) - local time = love.timer.getTime() - - player:update() - - pairDisplayTargetAlpha = player:pressed 'move' and 1 - or player:released 'move' and 1 - or player:down 'move' and .5 - or 0 - if pairDisplayAlpha > pairDisplayTargetAlpha then - pairDisplayAlpha = pairDisplayAlpha - 4 * dt - end - if pairDisplayAlpha < pairDisplayTargetAlpha then - pairDisplayAlpha = pairDisplayTargetAlpha - end - - buttonDisplayTargetAlpha = player:pressed 'action' and 1 - or player:released 'action' and 1 - or player:down 'action' and .5 - or 0 - if buttonDisplayAlpha > buttonDisplayTargetAlpha then - buttonDisplayAlpha = buttonDisplayAlpha - 4 * dt - end - if buttonDisplayAlpha < buttonDisplayTargetAlpha then - buttonDisplayAlpha = buttonDisplayTargetAlpha - end - - updateTime = updateTime + (love.timer.getTime() - time) - updates = updates + 1 -end - -function love.keypressed(key) - if key == 'escape' then - love.event.quit() - end -end - -local pairDisplayRadius = 128 - -function love.draw() - love.graphics.setColor(1, 1, 1) - love.graphics.print('Current active device: ' .. tostring(player:getActiveDevice())) - love.graphics.print('Average update time (us): ' .. math.floor(updateTime/updates*1000000), 0, 16) - love.graphics.print('Memory usage (kb): ' .. math.floor(collectgarbage 'count'), 0, 32) - - love.graphics.push() - love.graphics.translate(400, 300) - - love.graphics.setColor(.25, .25, .25, pairDisplayAlpha) - love.graphics.circle('fill', 0, 0, pairDisplayRadius) - - love.graphics.setColor(1, 1, 1) - love.graphics.circle('line', 0, 0, pairDisplayRadius) - - local r = pairDisplayRadius * player.config.deadzone - if player.config.squareDeadzone then - love.graphics.rectangle('line', -r, -r, r*2, r*2) - else - love.graphics.circle('line', 0, 0, r) - end - - love.graphics.setColor(.5, .5, .5) - local x, y = player:getRaw 'move' - love.graphics.circle('fill', x*pairDisplayRadius, y*pairDisplayRadius, 4) - love.graphics.setColor(1, 1, 1) - x, y = player:get 'move' - love.graphics.circle('fill', x*pairDisplayRadius, y*pairDisplayRadius, 4) - - love.graphics.setColor(1, 1, 1) - love.graphics.rectangle('line', -50, 150, 100, 100) - love.graphics.setColor(1, 1, 1, buttonDisplayAlpha) - love.graphics.rectangle('fill', -50, 150, 100, 100) - - love.graphics.pop() -end diff --git a/lib/baton/readme.md b/lib/baton/readme.md deleted file mode 100644 index 563671c..0000000 --- a/lib/baton/readme.md +++ /dev/null @@ -1,142 +0,0 @@ -# Baton -**Baton** is an input library for LÖVE that bridges the gap between keyboard and joystick controls and allows you to easily define and change controls on the fly. You can create multiple independent input managers, which you can use for multiplayer games or other organizational purposes. - -```lua -local baton = require 'baton' - -local input = baton.new { - controls = { - left = {'key:left', 'key:a', 'axis:leftx-', 'button:dpleft'}, - right = {'key:right', 'key:d', 'axis:leftx+', 'button:dpright'}, - up = {'key:up', 'key:w', 'axis:lefty-', 'button:dpup'}, - down = {'key:down', 'key:s', 'axis:lefty+', 'button:dpdown'}, - action = {'key:x', 'button:a'}, - }, - pairs = { - move = {'left', 'right', 'up', 'down'} - }, - joystick = love.joystick.getJoysticks()[1], -} - -function love.update(dt) - input:update() - - local x, y = input:get 'move' - playerShip:move(x*100, y*100) - if input:pressed 'action' then - playerShip:shoot() - end -end -``` - -## Installation -To use Baton, place `baton.lua` in your project, and then add this code to your `main.lua`: -```lua -baton = require 'baton' -- if your baton.lua is in the root directory -baton = require 'path.to.baton' -- if it's in subfolders -``` - -## Usage - -### Defining controls -Controls are defined using a table. Each key should be the name of a control, and each value should be another table. This table contains strings defining what sources should be mapped to the control. For example, this table -```lua -controls = { - left = {'key:left', 'key:a', 'axis:leftx-'} - shoot = {'key:x', 'button:a'} -} -``` -will create a control called "left" that responds to the left arrow key, the A key, and pushing the left analog stick on the controller to the left, and a control called "shoot" that responds to the X key on the keyboard and the A button on the gamepad. - -Sources are strings with the following format: -```lua -'[input type]:[input source]' -``` -Here are the different input types and the sources that can be associated with them: - -| Type | Description | Source | -| --------| -----------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `key` | A keyboard key. | Any LÖVE [KeyConstant](http://love2d.org/wiki/KeyConstant) | -| `sc` | A scancode. | Any LÖVE [KeyConstant](http://love2d.org/wiki/KeyConstant) | -| `mouse` | A mouse button. | A number representing a mouse button (see [love.mouse.isDown](https://love2d.org/wiki/love.mouse.isDown)) | -| `axis` | A joystick or gamepad axis. | Either a number representing a joystick axis or a LÖVE [GamepadAxis](http://love2d.org/wiki/GamepadAxis). Add a '+' or '-' on the end to denote the direction to detect.| -| `button`| A joystick or gamepad button.| Either a number representing a joystick button or a LÖVE [GamepadButton](http://love2d.org/wiki/GamepadButton) | -| `hat` | A joystick hat. | A number representing a joystick hat and a [JoystickHat](https://love2d.org/wiki/JoystickHat). For example '1r' corresponds to the 1st hat pushed right. | - -### Defining axis pairs -Baton allows you to define **axis pairs**, which group four controls under a single name. This is perfect for analog sticks, arrow keys, etc., as it allows you to get x and y components quickly. Each pair is defined by a table with the names of the four controls (in the order left, right, up, down). -```lua -pairs = { - move = {'moveLeft', 'moveRight', 'moveUp', 'moveDown'}, - aim = {'aimLeft', 'aimRight', 'aimUp', 'aimDown'}, -} -``` - -### Players -**Players** are the objects that monitor and manage inputs. - -#### Creating players -To create a player, use `baton.new`: -```lua -player = baton.new(config) -``` -`config` is a table containing the following values: -- `controls` - a table of controls -- `pairs` - a table of axis pairs (optional) -- `joystick` - a LÖVE joystick (returned from `love.joystick.getJoysticks`). The `joystick` argument is optional; if it's not specified, or if the joystick becomes unavailable later, the player object will just ignore controller inputs. -- `deadzone` - a number from 0-1 representing the minimum value axes have to cross to be detected (optional) -- `squareDeadzone` (bool) - whether to use square deadzones for axis pairs or not (optional) - -#### Updating players -You should update each player each frame by calling this function: -```lua -player:update() -``` - -#### Getting the value of controls -To get the value of a control, use: -```lua -value = player:get(control) -``` -For example, for the controls defined above, we could get the value of the "left" control by doing -```lua -left = player:get 'left' -``` -`player:get` always returns a number between 0 and 1, and as such, it is most applicable to controls that act as axes, such as movement controls. To get the value of a control without applying the deadzone, use `player:getRaw`. - -#### Getting the value of axis pairs -`player.get` can also get the x and y components of an axis pair. -```lua -x, y = player:get(pair) -``` -In this case, `x` and `y` are numbers between -1 and 1. The length of the vector x, y is capped to 1. `player.getRaw` will return the value of axis pairs without deadzone applied. - -#### Getting down, pressed, and released states -To see whether a control is currently "held down", use: -```lua -down = player:down(control) -``` -`player:down` returns `true` if the value of the control is greater than the deadzone, and `false` if not. - -```lua -pressed = player:pressed(control) -``` -`player:pressed` return `true` if the control was pressed this `frame`, and false if not. - -```lua -released = player:released(control) -``` -`player:released` return `true` if the control was released this `frame`, and false if not. - -These functions are most applicable for controls that act as buttons, such as a shoot button. That being said, they can be used for any control, which is useful if you want to, for example, use a movement control as a discrete button press to operate a menu. - -#### Updating the configuration -The `controls` table, `pairs` table, `joystick`, `deadzone`, and `squareDeadzone` can all be accessed via `player.config`. Any of the values can be changed, and the player's behavior will be updated automatically. Note, however, that new controls and pairs cannot be added after the player is created, and controls and pairs should not be removed entirely (if you want to disable a control, you can set it to an empty table, removing all of its sources). - -#### Getting the active input device -At any time, only the keyboard/mouse sources or the joystick sources for a player will be active. A device will be considered active if any of the sources for that device exceed the deadzone. The keyboard and mouse will always take precedence over the joystick. - -You can call `player:getActiveDevice()` to see which input device is currently active. It will return either `'kbm'` (keyboard/mouse) or `'joy'` (joystick) (or `'none'` if no sources have been used yet). This is useful if you need to change what you display on screen based on the controls the player is using (such as instructions). - -## Contributing -Issues and pull requests are always welcome. To run the test, run `love .` in the baton folder. diff --git a/lib/center/LICENSE b/lib/center/LICENSE deleted file mode 100644 index 88344e3..0000000 --- a/lib/center/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Semyon Entsov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/center/README.md b/lib/center/README.md deleted file mode 100644 index 6aecd8b..0000000 --- a/lib/center/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# center -Center is a simple library that dynamically aligns content to the center of game window. - - - -## Setup - -```lua -local center = require "center" - -center:setupScreen(600, 400) - -function love.draw() - center:start() - - -- draw here - - center:finish() -end - -function love.resize(width, height) - center:resize(width, height) -end -``` - -## API - -### setupScreen -```lua -center:setupScreen(width, height) -``` -Initializes Center - -### apply -```lua -center:apply() -``` -Applies changes. - -This function is responsible for the whole alignment process, so any function (except for `setupScreen` and `resize`) will not make sense without `apply()` after it. - -### setMaxWidth -```lua -center:setMaxWidth(width) -``` -Width of the content **won't be greater** than specified value (default value is 0, that means that there is no boundaries). - -### setMaxHeight -```lua -center:setMaxHeight(height) -``` -Works the same as the previous one. - -### setMaxRelativeWidth -```lua -center:setMaxRelativeWidth(width) -``` -The **relative** width of the content (actual width / available width) **won't be greater** than specified value (default value is 0, that means that there is no relative boundaries). - -### setMaxRelativeHeight -```lua -center:setMaxRelativeHeight(height) -``` -Works the same as the previous one. - -### setBorders -```lua -center:setBorders(t, r, b, l) -``` -Specify the available area for the content. -Each argument represents how far the content should be placed from each side from top to left clockwise. -The content is aligned to the center of **this** area. - -*For better understanding, illustrations will be added soon* - -### start, finish - -```lua -function love.draw() - center:start() - - -- draw here - - center:finish() -end -``` -This pair of functions is supposed to be called once inside `love.draw()`. Any rendering after `center.finish()` affect the default canvas, so you can draw UI or anything else. - -### toGame - -```lua -center:toGame(x, y) -``` -Return position of a point in aligned coordinate system insead of default one. diff --git a/lib/center/center.lua b/lib/center/center.lua deleted file mode 100644 index 1cd16c9..0000000 --- a/lib/center/center.lua +++ /dev/null @@ -1,119 +0,0 @@ ---[[ - MIT License - - Copyright (c) 2019 Semyon Entsov - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -]]-- - -local center = {} - -function center:setupScreen(width, height) - self._WIDTH = width - self._HEIGHT = height - self._MAX_WIDTH = 0 - self._MAX_HEIGHT = 0 - self._MAX_RELATIVE_WIDTH = 0 - self._MAX_RELATIVE_HEIGHT = 0 - self._SCREEN_WIDTH = love.graphics.getWidth() - self._SCREEN_HEIGHT = love.graphics.getHeight() - self._BORDERS = { - ['t'] = 0, - ['r'] = 0, - ['b'] = 0, - ['l'] = 0 - } - self:apply() - return self -end - -function center:setBorders(top, right, bottom, left) - self._BORDERS.t = top - self._BORDERS.r = right - self._BORDERS.b = bottom - self._BORDERS.l = left -end - -function center:setMaxWidth(width) - self._MAX_WIDTH = width -end - -function center:setMaxHeight(height) - self._MAX_HEIGHT = height -end - -function center:setMaxRelativeWidth(width) - self._MAX_RELATIVE_WIDTH = width -end - -function center:setMaxRelativeHeight(height) - self._MAX_RELATIVE_HEIGHT = height -end - -function center:resize(width, height) - self._SCREEN_WIDTH = width - self._SCREEN_HEIGHT = height - self:apply() -end - -function center:apply() - local available_width = self._SCREEN_WIDTH - self._BORDERS.l - self._BORDERS.r - local available_height = self._SCREEN_HEIGHT - self._BORDERS.t - self._BORDERS.b - local max_width = available_width - local max_height = available_height - if self._MAX_RELATIVE_WIDTH > 0 and available_width * self._MAX_RELATIVE_WIDTH < max_width then - max_width = available_width * self._MAX_RELATIVE_WIDTH - end - if self._MAX_RELATIVE_HEIGHT > 0 and available_height * self._MAX_RELATIVE_HEIGHT < max_height then - max_height = available_height * self._MAX_RELATIVE_HEIGHT - end - if self._MAX_WIDTH > 0 and self._MAX_WIDTH < max_width then - max_width = self._MAX_WIDTH - end - if self._MAX_HEIGHT > 0 and self._MAX_HEIGHT < max_height then - max_height = self._MAX_HEIGHT - end - if max_height / max_width > self._HEIGHT / self._WIDTH then - self._CANVAS_WIDTH = max_width - self._CANVAS_HEIGHT = self._CANVAS_WIDTH * (self._HEIGHT / self._WIDTH) - else - self._CANVAS_HEIGHT = max_height - self._CANVAS_WIDTH = self._CANVAS_HEIGHT * (self._WIDTH / self._HEIGHT) - end - self._SCALE = self._CANVAS_HEIGHT / self._HEIGHT - self._OFFSET_X = self._BORDERS.l + (available_width - self._CANVAS_WIDTH) / 2 - self._OFFSET_Y = self._BORDERS.t + (available_height - self._CANVAS_HEIGHT) / 2 -end - -function center:start() - love.graphics.push() - love.graphics.translate(self._OFFSET_X, self._OFFSET_Y) - love.graphics.scale(self._SCALE, self._SCALE) -end - -function center:finish() - love.graphics.pop() -end - -function center:toGame(x, y) - return (x - self._OFFSET_X) / self._SCALE, - (y - self._OFFSET_Y) / self._SCALE -end - -return center diff --git a/lib/center/screenshots/center.png b/lib/center/screenshots/center.png deleted file mode 100644 index 3b3b56b2446e00850955491fdfd191e0cd28a0da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21309 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 dK})wcb{r(vxr}2CrHIhc zWg)jCWv+>ty)X;Qsd!;7bEm@}aenF5^TYGQ^V{=zJ)ig8OZGNUNth%608p$g+7SRm z+yDSn4*~5ZEu{nq02~k@IlDzT;%~!42_e@5f_&i-QK7zYUt+*@03ePQxw_{c`&oMX zv!W@;gECP+Ff)2?b|Jn~W9ZCHsE?i@!ic^%mqMn3m7 zxMEyEKBaXrsD?DmS%^cWpfJm&uFc0hJLnkVg?CmmAvsBpuuK#m!R$eFO%bb9`2|Sg zZFJs3%BPCc(hd(_2=crnIy1KKZ4)AufZ=DjLsg zJGz2-6|`jyy%QV>3?AO4g@u?001V_N_T$l{dGKld2!JLu2wS$X^1op zYQhKE>^pzm%#H;q8)|n&R&@CDYVy(yFVEFvB3jh$kuA_RDW^~^KOMQo{gjnNx8`7G zq#Ra?zZt(?QCD{lo>Vo8ZuKW2L$Yyc4P9qQsOU&q6pPAdP-+Q15|5vSXAIaV9;eKo z*m;ZVz$1$E;^r$Ep1e{N)#?&AwANEnvKqXto4D!vFE2_DQMLM{uS)#E&~QJE16m@q zo53|S4e`&pw{BduzEPIgK{q8Z#iRB)1PV9u!r3+H*H9wv~ItmYBgFY=vT6(C_*^CH9yg z8g7YohCm==lemdJHZ)_=7S2Ta+$b_T0jZ1`Xq1*eF+c|Q-oFOE4?2BA1e(_hNe!;t zI2D@tN5RZ{pPYTJe2-ecXy(+;;YS6+@`YRLheyV%uCO`lN^R6@MmZvR0a7!eKS>Sk90}QYPAR-i5K;nn}xWWIjgG+Od zWdv`8#9~uWOu@8e=s9|M7U0EX1N#FdCjz$n=D)fjcr`B05&0g!b*~dSYwbWfTyAHs R&yT!c5sR@$KehBu_z$NsyORI_ diff --git a/lib/middleclass/.travis.yml b/lib/middleclass/.travis.yml new file mode 100644 index 0000000..53998d6 --- /dev/null +++ b/lib/middleclass/.travis.yml @@ -0,0 +1,36 @@ +language: python +sudo: false + +env: + - LUA="lua=5.1" + - LUA="lua=5.2" + - LUA="lua=5.3" + - LUA="luajit=2.0" + - LUA="luajit=2.1" + +before_install: + - pip install hererocks + - hererocks lua_install -r^ --$LUA + - export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH + +install: + - luarocks install luacheck + - luarocks install busted + - luarocks install luacov + - luarocks install luacov-coveralls + +script: + - luacheck --no-unused-args --std max+busted *.lua spec + - busted --verbose --coverage + +after_success: + - luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install + +branches: + except: + - gh-pages + +notifications: + email: + on_success: change + on_failure: always diff --git a/lib/middleclass/CHANGELOG.md b/lib/middleclass/CHANGELOG.md new file mode 100644 index 0000000..5f8b93a --- /dev/null +++ b/lib/middleclass/CHANGELOG.md @@ -0,0 +1,55 @@ +middleclass changelog +==================== + +# Version 4.1.1 + +* Fixed a bug in which `static` values which evaluated to `false` were not available + in subclasses (#51, thanks @qaisjp for the patch!) +* `isInstanceOf` does not throw an error any more when its first parameter is a + primitive (#55) (This effectively undoes the change introduced in 4.1.0) + + +# Version 4.1.0 + +* Simplifies implementation of `isInstanceOf` and `isSubclassOf`. They will now raise an error if their first + parameter (the `self`) isn't an instance or a class respectively. + +# Version 4.0.0 + +* Unified the method and metamethod lookup into a single algorithm +* Added the capacity of setting up the `__index` metamethod in classes +* Removed global `Object` (classes created with `class()` have no superclass now) +* Removed default method `Class:implements()` +* Renamed several internal functions + +# Version 3.2.0 + +* Changed the way metamethods were handled to fix certain bugs (un-stubbed metamethods could not be inherited) + +# Version 3.1.0 + +* Added Lua 5.3 metamethod support (`__band`, `__bor`, `__bxor`, `__shl`, `__bnot`) + +# Version 3.0.1 + +* Added `__len`, `__ipairs` and `__pairs` metamethods for Lua 5.2 + +# Version 3.0 + +* Anything that behaves reasonably like a class can be a class (no internal list of classes) +* The `class` global function is now just the return value of `require +'middleclass'`. It is a callable table, but works exactly as before. +* The global variable `Object` becomes `class.Object` +* The global function `instanceOf` becomes `class.Object.isInstanceOf`. Parameter order is reversed. +* The global function `subclassOf` becomes `class.Object.static.isSubclassOf`. Parameter order is reversed. +* The global function `implements` becomes `class.Object.static.implements`. Parameter order is reversed. +* Specs have been translated from telescope to busted + +# Version 2.0 + +* Static methods are now separated from instance methods +* class.superclass has now become class.super +* It's now possible to do class.subclasses +* middleclass is now a single file; init.lua has dissapeared +* license is changed from BSD to MIT. License included in source FTW + diff --git a/lib/middleclass/MIT-LICENSE.txt b/lib/middleclass/MIT-LICENSE.txt new file mode 100644 index 0000000..525287a --- /dev/null +++ b/lib/middleclass/MIT-LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2011 Enrique García Cota + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/middleclass/README.md b/lib/middleclass/README.md new file mode 100644 index 0000000..fc1153b --- /dev/null +++ b/lib/middleclass/README.md @@ -0,0 +1,80 @@ +middleclass +=========== + +[![Build Status](https://travis-ci.org/kikito/middleclass.png?branch=master)](https://travis-ci.org/kikito/middleclass) +[![Coverage Status](https://coveralls.io/repos/kikito/middleclass/badge.svg?branch=master&service=github)](https://coveralls.io/github/kikito/middleclass?branch=master) + +A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support. + +Quick Look +========== + +```lua +local class = require 'middleclass' + +local Fruit = class('Fruit') -- 'Fruit' is the class' name + +function Fruit:initialize(sweetness) + self.sweetness = sweetness +end + +Fruit.static.sweetness_threshold = 5 -- class variable (also admits methods) + +function Fruit:isSweet() + return self.sweetness > Fruit.sweetness_threshold +end + +local Lemon = class('Lemon', Fruit) -- subclassing + +function Lemon:initialize() + Fruit.initialize(self, 1) -- invoking the superclass' initializer +end + +local lemon = Lemon:new() + +print(lemon:isSweet()) -- false +``` + +Documentation +============= + +See the [github wiki page](https://github.com/kikito/middleclass/wiki) for examples & documentation. + +You can read the `CHANGELOG.md` file to see what has changed on each version of this library. + +If you need help updating to a new middleclass version, read `UPDATING.md`. + +Installation +============ + +Just copy the middleclass.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it: + +```lua +local class = require 'middleclass' +``` + +Specs +===== + +This project uses [busted](http://olivinelabs.com/busted/) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following: + +```bash +cd /folder/where/the/spec/folder/is +busted +``` + +Performance tests +================= + +Middleclass also comes with a small performance test suite. Just run the following command: + +```bash +lua performance/run.lua +``` + +License +======= + +Middleclass is distributed under the MIT license. + + diff --git a/lib/middleclass/UPDATING.md b/lib/middleclass/UPDATING.md new file mode 100644 index 0000000..83855c9 --- /dev/null +++ b/lib/middleclass/UPDATING.md @@ -0,0 +1,69 @@ +Updating from 3.x to 4.x +======================== + +In middleclass 4.0 there is no global `Object` class any more. Classes created with `class()` don't have a superclass any more. +If you need a global `Object` class, you must create it explicitly and then use it when creating new classes: + +```lua +local Object = class('Object') + +... + +local MyClass = class('MyClass', Object) +``` + +If you are using a library which depends on the internal implementation of middleclass they might not work with middleclass 4.0. You might need to update those other libraries. + +Middleclass 4.0 comes with support for `__index` metamethod support. If your library manipulated the classes' `__instanceDict` internal attribute, you might do the same thing now using `__index` instead. + +Also note that the class method `:implements` has been removed. + +Updating from 2.x to 3.x +======================== + +Middleclass used to expose several global variables on the main scope. It does not do that anymore. + +`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this: + +```lua +local class = require 'middleclass' +local MyClass = class('MyClass') -- works as before +``` + +`Object` is not a global variable any more. But you can get it from `class.Object` + +```lua +local class = require 'middleclass' +local Object = class.Object + +print(Object) -- prints 'class Object' +``` + +The public functions `instanceOf`, `subclassOf` and `includes` have been replaced by `Object.isInstanceOf`, `Object.static.isSubclassOf` and `Object.static.includes`. + +Prior to 3.x: + +```lua +instanceOf(MyClass, obj) +subclassOf(Object, aClass) +includes(aMixin, aClass) +``` + +Since 3.x: + +```lua +obj:isInstanceOf(MyClass) +aClass:isSubclassOf(Object) +aClass:includes(aMixin) +``` + +The 3.x code snippet will throw an error if `obj` is not an object, or if `aClass` is not a class (since they will not implement `isInstanceOf`, `isSubclassOf` or `includes`). +If you are unsure of whether `obj` and `aClass` are an object or a class, you can use the methods in `Object`. They are prepared to work with random types, not just classes and instances: + +```lua +Object.isInstanceOf(obj, MyClass) +Object.isSubclassOf(aClass, Object) +Object.includes(aClass, aMixin) +``` + +Notice that the parameter order is not the same now as it was in 2.x. Also note the change in naming: `isInstanceOf` instead of `instanceOf`, and `isSubclassOf` instead of `subclassOf`. diff --git a/lib/middleclass/init.lua b/lib/middleclass/init.lua new file mode 100644 index 0000000..7e36bcd --- /dev/null +++ b/lib/middleclass/init.lua @@ -0,0 +1,183 @@ +local middleclass = { + _VERSION = 'middleclass v4.1.1', + _DESCRIPTION = 'Object Orientation for Lua', + _URL = 'https://github.com/kikito/middleclass', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2011 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local function _createIndexWrapper(aClass, f) + if f == nil then + return aClass.__instanceDict + else + return function(self, name) + local value = aClass.__instanceDict[name] + + if value ~= nil then + return value + elseif type(f) == "function" then + return (f(self, name)) + else + return f[name] + end + end + end +end + +local function _propagateInstanceMethod(aClass, name, f) + f = name == "__index" and _createIndexWrapper(aClass, f) or f + aClass.__instanceDict[name] = f + + for subclass in pairs(aClass.subclasses) do + if rawget(subclass.__declaredMethods, name) == nil then + _propagateInstanceMethod(subclass, name, f) + end + end +end + +local function _declareInstanceMethod(aClass, name, f) + aClass.__declaredMethods[name] = f + + if f == nil and aClass.super then + f = aClass.super.__instanceDict[name] + end + + _propagateInstanceMethod(aClass, name, f) +end + +local function _tostring(self) return "class " .. self.name end +local function _call(self, ...) return self:new(...) end + +local function _createClass(name, super) + local dict = {} + dict.__index = dict + + local aClass = { name = name, super = super, static = {}, + __instanceDict = dict, __declaredMethods = {}, + subclasses = setmetatable({}, {__mode='k'}) } + + if super then + setmetatable(aClass.static, { + __index = function(_,k) + local result = rawget(dict,k) + if result == nil then + return super.static[k] + end + return result + end + }) + else + setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end }) + end + + setmetatable(aClass, { __index = aClass.static, __tostring = _tostring, + __call = _call, __newindex = _declareInstanceMethod }) + + return aClass +end + +local function _includeMixin(aClass, mixin) + assert(type(mixin) == 'table', "mixin must be a table") + + for name,method in pairs(mixin) do + if name ~= "included" and name ~= "static" then aClass[name] = method end + end + + for name,method in pairs(mixin.static or {}) do + aClass.static[name] = method + end + + if type(mixin.included)=="function" then mixin:included(aClass) end + return aClass +end + +local DefaultMixin = { + __tostring = function(self) return "instance of " .. tostring(self.class) end, + + initialize = function(self, ...) end, + + isInstanceOf = function(self, aClass) + return type(aClass) == 'table' + and type(self) == 'table' + and (self.class == aClass + or type(self.class) == 'table' + and type(self.class.isSubclassOf) == 'function' + and self.class:isSubclassOf(aClass)) + end, + + static = { + allocate = function(self) + assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") + return setmetatable({ class = self }, self.__instanceDict) + end, + + new = function(self, ...) + assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'") + local instance = self:allocate() + instance:initialize(...) + return instance + end, + + subclass = function(self, name) + assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") + assert(type(name) == "string", "You must provide a name(string) for your class") + + local subclass = _createClass(name, self) + + for methodName, f in pairs(self.__instanceDict) do + _propagateInstanceMethod(subclass, methodName, f) + end + subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end + + self.subclasses[subclass] = true + self:subclassed(subclass) + + return subclass + end, + + subclassed = function(self, other) end, + + isSubclassOf = function(self, other) + return type(other) == 'table' and + type(self.super) == 'table' and + ( self.super == other or self.super:isSubclassOf(other) ) + end, + + include = function(self, ...) + assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'") + for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end + return self + end + } +} + +function middleclass.class(name, super) + assert(type(name) == 'string', "A name (string) is needed for the new class") + return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin) +end + +setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) + +return middleclass diff --git a/lib/middleclass/performance/run.lua b/lib/middleclass/performance/run.lua new file mode 100644 index 0000000..8d8ba47 --- /dev/null +++ b/lib/middleclass/performance/run.lua @@ -0,0 +1,43 @@ +local class = require 'middleclass' + +time = require 'performance/time' + +time('class creation', function() + local A = class('A') +end) + +local A = class('A') + +time('instance creation', function() + local a = A:new() +end) + +function A:foo() + return 1 +end + +local a = A:new() + +time('instance method invocation', function() + a:foo() +end) + +local B = class('B', A) + +local b = B:new() + +time('inherited method invocation', function() + b:foo() +end) + +function A.static:bar() + return 2 +end + +time('class method invocation', function() + A:bar() +end) + +time('inherited class method invocation', function() + B:bar() +end) diff --git a/lib/middleclass/performance/time.lua b/lib/middleclass/performance/time.lua new file mode 100644 index 0000000..dd02455 --- /dev/null +++ b/lib/middleclass/performance/time.lua @@ -0,0 +1,13 @@ +return function(title, f) + + collectgarbage() + + local startTime = os.clock() + + for i=0,10000 do f() end + + local endTime = os.clock() + + print( title, endTime - startTime ) + +end diff --git a/lib/middleclass/rockspecs/middleclass-3.0-0.rockspec b/lib/middleclass/rockspecs/middleclass-3.0-0.rockspec new file mode 100644 index 0000000..f9ec58c --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-3.0-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "3.0-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v3.0.0.tar.gz", + dir = "middleclass-3.0.0" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/rockspecs/middleclass-3.1-0.rockspec b/lib/middleclass/rockspecs/middleclass-3.1-0.rockspec new file mode 100644 index 0000000..24a233e --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-3.1-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "3.1-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v3.1.0.tar.gz", + dir = "middleclass-3.1.0" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/rockspecs/middleclass-3.2-0.rockspec b/lib/middleclass/rockspecs/middleclass-3.2-0.rockspec new file mode 100644 index 0000000..03e3b30 --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-3.2-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "3.2-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v3.2.0.tar.gz", + dir = "middleclass-3.2.0" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/rockspecs/middleclass-4.0-0.rockspec b/lib/middleclass/rockspecs/middleclass-4.0-0.rockspec new file mode 100644 index 0000000..517984e --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-4.0-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "4.0-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v4.0.0.tar.gz", + dir = "middleclass-4.0.0" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/rockspecs/middleclass-4.1-0.rockspec b/lib/middleclass/rockspecs/middleclass-4.1-0.rockspec new file mode 100644 index 0000000..dc710e9 --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-4.1-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "4.1-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v4.1.0.tar.gz", + dir = "middleclass-4.1.0" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/rockspecs/middleclass-4.1.1-0.rockspec b/lib/middleclass/rockspecs/middleclass-4.1.1-0.rockspec new file mode 100644 index 0000000..ddaacd9 --- /dev/null +++ b/lib/middleclass/rockspecs/middleclass-4.1.1-0.rockspec @@ -0,0 +1,21 @@ +package = "middleclass" +version = "4.1.1-0" +source = { + url = "https://github.com/kikito/middleclass/archive/v4.1.1.tar.gz", + dir = "middleclass-4.1.1" +} +description = { + summary = "A simple OOP library for Lua", + detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support", + homepage = "https://github.com/kikito/middleclass", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + middleclass = "middleclass.lua" + } +} diff --git a/lib/middleclass/spec/class_spec.lua b/lib/middleclass/spec/class_spec.lua new file mode 100644 index 0000000..144cb9f --- /dev/null +++ b/lib/middleclass/spec/class_spec.lua @@ -0,0 +1,28 @@ +local class = require 'middleclass' + +describe('class()', function() + + describe('when given no params', function() + it('it throws an error', function() + assert.error(class) + end) + end) + + describe('when given a name', function() + it('the resulting class has the correct name and Object as its superclass', function() + local TheClass = class('TheClass') + assert.equal(TheClass.name, 'TheClass') + assert.is_nil(TheClass.super) + end) + end) + + describe('when given a name and a superclass', function() + it('the resulting class has the correct name and superclass', function() + local TheSuperClass = class('TheSuperClass') + local TheSubClass = class('TheSubClass', TheSuperClass) + assert.equal(TheSubClass.name, 'TheSubClass') + assert.equal(TheSubClass.super, TheSuperClass) + end) + end) + +end) diff --git a/lib/middleclass/spec/classes_spec.lua b/lib/middleclass/spec/classes_spec.lua new file mode 100644 index 0000000..7942f18 --- /dev/null +++ b/lib/middleclass/spec/classes_spec.lua @@ -0,0 +1,138 @@ +local class = require 'middleclass' + +describe('A Class', function() + + describe('Default stuff', function() + + local AClass + + before_each(function() + AClass = class('AClass') + end) + + describe('name', function() + it('is correctly set', function() + assert.equal(AClass.name, 'AClass') + end) + end) + + describe('tostring', function() + it('returns "class *name*"', function() + assert.equal(tostring(AClass), 'class AClass') + end) + end) + + describe('()', function() + it('returns an object, like Class:new()', function() + local obj = AClass() + assert.equal(obj.class, AClass) + end) + end) + + describe('include', function() + it('throws an error when used without the :', function() + assert.error(function() AClass.include() end) + end) + it('throws an error when passed a non-table:', function() + assert.error(function() AClass:include(1) end) + end) + end) + + describe('subclass', function() + + it('throws an error when used without the :', function() + assert.error(function() AClass.subclass() end) + end) + + it('throws an error when no name is given', function() + assert.error( function() AClass:subclass() end) + end) + + describe('when given a subclass name', function() + + local SubClass + + before_each(function() + function AClass.static:subclassed(other) self.static.child = other end + SubClass = AClass:subclass('SubClass') + end) + + it('it returns a class with the correct name', function() + assert.equal(SubClass.name, 'SubClass') + end) + + it('it returns a class with the correct superclass', function() + assert.equal(SubClass.super, AClass) + end) + + it('it invokes the subclassed hook method', function() + assert.equal(SubClass, AClass.child) + end) + + it('it includes the subclass in the list of subclasses', function() + assert.is_true(AClass.subclasses[SubClass]) + end) + + end) + + end) + + end) + + + + describe('attributes', function() + + local A, B + + before_each(function() + A = class('A') + A.static.foo = 'foo' + + B = class('B', A) + end) + + it('are available after being initialized', function() + assert.equal(A.foo, 'foo') + end) + + it('are available for subclasses', function() + assert.equal(B.foo, 'foo') + end) + + it('are overridable by subclasses, without affecting the superclasses', function() + B.static.foo = 'chunky bacon' + assert.equal(B.foo, 'chunky bacon') + assert.equal(A.foo, 'foo') + end) + + end) + + describe('methods', function() + + local A, B + + before_each(function() + A = class('A') + function A.static:foo() return 'foo' end + + B = class('B', A) + end) + + it('are available after being initialized', function() + assert.equal(A:foo(), 'foo') + end) + + it('are available for subclasses', function() + assert.equal(B:foo(), 'foo') + end) + + it('are overridable by subclasses, without affecting the superclasses', function() + function B.static:foo() return 'chunky bacon' end + assert.equal(B:foo(), 'chunky bacon') + assert.equal(A:foo(), 'foo') + end) + + end) + +end) diff --git a/lib/middleclass/spec/default_methods_spec.lua b/lib/middleclass/spec/default_methods_spec.lua new file mode 100644 index 0000000..91fd9b8 --- /dev/null +++ b/lib/middleclass/spec/default_methods_spec.lua @@ -0,0 +1,236 @@ +local class = require 'middleclass' + +describe('Default methods', function() + local Object + before_each(function() + Object = class('Object') + end) + + describe('name', function() + it('is correctly set', function() + assert.equal(Object.name, 'Object') + end) + end) + + describe('tostring', function() + it('returns "class Object"', function() + assert.equal(tostring(Object), 'class Object') + end) + end) + + describe('()', function() + it('returns an object, like Object:new()', function() + local obj = Object() + assert.is_true(obj:isInstanceOf(Object)) + end) + end) + + describe('subclass', function() + + it('throws an error when used without the :', function() + assert.error(function() Object.subclass() end) + end) + + it('throws an error when no name is given', function() + assert.error( function() Object:subclass() end) + end) + + describe('when given a class name', function() + + local SubClass + + before_each(function() + SubClass = Object:subclass('SubClass') + end) + + it('it returns a class with the correct name', function() + assert.equal(SubClass.name, 'SubClass') + end) + + it('it returns a class with the correct superclass', function() + assert.equal(SubClass.super, Object) + end) + + it('it includes the subclass in the list of subclasses', function() + assert.is_true(Object.subclasses[SubClass]) + end) + + end) + + end) + + describe('instance creation', function() + + local SubClass + + before_each(function() + SubClass = class('SubClass') + function SubClass:initialize() self.mark=true end + end) + + describe('allocate', function() + + it('allocates instances properly', function() + local instance = SubClass:allocate() + assert.equal(instance.class, SubClass) + assert.equal(tostring(instance), "instance of " .. tostring(SubClass)) + end) + + it('throws an error when used without the :', function() + assert.error(Object.allocate) + end) + + it('does not call the initializer', function() + local allocated = SubClass:allocate() + assert.is_nil(allocated.mark) + end) + + it('can be overriden', function() + + local previousAllocate = SubClass.static.allocate + + function SubClass.static:allocate() + local instance = previousAllocate(SubClass) + instance.mark = true + return instance + end + + local allocated = SubClass:allocate() + assert.is_true(allocated.mark) + end) + + end) + + describe('new', function() + + it('initializes instances properly', function() + local instance = SubClass:new() + assert.equal(instance.class, SubClass) + end) + + it('throws an error when used without the :', function() + assert.error(SubClass.new) + end) + + it('calls the initializer', function() + local initialized = SubClass:new() + assert.is_true(initialized.mark) + end) + + end) + + describe('isInstanceOf', function() + + describe('primitives', function() + local o = Object:new() + local primitives = {nil, 1, 'hello', {}, function() end, Object:new()} + + describe('used as classes', function() + for _,primitive in pairs(primitives) do + local theType = type(primitive) + it('object:isInstanceOf(, '.. theType ..') returns false', function() + assert.is_falsy(o:isInstanceOf(primitive)) + end) + end + end) + + describe('used as instances', function() + for _,primitive in pairs(primitives) do + local theType = type(primitive) + it('Object.isInstanceOf('.. theType ..', Object) returns false without error', function() + assert.is_falsy(Object.isInstanceOf(primitive, Object)) + end) + end + end) + + + end) + + describe('An instance', function() + local Class1 = class('Class1') + local Class2 = class('Class2', Class1) + local Class3 = class('Class3', Class2) + local UnrelatedClass = class('Unrelated') + + local o1, o2, o3 = Class1:new(), Class2:new(), Class3:new() + + it('isInstanceOf its class', function() + assert.is_true(o1:isInstanceOf(Class1)) + assert.is_true(o2:isInstanceOf(Class2)) + assert.is_true(o3:isInstanceOf(Class3)) + end) + + it('is instanceOf its class\' superclasses', function() + assert.is_true(o2:isInstanceOf(Class1)) + assert.is_true(o3:isInstanceOf(Class1)) + assert.is_true(o3:isInstanceOf(Class2)) + end) + + it('is not instanceOf its class\' subclasses', function() + assert.is_false(o1:isInstanceOf(Class2)) + assert.is_false(o1:isInstanceOf(Class3)) + assert.is_false(o2:isInstanceOf(Class3)) + end) + + it('is not instanceOf an unrelated class', function() + assert.is_false(o1:isInstanceOf(UnrelatedClass)) + assert.is_false(o2:isInstanceOf(UnrelatedClass)) + assert.is_false(o3:isInstanceOf(UnrelatedClass)) + end) + + end) + + end) + + end) + + describe('isSubclassOf', function() + + it('returns false for instances', function() + assert.is_false(Object:isSubclassOf(Object:new())) + end) + + describe('on primitives', function() + local primitives = {nil, 1, 'hello', {}, function() end} + + for _,primitive in pairs(primitives) do + local theType = type(primitive) + it('returns false for ' .. theType, function() + assert.is_false(Object:isSubclassOf(primitive)) + end) + end + + end) + + describe('Any class (except Object)', function() + local Class1 = class('Class1') + local Class2 = class('Class2', Class1) + local Class3 = class('Class3', Class2) + local UnrelatedClass = class('Unrelated') + + it('is subclassOf its direct superclass', function() + assert.is_true(Class2:isSubclassOf(Class1)) + assert.is_true(Class3:isSubclassOf(Class2)) + end) + + it('is subclassOf its ancestors', function() + assert.is_true(Class3:isSubclassOf(Class1)) + end) + + it('is a subclassOf its class\' subclasses', function() + assert.is_true(Class2:isSubclassOf(Class1)) + assert.is_true(Class3:isSubclassOf(Class1)) + assert.is_true(Class3:isSubclassOf(Class2)) + end) + + it('is not a subclassOf an unrelated class', function() + assert.is_false(Class1:isSubclassOf(UnrelatedClass)) + assert.is_false(Class2:isSubclassOf(UnrelatedClass)) + assert.is_false(Class3:isSubclassOf(UnrelatedClass)) + end) + + end) + end) +end) + + diff --git a/lib/middleclass/spec/instances_spec.lua b/lib/middleclass/spec/instances_spec.lua new file mode 100644 index 0000000..d9ac52c --- /dev/null +++ b/lib/middleclass/spec/instances_spec.lua @@ -0,0 +1,65 @@ +local class = require 'middleclass' + +describe('An instance', function() + + describe('attributes', function() + + local Person + + before_each(function() + Person = class('Person') + function Person:initialize(name) + self.name = name + end + end) + + it('are available in the instance after being initialized', function() + local bob = Person:new('bob') + assert.equal(bob.name, 'bob') + end) + + it('are available in the instance after being initialized by a superclass', function() + local AgedPerson = class('AgedPerson', Person) + function AgedPerson:initialize(name, age) + Person.initialize(self, name) + self.age = age + end + + local pete = AgedPerson:new('pete', 31) + assert.equal(pete.name, 'pete') + assert.equal(pete.age, 31) + end) + + end) + + describe('methods', function() + + local A, B, a, b + + before_each(function() + A = class('A') + function A:overridden() return 'foo' end + function A:regular() return 'regular' end + + B = class('B', A) + function B:overridden() return 'bar' end + + a = A:new() + b = B:new() + end) + + it('are available for any instance', function() + assert.equal(a:overridden(), 'foo') + end) + + it('are inheritable', function() + assert.equal(b:regular(), 'regular') + end) + + it('are overridable', function() + assert.equal(b:overridden(), 'bar') + end) + + end) + +end) diff --git a/lib/middleclass/spec/metamethods_lua_5_2.lua b/lib/middleclass/spec/metamethods_lua_5_2.lua new file mode 100644 index 0000000..2ea6c9b --- /dev/null +++ b/lib/middleclass/spec/metamethods_lua_5_2.lua @@ -0,0 +1,85 @@ +local class = require 'middleclass' + +local it = require('busted').it +local describe = require('busted').describe +local before_each = require('busted').before_each +local assert = require('busted').assert + +describe('Lua 5.2 Metamethods', function() + local Vector, v + before_each(function() + Vector= class('Vector') + function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end + function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end + + function Vector.__len(a) return 3 end + function Vector.__pairs(a) + local t = {x=a.x,y=a.y,z=a.z} + return coroutine.wrap(function() + for k,val in pairs(t) do + coroutine.yield(k,val) + end + end) + end + function Vector.__ipairs(a) + local t = {a.x,a.y,a.z} + return coroutine.wrap(function() + for k,val in ipairs(t) do + coroutine.yield(k,val) + end + end) + end + + v = Vector:new(1,2,3) + end) + + it('implements __len', function() + assert.equal(#v, 3) + end) + + it('implements __pairs',function() + local output = {} + for k,val in pairs(v) do + output[k] = val + end + assert.are.same(output,{x=1,y=2,z=3}) + end) + + it('implements __ipairs',function() + local output = {} + for _,i in ipairs(v) do + output[#output+1] = i + end + assert.are.same(output,{1,2,3}) + end) + + describe('Inherited Metamethods', function() + local Vector2, v2 + before_each(function() + Vector2= class('Vector2', Vector) + function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end + + v2 = Vector2:new(1,2,3) + end) + + it('implements __len', function() + assert.equal(#v2, 3) + end) + + it('implements __pairs',function() + local output = {} + for k,val in pairs(v2) do + output[k] = val + end + assert.are.same(output,{x=1,y=2,z=3}) + end) + + it('implements __ipairs',function() + local output = {} + for _,i in ipairs(v2) do + output[#output+1] = i + end + assert.are.same(output,{1,2,3}) + end) + end) +end) diff --git a/lib/middleclass/spec/metamethods_lua_5_3.lua b/lib/middleclass/spec/metamethods_lua_5_3.lua new file mode 100644 index 0000000..e74f6d7 --- /dev/null +++ b/lib/middleclass/spec/metamethods_lua_5_3.lua @@ -0,0 +1,106 @@ +local class = require 'middleclass' + +local it = require('busted').it +local describe = require('busted').describe +local before_each = require('busted').before_each +local assert = require('busted').assert + +describe('Lua 5.3 Metamethods', function() + local Vector, v, last_gc + before_each(function() + Vector= class('Vector') + function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end + function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end + function Vector.__pairs(a) + local t = {x=a.x,y=a.y,z=a.z} + return coroutine.wrap(function() + for k,val in pairs(t) do + coroutine.yield(k,val) + end + end) + end + function Vector.__len(a) return 3 end + + function Vector.__gc(a) last_gc = {a.class.name, a.x, a.y, a.z} end + function Vector.__band(a,n) return a.class:new(a.x & n, a.y & n, a.z & n) end + function Vector.__bor(a,n) return a.class:new(a.x | n, a.y | n, a.z | n) end + function Vector.__bxor(a,n) return a.class:new(a.x ~ n, a.y ~ n, a.z ~ n) end + function Vector.__shl(a,n) return a.class:new(a.x << n, a.y << n, a.z << n) end + function Vector.__shr(a,n) return a.class:new(a.x >> n, a.y >> n, a.z >> n) end + function Vector.__bnot(a) return a.class:new(~a.x, ~a.y, ~a.z) end + + v = Vector:new(1,2,3) + end) + + it('implements __gc', function() + collectgarbage() + v = nil + collectgarbage() + assert.are.same(last_gc, {"Vector",1,2,3}) + end) + + it('implements __band', function() + assert.equal(v & 1, Vector(1,0,1)) + end) + + it('implements __bor', function() + assert.equal(v | 0, Vector(1,2,3)) + end) + + it('implements __bxor', function() + assert.equal(v | 1, Vector(1,3,3)) + end) + + it('implements __shl', function() + assert.equal(v << 1, Vector(2,4,6)) + end) + + it('implements __shr', function() + assert.equal(v >> 1, Vector(0,1,1)) + end) + + it('implements __bnot', function() + assert.equal(~v, Vector(-2,-3,-4)) + end) + + describe('Inherited Metamethods', function() + local Vector2, v2 + before_each(function() + Vector2= class('Vector2', Vector) + function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end + + v2 = Vector2:new(1,2,3) + end) + + it('implements __gc', function() + collectgarbage() + v2 = nil + collectgarbage() + assert.are.same(last_gc, {"Vector2",1,2,3}) + end) + + it('implements __band', function() + assert.equal(v2 & 1, Vector2(1,0,1)) + end) + + it('implements __bor', function() + assert.equal(v2 | 0, Vector2(1,2,3)) + end) + + it('implements __bxor', function() + assert.equal(v2 | 1, Vector2(1,3,3)) + end) + + it('implements __shl', function() + assert.equal(v2 << 1, Vector2(2,4,6)) + end) + + it('implements __shr', function() + assert.equal(v2 >> 1, Vector2(0,1,1)) + end) + + it('implements __bnot', function() + assert.equal(~v2, Vector2(-2,-3,-4)) + end) + end) +end) diff --git a/lib/middleclass/spec/metamethods_spec.lua b/lib/middleclass/spec/metamethods_spec.lua new file mode 100644 index 0000000..73bf883 --- /dev/null +++ b/lib/middleclass/spec/metamethods_spec.lua @@ -0,0 +1,317 @@ +local class = require 'middleclass' + +local function is_lua_5_2_compatible() + return type(rawlen) == 'function' +end + +local function is_lua_5_3_compatible() + return type(string.unpack) == 'function' +end + +if is_lua_5_2_compatible() then + require 'spec/metamethods_lua_5_2' +end + +if is_lua_5_3_compatible() then + require 'spec.metamethods_lua_5_3' +end + +describe('Metamethods', function() + describe('Custom Metamethods', function() + local Vector, v, w + before_each(function() + Vector= class('Vector') + function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end + function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end + function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end + function Vector.__lt(a,b) return a() < b() end + function Vector.__le(a,b) return a() <= b() end + function Vector.__add(a,b) return a.class:new(a.x+b.x, a.y+b.y ,a.z+b.z) end + function Vector.__sub(a,b) return a.class:new(a.x-b.x, a.y-b.y, a.z-b.z) end + function Vector.__div(a,s) return a.class:new(a.x/s, a.y/s, a.z/s) end + function Vector.__unm(a) return a.class:new(-a.x, -a.y, -a.z) end + function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end + function Vector.__call(a) return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end + function Vector.__pow(a,b) + return a.class:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x) + end + function Vector.__mul(a,b) + if type(b)=="number" then return a.class:new(a.x*b, a.y*b, a.z*b) end + if type(a)=="number" then return b.class:new(a*b.x, a*b.y, a*b.z) end + end + Vector.__metatable = "metatable of a vector" + Vector.__mode = "k" + + v = Vector:new(1,2,3) + w = Vector:new(2,4,6) + end) + + it('implements __tostring', function() + assert.equal(tostring(v), "Vector[1,2,3]") + end) + + it('implements __eq', function() + assert.equal(v, v) + end) + + it('implements __lt', function() + assert.is_true(v < w) + end) + + it('implements __le', function() + assert.is_true(v <= w) + end) + + it('implements __add', function() + assert.equal(v+w, Vector(3,6,9)) + end) + + it('implements __sub', function() + assert.equal(w-v, Vector(1,2,3)) + end) + + it('implements __div', function() + assert.equal(w/2, Vector(1,2,3)) + end) + + it('implements __concat', function() + assert.equal(v..w, 28) + end) + + it('implements __call', function() + assert.equal(v(), math.sqrt(14)) + end) + + it('implements __pow', function() + assert.equal(v^w, Vector(0,0,0)) + end) + + it('implements __mul', function() + assert.equal(4*v, Vector(4,8,12)) + end) + + it('implements __metatable', function() + assert.equal("metatable of a vector", getmetatable(v)) + end) + + it('implements __mode', function() + v[{}] = true + collectgarbage() + for k in pairs(v) do assert.not_table(k) end + end) + + --[[ + it('implements __index', function() + assert.equal(b[1], 3) + end) + --]] + + describe('Inherited Metamethods', function() + local Vector2, v2, w2 + before_each(function() + Vector2= class('Vector2', Vector) + function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end + + v2 = Vector2:new(1,2,3) + w2 = Vector2:new(2,4,6) + end) + + it('implements __tostring', function() + assert.equal(tostring(v2), "Vector2[1,2,3]") + end) + + it('implements __eq', function() + assert.equal(v2, v2) + end) + + it('implements __lt', function() + assert.is_true(v2 < w2) + end) + + it('implements __le', function() + assert.is_true(v2 <= w2) + end) + + it('implements __add', function() + assert.equal(v2+w2, Vector2(3,6,9)) + end) + + it('implements __sub', function() + assert.equal(w2-v2, Vector2(1,2,3)) + end) + + it('implements __div', function() + assert.equal(w2/2, Vector2(1,2,3)) + end) + + it('implements __concat', function() + assert.equal(v2..w2, 28) + end) + + it('implements __call', function() + assert.equal(v2(), math.sqrt(14)) + end) + + it('implements __pow', function() + assert.equal(v2^w2, Vector2(0,0,0)) + end) + + it('implements __mul', function() + assert.equal(4*v2, Vector2(4,8,12)) + end) + + it('implements __metatable', function() + assert.equal("metatable of a vector", getmetatable(v2)) + end) + + it('implements __mode', function() + v2[{}] = true + collectgarbage() + for k in pairs(v2) do assert.not_table(k) end + end) + + it('allows inheriting further', function() + local Vector3 = class('Vector3', Vector2) + local v3 = Vector3(1,2,3) + local w3 = Vector3(3,4,5) + assert.equal(v3+w3, Vector3(4,6,8)) + end) + + describe('Updates', function() + it('overrides __add', function() + Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end + assert.equal(v2+w2, Vector2(1.5,3,4.5)) + end) + + it('updates __add', function() + Vector.__add = Vector.__sub + assert.equal(v2+w2, Vector2(-1,-2,-3)) + end) + + it('does not update __add after overriding', function() + Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end + Vector.__add = Vector.__sub + assert.equal(v2+w2, Vector2(-0.5,-1,-1.5)) + end) + + it('reverts __add override', function() + Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end + Vector2.__add = nil + assert.equal(v2+w2, Vector2(3,6,9)) + end) + end) + end) + end) + + describe('Custom __index and __newindex', function() + describe('Tables', function() + local Proxy, fallback, p + before_each(function() + Proxy = class('Proxy') + fallback = {foo = 'bar', common = 'fallback'} + Proxy.__index = fallback + Proxy.__newindex = fallback + Proxy.common = 'class' + p = Proxy() + end) + + it('uses __index', function() + assert.equal(p.foo, 'bar') + end) + + it('does not use __index when field exists in class', function() + assert.equal(p.common, 'class') + end) + + it('uses __newindex', function() + p.key = 'value' + assert.equal(fallback.key, 'value') + end) + + it('uses __newindex when field exists in class', function() + p.common = 'value' + assert.equal(p.common, 'class') + assert.equal(Proxy.common, 'class') + assert.equal(fallback.common, 'value') + end) + end) + + describe('Functions', function() + local Namespace, Rectangle, r + before_each(function() + Namespace = class('Namespace') + function Namespace:__index(name) + local getter = self.class[name.."Getter"] + if getter then return getter(self) end + end + function Namespace:__newindex(name, value) + local setter = self.class[name.."Setter"] + if setter then setter(self, value) else rawset(self, name, value) end + end + Rectangle = class('Rectangle', Namespace) + function Rectangle:initialize(x, y, scale) + self._scale, self.x, self.y = 1, x, y + self.scale = scale + end + function Rectangle:scaleGetter() return self._scale end + function Rectangle:scaleSetter(v) + self.x = self.x*v/self._scale + self.y = self.y*v/self._scale + self._scale = v + end + function Rectangle:areaGetter() return self.x * self.y end + r = Rectangle(3, 4, 2) + end) + + it('uses setter', function() + assert.equal(r.x, 6) + assert.equal(r.y, 8) + r.scale = 3 + assert.equal(r.x, 9) + assert.equal(r.y, 12) + end) + + it('uses getters', function() + assert.equal(r.scale, 2) + assert.equal(r.area, 48) + end) + + it('updates inherited __index', function() + function Namespace.__index() return 42 end + assert.equal(r.area, 42) + function Rectangle.__index() return 24 end + assert.equal(r.area, 24) + function Namespace.__index() return 96 end + assert.equal(r.area, 24) + Rectangle.__index = nil + assert.equal(r.area, 96) + end) + end) + end) + + describe('Default Metamethods', function() + + local Peter, peter + + before_each(function() + Peter = class('Peter') + peter = Peter() + end) + + describe('A Class', function() + it('has a call metamethod properly set', function() + assert.is_true(peter:isInstanceOf(Peter)) + end) + it('has a tostring metamethod properly set', function() + assert.equal(tostring(Peter), 'class Peter') + end) + end) + + describe('An instance', function() + it('has a tostring metamethod, returning a different result from Object.__tostring', function() + assert.equal(tostring(peter), 'instance of class Peter') + end) + end) + end) + +end) diff --git a/lib/middleclass/spec/mixins_spec.lua b/lib/middleclass/spec/mixins_spec.lua new file mode 100644 index 0000000..ef592a1 --- /dev/null +++ b/lib/middleclass/spec/mixins_spec.lua @@ -0,0 +1,53 @@ +local class = require 'middleclass' + +describe('A Mixin', function() + + local Mixin1, Mixin2, Class1, Class2 + + before_each(function() + Mixin1, Mixin2 = {},{} + + function Mixin1:included(theClass) theClass.includesMixin1 = true end + function Mixin1:foo() return 'foo' end + function Mixin1:bar() return 'bar' end + Mixin1.static = {} + Mixin1.static.bazzz = function() return 'bazzz' end + + + function Mixin2:baz() return 'baz' end + + Class1 = class('Class1'):include(Mixin1, Mixin2) + function Class1:foo() return 'foo1' end + + Class2 = class('Class2', Class1) + function Class2:bar2() return 'bar2' end + end) + + it('invokes the "included" method when included', function() + assert.is_true(Class1.includesMixin1) + end) + + it('has all its functions (except "included") copied to its target class', function() + assert.equal(Class1:bar(), 'bar') + assert.is_nil(Class1.included) + end) + + it('makes its functions available to subclasses', function() + assert.equal(Class2:baz(), 'baz') + end) + + it('allows overriding of methods in the same class', function() + assert.equal(Class2:foo(), 'foo1') + end) + + it('allows overriding of methods on subclasses', function() + assert.equal(Class2:bar2(), 'bar2') + end) + + it('makes new static methods available in classes', function() + assert.equal(Class1:bazzz(), 'bazzz') + assert.equal(Class2:bazzz(), 'bazzz') + end) + +end) + diff --git a/main.lua b/main.lua index fb87af0..88b5ab3 100644 --- a/main.lua +++ b/main.lua @@ -1,4 +1,5 @@ animx = require "lib/animx" +class = require "lib/middleclass" ---------------------------------------- -- LOAD @@ -7,6 +8,157 @@ function love.load () left = 0; right = 1; up = 2; down = 3 upleft = 4; downleft = 5; upright = 6; downright = 7 + player = Bat:new( ) +-- + -- for compliance with Statute 43.5 (2019); all birds must report births to local Officials + birdRegistry = {} +end + +---------------------------------------- +-- UPDATE +---------------------------------------- +function love.update ( dt ) +-- player.x = player.x + 100 * dt + + player:update( dt ) + animx.update(dt) +end + +---------------------------------------- +-- DRAW +---------------------------------------- +function love.draw () + love.graphics.print('Hello World!', 400, 300) + player:draw() +end + + +---------------------------------------- +-- INPUT +---------------------------------------- +function love.keypressed ( key ) + if ( key == "right" ) then + player.moving = true + player.direction = right + elseif ( key == "left" ) then + player.moving = true + player.direction = left + elseif ( key == "space" ) then + player.flying = true + player.actor:switch('flap') + player.actor:getAnimation():restart() + end +end + +function love.keyreleased (key) + if ( key == "right" and player.direction == right ) then + if ( love.keyboard.isDown("left") ) then + player.direction = left + else + player.moving = false + end + elseif ( key == "left" and player.direction == left ) then + if ( love.keyboard.isDown("right") ) then + player.direction = right + else + player.moving = false + end + end +end + + +---------------------------------------- +-- FLIERS +---------------------------------------- +-- birds and bats both fly. fliers. + +Flier = class('Flier') + +function Flier:initialize ( x, y, actor ) + self.x = x + self.y = y + self.y_vel = 0 + self.x_vel = 0 + self.moving = false + self.flying = false + self.actor = actor +end + +-- generic flier update: physics + changing position +function Flier:update ( dt ) + self:physics( dt ) +end + +function Flier:draw ( ) + if ( self.direction == right ) then + self.actor:flipX(true) + elseif (self.direction == left) then + self.actor:flipX(false) + end + self.actor:draw( self.x, self.y ) +end + +---------------------------------------- +-- "PHYSICS" +---------------------------------------- +-- "physics" being used verryyyyy lightly here + +-- does basics physics work (determines velocity) for a flier +function Flier:physics ( dt ) + self:physics_x( dt ) + self:physics_y( dt ) +end + +function Flier:physics_x ( dt ) + max_vel = 300 + min_vel = -300 + turn = 300 + + -- holding arrow-key + if ( self.moving ) then + if ( self.x_vel < max_vel and self.direction == right ) then + self.x_vel = self.x_vel + (max_vel / turn) + elseif ( self.x_vel > min_vel and self.direction == left ) then + self.x_vel = self.x_vel - (max_vel / turn) + end + else + if ( self.x_vel > 0 ) then + self.x_vel = self.x_vel - (max_vel / (turn * 3)) + elseif ( self.x_vel < 0 ) then + self.x_vel = self.x_vel + (max_vel / (turn * 3)) + end + end + + self.x = self.x + self.x_vel * dt +end + +function Flier:physics_y ( dt ) + gravity = .85 + floor = 500 + + -- wing-flap + if ( self.flying ) then + self.y_vel = -175 + self.flying = false + end + + -- gravity + if ( self.y < floor ) then + self.y_vel = self.y_vel + gravity + end + + -- if on ground; stop gravity + if ( self.y > floor ) then + self.y = floor + self.y_vel = 0 + end + + self.y = self.y + self.y_vel * dt +end + +Bat = class('Bat', Flier) + +function Bat:initialize() batSheet = love.graphics.newImage("art/sprites/bat.png") batFlapAnim = animx.newAnimation{ @@ -14,7 +166,7 @@ function love.load () tileWidth = 32, frames = { 2, 3, 4, 5 } }:onAnimOver( function() - bat.actor:switch('idle') + player.actor:switch('idle') end ) batIdleAnim = animx.newAnimation { @@ -28,144 +180,5 @@ function love.load () ['flap'] = batFlapAnim }:switch('idle') --- batActor:switch('idle') - - bat = { x = 50, y = 200, - y_vel = 0, x_vel = 0, - moving = false, - flying = false, - direction = left, - actor = batActor - } - - -- for compliance with Statute 43.5 (2019); all birds must report births to local Officials - birdRegistry = {} -end - ----------------------------------------- --- UPDATE ----------------------------------------- -function love.update ( dt ) --- bat.x = bat.x + 100 * dt - - flier_update( bat, dt ) - animx.update(dt) -end - ----------------------------------------- --- DRAW ----------------------------------------- -function love.draw () - love.graphics.print('Hello World!', 400, 300) - flier_draw( bat ) -end - - ----------------------------------------- --- INPUT ----------------------------------------- -function love.keypressed ( key ) - if ( key == "right" ) then - bat.moving = true - bat.direction = right - elseif ( key == "left" ) then - bat.moving = true - bat.direction = left - elseif ( key == "space" ) then - bat.flying = true - bat.actor:switch('flap') - bat.actor:getAnimation():restart() - end -end - -function love.keyreleased (key) - if ( key == "right" and bat.direction == right ) then - if ( love.keyboard.isDown("left") ) then - bat.direction = left - else - bat.moving = false - end - elseif ( key == "left" and bat.direction == left ) then - if ( love.keyboard.isDown("right") ) then - bat.direction = right - else - bat.moving = false - end - end -end - - ----------------------------------------- --- FLIERS ----------------------------------------- --- generic flier update: physics + changing position -function flier_update ( flier, dt ) - flier_physics( flier, dt ) -end - -function flier_draw ( flier ) - if ( flier.direction == right ) then - flier.actor:flipX(true) - elseif (flier.direction == left) then - flier.actor:flipX(false) - end - flier.actor:draw( flier.x, flier.y ) -end - ----------------------------------------- --- "PHYSICS" ----------------------------------------- --- "physics" being used verryyyyy lightly here - --- does basics physics work (determines velocity) for a flier -function flier_physics ( flier, dt ) - flier_physics_x( flier, dt ) - flier_physics_y( flier, dt ) -end - -function flier_physics_x ( flier, dt ) - max_vel = 300 - min_vel = -300 - floor = 500 - turn = 300 - - if ( flier.moving ) then - if ( flier.x_vel < max_vel and flier.direction == right ) then - flier.x_vel = flier.x_vel + (max_vel / turn) - elseif ( flier.x_vel > min_vel and flier.direction == left ) then - flier.x_vel = flier.x_vel - (max_vel / turn) - end - else - if ( flier.x_vel > 0 ) then - flier.x_vel = flier.x_vel - (max_vel / (turn * 3)) - elseif ( flier.x_vel < 0 ) then - flier.x_vel = flier.x_vel + (max_vel / (turn * 3)) - end - end - - flier.x = flier.x + flier.x_vel * dt -end - -function flier_physics_y ( flier, dt ) - gravity = .75 - floor = 500 - - -- wing-flap - if ( flier.flying ) then - flier.y_vel = -200 - flier.flying = false - end - - -- gravity - if ( flier.y < floor ) then - flier.y_vel = flier.y_vel + gravity - end - - -- if on ground; stop gravity - if ( flier.y > floor ) then - flier.y = floor - flier.y_vel = 0 - end - - flier.y = flier.y + flier.y_vel * dt + Flier.initialize( self, 50, 100, batActor ) end