Gintarus

ca

Jun 27th, 2026
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.28 KB | None | 0 0
  1. local component = require("component")
  2. local event = require("event")
  3. local computer = require("computer")
  4. local serialization = require("serialization")
  5.  
  6. local cfg = {
  7. title = "GintaForine",
  8. dataPath = "/home/casino_data.lua",
  9. initialBalance = 10,
  10. defaultBet = 1,
  11. minBet = 1,
  12. maxBet = 1000,
  13. idleTimeout = 30,
  14. animationSteps = 16,
  15. animationDelay = 0.06,
  16. preferredWidth = 100,
  17. preferredHeight = 30
  18. }
  19.  
  20. local colors = {
  21. black = 0x000000,
  22. dark = 0x06101a,
  23. panel = 0x071b2b,
  24. blue = 0x152cff,
  25. cyan = 0x00ffff,
  26. white = 0xffffff,
  27. gray = 0x777777,
  28. green = 0x38dd62,
  29. yellow = 0xffd34d,
  30. red = 0xff3b4f
  31. }
  32.  
  33. local iconColors = {
  34. [" "] = colors.dark,
  35. ["."] = colors.dark,
  36. K = 0x111111,
  37. W = 0xffffff,
  38. R = 0xff2c4d,
  39. P = 0xff4fa0,
  40. G = 0x48d85b,
  41. D = 0x246b2c,
  42. Y = 0xffd426,
  43. O = 0xff8d1a,
  44. L = 0xfff36b,
  45. V = 0x7c4dff,
  46. B = 0x5b35c8,
  47. M = 0x8a4c20
  48. }
  49.  
  50. local fruits = {
  51. {
  52. id = "cherry", name = "вишенки", text = "CH", color = 0xff2c4d,
  53. triple = 16, weight = 16,
  54. icon = {
  55. ".....G.....",
  56. "....G.G....",
  57. "...G...G...",
  58. "..R.....R..",
  59. ".RRR...RRR.",
  60. ".RRR...RRR.",
  61. "..R.....R..",
  62. "...........",
  63. "..........."
  64. }
  65. },
  66. {
  67. id = "lemon", name = "лимоны", text = "LM", color = 0xfff36b,
  68. triple = 12, weight = 18,
  69. icon = {
  70. "...........",
  71. "...KKKKK...",
  72. "..KLLLLLK..",
  73. ".KLLWLLLLK.",
  74. ".KLLLLLLLK.",
  75. ".KLLLLWLLK.",
  76. "..KLLLLLK..",
  77. "...KKKKK...",
  78. "..........."
  79. }
  80. },
  81. {
  82. id = "strawberry", name = "клубника", text = "ST", color = 0xff3b4f,
  83. triple = 15, weight = 15,
  84. icon = {
  85. "....GGG....",
  86. "...GGGGG...",
  87. "..KRRRRRK..",
  88. ".KRRWRRRRK.",
  89. ".KRRRWRRRK.",
  90. "..KRRRWRK..",
  91. "...KRRK....",
  92. "....KK.....",
  93. "..........."
  94. }
  95. },
  96. {
  97. id = "orange", name = "апельсины", text = "OR", color = 0xff8d1a,
  98. triple = 17, weight = 13,
  99. icon = {
  100. "....GG.....",
  101. "...G..G....",
  102. "...KKK.....",
  103. "..KOOOK....",
  104. ".KOOYOK....",
  105. ".KOOOOOK...",
  106. "..KOOOK....",
  107. "...KKK.....",
  108. "..........."
  109. }
  110. },
  111. {
  112. id = "apple", name = "яблоки", text = "AP", color = 0x48d85b,
  113. triple = 20, weight = 10,
  114. icon = {
  115. ".....M.....",
  116. "....MGG....",
  117. "...KKKKK...",
  118. "..KGGGGGK..",
  119. ".KGGWGGGK..",
  120. ".KGGGGGGK..",
  121. "..KGGGGK...",
  122. "...KKKK....",
  123. "..........."
  124. }
  125. },
  126. {
  127. id = "watermelon", name = "арбузы", text = "WM", color = 0x38dd62,
  128. triple = 25, weight = 8,
  129. icon = {
  130. "...........",
  131. "..KKKKKKK..",
  132. ".KDGGGGDK..",
  133. "KDGGRGGDK..",
  134. "KDGGRRGDK..",
  135. "KDGGRGGDK..",
  136. ".KDGGGGDK..",
  137. "..KKKKKKK..",
  138. "..........."
  139. }
  140. },
  141. {
  142. id = "banana", name = "бананы", text = "BN", color = 0xffd426,
  143. triple = 30, weight = 6,
  144. icon = {
  145. "...........",
  146. ".......KK..",
  147. "......KYYK.",
  148. ".....KYYYK.",
  149. "...KKYYYK..",
  150. "..KYYYYK...",
  151. ".KYYYYK....",
  152. "..KKK......",
  153. "..........."
  154. }
  155. },
  156. {
  157. id = "grape", name = "виноград", text = "GR", color = 0x7c4dff,
  158. triple = 40, weight = 3,
  159. icon = {
  160. "....GG.....",
  161. "...G.......",
  162. "...KVK.....",
  163. "..KVKVK....",
  164. ".KVKVKVK...",
  165. "..KVKVK....",
  166. "...KVK.....",
  167. "....K......",
  168. "..........."
  169. }
  170. },
  171. {
  172. id = "gold", name = "золотой фрукт", text = "GF", color = 0xffd34d,
  173. triple = 100, weight = 1,
  174. icon = {
  175. "....GG.....",
  176. "...G..G....",
  177. "...KKK.....",
  178. "..KYYYK....",
  179. ".KYYWYYK...",
  180. ".KYYYYYK...",
  181. "..KYYYK....",
  182. "...KKK.....",
  183. "..........."
  184. }
  185. }
  186. }
  187.  
  188. local gpu
  189. local sw, sh = 80, 25
  190. local buttons = {}
  191. local data
  192. local playerName
  193. local player
  194. local reels = { fruits[1], fruits[2], fruits[3] }
  195. local locked = { false, false, false }
  196. local message = "Нажмите на монитор, чтобы начать"
  197. local spinning = false
  198.  
  199. local function roundMoney(value)
  200. return math.floor((tonumber(value) or 0) * 100 + 0.5) / 100
  201. end
  202.  
  203. local function money(value)
  204. return string.format("%.2f$", roundMoney(value))
  205. end
  206.  
  207. local function now()
  208. return computer.uptime()
  209. end
  210.  
  211. local function clamp(value, minValue, maxValue)
  212. value = tonumber(value) or minValue
  213. if value < minValue then return minValue end
  214. if value > maxValue then return maxValue end
  215. return value
  216. end
  217.  
  218. local function setFg(c) gpu.setForeground(c) end
  219. local function setBg(c) gpu.setBackground(c) end
  220.  
  221. local function write(x, y, text, fg, bg)
  222. if fg then setFg(fg) end
  223. if bg then setBg(bg) end
  224. gpu.set(x, y, tostring(text or ""))
  225. end
  226.  
  227. local function fill(x, y, w, h, bg, ch)
  228. if w <= 0 or h <= 0 then return end
  229. setBg(bg or colors.black)
  230. gpu.fill(x, y, w, h, ch or " ")
  231. end
  232.  
  233. local function centerText(text, width)
  234. text = tostring(text or "")
  235. if #text >= width then return text:sub(1, width) end
  236. return string.rep(" ", math.floor((width - #text) / 2)) .. text
  237. end
  238.  
  239. local function box(x, y, w, h, title)
  240. setFg(colors.blue)
  241. setBg(colors.black)
  242. gpu.fill(x + 1, y, w - 2, 1, "-")
  243. gpu.fill(x + 1, y + h - 1, w - 2, 1, "-")
  244. gpu.fill(x, y + 1, 1, h - 2, "|")
  245. gpu.fill(x + w - 1, y + 1, 1, h - 2, "|")
  246. write(x, y, "+", colors.blue, colors.black)
  247. write(x + w - 1, y, "+", colors.blue, colors.black)
  248. write(x, y + h - 1, "+", colors.blue, colors.black)
  249. write(x + w - 1, y + h - 1, "+", colors.blue, colors.black)
  250. if title then
  251. write(x + 2, y, " " .. title .. " ", colors.cyan, colors.black)
  252. end
  253. end
  254.  
  255. local function addButton(x1, y1, x2, y2, label, action, value)
  256. buttons[#buttons + 1] = { x1 = x1, y1 = y1, x2 = x2, y2 = y2, label = label, action = action, value = value }
  257. end
  258.  
  259. local function drawButton(b, active)
  260. local bg = active and colors.blue or colors.panel
  261. fill(b.x1, b.y1, b.x2 - b.x1 + 1, b.y2 - b.y1 + 1, bg)
  262. write(b.x1 + 1, b.y1, centerText(b.label, b.x2 - b.x1 - 1), colors.cyan, bg)
  263. end
  264.  
  265. local function drawFruitIcon(x, y, w, h, fruit)
  266. fill(x, y, w, h, colors.dark)
  267. local iconH = #fruit.icon
  268. local iconW = #(fruit.icon[1] or "")
  269. for row = 1, h do
  270. local sourceRow = math.floor((row - 0.5) * iconH / h) + 1
  271. if sourceRow < 1 then sourceRow = 1 end
  272. if sourceRow > iconH then sourceRow = iconH end
  273. local line = fruit.icon[sourceRow]
  274. for col = 1, w do
  275. local sourceCol = math.floor((col - 0.5) * iconW / w) + 1
  276. if sourceCol < 1 then sourceCol = 1 end
  277. if sourceCol > iconW then sourceCol = iconW end
  278. fill(x + col - 1, y + row - 1, 1, 1, iconColors[line:sub(sourceCol, sourceCol)] or fruit.color)
  279. end
  280. end
  281. end
  282.  
  283. local function drawSlot(x, y, fruit, isLocked)
  284. fill(x, y, 17, 11, 0x303030)
  285. box(x, y, 17, 11)
  286. drawFruitIcon(x + 3, y + 1, 11, 9, fruit)
  287. if isLocked then
  288. write(x + 6, y + 10, "OK", colors.green, colors.black)
  289. end
  290. end
  291.  
  292. local function normalizeBet()
  293. if not player then return end
  294. if player.balance >= cfg.minBet then
  295. player.bet = clamp(player.bet, cfg.minBet, math.min(cfg.maxBet, player.balance))
  296. else
  297. player.bet = cfg.minBet
  298. end
  299. end
  300.  
  301. local function emptyData()
  302. return { totalSpent = 0, totalWon = 0, players = {} }
  303. end
  304.  
  305. local function loadData()
  306. local f = io.open(cfg.dataPath, "r")
  307. if not f then return emptyData() end
  308. local text = f:read("*a")
  309. f:close()
  310. local ok, loaded = pcall(serialization.unserialize, text)
  311. if not ok or type(loaded) ~= "table" or type(loaded.players) ~= "table" then
  312. return emptyData()
  313. end
  314. loaded.totalSpent = roundMoney(loaded.totalSpent)
  315. loaded.totalWon = roundMoney(loaded.totalWon)
  316. return loaded
  317. end
  318.  
  319. local function saveData()
  320. if not data then return end
  321. local tmp = cfg.dataPath .. ".tmp"
  322. local f, reason = io.open(tmp, "w")
  323. if not f then
  324. message = "Не удалось сохранить: " .. tostring(reason)
  325. return
  326. end
  327. f:write(serialization.serialize(data))
  328. f:write("\n")
  329. f:close()
  330. os.remove(cfg.dataPath)
  331. os.rename(tmp, cfg.dataPath)
  332. end
  333.  
  334. local function getPlayer(name)
  335. name = tostring(name or "")
  336. if name == "" then name = "Unknown" end
  337. if type(data.players[name]) ~= "table" then
  338. data.players[name] = {
  339. balance = cfg.initialBalance,
  340. totalSpent = 0,
  341. totalWon = 0,
  342. bet = cfg.defaultBet,
  343. lastActive = now()
  344. }
  345. end
  346. local p = data.players[name]
  347. p.balance = roundMoney(p.balance)
  348. p.totalSpent = roundMoney(p.totalSpent)
  349. p.totalWon = roundMoney(p.totalWon)
  350. p.bet = roundMoney(p.bet or cfg.defaultBet)
  351. p.lastActive = tonumber(p.lastActive) or now()
  352. return name, p
  353. end
  354.  
  355. local function randomFruit()
  356. local total = 0
  357. for i = 1, #fruits do total = total + fruits[i].weight end
  358. local roll = math.random() * total
  359. local seen = 0
  360. for i = 1, #fruits do
  361. seen = seen + fruits[i].weight
  362. if roll <= seen then return fruits[i] end
  363. end
  364. return fruits[#fruits]
  365. end
  366.  
  367. local function evaluate()
  368. local a, b, c = reels[1], reels[2], reels[3]
  369. if a.id == b.id and b.id == c.id then
  370. return a.triple, "Три " .. a.name
  371. end
  372. if a.id == b.id or b.id == c.id then
  373. return 2, "Два фрукта рядом"
  374. end
  375. if a.id == c.id then
  376. return 1, "Два фрукта по краям"
  377. end
  378. return 0, "Без совпадений"
  379. end
  380.  
  381. local function buttonAt(x, y)
  382. for i = #buttons, 1, -1 do
  383. local b = buttons[i]
  384. if x >= b.x1 and x <= b.x2 and y >= b.y1 and y <= b.y2 then return b end
  385. end
  386. end
  387.  
  388. local function secondsLeft()
  389. if not player then return cfg.idleTimeout end
  390. return math.max(0, math.ceil(cfg.idleTimeout - (now() - player.lastActive)))
  391. end
  392.  
  393. local function drawWaiting()
  394. buttons = {}
  395. fill(1, 1, sw, sh, colors.black)
  396. box(1, 1, sw, sh, cfg.title)
  397. write(4, 4, "Нажмите на монитор, чтобы начать", colors.cyan, colors.black)
  398. write(4, 6, "Фруктовый автомат. Новый баланс: " .. money(cfg.initialBalance), colors.white, colors.black)
  399. write(4, 7, "2 рядом = x2, 2 по краям = x1, 3 фрукта = большой бонус.", colors.white, colors.black)
  400. write(4, 9, "Редкие фрукты выпадают реже, но платят больше.", colors.yellow, colors.black)
  401.  
  402. local startX = math.max(4, math.floor((sw - 65) / 2))
  403. local y = math.max(12, math.floor(sh / 2))
  404. for i = 1, 3 do
  405. drawSlot(startX + (i - 1) * 24, y, fruits[i + 2], false)
  406. end
  407. end
  408.  
  409. local function drawGame()
  410. buttons = {}
  411. fill(1, 1, sw, sh, colors.black)
  412. box(1, 1, sw, sh, cfg.title)
  413.  
  414. if sw < 90 then
  415. write(4, 3, playerName .. " Баланс: " .. money(player.balance), colors.cyan, colors.black)
  416. write(4, 4, "Ставка: " .. money(player.bet), colors.yellow, colors.black)
  417. write(4, 5, message or "Крутите фрукты.", colors.white, colors.black)
  418.  
  419. local slotY = 8
  420. local slotStart = math.max(3, math.floor((sw - 55) / 2))
  421. for i = 1, 3 do
  422. drawSlot(slotStart + (i - 1) * 19, slotY, reels[i], locked[i])
  423. end
  424.  
  425. local betY = sh - 5
  426. local betX = math.max(2, math.floor((sw - 68) / 2))
  427. local betButtons = {
  428. { "-10$", -10 }, { "-5$", -5 }, { "-1$", -1 },
  429. { "Ставка " .. money(player.bet), 0 },
  430. { "+1$", 1 }, { "+5$", 5 }, { "+10$", 10 }
  431. }
  432. local widths = { 7, 6, 6, 16, 6, 6, 7 }
  433. local x = betX
  434. for i = 1, #betButtons do
  435. local label, value = betButtons[i][1], betButtons[i][2]
  436. addButton(x, betY, x + widths[i] - 1, betY + 1, label, value == 0 and "spin" or "bet", value)
  437. x = x + widths[i] + 2
  438. end
  439. addButton(slotStart, slotY, slotStart + 54, slotY + 10, "spinArea", "spin")
  440. addButton(4, sh - 2, 14, sh - 2, "Выход", "exit")
  441. for i = 1, #buttons do
  442. if buttons[i].label ~= "spinArea" then drawButton(buttons[i], not spinning) end
  443. end
  444. return
  445. end
  446.  
  447. local sideW = sw >= 82 and 30 or 24
  448. box(3, 3, sideW, sh - 4, "Игрок")
  449. write(5, 5, "Ник:", colors.blue, colors.black)
  450. write(5, 6, playerName, colors.cyan, colors.black)
  451. write(5, 8, "Баланс:", colors.blue, colors.black)
  452. write(5, 9, money(player.balance), colors.green, colors.black)
  453. write(5, 11, "Потрачено: " .. money(player.totalSpent), colors.white, colors.black)
  454. write(5, 12, "Выиграно: " .. money(player.totalWon), colors.white, colors.black)
  455. write(5, sh - 7, "Авто-выход: " .. tostring(secondsLeft()), colors.blue, colors.black)
  456. addButton(5, sh - 4, sideW - 2, sh - 3, "Выход", "exit")
  457.  
  458. local gameX = sideW + 5
  459. local gameW = sw - gameX - 2
  460. box(gameX, 3, gameW, sh - 4, "Фрукты")
  461. write(gameX + 3, 5, message or "Выберите ставку и крутите.", colors.cyan, colors.black)
  462.  
  463. local slotY = 9
  464. local slotStart = gameX + math.max(2, math.floor((gameW - 55) / 2))
  465. for i = 1, 3 do
  466. drawSlot(slotStart + (i - 1) * 19, slotY, reels[i], locked[i])
  467. end
  468.  
  469. local betY = sh - 6
  470. local betX = gameX + math.max(2, math.floor((gameW - 68) / 2))
  471. local betButtons = {
  472. { "-10$", -10 }, { "-5$", -5 }, { "-1$", -1 },
  473. { "Ставка " .. money(player.bet), 0 },
  474. { "+1$", 1 }, { "+5$", 5 }, { "+10$", 10 }
  475. }
  476. local widths = { 7, 6, 6, 16, 6, 6, 7 }
  477. local x = betX
  478. for i = 1, #betButtons do
  479. local label, value = betButtons[i][1], betButtons[i][2]
  480. local action = value == 0 and "spin" or "bet"
  481. addButton(x, betY, x + widths[i] - 1, betY + 1, label, action, value)
  482. x = x + widths[i] + 2
  483. end
  484.  
  485. addButton(slotStart, slotY, slotStart + 54, slotY + 10, "spinArea", "spin")
  486. for i = 1, #buttons do
  487. if buttons[i].label ~= "spinArea" then drawButton(buttons[i], not spinning) end
  488. end
  489. end
  490.  
  491. local function redraw()
  492. sw, sh = gpu.getResolution()
  493. if sw < 70 or sh < 22 then
  494. buttons = {}
  495. fill(1, 1, sw, sh, colors.black)
  496. write(2, 2, "Экран слишком маленький.", colors.red, colors.black)
  497. write(2, 3, "Нужно хотя бы 70x22.", colors.white, colors.black)
  498. return
  499. end
  500. if not player then drawWaiting() else drawGame() end
  501. end
  502.  
  503. local function login(name)
  504. playerName, player = getPlayer(name)
  505. player.lastActive = now()
  506. normalizeBet()
  507. message = "Добро пожаловать, " .. playerName
  508. saveData()
  509. redraw()
  510. end
  511.  
  512. local function logout()
  513. playerName, player = nil, nil
  514. locked = { false, false, false }
  515. message = "Нажмите на монитор, чтобы начать"
  516. redraw()
  517. end
  518.  
  519. local function changeBet(delta)
  520. if spinning or not player then return end
  521. player.lastActive = now()
  522. player.bet = roundMoney(player.bet + delta)
  523. normalizeBet()
  524. message = "Ставка: " .. money(player.bet)
  525. saveData()
  526. redraw()
  527. end
  528.  
  529. local function drainTouches()
  530. while event.pull(0, "touch") do end
  531. end
  532.  
  533. local function spin()
  534. if spinning or not player then return end
  535. player.lastActive = now()
  536. normalizeBet()
  537. if player.balance < player.bet or player.balance < cfg.minBet then
  538. message = "Не хватает денег на ставку"
  539. redraw()
  540. return
  541. end
  542.  
  543. spinning = true
  544. locked = { false, false, false }
  545. player.balance = roundMoney(player.balance - player.bet)
  546. player.totalSpent = roundMoney(player.totalSpent + player.bet)
  547. data.totalSpent = roundMoney(data.totalSpent + player.bet)
  548. message = "Крутим фрукты..."
  549. saveData()
  550.  
  551. local final = { randomFruit(), randomFruit(), randomFruit() }
  552. for step = 1, cfg.animationSteps do
  553. for slot = 1, 3 do
  554. local stopAt = math.floor(cfg.animationSteps * (0.45 + slot * 0.16))
  555. if step >= stopAt then
  556. reels[slot] = final[slot]
  557. locked[slot] = true
  558. else
  559. reels[slot] = randomFruit()
  560. locked[slot] = false
  561. end
  562. end
  563. redraw()
  564. os.sleep(cfg.animationDelay)
  565. end
  566.  
  567. reels = final
  568. locked = { true, true, true }
  569. local mult, reason = evaluate()
  570. local payout = roundMoney(player.bet * mult)
  571. if payout > 0 then
  572. player.balance = roundMoney(player.balance + payout)
  573. player.totalWon = roundMoney(player.totalWon + payout)
  574. data.totalWon = roundMoney(data.totalWon + payout)
  575. message = reason .. ": +" .. money(payout) .. " x" .. tostring(mult)
  576. else
  577. message = "Фрукты не совпали"
  578. end
  579. normalizeBet()
  580. spinning = false
  581. saveData()
  582. redraw()
  583. drainTouches()
  584. end
  585.  
  586. local function handleTouch(x, y, name)
  587. if not player then
  588. login(name)
  589. return
  590. end
  591. player.lastActive = now()
  592. local b = buttonAt(x, y)
  593. if not b then
  594. redraw()
  595. return
  596. end
  597. if b.action == "exit" then
  598. saveData()
  599. logout()
  600. elseif b.action == "bet" then
  601. changeBet(b.value)
  602. elseif b.action == "spin" then
  603. spin()
  604. end
  605. end
  606.  
  607. if not component.isAvailable("gpu") then
  608. io.stderr:write("GintaForine: не найдена видеокарта GPU.\n")
  609. return
  610. end
  611. if not component.isAvailable("screen") then
  612. io.stderr:write("GintaForine: не найден экран OpenComputers.\n")
  613. return
  614. end
  615.  
  616. gpu = component.gpu
  617. pcall(function()
  618. if gpu.getScreen and not gpu.getScreen() then
  619. gpu.bind(component.screen.address, true)
  620. end
  621. end)
  622. pcall(function()
  623. local maxW, maxH = gpu.maxResolution()
  624. gpu.setResolution(math.min(maxW, cfg.preferredWidth), math.min(maxH, cfg.preferredHeight))
  625. end)
  626.  
  627. math.randomseed(os.time() + math.floor(computer.uptime() * 100000))
  628. for _ = 1, 5 do math.random() end
  629. data = loadData()
  630.  
  631. local function main()
  632. redraw()
  633. while true do
  634. local ev, _, x, y, _, name = event.pull(1)
  635. if ev == "interrupted" then
  636. break
  637. elseif ev == "screen_resized" then
  638. redraw()
  639. elseif ev == "touch" then
  640. handleTouch(tonumber(x) or 1, tonumber(y) or 1, name)
  641. elseif player then
  642. if now() - player.lastActive >= cfg.idleTimeout then
  643. saveData()
  644. logout()
  645. else
  646. redraw()
  647. end
  648. end
  649. end
  650. end
  651.  
  652. local ok, err = pcall(main)
  653. saveData()
  654. fill(1, 1, sw, sh, colors.black)
  655. setFg(colors.white)
  656. setBg(colors.black)
  657. if not ok and tostring(err) ~= "interrupted" then error(err) end
  658.  
Add Comment
Please, Sign In to add comment