pitchcar 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|