Compare commits

..

18 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
83e498534c Merge branch 'master' of https://github.com/sashlilac/cambridge 2021-02-18 12:01:05 -05:00
Ishaan Bhardwaj
8f19c73e2a Simultaneous keyboard and joystick inputs implemented!
Implements #9!!!
2021-02-18 12:00:57 -05:00
Ishaan Bhardwaj
e36b855ff7 The Discord server is no longer sponsored by the project. 2021-02-18 10:42:19 -05:00
Ishaan Bhardwaj
23b58951cb World rule Survival A2 has a lenient torikan time 2021-02-17 22:46:33 -05:00
Ishaan Bhardwaj
1d73916b7c Arika-SRS rulesets no longer lock immediately 2021-02-17 18:29:14 -05:00
Ishaan Bhardwaj
3947e9f02f Fix the drop block lock rotation with SRS 2021-02-17 17:31:16 -05:00
Ishaan Bhardwaj
99b15803ee Adjusted 0 ARR to trigger onPieceMove multiple times 2021-02-17 17:21:51 -05:00
Ishaan Bhardwaj
d350b25726 Forgot to set guideline SRS to always rotate 2021-02-17 14:52:05 -05:00
Ishaan Bhardwaj
44e4d00172 Merge branch 'master' of https://github.com/sashlilac/cambridge 2021-02-17 14:48:42 -05:00
Ishaan Bhardwaj
31e2529265 Upward kicks for SRS count toward rotation limit 2021-02-17 14:48:35 -05:00
Ishaan Bhardwaj
ea7c75f0b3 Cambridge Discord Server temp. decommissioned
Please contact Milla#7746 on Discord for help.
2021-02-17 10:45:07 -05:00
Ishaan Bhardwaj
714c6b5e99 Floorkicks reworked (read comments)
If not classic lock, upward kicks reset to the top of the tile
2021-02-16 23:28:54 -05:00
Ishaan Bhardwaj
6a5d5a9c88 Fixed some modes' getNextPiece routines 2021-02-16 17:02:13 -05:00
Ishaan Bhardwaj
03491ba151 Strategy mode endgame nerfed 2021-02-16 16:57:31 -05:00
Ishaan Bhardwaj
6e22e3d15b Ti-ARS autolock fix 2021-02-16 16:19:51 -05:00
Ishaan Bhardwaj
66ab5992ad Added onPieceMove/Rotate/Drop for gamemodes 2021-02-16 15:27:57 -05:00
Ishaan Bhardwaj
2c07c2a58c BigInt changes, read extended description
Disabled strict type checking, can be re-enabled in bleeding edge. (This is done so bigint ops run faster)
Added a negation method and updated the corresponding metamethod to use it.
2021-02-16 13:03:53 -05:00
Ishaan Bhardwaj
a4d3f3bffc Update README.md 2021-02-16 13:00:07 -05:00
14 changed files with 161 additions and 100 deletions

View File

@@ -5,9 +5,9 @@ Cambridge
Welcome to Cambridge, the next open-source falling-block game engine!
The project is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
The project is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
Credits
-------
@@ -17,19 +17,7 @@ Credits
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
The following people in no particular order also helped with the project:
- [Hailey](https://github.com/haileylgbt)
- CylinderKnot
- MarkGamed7794
- [Mizu](https://github.com/rexxt)
- MattMayuga
- Kitaru
- switchpalacecorner
- [sinefuse](https://github.com/sinefuse)
- [2Tie](https://github.com/2Tie)
- [nightmareci](https://github.com/nightmareci)
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
- [Dr Ocelot](https://github.com/Dr-Ocelot)
More special thanks can be found in-game, under the "Credits" menu.
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)

View File

@@ -2,7 +2,7 @@
-- If this variable is true, then strict type checking is performed for all
-- operations. This may result in slower code, but it will allow you to catch
-- errors and bugs earlier.
local strict = true
local strict = false
--------------------------------------------------------------------------------
@@ -33,12 +33,7 @@ function bigint.new(num)
return bigint.add(lhs, rhs)
end,
__unm = function()
if (self.sign == "+") then
self.sign = "-"
else
self.sign = "+"
end
return self
return bigint.negate(self)
end,
__sub = function(lhs, rhs)
return bigint.subtract(lhs, rhs)
@@ -98,6 +93,18 @@ function bigint.abs(big)
return result
end
-- Return a new big with the same digits but the opposite sign (negation)
function bigint.negate(big)
bigint.check(big)
local result = big:clone()
if (result.sign == "+") then
result.sign = "-"
else
result.sign = "+"
end
return result
end
-- Return the number of digits in the big
function bigint.digits(big)
bigint.check(big)

View File

@@ -31,6 +31,7 @@ end
function ConfigScene:new()
self.input_state = 1
self.key = 1
self.set_inputs = newSetInputs()
self.new_input = {}
self.axis_timer = 0
@@ -62,7 +63,7 @@ function ConfigScene:render()
if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else
love.graphics.print("press key or joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0)
love.graphics.print("press " .. (self.key == 2 and "joystick" or "key") .. " input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0)
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
end
@@ -97,18 +98,28 @@ function ConfigScene:onInputPress(e)
self.new_input = {}
end
elseif e.scancode == "tab" then
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
self.input_state = self.input_state + 1
elseif e.scancode ~= "escape" then
self.set_inputs[configurable_inputs[self.input_state]] =
(
self.set_inputs[configurable_inputs[self.input_state]] == false
and "" or self.set_inputs[configurable_inputs[self.input_state]]
) ..
(self.key == 2 and " / " or "") .. "skipped"
if self.key == 2 then
self.input_state = self.input_state + 1
self.key = 1
else
self.key = 2
end
elseif e.scancode ~= "escape" and self.key == 1 then
-- all other keys can be configured
if not self.new_input.keys then
self.new_input.keys = {}
end
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
self.key = 2
end
elseif string.sub(e.type, 1, 3) == "joy" then
elseif string.sub(e.type, 1, 3) == "joy" and self.key == 2 then
if self.input_state <= table.getn(configurable_inputs) then
if e.type == "joybutton" then
addJoystick(self.new_input, e.name)
@@ -116,11 +127,13 @@ function ConfigScene:onInputPress(e)
self.new_input.joysticks[e.name].buttons = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jbtn " ..
self.set_inputs[configurable_inputs[self.input_state]] ..
" / jbtn " ..
e.button ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].buttons[e.button] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
self.key = 1
elseif e.type == "joyaxis" then
if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
addJoystick(self.new_input, e.name)
@@ -131,11 +144,13 @@ function ConfigScene:onInputPress(e)
self.new_input.joysticks[e.name].axes[e.axis] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jaxis " ..
self.set_inputs[configurable_inputs[self.input_state]] ..
" / jaxis " ..
(e.value >= 1 and "+" or "-") .. e.axis ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
self.key = 1
self.last_axis = e.axis
self.axis_timer = 0
end
@@ -149,11 +164,13 @@ function ConfigScene:onInputPress(e)
self.new_input.joysticks[e.name].hats[e.hat] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jhat " ..
self.set_inputs[configurable_inputs[self.input_state]]
" / jhat " ..
e.hat .. " " .. e.direction ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
self.key = 1
end
end
end

View File

@@ -117,11 +117,15 @@ function Piece:lockIfBottomed(grid)
return self
end
function Piece:addGravity(gravity, grid)
function Piece:addGravity(gravity, grid, classic_lock)
local new_gravity = self.gravity + gravity
if self:isDropBlocked(grid) then
self.gravity = math.min(1, new_gravity)
self.lock_delay = self.lock_delay + 1
if classic_lock then
self.gravity = math.min(1, new_gravity)
else
self.gravity = 0
self.lock_delay = self.lock_delay + 1
end
else
local dropped_squares = math.floor(new_gravity)
local new_frac_gravity = new_gravity - dropped_squares

View File

@@ -137,7 +137,7 @@ function GameMode:update(inputs, ruleset)
-- set attempt flags
if inputs["left"] or inputs["right"] then
self:onAttemptPieceMove(self.piece)
self:onAttemptPieceMove(self.piece, self.grid)
if self.immobile_spin_bonus and self.piece ~= nil then
if not self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
not self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) then
@@ -150,7 +150,7 @@ function GameMode:update(inputs, ruleset)
inputs["rotate_left2"] or inputs["rotate_right2"] or
inputs["rotate_180"]
then
self:onAttemptPieceRotate(self.piece)
self:onAttemptPieceRotate(self.piece, self.grid)
if self.immobile_spin_bonus and self.piece ~= nil then
if self.piece:isDropBlocked(self.grid) and
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
@@ -193,6 +193,7 @@ function GameMode:update(inputs, ruleset)
-- diff vars to use in checks
local piece_y = self.piece.position.y
local piece_x = self.piece.position.x
local piece_rot = self.piece.rotation
ruleset:processPiece(
@@ -203,6 +204,7 @@ function GameMode:update(inputs, ruleset)
)
local piece_dy = self.piece.position.y - piece_y
local piece_dx = self.piece.position.x - piece_x
local piece_drot = self.piece.rotation - piece_rot
-- das cut
@@ -214,12 +216,13 @@ function GameMode:update(inputs, ruleset)
inputs.rotate_180
))
) then
self.das.frames = math.max(
self.das.frames - self:getDasCutDelay(),
-(self:getDasCutDelay() + 1)
)
self:dasCut()
end
if (piece_dx ~= 0) then self:onPieceMove(self.piece, self.grid) end
if (piece_drot ~= 0) then self:onPieceRotate(self.piece, self.grid) end
if (piece_dy ~= 0) then self:onPieceDrop(self.piece, self.grid) end
if inputs["up"] == true and
self.piece:isDropBlocked(self.grid) and
not self.hard_drop_locked then
@@ -294,8 +297,11 @@ end
-- event functions
function GameMode:whilePieceActive() end
function GameMode:onAttemptPieceMove(piece) end
function GameMode:onAttemptPieceRotate(piece) end
function GameMode:onAttemptPieceMove(piece, grid) end
function GameMode:onAttemptPieceRotate(piece, grid) end
function GameMode:onPieceMove(piece, grid) end
function GameMode:onPieceRotate(piece, grid) end
function GameMode:onPieceDrop(piece, grid) end
function GameMode:onPieceLock(piece, cleared_row_count)
playSE("lock")
end
@@ -390,6 +396,13 @@ function GameMode:chargeDAS(inputs)
end
end
function GameMode:dasCut()
self.das.frames = math.max(
self.das.frames - self:getDasCutDelay(),
-(self:getDasCutDelay() + 1)
)
end
function GameMode:areCancel(inputs, ruleset)
if ruleset.are_cancel and self.piece_hard_dropped and
not self.prev_inputs.up and
@@ -773,14 +786,15 @@ function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
for section, time in pairs(self.section_times) do
if section > 0 then
love.graphics.setColor(self:sectionColourFunction(section))
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
split_time = split_time + time
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
end
end
if (current_section <= section_limit) then
love.graphics.setColor(self:sectionColourFunction(current_section))
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
end

View File

@@ -52,23 +52,14 @@ function StrategyGame:getLineClearDelay()
end
function StrategyGame:getLockDelay()
if self.level < 500 then return 8
elseif self.level < 700 then return 6
else return 4 end
if self.level < 700 then return 8
else return 6 end
end
function StrategyGame:getGravity()
return 20
end
function StrategyGame:getNextPiece(ruleset)
return {
skin = "2tie",
shape = self.randomizer:nextPiece(),
orientation = ruleset:getDefaultOrientation(),
}
end
function StrategyGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1

View File

@@ -94,12 +94,8 @@ function Survival2020Game:getGravity()
return 20
end
function Survival2020Game:getNextPiece(ruleset)
return {
skin = self.level >= 1000 and "bone" or "2tie",
shape = self.randomizer:nextPiece(),
orientation = ruleset:getDefaultOrientation(),
}
function Survival2020Game:getSkin()
return self.level >= 1000 and "bone" or "2tie"
end
function Survival2020Game:hitTorikan(old_level, new_level)

View File

@@ -30,6 +30,11 @@ function SurvivalA2Game:new()
self.lock_hard_drop = true
end
function SurvivalA2Game:initialize(ruleset)
SurvivalA2Game.super.initialize(self, ruleset)
self.world = ruleset.world
end
function SurvivalA2Game:getARE()
if self.level < 100 then return 18
elseif self.level < 300 then return 14
@@ -69,7 +74,8 @@ function SurvivalA2Game:getGravity()
end
function SurvivalA2Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,25) then
local torikan_time = self.world and frameTime(3,55) or frameTime(3,25)
if old_level < 500 and new_level >= 500 and self.frames > torikan_time then
self.level = 500
return true
end

View File

@@ -21,14 +21,18 @@ SRS.spawn_above_field = true
SRS.MANIPULATIONS_MAX = 128
function SRS:onPieceRotate(piece, grid)
function SRS:onPieceRotate(piece, grid, upward)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
if upward or piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= self.MANIPULATIONS_MAX then
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.manipulations < self.MANIPULATIONS_MAX
end
return SRS

View File

@@ -96,6 +96,9 @@ end
function ARS:onPieceRotate(piece, grid)
if piece.floorkick >= 1 then
piece.floorkick = piece.floorkick + 1
if piece:isDropBlocked(grid) then
piece.locked = true
end
end
end

View File

@@ -115,7 +115,9 @@ function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
local was_drop_blocked = piece:isDropBlocked(grid)
self:attemptRotate(new_inputs, piece, grid, initial)
if self:canPieceRotate(piece, grid) then
self:attemptRotate(new_inputs, piece, grid, initial)
end
if not was_drop_blocked and piece:isDropBlocked(grid) then
playSE("bottom")
@@ -160,28 +162,43 @@ function Ruleset:attemptWallkicks(piece, new_piece, rot_dir, grid)
end
function Ruleset:movePiece(piece, grid, move, instant)
local x = piece.position.x
if not self:canPieceMove(piece, grid) then return end
local was_drop_blocked = piece:isDropBlocked(grid)
local offset = ({x=0, y=0})
local moves = 0
if move == "left" then
piece:moveInGrid({x=-1, y=0}, 1, grid, false)
offset.x = -1
moves = 1
elseif move == "right" then
piece:moveInGrid({x=1, y=0}, 1, grid, false)
offset.x = 1
moves = 1
elseif move == "speedleft" then
piece:moveInGrid({x=-1, y=0}, grid.width, grid, instant)
offset.x = -1
moves = grid.width
elseif move == "speedright" then
piece:moveInGrid({x=1, y=0}, grid.width, grid, instant)
offset.x = 1
moves = grid.width
end
if piece.position.x ~= x then
self:onPieceMove(piece, grid)
if not was_drop_blocked and piece:isDropBlocked(grid) then
playSE("bottom")
for i = 1, moves do
local x = piece.position.x
if moves ~= 1 then
piece:moveInGrid(offset, 1, grid, instant)
else
piece:moveInGrid(offset, 1, grid, false)
end
if piece.position.x ~= x then
self:onPieceMove(piece, grid)
if piece.locked then break end
end
end
if not was_drop_blocked and piece:isDropBlocked(grid) then
playSE("bottom")
end
end
function Ruleset:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity
hard_drop_enabled, additive_gravity, classic_lock
)
if piece.big then
gravity = gravity / 2
@@ -191,18 +208,18 @@ function Ruleset:dropPiece(
local y = piece.position.y
if inputs["down"] == true and drop_locked == false then
if additive_gravity then
piece:addGravity(gravity + drop_speed, grid)
piece:addGravity(gravity + drop_speed, grid, classic_lock)
else
piece:addGravity(math.max(gravity, drop_speed), grid)
piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
end
elseif inputs["up"] == true and hard_drop_enabled == true then
if hard_drop_locked == true or piece:isDropBlocked(grid) then
piece:addGravity(gravity, grid)
piece:addGravity(gravity, grid, classic_lock)
else
piece:dropToBottom(grid)
end
else
piece:addGravity(gravity, grid)
piece:addGravity(gravity, grid, classic_lock)
end
if piece.position.y ~= y then
self:onPieceDrop(piece, grid)
@@ -303,11 +320,13 @@ function Ruleset:processPiece(
end
self:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity
hard_drop_enabled, additive_gravity, classic_lock
)
self:lockPiece(piece, grid, lock_delay, classic_lock)
end
function Ruleset:canPieceMove(piece, grid) return true end
function Ruleset:canPieceRotate(piece, grid) return true end
function Ruleset:onPieceMove(piece) end
function Ruleset:onPieceRotate(piece) end
function Ruleset:onPieceDrop(piece) end

View File

@@ -92,11 +92,14 @@ function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
self:checkNewLow(piece)
piece.manipulations = piece.manipulations + 1
if piece:isDropBlocked(grid) then
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
piece.locked = true
end
end
if piece.manipulations >= self.MANIPULATIONS_MAX then
piece:moveInGrid({ x = 0, y = 1 }, 1, grid)
if piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate() return true end
return SRS

View File

@@ -54,7 +54,7 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid)
self:onPieceRotate(piece, grid, offset.y < 0)
return
end
end
@@ -69,7 +69,10 @@ end
function SRS:onPieceDrop(piece, grid)
self:checkNewLow(piece)
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
if (
piece.manipulations >= self.MANIPULATIONS_MAX or
piece.rotations >= self.ROTATIONS_MAX
) and piece:isDropBlocked(grid) then
piece.locked = true
else
piece.lock_delay = 0 -- step reset
@@ -86,16 +89,18 @@ function SRS:onPieceMove(piece, grid)
end
end
function SRS:onPieceRotate(piece, grid)
function SRS:onPieceRotate(piece, grid, upward)
piece.lock_delay = 0 -- rotate reset
self:checkNewLow(piece)
if piece.rotations >= self.ROTATIONS_MAX then
piece.rotations = piece.rotations + 1
piece:moveInGrid({ x = 0, y = 1 }, 1, grid)
if piece:isDropBlocked(grid) then
piece.locked = true
end
end
if upward or piece:isDropBlocked(grid) then
piece.rotations = piece.rotations + 1
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.rotations < self.ROTATIONS_MAX
end
function SRS:get180RotationValue() return 2 end

View File

@@ -150,9 +150,9 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid, offset.y < 0)
return
end
end
@@ -182,16 +182,20 @@ function SRS:onPieceMove(piece, grid)
end
end
function SRS:onPieceRotate(piece, grid)
function SRS:onPieceRotate(piece, grid, upward)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
if upward or piece:isDropBlocked(grid) then
piece.rotations = piece.rotations + 1
if piece.rotations >= self.ROTATIONS_MAX then
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.rotations < self.ROTATIONS_MAX
end
function SRS:get180RotationValue() return 3 end
return SRS