Skip to content

Instantly share code, notes, and snippets.

@Erquint
Last active February 2, 2022 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Erquint/75825961df606c4fc7cf0786bd563e4b to your computer and use it in GitHub Desktop.
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.
# 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) && !@predated.nil?) ? @predated + 1 : 0
# @palevo = (defined?(@palevo) && !@palevo.nil?) ? @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) && !@predated.nil?) ? @predated + 1 : 0
# @palevo = (defined?(@palevo) && !@palevo.nil?) ? @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