pitchcar 0.1.0
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/LICENSE.md +22 -0
- data/Readme.md +42 -0
- data/boyermoore.rb +117 -0
- data/finder.rb +50 -0
- data/piece.rb +66 -0
- data/pitchcar_tracks +20160 -0
- data/track.rb +99 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b54b3e0e398d6dee15a2c15379efa23ced9a327
|
4
|
+
data.tar.gz: 5b0c6ecfefc77fb815fd33b140a27f906f8922f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 45e2206b61b7a56e40784fe507d60a71759393a73a5204e16ed6bf86a2cb2be1241a1bf1cf9e7ff6653090b5aa91da2d18bf784d5b338af3e0703b297e8851e9
|
7
|
+
data.tar.gz: 6de830f9c750dd966cb53dd391747b579a33055e60d4279f6f84e8e69c72dff8d9b45cc720405e2d37211c19be5c11d1b3725744cd753337e1f02f6890e8c835
|
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Jonah Hirsch
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/Readme.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# PitchCar Track Generator
|
2
|
+
|
3
|
+
Generates all possible tracks, or a single random valid track, for
|
4
|
+
[PitchCar](https://boardgamegeek.com/boardgame/150/pitchcar)
|
5
|
+
|
6
|
+
## Rules:
|
7
|
+
* Tracks will always begin with a straight piece
|
8
|
+
* Tracks must not overlap
|
9
|
+
* Tracks must form a complete look
|
10
|
+
* Track must not already have been found as a rotation
|
11
|
+
|
12
|
+
All possible values for a basic PitchCar set (10 curves, 6 straight pieces)
|
13
|
+
are provided in `pitchcar_tracks`
|
14
|
+
* `Slw` = Straight piece with left wall
|
15
|
+
* `Srw` = Straight piece with right wall
|
16
|
+
* `R` = Right Turn
|
17
|
+
* `L` = Left Turn
|
18
|
+
|
19
|
+
## Usage:
|
20
|
+
### Generate all possible tracks
|
21
|
+
```ruby
|
22
|
+
require_relative 'finder.rb'
|
23
|
+
# tracks = Pitchcar::Finder.find_all_tracks(STRAIGHT_PIECES, LEFT_RIGHT_PIECES)
|
24
|
+
tracks = Pitchcar::Finder.find_all_tracks(6, 10)
|
25
|
+
tracks.first.to_s # => 'Slw Slw Slw Slw L Slw L Slw L R R L L R L L'
|
26
|
+
```
|
27
|
+
|
28
|
+
### Find a random valid track
|
29
|
+
```ruby
|
30
|
+
require_relative 'finder.rb'
|
31
|
+
# track = Pitchcar::Finder.random_valid_track(STRAIGHT_PIECES, LEFT_RIGHT_PIECES)
|
32
|
+
track = Pitchcar::Finder.random_valid_track(6, 10)
|
33
|
+
track.to_s # => 'Slw Slw L Slw R R Slw Slw R L R R L Slw R R'
|
34
|
+
```
|
35
|
+
|
36
|
+
### Find `n` random valid tracks
|
37
|
+
```ruby
|
38
|
+
require_relative 'finder.rb'
|
39
|
+
# tracks = Pitchcar::Finder.random_valid_tracks(STRAIGHT_PIECES, LEFT_RIGHT_PIECES, COUNT)
|
40
|
+
track = Pitchcar::Finder.random_valid_tracks(6, 10, 4)
|
41
|
+
track.map(&:to_s) # => ["Slw Srw L R Srw L L Srw R L L Slw R Slw L L", "Slw L Slw L Slw R Srw L L R L Srw L R L Slw", "Slw R Slw Slw R Slw Srw L R R Srw R L R L R", "Slw Slw L Srw L R L Srw R L L Srw L R Slw L"]
|
42
|
+
```
|
data/boyermoore.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# From https://github.com/jashmenn/boyermoore
|
2
|
+
|
3
|
+
# Hash impersonator that accepts regular expressions as keys. But the regular
|
4
|
+
# expression lookups are slow, so don't use them (unless you have to).
|
5
|
+
class RichHash
|
6
|
+
def initialize
|
7
|
+
@regexps = {}
|
8
|
+
@regular = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](k)
|
12
|
+
regular = @regular[k]
|
13
|
+
return regular if regular
|
14
|
+
if @regexps.size > 0
|
15
|
+
@regexps.each do |regex,v| # linear search is going to be slow
|
16
|
+
return v if regex.match(k)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(k,v)
|
23
|
+
if k.kind_of?(Regexp)
|
24
|
+
@regexps[k] = v
|
25
|
+
else
|
26
|
+
@regular[k] = v
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# ported directly from this version wikipedia:
|
32
|
+
# http://en.wikipedia.org/w/index.php?title=Boyer%E2%80%93Moore_string_search_algorithm&diff=391986850&oldid=391398281
|
33
|
+
# it's not very rubyish but it works
|
34
|
+
module BoyerMoore
|
35
|
+
|
36
|
+
def self.compute_prefix(str)
|
37
|
+
size = str.length
|
38
|
+
k = 0
|
39
|
+
result = [0]
|
40
|
+
1.upto(size - 1) do |q|
|
41
|
+
while (k > 0) && (str[k] != str[q])
|
42
|
+
k = result[k-1]
|
43
|
+
end
|
44
|
+
k += 1 if(str[k] == str[q])
|
45
|
+
result[q] = k
|
46
|
+
end
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.prepare_badcharacter_heuristic(str)
|
51
|
+
result = RichHash.new
|
52
|
+
0.upto(str.length - 1) do |i|
|
53
|
+
result[str[i]] = i
|
54
|
+
end
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.prepare_goodsuffix_heuristic(normal)
|
59
|
+
size = normal.size
|
60
|
+
result = []
|
61
|
+
|
62
|
+
reversed = normal.dup.reverse
|
63
|
+
prefix_normal = compute_prefix(normal)
|
64
|
+
prefix_reversed = compute_prefix(reversed)
|
65
|
+
|
66
|
+
0.upto(size) do |i|
|
67
|
+
result[i] = size - prefix_normal[size-1]
|
68
|
+
end
|
69
|
+
|
70
|
+
0.upto(size-1) do |i|
|
71
|
+
j = size - prefix_reversed[i]
|
72
|
+
k = i - prefix_reversed[i]+1
|
73
|
+
result[j] = k if result[j] > k
|
74
|
+
end
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.search(haystack, needle)
|
79
|
+
needle_len = needle.size
|
80
|
+
haystack_len = haystack.size
|
81
|
+
|
82
|
+
return nil if haystack_len == 0
|
83
|
+
return haystack if needle_len == 0
|
84
|
+
|
85
|
+
badcharacter = self.prepare_badcharacter_heuristic(needle)
|
86
|
+
goodsuffix = self.prepare_goodsuffix_heuristic(needle)
|
87
|
+
|
88
|
+
s = 0
|
89
|
+
while s <= haystack_len - needle_len
|
90
|
+
j = needle_len
|
91
|
+
while (j > 0) && self.needle_matches?(needle[j-1], haystack[s+j-1])
|
92
|
+
j -= 1
|
93
|
+
end
|
94
|
+
|
95
|
+
if(j > 0)
|
96
|
+
k = badcharacter[haystack[s+j-1]]
|
97
|
+
k = -1 unless k
|
98
|
+
if (k < j) && (m = j-k-1) > goodsuffix[j]
|
99
|
+
s += m
|
100
|
+
else
|
101
|
+
s += goodsuffix[j]
|
102
|
+
end
|
103
|
+
else
|
104
|
+
return s
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.needle_matches?(needle, haystack)
|
111
|
+
if needle.kind_of?(Regexp)
|
112
|
+
needle.match(haystack) ? true : false
|
113
|
+
else
|
114
|
+
needle == haystack
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/finder.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'piece'
|
2
|
+
require_relative 'track'
|
3
|
+
|
4
|
+
module Pitchcar
|
5
|
+
class Finder
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def find_all_tracks(straight, left_right)
|
9
|
+
tracks = find_tracks(straight - 1, left_right, 'S', [])
|
10
|
+
tracks.map(&:with_wall_combinations).flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
def random_valid_track(straight, left_right)
|
14
|
+
track = nil
|
15
|
+
track = random_track(straight, left_right) until !track.nil? && track.valid?
|
16
|
+
track.with_wall_combinations.sample
|
17
|
+
end
|
18
|
+
|
19
|
+
def random_valid_tracks(straight, left_right, count = 2)
|
20
|
+
tracks = []
|
21
|
+
count.times { tracks << random_valid_track(straight, left_right) }
|
22
|
+
tracks
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def find_tracks(straight, left_right, track_pieces, tracks)
|
28
|
+
print "Found #{tracks.size} tracks\r"
|
29
|
+
track = Track.build_from(track_pieces)
|
30
|
+
return false if track.overlaps?
|
31
|
+
|
32
|
+
return tracks << track if straight == 0 && left_right == 0 && track.valid?(tracks)
|
33
|
+
[track_pieces].product((['S'] * straight + ['L'] * left_right + ['R'] * left_right).uniq).each do |result|
|
34
|
+
if result[1] == 'S'
|
35
|
+
find_tracks(straight - 1, left_right, result.join, tracks)
|
36
|
+
else
|
37
|
+
find_tracks(straight, left_right - 1, result.join, tracks)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
tracks
|
41
|
+
end
|
42
|
+
|
43
|
+
def random_track(straight, left_right)
|
44
|
+
left = Random.rand(1..left_right)
|
45
|
+
right = left_right - left
|
46
|
+
Track.build_from("S#{'S' * (straight - 1)}#{'L' * left}#{'R' * right}".split('').shuffle.join)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/piece.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Pitchcar
|
4
|
+
class Piece
|
5
|
+
TYPES = { STRAIGHT: 0, LEFT: 1, RIGHT: 2, STRAIGHT_LEFT_WALL: 3, STRAIGHT_RIGHT_WALL: 4 }
|
6
|
+
DIRECTIONS = { NORTH: 0, EAST: 1, WEST: 2, SOUTH: 3 }
|
7
|
+
attr_accessor :direction, :x, :y, :type
|
8
|
+
|
9
|
+
def self.starting_piece
|
10
|
+
piece = new
|
11
|
+
piece.x = 0
|
12
|
+
piece.y = 0
|
13
|
+
piece.type = TYPES[:STRAIGHT]
|
14
|
+
piece.direction = DIRECTIONS[:SOUTH]
|
15
|
+
piece
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.type_from_string(string)
|
19
|
+
case string
|
20
|
+
when 'S'
|
21
|
+
TYPES[:STRAIGHT]
|
22
|
+
when 'L'
|
23
|
+
TYPES[:LEFT]
|
24
|
+
when 'R'
|
25
|
+
TYPES[:RIGHT]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_direction(from)
|
30
|
+
case type
|
31
|
+
when TYPES[:LEFT]
|
32
|
+
case from
|
33
|
+
when DIRECTIONS[:NORTH]
|
34
|
+
DIRECTIONS[:WEST]
|
35
|
+
when DIRECTIONS[:EAST]
|
36
|
+
DIRECTIONS[:NORTH]
|
37
|
+
when DIRECTIONS[:WEST]
|
38
|
+
DIRECTIONS[:SOUTH]
|
39
|
+
when DIRECTIONS[:SOUTH]
|
40
|
+
DIRECTIONS[:EAST]
|
41
|
+
end
|
42
|
+
when TYPES[:RIGHT]
|
43
|
+
case from
|
44
|
+
when DIRECTIONS[:NORTH]
|
45
|
+
DIRECTIONS[:EAST]
|
46
|
+
when DIRECTIONS[:EAST]
|
47
|
+
DIRECTIONS[:SOUTH]
|
48
|
+
when DIRECTIONS[:WEST]
|
49
|
+
DIRECTIONS[:NORTH]
|
50
|
+
when DIRECTIONS[:SOUTH]
|
51
|
+
DIRECTIONS[:WEST]
|
52
|
+
end
|
53
|
+
else
|
54
|
+
from
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
TYPES.key(type).to_s.split('_').map { |word| word[0] }.join.downcase.capitalize
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_h
|
63
|
+
{ x: x, y: y, type: TYPES.key(type).downcase, direction: DIRECTIONS.key(direction).downcase }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|