conway_deathmatch 0.0.0.1
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 +7 -0
- data/bin/conway_deathmatch +66 -0
- data/lib/conway_deathmatch/data/shapes.yaml +175 -0
- data/lib/conway_deathmatch/shapes.rb +35 -0
- data/lib/conway_deathmatch.rb +120 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4d28b8f025d9ac021d8d3b470733d5e7ceec6855
|
4
|
+
data.tar.gz: 1410214a78a7fa36ff54da8dcb30490600e90d8c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4136f08c1a086e3caaded1ce6973bba424ea183eab29b4054e399b1a16a5a1718783d96f53a2dd06a41a2af37dd112ae8485311ab49daf0cd39788d6b44ad4d5
|
7
|
+
data.tar.gz: bd31468226a5b4bb3d01fe3848f0f6569099bca4b1ac1b507b3ab9cb8be0ff0d10d07bda87bb969b7e0d62543b3fba0497ba886ec28901cf717c22406a7a5ef9
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# process cmdline options
|
4
|
+
#
|
5
|
+
require 'slop'
|
6
|
+
opts = Slop.parse(help: true,
|
7
|
+
banner: true,
|
8
|
+
strict: true,
|
9
|
+
optional_arguments: true) do
|
10
|
+
banner 'Usage: conway_deathmatch [options]'
|
11
|
+
|
12
|
+
on 'w', 'width=', '[int] Board width', as: Integer
|
13
|
+
on 'height=', '[int] Board height', as: Integer
|
14
|
+
on 'n', 'num_ticks=', '[int] Max number of ticks to generate', as: Integer
|
15
|
+
on 's', 'sleep=', '[flt] Sleep duration', as: Float
|
16
|
+
on 'p', 'points=', '[str] e.g. "acorn 50 18 p 1 2 p 3 4"', as: String
|
17
|
+
on 'S', 'step', 'Hold ticks for user input'
|
18
|
+
on 'silent', 'Only render the final state'
|
19
|
+
on 'm', 'multiplayer', 'Use multiplayer evaluation'
|
20
|
+
end
|
21
|
+
width = opts[:width] || 70
|
22
|
+
height = opts[:height] || 40
|
23
|
+
# assumes 1P, TODO: figure out MP
|
24
|
+
shapes = opts[:points] || "acorn 50 18"
|
25
|
+
slp = opts[:sleep] || 0.02
|
26
|
+
n = opts[:num_ticks]
|
27
|
+
render_continuous = (n.nil? or !opts.silent?)
|
28
|
+
|
29
|
+
|
30
|
+
# create game
|
31
|
+
#
|
32
|
+
require 'conway_deathmatch'
|
33
|
+
require 'conway_deathmatch/shapes'
|
34
|
+
|
35
|
+
include ConwayGame
|
36
|
+
b = BoardState.new(width, height)
|
37
|
+
Shapes.add(b, shapes) # assumes 1P
|
38
|
+
b.multiplayer = opts.multiplayer?
|
39
|
+
|
40
|
+
# play game
|
41
|
+
#
|
42
|
+
count = 0
|
43
|
+
while n.nil? or count <= n
|
44
|
+
if render_continuous
|
45
|
+
puts
|
46
|
+
puts count
|
47
|
+
puts b.render
|
48
|
+
end
|
49
|
+
|
50
|
+
b.tick
|
51
|
+
|
52
|
+
if opts.step?
|
53
|
+
gets
|
54
|
+
else
|
55
|
+
sleep slp if slp > 0.0
|
56
|
+
end
|
57
|
+
count += 1
|
58
|
+
end
|
59
|
+
|
60
|
+
# finish
|
61
|
+
#
|
62
|
+
if n and opts.silent?
|
63
|
+
puts
|
64
|
+
puts count
|
65
|
+
puts b.render
|
66
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
acorn:
|
2
|
+
- [0, 2]
|
3
|
+
- [1, 0]
|
4
|
+
- [1, 2]
|
5
|
+
- [3, 1]
|
6
|
+
- [4, 2]
|
7
|
+
- [5, 2]
|
8
|
+
- [6, 2]
|
9
|
+
|
10
|
+
beacon:
|
11
|
+
- [0, 0]
|
12
|
+
- [0, 1]
|
13
|
+
- [1, 0]
|
14
|
+
- [2, 3]
|
15
|
+
- [3, 2]
|
16
|
+
- [3, 3]
|
17
|
+
|
18
|
+
beehive:
|
19
|
+
- [0, 1]
|
20
|
+
- [1, 0]
|
21
|
+
- [1, 2]
|
22
|
+
- [2, 0]
|
23
|
+
- [2, 2]
|
24
|
+
- [3, 1]
|
25
|
+
|
26
|
+
blinker:
|
27
|
+
- [0, 1]
|
28
|
+
- [1, 1]
|
29
|
+
- [2, 1]
|
30
|
+
|
31
|
+
block:
|
32
|
+
- [0, 0]
|
33
|
+
- [0, 1]
|
34
|
+
- [1, 0]
|
35
|
+
- [1, 1]
|
36
|
+
|
37
|
+
block_engine_count:
|
38
|
+
- [0, 5]
|
39
|
+
- [2, 4]
|
40
|
+
- [2, 5]
|
41
|
+
- [4, 1]
|
42
|
+
- [4, 2]
|
43
|
+
- [4, 3]
|
44
|
+
- [6, 0]
|
45
|
+
- [6, 1]
|
46
|
+
- [6, 2]
|
47
|
+
- [7, 1]
|
48
|
+
|
49
|
+
block_engine_space:
|
50
|
+
- [0, 0]
|
51
|
+
- [0, 1]
|
52
|
+
- [1, 0]
|
53
|
+
- [1, 3]
|
54
|
+
- [2, 0]
|
55
|
+
- [2, 3]
|
56
|
+
- [2, 4]
|
57
|
+
- [3, 2]
|
58
|
+
- [4, 0]
|
59
|
+
- [4, 2]
|
60
|
+
- [4, 3]
|
61
|
+
- [4, 4]
|
62
|
+
|
63
|
+
block_engine_stripe:
|
64
|
+
- [0, 0]
|
65
|
+
- [1, 0]
|
66
|
+
- [2, 0]
|
67
|
+
- [3, 0]
|
68
|
+
- [4, 0]
|
69
|
+
- [5, 0]
|
70
|
+
- [6, 0]
|
71
|
+
- [7, 0]
|
72
|
+
- [9, 0]
|
73
|
+
- [10, 0]
|
74
|
+
- [11, 0]
|
75
|
+
- [12, 0]
|
76
|
+
- [13, 0]
|
77
|
+
- [17, 0]
|
78
|
+
- [18, 0]
|
79
|
+
- [19, 0]
|
80
|
+
- [26, 0]
|
81
|
+
- [27, 0]
|
82
|
+
- [28, 0]
|
83
|
+
- [29, 0]
|
84
|
+
- [30, 0]
|
85
|
+
- [31, 0]
|
86
|
+
- [32, 0]
|
87
|
+
- [34, 0]
|
88
|
+
- [35, 0]
|
89
|
+
- [36, 0]
|
90
|
+
- [37, 0]
|
91
|
+
- [38, 0]
|
92
|
+
|
93
|
+
boat:
|
94
|
+
- [0, 0]
|
95
|
+
- [0, 1]
|
96
|
+
- [1, 0]
|
97
|
+
- [1, 2]
|
98
|
+
- [2, 1]
|
99
|
+
|
100
|
+
die_hard:
|
101
|
+
- [0, 1]
|
102
|
+
- [1, 1]
|
103
|
+
- [1, 2]
|
104
|
+
- [5, 2]
|
105
|
+
- [6, 0]
|
106
|
+
- [6, 2]
|
107
|
+
- [7, 2]
|
108
|
+
|
109
|
+
glider:
|
110
|
+
- [0, 2]
|
111
|
+
- [1, 0]
|
112
|
+
- [1, 2]
|
113
|
+
- [2, 1]
|
114
|
+
- [2, 2]
|
115
|
+
|
116
|
+
loaf:
|
117
|
+
- [0, 1]
|
118
|
+
- [1, 0]
|
119
|
+
- [1, 2]
|
120
|
+
- [2, 0]
|
121
|
+
- [2, 3]
|
122
|
+
- [3, 1]
|
123
|
+
- [3, 2]
|
124
|
+
|
125
|
+
lwss:
|
126
|
+
- [0, 1]
|
127
|
+
- [0, 3]
|
128
|
+
- [1, 0]
|
129
|
+
- [2, 0]
|
130
|
+
- [3, 0]
|
131
|
+
- [3, 3]
|
132
|
+
- [4, 0]
|
133
|
+
- [4, 1]
|
134
|
+
- [4, 2]
|
135
|
+
|
136
|
+
rpent:
|
137
|
+
- [0, 1]
|
138
|
+
- [1, 0]
|
139
|
+
- [1, 1]
|
140
|
+
- [1, 2]
|
141
|
+
- [2, 0]
|
142
|
+
|
143
|
+
swastika:
|
144
|
+
- [0, 0]
|
145
|
+
- [0, 1]
|
146
|
+
- [0, 2]
|
147
|
+
- [0, 3]
|
148
|
+
- [0, 6]
|
149
|
+
- [1, 3]
|
150
|
+
- [1, 6]
|
151
|
+
- [2, 3]
|
152
|
+
- [2, 6]
|
153
|
+
- [3, 0]
|
154
|
+
- [3, 1]
|
155
|
+
- [3, 2]
|
156
|
+
- [3, 4]
|
157
|
+
- [3, 5]
|
158
|
+
- [3, 6]
|
159
|
+
- [4, 0]
|
160
|
+
- [4, 3]
|
161
|
+
- [5, 0]
|
162
|
+
- [5, 3]
|
163
|
+
- [6, 0]
|
164
|
+
- [6, 3]
|
165
|
+
- [6, 4]
|
166
|
+
- [6, 5]
|
167
|
+
- [6, 6]
|
168
|
+
|
169
|
+
toad:
|
170
|
+
- [0, 1]
|
171
|
+
- [0, 2]
|
172
|
+
- [1, 3]
|
173
|
+
- [2, 0]
|
174
|
+
- [3, 1]
|
175
|
+
- [3, 2]
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'conway_deathmatch'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module ConwayGame
|
5
|
+
module Shapes
|
6
|
+
@@known
|
7
|
+
|
8
|
+
# memoize data/shapes.yaml
|
9
|
+
def self.known
|
10
|
+
@@known ||= YAML.load_file(File.join(__dir__, 'data', 'shapes.yaml'))
|
11
|
+
end
|
12
|
+
|
13
|
+
# parse a string like "acorn 12 22 block 5 0 p 1 2 p 3 4 p 56 78"
|
14
|
+
# add known shapes
|
15
|
+
def self.add(board, str, val = BoardState::ALIVE)
|
16
|
+
tokens = str.split
|
17
|
+
points = []
|
18
|
+
known = self.known
|
19
|
+
|
20
|
+
while !tokens.empty?
|
21
|
+
shape = tokens.shift.downcase
|
22
|
+
raise "no coordinates for #{shape}" if tokens.length < 2
|
23
|
+
x = tokens.shift.to_i
|
24
|
+
y = tokens.shift.to_i
|
25
|
+
case shape.downcase
|
26
|
+
when 'p'
|
27
|
+
points << [x, y]
|
28
|
+
else
|
29
|
+
board.add_points(known.fetch(shape), x, y, val)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
board.add_points(points, 0, 0, val)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module ConwayGame; end # create namespace
|
2
|
+
|
3
|
+
# data structure for the board - 2d array
|
4
|
+
# implements standard and multiplayer evaluation
|
5
|
+
# static boundaries are treated as dead
|
6
|
+
#
|
7
|
+
class ConwayGame::BoardState
|
8
|
+
class BoundsError < RuntimeError; end
|
9
|
+
|
10
|
+
DEAD = '.'
|
11
|
+
ALIVE = '0'
|
12
|
+
|
13
|
+
def self.new_state(x_len, y_len)
|
14
|
+
state = []
|
15
|
+
x_len.times { state << Array.new(y_len, DEAD) }
|
16
|
+
state
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :multiplayer
|
20
|
+
|
21
|
+
def initialize(x_len, y_len)
|
22
|
+
# ranges, yay! (exclude_end)
|
23
|
+
@xr = (0...x_len)
|
24
|
+
@yr = (0...y_len)
|
25
|
+
@state = self.class.new_state(x_len, y_len)
|
26
|
+
@multiplayer = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Conway's Game of Life transition rules
|
30
|
+
def next_value(x, y)
|
31
|
+
n, birthright = neighbor_stats(x, y)
|
32
|
+
if alive?(x, y)
|
33
|
+
(n == 2 or n == 3) ? birthright : DEAD
|
34
|
+
else
|
35
|
+
(n == 3) ? birthright : DEAD
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def in_bounds?(x, y)
|
40
|
+
@xr.include?(x) and @yr.include?(y)
|
41
|
+
end
|
42
|
+
|
43
|
+
def in_bounds!(x, y)
|
44
|
+
raise(BoundsError, "(#{x}, #{y}) (#{@xr}, #{@yr})") unless in_bounds?(x, y)
|
45
|
+
end
|
46
|
+
|
47
|
+
# out of bounds considered dead
|
48
|
+
def alive?(x, y)
|
49
|
+
in_bounds?(x, y) and @state[x][y] != DEAD
|
50
|
+
end
|
51
|
+
|
52
|
+
# population of each neighbor
|
53
|
+
def neighbor_population(x, y)
|
54
|
+
neighbors = Hash.new(0)
|
55
|
+
(x-1..x+1).each { |xn|
|
56
|
+
(y-1..y+1).each { |yn|
|
57
|
+
if alive?(xn, yn) and !(xn == x and yn == y) # don't count self
|
58
|
+
neighbors[@state[xn][yn]] += 1
|
59
|
+
end
|
60
|
+
}
|
61
|
+
}
|
62
|
+
neighbors
|
63
|
+
end
|
64
|
+
|
65
|
+
# multiplayer, neighbor count and birthright
|
66
|
+
def neighbor_stats(x, y)
|
67
|
+
if @multiplayer
|
68
|
+
total = 0
|
69
|
+
largest = 0
|
70
|
+
birthright = nil
|
71
|
+
neighbor_population(x, y).each { |sym, cnt|
|
72
|
+
total += cnt
|
73
|
+
if cnt > largest
|
74
|
+
largest = cnt
|
75
|
+
birthright = sym
|
76
|
+
end
|
77
|
+
}
|
78
|
+
[total, birthright]
|
79
|
+
else
|
80
|
+
[neighbor_population(x, y).values.reduce(:+), ALIVE]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# generate the next state table
|
85
|
+
def tick
|
86
|
+
new_state = self.class.new_state(@xr.last, @yr.last)
|
87
|
+
@xr.each { |x| @yr.each { |y| new_state[x][y] = next_value(x, y) } }
|
88
|
+
@state = new_state
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# set a single point, raise on OOB
|
93
|
+
def populate(x, y, val = ALIVE)
|
94
|
+
in_bounds!(x, y)
|
95
|
+
@state[x][y] = val
|
96
|
+
end
|
97
|
+
|
98
|
+
# set several points (2d array), ignore OOB
|
99
|
+
def add_points(points, x_off = 0, y_off = 0, val = ALIVE)
|
100
|
+
points.each { |point|
|
101
|
+
x = point[0] + x_off
|
102
|
+
y = point[1] + y_off
|
103
|
+
@state[x][y] = val if self.in_bounds?(x, y)
|
104
|
+
}
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# for line-based text output, iterate over y-values first (i.e. per row)
|
109
|
+
def render_text
|
110
|
+
@state.transpose.map { |row| row.join }.join("\n")
|
111
|
+
end
|
112
|
+
alias_method :render, :render_text
|
113
|
+
|
114
|
+
# full board scan
|
115
|
+
def population
|
116
|
+
population = Hash.new(0)
|
117
|
+
@state.each { |col| col.each { |val| population[val] += 1 } }
|
118
|
+
population
|
119
|
+
end
|
120
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: conway_deathmatch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rick Hull
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Deathmatch
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- bin/conway_deathmatch
|
20
|
+
- lib/conway_deathmatch.rb
|
21
|
+
- lib/conway_deathmatch/data/shapes.yaml
|
22
|
+
- lib/conway_deathmatch/shapes.rb
|
23
|
+
homepage:
|
24
|
+
licenses:
|
25
|
+
- GPL
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 2.2.2
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: Conway's Game of Life
|
47
|
+
test_files: []
|