Quick actions

cmd+k|ctrl+k

Navigation

Languages

minewswep

Snippet info

Language

Lua

Visibility

public

Author

legacy-v1-15894

Created

2024-04-10T00:10:34.432992Z

Updated

2024-12-09T05:16:44.418058Z

-- ceat_ceat
-- missing textures and colors LOL
-- original https://pastebin.com/KAjDQugX
-- whitelist feature + some code cleanup by datz
warn("mineswep by ceat yay")

if game:GetService("RunService"):IsStudio() then
	require(game.ServerStorage.lsbenv)()
	owner,printf,warnf,LoadLibrary,LoadAssets,NLS=owner,printf,warnf,LoadLibrary,LoadAssets,NLS
end


local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")

local DEFAULT_TILE_WIDTH = 1.5
local FIELD_RADIUS = 1
local RESTART_TIME = 3

local UNOPENED_TEX = "rbxassetid://15144071117"
local OPENED_TEX = "rbxassetid://13348346487"
local MINE_TEX = "rbxassetid://13348250535"
local FLAG_TEX = "rbxassetid://10720664742"
local BLANK_TEX = "rbxassetid://12381187927"
local NUMBER_FONT = Enum.Font.SourceSansBold
local WIN_MINE_COLOR = Color3.new(0.5, 1, 0.5)

local DIG_SOUND = "rbxassetid://3498493622"
local FLAG_SOUND = "rbxassetid://8388724806"
local CORRECT_SOUND = "rbxassetid://3422389728"
local YAY_SOUND = "rbxassetid://9068897474"
local ALARM_SOUND = "rbxassetid://2778386920"
local EXPLODE_SOUND = "rbxassetid://165969964"

local COLORS = {
	Color3.fromRGB(0, 0, 255),
	Color3.fromRGB(39, 107, 31),
	Color3.fromRGB(255, 0, 0),
	Color3.fromRGB(0, 0, 130),
	Color3.fromRGB(157, 0, 0),
	Color3.fromRGB(0, 214, 146),
	Color3.fromRGB(98, 0, 168),
	Color3.fromRGB(100, 100, 100),
}

local FLASH_TWEENINFO = TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local WIN_FLASH_TWEENINFO = TweenInfo.new(0.75, Enum.EasingStyle.Linear, Enum.EasingDirection.In)

local whitelist = false
local whitelisted = {}

local settings = {
	tileWidth = DEFAULT_TILE_WIDTH,
	use3DFlags = false,
	width = 25,
	height = 25,
	mineCount = 90,
	debug = false
}

local currentGame

local baseTilePart do
	baseTilePart = Instance.new("Part")
	baseTilePart.Anchored = true

	local tileDecal = Instance.new("Decal")
	tileDecal.Name = "TileTexture"
	tileDecal.Texture = UNOPENED_TEX
	tileDecal.Face = Enum.NormalId.Top
	tileDecal.Parent = baseTilePart

	local clickDetector = Instance.new("ClickDetector")
	clickDetector.Name = "ClickDetector"
	clickDetector.MaxActivationDistance = 30
	clickDetector.Parent = baseTilePart
end

local function createTilePart(cf, tileWidth)
	local part = baseTilePart:Clone()
	part.CFrame = cf
	part.Size = Vector3.new(tileWidth, 0.5, tileWidth)
	return part
end

local baseFlagDecal do
	baseFlagDecal = Instance.new("Decal")
	baseFlagDecal.Name = "FlagDecal"
	baseFlagDecal.Texture = FLAG_TEX
	baseFlagDecal.Face = Enum.NormalId.Top
	baseFlagDecal.ZIndex = 5
end

local function createFlagDecal(part)
	local decal = baseFlagDecal:Clone()
	decal.Parent = part
	return decal
end

local baseNumberSurface, baseTileFlashDecal do
	baseNumberSurface = Instance.new("SurfaceGui")
	baseNumberSurface.Name = "NumberFace"
	baseNumberSurface.Face = Enum.NormalId.Top
	baseNumberSurface.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
	baseNumberSurface.PixelsPerStud = 25

	local label = Instance.new("TextLabel")
	label.Name = "NumberLabel"
	label.Font = NUMBER_FONT
	label.TextScaled = true
	label.BackgroundTransparency = 1
	label.AnchorPoint = Vector2.new(0.5, 0.5)
	label.Position = UDim2.fromScale(0.5, 0.5)
	label.Size = UDim2.fromScale(2, 2)
	label.TextTransparency = 1
	label.Rotation = -90
	label.Parent = baseNumberSurface

	baseTileFlashDecal = Instance.new("Decal")
	baseTileFlashDecal.Texture = BLANK_TEX
	baseTileFlashDecal.Face = Enum.NormalId.Top
	baseTileFlashDecal.ZIndex = 5
	baseTileFlashDecal.Transparency = 0
	baseTileFlashDecal.Parent = baseNumberSurface
end

local function displayNumber(tile)
	local surfaceGui = baseNumberSurface:Clone()
	local label = surfaceGui.NumberLabel
	local flashDecal = baseTileFlashDecal:Clone()

	label.Text = tostring(tile.minesAround)
	label.TextColor3 = COLORS[tile.minesAround]

	flashDecal.Parent = tile.part
	label.Parent = surfaceGui
	surfaceGui.Parent = tile.part

	TweenService:Create(flashDecal, FLASH_TWEENINFO, { Transparency = 1 }):Play()
	TweenService:Create(label, TweenInfo.new(0.2), {
		TextTransparency = 0
	}):Play()
	TweenService:Create(label, TweenInfo.new(0.2 + math.random()*0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
		Size = UDim2.fromScale(1, 1)
	}):Play()
	Debris:AddItem(flashDecal, FLASH_TWEENINFO.Time)
end

local function iterTiles(tiles)
	local width = #tiles
	local height = #tiles[1]
	local x = 1
	local y = 1
	return function()
		if x > width then
			x = 1
			y += 1
			if y > height then
				return
			end
		end
		local x1 = x
		x += 1
		return x1, y, tiles[x1][y]
	end
end

local function disableTile(tile)
	tile.enabled = false
	tile.leftClickConnection:Disconnect()
	tile.rightClickConnection:Disconnect()
	tile.leftClickConnection = nil
	tile.rightClickConnection = nil
	tile.clickDetector:Destroy()
end

local function unflagTile(tile)
	if tile.flagInstance then
		tile.flagInstance:Destroy()
		tile.flagInstance = nil
	end
end

local function getTilesToFlash(tiles, x, y, offset)
	local flashyGuys = {}
	-- top left -> top right
	for xOff = -offset, offset do
		local tile = tiles[x - xOff] and tiles[x - xOff][y - offset]
		if tile then
			table.insert(flashyGuys, tile)
		end
	end
	-- bottom left -> bottom right
	for xOff = -offset, offset do
		local tile = tiles[x + xOff] and tiles[x + xOff][y + offset]
		if tile and not table.find(flashyGuys, tile) then
			table.insert(flashyGuys, tile)
		end
	end
	-- top left -> bottom left
	if tiles[x - offset] then
		for yOff = -offset, offset do
			local tile = tiles[x - offset][y + yOff]
			if tile and not table.find(flashyGuys, tile) then
				table.insert(flashyGuys, tile)
			end
		end
	end
	-- top right -> bottom right
	if tiles[x + offset] then
		for yOff = -offset, offset do
			local tile = tiles[x + offset][y + yOff]
			if tile and not table.find(flashyGuys, tile) then
				table.insert(flashyGuys, tile)
			end
		end
	end
	return flashyGuys
end

local function flashTilesWave(tiles, tile, width, height, winner, color)
	local baseX = tile.x
	local baseY = tile.y
	for i = 0, math.max(width, height) do
		for _, tile in getTilesToFlash(tiles, baseX, baseY, i) do
			if tile.isFlagged then
				tile.isFlagged = false
				unflagTile(tile)
			end
			if winner then
				tile.tileTexture.Texture = OPENED_TEX
			else
				if tile.isMine then
					tile.tileTexture.Texture = MINE_TEX
				end
			end
			tile.tileTexture.Color3 = color
			if not tile.isMine then
				TweenService:Create(tile.tileTexture, WIN_FLASH_TWEENINFO, { Color3 = Color3.new(1, 1, 1) }):Play()
			end
		end
		task.wait()
	end
end

local function playSound(part, soundId, pitch)
	local sound = Instance.new("Sound")
	sound.SoundId = soundId
	sound.Pitch = pitch
	sound.Parent = part
	sound:Play()
	Debris:AddItem(sound, 4)
	return sound
end

local function isWhitelisted(plr)
	if whitelist and not whitelisted[plr.UserId] and plr ~= owner then
		return false
	end
	return true
end

-- debug visuals

local function highlightTile(tile, color)
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.CanTouch = false
	part.CanQuery = false
	part.Material = Enum.Material.Neon
	part.Color = color
	part.Size = Vector3.new(settings.tileWidth, 0.7, settings.tileWidth)
	part.Transparency = 0.8
	part.CFrame = tile.part.CFrame
	return part
end

local function showNeighbors(tiles, tile)
	local main = highlightTile(tile, Color3.new(1, 0, 0))
	main.Parent = script

	local neighborParts = {}
	for _, position in tile.neighbors do
		local part = highlightTile(tiles[position.x][position.y], Color3.new(0, 0, 1))
		part.Parent = script
		table.insert(neighborParts, part)
	end
	task.wait()
	main:Destroy()
	for _, part in neighborParts do
		part:Destroy()
	end
end

-- game wow

local restartTask

local function restart(spawnCF, width, height, numMines)
	restartTask = task.delay(RESTART_TIME, function()
		restartTask = nil
		stopGame(currentGame)
		currentGame = createGame(spawnCF, width, height, numMines)
	end)
end

function createGame(spawnCF, width, height, numMines)
	local gameObject = {}

	local gameSettings = table.clone(settings)
	gameObject.settings = gameSettings

	local model = Instance.new("Model")
	model.Name = "Minesweeper"
	gameObject.model = model

	local tiles = {}
	gameObject.tiles = tiles
	
	local active = true
	local didFirstClick = false

	local function layMines(clickedTile)
		local validPositions = {}
		-- filter out start field
		for x, y, tile in iterTiles(tiles) do
			if math.abs(x - clickedTile.x) <= FIELD_RADIUS and math.abs(y - clickedTile.y) <= FIELD_RADIUS then
				continue
			end
			table.insert(validPositions, { x = x, y = y })
		end
		for i = 1, math.min(numMines, #validPositions) do
			local pos = table.remove(validPositions, math.random(#validPositions))
			local tile = tiles[pos.x][pos.y]
			tile.isMine = true
			if gameSettings.debug then
				tile.tileTexture.Color3 = Color3.new(1, 0.5, 0.5)
			end
		end
		-- count mines around tiles 
		for x, y, tile in iterTiles(tiles) do
			for _, position in tile.neighbors do
				if tiles[position.x][position.y].isMine then
					tile.minesAround += 1
				end
			end
			if gameSettings.debug then
				showNeighbors(tiles, tile)
			end
		end
	end

	local function onLose(plr, tile)
		active = false
		for x, y, tile in iterTiles(tiles) do
			if tile.enabled then
				disableTile(tile)
			end
			if tile.isMine then
				tile.tileTexture.Texture = MINE_TEX
			end
		end
		tile.part.CFrame *= CFrame.new(0, 1, 0)
		tile.part.Anchored = false
		tile.part.Velocity  = Vector3.new(-3 + math.random()*6, math.random(20, 30), -3 + math.random()*6)
		tile.part.RotVelocity = Vector3.new(math.random(-10, 10), math.random(-10, 10), math.random(-10, 10))
		playSound(tile.part, ALARM_SOUND, 1).TimePosition = 0.35
		playSound(tile.part, EXPLODE_SOUND, 1)

		local explod = Instance.new("Explosion")
		explod.BlastPressure = 0
		explod.BlastRadius = 0
		explod.Position = tile.part.Position
		explod.Parent = workspace
		
		flashTilesWave(tiles, tile, width, height, false, Color3.new(1, 0.5, 0.5))
		restart(spawnCF, width, height, numMines)
		warn(`{plr} lost the game`)
	end

	local function onWin(tile)
		for x, y, tile in iterTiles(tiles) do
			if tile.enabled then
				disableTile(tile)
			end
		end
		playSound(tile.part, CORRECT_SOUND, 1)
		playSound(tile.part, YAY_SOUND, 1)
		flashTilesWave(tiles, tile, width, height, true, WIN_MINE_COLOR)
		restart(spawnCF, width, height, numMines)
	end

	local mineTile -- scream

	local function chordTile(plr, tile)
		local unflaggedTiles = {}
		local numFlagged = 0
		for _, position in tile.neighbors do
			local otherTile = tiles[position.x][position.y]
			if otherTile.opened then continue end
			if otherTile.isFlagged then
				numFlagged += 1
			else
				table.insert(unflaggedTiles, otherTile)
			end
		end
		if numFlagged == tile.minesAround then
			for _, otherTile in unflaggedTiles do
				mineTile(plr, otherTile)
			end
		end
	end

	function mineTile(plr, tile)
		if not active then
			return
		end
		if tile.isMine then
			onLose(plr, tile)
			return
		end
		if tile.opened then
			chordTile(plr, tile)
			return
		end
		if tile.isFlagged then
			tile.isFlagged = false
			unflagTile(tile)
		end
		tile.opened = true
		tile.tileTexture.Texture = OPENED_TEX
		if tile.minesAround == 0 then
			disableTile(tile)
			for _, position in tile.neighbors do
				local otherTile = tiles[position.x][position.y]
				if otherTile.opened then continue end
				mineTile(plr, otherTile)
			end
		else
			displayNumber(tile)
		end
	end

	local function checkWin()
		local numNonMines = 0
		local numOpened = 0
		for x, y, tile in iterTiles(tiles) do
			if not tile.isMine then
				numNonMines += 1
			end
			if tile.opened then
				numOpened += 1 
			end
		end
		return numNonMines == numOpened
	end

	local function onLeftClick(tile)
		return function(plr)
			if not isWhitelisted(plr) then
				return
			end
			if tile.isFlagged then
				return
			end
			if not didFirstClick then
				didFirstClick = true
				layMines(tile)
			end
			playSound(tile.part, DIG_SOUND, 0.9 + math.random()*0.2)
			mineTile(plr, tile)
			if checkWin() then
				onWin(tile)
			end
		end
	end

	local function onRightClick(tile)
		return function(plr)
			if not isWhitelisted(plr) then
				return
			end
			if tile.opened then
				return
			end
			tile.isFlagged = not tile.isFlagged
			playSound(tile.part, FLAG_SOUND, 0.8 + math.random()*0.4)
			if tile.isFlagged then
				if gameSettings.use3DFlags then
					-- not implemented
				else
					tile.flagInstance = createFlagDecal(tile.part)
				end
			else
				unflagTile(tile)
			end
		end
	end

	local originCF = spawnCF * CFrame.new(-width/2*gameSettings.tileWidth, 0, -height/2*gameSettings.tileWidth)
	for x = 1, width do
		local row = {}
		for y = 1, height do
			local tileCF = originCF * CFrame.new(x*gameSettings.tileWidth, 0, y*gameSettings.tileWidth)
			local part = createTilePart(tileCF, gameSettings.tileWidth)

			--part.CanCollide = false
			--part.Transparency = 1

			local self = {
				x = x,
				y = y,
				enabled = true,
				opened = false,
				isMine = false,
				isFlagged = false,
				neighbors = {}, -- {x = num, y = num}
				minesAround = 0, -- decided by layMines

				part = part,
				tileTexture = part.TileTexture,
				clickDetector = part.ClickDetector,
				flagInstance = nil, -- decal or instance
				leftClickConnection = nil,
				rightClickConnection = nil,
			}

			self.leftClickConnection = self.clickDetector.MouseClick:Connect(onLeftClick(self))
			self.rightClickConnection = self.clickDetector.RightMouseClick:Connect(onRightClick(self))

			for xOffset = -1, 1 do
				for yOffset = -1, 1 do
					if xOffset == 0 and yOffset == 0 then continue end
					local x2 = x + xOffset
					local y2 = y + yOffset
					if x2 < 1 or x2 > width then continue end
					if y2 < 1 or y2 > height then continue end
					table.insert(self.neighbors, { x = x2, y = y2 })
				end
			end
			part.Parent = model

			--local delayTime = (y - 1)*(width)*0.00005 + x*0.00005

			--TweenService:Create(part, TweenInfo.new(0.15 + math.random()*0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
			--	Transparency = 0,
			--	CFrame = tileCF,
			--	CanCollide = true
			--}):Play()

			row[y] = self
		end
		tiles[x] = row
	end

	model.Parent = script
	return gameObject
end

function stopGame(gameObject)
	for x, y, tile in iterTiles(gameObject.tiles) do
		if tile.enabled then
			disableTile(tile)
		end
	end
	gameObject.model:Destroy()
end

-- currentGame = createGame(CFrame.new(0, 5, 0), settings.width, settings.height, settings.mineCount)

local function getRootPos()
	local humanoid = owner.Character and owner.Character:FindFirstChildOfClass("Humanoid")
	if humanoid and humanoid.RootPart then
		return humanoid.RootPart.CFrame.Position
	end
end

local function findPlayer(target)
	target = target:lower()
	for _, plr in Players:GetPlayers() do
		if plr.Name:lower():sub(1, #target) == target then
			return plr
		end
	end
end

owner.Chatted:Connect(function(msg)
	local cmdString = msg:match("ms/(.+)")
	if not cmdString then return end

	local args = cmdString:split("/")
	local cmdName = table.remove(args, 1)

	if cmdName == "size" then
		if args[2] then
			settings.width = tonumber(args[1])
			settings.height = tonumber(args[2])
			printf(`set size to <b>{settings.width}x{settings.height}</b>`)
		else
			local size = tonumber(args[1])
			if not size then
				return printf("<i>pls provide size</i>")
			end
			settings.width = size
			settings.height = size
			printf(`set size to <b>{size}x{size}</b>`)
		end

	elseif cmdName == "mines" then
		local mineCount = tonumber(args[1])
		if not mineCount then
			return printf("<i>pls provide minecount</i>")
		end
		settings.mineCount = mineCount
		printf(`set mine count to <b>{settings.mineCount}</b>`)

	elseif cmdName == "minefreq" then
		local freq = tonumber(args[1])
		if not freq then
			return printf("<i>pls provide freq</i>")
		end
		settings.mineCount = math.floor(settings.width*settings.height*freq)

		printf(`set mine count to <b>{settings.mineCount}</b>`)
	elseif cmdName == "game" then
		local rootPos = getRootPos()
		if not rootPos then
			return printf("<i>could not find rootpos</i>")
		end
		if currentGame then
			stopGame(currentGame)
		end
		currentGame = createGame(CFrame.new(rootPos + Vector3.new(0, -1.75, 0)), settings.width, settings.height, settings.mineCount)
		printf("<i>started game</i>")

	elseif cmdName == "stop" then
		if restartTask then
			task.cancel(restartTask)
			restartTask = nil
		end
		if currentGame then
			stopGame(currentGame)
		else
			printf("<i>no game active</i>")
		end

	elseif cmdName == "debug" then
		settings.debug = not settings.debug
		printf(`<i>debug {settings.debug and "enabled" or "disabled"}</i>`)

	elseif cmdName == "whitelist" then
		whitelist = not whitelist
		printf(`<i>whitelist {whitelist and "enabled" or "disabled"}</i>`)

	elseif cmdName == "allow" then
		local username = tostring(args[1])
		if not username then
			return printf("<i>pls provide username</i>")
		end
		local plr = findPlayer(username)
		if not plr then
			return printf("<i>player not found</i>")
		end
		if whitelisted[plr.UserId] then
			return printf("<i>player is already whitelisted</i>")
		end
		whitelisted[plr.UserId] = true
		printf(`added <b>{plr}</b> to whitelist`)

	elseif cmdName == "unallow" then
		local username = tostring(args[1])
		if not username then
			return printf("<i>pls provide username</i>")
		end
		local plr = findPlayer(username)
		if not plr then
			return printf("<i>player not found</i>")
		end
		if not whitelisted[plr.UserId] then
			return printf("<i>player is not whitelisted</i>")
		end
		whitelisted[plr.UserId] = nil
		printf(`removed <b>{plr}</b> from whitelist`)

	elseif cmdName == "cmds" then
		printf([[ 
<b>ms/size/[width]/[height]</b> set board size to (width, height)
<b>ms/size/[sideLength]</b> set board size to (sideLength, sideLength)
<b>ms/mines/[mineCount]</b> set mine count to mineCount
<b>ms/minefreq/[mineFrequency]</b> set mine count to width*height*mineFrequency
<b>ms/game</b> start game
<b>ms/stop</b> end game
<b>ms/whitelist</b> toggle the whitelist
<b>ms/allow/[name]</b> add a player to the whitelist
<b>ms/unallow/[name]</b> remove a player from the whitelist
<b>ms/cmds</b> this thing]])

	else
		printf("<i>invalid ms/ command</i>")
	end
end)

print("ms/cmds for list of commands (works with /e)")
INFO