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 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: []