Start using sinatra & ruby for backend

Signed-off-by: Syed Daanish <me@syedm.dev>
This commit is contained in:
2025-06-21 17:38:17 +03:00
parent 9d387eb482
commit d8fd37e757
32 changed files with 457 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.db

35
.rubocop.yml Normal file
View File

@@ -0,0 +1,35 @@
AllCops:
TargetRubyVersion: 3.4.1
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
Style/StringLiterals:
Enabled: false
Style/TrailingCommaInHashLiteral:
Enabled: false
Style/TrailingCommaInArrayLiteral:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Layout/SpaceAroundOperators:
Enabled: false
Metrics/AbcSize:
Enabled: false
Metrics/MethodLength:
Enabled: false
Layout/EmptyLineAfterGuardClause:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false

1
.ruby-version Normal file
View File

@@ -0,0 +1 @@
3.4.1

25
.solargraph.yml Normal file
View File

@@ -0,0 +1,25 @@
---
include:
- "**/*.rb"
exclude:
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
require: []
domains: []
reporters:
- rubocop
formatter:
rubocop:
cops: safe
except: []
only: []
extra_args: []
require_paths: []
plugins: []
max_files: 5000
useBundler: true
rubyVersion: "3.4.1"
workspace:
root: .

9
Gemfile Normal file
View File

@@ -0,0 +1,9 @@
source "https://rubygems.org"
gem "sinatra"
gem "xxhash"
gem "sequel"
gem "json"

40
Gemfile.lock Normal file
View File

@@ -0,0 +1,40 @@
GEM
remote: https://rubygems.org/
specs:
base64 (0.3.0)
bigdecimal (3.2.2)
logger (1.7.0)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
rack (3.1.16)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
ruby2_keywords (0.0.5)
sequel (5.93.0)
bigdecimal
sinatra (4.1.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
tilt (2.6.0)
xxhash (0.6.0)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
sequel
sinatra
xxhash
BUNDLED WITH
2.6.9

View File

1
config/puma.rb Normal file
View File

@@ -0,0 +1 @@
stdout_redirect "./log/puma.stdout.log", "./log/puma.stderr.log", true

3
db.json Normal file
View File

@@ -0,0 +1,3 @@
{
"account_num": 6
}

78
db.rb Normal file
View File

@@ -0,0 +1,78 @@
require "xxhash"
require "sequel"
require "json"
ALPHANUM = [*"0".."9", *"A".."Z", *"a".."z", "-", "_"].freeze
# DataBase handler module
module DataBase
db_file = File.expand_path("infinsweeper.db")
DB = Sequel.connect("sqlite:///#{db_file}", single_threaded: false)
def self.player_list
DB["select * from Players"].all
end
def self.rm_player(email)
DB["delete from Players where email = ?", email].delete
end
def self.mk_player(username, email, pass)
raise ArgumentError, "Email format is wrong!" unless email.match?(/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/)
raise ArgumentError, "Password must be at least 8 characters and valid format." unless
pass.match?(/\A[a-zA-Z0-9_.!?@#$%^&*()+=-]+\z/) && pass.length >= 8
digest = XXhash.xxh32(pass, 1234)
path = File.expand_path("db.json")
json = File.exist?(path) ? JSON.parse(File.read(path)) : {}
json["account_num"] ||= 0
account_num = json["account_num"]
json["account_num"] += 1
File.write(path, JSON.pretty_generate(json))
account_num = XXhash.xxh64(account_num, 1234)
code = ""
while account_num.positive?
code << ALPHANUM[account_num % 64]
account_num /= 64
end
code = code.reverse.rjust(12, "0")
DB["insert into Players (email, digest, username, code) values (?, ?, ?, ?)", email, digest, username, code].insert
send_email(email, username, code)
Thread.new do
sleep 24 * 60 * 60
rm_player(email) unless verified?(email)
end
"Successfully registered!"
rescue ArgumentError => e
e.message
rescue Sequel::UniqueConstraintViolation
"Account already exists with this email or username!"
end
def self.verify(code)
DB["update Players set code = ? where code = ?", "!", code].update != 0
end
def self.[](email)
DB["select * from Players where email = ?", email].first
end
def self.[]=(email, data)
DB["update Players set data = ? where email = ?", data, email].update
end
def self.authorized?(email, pass)
digest = XXhash.xxh32(pass, 1234)
player = self[email]
player && player[:digest].to_i == digest.to_i ? player : false
end
def self.verified?(email)
player = self[email]
player && player[:code] == "!"
end
end

12
event_bus.rb Normal file
View File

@@ -0,0 +1,12 @@
# Event bus
module EventBus
@events = {}
def self.on(event, &callback)
@events[event] = callback
end
def self.get(event, *args)
@events[event]&.call(*args)
end
end

100
game_logic.rb Normal file
View File

@@ -0,0 +1,100 @@
require "xxhash"
# Game logic main class
class GameLogic
@seed = rand(111_111..999_999)
@pos = {
board: {},
lost: {},
cache: {},
}
# Returns a pseudorandom number between 0 and 100
def self.hash(data)
XXhash.xxh32(data.to_s, @seed) % 100
end
def self.reset_pos
@pos.each_value(&:clear)
end
def self.reveal(g_x, g_y)
l_x = ((g_x % 9) + 9) % 9
l_y = ((g_y % 9) + 9) % 9
s_x = (g_x / 9).floor
s_y = (g_y / 9).floor
return if @pos.board["#{s_x}:#{s_y}"] == true
build_sector(s_x, s_y) if @pos.board["#{s_x}:#{s_y}"].nil?
return if @pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] || @pos.board["#{s_x}:#{s_y}"][l_x][l_y][2]
if @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0] == -1
@pos.lost["#{s_x}:#{s_y}"] = 1
elsif @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0].zero?
(-1..1).each do |x|
(-1..1).each do |y|
next if x.zero? && y.zero?
reveal(g_x + x, g_y + y)
end
end
else
@pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] = true
end
@pos.board["#{s_x}:#{s_y}"] = true if solved?(s_x, s_y)
end
def self.solved?(s_x, s_y)
(0..8).each do |l_x|
(0..8).each do |l_y|
return false if !@pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] && @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0] != -1
end
end
true
end
def self.count(g_x, g_y)
sum = 0
(-1..1).each do |x|
(-1..1).each do |y|
next if x.zero? && y.zero?
sum += 1 if mine?(g_x + x, g_y + y)
end
end
sum
end
def self.build_sector(s_x, s_y)
@pos.board["#{s_x}:#{s_y}"] = []
(0..8).each do |l_x|
@pos.board["#{s_x}:#{s_y}"][l_x] = []
(0..8).each do |l_y|
g_x = l_x + s_x * 9
g_y = l_y + s_y * 9
@pos.board["#{s_x}:#{s_y}"][l_x][l_y] = [
mine?(g_x, g_y) ? -1 : count(g_x, g_y),
false, false,
]
end
end
end
def self.build_cache(s_x, s_y)
return if @pos.cache["#{s_x}:#{s_y}"]
return unless @pos.board["#{s_x}:#{s_y}"] == 1
@pos.cache["#{s_x}:#{s_y}"] = []
(0..8).each do |l_x|
@pos.cache["#{s_x}:#{s_y}"][l_x] = []
(0..8).each do |l_y|
g_x = l_x + s_x * 9
g_y = l_y + s_y * 9
@pos.cache["#{s_x}:#{s_y}"][l_x][l_y] =
mine?(g_x, g_y) ? [-1, false, true] : [count(g_x, g_y), true, false]
end
end
end
def self.mine?(g_x, g_y)
g_x == 4 || g_y == 4 ? false : hash("#{g_x}:#{g_y}") < 17
end
end

60
log/puma.stderr.log Normal file
View File

@@ -0,0 +1,60 @@
=== puma startup: 2025-06-19 18:12:41 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:13:06 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:13:21 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:13:35 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:15:41 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:15:52 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:16:47 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:17:35 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:18:08 +0300 ===
127.0.0.1 - - [19/Jun/2025:18:18:17 +0300] "GET / HTTP/1.1" 200 460 0.0036
127.0.0.1 - - [19/Jun/2025:18:18:17 +0300] "GET / HTTP/1.1" 304 - 0.0010
127.0.0.1 - - [19/Jun/2025:18:18:18 +0300] "GET / HTTP/1.1" 304 - 0.0004
127.0.0.1 - - [19/Jun/2025:18:18:18 +0300] "GET / HTTP/1.1" 304 - 0.0005
127.0.0.1 - - [19/Jun/2025:18:18:19 +0300] "GET / HTTP/1.1" 304 - 0.0008
127.0.0.1 - - [19/Jun/2025:18:18:19 +0300] "GET / HTTP/1.1" 304 - 0.0005
127.0.0.1 - - [19/Jun/2025:18:18:19 +0300] "GET / HTTP/1.1" 304 - 0.0005
127.0.0.1 - - [19/Jun/2025:18:18:20 +0300] "GET / HTTP/1.1" 304 - 0.0006
127.0.0.1 - - [19/Jun/2025:18:18:20 +0300] "GET / HTTP/1.1" 304 - 0.0010
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:19:57 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:21:11 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:22:09 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:22:45 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:23:15 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:23:34 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-19 18:23:45 +0300 ===
127.0.0.1 - - [19/Jun/2025:18:24:05 +0300] "GET / HTTP/1.1" 304 - 0.0032
127.0.0.1 - - [19/Jun/2025:18:32:41 +0300] "GET / HTTP/1.1" 304 - 0.0006
127.0.0.1 - - [19/Jun/2025:23:53:35 +0300] "GET / HTTP/1.1" 304 - 0.0005
=== puma startup: 2025-06-20 23:53:14 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-20 23:53:29 +0300 ===
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-20 23:53:35 +0300 ===
127.0.0.1 - - [20/Jun/2025:23:53:55 +0300] "GET / HTTP/1.1" 304 - 0.0024
127.0.0.1 - - [20/Jun/2025:23:53:55 +0300] "GET /src/js/game_logic.js HTTP/1.1" 304 - 0.0005
127.0.0.1 - - [20/Jun/2025:23:53:55 +0300] "GET /src/js/game_renderer.js HTTP/1.1" 304 - 0.0064
== Sinatra has ended his set (crowd applauds)
=== puma startup: 2025-06-21 14:36:03 +0300 ===
127.0.0.1 - - [21/Jun/2025:14:36:35 +0300] "GET / HTTP/1.1" 304 - 0.0036
127.0.0.1 - - [21/Jun/2025:14:36:35 +0300] "GET /src/js/event_handler.js HTTP/1.1" 304 - 0.0010
127.0.0.1 - - [21/Jun/2025:14:36:35 +0300] "GET /src/js/ui_renderer.js HTTP/1.1" 304 - 0.0081
127.0.0.1 - - [21/Jun/2025:14:36:35 +0300] "GET /src/js/saver.js HTTP/1.1" 304 - 0.0009
127.0.0.1 - - [21/Jun/2025:14:36:36 +0300] "GET / HTTP/1.1" 304 - 0.0007
127.0.0.1 - - [21/Jun/2025:14:36:36 +0300] "GET / HTTP/1.1" 304 - 0.0006
== Sinatra has ended his set (crowd applauds)

47
log/puma.stdout.log Normal file
View File

@@ -0,0 +1,47 @@
=== puma startup: 2025-06-19 18:12:41 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-06-19 18:13:05 +0300 ===
- Goodbye!
=== puma startup: 2025-06-19 18:13:06 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-06-19 18:13:13 +0300 ===
- Goodbye!
=== puma startup: 2025-06-19 18:13:21 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:13:35 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:15:41 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:15:52 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:16:47 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:17:35 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-06-19 18:17:44 +0300 ===
- Goodbye!
=== puma startup: 2025-06-19 18:18:08 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:19:57 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:21:11 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:22:09 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:22:45 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:23:15 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-06-19 18:23:22 +0300 ===
- Goodbye!
=== puma startup: 2025-06-19 18:23:34 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-19 18:23:45 +0300 ===
=== puma startup: 2025-06-20 23:53:14 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-20 23:53:29 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-20 23:53:35 +0300 ===
- Gracefully stopping, waiting for requests to finish
=== puma startup: 2025-06-21 14:36:03 +0300 ===
- Gracefully stopping, waiting for requests to finish

7
main.rb Normal file
View File

@@ -0,0 +1,7 @@
require "sinatra"
set :public_folder, "public"
get "/" do
send_file "index.html"
end

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 353 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

31
run.fish Normal file
View File

@@ -0,0 +1,31 @@
set pipe_path /tmp/infin
set oldsum (cat (find . -name '*.rb' | sort) | md5sum)
if not test -p infinsweeper.db
sqlite3 infinsweeper.db < schema.sql
end
pkill ruby
ruby main.rb -p8080 &
if not test -p $pipe_path
touch $pipe_path
end
while true
sleep 1
set newsum (cat (find . -name '*.rb' | sort) | md5sum)
if test "$oldsum" != "$newsum"
set oldsum $newsum
pkill ruby
ruby main.rb -p8080 &
end
if set line (cat $pipe_path)
echo $line
if test "$line" = "die"
pkill ruby
echo "" > $pipe_path
exit
end
if test "$line" = "open"
echo "" > $pipe_path
xdg-open http://localhost:8080
end
end
end

7
schema.sql Normal file
View File

@@ -0,0 +1,7 @@
CREATE TABLE Players (
email TEXT PRIMARY KEY,
username TEXT UNIQUE,
digest BLOB,
data BLOB,
code TEXT
);