Use middleclass for OOP
BIN
art/sprites/dad
Before Width: | Height: | Size: 898 B |
Before Width: | Height: | Size: 549 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 543 B |
|
@ -1,368 +0,0 @@
|
|||
local baton = {
|
||||
_VERSION = 'Baton v1.0.1',
|
||||
_DESCRIPTION = 'Input library for LÖVE.',
|
||||
_URL = 'https://github.com/tesselode/baton',
|
||||
_LICENSE = [[
|
||||
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.
|
||||
]]
|
||||
}
|
||||
|
||||
-- string parsing functions --
|
||||
|
||||
-- splits a source definition into type and value
|
||||
-- example: 'button:a' -> '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
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Semyon Entsov <swalrus@yandex.ru>
|
||||
|
||||
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.
|
|
@ -1,94 +0,0 @@
|
|||
# center
|
||||
Center is a simple library that dynamically aligns content to the center of game window.
|
||||
|
||||
<img src="https://github.com/S-Walrus/center/blob/master/screenshots/center.png?raw=true" width="256">
|
||||
|
||||
## 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.
|
|
@ -1,119 +0,0 @@
|
|||
--[[
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Semyon Entsov <swalrus@yandex.ru>
|
||||
|
||||
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
|
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB |
|
@ -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
|
|
@ -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(<name>)` have no superclass now)
|
||||
* Removed default method `Class:implements(<mixin>)`
|
||||
* 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
|
||||
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
|
|
@ -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(<name>)` 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`.
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
|
295
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
|
||||
|
|