stockshark 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8dcf3f23451283338c0ab09bcf73a1d218b40864ec1238950d65ac40698aab17
4
+ data.tar.gz: ac7a4ca6772c90c1f989a4f4fa42d756896570c6bc2ff55495a2bb724a0869f4
5
+ SHA512:
6
+ metadata.gz: 8a03cbc0c0e94c066a22278f369f132414020a18f04bb2dddc39259a2b773c4f352f2ef75c6680dc20448255a121b5a3777ab6b20bbae6254520cefaf8cb534e
7
+ data.tar.gz: d1329f806ead3a46f7afc6f6270c9c53785797e7487faca0ab5b9b643cb230e3056c802ddb456d8d967cfbd5bf9963c71f6bfa57efb745ba274a1a48032eb559
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /pkg/
4
+ /spec/reports/
5
+ /spec/examples.txt
6
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.8
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake', '~> 13.4'
6
+ gem 'rspec', '~> 3.13'
7
+ gem 'rubocop', '~> 1.88'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Stockshark
2
+
3
+ A small, dependency-free Ruby wrapper for talking to a UCI-speaking chess
4
+ engine (Stockfish, or anything else that implements the protocol) over
5
+ stdin/stdout/stderr pipes: start it, hand it a FEN, get back a structured
6
+ evaluation (centipawns or mate score, best move, principal variation, depth
7
+ reached). No chess rules engine, no board representation, no opinions about
8
+ perspective or move classification — just the UCI protocol, cleanly
9
+ wrapped.
10
+
11
+ ## Installation
12
+
13
+ ```
14
+ bundle add stockshark
15
+ ```
16
+
17
+ Or add it to your Gemfile directly:
18
+
19
+ ```ruby
20
+ gem "stockshark"
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ engine = Stockshark::Engine.new
27
+ result = engine.analyze(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", depth: 18)
28
+
29
+ result.best_move # => "e2e4"
30
+ result.depth # => 18
31
+ result.lines.first.pv # => ["e2e4", "e7e5"]
32
+ engine.quit
33
+ ```
34
+
35
+ Pass `movetime_ms:` instead of `depth:` for a time-bounded search, and
36
+ `multipv:` to get more than one ranked line back (`result.lines` is sorted
37
+ by rank, so `lines.first` is always the primary line).
38
+
39
+ ## Configuration
40
+
41
+ ```ruby
42
+ configuration = Stockshark::Configuration.new(threads: 4, hash_mb: 128)
43
+ engine = Stockshark::Engine.new(configuration)
44
+ ```
45
+
46
+ Every option also has an environment variable default (see
47
+ `lib/stockshark/configuration.rb`) — deliberately named `STOCKFISH_*`, not
48
+ `STOCKSHARK_*`, since they configure the actual engine binary being
49
+ launched rather than this library:
50
+
51
+ - **`STOCKFISH_PATH`** (default `stockfish`) — path to the engine binary,
52
+ resolved via `$PATH` if not absolute.
53
+ - **`STOCKFISH_THREADS`** (default `1`) — UCI `Threads`.
54
+ - **`STOCKFISH_HASH`** (default `16`) — UCI `Hash`, transposition table
55
+ size in MB.
56
+ - **`STOCKFISH_MAX_ANALYZE_MS`** (default `30000`) — how long the engine
57
+ is given to respond before `Stockshark::TimeoutError` is raised.
58
+
59
+ ## Errors
60
+
61
+ All raised errors descend from `Stockshark::Error`:
62
+
63
+ - `Stockshark::EngineNotFoundError` — the configured binary doesn't exist,
64
+ isn't executable, or couldn't be spawned.
65
+ - `Stockshark::TimeoutError` — the engine didn't respond within the
66
+ configured deadline.
67
+ - `Stockshark::CrashedError` — the engine process exited mid-conversation.
68
+
69
+ ## Development
70
+
71
+ After checking out the repo, run `bin/setup` to install dependencies.
72
+ Then:
73
+
74
+ ```
75
+ bundle exec rspec # fake-engine suite, no binary required
76
+ bundle exec rspec --tag stockfish # also runs the real-engine integration spec
77
+ ```
78
+
79
+ The `--tag stockfish` run needs an actual Stockfish binary on `$PATH` (or
80
+ pointed to via `STOCKFISH_PATH`) — it's skipped automatically otherwise
81
+ (see `spec/support/stockfish_availability.rb`). Install one with:
82
+
83
+ ```
84
+ brew install stockfish # macOS
85
+ apt install stockfish # Debian/Ubuntu
86
+ ```
87
+
88
+ or download a build from [stockfishchess.org/download](https://stockfishchess.org/download).
89
+
90
+ Run `bin/console` for an interactive prompt with the gem already loaded.
91
+
92
+ To install this gem onto your local machine, run `bundle exec rake
93
+ install`. To release a new version, bump the version in
94
+ `lib/stockshark/version.rb`, then run `bundle exec rake release`, which
95
+ tags the version, pushes the commit and tag, and pushes the `.gem` file to
96
+ rubygems.org.
97
+
98
+ ## License
99
+
100
+ MIT — see `LICENSE.txt`.
101
+
102
+ ---
103
+
104
+ Built by [TrueFlux](https://trueflux.agency).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "stockshark"
6
+
7
+ require "irb"
8
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,25 @@
1
+ module Stockshark
2
+ # Environment-driven configuration — nothing here is ever a hardcoded,
3
+ # machine-specific path. Every value has a sensible default so the
4
+ # library works out of the box locally, but production is expected to
5
+ # set these explicitly (particularly STOCKFISH_PATH, since "stockfish"
6
+ # resolving via $PATH isn't guaranteed on every deploy target). Env var
7
+ # names stay STOCKFISH_* (not STOCKSHARK_*) deliberately — they configure
8
+ # the actual Stockfish (or Stockfish-compatible) binary being launched,
9
+ # which is a separate thing from what this gem happens to be called.
10
+ class Configuration
11
+ attr_accessor :path, :threads, :hash_mb, :max_analyze_ms
12
+
13
+ def initialize(
14
+ path: ENV.fetch("STOCKFISH_PATH", "stockfish"),
15
+ threads: Integer(ENV.fetch("STOCKFISH_THREADS", 1)),
16
+ hash_mb: Integer(ENV.fetch("STOCKFISH_HASH", 16)),
17
+ max_analyze_ms: Integer(ENV.fetch("STOCKFISH_MAX_ANALYZE_MS", 30_000))
18
+ )
19
+ @path = path
20
+ @threads = threads
21
+ @hash_mb = hash_mb
22
+ @max_analyze_ms = max_analyze_ms
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ module Stockshark
2
+ # The engine process exited (or was killed) mid-conversation.
3
+ class CrashedError < Error; end
4
+ end
@@ -0,0 +1,124 @@
1
+ module Stockshark
2
+ # The public façade for talking to a UCI engine — the only class most
3
+ # callers should ever need to touch. Hides process management (Process)
4
+ # and protocol parsing (UciProtocol) entirely: callers only ever see
5
+ # "give me a FEN and a depth/movetime, get back an evaluation."
6
+ class Engine
7
+ # One multipv rank's worth of a search result: its own score (exactly
8
+ # one of score_cp/score_mate is ever set, matching UCI itself — an
9
+ # engine never reports both for the same line) and principal variation.
10
+ Line = Struct.new(:multipv, :score_cp, :score_mate, :pv, keyword_init: true)
11
+
12
+ # The full result of one #analyze call: the move the engine would
13
+ # actually play, the deepest ply reached, and one Line per multipv
14
+ # rank requested (ranks are 1-indexed and sorted, so lines.first is
15
+ # always the primary/best line). Raw and side-to-move-relative —
16
+ # unopinionated about which color that was.
17
+ AnalysisResult = Struct.new(:best_move, :depth, :lines, keyword_init: true)
18
+
19
+ attr_reader :name
20
+
21
+ def initialize(configuration = Stockshark::Configuration.new, process: nil)
22
+ @configuration = configuration
23
+ @process = process || Stockshark::Process.new(configuration.path)
24
+ @multipv = 1
25
+ handshake
26
+ end
27
+
28
+ def analyze(fen:, depth: nil, movetime_ms: nil, multipv: 1)
29
+ raise ArgumentError, "must give depth or movetime_ms" unless depth || movetime_ms
30
+
31
+ set_multipv(multipv)
32
+ send_line("position fen #{fen}")
33
+ send_line(depth ? "go depth #{depth}" : "go movetime #{movetime_ms}")
34
+
35
+ collect_result(search_timeout(movetime_ms))
36
+ end
37
+
38
+ # Safe to call more than once, and safe to call even if the engine was
39
+ # never fully brought up (e.g. handshake itself failed).
40
+ def quit
41
+ @process.stop
42
+ end
43
+
44
+ private
45
+
46
+ def handshake
47
+ send_line("uci")
48
+ await("uciok") { |message| @name = Stockshark::UciProtocol.parse_id(message)&.fetch(:name) || @name }
49
+
50
+ send_line("setoption name Threads value #{@configuration.threads}")
51
+ send_line("setoption name Hash value #{@configuration.hash_mb}")
52
+ send_line("isready")
53
+ await("readyok")
54
+
55
+ send_line("ucinewgame")
56
+ end
57
+
58
+ def set_multipv(multipv)
59
+ return if multipv == @multipv
60
+
61
+ send_line("setoption name MultiPV value #{multipv}")
62
+ @multipv = multipv
63
+ end
64
+
65
+ def send_line(line)
66
+ @process.write_line(line)
67
+ end
68
+
69
+ # Reads lines (via the configured handshake timeout) until `stop_line`
70
+ # is seen, yielding every line along the way so the caller can pick up
71
+ # anything useful (like "id name ...") that appears before it.
72
+ def await(stop_line)
73
+ deadline = Time.now + (@configuration.max_analyze_ms / 1000.0)
74
+ loop do
75
+ message = read(deadline)
76
+ yield message if block_given?
77
+ return if message == stop_line
78
+ end
79
+ end
80
+
81
+ def collect_result(timeout)
82
+ deadline = Time.now + timeout
83
+ latest_by_rank = {}
84
+ max_depth = 0
85
+
86
+ loop do
87
+ message = read(deadline)
88
+
89
+ if (info = Stockshark::UciProtocol.parse_info(message))
90
+ latest_by_rank[info[:multipv]] = info
91
+ max_depth = [ max_depth, info[:depth] ].max
92
+ elsif (best = Stockshark::UciProtocol.parse_bestmove(message))
93
+ lines = latest_by_rank.sort.map do |_, info|
94
+ Line.new(multipv: info[:multipv], score_cp: info[:score_cp], score_mate: info[:score_mate], pv: info[:pv])
95
+ end
96
+ return AnalysisResult.new(best_move: best[:best_move], depth: max_depth, lines: lines)
97
+ end
98
+ end
99
+ end
100
+
101
+ # Blocks until a line arrives on stdout or `deadline` passes, skipping
102
+ # (but not erroring on) stderr activity — Process already drains it
103
+ # continuously in the background, this just isn't interested in its
104
+ # content, only in not being blocked by it.
105
+ def read(deadline)
106
+ loop do
107
+ remaining = deadline - Time.now
108
+ raise Stockshark::TimeoutError, "engine did not respond in time" if remaining <= 0
109
+ raise Stockshark::CrashedError, "engine process exited unexpectedly" unless @process.alive?
110
+
111
+ entry = @process.read_line(timeout: remaining)
112
+ next if entry.nil?
113
+ next if entry[:stream] == :stderr
114
+
115
+ return entry[:line]
116
+ end
117
+ end
118
+
119
+ def search_timeout(movetime_ms)
120
+ base = movetime_ms ? movetime_ms / 1000.0 : 0
121
+ base + (@configuration.max_analyze_ms / 1000.0)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,5 @@
1
+ module Stockshark
2
+ # The configured engine binary doesn't exist, isn't executable, or
3
+ # couldn't be spawned for some other OS-level reason.
4
+ class EngineNotFoundError < Error; end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Stockshark
2
+ # Base class for every error this library raises. Callers that only care
3
+ # that "something went wrong talking to the engine" can rescue this one
4
+ # class rather than enumerate every specific failure mode.
5
+ class Error < StandardError; end
6
+ end
@@ -0,0 +1,81 @@
1
+ require "open3"
2
+
3
+ module Stockshark
4
+ # Raw OS process lifecycle for a UCI-speaking engine — spawns it, writes
5
+ # lines to its stdin, and continuously drains stdout *and* stderr from
6
+ # dedicated reader threads so neither pipe can ever fill up and block the
7
+ # child. Draining stderr matters here specifically because the engine
8
+ # path is operator-configurable: nothing guarantees the binary behind it
9
+ # is a "pure" build that never writes to stderr, and if it did while
10
+ # nobody was reading that pipe, the child would block writing to it and
11
+ # every read on stdout would hang right along with it.
12
+ #
13
+ # Zero chess/UCI knowledge on purpose — Engine owns the protocol, this
14
+ # only owns "a subprocess and its pipes."
15
+ class Process
16
+ def initialize(path)
17
+ @stdin, stdout, stderr, @wait_thread = Open3.popen3(path)
18
+ @queue = Queue.new
19
+ @readers = [ reader_thread(stdout, :stdout), reader_thread(stderr, :stderr) ]
20
+ rescue Errno::ENOENT, Errno::EACCES => e
21
+ raise Stockshark::EngineNotFoundError, "could not start engine at #{path.inspect}: #{e.message}"
22
+ end
23
+
24
+ def write_line(line)
25
+ raise Stockshark::CrashedError, "engine process is not running" unless alive?
26
+
27
+ @stdin.puts(line)
28
+ rescue Errno::EPIPE, IOError => e
29
+ raise Stockshark::CrashedError, "could not write to engine: #{e.message}"
30
+ end
31
+
32
+ # Pops the next available line from either stream, tagged with which
33
+ # one it came from, waiting at most `timeout` seconds. Returns nil on
34
+ # timeout — never raises Timeout::Error, deliberately: cancellation
35
+ # here is just "give up waiting on the queue," not "interrupt whatever
36
+ # is actually blocking," which is what makes Ruby's Timeout.timeout
37
+ # unsafe to wrap around real IO.
38
+ def read_line(timeout:)
39
+ @queue.pop(timeout: timeout)
40
+ end
41
+
42
+ def alive?
43
+ @wait_thread.alive?
44
+ end
45
+
46
+ # Safe to call more than once, and safe to call after the process has
47
+ # already died on its own.
48
+ def stop
49
+ return unless alive?
50
+
51
+ begin
52
+ @stdin.puts("quit")
53
+ rescue Errno::EPIPE, IOError
54
+ # already gone — nothing left to say goodbye to.
55
+ end
56
+
57
+ unless @wait_thread.join(0.5)
58
+ ::Process.kill("KILL", @wait_thread.pid)
59
+ @wait_thread.join
60
+ end
61
+ ensure
62
+ @readers.each(&:kill)
63
+ @stdin.close unless @stdin.closed?
64
+ end
65
+
66
+ private
67
+
68
+ def reader_thread(io, tag)
69
+ Thread.new do
70
+ loop do
71
+ line = io.gets
72
+ break if line.nil?
73
+
74
+ @queue << { stream: tag, line: line.chomp }
75
+ end
76
+ rescue IOError
77
+ # stream closed out from under us during shutdown — fine.
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ module Stockshark
2
+ # The engine didn't respond within the configured deadline — either a
3
+ # genuinely wedged search, or a process that's alive but not speaking
4
+ # UCI back (e.g. an unexpected build writing diagnostics to stderr and
5
+ # never reaching readyok/bestmove on stdout).
6
+ class TimeoutError < Error; end
7
+ end
@@ -0,0 +1,53 @@
1
+ module Stockshark
2
+ # Pure parsing of UCI protocol text into structured Ruby data. No IO, no
3
+ # process — every method here is a plain string in, hash/nil out, which
4
+ # is what makes it trivially unit-testable without ever spawning a real
5
+ # engine. Scores are returned exactly as the engine reports them:
6
+ # relative to whichever side is to move in the position that was
7
+ # analyzed. Flipping that to a fixed perspective is a game-aware decision
8
+ # this module deliberately knows nothing about — that's up to whatever
9
+ # calls this gem.
10
+ module UciProtocol
11
+ module_function
12
+
13
+ # "id name Stockfish 16" => { name: "Stockfish 16" }
14
+ # "id author ..." and any other id line => nil (not what we need).
15
+ def parse_id(line)
16
+ match = line.match(/\Aid name (?<name>.+)\z/)
17
+ return nil unless match
18
+
19
+ { name: match[:name] }
20
+ end
21
+
22
+ # "bestmove e2e4 ponder e7e5" => { best_move: "e2e4", ponder: "e7e5" }
23
+ def parse_bestmove(line)
24
+ match = line.match(/\Abestmove (?<best_move>\S+)(?: ponder (?<ponder>\S+))?/)
25
+ return nil unless match
26
+
27
+ { best_move: match[:best_move], ponder: match[:ponder] }
28
+ end
29
+
30
+ # "info depth 20 seldepth 28 multipv 1 score cp 34 nodes 12345 pv e2e4 e7e5 g1f3"
31
+ # => { depth: 20, multipv: 1, score_cp: 34, score_mate: nil, pv: ["e2e4", "e7e5", "g1f3"] }
32
+ # "info depth 12 multipv 2 score mate -3 pv ... "
33
+ # => { depth: 12, multipv: 2, score_cp: nil, score_mate: -3, pv: [...] }
34
+ # Returns nil for info lines that don't carry a scored principal
35
+ # variation (plain "info string ..." diagnostics, "info currmove ...",
36
+ # etc.) — callers should just skip those.
37
+ def parse_info(line)
38
+ return nil unless line.start_with?("info ") && line.include?(" pv ")
39
+
40
+ depth = line[/\bdepth (\d+)/, 1]
41
+ pv = line[/ pv (.+)\z/, 1]
42
+ return nil unless depth && pv
43
+
44
+ {
45
+ depth: depth.to_i,
46
+ multipv: (line[/\bmultipv (\d+)/, 1] || "1").to_i,
47
+ score_cp: line[/\bscore cp (-?\d+)/, 1]&.to_i,
48
+ score_mate: line[/\bscore mate (-?\d+)/, 1]&.to_i,
49
+ pv: pv.split(" ")
50
+ }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Stockshark
2
+ VERSION = "0.1.0"
3
+ end
data/lib/stockshark.rb ADDED
@@ -0,0 +1,20 @@
1
+ require_relative "stockshark/version"
2
+ require_relative "stockshark/error"
3
+ require_relative "stockshark/engine_not_found_error"
4
+ require_relative "stockshark/timeout_error"
5
+ require_relative "stockshark/crashed_error"
6
+ require_relative "stockshark/configuration"
7
+ require_relative "stockshark/process"
8
+ require_relative "stockshark/uci_protocol"
9
+ require_relative "stockshark/engine"
10
+
11
+ # Stockshark::Engine talks to a UCI chess engine (Stockfish, or anything
12
+ # else that speaks the protocol) over stdin/stdout/stderr pipes. Plain Ruby,
13
+ # stdlib only (Open3) — no chess rules engine, no board representation, no
14
+ # opinions about evaluation perspective or move classification, so it stays
15
+ # usable by anything that just needs "FEN in, evaluation out" regardless of
16
+ # what it's built on top of. No autoloader here (unlike when this lived
17
+ # inside a Rails app under Zeitwerk), so every file is required explicitly
18
+ # above, in dependency order.
19
+ module Stockshark
20
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/stockshark/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'stockshark'
5
+ spec.version = Stockshark::VERSION
6
+ spec.authors = ['Phil Brockwell']
7
+ spec.email = ['phil@trueflux.agency']
8
+
9
+ spec.summary = 'A small, dependency-free Ruby wrapper for talking to a UCI chess engine.'
10
+ spec.description = <<~DESC
11
+ Stockshark drives any UCI-speaking chess engine (Stockfish, or anything
12
+ else that implements the protocol) over stdin/stdout/stderr pipes: start
13
+ it, hand it a FEN, get back a structured evaluation (centipawns or mate
14
+ score, best move, principal variation, depth reached). No chess rules
15
+ engine, no board representation, no opinions about perspective or move
16
+ classification — just the UCI protocol, cleanly wrapped.
17
+ DESC
18
+ spec.homepage = 'https://github.com/TrueFlux/stockshark'
19
+ spec.license = 'MIT'
20
+ spec.required_ruby_version = '>= 3.2.0'
21
+
22
+ spec.metadata['homepage_uri'] = spec.homepage
23
+ spec.metadata['source_code_uri'] = spec.homepage
24
+
25
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
26
+ .reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ spec.require_paths = ['lib']
28
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stockshark
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Phil Brockwell
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |
13
+ Stockshark drives any UCI-speaking chess engine (Stockfish, or anything
14
+ else that implements the protocol) over stdin/stdout/stderr pipes: start
15
+ it, hand it a FEN, get back a structured evaluation (centipawns or mate
16
+ score, best move, principal variation, depth reached). No chess rules
17
+ engine, no board representation, no opinions about perspective or move
18
+ classification — just the UCI protocol, cleanly wrapped.
19
+ email:
20
+ - phil@trueflux.agency
21
+ executables: []
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - ".gitignore"
26
+ - ".rspec"
27
+ - ".ruby-version"
28
+ - Gemfile
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - bin/console
33
+ - bin/setup
34
+ - lib/stockshark.rb
35
+ - lib/stockshark/configuration.rb
36
+ - lib/stockshark/crashed_error.rb
37
+ - lib/stockshark/engine.rb
38
+ - lib/stockshark/engine_not_found_error.rb
39
+ - lib/stockshark/error.rb
40
+ - lib/stockshark/process.rb
41
+ - lib/stockshark/timeout_error.rb
42
+ - lib/stockshark/uci_protocol.rb
43
+ - lib/stockshark/version.rb
44
+ - stockshark.gemspec
45
+ homepage: https://github.com/TrueFlux/stockshark
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ homepage_uri: https://github.com/TrueFlux/stockshark
50
+ source_code_uri: https://github.com/TrueFlux/stockshark
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 3.2.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.6.9
66
+ specification_version: 4
67
+ summary: A small, dependency-free Ruby wrapper for talking to a UCI chess engine.
68
+ test_files: []