View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
22 LICENSE
@@ -0,0 +1,22 @@
Copyright (c) 2015 Kevin Burke.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
View
@@ -1,16 +1,30 @@
.PHONY: test serve install experiment
test:
go test ./...
# need to skip the wip directory
all: install test
install:
go get github.com/jmhodges/justrun
go install github.com/jmhodges/justrun
go get ./...
go install ./...
go install ./bits/... ./genetic/... ./geo/... ./image/... ./rle/... ./server/... ./td6/... ./tracks/...
test:
go test -race -timeout 1s \
./bits/... \
./genetic/... \
./geo/... \
./image/... \
./physics/... \
./rle/... \
./server/... \
./td6/... \
./tracks/...
serve:
find . -name '*.go' -o -name '*.html' | justrun -stdin -v=true -c 'go run server/main.go --template-directory="$(PWD)/server/templates" --static-directory="$(PWD)/server/static"'
experiment:
go run genetic/runners/main.go
experiment: install
run_experiment --package-root ~/code/go/src/github.com/kevinburke/rct
compress: install
get_old_experiments | bash scripts/compress.bash
View
@@ -0,0 +1,278 @@
package main
import (
"bufio"
"fmt"
"strings"
"github.com/kevinburke/rct/tracks"
)
func main() {
blah := ` { 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 64, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 32, 0, 0 },
{ 0, 0, 0, 32, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 64, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 32, 0, 0, 0 },
{ 0, 0, 32, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 3, 0, 0, -64, -64 },
{ 0, 1, 0, 0, -64, 64 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 0, -64, -64 },
{ 0, 1, 0, 0, -64, 64 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 64, -64, -64 },
{ 0, 1, 0, 64, -64, 64 },
{ 0, 3, 64, 0, -64, -64 },
{ 0, 1, 64, 0, -64, 64 },
{ 0, 0, 0, 0, -64, -32 },
{ 0, 0, 0, 0, -64, 32 },
{ 0, 0, 0, 0, -32, -32 },
{ 0, 0, 0, 0, -32, 32 },
{ 0, 3, 0, 0, -32, -32 },
{ 0, 1, 0, 0, -32, 32 },
{ 0, 3, 0, 0, -32, -32 },
{ 0, 1, 0, 0, -32, 32 },
{ 0, 3, 0, 32, -32, -32 },
{ 0, 1, 0, 32, -32, 32 },
{ 0, 3, 32, 0, -32, -32 },
{ 0, 1, 32, 0, -32, 32 },
{ 0, 3, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 16, -64, 0 },
{ 0, 0, 0, 16, -64, 0 },
{ 0, 0, 0, -16, -64, 0 },
{ 0, 0, 0, -16, -64, 0 },
{ 0, 2, 0, 152, -32, 0 },
{ 0, 2, 0, -152, 32, 0 },
{ 0, 3, 0, 80, -32, -32 },
{ 0, 1, 0, 80, -32, 32 },
{ 0, 3, 0, -80, -32, -32 },
{ 0, 1, 0, -80, -32, 32 },
{ 0, 0, 0, 24, 0, 0 },
{ 0, 0, 0, 24, 0, 0 },
{ 0, 0, 24, 0, 0, 0 },
{ 0, 0, 24, 0, 0, 0 },
{ 0, 0, 0, 96, 32, 0 },
{ 0, 0, 0, 32, 32, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 64, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 32, 0, 0 },
{ 0, 0, 0, 32, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 64, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 32, 0, 0, 0 },
{ 0, 0, 32, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 3, 0, 0, -64, -64 },
{ 0, 1, 0, 0, -64, 64 },
{ 0, 0, 0, 0, -64, -32 },
{ 0, 0, 0, 0, -64, 32 },
{ 0, 3, 0, 0, -32, -32 },
{ 0, 1, 0, 0, -32, 32 },
{ 0, 2, 0, 16, 0, -96 },
{ 0, 2, 0, 16, 0, 96 },
{ 0, 2, 16, 0, 0, -96 },
{ 0, 2, 16, 0, 0, 96 },
{ 0, 2, 0, 16, 0, -160 },
{ 0, 2, 0, 16, 0, 160 },
{ 0, 2, 16, 0, 0, -160 },
{ 0, 2, 16, 0, 0, 160 },
{ 0, 3, 0, 64, 0, 0 },
{ 0, 1, 0, 64, 0, 0 },
{ 0, 3, 64, 0, 0, 0 },
{ 0, 1, 64, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 16, -64, -64 },
{ 0, 1, 0, 16, -64, 64 },
{ 0, 3, 16, 0, -64, -64 },
{ 0, 1, 16, 0, -64, 64 },
{ 0, 3, 0, 16, -64, -64 },
{ 0, 1, 0, 16, -64, 64 },
{ 0, 3, 16, 0, -64, -64 },
{ 0, 1, 16, 0, -64, 64 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 16, 16, -128, 0 },
{ 0, 0, 0, 88, -96, 0 },
{ 0, 0, 0, 88, -96, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 88, 0, -96, 0 },
{ 0, 0, 88, 0, -96, 0 },
{ 0, 0, 0, -96, -96, 0 },
{ 0, 0, 0, 240, -160, 0 },
{ 0, 0, 0, 80, 32, 0 },
{ 0, 0, 0, 32, 32, 0 },
{ 0, 0, 32, 0, 32, 0 },
{ 0, 0, 0, 56, 32, 0 },
{ 0, 0, 56, 0, 0, 0 },
{ 0, 0, 0, 56, 0, 0 },
{ 0, 0, 56, 0, 32, 0 },
{ 0, 0, 24, 0, 0, 0 },
{ 0, 7, 0, 0, -64, -32 },
{ 0, 4, 0, 0, -64, 32 },
{ 4, 0, 0, 0, -64, 32 },
{ 4, 1, 0, 0, -32, 64 },
{ 0, 7, 0, 0, -64, -32 },
{ 0, 4, 0, 0, -64, 32 },
{ 4, 0, 0, 0, -64, 32 },
{ 4, 1, 0, 0, -32, 64 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 16, -32, 32 },
{ 4, 4, 0, 64, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 0, 32, -32, 32 },
{ 4, 4, 0, 32, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 16, 0, -32, 32 },
{ 4, 4, 64, 0, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 32, 0, -32, 32 },
{ 4, 4, 32, 0, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 0, 24, -32, 32 },
{ 4, 4, 0, 24, -32, 32 },
{ 4, 4, 24, 0, -32, 32 },
{ 4, 4, 24, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 0, 8, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 8, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 4, 4, 0, 0, -32, 32 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 32, -64, 0 },
{ 0, 0, 0, 32, -64, 0 },
{ 0, 0, 0, -32, -64, 0 },
{ 0, 0, 0, -32, -64, 0 },
{ 0, 3, 0, 24, -32, -32 },
{ 0, 1, 0, 24, -32, 32 },
{ 0, 3, 24, 0, -32, -32 },
{ 0, 1, 24, 0, -32, 32 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 2, 0, 280, -64, -32 },
{ 0, 2, 0, 280, -64, 32 },
{ 0, 2, 0, -280, 64, -32 },
{ 0, 2, 0, -280, 64, 32 },
{ 0, 0, 0, -16, -64, 0 },
{ 0, 0, 0, -16, -64, 0 },
{ 0, 0, 0, 16, -64, 0 },
{ 0, 0, 0, 16, -64, 0 },
{ 0, 2, 0, 120, -32, 0 },
{ 0, 2, 0, -120, 32, 0 },
{ 0, 3, 0, 48, -32, -32 },
{ 0, 1, 0, 48, -32, 32 },
{ 0, 3, 0, -48, -32, -32 },
{ 0, 1, 0, -48, -32, 32 },
{ 0, 2, 0, 32, 0, 0 },
{ 0, 2, 0, -32, 0, 0 },
{ 0, 0, 0, 0, -160, 0 },
{ 0, 0, 0, 0, -160, 0 },
{ 0, 0, 0, 0, -32, 0 },
{ 0, 0, 0, 0, -32, 0 },
{ 0, 0, 0, 0, -32, 0 },
{ 0, 1, 0, 0, -32, 32 },
{ 0, 3, 0, 0, -32, -32 },
{ 0, 2, 0, -96, -96, 0 },
{ 0, 2, 0, 128, 64, 0 },
{ 0, 2, 0, -128, -96, 0 },
{ 0, 3, 0, 16, -32, -32 },
{ 0, 1, 0, 16, -32, 32 },
{ 0, 0, 0, 0, -64, 0 },
{ 0, 0, 0, 0, -64, 0 },
{ 0, 0, 0, 0, -32, 0 },
{ 0, 0, 80, 0, 32, 0 },
{ 0, 0, 240, 0, -160, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 32, -32, -32 },
{ 0, 1, 0, 32, -32, 32 },
{ 0, 3, 32, 0, -32, -32 },
{ 0, 1, 32, 0, -32, 32 },
{ 0, 3, 0, 64, -64, -64 },
{ 0, 1, 0, 64, -64, 64 },
{ 0, 3, 64, 0, -64, -64 },
{ 0, 1, 64, 0, -64, 64 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 0, 16, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 16, 0, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 0, 8, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 0 },
{ 0, 3, 0, 96, 0, 32 },
{ 0, 1, 0, 96, 0, -32 },
{ 0, 3, 96, 0, 0, 32 },
{ 0, 1, 96, 0, 0, -32 },
{ 0, 2, 0, 96, 64, 0 },
{ 0, 2, 0, -128, -96, 0 },
{ 0, 2, 0, 128, 64, 0 }`
scanner := bufio.NewScanner(strings.NewReader(blah))
count := 0
for scanner.Scan() {
if count < len(tracks.ElementNames) {
fmt.Printf("%s\t// %s\n", scanner.Text(), tracks.ElementNames[count])
count++
} else {
fmt.Printf("%s\n", scanner.Text())
}
}
}
View
@@ -0,0 +1,72 @@
package main
import (
"os"
"github.com/kevinburke/rct/exe_reader"
"github.com/kevinburke/rct/tracks"
)
// taken from track_data.h
const RCT_DIRECTION_ADDR = 0x005968BB
const RCT_DIRECTION_WIDTH = 10
const RCT_BANK_SLOPE_ADDR = 0x00597c9d
const RCT_BANK_SLOPE_WIDTH = 8
// Follows the format in TrackCoordinates
/*
sint8 rotation_negative; // 0x00
sint8 rotation_positive; // 0x01
sint16 z_negative; // 0x02
sint16 z_positive; // 0x04
sint16 x; // 0x06
// my sideways delta
sint16 y; // 0x08
*/
const RCT_FORWARD_ADDR = 0x005968bb
const RCT_FORWARD_WIDTH = 0x0A
func main() {
f, err := os.Open(os.Getenv("HOME") + "/code/OpenRCT2/openrct2.exe")
if err != nil {
panic(err)
}
defer f.Close()
b := make([]byte, 256*RCT_DIRECTION_WIDTH)
f.ReadAt(b, int64(RCT_DIRECTION_ADDR))
c := make([]byte, 256*RCT_BANK_SLOPE_WIDTH)
f.ReadAt(c, RCT_BANK_SLOPE_ADDR)
d := make([]byte, 256*RCT_FORWARD_WIDTH)
f.ReadAt(d, RCT_FORWARD_ADDR)
for i := 0; i < len(tracks.ElementNames); i++ {
//fmt.Println(i)
//fmt.Printf("%55s ", tracks.ElementNames[i])
//fmt.Printf("%4d ", b[i*WIDTH])
//fmt.Printf("\n")
idx := i * RCT_DIRECTION_WIDTH
bitSubset := b[idx : idx+RCT_DIRECTION_WIDTH]
// xxx - there are 2 pieces to direction change, the first one is for
// diagonal to straight. we're ignoring diagonals for the moment.
//fmt.Printf("%s: %v\n", tracks.ElementNames[i], bitSubset[0])
//fmt.Printf("%s: %v\n", tracks.ElementNames[i], bitSubset[8])
bankIdx := i * RCT_BANK_SLOPE_WIDTH
bankBitSubset := c[bankIdx : bankIdx+RCT_BANK_SLOPE_WIDTH]
forwardIdx := i * RCT_FORWARD_WIDTH
forwardBitSubset := d[forwardIdx : forwardIdx+RCT_FORWARD_WIDTH]
exe_reader.PrintValues(i, tracks.ElementNames[i], int(bitSubset[1]), int(bitSubset[8]), int(bitSubset[2]), int(bitSubset[4]), bankBitSubset, forwardBitSubset)
}
//fmt.Printf("%#v\n", tracks.TS_MAP)
//fmt.Printf("%T\n", tracks.TS_MAP)
}
View
@@ -1,17 +1,24 @@
package main
package exe_reader
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"github.com/kevinburke/rct/bits"
"github.com/kevinburke/rct/tracks"
)
func hasBit(n int, pos uint) bool {
val := n & (1 << pos)
return (val > 0)
// XXX, this doesn't correctly handle s-bends, which only move sideways by 1
// piece, I think.
func SidewaysDelta(sidewaysDeltaByte int) int {
if sidewaysDeltaByte == 0 {
return 0
}
if bits.On(sidewaysDeltaByte, 7) {
return 1 + (256-sidewaysDeltaByte)>>5
}
return -(1 + sidewaysDeltaByte>>5)
}
var reverseMap = map[tracks.DirectionDelta]string{
@@ -27,7 +34,7 @@ var reverseMap = map[tracks.DirectionDelta]string{
tracks.DIR_DIAGONAL_RIGHT: "DIR_DIAGONAL_RIGHT",
}
func getDiagonalFromRCTStruct(b []byte) tracks.DirectionDelta {
func GetDiagonalFromRCTStruct(b []byte) tracks.DirectionDelta {
startingDirectionInt := int(b[0])
startingDirection := tracks.RCTDirectionKeys[startingDirectionInt]
endingDirectionInt := int(b[1])
@@ -48,19 +55,7 @@ func getDiagonalFromRCTStruct(b []byte) tracks.DirectionDelta {
}
}
// XXX, this doesn't correctly handle s-bends, which only move sideways by 1
// piece, I think.
func getSidewaysDelta(sidewaysDeltaByte int) int {
if hasBit(sidewaysDeltaByte, 7) {
return -(1 + (256-sidewaysDeltaByte)>>5)
}
if hasBit(sidewaysDeltaByte, 6) {
return 1 + sidewaysDeltaByte>>5
}
return 0
}
func getElevationDelta(positiveHeightBit int, negativeHeightBit int) int {
func ElevationDelta(positiveHeightBit int, negativeHeightBit int) int {
if positiveHeightBit > 0 {
return positiveHeightBit >> 3
}
@@ -70,28 +65,49 @@ func getElevationDelta(positiveHeightBit int, negativeHeightBit int) int {
return 0
}
var PRINT = true
func pt(format string, val ...interface{}) {
if PRINT {
fmt.Printf(format, val...)
}
}
func DirectionDelta(directionbit int) tracks.DirectionDelta {
if directionbit == 0 {
return tracks.DIR_STRAIGHT
} else if directionbit == 1 {
return tracks.DIR_90_DEG_RIGHT
} else if directionbit == 2 {
return tracks.DIR_180_DEG
} else if directionbit == 3 {
return tracks.DIR_90_DEG_LEFT
} else {
// XXX requires parsing more bits, see TrackCoordinates in track_data.h
return tracks.DIR_DIAGONAL
}
}
// Print out the Go code to make up a track segment
// XXX actually build the Go datatypes instead of printing/needing to copy
// paste
func printValues(typ int, elementName string, diagonalByte []byte, bankByte []byte, forwardByte []byte) {
func PrintValues(typ int, elementName string, direction int, sideways int, negativeHeightBit int, positiveHeightBit int, bankByte []byte, forwardByte []byte) {
dir := getDiagonalFromRCTStruct(diagonalByte)
sidewaysDelta := getSidewaysDelta(int(diagonalByte[8]))
negativeHeightBit := int(diagonalByte[2])
positiveHeightBit := int(diagonalByte[4])
elevationDelta := getElevationDelta(positiveHeightBit, negativeHeightBit)
dir := DirectionDelta(direction)
sidewaysDelta := SidewaysDelta(sideways)
elevationDelta := ElevationDelta(positiveHeightBit, negativeHeightBit)
fmt.Printf("%s: &Segment{\n", elementName)
pt("%s: &Segment{\n", elementName)
fmt.Printf("\tType: %#x,\n", typ)
pt("\tType: %#x,\n", typ)
bitVal := int(bankByte[2])
fmt.Printf("\tInputDegree: %s,\n", configTable1Map[2][bitVal])
pt("\tInputDegree: %s,\n", configTable1Map[2][bitVal])
bitVal = int(bankByte[1])
fmt.Printf("\tOutputDegree: %s,\n", configTable1Map[1][bitVal])
pt("\tOutputDegree: %s,\n", configTable1Map[1][bitVal])
bitVal = int(bankByte[4])
fmt.Printf("\tStartingBank: %s,\n", configTable1Map[4][bitVal])
pt("\tStartingBank: %s,\n", configTable1Map[4][bitVal])
bitVal = int(bankByte[3])
fmt.Printf("\tEndingBank: %s,\n", configTable1Map[3][bitVal])
pt("\tEndingBank: %s,\n", configTable1Map[3][bitVal])
var forward int16
buf := bytes.NewReader(forwardByte[6:8])
@@ -100,20 +116,14 @@ func printValues(typ int, elementName string, diagonalByte []byte, bankByte []by
panic(err)
}
fmt.Printf("\tForwardDelta: %d,\n", forward/-32+1)
pt("\tForwardDelta: %d,\n", forward/-32+1)
fmt.Printf("\tDirectionDelta: %s,\n", reverseMap[dir])
fmt.Printf("\tSidewaysDelta: %d,\n", sidewaysDelta)
fmt.Printf("\tElevationDelta: %d,\n", elevationDelta)
fmt.Printf("},\n")
pt("\tDirectionDelta: %s,\n", reverseMap[dir])
pt("\tSidewaysDelta: %d,\n", sidewaysDelta)
pt("\tElevationDelta: %d,\n", elevationDelta)
pt("},\n")
}
const RCT_DIRECTION_ADDR = 0x005972bb
const RCT_DIRECTION_WIDTH = 10
const RCT_BANK_SLOPE_ADDR = 0x00597c9d
const RCT_BANK_SLOPE_WIDTH = 8
// Follows the format in TrackCoordinates
/*
sint8 rotation_negative; // 0x00
@@ -123,9 +133,6 @@ const RCT_BANK_SLOPE_WIDTH = 8
sint16 x; // 0x06
sint16 y; // 0x08
*/
const RCT_FORWARD_ADDR = 0x005968bb
const RCT_FORWARD_WIDTH = 0x0A
var configTable1Map = map[int]map[int]string{
0: map[int]string{
0: "TRACK_FLAT",
@@ -193,41 +200,3 @@ var configTable1Map = map[int]map[int]string{
224: "TRACK_CORKSCREW_DOWN",
},
}
func main() {
f, err := os.Open(os.Getenv("HOME") + "/code/OpenRCT2/openrct2.exe")
if err != nil {
panic(err)
}
defer f.Close()
b := make([]byte, 256*RCT_DIRECTION_WIDTH)
f.ReadAt(b, int64(RCT_DIRECTION_ADDR))
c := make([]byte, 256*RCT_BANK_SLOPE_WIDTH)
f.ReadAt(c, RCT_BANK_SLOPE_ADDR)
d := make([]byte, 256*RCT_FORWARD_WIDTH)
f.ReadAt(d, RCT_FORWARD_ADDR)
for i := 0; i < len(tracks.ElementNames); i++ {
//fmt.Println(i)
//fmt.Printf("%55s ", tracks.ElementNames[i])
//fmt.Printf("%4d ", b[i*WIDTH])
//fmt.Printf("\n")
idx := i * RCT_DIRECTION_WIDTH
bitSubset := b[idx : idx+RCT_DIRECTION_WIDTH]
bankIdx := i * RCT_BANK_SLOPE_WIDTH
bankBitSubset := c[bankIdx : bankIdx+RCT_BANK_SLOPE_WIDTH]
forwardIdx := i * RCT_FORWARD_WIDTH
forwardBitSubset := d[forwardIdx : forwardIdx+RCT_FORWARD_WIDTH]
printValues(i, tracks.ElementNames[i], bitSubset, bankBitSubset, forwardBitSubset)
}
//fmt.Printf("%#v\n", tracks.TS_MAP)
//fmt.Printf("%T\n", tracks.TS_MAP)
}
View
@@ -0,0 +1,25 @@
package exe_reader
import "testing"
var sidewaysTests = []struct {
in int
expected int
}{
{0, 0},
{32, -2},
{64, -3},
{96, -4},
{160, 4},
{192, 3},
{224, 2},
}
func TestSidewaysDelta(t *testing.T) {
for _, tt := range sidewaysTests {
out := SidewaysDelta(tt.in)
if out != tt.expected {
t.Errorf("expected SidewaysDelta(%d) to be %d, was %d", tt.in, tt.expected, out)
}
}
}
View
@@ -0,0 +1,66 @@
package genetic
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"time"
)
// GetExperimentFiles returns a list of directories containing experiments
func GetExperimentFiles() (folders []string, err error) {
expDir := filepath.Join(*directory, "experiments")
finfo, err := ioutil.ReadDir(expDir)
if err != nil {
return folders, err
}
for _, dir := range finfo {
path := filepath.Join(expDir, dir.Name())
folders = append(folders, path)
}
return folders, nil
}
func GetOldFiles(d time.Duration) []string {
var oldFolders []string
files, err := GetExperimentFiles()
if err != nil {
panic(err)
}
now := time.Now().UTC()
for _, dir := range files {
metaPath := filepath.Join(dir, "meta.json")
em, err := ReadMetadata(metaPath)
if err != nil {
panic(err)
}
duration := now.Sub(em.Date)
if duration > d {
oldFolders = append(oldFolders, dir)
}
}
return oldFolders
}
// encode writes the given interface to the disk.
func encode(name string, v interface{}) error {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
return enc.Encode(v)
}
func ReadMetadata(filename string) (ExperimentMetadata, error) {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
var em ExperimentMetadata
js := json.NewDecoder(f)
err = js.Decode(&em)
return em, err
}
View
@@ -2,22 +2,30 @@
package genetic
import (
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"path"
"sort"
"strconv"
"strings"
"sync"
"time"
"code.google.com/p/go-uuid/uuid"
"github.com/kevinburke/rct/Godeps/_workspace/src/github.com/pborman/uuid"
"github.com/kevinburke/rct/tracks"
)
var directory *string
func init() {
directory = flag.String("directory", "/usr/local/rct", "Path to the folder storing RCT experiment data")
}
// constants which may be altered to affect the ride runtime
const PARENTS = 2
const FIT_PERCENTAGE = 0.2
@@ -26,28 +34,8 @@ const MUTATION_RATE = 0.05
// crossover with a probability of 0.6 (taken from the book & De Jong 1975)
const CROSSOVER_PROBABILITY = 0.6
const POOL_SIZE = 500
const ITERATIONS = 10
const PRINT_RESULTS_EVERY = 1
// create a directory and ignore "directory exists" errors
func mkdir(name string) error {
err := os.Mkdir(name, 0755)
// ugh
if err != nil && err.(*os.PathError).Err.Error() != "file exists" {
return err
}
return nil
}
func encode(name string, v interface{}) error {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
return enc.Encode(v)
}
const ITERATIONS = 1000
const PRINT_RESULTS_EVERY = 5
type ExperimentMetadata struct {
Hash string
@@ -60,26 +48,32 @@ type ExperimentMetadata struct {
MutationRate float32
}
func Run(outputDirectory string, packageRoot string) error {
expDir := path.Join(outputDirectory, "experiments")
err := mkdir(expDir)
func Run(packageRoot string) error {
if directory == nil {
return fmt.Errorf("invalid directory - need to specify it")
}
expDir := path.Join(*directory, "experiments")
err := os.MkdirAll(expDir, 0755)
if err != nil {
return err
}
id := fmt.Sprintf("exp_%s", uuid.New())
expIdDir := path.Join(expDir, id)
err = mkdir(expIdDir)
err = os.MkdirAll(expIdDir, 0755)
if err != nil {
return err
}
iterationsDir := path.Join(expIdDir, "iterations")
err = mkdir(iterationsDir)
err = os.MkdirAll(iterationsDir, 0755)
if err != nil {
return err
}
cmd := exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = packageRoot
hashb, err := cmd.Output()
if err != nil {
return err
}
mtd := ExperimentMetadata{
Hash: strings.TrimSpace(string(hashb)),
Date: time.Now().UTC(),
@@ -102,11 +96,11 @@ func Run(outputDirectory string, packageRoot string) error {
pool.Mutate(MUTATION_RATE)
pool.Evaluate()
iterationDir := path.Join(iterationsDir, strconv.Itoa(i))
err = mkdir(iterationDir)
err = os.MkdirAll(iterationDir, 0755)
if err != nil {
return err
}
pool.Statistics(i, outputDirectory)
pool.Statistics(i, *directory)
}
return nil
}
@@ -120,35 +114,74 @@ type Member struct {
Id string
Score int64
// Advantage or disadvantage in reproducing
Fitness float64
Runtime time.Duration
Track []tracks.Element
Fitness float64
Runtime time.Duration
Track []tracks.Element
ScoreData scoreData
}
type scoresArray [500]int64
func (a *scoresArray) Len() int {
return len(a)
}
func (a *scoresArray) Swap(i int, j int) {
a[i], a[j] = a[j], a[i]
}
func (a *scoresArray) Less(i, j int) bool {
return a[i] < a[j]
}
func (p *Pool) Statistics(iteration int, outputDirectory string) {
var scores scoresArray
var highestScore int64 = -1
var worstScore int64 = 1000 * 1000 * 1000
bestMember := new(Member)
worstMember := new(Member)
for i := 0; i < len(p.Members); i++ {
scores[i] = p.Members[i].Score
if p.Members[i].Score > highestScore {
highestScore = p.Members[i].Score
bestMember = p.Members[i]
}
if p.Members[i].Score < worstScore {
worstScore = p.Members[i].Score
worstMember = p.Members[i]
}
}
if iteration%PRINT_RESULTS_EVERY == 0 {
var highestScore int64 = -1
var worstScore int64 = 7000
bestMember := new(Member)
for i := 0; i < len(p.Members); i++ {
if p.Members[i].Score > highestScore {
highestScore = p.Members[i].Score
bestMember = p.Members[i]
}
if p.Members[i].Score < worstScore {
worstScore = p.Members[i].Score
middle := len(scores) / 2
median := (scores[middle] + scores[middle-1]) / 2
sort.Sort(&scores)
bestScorer := fmt.Sprintf("\t(length: %d, collisions: %d, to completion: %d, negative speed points: %d)\n\n",
bestMember.ScoreData.Length, bestMember.ScoreData.Collisions,
bestMember.ScoreData.Distance, bestMember.ScoreData.NegativeSpeed)
fmt.Printf("Iteration %d: %d members, best member %s has score %d, "+
"median %d, worst has score %d\n%s",
iteration, len(p.Members), bestMember.Id, bestMember.Score, median,
worstScore, bestScorer)
if os.Getenv("DEBUG_BEST_TRACK") == "true" {
if iteration%20 == 0 && iteration > 0 {
for _, elem := range bestMember.Track {
fmt.Printf("%s %t\n", elem.Segment.String(), elem.ChainLift)
}
fmt.Println("==================")
}
}
fmt.Printf("Iteration %d: %d members, best member has score %d, worst has score %d\n", iteration, len(p.Members), bestMember.Score, worstScore)
}
// XXX, move offline to a goroutine
for i := 0; i < len(p.Members); i++ {
pth := path.Join(outputDirectory, "experiments", p.Id,
"iterations", strconv.Itoa(iteration), fmt.Sprintf("%s.json", p.Members[i].Id))
err := encode(pth, p.Members[i])
if err != nil {
log.Print(err)
}
pth := path.Join(outputDirectory, "experiments", p.Id,
"iterations", strconv.Itoa(iteration), fmt.Sprintf("%s.json", bestMember.Id))
err := encode(pth, bestMember)
if err != nil {
log.Print(err)
}
pth = path.Join(outputDirectory, "experiments", p.Id,
"iterations", strconv.Itoa(iteration), fmt.Sprintf("%s.json", worstMember.Id))
err = encode(pth, worstMember)
if err != nil {
log.Print(err)
}
}
@@ -161,17 +194,23 @@ func SeedPool(size int) *Pool {
members := make([]*Member, POOL_SIZE)
for i := 0; i < POOL_SIZE; i++ {
track := CreateStation()
idx := STATION_LENGTH - 1
for j := 0; j < INITIAL_TRACK_LENGTH-STATION_LENGTH; j++ {
poss := track[idx].Possibilities()
for j := STATION_LENGTH - 1; j < INITIAL_TRACK_LENGTH-STATION_LENGTH; j++ {
poss := track[j].Possibilities()
track = append(track, poss[rand.Intn(len(poss))])
}
score := GetScore(track)
score, d := GetScore(track)
members[i] = &Member{
Id: fmt.Sprintf("iter_%s", uuid.New()),
Track: track,
Score: score,
Id: fmt.Sprintf("iter_%s", uuid.New()),
Track: track,
Score: score,
ScoreData: d,
}
//if i%100 == 0 {
//for _, elem := range track {
//fmt.Println(elem.Segment.String())
//}
//fmt.Printf("========\n")
//}
}
return &Pool{Members: members}
}
@@ -188,13 +227,18 @@ func (p *Pool) Mutate(rate float64) {
// Assign scores for every member of the pool
func (p *Pool) Evaluate() {
var wg sync.WaitGroup
for i := 0; i < POOL_SIZE; i++ {
p.Members[i].Score = GetScore(p.Members[i].Track)
wg.Add(1)
go func(i int, track []tracks.Element) {
p.Members[i].Score, p.Members[i].ScoreData = GetScore(track)
wg.Done()
}(i, p.Members[i].Track)
}
wg.Wait()
// Assign fitness for every member. For now, every member gets a fitness of
// 1. In the future, consider sorting/giving higher score members a better
// chance of reproducing.
// Assign fitness for every member. In the future, consider a smarter
// algorithm higher score members a better chance of reproducing.
for i := 0; i < POOL_SIZE; i++ {
p.Members[i].Fitness = float64(p.Members[i].Score)
}
@@ -208,7 +252,7 @@ func (p *Pool) Select() (int, *Member) {
var weightedTotal float64 = 0
totals := make([]float64, len(p.Members))
for i := 0; i < len(p.Members); i++ {
weightedTotal += p.Members[i].Fitness
weightedTotal += max(p.Members[i].Fitness, 0)
totals[i] = weightedTotal
}
rnd := rand.Float64() * weightedTotal
@@ -227,14 +271,24 @@ func min(a int, b int) int {
return b
}
func crossoverOne(parent1 *Member, parent2 *Member) (*Member, *Member) {
// choose a random point between the beginning and the end
minval := min(len(parent1.Track), len(parent2.Track))
crossPoint1 := rand.Intn(minval)
crossPoint2 := crossPoint1
func max(a float64, b float64) float64 {
if a > b {
return a
}
return b
}
// crossPoint1 = splice point in parent1.
func crossoverAtPoint(parent1 *Member, parent2 *Member, crossPoint1 int) (*Member, *Member) {
// crossPoint2 = splice point in parent2.
crossPoint2 := crossPoint1 + 1
foundMatch := false
for {
// say cross point 1 = 3, track length = 6
// parent 1 == 0-3, parent 2 = 4-5
if tracks.Compatible(parent1.Track[crossPoint1], parent2.Track[crossPoint2]) {
// Increment so we splice *after* the cross point in parent 1
crossPoint1++
foundMatch = true
break
}
@@ -244,6 +298,8 @@ func crossoverOne(parent1 *Member, parent2 *Member) (*Member, *Member) {
}
if tracks.Compatible(parent1.Track[crossPoint1], parent2.Track[crossPoint2]) {
foundMatch = true
// Increment so we splice *after* the cross point in parent 1
crossPoint1++
break
}
crossPoint2++
@@ -253,11 +309,40 @@ func crossoverOne(parent1 *Member, parent2 *Member) (*Member, *Member) {
}
// swap the track pieces at the chosen point on track A and track B
if foundMatch {
return Swap(parent1, parent2, crossPoint1, crossPoint2)
c1, c2 := Swap(parent1, parent2, crossPoint1, crossPoint2)
if os.Getenv("DEBUG_SWAPS") == "true" && crossPoint1 > 4 {
fmt.Println("swapped at", crossPoint1, crossPoint2)
fmt.Println("parent1")
printTrack(parent1.Track)
fmt.Println("parent2")
printTrack(parent2.Track)
fmt.Println("child1")
printTrack(c1.Track)
fmt.Println("child2")
printTrack(c2.Track)
}
return c1, c2
}
return parent1, parent2
}
// crossoverOne crosses over the parent tracks. if point is -1, a random point
// is chosen. (point is used for testing)
func crossoverOne(parent1 *Member, parent2 *Member, point int) (*Member, *Member) {
// choose a random point between the beginning and the end
if point == -1 {
minval := min(len(parent1.Track)-1, len(parent2.Track)-1)
point = rand.Intn(minval)
}
return crossoverAtPoint(parent1, parent2, point)
}
func printTrack(t []tracks.Element) {
for i, elem := range t {
fmt.Printf("%d %s\n", i, elem.Segment.String())
}
}
// Crossover chooses two members of a pool and joins them at random.
func (p *Pool) Crossover() *Pool {
halfLen := len(p.Members) / 2
@@ -268,9 +353,13 @@ func (p *Pool) Crossover() *Pool {
if idx1 == -1 || idx2 == -1 {
continue
}
if idx1 == idx2 {
// No point in crossing over with ourself
continue
}
if rand.Float64() < CROSSOVER_PROBABILITY {
// XXX delete parents
child1, child2 := crossoverOne(parent1, parent2)
child1, child2 := crossoverOne(parent1, parent2, -1)
p.Members[idx1] = child1
p.Members[idx2] = child2
}
@@ -282,15 +371,20 @@ func (p *Pool) Crossover() *Pool {
// the given cross points. The sum of the two track lengths may be the same,
// but the tracks themselves will change.
func Swap(parent1 *Member, parent2 *Member, crossPoint1 int, crossPoint2 int) (*Member, *Member) {
child1len := crossPoint1 + (len(parent2.Track) - crossPoint1)
child2len := crossPoint2 + (len(parent1.Track) - crossPoint2)
// XXX, probably something fancy you can do with xor, or a temporary array.
child1len := crossPoint1 + (len(parent2.Track) - crossPoint2)
child2len := crossPoint2 + (len(parent1.Track) - crossPoint1)
child1track := make([]tracks.Element, child1len)
child2track := make([]tracks.Element, child2len)
copy(child1track[:crossPoint1], parent1.Track[:crossPoint1])
copy(child1track[crossPoint1:], parent2.Track[crossPoint1:])
// if we filled in to child1 from [crossPoint2:] we might have gaps in the
// track. fill from the end of cross point 1, with the contents of cross
// point 2.
copy(child1track[crossPoint1:], parent2.Track[crossPoint2:])
copy(child2track[:crossPoint2], parent2.Track[:crossPoint2])
copy(child2track[crossPoint2:], parent1.Track[crossPoint2:])
copy(child2track[crossPoint2:], parent1.Track[crossPoint1:])
child1 := &Member{
Id: fmt.Sprintf("iter_%s", uuid.New()),
Track: child1track,
View
@@ -0,0 +1,102 @@
package genetic
import (
"testing"
"github.com/kevinburke/rct/tracks"
)
func TestSwap(t *testing.T) {
a := &Member{
Track: []tracks.Element{
{Segment: tracks.TS_MAP[tracks.ELEM_FLAT]},
{Segment: tracks.TS_MAP[tracks.ELEM_25_DEG_UP]},
},
}
b := &Member{
Track: []tracks.Element{
{Segment: tracks.TS_MAP[tracks.ELEM_25_DEG_DOWN]},
{Segment: tracks.TS_MAP[tracks.ELEM_60_DEG_DOWN]},
},
}
c, d := Swap(a, b, 1, 1)
cexpected := []tracks.Element{
{Segment: tracks.TS_MAP[tracks.ELEM_FLAT]},
{Segment: tracks.TS_MAP[tracks.ELEM_60_DEG_DOWN]},
}
if c.Track[0] != cexpected[0] || c.Track[1] != cexpected[1] {
t.Errorf("expected c to be %v, was %v", cexpected, c.Track)
}
dexpected := []tracks.Element{
{Segment: tracks.TS_MAP[tracks.ELEM_25_DEG_DOWN]},
{Segment: tracks.TS_MAP[tracks.ELEM_25_DEG_UP]},
}
if d.Track[0] != dexpected[0] || d.Track[1] != dexpected[1] {
t.Errorf("expected d to be %v, was %v", dexpected, d.Track)
}
}
func elem(s tracks.SegmentType) tracks.Element {
return tracks.Element{
Segment: tracks.TS_MAP[s],
}
}
func TestCrossover(t *testing.T) {
parent1 := &Member{
Track: []tracks.Element{
elem(tracks.ELEM_BEGIN_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_END_STATION),
elem(tracks.ELEM_FLAT_TO_RIGHT_BANK),
elem(tracks.ELEM_RIGHT_QUARTER_TURN_3_TILES_BANK),
elem(tracks.ELEM_RIGHT_QUARTER_TURN_3_TILES_BANK),
elem(tracks.ELEM_RIGHT_QUARTER_BANKED_HELIX_LARGE_DOWN),
elem(tracks.ELEM_RIGHT_BANK_TO_25_DEG_DOWN),
elem(tracks.ELEM_LEFT_QUARTER_TURN_3_TILES_25_DEG_DOWN),
elem(tracks.ELEM_25_DEG_DOWN_TO_LEFT_BANK),
elem(tracks.ELEM_LEFT_BANK_TO_25_DEG_UP),
elem(tracks.ELEM_25_DEG_UP_TO_FLAT),
elem(tracks.ELEM_FLAT_TO_25_DEG_UP),
elem(tracks.ELEM_LEFT_QUARTER_TURN_3_TILES_25_DEG_UP),
},
}
parent2 := &Member{
Track: []tracks.Element{
elem(tracks.ELEM_BEGIN_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_MIDDLE_STATION),
elem(tracks.ELEM_END_STATION),
elem(tracks.ELEM_S_BEND_RIGHT),
elem(tracks.ELEM_LEFT_QUARTER_TURN_5_TILES),
elem(tracks.ELEM_LEFT_QUARTER_TURN_3_TILES),
elem(tracks.ELEM_FLAT_TO_RIGHT_BANK),
elem(tracks.ELEM_RIGHT_BANK_TO_25_DEG_UP),
elem(tracks.ELEM_25_DEG_UP_TO_LEFT_BANK),
elem(tracks.ELEM_LEFT_BANK),
elem(tracks.ELEM_LEFT_HALF_BANKED_HELIX_UP_SMALL),
elem(tracks.ELEM_LEFT_HALF_BANKED_HELIX_DOWN_SMALL),
elem(tracks.ELEM_LEFT_HALF_BANKED_HELIX_DOWN_LARGE),
elem(tracks.ELEM_LEFT_HALF_BANKED_HELIX_UP_SMALL),
},
}
checkCompatible := func(track []tracks.Element) {
for i, elem := range track[:len(track)-1] {
if tracks.Compatible(elem, track[i+1]) == false {
t.Errorf("Crossover generated incompatible segments: %s, %s", elem.Segment.String(), track[i+1].Segment.String())
}
}
}
child1, child2 := crossoverOne(parent1, parent2, 11)
checkCompatible(child1.Track)
checkCompatible(child2.Track)
// Reverse and try again.
child1, child2 = crossoverOne(parent2, parent1, 10)
checkCompatible(child1.Track)
checkCompatible(child2.Track)
}
Oops, something went wrong.