Last active
February 2, 2022 20:29
-
-
Save Erquint/75825961df606c4fc7cf0786bd563e4b to your computer and use it in GitHub Desktop.
Banal natural selection emulation. Wrote mainly to mess around with terminal manipulation and Win32 bindings.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ENCODING: UTF-8 | |
# ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x64-mingw32] | |
# THIS SCRIPT WILL INSTALL REQUIRED GEMS IF POSSIBLE. | |
# irb -I . -r ./main.rb # Ignore this. | |
def require_gem gemname, requirename = nil | |
requirename = gemname if requirename.nil? | |
require 'rubygems' | |
begin | |
gem gemname | |
rescue Gem::LoadError | |
Gem.install gemname | |
gem gemname | |
end | |
require requirename | |
end | |
require 'io/console' | |
require_gem 'rainbow' | |
require_gem 'win32-api', 'win32/api' | |
include Win32 | |
Period = ['-p', '--period'].include?(ARGV[0]) ? ARGV[1].to_i : 0.2 | |
Character = ?@ | |
Column_max = 80 | |
Critter_count = 60 * 12 | |
Predator_count = 5 | |
$footer = "Try #{Rainbow('ruby ./main.rb --period 0').yellow} (or #{Rainbow('-p 0').yellow}) for unlimited speed.\n" + | |
"Exit by sending an interrupt signal using #{Rainbow('[Ctrl]+[C]').yellow}." | |
# Win32 API binding data type legend. | |
# 'I' (integer) | |
# 'L' (long) | |
# 'V' (void) | |
# 'P' (pointer) | |
# 'K' (callback) | |
# 'S' (string) | |
Get_std_handle = API.new('GetStdHandle', ?I) | |
Get_console_screen_buffer_info = API.new('GetConsoleScreenBufferInfo', 'IS') | |
Set_console_cursor_position = API.new('SetConsoleCursorPosition', 'IS') | |
Get_last_error = API.new('GetLastError') | |
Get_console_cursor_info = API.new('GetConsoleCursorInfo', 'IS') | |
Set_console_cursor_info = API.new('SetConsoleCursorInfo', 'IS') | |
class Critter | |
Character = Character | |
attr_accessor :r, :g, :b, :s | |
def initialize(*args) | |
if args.empty? | |
@s = Character | |
rand_c | |
elsif args.size == 1 | |
@s = args[0] | |
rand_c | |
elsif args.size == 3 | |
@r, @g, @b, @s = args[0], args[1], args[2], Character | |
elsif args.size == 4 | |
@r, @g, @b, @s = args[0], args[1], args[2], args[4] | |
else | |
raise ArgumentError | |
end | |
end | |
def rand_c | |
@r, @g, @b = rand(255), rand(255), rand(255) | |
return self | |
end | |
def string | |
return Rainbow(@s).fg(@r, @g, @b) | |
end | |
def print | |
print string | |
return true | |
end | |
end | |
class Field | |
Column_max = Column_max | |
def initialize(critter_count = Critter_count, predator_count = Predator_count, column_max = Column_max) | |
@predator_count = predator_count | |
@column_count = (Math.sqrt(critter_count) * 1.5).truncate.clamp(0, column_max) | |
@critters = Array.new(critter_count) | |
@critters.map!{|critter| Critter.new} | |
end | |
def map! *args | |
@critters.map! *args | |
end | |
def each *args | |
@critters.each *args | |
end | |
def inject *args, &block | |
@critters.inject *args, &block | |
end | |
def string | |
buffer = String.new | |
column = 0 | |
@critters.each do |critter| | |
if column < @column_count | |
# critter.print | |
buffer.concat critter.string | |
column += 1 | |
else | |
column = 0 | |
buffer.concat "\n" | |
redo | |
end | |
end | |
buffer.concat "\n", $footer, "\n" | |
return buffer | |
end | |
def print | |
# console_screen_buffer_info = 0.chr * [4, 4, 2, 8, 4].sum | |
# raise('GetConsoleScreenBufferInfo → zero!', cause: Exception.new(Get_last_error.call)) if | |
# Get_console_screen_buffer_info.call(Stdout_handle, console_screen_buffer_info).zero? | |
# console_screen_buffer_info = console_screen_buffer_info.unpack('a4a4Sa8a4') | |
# dwSize = console_screen_buffer_info[0].unpack('s2') | |
# dwCursorPosition = console_screen_buffer_info[1].unpack('s2') | |
# wAttributes = console_screen_buffer_info[2] | |
# srWindow = console_screen_buffer_info[3].unpack('s4') | |
# dwMaximumWindowSize = console_screen_buffer_info[4].unpack('s2') | |
STDOUT.sync = false | |
STDOUT.goto 0, 0 | |
STDOUT.write string | |
STDOUT.flush | |
STDOUT.sync = true | |
# raise('SetConsoleCursorPosition → zero!', cause: Exception.new(Get_last_error.call)) if | |
# Set_console_cursor_position.call(Stdout_handle, dwCursorPosition).zero? | |
return true | |
end | |
def predate | |
# probability_space = Array.new | |
# self.each do |critter| | |
# (critter.r + critter.g).times{probability_space << critter} | |
# end | |
# @predator_count.times do | |
# critter = probability_space.sample | |
# critter.rand_c | |
# @predated = (defined?(@predated) && [email protected]?) ? @predated + 1 : 0 | |
# @palevo = (defined?(@palevo) && [email protected]?) ? @palevo + (critter.r + critter.g) : 0 | |
# $footer = "palevo_ratio: #{@palevo.fdiv @predated}" if defined?(@palevo) | |
# end | |
# # palevo_ratio: 254.745014900298 | |
# Antender's algo. | |
lottery_entries = @critters.inject(0){|sum, critter| next (sum + critter.r + critter.g)} | |
@predator_count.times do | |
winning_ticket = rand(lottery_entries) | |
@critters.each do |critter| | |
winning_ticket -= critter.r + critter.g | |
unless winning_ticket.positive? | |
# @predated = (defined?(@predated) && [email protected]?) ? @predated + 1 : 0 | |
# @palevo = (defined?(@palevo) && [email protected]?) ? @palevo + (critter.r + critter.g) : 0 | |
# $footer = "palevo_ratio: #{@palevo.fdiv @predated}" if defined?(@palevo) | |
critter.rand_c | |
break | |
end | |
end | |
end | |
# # palevo_ratio: 254.45631067961165 | |
return true | |
end | |
end | |
class Periodic | |
def initialize period | |
@period = period | |
@timestamp = self.class.time_float | |
end | |
def self.time_float | |
time = Time.now | |
return time.to_i + time.nsec / 1e9 | |
end | |
def remainder | |
return @period - (self.class.time_float - @timestamp) | |
end | |
def wait | |
sleep(remainder) if remainder.positive? | |
@timestamp = self.class.time_float | |
return true | |
end | |
end | |
begin | |
Stdout_handle = Get_std_handle.call(-11) | |
# $footer << "Stdout_handle: #{Stdout_handle}" | |
Field = Field.new | |
Sim_turn = Periodic.new Period | |
STDOUT.erase_screen 2 | |
# console_cursor_info = 0.chr * [4, 1].sum | |
# raise('GetConsoleCursorInfo → zero!', cause: Exception.new(Get_last_error.call)) if | |
# Get_console_cursor_info.call(Stdout_handle, console_cursor_info).zero? | |
# console_cursor_info = console_cursor_info.unpack('LC') | |
# puts dwSize = console_cursor_info[0] | |
# puts bVisible = console_cursor_info[1] | |
raise('SetConsoleCursorInfo → zero!', cause: Exception.new(Get_last_error.call)) if | |
Set_console_cursor_info.call(Stdout_handle, [25, 0].pack('IC')).zero? | |
while Sim_turn.wait | |
Field.print | |
Field.predate | |
end | |
ensure | |
STDOUT.goto 0, 0 | |
STDOUT.erase_screen 2 | |
puts $footer | |
raise('SetConsoleCursorInfo → zero!', cause: Exception.new(Get_last_error.call)) if | |
Set_console_cursor_info.call(Stdout_handle, [25, 1].pack('IC')).zero? | |
# exit | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment