v1.0 unless something breaks in which case this was a beta

This commit is contained in:
Matthias Portzel 2024-02-29 21:42:05 -05:00
parent 764f23b7bb
commit 1399db073d
25 changed files with 992 additions and 20 deletions

3
.gitignore vendored
View File

@ -1 +1,4 @@
config/.env
my_solutions/
puzzles.toml
*.sqlite3

View File

@ -3,9 +3,12 @@ source "https://rubygems.org"
gem "dotenv"
gem "activerecord"
gem "sqlite3"
gem "activesupport"
gem "activejob"
gem "discordrb"
gem "pry"

View File

@ -1,18 +1,30 @@
GEM
remote: https://rubygems.org/
specs:
activemodel (7.0.7)
activesupport (= 7.0.7)
activerecord (7.0.7)
activemodel (= 7.0.7)
activesupport (= 7.0.7)
activesupport (7.0.7)
activejob (7.1.3.2)
activesupport (= 7.1.3.2)
globalid (>= 0.3.6)
activemodel (7.1.3.2)
activesupport (= 7.1.3.2)
activerecord (7.1.3.2)
activemodel (= 7.1.3.2)
activesupport (= 7.1.3.2)
timeout (>= 0.4.0)
activesupport (7.1.3.2)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
base64 (0.2.0)
bigdecimal (3.1.6)
coderay (1.1.3)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
discordrb (3.5.0)
discordrb-webhooks (~> 3.5.0)
ffi (>= 1.9.24)
@ -21,21 +33,25 @@ GEM
websocket-client-simple (>= 0.3.0)
discordrb-webhooks (3.5.0)
rest-client (>= 2.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
domain_name (0.6.20240107)
dotenv (3.1.0)
drb (2.2.0)
ruby2_keywords
event_emitter (0.2.6)
ffi (1.15.5)
ffi (1.16.3)
globalid (1.2.1)
activesupport (>= 6.1)
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
method_source (1.0.0)
mime-types (3.5.0)
mime-types (3.5.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808)
minitest (5.19.0)
mime-types-data (3.2024.0206)
minitest (5.22.2)
mutex_m (0.2.0)
netrc (0.11.0)
opus-ruby (1.0.1)
ffi
@ -47,25 +63,28 @@ GEM
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
ruby2_keywords (0.0.5)
sqlite3 (1.7.2-x86_64-darwin)
timeout (0.4.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
websocket (1.2.9)
websocket (1.2.10)
websocket-client-simple (0.8.0)
event_emitter
websocket
PLATFORMS
x86_64-darwin-21
x86_64-darwin-23
DEPENDENCIES
activejob
activerecord
activesupport
discordrb
dotenv
pry
sqlite3
BUNDLED WITH
2.4.13

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Puzzle Format
text, a string with the text of the puzzle
solution, a string to compare
check_solution, a string; a ruby expression that will evaluate to true or false. `discord_username` and `submission` are in scope.
One of solution or check_solution must be present.

Binary file not shown.

BIN
assets/glasses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

32
db.rb Normal file
View File

@ -0,0 +1,32 @@
require "active_record"
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db.sqlite3'
)
# Migrations Boilerplate
Dir["migrations/*.rb"].each {|f| require_relative f}
unless ActiveRecord::Base.connection.table_exists? :puzzles
CreatePuzzleTable.migrate(:up)
end
# Puzzle model
class Puzzle < ActiveRecord::Base
# .string :text, The markdown text of the message
# One of these must be present
# table.string :solution, null: true # A string to compare against
# table.string :check_solution, null: true # a ruby expression that will evaluate to true or false. `discord_username` and `submission` are in scope.
# # Public_id or title, used to identify the puzzle when upserting toml
# table.string :public_id
# # used / current / ready / draft
# table.integer :state, default: 0
enum :state, %i[draft ready current used]
def validate
return solution.present? || check_solution.present
end
end

84
main.rb
View File

@ -1,3 +1,5 @@
require "pp"
require "dotenv"
Dotenv.load("config/.env")
@ -5,6 +7,9 @@ require "discordrb"
require "active_support/all"
require_relative "send_puzzle_job"
require_relative "db"
$bot = Discordrb::Bot.new(token: ENV["DISCORD_TOKEN"], client_id: ENV["DISCORD_CLIENT_ID"])
## Setup
@ -13,6 +18,9 @@ at_exit { $bot.stop }
$server_id, $server = $bot.servers.find { |id, s| s.name == "The Linoleum Club" }
$puzzle_solver_role = $server.roles.find { |r| r.name == "Puzzle Solver" }
$puzzle_channel = $server.channels.find { |c| c.name == "puzzle" }
$study_channel = $server.channels.find { |c| c.name == "sylvains-study" }
$me = $server.users.find { |u| u.username == "matthiasportzel" }
def remove_solver_role
p "remove solver role"
@ -61,6 +69,10 @@ end
# There's probably a better way to do this re-loading
define_command "Remove commands", [] do |event|
if event.user != $me
event.respond "No you did-aint!", ephemeral: false
return
end
p "Removing all commands"
# Discord displays "Linoleum Club is thinking..." until we edit the response
@ -75,7 +87,30 @@ end
define_command("Solve", [
{ type: :string, name: "solution", description: "Your solution to the puzzle", required: true }
]) do |event|
if event.options["solution"]&.strip == "1193" then
submission = event.options["solution"]
current_puzzle = Puzzle.current.first
if !submission.present?
event.respond content: "That's not an answer"
return
end
solved = false
if current_puzzle.solution.present?
solved = current_puzzle.solution == submission.strip
elsif current_puzzle.check_solution.present?
discord_username = event.user.username
unless discord_username.present?
$study_channel.send "User with no username needs manual handling"
return
end
# submission is also in scope, from above
solved = eval current_puzzle.check_solution, binding
else
raise "No way to handle this puzzle"
end
if solved then
event.user.add_role $puzzle_solver_role
event.respond content: "Correct! You've gained full access for this week.", ephemeral: true
else
@ -83,6 +118,51 @@ define_command("Solve", [
end
end
$irb_sessions = []
# Join the bot thread, so we don't exit until the bot does
class DiscordIRBSession
attr_accessor :user
attr_accessor :channel
attr_accessor :enviroment
end
define_command("IRB Discord", []) do |event|
if event.user == $me
event.respond content: "Started an IRB-Discord session. Use `exit` to exit."
session = DiscordIRBSession.new
session.user = event.user
session.channel = event.channel
session.enviroment = binding
$irb_sessions.push session
else
event.respond content: "You don't have permission to use this command"
end
end
$bot.message(from: $me) do |event|
session = $irb_sessions.filter { |s| s.user == event.user and s.channel == event.channel }
if session.length == 1
session = session.first
code = event.message.content
if code.strip == "exit"
$irb_sessions.delete session
event.channel.send "Thanks for irb-ing ;)"
else
begin
res = eval code, session.enviroment
rescue => err
res = err
ensure
event.channel.send "```rb\n#{PP.pp(res, "")}\n```"
end
end
end
end
# Get ready to send the next puzzle at the next UTC sunday
SendPuzzleJob.new.enqueue(wait_until: Date.today.next_occurring(:sunday).beginning_of_day)
# binding.irb
# Wait forever for the bot to exit
$bot.join

22
migrations/0000_create.rb Normal file
View File

@ -0,0 +1,22 @@
# TODO: finishing writing and migrated
class CreatePuzzleTable < ActiveRecord::Migration[7.1]
def change
create_table :puzzles do |table|
# The markdown text of the message
table.string :text
# One of these must be present
table.string :solution, null: true
table.string :check_solution, null: true
# We need a public_id or title that I can use to identify the puzzle when upserting toml
table.string :public_id
# used / current / ready / draft
table.integer :state, default: 0
table.timestamps
end
end
end

View File

@ -0,0 +1,108 @@
# frozen_string_literal: true
require "active_support/all"
require "pry"
class Set
alias :includes? :include?
end
class State
def initialize(flat)
@flat = flat
end
attr_reader :flat
def square
@square ||= @flat.in_groups_of 4
end
# Return the index of the empty square, 0-15
def empty
@empty ||= flat.index 0
end
def slide(dir)
# Let's assume the move is possible
if dir == :up
# index of the tile to move, so that's the tile below the current pos
# So we're going to grab empty and add three to push it to the next row
# And that's all good as long as this is a valid move
to_move_index = empty + 4
elsif dir == :down
to_move_index = empty - 4
elsif dir == :left
# Only valid if empty is in the first three columns
# So the index + 1 should work
to_move_index = empty + 1
elsif dir == :right
to_move_index = empty - 1
end
new_state = flat.dup
to_move = flat[to_move_index]
new_state[empty] = to_move
new_state[to_move_index] = 0
return State.new new_state
end
def possible_moves
valid = []
if empty < 12 # so 0 - 11, that is, the first three rows
valid.push :up
end
if empty >= 4
valid.push :down
end
# Can't go left if empty is in the last row
if empty % 4 != 3
valid.push :left
end
if empty % 4 != 0
valid.push :right
end
return valid
end
def adjacent_states
return possible_moves.map do |dir|
slide dir
end
end
def to_s
@s ||= flat.join ""
end
def dis
puts square.map(&:to_s).join("\n").sub("0", " ")
puts "\n"
end
end
seen_states = Set.new
# Last is actually the one we're going to work off of, so we can push and pop
unchecked_states = [State.new((0..15).to_a)]
while unchecked_states.length > 0
state = unchecked_states.pop
state.adjacent_states.each do |s|
unless seen_states.includes? s.to_s
seen_states.add s.to_s
unchecked_states.push s
end
end
end
# seen_states.count
# => 181440
binding.pry

42
my-solutions/3d-space.js Normal file
View File

@ -0,0 +1,42 @@
// From /Users/matthias/Programs/tmp-name-js-plus/main.js
Object.defineProperty(Array.prototype, "chunks", {
value: function chunks (chunkSize) {
// TODO: Error on float
if (chunkSize<= 0) {
throw new Error("Window size must be a positive integer.");
}
if (this.length < chunkSize) {
return this.slice(0);
}
const accum = [];
for (let i = 0; i < this.length; i += chunkSize) {
accum.push(this.slice(i, i + chunkSize));
}
return accum;
}
});
const input = "W 3 N 5 I 2 W 3 E 2 R 5 O 1 I 5 S 2 S 8 O 2 I 8 W 5 W 6 W 2 W 1 E 7 O 9";
const i = input.split(" ").chunks(2)
let x = 0, y = 0, z = 0;
for (let [dir, dist] of i) {
dist = parseInt(dist, 10);
if (dir === "N") {
y -= dist;
} else if (dir === "S") {
y += dist;
} else if (dir === "E") {
x += dist;
} else if (dir === "W") {
x -= dist;
} else if (dir === "I") {
z += dist;
} else if (dir === "O") {
z -= dist;
}
}
console.log(`(${x}, ${y}, ${z})`)

View File

@ -0,0 +1,108 @@
# frozen_string_literal: true
require "active_support/all"
require "pry"
class Set
alias :includes? :include?
end
class State
def initialize(flat)
@flat = flat
end
attr_reader :flat
def square
@square ||= @flat.in_groups_of 3
end
# Return the index of the empty square, 0-8
def empty
@empty ||= flat.index 0
end
def slide(dir)
# Let's assume the move is possible
if dir == :up
# index of the tile to move, so that's the tile below the current pos
# So we're going to grab empty and add three to push it to the next row
# And that's all good as long as this is a valid move
to_move_index = empty + 3
elsif dir == :down
to_move_index = empty - 3
elsif dir == :left
# Only valid if empty is in the first two columns
# So the index + 1 should work
to_move_index = empty + 1
elsif dir == :right
to_move_index = empty - 1
end
new_state = flat.dup
to_move = flat[to_move_index]
new_state[empty] = to_move
new_state[to_move_index] = 0
return State.new new_state
end
def possible_moves
valid = []
if empty <= 5
valid.push :up
end
if empty >= 3
valid.push :down
end
# Can't go left if empty is in the last row
if empty % 3 != 2
valid.push :left
end
if empty % 3 != 0
valid.push :right
end
return valid
end
def adjacent_states
return possible_moves.map do |dir|
slide dir
end
end
def to_s
@s ||= flat.join ""
end
def dis
puts square.map(&:to_s).join("\n").sub("0", " ")
puts "\n"
end
end
seen_states = Set.new
# Last is actually the one we're going to work off of, so we can push and pop
unchecked_states = [State.new((0..8).to_a)]
while unchecked_states.length > 0
state = unchecked_states.pop
state.adjacent_states.each do |s|
unless seen_states.includes? s.to_s
seen_states.add s.to_s
unchecked_states.push s
end
end
end
# seen_states.count
# => 181440
binding.pry

BIN
my-solutions/Linoleum.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

18
my-solutions/euler-19.rb Normal file
View File

@ -0,0 +1,18 @@
require "active_support/all"
first_sundays = 0
start_date = Date.parse "1901-01-01"
end_date = Date.parse "2000-12-31"
date = start_date
while date < end_date
if date.day == 1 and date.sunday? then
first_sundays += 1
end
date = date.tomorrow
end
p first_sundays

View File

@ -0,0 +1,15 @@
require "vips"
im = Vips::Image.new_from_file "Linoleum.jpg"
binding.irb
count = 0
im.to_a.each do |row|
row.each do |pixel|
count += 1 if pixel == [52, 49, 53]
# p pixel if pixel[0] == 71
end
end
p count

208
my-solutions/nim/mobius.rb Normal file
View File

@ -0,0 +1,208 @@
# Authored 2023 Matthias Portzel. Public Domain
require "pry"
require "active_support/all"
require "ostruct"
class Direction
attr_accessor :name
attr_accessor :offset
def initialize(name, offset)
@name = name
@offset = OpenStruct.new x: offset[0], y: offset[1]
end
end
DIRECTIONS = [
Direction.new(:up_left, [-1, -1]),
Direction.new(:up, [0, -1]),
Direction.new(:up_right, [1, -1]),
Direction.new(:left, [-1, 0]),
Direction.new(:right, [1, 0]),
Direction.new(:down_left, [-1, +1]),
Direction.new(:down, [0, +1]),
Direction.new(:down_right, [+1, +1])
]
def for_xy (x_range, y_range, &block)
for x in x_range
for y in y_range
block.call x, y
end
end
end
class Cell
attr_accessor :is_alive
def alive?
@is_alive
end
def dead?
!@is_alive
end
for d in DIRECTIONS
attr_accessor d.name
end
attr_accessor :coords
def initialize(is_alive)
@is_alive = is_alive
@coords = OpenStruct.new x: nil, y: nil
end
def neighbors
DIRECTIONS.map do |d|
send d.name
end
end
end
class MobiusStrip
# 2D array of trues and falses
def initialize(grid)
@state = grid.map do |row|
row.map do |cell|
Cell.new cell
end
end
max_x = @state.first.length
max_y = @state.length
for_xy (0..(max_x-1)), (0..(max_y-1)) do |x, y|
cell = @state[y][x]
cell.coords.x = x
cell.coords.y = y
for d in DIRECTIONS
link_x = (x + d.offset.x) % max_x
link_y = (y + d.offset.y) % max_y
if x + d.offset.x == -1 || x + d.offset.x == max_x
link_y = ((max_y - 1) - link_y) % max_y
end
cell.send(d.name.to_s + "=", @state[link_y][link_x])
end
end
end
def grid
@state
end
def self.from_string(grid_string)
new(grid_string.split("\n").map(&:strip).map do |row|
row.split("").map do |cell|
cell == "#"
end
end)
end
def count_living_neighbors(cell)
cell.neighbors.filter(&:alive?).length
end
def step
max_x = @state.first.length
max_y = @state.length
new_grid = Array.new(max_y).map { Array.new(max_x) }
for_xy (0..(max_x-1)), (0..(max_y-1)) do |x, y|
cell = @state[y][x]
live_neighbors = count_living_neighbors(@state[y][x])
# Cell alive, and 0..1 live neighbors, die
# Cell alive, 4.. live neighbors, die
# Cell alive, no change
# Cell dead, 3 live neighbors, live
# Cell dead, no change
new_state = if cell.alive? && (0..1) === live_neighbors then
false
elsif cell.alive? && live_neighbors >= 4 then
false
elsif cell.alive? then
true
elsif cell.dead? && live_neighbors == 3 then
true
elsif cell.dead?
false
end
# p [cell.coords, cell.alive?.to_s, live_neighbors.to_s, new_state.to_s].join ", "
new_grid[y][x] = new_state
end
MobiusStrip.new new_grid
end
def display
lines = @state.map do |row|
chars = row.map do |cell|
if cell.alive? then "#" else "." end
end
chars.join ""
end
lines.join "\n"
end
end
test_board = MobiusStrip.from_string(<<-EOF
..........
..........
........#.
........#.
#.#..#..#.
#.#.#.#...
.##..##.#.
..#.......
##........
..........
EOF
)
board = MobiusStrip.from_string(<<-EOF
....................
............###.....
.....#.#...#...#....
...........#........
.....#.#...#........
......#....#.###....
...........#...#....
...##......#...#....
...##.......###.....
....................
....................
...#####...#...#....
...#.......#...#....
...#.......#...#....
...#####...#####....
...#.......#...#....
...#.......#...#....
...#.......#...#....
...#.......#...#....
....................
EOF
)
# board = test_board
past_boards = []
day = 0
loop do
board_string = board.display
if past_boards.include? board_string
after_days = day - 1
puts "Repetition after day #{after_days}"
break
end
puts "On day #{day}:"
puts board_string
past_boards << board_string
board = board.step
day += 1
end

View File

@ -0,0 +1,12 @@
const a = {b: 2, c: 3};
Object.defineProperty(Object.prototype, "with_key_methods", {
value: function () {
for (const {k: v} in this) {
console.log(k, v);
// this[k] = () => v;
}
}
});
a.with_key_methods().b()

View File

@ -0,0 +1 @@
{"type":"module"}

View File

@ -0,0 +1,37 @@
// What is the second smallest prime number:
// * whose digits are all odd,
// * greater than 1057
// * whose palindrome is also an odd number
const primes = [];
function isPrime(num) {
if (primes.includes(num)) {
return true;
}else {
const s = sqrt(num);
for (const i = 0; i < s; i++) {
if (num % i === 0) {
return false;
}
}
primes.push(num);
return true;
}
}
Number.prototype.digits = function () {
return this.toString().split().reverse().map(d => parseInt(d, 10));
}
Number.prototype.isOdd = function () {
return this % 2 === 1;
}
const numFound = 0;
for (const i = 1057; numFound < 2; i++) {
if (isPrime(i) && i.digits().all()) {
}
}

17
my-solutions/prime.rb Normal file
View File

@ -0,0 +1,17 @@
require 'prime'
# // What is the second smallest prime number:
# // * whose digits are all odd,
# // * greater than 1057
# // * whose palindrome is also an odd number
numFound = 0
i = 1057
while numFound < 30 do
if i.prime? && i.digits.join.to_i.prime?
p i
numFound += 1
end
i += 1
end

View File

@ -0,0 +1,182 @@
// Going to regret doing this in JS but whatever
import "/Users/matthias/Programs/tmp-name-js-plus/main.js"
/*
const points = [];
for (let i = 0; i < 33; i++) {
points.push({
x: Math.floor(Math.random() * 100),
y: Math.floor(Math.random() * 100)
});
}
const seen = new Set();
for (const point of points) {
seen.add(JSON.stringify(point));
}
points.clear();
for (const point of seen) {
points.push(JSON.parse(point));
}
*/
const points = [
{ x: 21, y: 21 }, { x: 94, y: 81 },
{ x: 22, y: 32 }, { x: 98, y: 96 },
{ x: 12, y: 94 }, { x: 57, y: 58 },
{ x: 31, y: 30 }, { x: 56, y: 7 },
{ x: 60, y: 1 }, { x: 27, y: 47 },
{ x: 74, y: 55 }, { x: 53, y: 70 },
{ x: 48, y: 74 }, { x: 20, y: 68 },
// { x: 47, y: 70 }, { x: 51, y: 27 },
// { x: 93, y: 27 }, { x: 31, y: 88 },
// { x: 25, y: 36 }, { x: 28, y: 31 },
// { x: 44, y: 33 }, { x: 18, y: 56 },
// { x: 80, y: 18 }, { x: 44, y: 65 },
// { x: 0, y: 61 }, { x: 57, y: 55 },
// { x: 39, y: 29 }, { x: 4, y: 39 },
// { x: 83, y: 59 }, { x: 9, y: 38 },
// { x: 28, y: 2 }, { x: 28, y: 73 },
// { x: 72, y: 40 }
];
// At least 3 points in each loop
// A*?
// I'm nervous because this is a really poor match for A* on the face of it; A* excels at deep problems, but this is *really* shallow. Maybe that's fine? Because it's so shallow it shouldn't be that hard to finish any particular branch.
// But we also might hit memory limitations.
// My intuition tells me brute force will be just out of reach. 33! solutions? / 3 for symmetry between the 3 loops. I think. 3 e 36.
// At least several days
// let l = 0
// for (let i = 0; i < 2.894439206E36; i ++) {
// l += i;
// if (i % 100000000 === 0) {
// console.log(i / 2.894439206E36)
// }
// }
// console.log(l);
// I'm going to give A* a shot
// A node is a configuration of assigned loops and any unassigned points
// A*'s nice because as soon as we find one solution that assigns all points, we have the minimum
// A* is best-first + heuristic, with the score being the current loop distance
// The problem is we don't have a good heuristic
// Let's run best-first and see what happens
function distance (point1_id, point2_id) {
const point1 = points[point1_id];
const point2 = points[point2_id];
return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2);
}
class State {
constructor () {
// super(); ???
// Three loops. Each one is a list of points
// "point" refers to the index of the point in points, we're not copying them
this.loops = [[], [], []];
this.unassignedPoints = Array.range(points.length);
// We can start off with the first point in the first loop
this.assign(0, 0);
}
assign (point_i, loop_i) {
this.loops[loop_i].push(point_i);
this.unassignedPoints.remove(point_i);
this.updateScore();
}
isComplete () {
return this.unassignedPoints.length === 0;
}
getIncompleteScore () {
let score = 0;
for (const loop of this.loops) {
loop.windows(2).forEach(function (pair) {
score += distance(...pair);
});
}
return score;
}
getCompleteScore () {
// The complete score is just the incomplete score + the distance to "close", from the last point to the first one
let score = this.getIncompleteScore();
for (const loop of loops) {
score += distance(loop.last, loop.first);
}
return score;
}
// Get the score
getScore () {
return this.isComplete() ? this.getCompleteScore() : this.getIncompleteScore();
}
updateScore () {
this.score = this.getScore();
}
clone () {
// This creates a new initial State, but that's fine
const state = new State();
state.unassignedPoints = this.unassignedPoints.copy();
for (let i = 0; i < this.loops.length; i ++) {
state.loops[i] = this.loops[i].copy();
}
state.updateScore();
return state;
}
// Return all possible next states
getNextStates () {
// if there aren't any, return []
if (this.isComplete()) {
return [];
}
const nextStates = [];
// Otherwise, we have the option of assigning any unassigned point to any of the three loops
for (let i = 0; i < this.unassignedPoints.length; i++) {
for (let j = 0; j < this.loops.length; j++) {
const state = this.clone();
state.assign(this.unassignedPoints[i], j);
nextStates.push(state);
}
}
return nextStates;
}
}
// Sorted by score of course
// Best score at the end, so we can .pop() it
// Best score is lowest of course
const states = [new State()];
// console.log(states.first);
// console.log(states.first.getNextStates());
// Insert 1 or more states preserving sort order
function insertState (...states) {
for (const state of states) {
// i is the index that the state will end up at
// If we're inserting at the end a lot, running this loop in reverse may be worth it
let i = 0;
while (i < states.length && states[i].score > state.score) {
i ++;
}
states.splice(i, 0, state);
}
}
while (!states.last.isComplete()) {
// process a state
const state = states.pop();
insertState(...state.getNextStates());
}
print(states);

24
my-solutions/svg.rb Normal file
View File

@ -0,0 +1,24 @@
require "nokogiri"
f = File.read("Linoleumherstellung.svg")
xml = Nokogiri::XML(f)
# binding.irb
# stack = [xml]
# max_depth = 0
# until stack.empty?
# end
# Height is the number of edges, so a tree with two nodes has height 1
# <foo> <bar> </bar> </foo>
def max_depth(tree)
return 0 if tree.children.empty?
return 1 + (tree.children.map { |c| max_depth c }).max
end
p max_depth(xml)

24
send_puzzle_job.rb Normal file
View File

@ -0,0 +1,24 @@
require "active_job"
class SendPuzzleJob < ActiveJob::Base
queue_as :default
# Do something later
def perform
# Remove the puzzle command from everyone
remove_solver_role
# Get a random puzzle
puzzle = Puzzle.ready.order("RANDOM()").first
if puzzle.present?
# Send the new puzzle
$puzzle_channel.send puzzle.text
# Mark it as current
puzzle.current!
else
# Panic
$study_channel.send "<@#{$me.id}> Help! No more puzzles! No more Club???"
end
end
end

9
test.rb Normal file
View File

@ -0,0 +1,9 @@
$value = 5
# p awdawd
def test_func
p $value.to_s
end
test_func