Start using sinatra & ruby for backend
Signed-off-by: Syed Daanish <me@syedm.dev>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.db
|
35
.rubocop.yml
Normal file
35
.rubocop.yml
Normal 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
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
3.4.1
|
25
.solargraph.yml
Normal file
25
.solargraph.yml
Normal 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
9
Gemfile
Normal file
@@ -0,0 +1,9 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "sinatra"
|
||||
|
||||
gem "xxhash"
|
||||
|
||||
gem "sequel"
|
||||
|
||||
gem "json"
|
40
Gemfile.lock
Normal file
40
Gemfile.lock
Normal 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
|
1
config/puma.rb
Normal file
1
config/puma.rb
Normal file
@@ -0,0 +1 @@
|
||||
stdout_redirect "./log/puma.stdout.log", "./log/puma.stderr.log", true
|
78
db.rb
Normal file
78
db.rb
Normal 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
12
event_bus.rb
Normal 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
100
game_logic.rb
Normal 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
60
log/puma.stderr.log
Normal 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
47
log/puma.stdout.log
Normal 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
7
main.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
require "sinatra"
|
||||
|
||||
set :public_folder, "public"
|
||||
|
||||
get "/" do
|
||||
send_file "index.html"
|
||||
end
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
31
run.fish
Normal file
31
run.fish
Normal 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
7
schema.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE Players (
|
||||
email TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE,
|
||||
digest BLOB,
|
||||
data BLOB,
|
||||
code TEXT
|
||||
);
|
Reference in New Issue
Block a user