Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
freem
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Openai/690c794b-14ac-8002-a146-ba84ec97f888
(section)
Add languages
Page
Discussion
English
Read
Edit
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
Edit source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
= = <syntaxhighlight lang="lua"> -- ModuleScript: GameCore (paste into ServerScriptService as ModuleScript named GameCore) -- Luau / Roblox์ฉ. ์๋ฒ์์ ์๋ํ๋๋ก ์ค๊ณ๋จ. -- ํต์ฌ: ์๊ฐ ๊ธฐ๋ฐ ๋ฒ์ฃ์ vs ํด ๊ธฐ๋ฐ ํ์ฌ, AP/์๊ฐ ์ฝ์คํธ, ์ฆ๊ฑฐ ๊ฐ์ , ๋์ ๋์ด๋ ๋ณด์ , ์๋ฎฌ๋ ์ดํฐ ํฌํจ local GameCore = {} GameCore.__index = GameCore -- ========================= -- Balance / config -- ========================= local Balance = { -- Preset: "mid" (recommended) preset = "mid", presets = { fast = { totalTime = 90, detectiveTurns = 6, turnSeconds = 15 }, mid = { totalTime = 180, detectiveTurns = 9, turnSeconds = 20 }, slow = { totalTime = 300, detectiveTurns = 12, turnSeconds = 25 }, }, -- AP model (per-turn AP for detective) perTurnAP = 100, -- Action cost table (AP, time seconds) actionCosts = { scan = {cost = 25, time = 8}, photo = {cost = 15, time = 4}, collect = {cost = 60, time = 30}, -- ์ฑ์ฆ cctv = {cost = 80, time = 20}, interview_short = {cost = 40, time = 15}, interview_long = {cost = 100, time = 45}, }, -- crime actions (time to complete; criminal acts in real seconds) crimeActions = { steal = 30, partialDestroy = 20, fullDestroy = 45, escapeStep = 25, }, -- evidence decay: per second percentage of remaining quality lost (e.g. 0.001 = 0.1%/s) evidenceDecayPerSecond = 0.0015, -- ์กฐ์ ๊ฐ๋ฅ (๋น ๋ฅด๋ฉด ํ์ฌ ๋ถ๋ฆฌ) -- dynamic difficulty parameters targetWinrate = 0.5, dd_adjustment_step = 0.05, -- ์น๋ฅ ๋ณด์ ๋จ์ (ยฑ5%) } -- Helper: get preset local function getPreset() return Balance.presets[Balance.preset] end -- ========================= -- Evidence object -- ========================= local Evidence = {} Evidence.__index = Evidence function Evidence.new(typeName, baseQuality) local self = setmetatable({}, Evidence) self.typeName = typeName or "generic" self.quality = baseQuality or 1.0 -- 1.0 == 100% self.createdAt = tick() return self end function Evidence:updateDecay(now) local dt = now - self.createdAt -- exponential-ish decay approximation: quality *= (1 - r)^dt local r = Balance.evidenceDecayPerSecond local newQuality = self.quality * (1 - r) ^ math.max(0, dt) self.quality = math.clamp(newQuality, 0, 1) self.createdAt = now end function Evidence:destroy(completeness) -- completeness 0..1 completeness = math.clamp(completeness or 1, 0, 1) self.quality = self.quality * (1 - completeness) end -- ========================= -- Actor base (criminal / detective) -- ========================= local Actor = {} Actor.__index = Actor function Actor.new(name) local self = setmetatable({}, Actor) self.name = name or "Actor" return self end -- Detective subclass local Detective = setmetatable({}, Actor) Detective.__index = Detective function Detective.new(name) local self = Actor.new(name) setmetatable(self, Detective) self.ap = Balance.perTurnAP self.collected = {} -- evidence references self.turnsLeft = getPreset().detectiveTurns return self end function Detective:startTurn() self.ap = Balance.perTurnAP -- turn countdown handled by engine end function Detective:doAction(actionName, evidencePool, now) local costs = Balance.actionCosts[actionName] if not costs then return false, "unknown action" end if self.ap < costs.cost then return false, "not enough AP" end self.ap = self.ap - costs.cost -- action effect simplified: some actions can reveal or collect evidence if actionName == "scan" then -- scanning slightly increases chance to spot low-quality evidence for _, ev in pairs(evidencePool) do ev:updateDecay(now) if ev.quality > 0.1 then table.insert(self.collected, ev) end end return true, "scan done" elseif actionName == "collect" then -- collect highest quality evidence local best, bestIdx = nil, nil for i, ev in pairs(evidencePool) do ev:updateDecay(now) if not best or ev.quality > best.quality then best = ev; bestIdx = i end end if best then table.insert(self.collected, best) table.remove(evidencePool, bestIdx) return true, "collected" else return false, "no evidence" end elseif actionName == "cctv" then -- simulated delay is ignored here; we just mark an intel token table.insert(self.collected, Evidence.new("cctv", 0.9)) return true, "cctv gained" elseif actionName:match("^interview") then -- interviews give lower-quality evidence (testimony) table.insert(self.collected, Evidence.new("testimony", 0.5)) return true, "interview done" elseif actionName == "photo" then table.insert(self.collected, Evidence.new("photo", 0.6)) return true, "photo done" else return false, "no effect" end end -- Criminal subclass local Criminal = setmetatable({}, Actor) Criminal.__index = Criminal function Criminal.new(name) local self = Actor.new(name) setmetatable(self, Criminal) self.progress = 0 -- 0..totalTime self.actions = {} -- list of planned actions (for simulation) return self end function Criminal:performAction(actionName, evidencePool, now) local t = Balance.crimeActions[actionName] or 10 self.progress = self.progress + t -- action effects if actionName == "steal" then table.insert(evidencePool, Evidence.new("stolen_trace", 0.9)) elseif actionName == "partialDestroy" then -- reduce one evidence partially if #evidencePool > 0 then local ev = evidencePool[math.random(1, #evidencePool)] ev:destroy(0.5) end elseif actionName == "fullDestroy" then if #evidencePool > 0 then local ev = table.remove(evidencePool, math.random(1, #evidencePool)) -- removed fully end elseif actionName == "escapeStep" then -- maybe plant a fake clue table.insert(evidencePool, Evidence.new("fake_lead", 0.7)) end return t -- time consumed end -- ========================= -- Game Engine -- ========================= function GameCore.new(opts) opts = opts or {} local self = setmetatable({}, GameCore) self.balance = Balance if opts.preset then if Balance.presets[opts.preset] then Balance.preset = opts.preset end end local p = getPreset() self.totalTime = p.totalTime self.timeNow = 0 self.evidencePool = {} -- list of Evidence objects self.criminal = Criminal.new("Criminal") self.detective = Detective.new("Detective") self.log = {} -- dynamic difficulty state self.dd_offset = 0 -- positive favors detective, negative favors criminal return self end function GameCore:logEvent(s) table.insert(self.log, {t = self.timeNow, text = s}) end -- tick-based simple simulation function (not tied to Roblox Heartbeat here; this is a discrete sim) function GameCore:simulateMatch(criminalPlan) -- criminalPlan: array of actions in order (e.g. {"steal","partialDestroy","escapeStep"}) self.timeNow = 0 self.evidencePool = {} self.criminal.progress = 0 self.detective.collected = {} self.detective.turnsLeft = getPreset().detectiveTurns self.detective.ap = Balance.perTurnAP self.log = {} local totalTime = self.totalTime -- For simplicity: criminal acts continuously, detective acts by turns spaced out evenly local detectiveTurnInterval = totalTime / getPreset().detectiveTurns local nextDetectiveTurnAt = detectiveTurnInterval local criminalActionIndex = 1 self:logEvent("Match start") while self.timeNow < totalTime do -- Criminal takes small step if criminalActionIndex <= #criminalPlan then local act = criminalPlan[criminalActionIndex] local tcost = Balance.crimeActions[act] or 10 -- apply action local consumed = self.criminal:performAction(act, self.evidencePool, self.timeNow) self:logEvent("Criminal did " .. act .. " consumed:" .. tostring(consumed)) -- advance time by the action time or until next detective turn, whichever smaller local advance = math.min(consumed, nextDetectiveTurnAt - self.timeNow) self.timeNow = self.timeNow + advance -- if criminal's action extends past the detective turn, we will continue loop if advance < consumed then -- partial action: remaining of that criminal action continues next loop iteration (we won't model partial here for simplicity) else criminalActionIndex = criminalActionIndex + 1 end else -- no more planned criminal actions -> just progress time local advance = math.min(nextDetectiveTurnAt - self.timeNow, totalTime - self.timeNow) self.timeNow = self.timeNow + math.max(0.1, advance) end -- periodically decay evidence for _, ev in pairs(self.evidencePool) do ev:updateDecay(self.timeNow) end -- Detective turn? if self.timeNow >= nextDetectiveTurnAt and self.detective.turnsLeft > 0 then -- start detective turn self.detective:startTurn() self.detective.turnsLeft = self.detective.turnsLeft - 1 -- Very simple detective policy for simulation: try collect -> cctv -> interview_short -> scan local acted = false local now = self.timeNow local tryOrder = {"collect", "cctv", "interview_short", "scan", "photo"} for _, act in ipairs(tryOrder) do local ok, msg = self.detective:doAction(act, self.evidencePool, now) if ok then self:logEvent("Detective did " .. act .. " -> " .. tostring(msg)) acted = true break end end -- if did nothing due to AP, skip nextDetectiveTurnAt = nextDetectiveTurnAt + detectiveTurnInterval end -- break if criminal progress >= totalTime (criminal wins) if self.criminal.progress >= totalTime then self:logEvent("Criminal reached goal at t=" .. tostring(self.timeNow)) break end -- safety: avoid infinite loop if #self.log > 5000 then break end end -- Determine winner heuristics: -- If detective collected >= 2 high-quality evidence items or any evidence quality > 0.7 => detective wins local detectiveScore = 0 for _, ev in pairs(self.detective.collected) do detectiveScore = detectiveScore + ev.quality end local criminalWin = (self.criminal.progress >= totalTime) and (detectiveScore < 1.2) local winner = criminalWin and "criminal" or "detective" self:logEvent("Match end. Winner: " .. winner .. " (detectiveScore=" .. tostring(detectiveScore) .. ")") return { winner = winner, log = self.log, detectiveScore = detectiveScore, evidencePool = self.evidencePool, } end -- ========================= -- Simple batch simulator for tuning -- ========================= function GameCore:batchSimulate(plan, runs) runs = runs or 100 local results = {criminal = 0, detective = 0} for i=1, runs do local r = self:simulateMatch(plan) results[r.winner] = results[r.winner] + 1 end results.criminal = results.criminal / runs results.detective = results.detective / runs return results end -- Expose constructors for tests GameCore.Balance = Balance GameCore.NewEvidence = Evidence.new GameCore.New = GameCore.new return GameCore </syntaxhighlight> <blockquote></blockquote> <syntaxhighlight lang="lua"> local GameCoreModule = require(game.ServerScriptService.GameCore) local core = GameCoreModule.New({preset = "mid"}) local plan = {"steal", "partialDestroy", "escapeStep", "fullDestroy"} local out = core:simulateMatch(plan) print("Winner:", out.winner) for i, e in ipairs(out.log) do print(i, e.t, e.text) end -- batch sample local stats = core:batchSimulate(plan, 50) print("Winrate criminal:", stats.criminal, "detective:", stats.detective) </syntaxhighlight>
Summary:
Please note that all contributions to freem are considered to be released under the Creative Commons Attribution-ShareAlike 4.0 (see
Freem:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)