conway_deathmatch 0.0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|