mirror of https://github.com/tildeio/helix
Merge pull request #152 from tildeio/usize_isize_coercions
Add missing coercions for `usize` and `isize`
This commit is contained in:
commit
c6c0011b4b
|
@ -3,6 +3,7 @@ cache:
|
|||
- '%HOMEDRIVE%%HOMEPATH%\.multirust -> .appveyor.yml'
|
||||
|
||||
environment:
|
||||
# can't run game_of_life since termion doesn't support windows https://github.com/redox-os/termion/issues/103
|
||||
EXAMPLES: duration calculator console geometry membership text_transform turbo_blank json_builder
|
||||
VERBOSE: true
|
||||
RUST_BACKTRACE: 1
|
||||
|
|
|
@ -21,7 +21,7 @@ cache:
|
|||
|
||||
env:
|
||||
global:
|
||||
- EXAMPLES="duration calculator console geometry membership text_transform turbo_blank json_builder"
|
||||
- EXAMPLES="duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
|
||||
- VERBOSE=true
|
||||
- RUST_BACKTRACE=1
|
||||
- RUST_VERSION=stable
|
||||
|
|
|
@ -21,7 +21,7 @@ appveyor = { repository = "tildeio/helix", branch = "master", service = "github"
|
|||
|
||||
[workspace]
|
||||
|
||||
members = ["examples/calculator", "examples/console", "examples/geometry", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank", "examples/json_builder"]
|
||||
members = ["examples/calculator", "examples/console", "examples/game_of_life", "examples/geometry", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank", "examples/json_builder"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.0"
|
||||
|
|
4
Rakefile
4
Rakefile
|
@ -4,7 +4,7 @@ task :test do
|
|||
sh "bundle exec rake"
|
||||
end
|
||||
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console geometry membership text_transform turbo_blank json_builder"
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
|
||||
|
||||
sh "bash ./examples/runner default #{examples}"
|
||||
end
|
||||
|
@ -15,7 +15,7 @@ task :install do
|
|||
sh "bundle"
|
||||
end
|
||||
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console geometry membership text_transform turbo_blank json_builder"
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
|
||||
|
||||
sh "bash ./examples/runner install #{examples}"
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
extern crate libc;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const PKG_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
@ -14,6 +15,10 @@ pub fn check_version() {
|
|||
if PKG_VERSION != version {
|
||||
panic!("libcsys-ruby version ({}) doesn't match helix_runtime version ({}).", PKG_VERSION, version);
|
||||
}
|
||||
|
||||
if size_of::<usize>() != size_of::<u32>() && size_of::<usize>() != size_of::<u64>() {
|
||||
panic!("unsupported architecture, size_of::<usize>() = {}", size_of::<usize>());
|
||||
}
|
||||
}
|
||||
|
||||
pub type void = libc::c_void;
|
||||
|
@ -256,3 +261,39 @@ extern "C" {
|
|||
#[link_name = "HELIX_Data_Set_Struct_Value"]
|
||||
pub fn Data_Set_Struct_Value(obj: VALUE, data: *mut void);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn NUM2USIZE(v: VALUE) -> usize {
|
||||
if size_of::<usize>() == size_of::<u32>() {
|
||||
NUM2U32(v) as usize
|
||||
} else {
|
||||
NUM2U64(v) as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn USIZE2NUM(num: usize) -> VALUE {
|
||||
if size_of::<usize>() == size_of::<u32>() {
|
||||
U322NUM(num as u32)
|
||||
} else {
|
||||
U642NUM(num as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn NUM2ISIZE(v: VALUE) -> isize {
|
||||
if size_of::<isize>() == size_of::<i32>() {
|
||||
NUM2I32(v) as isize
|
||||
} else {
|
||||
NUM2I64(v) as isize
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn ISIZE2NUM(num: isize) -> VALUE {
|
||||
if size_of::<isize>() == size_of::<i32>() {
|
||||
I322NUM(num as i32)
|
||||
} else {
|
||||
I642NUM(num as i64)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "game_of_life"
|
||||
version = "0.1.0"
|
||||
authors = ["Godfrey Chan <godfrey@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
||||
|
||||
[dependencies]
|
||||
termion = "*"
|
||||
rand = "*"
|
|
@ -0,0 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rspec', '~> 3.4'
|
|
@ -0,0 +1,26 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require 'rspec/core/rake_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :demo => :build do
|
||||
$LOAD_PATH.unshift File.expand_path("./lib", __dir__)
|
||||
require 'game_of_life'
|
||||
GameOfLife.random.play!
|
||||
end
|
||||
|
||||
task :spec => :build
|
||||
task :default => :spec
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'game_of_life/native'
|
|
@ -0,0 +1,401 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe GameOfLife do
|
||||
describe '.parse' do
|
||||
context 'malformed inputs' do
|
||||
it 'should reject zero-sized boards' do
|
||||
expect(->() { GameOfLife.parse(0, 0, '') }).to raise_error(/cannot be 0/)
|
||||
expect(->() { GameOfLife.parse(0, 10, '') }).to raise_error(/cannot be 0/)
|
||||
expect(->() { GameOfLife.parse(10, 0, '') }).to raise_error(/cannot be 0/)
|
||||
end
|
||||
|
||||
it 'should reject lines that are too long' do
|
||||
expect(->() { GameOfLife.parse(2, 2, '...') }).to raise_error(/line 1 is too long/)
|
||||
end
|
||||
|
||||
it 'should reject too many lines' do
|
||||
expect(->() { GameOfLife.parse(2, 2, "\n\n\n") }).to raise_error(/too many lines/)
|
||||
end
|
||||
|
||||
it 'should parse correctly' do
|
||||
pattern = <<~GAME
|
||||
..*..**
|
||||
.......
|
||||
**..*..
|
||||
GAME
|
||||
|
||||
game = GameOfLife.parse(7, 3, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'game rules' do
|
||||
context 'a live cell' do
|
||||
context 'with zero live neighbors' do
|
||||
it 'should die, as if by under population' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
...
|
||||
.*.
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one live neighbor' do
|
||||
it 'should die, as if by under population' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
.*.
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with two live neighbors' do
|
||||
it 'should live on' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
.**
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with three live neighbors' do
|
||||
it 'should live on' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
***
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with four live neighbors' do
|
||||
it 'should die, as if by overpopulation' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
***
|
||||
.*.
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'a dead cell' do
|
||||
context 'with zero live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
...
|
||||
...
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one live neighbor' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
...
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with two live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
..*
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with three live neighbors' do
|
||||
it 'should become a live cell, as if by reproduction' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
*.*
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with four live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
*.*
|
||||
.*.
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'known patterns' do
|
||||
shared_examples 'still life' do
|
||||
it 'should stay still' do
|
||||
game = GameOfLife.parse(width, height, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
10.times { game.advance! }
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'oscillator' do |period:|
|
||||
it 'should oscillate' do
|
||||
game = GameOfLife.parse(width, height, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game.to_s).to_not eq(pattern)
|
||||
|
||||
(period - 1).times { game.advance! }
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
context 'Block' do
|
||||
let(:width) { 4 }
|
||||
let(:height) { 4 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
....
|
||||
.**.
|
||||
.**.
|
||||
....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Beehive' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
..**..
|
||||
.*..*.
|
||||
..**..
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Loaf' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
..**..
|
||||
.*..*.
|
||||
..*.*.
|
||||
...*..
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Boat' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
.**..
|
||||
.*.*.
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Tub' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
..*..
|
||||
.*.*.
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Blinker' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
..*..
|
||||
..*..
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Toad' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
......
|
||||
..***.
|
||||
.***..
|
||||
......
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Beacon' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
.**...
|
||||
.**...
|
||||
...**.
|
||||
...**.
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Pulsar' do
|
||||
let(:width) { 17 }
|
||||
let(:height) { 17 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.................
|
||||
.................
|
||||
....***...***....
|
||||
.................
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
....***...***....
|
||||
.................
|
||||
....***...***....
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
.................
|
||||
....***...***....
|
||||
.................
|
||||
.................
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 3
|
||||
end
|
||||
|
||||
context 'Pentadecathlon' do
|
||||
let(:width) { 11 }
|
||||
let(:height) { 18 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
....***....
|
||||
...*...*...
|
||||
...*...*...
|
||||
....***....
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
....***....
|
||||
...*...*...
|
||||
...*...*...
|
||||
....***....
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 15
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'game_of_life'
|
|
@ -0,0 +1,376 @@
|
|||
#[macro_use]
|
||||
extern crate helix;
|
||||
extern crate rand;
|
||||
extern crate termion;
|
||||
|
||||
ruby! {
|
||||
class GameOfLife {
|
||||
struct {
|
||||
width: usize,
|
||||
height: usize,
|
||||
cells: Vec<bool>,
|
||||
}
|
||||
|
||||
def random() -> GameOfLife {
|
||||
use termion::terminal_size;
|
||||
|
||||
let (width, height) = terminal_size().unwrap_or((100, 100));
|
||||
let mut game = GameOfLife::new(width as usize, height as usize);
|
||||
|
||||
game.randomize();
|
||||
|
||||
game
|
||||
}
|
||||
|
||||
def parse(width: usize, height: usize, input: String) -> GameOfLife {
|
||||
let mut game = GameOfLife::new(width, height);
|
||||
|
||||
for (y, line) in input.lines().enumerate() {
|
||||
assert!(y < height, "invalid input: too many lines (the game has a height of {})", height);
|
||||
|
||||
for (x, c) in line.chars().enumerate() {
|
||||
assert!(x < width, "invalid input: line {} is too long (the game has a width of {})", y+1, width);
|
||||
|
||||
if c == '*' {
|
||||
game.set(x, y, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game
|
||||
}
|
||||
|
||||
def initialize(helix, width: usize, height: usize) {
|
||||
assert!(width > 0, "width cannot be 0");
|
||||
assert!(height > 0, "height cannot be 0");
|
||||
|
||||
let mut cells = vec![];
|
||||
|
||||
cells.resize(width * height, false);
|
||||
|
||||
GameOfLife { helix, width, height, cells }
|
||||
}
|
||||
|
||||
#[ruby_name = "[]"]
|
||||
def get(&self, x: usize, y: usize) -> bool {
|
||||
self.cell_at(x, y).is_alive()
|
||||
}
|
||||
|
||||
#[ruby_name = "[]="]
|
||||
def set(&mut self, x: usize, y: usize, is_alive: bool) {
|
||||
self.mut_cell_at(x, y).set(is_alive)
|
||||
}
|
||||
|
||||
#[ruby_name = "advance!"]
|
||||
def advance(&mut self) {
|
||||
let mut next = self.cells.clone();
|
||||
|
||||
for cell in self.cells() {
|
||||
let index = cell.index();
|
||||
let is_alive = cell.is_alive();
|
||||
let live_neighbors = cell.live_neighbors();
|
||||
|
||||
// Any live cell with fewer than two live neighbors dies, as if
|
||||
// by under population.
|
||||
if is_alive && live_neighbors < 2 {
|
||||
next[index] = false;
|
||||
// Any live cell with more than three live neighbors dies, as
|
||||
// if by overpopulation.
|
||||
} else if is_alive && live_neighbors > 3 {
|
||||
next[index] = false;
|
||||
// Any dead cell with exactly three live neighbors becomes a
|
||||
// live cell, as if by reproduction.
|
||||
} else if !is_alive && live_neighbors == 3 {
|
||||
next[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.cells = next;
|
||||
}
|
||||
|
||||
#[ruby_name = "randomize!"]
|
||||
def randomize(&mut self) {
|
||||
use rand::prelude::random;
|
||||
|
||||
for cell in self.cells.iter_mut() {
|
||||
*cell = random();
|
||||
}
|
||||
}
|
||||
|
||||
#[ruby_name = "play!"]
|
||||
def play(&mut self) {
|
||||
use std::io::Write;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use termion::async_stdin;
|
||||
use termion::clear;
|
||||
use termion::color::*;
|
||||
use termion::cursor::{Goto, Hide, Show};
|
||||
use termion::event::Key::Char;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::terminal_size;
|
||||
|
||||
let mut stdin = async_stdin().keys();
|
||||
let mut stdout = std::io::stdout().into_raw_mode().unwrap();
|
||||
|
||||
let (terminal_width, terminal_height) = terminal_size().unwrap();
|
||||
|
||||
// ~30 FPS
|
||||
let timeout = Duration::from_millis(30);
|
||||
|
||||
print!("{}", clear::All);
|
||||
print!("{}", Hide);
|
||||
|
||||
loop {
|
||||
for cell in self.cells() {
|
||||
let (x, y) = cell.coordinates();
|
||||
|
||||
if x >= terminal_width as usize || y >= terminal_height as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
print!("{}", Goto(x as u16 + 1, y as u16 + 1));
|
||||
|
||||
if cell.is_alive() {
|
||||
print!("{}*{}", Fg(LightYellow), Fg(Reset));
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
|
||||
self.advance();
|
||||
|
||||
print!("{}", Goto(1, terminal_height));
|
||||
print!("{} Press {}r{} to randomize, {}q{} to quit. {}", Bg(Blue), Fg(LightWhite), Fg(Reset), Fg(LightWhite), Fg(Reset), Bg(Reset));
|
||||
|
||||
stdout.flush().unwrap();
|
||||
|
||||
match stdin.next() {
|
||||
Some(Ok(Char('r'))) => {
|
||||
self.randomize();
|
||||
},
|
||||
Some(Ok(Char('q'))) => {
|
||||
print!("{}", clear::All);
|
||||
print!("{}", Goto(1, 1));
|
||||
print!("{}", Show);
|
||||
|
||||
stdout.flush().unwrap();
|
||||
|
||||
return;
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
|
||||
sleep(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
def to_s(&self) -> String {
|
||||
let mut s = String::with_capacity(self.cells.len() + self.height);
|
||||
|
||||
for cell in self.cells() {
|
||||
if cell.is_alive() {
|
||||
s.push('*');
|
||||
} else {
|
||||
s.push('.');
|
||||
}
|
||||
|
||||
if cell.is_last_in_row() {
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These are hidden from Ruby
|
||||
|
||||
impl GameOfLife {
|
||||
fn index_for(&self, x: usize, y: usize) -> usize {
|
||||
assert!(x < self.width && y < self.height, "({}, {}) is out-of-bounds", x, y);
|
||||
y * self.width + x
|
||||
}
|
||||
|
||||
fn cell_at(&self, x: usize, y: usize) -> Cell {
|
||||
let index = self.index_for(x, y);
|
||||
Cell { game: self, index }
|
||||
}
|
||||
|
||||
fn mut_cell_at(&mut self, x: usize, y: usize) -> MutCell {
|
||||
let index = self.index_for(x, y);
|
||||
MutCell { game: self, index }
|
||||
}
|
||||
|
||||
fn cells(&self) -> Cells {
|
||||
Cells { next: Some(self.cell_at(0,0)), size: self.cells.len() }
|
||||
}
|
||||
}
|
||||
|
||||
struct Cells<'a> {
|
||||
next: Option<Cell<'a>>,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Iterator for Cells<'a> {
|
||||
type Item = Cell<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = match self.next {
|
||||
Some(ref cell) => cell.next(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
std::mem::replace(&mut self.next, next)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let remaining = match self.next {
|
||||
Some(ref cell) => self.size - cell.index(),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
(remaining, Some(remaining))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::ExactSizeIterator for Cells<'a> {}
|
||||
impl<'a> std::iter::FusedIterator for Cells<'a> {}
|
||||
|
||||
struct Cell<'a> {
|
||||
game: &'a GameOfLife,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
struct MutCell<'a> {
|
||||
game: &'a mut GameOfLife,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
enum Neighbor {
|
||||
TopLeft,
|
||||
Top,
|
||||
TopRight,
|
||||
Left,
|
||||
Right,
|
||||
BottomLeft,
|
||||
Bottom,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
trait Navigate {
|
||||
fn game(&self) -> &GameOfLife;
|
||||
fn index(&self) -> usize;
|
||||
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
(self.x(), self.y())
|
||||
}
|
||||
|
||||
fn x(&self) -> usize {
|
||||
self.index() % self.game().width
|
||||
}
|
||||
|
||||
fn y(&self) -> usize {
|
||||
self.index() / self.game().width
|
||||
}
|
||||
|
||||
fn is_alive(&self) -> bool {
|
||||
self.game().cells[self.index()]
|
||||
}
|
||||
|
||||
fn is_last_in_row(&self) -> bool {
|
||||
self.neighbor(Neighbor::Right).is_none()
|
||||
}
|
||||
|
||||
fn is_last(&self) -> bool {
|
||||
self.index() == self.game().cells.len() - 1
|
||||
}
|
||||
|
||||
fn live_neighbors(&self) -> usize {
|
||||
use Neighbor::*;
|
||||
|
||||
let neighbors = vec![
|
||||
self.neighbor(TopLeft),
|
||||
self.neighbor(Top),
|
||||
self.neighbor(TopRight),
|
||||
self.neighbor(Left),
|
||||
self.neighbor(Right),
|
||||
self.neighbor(BottomLeft),
|
||||
self.neighbor(Bottom),
|
||||
self.neighbor(BottomRight),
|
||||
];
|
||||
|
||||
neighbors.into_iter()
|
||||
.filter(Option::is_some)
|
||||
.map(Option::unwrap)
|
||||
.map(|cell| if cell.is_alive() { 1 } else { 0 })
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn neighbor(&self, position: Neighbor) -> Option<Cell> {
|
||||
use Neighbor::*;
|
||||
|
||||
let (x, y) = self.coordinates();
|
||||
let game = self.game();
|
||||
|
||||
let mut nx = x;
|
||||
let mut ny = y;
|
||||
|
||||
match position {
|
||||
TopLeft => { nx -= 1; ny -= 1; },
|
||||
Top => { ny -= 1; },
|
||||
TopRight => { nx += 1; ny -= 1; },
|
||||
Left => { nx -= 1; },
|
||||
Right => { nx += 1; },
|
||||
BottomLeft => { nx -= 1; ny += 1; },
|
||||
Bottom => { ny += 1; },
|
||||
BottomRight => { nx += 1; ny += 1; },
|
||||
}
|
||||
|
||||
if nx >= game.width || ny >= game.height {
|
||||
None
|
||||
} else {
|
||||
Some(Cell { game, index: game.index_for(nx, ny) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Navigate for Cell<'a> {
|
||||
fn game(&self) -> &GameOfLife {
|
||||
self.game
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Navigate for MutCell<'a> {
|
||||
fn game(&self) -> &GameOfLife {
|
||||
self.game
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Cell<'a> {
|
||||
// TODO: this should be in Navigate
|
||||
fn next(&self) -> Option<Cell<'a>> {
|
||||
if self.is_last() {
|
||||
None
|
||||
} else {
|
||||
Some(Cell { game: self.game, index: self.index + 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MutCell<'a> {
|
||||
fn set(&mut self, value: bool) {
|
||||
self.game.cells[self.index] = value
|
||||
}
|
||||
}
|
|
@ -1,6 +1,55 @@
|
|||
use std::mem::size_of;
|
||||
use sys::{self, VALUE, T_FIXNUM, T_BIGNUM};
|
||||
use super::{FromRuby, CheckResult, CheckedValue, ToRuby, ToRubyResult};
|
||||
|
||||
impl FromRuby for usize {
|
||||
type Checked = CheckedValue<usize>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<usize>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else if size_of::<usize>() == size_of::<u32>() {
|
||||
type_error!(value, "a 32-bit unsigned integer")
|
||||
} else {
|
||||
type_error!(value, "a 64-bit unsigned integer")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<usize>) -> usize {
|
||||
unsafe { sys::NUM2USIZE(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for usize {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::USIZE2NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRuby for isize {
|
||||
type Checked = CheckedValue<isize>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<isize>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else if size_of::<isize>() == size_of::<i32>() {
|
||||
type_error!(value, "a 32-bit signed integer")
|
||||
} else {
|
||||
type_error!(value, "a 64-bit signed integer")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<isize>) -> isize {
|
||||
unsafe { sys::NUM2ISIZE(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for isize {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::ISIZE2NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRuby for u64 {
|
||||
type Checked = CheckedValue<u64>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue