<commit_before>package sudoku

import (
	"fmt"
	"log"
	"strconv"
)

type forcingChainsTechnique struct {
	*basicSolveTechnique
}

func (self *forcingChainsTechnique) HumanLikelihood() float64 {
	//TODO: figure out what the baseDifficulty should be
	return self.difficultyHelper(200.0)
}

func (self *forcingChainsTechnique) Description(step *SolveStep) string {
	//TODO: implement this
	return "ERROR: NOT IMPLEMENTED"
}

func (self *forcingChainsTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {
	//TODO: test that this will find multiple if they exist.
	//TODO: Implement this.

	getter := grid.queue().DefaultGetter()

	_MAX_IMPLICATION_STEPS := 6

	for {

		//Check if it's time to stop.
		select {
		case <-done:
			return
		default:
		}

		candidate := getter.GetSmallerThan(3)

		if candidate == nil {
			break
		}

		candidateCell := candidate.(*Cell)

		if len(candidateCell.Possibilities()) != 2 {
			//We found one with 1 possibility, which isn't interesting for us--nakedSingle should do that one.
			continue
		}

		firstPossibilityNum := candidateCell.Possibilities()[0]
		secondPossibilityNum := candidateCell.Possibilities()[1]

		firstGrid := grid.Copy()
		secondGrid := grid.Copy()

		//Check that the neighbor isn't just already having a single possibility, because then this technique is overkill.

		firstAccumulator := makeChainSeacherAccumulator(_MAX_IMPLICATION_STEPS)
		secondAccumulator := makeChainSeacherAccumulator(_MAX_IMPLICATION_STEPS)

		chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(firstGrid),
			firstPossibilityNum,
			firstAccumulator)

		chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(secondGrid),
			secondPossibilityNum,
			secondAccumulator)

		//TODO:Check if the sets overlap.

		doPrint := candidateCell.Row() == 1 && candidateCell.Col() == 0

		//For these debugging purposes, only print out the candidateCell we know to be interesting in the test case.
		if doPrint {
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		//See if either branch, at some generation, has the same cell forced to the same number in either generation.

		//accumulate forward, so the last generation has ALL cells affected in any generation on that branch
		firstAccumulator.accumulateGenerations()
		secondAccumulator.accumulateGenerations()

		if doPrint {
			log.Println("Accumulators after accumulating generations:")
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		foundOne := false

		for generation := _MAX_IMPLICATION_STEPS - 1; generation >= 0 && !foundOne; generation-- {

			//Check for any overlap at the last generation
			firstAffectedCells := firstAccumulator[generation]
			secondAffectedCells := secondAccumulator[generation]

			for key, val := range firstAffectedCells {

				//Skip the candidateCell, because that's not a meaningful overlap--we set that one as a way of branching!
				if key == candidateCell.ref() {
					continue
				}

				if num, ok := secondAffectedCells[key]; ok {
					//Found cell overlap! ... is the forced number the same?
					if val == num {
						//Yup, seems like we've found a cell that is forced to the same value on either branch.
						step := &SolveStep{self,
							CellSlice{key.Cell(grid)},
							IntSlice{val},
							CellSlice{candidateCell},
							candidateCell.Possibilities(),
						}

						if doPrint {
							log.Println(step)
							log.Println("Candidate Cell", candidateCell.ref())
						}

						if step.IsUseful(grid) {
							foundOne = true
							if doPrint {
								log.Println("Found solution on generation: ", generation)
							}
							select {
							case results <- step:
							case <-done:
								return
							}
						}
					}
				}
			}
		}

		//TODO: figure out why the tests are coming back with different answers, even when only looking at the key cell
		//that should work from the example.
		//TODO: we should prefer solutions where the total implications on both branches are minimized.
		//For example, if only one implication is requried on left, but 4 are on right, that's preferable to one where
		//three implications are required on both sides.
		//TODO: figure out a way to only compute a generation if required on each branch (don't compute all the way to _MAX_IMPLICATIONS to start)

	}
}

type chainSearcherGenerationDetails map[cellRef]int

func (c chainSearcherGenerationDetails) String() string {
	result := "Begin map\n"
	for cell, num := range c {
		result += "\t" + cell.String() + " : " + strconv.Itoa(num) + "\n"
	}
	result += "End map\n"
	return result
}

type chainSearcherAccumulator []chainSearcherGenerationDetails

func (c chainSearcherAccumulator) String() string {
	result := "Accumulator[\n"
	for _, rec := range c {
		result += fmt.Sprintf("%s\n", rec)
	}
	result += "]\n"
	return result
}

//accumulateGenerations goes through each generation (youngest to newest)
//and squaches older generation maps into each generation, so each
//generation's map represents the totality of all cells seen at that point.
func (c chainSearcherAccumulator) accumulateGenerations() {
	for i := len(c) - 2; i >= 0; i-- {
		lastGeneration := c[i+1]
		currentGeneration := c[i]
		for key, val := range lastGeneration {
			if currentVal, ok := currentGeneration[key]; ok {
				if currentVal != val {
					//No, this should be expected to happen when we get to an invalid grid state,
					//which we should expect to happen down one of the two branches (at least as explore
					//far enough.)
					log.Println("We were about to overwrite a value from an earlier generation... this shouldn't happen.")
				}
			}
			currentGeneration[key] = val
		}
	}
}

func makeChainSeacherAccumulator(size int) chainSearcherAccumulator {
	result := make(chainSearcherAccumulator, size)
	for i := 0; i < size; i++ {
		result[i] = make(map[cellRef]int)
	}
	return result
}

func chainSearcher(i int, cell *Cell, numToApply int, accumulator chainSearcherAccumulator) {
	if i <= 0 || cell == nil {
		//Base case
		return
	}

	if i-1 >= len(accumulator) {
		panic("The accumulator provided was not big enough for the i provided.")
	}

	generationDetails := accumulator[i-1]

	//Find the nextCells that WILL have their numbers forced by the cell we're thinking of fillint.
	cellsToVisit := cell.Neighbors().FilterByPossible(numToApply).FilterByNumPossibilities(2)

	//Now that we know which cells will be affected and what their next number will be,
	//set the number in the given cell and then recurse downward down each branch.
	cell.SetNumber(numToApply)

	generationDetails[cell.ref()] = numToApply

	for _, cellToVisit := range cellsToVisit {

		possibilities := cellToVisit.Possibilities()

		if len(possibilities) != 1 {
			panic("Expected the cell to have one possibility")
		}

		forcedNum := possibilities[0]

		//Each branch modifies the grid, so create a new copy
		newGrid := cellToVisit.grid.Copy()
		cellToVisit = cellToVisit.InGrid(newGrid)

		//Recurse downward
		chainSearcher(i-1, cellToVisit, forcedNum, accumulator)

	}

}
<commit_msg>TESTS FAIL. Added a note about what to do next in implementation to fix the bug we've identified over the last couple of commits.<commit_after>package sudoku

import (
	"fmt"
	"log"
	"strconv"
)

type forcingChainsTechnique struct {
	*basicSolveTechnique
}

func (self *forcingChainsTechnique) HumanLikelihood() float64 {
	//TODO: figure out what the baseDifficulty should be
	return self.difficultyHelper(200.0)
}

func (self *forcingChainsTechnique) Description(step *SolveStep) string {
	//TODO: implement this
	return "ERROR: NOT IMPLEMENTED"
}

func (self *forcingChainsTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {
	//TODO: test that this will find multiple if they exist.
	//TODO: Implement this.

	getter := grid.queue().DefaultGetter()

	_MAX_IMPLICATION_STEPS := 6

	for {

		//Check if it's time to stop.
		select {
		case <-done:
			return
		default:
		}

		candidate := getter.GetSmallerThan(3)

		if candidate == nil {
			break
		}

		candidateCell := candidate.(*Cell)

		if len(candidateCell.Possibilities()) != 2 {
			//We found one with 1 possibility, which isn't interesting for us--nakedSingle should do that one.
			continue
		}

		firstPossibilityNum := candidateCell.Possibilities()[0]
		secondPossibilityNum := candidateCell.Possibilities()[1]

		firstGrid := grid.Copy()
		secondGrid := grid.Copy()

		//Check that the neighbor isn't just already having a single possibility, because then this technique is overkill.

		firstAccumulator := makeChainSeacherAccumulator(_MAX_IMPLICATION_STEPS)
		secondAccumulator := makeChainSeacherAccumulator(_MAX_IMPLICATION_STEPS)

		chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(firstGrid),
			firstPossibilityNum,
			firstAccumulator)

		chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(secondGrid),
			secondPossibilityNum,
			secondAccumulator)

		//TODO:Check if the sets overlap.

		doPrint := candidateCell.Row() == 1 && candidateCell.Col() == 0

		//For these debugging purposes, only print out the candidateCell we know to be interesting in the test case.
		if doPrint {
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		//See if either branch, at some generation, has the same cell forced to the same number in either generation.

		//accumulate forward, so the last generation has ALL cells affected in any generation on that branch
		firstAccumulator.accumulateGenerations()
		secondAccumulator.accumulateGenerations()

		if doPrint {
			log.Println("Accumulators after accumulating generations:")
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		foundOne := false

		for generation := _MAX_IMPLICATION_STEPS - 1; generation >= 0 && !foundOne; generation-- {

			//Check for any overlap at the last generation
			firstAffectedCells := firstAccumulator[generation]
			secondAffectedCells := secondAccumulator[generation]

			for key, val := range firstAffectedCells {

				//Skip the candidateCell, because that's not a meaningful overlap--we set that one as a way of branching!
				if key == candidateCell.ref() {
					continue
				}

				if num, ok := secondAffectedCells[key]; ok {
					//Found cell overlap! ... is the forced number the same?
					if val == num {
						//Yup, seems like we've found a cell that is forced to the same value on either branch.
						step := &SolveStep{self,
							CellSlice{key.Cell(grid)},
							IntSlice{val},
							CellSlice{candidateCell},
							candidateCell.Possibilities(),
						}

						if doPrint {
							log.Println(step)
							log.Println("Candidate Cell", candidateCell.ref())
						}

						if step.IsUseful(grid) {
							foundOne = true
							if doPrint {
								log.Println("Found solution on generation: ", generation)
							}
							select {
							case results <- step:
							case <-done:
								return
							}
						}
					}
				}
			}
		}

		//TODO: figure out why the tests are coming back with different answers, even when only looking at the key cell
		//that should work from the example.
		//TODO: we should prefer solutions where the total implications on both branches are minimized.
		//For example, if only one implication is requried on left, but 4 are on right, that's preferable to one where
		//three implications are required on both sides.
		//TODO: figure out a way to only compute a generation if required on each branch (don't compute all the way to _MAX_IMPLICATIONS to start)

	}
}

type chainSearcherGenerationDetails map[cellRef]int

func (c chainSearcherGenerationDetails) String() string {
	result := "Begin map\n"
	for cell, num := range c {
		result += "\t" + cell.String() + " : " + strconv.Itoa(num) + "\n"
	}
	result += "End map\n"
	return result
}

type chainSearcherAccumulator []chainSearcherGenerationDetails

func (c chainSearcherAccumulator) String() string {
	result := "Accumulator[\n"
	for _, rec := range c {
		result += fmt.Sprintf("%s\n", rec)
	}
	result += "]\n"
	return result
}

//accumulateGenerations goes through each generation (youngest to newest)
//and squaches older generation maps into each generation, so each
//generation's map represents the totality of all cells seen at that point.
func (c chainSearcherAccumulator) accumulateGenerations() {
	for i := len(c) - 2; i >= 0; i-- {
		lastGeneration := c[i+1]
		currentGeneration := c[i]
		for key, val := range lastGeneration {
			if currentVal, ok := currentGeneration[key]; ok {
				if currentVal != val {
					//No, this should be expected to happen when we get to an invalid grid state,
					//which we should expect to happen down one of the two branches (at least as explore
					//far enough.)
					log.Println("We were about to overwrite a value from an earlier generation... this shouldn't happen.")
				}
			}
			currentGeneration[key] = val
		}
	}
}

func makeChainSeacherAccumulator(size int) chainSearcherAccumulator {
	result := make(chainSearcherAccumulator, size)
	for i := 0; i < size; i++ {
		result[i] = make(map[cellRef]int)
	}
	return result
}

func chainSearcher(i int, cell *Cell, numToApply int, accumulator chainSearcherAccumulator) {

	//TODO: we should change this implementation so that it's not DFS but BFS.
	//the first time we cross over into a new generation, we should do a one-time copy of the old generation
	//into the new.
	//At any write, if we notice that we'd be overwriting to a different value, we can bail out (how would
	//we mark that we bailed early), since we've run into an inconsistency down this branch and following
	//it further is not useful.

	if i <= 0 || cell == nil {
		//Base case
		return
	}

	if i-1 >= len(accumulator) {
		panic("The accumulator provided was not big enough for the i provided.")
	}

	generationDetails := accumulator[i-1]

	//Find the nextCells that WILL have their numbers forced by the cell we're thinking of fillint.
	cellsToVisit := cell.Neighbors().FilterByPossible(numToApply).FilterByNumPossibilities(2)

	//Now that we know which cells will be affected and what their next number will be,
	//set the number in the given cell and then recurse downward down each branch.
	cell.SetNumber(numToApply)

	generationDetails[cell.ref()] = numToApply

	for _, cellToVisit := range cellsToVisit {

		possibilities := cellToVisit.Possibilities()

		if len(possibilities) != 1 {
			panic("Expected the cell to have one possibility")
		}

		forcedNum := possibilities[0]

		//Each branch modifies the grid, so create a new copy
		newGrid := cellToVisit.grid.Copy()
		cellToVisit = cellToVisit.InGrid(newGrid)

		//Recurse downward
		chainSearcher(i-1, cellToVisit, forcedNum, accumulator)

	}

}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package assert

import (
	"fmt"
	"os"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

// 定位错误信息的触发函数。输出格式为：TestXxx(xxx_test.go:17)。
func getCallerInfo() string {
	var info string

	for i := 0; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}

		// 定位以 _test.go 结尾的文件。
		basename := path.Base(file)
		if !strings.HasSuffix(basename, "_test.go") {
			continue
		}

		// 定位函数名为 Test 开头的行。
		// 为什么要定位到 TestXxx 函数，是因为考虑以下情况：
		//  func isOK(val interface{}, t *testing.T) {
		//      // do somthing
		//      assert.True(t, val)  // (1
		//  }
		//
		//  func TestOK(t *testing.T) {
		//      isOK("123", t)       // (2
		//      isOK(123, t)         // (3
		//  }
		// 以上这段代码，定位到 (2、(3 的位置比总是定位到 (1 的位置更直观！
		funcName := runtime.FuncForPC(pc).Name()
		index := strings.LastIndex(funcName, ".Test")
		if -1 == index {
			index = strings.LastIndex(funcName, ".Benchmark")
			if index == -1 {
				continue
			}
		}
		funcName = funcName[index+1:]

		// Go1.5 之后的匿名函数为 TestA.func1
		// 包含以下几种情况：
		// 调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用外部函数；
		//
		// 但是无法处理 go xx() 的情况，该情况直接开启一个新的堆栈信息，无法定位当前函数中的调用位置。
		if index := strings.IndexByte(funcName, '.'); index > -1 {
			funcName = funcName[:index]
			info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
			continue
		}

		info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
		break
	}

	if info == "" {
		info = "<无法获取调用者信息>"
	}
	return info
}

// 格式化错误提示信息。
//
// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数，
// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
//
// msg2 参数格式与 msg1 完全相同，在 msg1 为空的情况下，会使用 msg2 的内容，
// 否则 msg2 不会启作用。
func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
	msg := msg1
	if len(msg) == 0 {
		msg = msg2
	}

	if len(msg) == 0 {
		return "<未提供任何错误信息>"
	}

	if len(msg) == 1 {
		return fmt.Sprint(msg[0])
	}

	format := ""
	switch v := msg[0].(type) {
	case []byte:
		format = string(v)
	case []rune:
		format = string(v)
	case string:
		format = v
	case fmt.Stringer:
		format = v.String()
	default:
		return fmt.Sprintln(msg...)
	}

	return fmt.Sprintf(format, msg[1:]...)
}

// 当 expr 条件不成立时，输出错误信息。
//
// expr 返回结果值为bool类型的表达式；
// msg1,msg2 输出的错误信息，之所以提供两组信息，是方便在用户没有提供的情况下，
// 可以使用系统内部提供的信息，优先使用 msg1 中的信息，若不存在，则使用 msg2 的内容。
func assert(t testing.TB, expr bool, msg1 []interface{}, msg2 []interface{}) {
	if !expr {
		t.Error(formatMessage(msg1, msg2) + "@" + getCallerInfo())
	}
}

// True 断言表达式 expr 为 true，否则输出错误信息。
//
// args 对应 fmt.Printf() 函数中的参数，其中 args[0] 对应第一个参数 format，依次类推，
// 具体可参数 formatMessage() 函数的介绍。其它断言函数的 args 参数，功能与此相同。
func True(t testing.TB, expr bool, args ...interface{}) {
	assert(t, expr, args, []interface{}{"True 失败，实际值为[%T:%[1]v]", expr})
}

// False 断言表达式 expr 为 false，否则输出错误信息
func False(t testing.TB, expr bool, args ...interface{}) {
	assert(t, !expr, args, []interface{}{"False 失败，实际值为[%T:%[1]v]", expr})
}

// Nil 断言表达式 expr 为 nil，否则输出错误信息
func Nil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsNil(expr), args, []interface{}{"Nil 失败，实际值为[%T:%[1]v]", expr})
}

// NotNil 断言表达式 expr 为非 nil 值，否则输出错误信息
func NotNil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsNil(expr), args, []interface{}{"NotNil 失败，实际值为[%T:%[1]v]", expr})
}

// Equal 断言 v1 与 v2 两个值相等，否则输出错误信息
func Equal(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, IsEqual(v1, v2), args, []interface{}{"Equal 失败，实际值为v1=[%T:%[1]v];v2=[%T:%[2]v]", v1, v2})
}

// NotEqual 断言 v1 与 v2 两个值不相等，否则输出错误信息
func NotEqual(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, !IsEqual(v1, v2), args, []interface{}{"NotEqual 失败，实际值为v1=[%T:%[1]v];v2=[%T:%[2]v]", v1, v2})
}

// Empty 断言 expr 的值为空(nil,"",0,false)，否则输出错误信息
func Empty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsEmpty(expr), args, []interface{}{"Empty 失败，实际值为[%T:%[1]v]", expr})
}

// NotEmpty 断言 expr 的值为非空(除 nil,"",0,false之外)，否则输出错误信息
func NotEmpty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsEmpty(expr), args, []interface{}{"NotEmpty 失败，实际值为[%T:%[1]v]", expr})
}

// Error 断言有错误发生，否则输出错误信息。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func Error(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"Error 失败，实际值为 Nil：[%T]", expr})
		return
	}

	_, ok := expr.(error)
	assert(t, ok, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
}

// ErrorString 断言有错误发生，且错误信息中包含指定的字符串 str。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorString(t testing.TB, expr interface{}, str string, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorString 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if err, ok := expr.(error); ok {
		index := strings.Index(err.Error(), str)
		assert(t, index >= 0, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
	}
}

// ErrorType 断言有错误发生，且错误的类型与 typ 的类型相同。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorType(t testing.TB, expr interface{}, typ error, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorType 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if _, ok := expr.(error); !ok {
		assert(t, false, args, []interface{}{"ErrorType 失败，实际类型为[%T]，且无法转换成 error 接口", expr})
		return
	}

	t1 := reflect.TypeOf(expr)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"ErrorType 失败，v1[%v]为一个错误类型，但与v2[%v]的类型不相同", t1, t2})
}

// NotError 断言没有错误发生，否则输出错误信息
func NotError(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值必定没有错误
		assert(t, true, args, []interface{}{"NotError 失败，实际类型为[%T]", expr})
		return
	}
	err, ok := expr.(error)
	assert(t, !ok, args, []interface{}{"NotError 失败，错误信息为[%v]", err})
}

// FileExists 断言文件存在，否则输出错误信息
func FileExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err != nil && !os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileExists 失败，且附带以下错误：%v", err})
	}
}

// FileNotExists 断言文件不存在，否则输出错误信息
func FileNotExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err == nil {
		assert(t, false, args, []interface{}{"FileNotExists 失败"})
	}
	if os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileNotExists 失败，且返回以下错误信息：%v", err})
	}
}

// Panic 断言函数会发生 panic，否则输出错误信息。
func Panic(t testing.TB, fn func(), args ...interface{}) {
	has, _ := HasPanic(fn)
	assert(t, has, args, []interface{}{"并未发生 panic"})
}

// PanicString 断言函数会发生 panic，且 panic 信息中包含指定的字符串内容，否则输出错误信息。
func PanicString(t testing.TB, fn func(), str string, args ...interface{}) {
	if has, msg := HasPanic(fn); has {
		index := strings.Index(fmt.Sprint(msg), str)
		assert(t, index >= 0, args, []interface{}{"并未发生 panic"})
	}
}

// PanicType 断言函数会发生 panic，且 panic 返回的类型与 typ 的类型相同。
func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
	has, msg := HasPanic(fn)
	if !has {
		return
	}

	t1 := reflect.TypeOf(msg)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"PanicType 失败，v1[%v]的类型与v2[%v]的类型不相同", t1, t2})

}

// NotPanic 断言函数会发生 panic，否则输出错误信息。
func NotPanic(t testing.TB, fn func(), args ...interface{}) {
	has, msg := HasPanic(fn)
	assert(t, !has, args, []interface{}{"发生了 panic，其信息为[%v]", msg})
}

// Contains 断言 container 包含 item 的或是包含 item 中的所有项
// 具体函数说明可参考 IsContains()
func Contains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, IsContains(container, item), args,
		[]interface{}{"container:[%v]并未包含item[%v]", container, item})
}

// NotContains 断言 container 不包含 item 的或是不包含 item 中的所有项
func NotContains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, !IsContains(container, item), args,
		[]interface{}{"container:[%v]包含item[%v]", container, item})
}
<commit_msg>refactor: 格式化输出<commit_after>// SPDX-License-Identifier: MIT

package assert

import (
	"fmt"
	"os"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

// 定位错误信息的触发函数。输出格式为：TestXxx(xxx_test.go:17)。
func getCallerInfo() string {
	var info string

	for i := 0; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}

		// 定位以 _test.go 结尾的文件。
		basename := path.Base(file)
		if !strings.HasSuffix(basename, "_test.go") {
			continue
		}

		// 定位函数名为 Test 开头的行。
		// 为什么要定位到 TestXxx 函数，是因为考虑以下情况：
		//  func isOK(val interface{}, t *testing.T) {
		//      // do somthing
		//      assert.True(t, val)  // (1
		//  }
		//
		//  func TestOK(t *testing.T) {
		//      isOK("123", t)       // (2
		//      isOK(123, t)         // (3
		//  }
		// 以上这段代码，定位到 (2、(3 的位置比总是定位到 (1 的位置更直观！
		funcName := runtime.FuncForPC(pc).Name()
		index := strings.LastIndex(funcName, ".Test")
		if -1 == index {
			index = strings.LastIndex(funcName, ".Benchmark")
			if index == -1 {
				continue
			}
		}
		funcName = funcName[index+1:]

		// Go1.5 之后的匿名函数为 TestA.func1
		// 包含以下几种情况：
		// 调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用外部函数；
		//
		// 但是无法处理 go xx() 的情况，该情况直接开启一个新的堆栈信息，无法定位当前函数中的调用位置。
		if index := strings.IndexByte(funcName, '.'); index > -1 {
			funcName = funcName[:index]
			info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
			continue
		}

		info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
		break
	}

	if info == "" {
		info = "<无法获取调用者信息>"
	}
	return info
}

// 格式化错误提示信息。
//
// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数，
// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
//
// msg2 参数格式与 msg1 完全相同，在 msg1 为空的情况下，会使用 msg2 的内容，
// 否则 msg2 不会启作用。
func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
	msg := msg1
	if len(msg) == 0 {
		msg = msg2
	}

	if len(msg) == 0 {
		return "<未提供任何错误信息>"
	}

	if len(msg) == 1 {
		return fmt.Sprint(msg[0])
	}

	format := ""
	switch v := msg[0].(type) {
	case []byte:
		format = string(v)
	case []rune:
		format = string(v)
	case string:
		format = v
	case fmt.Stringer:
		format = v.String()
	default:
		return fmt.Sprintln(msg...)
	}

	return fmt.Sprintf(format, msg[1:]...)
}

// 当 expr 条件不成立时，输出错误信息。
//
// expr 返回结果值为bool类型的表达式；
// msg1,msg2 输出的错误信息，之所以提供两组信息，是方便在用户没有提供的情况下，
// 可以使用系统内部提供的信息，优先使用 msg1 中的信息，若不存在，则使用 msg2 的内容。
func assert(t testing.TB, expr bool, msg1 []interface{}, msg2 []interface{}) {
	if !expr {
		t.Error(formatMessage(msg1, msg2) + "@" + getCallerInfo())
	}
}

// True 断言表达式 expr 为 true，否则输出错误信息。
//
// args 对应 fmt.Printf() 函数中的参数，其中 args[0] 对应第一个参数 format，依次类推，
// 具体可参数 formatMessage() 函数的介绍。其它断言函数的 args 参数，功能与此相同。
func True(t testing.TB, expr bool, args ...interface{}) {
	assert(t, expr, args, []interface{}{"True 失败，实际值为[%T:%[1]v]", expr})
}

// False 断言表达式 expr 为 false，否则输出错误信息
func False(t testing.TB, expr bool, args ...interface{}) {
	assert(t, !expr, args, []interface{}{"False 失败，实际值为[%T:%[1]v]", expr})
}

// Nil 断言表达式 expr 为 nil，否则输出错误信息
func Nil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsNil(expr), args, []interface{}{"Nil 失败，实际值为[%T:%[1]v]", expr})
}

// NotNil 断言表达式 expr 为非 nil 值，否则输出错误信息
func NotNil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsNil(expr), args, []interface{}{"NotNil 失败，实际值为[%T:%[1]v]", expr})
}

// Equal 断言 v1 与 v2 两个值相等，否则输出错误信息
func Equal(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, IsEqual(v1, v2), args, []interface{}{"Equal 失败，实际值为\nv1=[%T:%[1]v]\nv2=[%T:%[2]v]", v1, v2})
}

// NotEqual 断言 v1 与 v2 两个值不相等，否则输出错误信息
func NotEqual(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, !IsEqual(v1, v2), args, []interface{}{"NotEqual 失败，实际值为\nv1=[%T:%[1]v]\nv2=[%T:%[2]v]", v1, v2})
}

// Empty 断言 expr 的值为空(nil,"",0,false)，否则输出错误信息
func Empty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsEmpty(expr), args, []interface{}{"Empty 失败，实际值为[%T:%[1]v]", expr})
}

// NotEmpty 断言 expr 的值为非空(除 nil,"",0,false之外)，否则输出错误信息
func NotEmpty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsEmpty(expr), args, []interface{}{"NotEmpty 失败，实际值为[%T:%[1]v]", expr})
}

// Error 断言有错误发生，否则输出错误信息。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func Error(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"Error 失败，实际值为 Nil：[%T]", expr})
		return
	}

	_, ok := expr.(error)
	assert(t, ok, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
}

// ErrorString 断言有错误发生，且错误信息中包含指定的字符串 str。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorString(t testing.TB, expr interface{}, str string, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorString 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if err, ok := expr.(error); ok {
		index := strings.Index(err.Error(), str)
		assert(t, index >= 0, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
	}
}

// ErrorType 断言有错误发生，且错误的类型与 typ 的类型相同。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorType(t testing.TB, expr interface{}, typ error, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorType 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if _, ok := expr.(error); !ok {
		assert(t, false, args, []interface{}{"ErrorType 失败，实际类型为[%T]，且无法转换成 error 接口", expr})
		return
	}

	t1 := reflect.TypeOf(expr)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"ErrorType 失败，v1[%v]为一个错误类型，但与v2[%v]的类型不相同", t1, t2})
}

// NotError 断言没有错误发生，否则输出错误信息
func NotError(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值必定没有错误
		assert(t, true, args, []interface{}{"NotError 失败，实际类型为[%T]", expr})
		return
	}
	err, ok := expr.(error)
	assert(t, !ok, args, []interface{}{"NotError 失败，错误信息为[%v]", err})
}

// FileExists 断言文件存在，否则输出错误信息
func FileExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err != nil && !os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileExists 失败，且附带以下错误：%v", err})
	}
}

// FileNotExists 断言文件不存在，否则输出错误信息
func FileNotExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err == nil {
		assert(t, false, args, []interface{}{"FileNotExists 失败"})
	}
	if os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileNotExists 失败，且返回以下错误信息：%v", err})
	}
}

// Panic 断言函数会发生 panic，否则输出错误信息。
func Panic(t testing.TB, fn func(), args ...interface{}) {
	has, _ := HasPanic(fn)
	assert(t, has, args, []interface{}{"并未发生 panic"})
}

// PanicString 断言函数会发生 panic，且 panic 信息中包含指定的字符串内容，否则输出错误信息。
func PanicString(t testing.TB, fn func(), str string, args ...interface{}) {
	if has, msg := HasPanic(fn); has {
		index := strings.Index(fmt.Sprint(msg), str)
		assert(t, index >= 0, args, []interface{}{"并未发生 panic"})
	}
}

// PanicType 断言函数会发生 panic，且 panic 返回的类型与 typ 的类型相同。
func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
	has, msg := HasPanic(fn)
	if !has {
		return
	}

	t1 := reflect.TypeOf(msg)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"PanicType 失败，v1[%v]的类型与v2[%v]的类型不相同", t1, t2})

}

// NotPanic 断言函数会发生 panic，否则输出错误信息。
func NotPanic(t testing.TB, fn func(), args ...interface{}) {
	has, msg := HasPanic(fn)
	assert(t, !has, args, []interface{}{"发生了 panic，其信息为[%v]", msg})
}

// Contains 断言 container 包含 item 的或是包含 item 中的所有项
// 具体函数说明可参考 IsContains()
func Contains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, IsContains(container, item), args,
		[]interface{}{"container:[%v]并未包含item[%v]", container, item})
}

// NotContains 断言 container 不包含 item 的或是不包含 item 中的所有项
func NotContains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, !IsContains(container, item), args,
		[]interface{}{"container:[%v]包含item[%v]", container, item})
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	log "github.com/sirupsen/logrus"

	voucher "github.com/grafeas/voucher/v2"
)

func init() {
	log.SetFormatter(&log.JSONFormatter{})
}

// LogRequests logs the request fields to stdout as Info
func LogRequests(r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		log.WithError(err).Info("received request with malformed form")
		return
	}

	log.WithFields(log.Fields{
		"url":  r.URL,
		"path": r.URL.Path,
		"form": r.Form,
	}).Info("received request")
}

// LogResult logs each test run as Info
func LogResult(response voucher.Response) {
	for _, result := range response.Results {
		log.WithFields(log.Fields{
			"check":    result.Name,
			"image":    response.Image,
			"passed":   result.Success,
			"attested": result.Attested,
			"error":    result.Err,
		}).Info("Check Result")
	}
}

// LogError logs server errors to stdout as Error
func LogError(message string, err error) {
	log.Errorf("Server error: %s: %s", message, err)
}

// LogWarning logs server errors to stdout as Warning
func LogWarning(message string, err error) {
	log.Warningf("Server warning: %s: %s", message, err)
}

// LogInfo logs server information to stdout as Information.
func LogInfo(message string) {
	log.Infof("Server info: %s", message)
}
<commit_msg>logging: url as string<commit_after>package server

import (
	"net/http"

	log "github.com/sirupsen/logrus"

	voucher "github.com/grafeas/voucher/v2"
)

func init() {
	log.SetFormatter(&log.JSONFormatter{})
}

// LogRequests logs the request fields to stdout as Info
func LogRequests(r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		log.WithError(err).Info("received request with malformed form")
		return
	}

	log.WithFields(log.Fields{
		"url":  r.URL.String(),
		"path": r.URL.Path,
		"form": r.Form,
	}).Info("received request")
}

// LogResult logs each test run as Info
func LogResult(response voucher.Response) {
	for _, result := range response.Results {
		log.WithFields(log.Fields{
			"check":    result.Name,
			"image":    response.Image,
			"passed":   result.Success,
			"attested": result.Attested,
			"error":    result.Err,
		}).Info("Check Result")
	}
}

// LogError logs server errors to stdout as Error
func LogError(message string, err error) {
	log.Errorf("Server error: %s: %s", message, err)
}

// LogWarning logs server errors to stdout as Warning
func LogWarning(message string, err error) {
	log.Warningf("Server warning: %s: %s", message, err)
}

// LogInfo logs server information to stdout as Information.
func LogInfo(message string) {
	log.Infof("Server info: %s", message)
}
<|endoftext|>


<commit_before>package main

import (
    "github.com/ant0ine/go-json-rest/rest"
    "github.com/tbruyelle/hipchat-go/hipchat"
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "strings"
    "sync"
    "time"
)

/*
To-do list
---------
Trains active
Manually depart a train
Derail a train
Start a train at a designated time
Tag people when the train leaves
Propose a train that starts if enough people join -> X
-----
Keep track of usage statistics
Look into making your own log to log things

53 so far
Look into moving to AWS
*/

var authKey string = ""
var roomName string = ""

var station *Station = &Station{
	Lock: &sync.Mutex{},
	Trains: make(map[string]*Train),
}

type WebhookMessage struct {
    Item struct {
        MessageStruct struct {
        	From struct {
        		MentionName string `json:"mention_name"`
        	}
        	Message string `json:"message"`
        } `json:"message"`

    }  `json:"item"`
}

type Train struct {
	Lock *sync.Mutex
	LeavingTimer *time.Timer
	ReminderTimer *time.Timer
	MapDestination string
	DisplayDestination string
	Passengers []string
	PassengerSet map[string]struct{}
}

func NewTrain(conductor string, departure int, dest string) *Train {
	timer := time.NewTimer(time.Minute * time.Duration(departure))
	timer2 := time.NewTimer(time.Minute * time.Duration(departure - 1))
	users := []string{conductor}
	trainMap := make(map[string]struct{})
	trainMap[conductor] = struct{}{}
	return &Train{
		Lock: &sync.Mutex{},
		LeavingTimer: timer,
		ReminderTimer: timer2,
		MapDestination: strings.ToLower(dest),
		DisplayDestination: dest,
		Passengers: users,
		PassengerSet: trainMap,
	}
}

func (t *Train) NewPassenger(pass string) error {
	t.Lock.Lock()
	defer t.Lock.Unlock()
	_, ok := t.PassengerSet[pass]
	if !ok {
		t.PassengerSet[pass] = struct{}{}
		t.Passengers = append(t.Passengers, pass)
		return nil
	} else {
		log.Printf("Passenger %s is already on the train\n", pass)
		return fmt.Errorf("Passenger %s is already on the train", pass)
	}
}

func (t *Train) PassengerString() string {
	t.Lock.Lock()
	defer t.Lock.Unlock()
	var buffer bytes.Buffer
	for i, v := range t.Passengers {
	   buffer.WriteString(v)
	   if i != len(t.Passengers) - 1 {
	    	buffer.WriteString(", ")
	    }
	   if i == len(t.Passengers) - 2 {
	    	buffer.WriteString("and ")
	   }
	}
	return buffer.String()

}

type Station struct {
	Lock *sync.Mutex
	Trains map[string]*Train
}

func (s *Station) AddTrain(t *Train) error {
	s.Lock.Lock()
	defer s.Lock.Unlock()
	_, ok := s.Trains[t.MapDestination]
	if !ok {
		s.Trains[t.MapDestination] = t
		return nil
	} else {
		return fmt.Errorf("Train to %s already exists", t.DisplayDestination)
	}
}

func (s *Station) DeleteTrain(dest string) error {
	s.Lock.Lock()
	defer s.Lock.Unlock()
	_, ok := s.Trains[dest]
	if ok {
		 delete(s.Trains, dest)
		 return nil
	} else {
		return fmt.Errorf("The train to %s doesn't exist so it can't be removed", dest)
	}
}

func PostMessage(msg string) {
	c := hipchat.NewClient(authKey)
	msgReq := &hipchat.NotificationRequest{Message: msg}
	_, err := c.Room.Notification(roomName, msgReq)
	if err != nil {
		panic(err)
	}
}

func MonitorTrain(train *Train) {
	for {
		select {
	    case <- train.LeavingTimer.C:
	    	var buffer bytes.Buffer
	    	start := fmt.Sprintf("The train to %v has left the station with ", train.DisplayDestination)
	    	buffer.WriteString(start)
	    	buffer.WriteString(train.PassengerString())
	    	buffer.WriteString(" on it!")
	    	PostMessage(buffer.String())
	    	station.DeleteTrain(train.MapDestination)
	    	return
	    case <- train.ReminderTimer.C:
            PostMessage(fmt.Sprintf("Reminder, the next train to %v leaves in one minute", train.DisplayDestination))
	    default:
		}
	}
}

func GetDestinationAndTime(start int, messageParts []string, getTime bool) (string, int, error) {
	var dest bytes.Buffer
	for i := start; i < len(messageParts); i++ {
		if getTime {
			num, err := strconv.Atoi(messageParts[i])
			if err == nil && i == len(messageParts) - 1 {
				return dest.String(), num, nil
			}
		}
		if i > start {
			dest.WriteString(" ")
		}
		dest.WriteString(messageParts[i])
	}
	if !getTime {
		return dest.String(), 0, nil
	}
	return "", 0, fmt.Errorf("Couldn't parse dest and/or time to departure")
}

func Handler(w rest.ResponseWriter, r *rest.Request) {
	var webMsg WebhookMessage
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&webMsg)
	if err != nil {
		PostMessage(err.Error())
		return
	}

	author := webMsg.Item.MessageStruct.From.MentionName
	insufficientParams := fmt.Sprintf("%v messed up and forgot to provide the sufficient number of params", author)
	messageParts := strings.Split(webMsg.Item.MessageStruct.Message, " ")

	var msg string
	if len(messageParts) < 2 {
		PostMessage(insufficientParams)
		return
	}
	cmd := strings.ToLower(messageParts[1])
	malformed := "Your command is malformed or not found, please view the help message (/train help) for more details"
	notFound := "That train doesn't exist, please try again"
	switch cmd {
	case "help":
		msg = "Usage: /train start <destination> <#minutes> || /train join <destination> || /train passengers <destination>"
		PostMessage(msg)
	case "passengers":
		dest, _, err := GetDestinationAndTime(2, messageParts, false)
		if err != nil {
			PostMessage(err.Error())
			break
		}
		train, ok := station.Trains[strings.ToLower(dest)]
		if !ok {
			PostMessage(notFound)
		} else {
			if len(train.Passengers) == 1 {
				msg = fmt.Sprintf("%v is on the train to %v", train.PassengerString(), train.DisplayDestination)
			} else {
				msg = fmt.Sprintf("%v are on the train to %v", train.PassengerString(), train.DisplayDestination)
			}
			PostMessage(msg)
		}
	case "join":
		dest, _, err := GetDestinationAndTime(2, messageParts, false)
		if err != nil {
			PostMessage(err.Error())
			break
		}
		train, ok := station.Trains[strings.ToLower(dest)]
		if ok {
			err := train.NewPassenger(author)
			if err == nil {
				msg = fmt.Sprintf("%s jumped on the train to %s", author, train.DisplayDestination)
				PostMessage(msg)
			} else {
				msg = err.Error()
				PostMessage(msg)
			}
		} else {
			PostMessage(notFound)
		}
	case "start":
		dest, length, err := GetDestinationAndTime(2, messageParts, true)
		if err != nil {
			PostMessage(malformed)
			break
		}
		if length <= 0 {
			msg = fmt.Sprintf("Please specify a time greater than 0 mins")
			PostMessage(msg)
			break
		}
		_, ok := station.Trains[strings.ToLower(dest)]
		if ok {
			msg = fmt.Sprintf("There's already a train to %v!", dest)
			PostMessage(msg)
			break
		} else {
			train := NewTrain(author, length, dest)
			err = station.AddTrain(train)
			if err != nil {
				PostMessage(err.Error())
				break
			} else {
				msg = fmt.Sprintf("%s has started a train to %v that leaves in %v minutes!", author, train.DisplayDestination, length)
				PostMessage(msg)
				go MonitorTrain(train)
			}
		}
	default:
		PostMessage(malformed)
	}
}

func ValidityHandler(w rest.ResponseWriter, r *rest.Request) {
	str := "Everything is OK!"
	w.WriteJson(&str)
}

func main() {
   	api := rest.NewApi()
    api.Use(rest.DefaultDevStack...)

    router, err := rest.MakeRouter(
  		rest.Get("/", ValidityHandler),
    	rest.Post("/train", Handler),
    )

    api.SetApp(router)
    ip := os.Getenv("OPENSHIFT_GO_IP")
    port := os.Getenv("OPENSHIFT_GO_PORT")
    if port == "" {
    	port = "8080"
    }
    bind := fmt.Sprintf("%s:%s",ip,port)
	err = http.ListenAndServe(bind, api.MakeHandler())
	if err != nil {
    	log.Println(err)
    }
}<commit_msg>Added improved logging<commit_after>package main

import (
    "github.com/ant0ine/go-json-rest/rest"
    "github.com/tbruyelle/hipchat-go/hipchat"
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "strings"
    "sync"
    "time"
)

/*
To-do list
---------
Trains active
Manually depart a train
Derail a train
Start a train at a designated time
Tag people when the train leaves
Propose a train that starts if enough people join -> X
-----
Keep track of usage statistics
Look into making your own log to log things

53 so far
Look into moving to AWS
*/

var authKey string = ""
var roomName string = ""

var station *Station = &Station{
	Lock: &sync.Mutex{},
	Trains: make(map[string]*Train),
}

type WebhookMessage struct {
    Item struct {
        MessageStruct struct {
        	From struct {
        		MentionName string `json:"mention_name"`
        	}
        	Message string `json:"message"`
        } `json:"message"`

    }  `json:"item"`
}

type Train struct {
	Lock *sync.Mutex
	LeavingTimer *time.Timer
	ReminderTimer *time.Timer
	MapDestination string
	DisplayDestination string
	Passengers []string
	PassengerSet map[string]struct{}
}

func NewTrain(conductor string, departure int, dest string) *Train {
	timer := time.NewTimer(time.Minute * time.Duration(departure))
	timer2 := time.NewTimer(time.Minute * time.Duration(departure - 1))
	users := []string{conductor}
	trainMap := make(map[string]struct{})
	trainMap[conductor] = struct{}{}
	return &Train{
		Lock: &sync.Mutex{},
		LeavingTimer: timer,
		ReminderTimer: timer2,
		MapDestination: strings.ToLower(dest),
		DisplayDestination: dest,
		Passengers: users,
		PassengerSet: trainMap,
	}
}

func (t *Train) NewPassenger(pass string) error {
	t.Lock.Lock()
	defer t.Lock.Unlock()
	_, ok := t.PassengerSet[pass]
	if !ok {
		t.PassengerSet[pass] = struct{}{}
		t.Passengers = append(t.Passengers, pass)
		return nil
	} else {
		log.Printf("Passenger %s is already on the train\n", pass)
		return fmt.Errorf("Passenger %s is already on the train", pass)
	}
}

func (t *Train) PassengerString() string {
	t.Lock.Lock()
	defer t.Lock.Unlock()
	var buffer bytes.Buffer
	for i, v := range t.Passengers {
	   buffer.WriteString(v)
	   if i != len(t.Passengers) - 1 {
	    	buffer.WriteString(", ")
	    }
	   if i == len(t.Passengers) - 2 {
	    	buffer.WriteString("and ")
	   }
	}
	return buffer.String()

}

type Station struct {
	Lock *sync.Mutex
	Trains map[string]*Train
}

func (s *Station) AddTrain(t *Train) error {
	s.Lock.Lock()
	defer s.Lock.Unlock()
	_, ok := s.Trains[t.MapDestination]
	if !ok {
		s.Trains[t.MapDestination] = t
		return nil
	} else {
		log.Printf("Train to %s already exists", t.DisplayDestination)
		return fmt.Errorf("Train to %s already exists", t.DisplayDestination)
	}
}

func (s *Station) DeleteTrain(dest string) error {
	s.Lock.Lock()
	defer s.Lock.Unlock()
	_, ok := s.Trains[dest]
	if ok {
		 delete(s.Trains, dest)
		 return nil
	} else {
		log.Printf("The train to %s doesn't exist so it can't be removed", dest)
		return fmt.Errorf("The train to %s doesn't exist so it can't be removed", dest)
	}
}

func PostMessage(msg string) {
	c := hipchat.NewClient(authKey)
	msgReq := &hipchat.NotificationRequest{Message: msg}
	_, err := c.Room.Notification(roomName, msgReq)
	if err != nil {
		panic(err)
	}
}

func MonitorTrain(train *Train) {
	for {
		select {
	    case <- train.LeavingTimer.C:
	    	var buffer bytes.Buffer
	    	start := fmt.Sprintf("The train to %v has left the station with ", train.DisplayDestination)
	    	buffer.WriteString(start)
	    	buffer.WriteString(train.PassengerString())
	    	buffer.WriteString(" on it!")
	    	PostMessage(buffer.String())
	    	station.DeleteTrain(train.MapDestination)
	    	return
	    case <- train.ReminderTimer.C:
            PostMessage(fmt.Sprintf("Reminder, the next train to %v leaves in one minute", train.DisplayDestination))
	    default:
		}
	}
}

func GetDestinationAndTime(start int, messageParts []string, getTime bool) (string, int, error) {
	var dest bytes.Buffer
	for i := start; i < len(messageParts); i++ {
		if getTime {
			num, err := strconv.Atoi(messageParts[i])
			if err == nil && i == len(messageParts) - 1 {
				return dest.String(), num, nil
			}
		}
		if i > start {
			dest.WriteString(" ")
		}
		dest.WriteString(messageParts[i])
	}
	if !getTime {
		return dest.String(), 0, nil
	}
	return "", 0, fmt.Errorf("Couldn't parse dest and/or time to departure")
}

func Handler(w rest.ResponseWriter, r *rest.Request) {
	var webMsg WebhookMessage
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&webMsg)
	if err != nil {
		log.Printf(err.Error())
		PostMessage(err.Error())
		return
	}

	author := webMsg.Item.MessageStruct.From.MentionName
	insufficientParams := fmt.Sprintf("%v messed up and forgot to provide the sufficient number of params", author)
	messageParts := strings.Split(webMsg.Item.MessageStruct.Message, " ")

	var msg string
	if len(messageParts) < 2 {
		PostMessage(insufficientParams)
		return
	}
	cmd := strings.ToLower(messageParts[1])
	malformed := "Your command is malformed or not found, please view the help message (/train help) for more details"
	notFound := "That train doesn't exist, please try again"
	switch cmd {
	case "help":
		msg = "Usage: /train start <destination> <#minutes> || /train join <destination> || /train passengers <destination>"
		PostMessage(msg)
	case "passengers":
		dest, _, err := GetDestinationAndTime(2, messageParts, false)
		if err != nil {
			PostMessage(err.Error())
			break
		}
		train, ok := station.Trains[strings.ToLower(dest)]
		if !ok {
			PostMessage(notFound)
		} else {
			if len(train.Passengers) == 1 {
				msg = fmt.Sprintf("%v is on the train to %v", train.PassengerString(), train.DisplayDestination)
			} else {
				msg = fmt.Sprintf("%v are on the train to %v", train.PassengerString(), train.DisplayDestination)
			}
			PostMessage(msg)
		}
	case "join":
		dest, _, err := GetDestinationAndTime(2, messageParts, false)
		if err != nil {
			PostMessage(err.Error())
			break
		}
		train, ok := station.Trains[strings.ToLower(dest)]
		if ok {
			err := train.NewPassenger(author)
			if err == nil {
				msg = fmt.Sprintf("%s jumped on the train to %s", author, train.DisplayDestination)
				PostMessage(msg)
			} else {
				msg = err.Error()
				PostMessage(msg)
			}
		} else {
			PostMessage(notFound)
		}
	case "start":
		dest, length, err := GetDestinationAndTime(2, messageParts, true)
		if err != nil {
			PostMessage(malformed)
			break
		}
		if length <= 0 {
			msg = fmt.Sprintf("Please specify a time greater than 0 mins")
			PostMessage(msg)
			break
		}
		_, ok := station.Trains[strings.ToLower(dest)]
		if ok {
			msg = fmt.Sprintf("There's already a train to %v!", dest)
			PostMessage(msg)
			break
		} else {
			train := NewTrain(author, length, dest)
			err = station.AddTrain(train)
			if err != nil {
				PostMessage(err.Error())
				break
			} else {
				msg = fmt.Sprintf("%s has started a train to %v that leaves in %v minutes!", author, train.DisplayDestination, length)
				PostMessage(msg)
				go MonitorTrain(train)
			}
		}
	default:
		PostMessage(malformed)
	}
}

func ValidityHandler(w rest.ResponseWriter, r *rest.Request) {
	str := "Everything is OK!"
	w.WriteJson(&str)
}

func main() {
   	api := rest.NewApi()
    api.Use(rest.DefaultDevStack...)

    router, err := rest.MakeRouter(
  		rest.Get("/", ValidityHandler),
    	rest.Post("/train", Handler),
    )

    api.SetApp(router)
    ip := os.Getenv("OPENSHIFT_GO_IP")
    port := os.Getenv("OPENSHIFT_GO_PORT")
    if port == "" {
    	port = "8080"
    }
    bind := fmt.Sprintf("%s:%s",ip,port)
	err = http.ListenAndServe(bind, api.MakeHandler())
	if err != nil {
    	log.Println(err)
    }
}<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"regexp"

	"github.com/mozillazg/request"
)

func home(a *request.Args) (statusCode int) {
	resp, err := request.Get("http://login-test.3sd.me:10081/", a)
	if err != nil {
		return 500
	}
	return resp.StatusCode
}

func getCSRFToken(a *request.Args) (string, error) {
	url := "http://login-test.3sd.me:10081/login/"
	resp, err := request.Get(url, a)
	if err != nil {
		return "", err
	}
	s, err := resp.Text()
	if err != nil {
		return "", err
	}

	reInput := regexp.MustCompile(
		`<input\s+[^>]*?name=['"]csrfmiddlewaretoken['"'][^>]*>`,
	)
	input := reInput.FindString(s)
	reValue := regexp.MustCompile(`value=['"]([^'"]+)['"]`)
	csrfToken := reValue.FindStringSubmatch(input)
	if len(csrfToken) < 2 {
		return "", err
	}
	return csrfToken[1], err
}

func login(a *request.Args) error {
	url := "http://login-test.3sd.me:10081/login/"
	_, err := request.Post(url, a)
	return err
}

func main() {
	c := new(http.Client)
	a := request.NewArgs(c)
	log.Println(home(a)) // 403

	// login
	csrfToken, err := getCSRFToken(a)
	if err != nil {
		log.Fatal(err)
	}
	a.Data = map[string]string{
		"csrfmiddlewaretoken": csrfToken,
		"name":                "go-request",
		"password":            "go-request-passwd",
	}
	log.Println(csrfToken)
	err = login(a)
	if err != nil {
		log.Fatal(err)
	}

	log.Println(home(a)) // 200
}
<commit_msg>update login example<commit_after>package main

import (
	"log"
	"net/http"
	"regexp"

	"github.com/mozillazg/request"
)

const (
	loginRequiredPageURL = "http://login-test.3sd.me:10081/"
	loginPageURL         = "http://login-test.3sd.me:10081/login/"
)

func home(a *request.Args) (statusCode int) {
	resp, err := request.Get(loginRequiredPageURL, a)
	if err != nil {
		return 500
	}
	return resp.StatusCode
}

func getCSRFToken(a *request.Args) (string, error) {
	resp, err := request.Get(loginPageURL, a)
	if err != nil {
		return "", err
	}
	s, err := resp.Text()
	if err != nil {
		return "", err
	}

	reInput := regexp.MustCompile(
		`<input\s+[^>]*?name=['"]csrfmiddlewaretoken['"'][^>]*>`,
	)
	input := reInput.FindString(s)
	reValue := regexp.MustCompile(`value=['"]([^'"]+)['"]`)
	csrfToken := reValue.FindStringSubmatch(input)
	if len(csrfToken) < 2 {
		return "", err
	}
	return csrfToken[1], err
}

func login(a *request.Args) error {
	_, err := request.Post(loginPageURL, a)
	return err
}

func main() {
	c := new(http.Client)
	a := request.NewArgs(c)
	log.Println(home(a)) // 403

	// login
	csrfToken, err := getCSRFToken(a)
	if err != nil {
		log.Fatal(err)
	}
	a.Data = map[string]string{
		"csrfmiddlewaretoken": csrfToken,
		"name":                "go-request",
		"password":            "go-request-passwd",
	}
	log.Println(csrfToken)
	err = login(a)
	if err != nil {
		log.Fatal(err)
	}

	log.Println(home(a)) // 200
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin linux

package main

import (
	"image"
	"log"
	"math"
	"math/rand"

	_ "image/png"

	"golang.org/x/mobile/asset"
	"golang.org/x/mobile/exp/f32"
	"golang.org/x/mobile/exp/sprite"
	"golang.org/x/mobile/exp/sprite/clock"
)

const (
	tileWidth, tileHeight = 16, 16 // width and height of each tile
	tilesX, tilesY        = 16, 16 // number of horizontal tiles

	gopherTile = 1 // which tile the gopher is standing on (0-indexed)

	initScrollV = 1     // initial scroll velocity
	scrollA     = 0.001 // scroll accelleration
	gravity     = 0.1   // gravity
	jumpV       = -5    // jump velocity
	flapV       = -1.5  // flap velocity

	deadScrollA         = -0.01 // scroll deceleration after the gopher dies
	deadTimeBeforeReset = 240   // how long to wait before restarting the game

	groundChangeProb = 5 // 1/probability of ground height change
	groundWobbleProb = 3 // 1/probability of minor ground height change
	groundMin        = tileHeight * (tilesY - 2*tilesY/5)
	groundMax        = tileHeight * tilesY
	initGroundY      = tileHeight * (tilesY - 1)

	climbGrace = tileHeight / 3 // gopher won't die if it hits a cliff this high
)

type Game struct {
	gopher struct {
		y        float32    // y-offset
		v        float32    // velocity
		atRest   bool       // is the gopher on the ground?
		flapped  bool       // has the gopher flapped since it became airborne?
		dead     bool       // is the gopher dead?
		deadTime clock.Time // when the gopher died
	}
	scroll struct {
		x float32 // x-offset
		v float32 // velocity
	}
	groundY   [tilesX + 3]float32 // ground y-offsets
	groundTex [tilesX + 3]int     // ground texture
	lastCalc  clock.Time          // when we last calculated a frame
}

func NewGame() *Game {
	var g Game
	g.reset()
	return &g
}

func (g *Game) reset() {
	g.gopher.y = 0
	g.gopher.v = 0
	g.scroll.x = 0
	g.scroll.v = initScrollV
	for i := range g.groundY {
		g.groundY[i] = initGroundY
		g.groundTex[i] = randomGroundTexture()
	}
	g.gopher.atRest = false
	g.gopher.flapped = false
	g.gopher.dead = false
	g.gopher.deadTime = 0
}

func (g *Game) Scene(eng sprite.Engine) *sprite.Node {
	texs := loadTextures(eng)

	scene := &sprite.Node{}
	eng.Register(scene)
	eng.SetTransform(scene, f32.Affine{
		{1, 0, 0},
		{0, 1, 0},
	})

	newNode := func(fn arrangerFunc) {
		n := &sprite.Node{Arranger: arrangerFunc(fn)}
		eng.Register(n)
		scene.AppendChild(n)
	}

	// The ground.
	for i := range g.groundY {
		i := i
		// The top of the ground.
		newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
			eng.SetSubTex(n, texs[g.groundTex[i]])
			eng.SetTransform(n, f32.Affine{
				{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
				{0, tileHeight, g.groundY[i]},
			})
		})
		// The earth beneath.
		newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
			eng.SetSubTex(n, texs[texEarth])
			eng.SetTransform(n, f32.Affine{
				{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
				{0, tileHeight * tilesY, g.groundY[i] + tileHeight},
			})
		})
	}

	// The gopher.
	newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
		a := f32.Affine{
			{tileWidth * 2, 0, tileWidth*(gopherTile-1) + tileWidth/8},
			{0, tileHeight * 2, g.gopher.y - tileHeight + tileHeight/4},
		}
		var x int
		switch {
		case g.gopher.dead:
			x = frame(t, 16, texGopherDead1, texGopherDead2)
			animateDeadGopher(&a, t-g.gopher.deadTime)
		case g.gopher.v < 0:
			x = frame(t, 4, texGopherFlap1, texGopherFlap2)
		case g.gopher.atRest:
			x = frame(t, 4, texGopherRun1, texGopherRun2)
		default:
			x = frame(t, 8, texGopherRun1, texGopherRun2)
		}
		eng.SetSubTex(n, texs[x])
		eng.SetTransform(n, a)
	})

	return scene
}

// frame returns the frame for the given time t
// when each frame is displayed for duration d.
func frame(t, d clock.Time, frames ...int) int {
	total := int(d) * len(frames)
	return frames[(int(t)%total)/int(d)]
}

func animateDeadGopher(a *f32.Affine, t clock.Time) {
	dt := float32(t)
	a.Scale(a, 1+dt/20, 1+dt/20)
	a.Translate(a, 0.5, 0.5)
	a.Rotate(a, dt/math.Pi/-8)
	a.Translate(a, -0.5, -0.5)
}

type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)

func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }

const (
	texGopherRun1 = iota
	texGopherRun2
	texGopherFlap1
	texGopherFlap2
	texGopherDead1
	texGopherDead2
	texGround1
	texGround2
	texGround3
	texGround4
	texEarth
)

func randomGroundTexture() int {
	return texGround1 + rand.Intn(4)
}

func loadTextures(eng sprite.Engine) []sprite.SubTex {
	a, err := asset.Open("sprite.png")
	if err != nil {
		log.Fatal(err)
	}
	defer a.Close()

	m, _, err := image.Decode(a)
	if err != nil {
		log.Fatal(err)
	}
	t, err := eng.LoadTexture(m)
	if err != nil {
		log.Fatal(err)
	}

	const n = 128
	// TODO(adg,nigeltao): remove +1's and -1's below once texture bleed issue is fixed
	return []sprite.SubTex{
		texGopherRun1:  sprite.SubTex{t, image.Rect(n*0, 0, n*1, n)},
		texGopherRun2:  sprite.SubTex{t, image.Rect(n*1, 0, n*2, n)},
		texGopherFlap1: sprite.SubTex{t, image.Rect(n*2, 0, n*3, n)},
		texGopherFlap2: sprite.SubTex{t, image.Rect(n*3, 0, n*4, n)},
		texGopherDead1: sprite.SubTex{t, image.Rect(n*4, 0, n*5, n)},
		texGopherDead2: sprite.SubTex{t, image.Rect(n*5, 0, n*6-1, n)},
		texGround1:     sprite.SubTex{t, image.Rect(n*6+1, 0, n*7-1, n)},
		texGround2:     sprite.SubTex{t, image.Rect(n*7+1, 0, n*8-1, n)},
		texGround3:     sprite.SubTex{t, image.Rect(n*8+1, 0, n*9-1, n)},
		texGround4:     sprite.SubTex{t, image.Rect(n*9+1, 0, n*10-1, n)},
		texEarth:       sprite.SubTex{t, image.Rect(n*10+1, 0, n*11-1, n)},
	}
}

func (g *Game) Press(down bool) {
	if g.gopher.dead {
		// Player can't control a dead gopher.
		return
	}

	if down {
		switch {
		case g.gopher.atRest:
			// Gopher may jump from the ground.
			g.gopher.v = jumpV
		case !g.gopher.flapped:
			// Gopher may flap once in mid-air.
			g.gopher.flapped = true
			g.gopher.v = flapV
		}
	} else {
		// Stop gopher rising on button release.
		if g.gopher.v < 0 {
			g.gopher.v = 0
		}
	}
}

func (g *Game) Update(now clock.Time) {
	if g.gopher.dead && now-g.gopher.deadTime > deadTimeBeforeReset {
		// Restart if the gopher has been dead for a while.
		//g.reset()
	}

	// Compute game states up to now.
	for ; g.lastCalc < now; g.lastCalc++ {
		g.calcFrame()
	}
}

func (g *Game) calcFrame() {
	g.calcScroll()
	g.calcGopher()
}

func (g *Game) calcScroll() {
	// Compute velocity.
	if g.gopher.dead {
		// Decrease scroll speed when the gopher dies.
		g.scroll.v += deadScrollA
		if g.scroll.v < 0 {
			g.scroll.v = 0
		}
	} else {
		// Increase scroll speed.
		g.scroll.v += scrollA
	}

	// Compute offset.
	g.scroll.x += g.scroll.v

	// Create new ground tiles if we need to.
	for g.scroll.x > tileWidth {
		g.newGroundTile()

		// Check whether the gopher has crashed.
		// Do this for each new ground tile so that when the scroll
		// velocity is >tileWidth/frame it can't pass through the ground.
		if !g.gopher.dead && g.gopherCrashed() {
			g.killGopher()
		}
	}
}

func (g *Game) calcGopher() {
	// Compute velocity.
	g.gopher.v += gravity

	// Compute offset.
	g.gopher.y += g.gopher.v

	g.clampToGround()
}

func (g *Game) newGroundTile() {
	// Compute next ground y-offset.
	next := g.nextGroundY()
	nextTex := randomGroundTexture()

	// Shift ground tiles to the left.
	g.scroll.x -= tileWidth
	copy(g.groundY[:], g.groundY[1:])
	copy(g.groundTex[:], g.groundTex[1:])
	last := len(g.groundY) - 1
	g.groundY[last] = next
	g.groundTex[last] = nextTex
}

func (g *Game) nextGroundY() float32 {
	prev := g.groundY[len(g.groundY)-1]
	if change := rand.Intn(groundChangeProb) == 0; change {
		return (groundMax-groundMin)*rand.Float32() + groundMin
	}
	if wobble := rand.Intn(groundWobbleProb) == 0; wobble {
		return prev + (rand.Float32()-0.5)*climbGrace
	}
	return prev
}

func (g *Game) gopherCrashed() bool {
	return g.gopher.y+tileHeight-climbGrace > g.groundY[gopherTile+1]
}

func (g *Game) killGopher() {
	g.gopher.dead = true
	g.gopher.deadTime = g.lastCalc
	g.gopher.v = jumpV * 1.5 // Bounce off screen.
}

func (g *Game) clampToGround() {
	if g.gopher.dead {
		// Allow the gopher to fall through ground when dead.
		return
	}

	// Compute the minimum offset of the ground beneath the gopher.
	minY := g.groundY[gopherTile]
	if y := g.groundY[gopherTile+1]; y < minY {
		minY = y
	}

	// Prevent the gopher from falling through the ground.
	maxGopherY := minY - tileHeight
	g.gopher.atRest = false
	if g.gopher.y >= maxGopherY {
		g.gopher.v = 0
		g.gopher.y = maxGopherY
		g.gopher.atRest = true
		g.gopher.flapped = false
	}
}
<commit_msg>example/flappy: document texture issue, re-enable reset<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin linux

package main

import (
	"image"
	"log"
	"math"
	"math/rand"

	_ "image/png"

	"golang.org/x/mobile/asset"
	"golang.org/x/mobile/exp/f32"
	"golang.org/x/mobile/exp/sprite"
	"golang.org/x/mobile/exp/sprite/clock"
)

const (
	tileWidth, tileHeight = 16, 16 // width and height of each tile
	tilesX, tilesY        = 16, 16 // number of horizontal tiles

	gopherTile = 1 // which tile the gopher is standing on (0-indexed)

	initScrollV = 1     // initial scroll velocity
	scrollA     = 0.001 // scroll accelleration
	gravity     = 0.1   // gravity
	jumpV       = -5    // jump velocity
	flapV       = -1.5  // flap velocity

	deadScrollA         = -0.01 // scroll deceleration after the gopher dies
	deadTimeBeforeReset = 240   // how long to wait before restarting the game

	groundChangeProb = 5 // 1/probability of ground height change
	groundWobbleProb = 3 // 1/probability of minor ground height change
	groundMin        = tileHeight * (tilesY - 2*tilesY/5)
	groundMax        = tileHeight * tilesY
	initGroundY      = tileHeight * (tilesY - 1)

	climbGrace = tileHeight / 3 // gopher won't die if it hits a cliff this high
)

type Game struct {
	gopher struct {
		y        float32    // y-offset
		v        float32    // velocity
		atRest   bool       // is the gopher on the ground?
		flapped  bool       // has the gopher flapped since it became airborne?
		dead     bool       // is the gopher dead?
		deadTime clock.Time // when the gopher died
	}
	scroll struct {
		x float32 // x-offset
		v float32 // velocity
	}
	groundY   [tilesX + 3]float32 // ground y-offsets
	groundTex [tilesX + 3]int     // ground texture
	lastCalc  clock.Time          // when we last calculated a frame
}

func NewGame() *Game {
	var g Game
	g.reset()
	return &g
}

func (g *Game) reset() {
	g.gopher.y = 0
	g.gopher.v = 0
	g.scroll.x = 0
	g.scroll.v = initScrollV
	for i := range g.groundY {
		g.groundY[i] = initGroundY
		g.groundTex[i] = randomGroundTexture()
	}
	g.gopher.atRest = false
	g.gopher.flapped = false
	g.gopher.dead = false
	g.gopher.deadTime = 0
}

func (g *Game) Scene(eng sprite.Engine) *sprite.Node {
	texs := loadTextures(eng)

	scene := &sprite.Node{}
	eng.Register(scene)
	eng.SetTransform(scene, f32.Affine{
		{1, 0, 0},
		{0, 1, 0},
	})

	newNode := func(fn arrangerFunc) {
		n := &sprite.Node{Arranger: arrangerFunc(fn)}
		eng.Register(n)
		scene.AppendChild(n)
	}

	// The ground.
	for i := range g.groundY {
		i := i
		// The top of the ground.
		newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
			eng.SetSubTex(n, texs[g.groundTex[i]])
			eng.SetTransform(n, f32.Affine{
				{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
				{0, tileHeight, g.groundY[i]},
			})
		})
		// The earth beneath.
		newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
			eng.SetSubTex(n, texs[texEarth])
			eng.SetTransform(n, f32.Affine{
				{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
				{0, tileHeight * tilesY, g.groundY[i] + tileHeight},
			})
		})
	}

	// The gopher.
	newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
		a := f32.Affine{
			{tileWidth * 2, 0, tileWidth*(gopherTile-1) + tileWidth/8},
			{0, tileHeight * 2, g.gopher.y - tileHeight + tileHeight/4},
		}
		var x int
		switch {
		case g.gopher.dead:
			x = frame(t, 16, texGopherDead1, texGopherDead2)
			animateDeadGopher(&a, t-g.gopher.deadTime)
		case g.gopher.v < 0:
			x = frame(t, 4, texGopherFlap1, texGopherFlap2)
		case g.gopher.atRest:
			x = frame(t, 4, texGopherRun1, texGopherRun2)
		default:
			x = frame(t, 8, texGopherRun1, texGopherRun2)
		}
		eng.SetSubTex(n, texs[x])
		eng.SetTransform(n, a)
	})

	return scene
}

// frame returns the frame for the given time t
// when each frame is displayed for duration d.
func frame(t, d clock.Time, frames ...int) int {
	total := int(d) * len(frames)
	return frames[(int(t)%total)/int(d)]
}

func animateDeadGopher(a *f32.Affine, t clock.Time) {
	dt := float32(t)
	a.Scale(a, 1+dt/20, 1+dt/20)
	a.Translate(a, 0.5, 0.5)
	a.Rotate(a, dt/math.Pi/-8)
	a.Translate(a, -0.5, -0.5)
}

type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)

func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }

const (
	texGopherRun1 = iota
	texGopherRun2
	texGopherFlap1
	texGopherFlap2
	texGopherDead1
	texGopherDead2
	texGround1
	texGround2
	texGround3
	texGround4
	texEarth
)

func randomGroundTexture() int {
	return texGround1 + rand.Intn(4)
}

func loadTextures(eng sprite.Engine) []sprite.SubTex {
	a, err := asset.Open("sprite.png")
	if err != nil {
		log.Fatal(err)
	}
	defer a.Close()

	m, _, err := image.Decode(a)
	if err != nil {
		log.Fatal(err)
	}
	t, err := eng.LoadTexture(m)
	if err != nil {
		log.Fatal(err)
	}

	const n = 128
	// The +1's and -1's in the rectangles below are to prevent colors from
	// adjacent textures leaking into a given texture.
	// See: http://stackoverflow.com/questions/19611745/opengl-black-lines-in-between-tiles
	return []sprite.SubTex{
		texGopherRun1:  sprite.SubTex{t, image.Rect(n*0+1, 0, n*1-1, n)},
		texGopherRun2:  sprite.SubTex{t, image.Rect(n*1+1, 0, n*2-1, n)},
		texGopherFlap1: sprite.SubTex{t, image.Rect(n*2+1, 0, n*3-1, n)},
		texGopherFlap2: sprite.SubTex{t, image.Rect(n*3+1, 0, n*4-1, n)},
		texGopherDead1: sprite.SubTex{t, image.Rect(n*4+1, 0, n*5-1, n)},
		texGopherDead2: sprite.SubTex{t, image.Rect(n*5+1, 0, n*6-1, n)},
		texGround1:     sprite.SubTex{t, image.Rect(n*6+1, 0, n*7-1, n)},
		texGround2:     sprite.SubTex{t, image.Rect(n*7+1, 0, n*8-1, n)},
		texGround3:     sprite.SubTex{t, image.Rect(n*8+1, 0, n*9-1, n)},
		texGround4:     sprite.SubTex{t, image.Rect(n*9+1, 0, n*10-1, n)},
		texEarth:       sprite.SubTex{t, image.Rect(n*10+1, 0, n*11-1, n)},
	}
}

func (g *Game) Press(down bool) {
	if g.gopher.dead {
		// Player can't control a dead gopher.
		return
	}

	if down {
		switch {
		case g.gopher.atRest:
			// Gopher may jump from the ground.
			g.gopher.v = jumpV
		case !g.gopher.flapped:
			// Gopher may flap once in mid-air.
			g.gopher.flapped = true
			g.gopher.v = flapV
		}
	} else {
		// Stop gopher rising on button release.
		if g.gopher.v < 0 {
			g.gopher.v = 0
		}
	}
}

func (g *Game) Update(now clock.Time) {
	if g.gopher.dead && now-g.gopher.deadTime > deadTimeBeforeReset {
		// Restart if the gopher has been dead for a while.
		g.reset()
	}

	// Compute game states up to now.
	for ; g.lastCalc < now; g.lastCalc++ {
		g.calcFrame()
	}
}

func (g *Game) calcFrame() {
	g.calcScroll()
	g.calcGopher()
}

func (g *Game) calcScroll() {
	// Compute velocity.
	if g.gopher.dead {
		// Decrease scroll speed when the gopher dies.
		g.scroll.v += deadScrollA
		if g.scroll.v < 0 {
			g.scroll.v = 0
		}
	} else {
		// Increase scroll speed.
		g.scroll.v += scrollA
	}

	// Compute offset.
	g.scroll.x += g.scroll.v

	// Create new ground tiles if we need to.
	for g.scroll.x > tileWidth {
		g.newGroundTile()

		// Check whether the gopher has crashed.
		// Do this for each new ground tile so that when the scroll
		// velocity is >tileWidth/frame it can't pass through the ground.
		if !g.gopher.dead && g.gopherCrashed() {
			g.killGopher()
		}
	}
}

func (g *Game) calcGopher() {
	// Compute velocity.
	g.gopher.v += gravity

	// Compute offset.
	g.gopher.y += g.gopher.v

	g.clampToGround()
}

func (g *Game) newGroundTile() {
	// Compute next ground y-offset.
	next := g.nextGroundY()
	nextTex := randomGroundTexture()

	// Shift ground tiles to the left.
	g.scroll.x -= tileWidth
	copy(g.groundY[:], g.groundY[1:])
	copy(g.groundTex[:], g.groundTex[1:])
	last := len(g.groundY) - 1
	g.groundY[last] = next
	g.groundTex[last] = nextTex
}

func (g *Game) nextGroundY() float32 {
	prev := g.groundY[len(g.groundY)-1]
	if change := rand.Intn(groundChangeProb) == 0; change {
		return (groundMax-groundMin)*rand.Float32() + groundMin
	}
	if wobble := rand.Intn(groundWobbleProb) == 0; wobble {
		return prev + (rand.Float32()-0.5)*climbGrace
	}
	return prev
}

func (g *Game) gopherCrashed() bool {
	return g.gopher.y+tileHeight-climbGrace > g.groundY[gopherTile+1]
}

func (g *Game) killGopher() {
	g.gopher.dead = true
	g.gopher.deadTime = g.lastCalc
	g.gopher.v = jumpV * 1.5 // Bounce off screen.
}

func (g *Game) clampToGround() {
	if g.gopher.dead {
		// Allow the gopher to fall through ground when dead.
		return
	}

	// Compute the minimum offset of the ground beneath the gopher.
	minY := g.groundY[gopherTile]
	if y := g.groundY[gopherTile+1]; y < minY {
		minY = y
	}

	// Prevent the gopher from falling through the ground.
	maxGopherY := minY - tileHeight
	g.gopher.atRest = false
	if g.gopher.y >= maxGopherY {
		g.gopher.v = 0
		g.gopher.y = maxGopherY
		g.gopher.atRest = true
		g.gopher.flapped = false
	}
}
<|endoftext|>


<commit_before>package models

import (
	"fmt"
	"encoding/json"
	"strings"
	"time"
)

/*
GenericFile contains information about a file that makes up
part (or all) of an IntellectualObject.

IntellectualObject is the object to which the file belongs.

Format is typically a mime-type, such as "application/xml",
that describes the file format.

URI describes the location of the object (in APTrust?).

Size is the size of the object, in bytes.

FileCreated is the date and time at which the file was created
by the depositor.

FileModified is the data and time at which the object was last
modified (in APTrust, or at the institution that owns it?).

CreatedAt and UpdatedAt are Rails timestamps describing when
this GenericFile records was created and last updated.

FileCreated and FileModified should be ISO8601 DateTime strings,
such as:
1994-11-05T08:15:30-05:00     (Local Time)
1994-11-05T08:15:30Z          (UTC)
*/
type GenericFile struct {
	// Pharos fields.
	// If the Id is non-zero, it's been recorded in Pharos.
	Id                           int            `json:"id"`
	Identifier                   string         `json:"identifier"`
	IntellectualObjectId         int            `json:"intellectual_object_id"`
	IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`
	FileFormat                   string         `json:"file_format"`
	URI                          string         `json:"uri"`
	Size                         int64          `json:"size"`
	FileCreated                  time.Time      `json:"file_created"`
	FileModified                 time.Time      `json:"file_modified"`
	CreatedAt                    time.Time      `json:"created_at"`
	UpdatedAt                    time.Time      `json:"updated_at"`
	Checksums                    []*Checksum    `json:"checksums"`
	PremisEvents                 []*PremisEvent `json:"premis_events"`

	// Exchange fields. These are for internal housekeeping.
	// We don't send this data to Pharos.

	// IngestFileType can be one of the types defined in constants.
	// PAYLOAD_FILE, PAYLOAD_MANIFEST, TAG_MANIFEST, TAG_FILE
	IngestFileType               string         `json:"ingest_file_type"`
	IngestLocalPath              string         `json:"ingest_local_path"`
	IngestMd5                    string         `json:"ingest_md5"`
	IngestMd5GeneratedAt         time.Time      `json:"ingest_md5_generated_at"`
	IngestMd5VerifiedAt          time.Time      `json:"ingest_md5_verified_at"`
	IngestSha256                 string         `json:"ingest_sha_256"`
	IngestSha256GeneratedAt      time.Time      `json:"ingest_sha_256_generated_at"`
	IngestSha256VerifiedAt       time.Time      `json:"ingest_sha_256_verified_at"`
	IngestUUID                   string         `json:"ingest_uuid"`
	IngestUUIDGeneratedAt        time.Time      `json:"ingest_uuid_generated_at"`
	IngestStorageURL             string         `json:"ingest_storage_url"`
	IngestStoredAt               time.Time      `json:"ingest_stored_at"`
	IngestPreviousVersionExists  bool           `json:"ingest_previous_version_exists"`
	IngestNeedsSave              bool           `json:"ingest_needs_save"`
	IngestErrorMessage           string         `json:"ingesterror_message"`

	IngestFileUid                int            `json:"ingest_file_uid"`
	IngestFileGid                int            `json:"ingest_file_gid"`
	IngestFileUname              string         `json:"ingest_file_uname"`
	IngestFileGname              string         `json:"ingest_file_gname"`
	IngestFileMode               int64          `json:"ingest_file_mode"`
}

func NewGenericFile() (*GenericFile) {
	return &GenericFile{
		Checksums: make([]*Checksum, 0),
		PremisEvents: make([]*PremisEvent, 0),
		IngestPreviousVersionExists: false,
		IngestNeedsSave: true,
	}
}

// Serializes a version of GenericFile that Fluctus will accept as post/put input.
// Note that we don't serialize the id or any of our internal housekeeping info.
func (gf *GenericFile) SerializeForPharos() ([]byte, error) {
	// We have to create a temporary structure to prevent json.Marshal
	// from serializing Size (int64) with scientific notation.
	// Without this step, Size will be serialized as something like
	// 2.706525e+06, which is not valid JSON.
	temp := struct{
		Identifier                   string         `json:"identifier"`
		IntellectualObjectId         int            `json:"intellectual_object_id"`
		IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`
		FileFormat                   string         `json:"file_format"`
		URI                          string         `json:"uri"`
		Size                         int64          `json:"size"`
		FileCreated                  time.Time      `json:"file_created"`
		FileModified                 time.Time      `json:"file_modified"`
		CreatedAt                    time.Time      `json:"created_at"`
		UpdatedAt                    time.Time      `json:"updated_at"`
		Checksums                    []*Checksum    `json:"checksums"`
	} {
		Identifier:                     gf.Identifier,
		IntellectualObjectId:           gf.IntellectualObjectId,
		IntellectualObjectIdentifier:   gf.IntellectualObjectIdentifier,
		FileFormat:                     gf.FileFormat,
		URI:                            gf.URI,
		Size:                           gf.Size,
		FileCreated:                    gf.FileCreated,
		FileModified:                   gf.FileModified,
		Checksums:                      gf.Checksums,
	}
	return json.Marshal(temp)
}

// Returns the original path of the file within the original bag.
// This is just the identifier minus the institution id and bag name.
// For example, if the identifier is "uc.edu/cin.675812/data/object.properties",
// this returns "data/object.properties"
func (gf *GenericFile) OriginalPath() (string, error) {
	parts := strings.SplitN(gf.Identifier, "/", 3)
	if len(parts) < 3 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[2], nil
}

// Returns the name of the institution that owns this file.
func (gf *GenericFile) InstitutionIdentifier() (string, error) {
	parts := strings.Split(gf.Identifier, "/")
	if len(parts) < 2 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[0], nil
}

// Returns the checksum digest for the given algorithm for this file.
func (gf *GenericFile) GetChecksum(algorithm string) (*Checksum) {
	for _, cs := range gf.Checksums {
		if cs != nil && cs.Algorithm == algorithm {
			return cs
		}
	}
	return nil
}

// Returns events of the specified type
func (gf *GenericFile) FindEventsByType(eventType string) ([]PremisEvent) {
	events := make([]PremisEvent, 0)
	for _, event := range gf.PremisEvents {
		if event != nil && event.EventType == eventType {
			events = append(events, *event)
		}
	}
	return events
}

// Returns the name of this file in the preservation storage bucket
// (that should be a UUID), or an error if the GenericFile does not
// have a valid preservation storage URL.
func (gf *GenericFile) PreservationStorageFileName() (string, error) {
	if strings.Index(gf.URI, "/") < 0 {
		return "", fmt.Errorf("Cannot get preservation storage file name because GenericFile has an invalid URI")
	}
	parts := strings.Split(gf.URI, "/")
	return parts[len(parts) - 1], nil
}
<commit_msg>Added documentation and ingest properties to GenericFile<commit_after>package models

import (
	"fmt"
	"encoding/json"
	"strings"
	"time"
)

/*
GenericFile contains information about a file that makes up
part (or all) of an IntellectualObject.

IntellectualObject is the object to which the file belongs.

Format is typically a mime-type, such as "application/xml",
that describes the file format.

URI describes the location of the object (in APTrust?).

Size is the size of the object, in bytes.

FileCreated is the date and time at which the file was created
by the depositor.

FileModified is the data and time at which the object was last
modified (in APTrust, or at the institution that owns it?).

CreatedAt and UpdatedAt are Rails timestamps describing when
this GenericFile records was created and last updated.

FileCreated and FileModified should be ISO8601 DateTime strings,
such as:
1994-11-05T08:15:30-05:00     (Local Time)
1994-11-05T08:15:30Z          (UTC)
*/
type GenericFile struct {
	// Pharos fields.

	// The Rails/Database id for this generic file.
	// If the Id is non-zero, it's been recorded in Pharos.
	Id                           int            `json:"id"`

	// The human-readable identifier for this file. It consists of
	// the object name, followed by a slash, followed by the path
	// of the file within the bag. E.g. "virginia.edu/bag001/data/file1.pdf"
	Identifier                   string         `json:"identifier"`

	// The id of the IntellectualObject to which this file belongs.
	IntellectualObjectId         int            `json:"intellectual_object_id"`

	// The identifier of the intellectual object to which this file belongs.
	IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`

	// The file's mime type. E.g. "application/xml"
	FileFormat                   string         `json:"file_format"`

	// The location of this file in our primary s3 long-term storage bucket.
	URI                          string         `json:"uri"`

	// The size of the file, in bytes.
	Size                         int64          `json:"size"`

	// The date this file was created by the depositor. This date comes from
	// the file record in the tarred bag.
	FileCreated                  time.Time      `json:"file_created"`

	// The date this file was last modified by the depository. This date comes
	// from the file record in the tarred bag.
	FileModified                 time.Time      `json:"file_modified"`

	// A timestamp indicating when this GenericFile record was created in
	// our repository.
	CreatedAt                    time.Time      `json:"created_at"`

	// A timestamp indicating when this GenericFile record was last updated in
	// our repository.
	UpdatedAt                    time.Time      `json:"updated_at"`

	// A list of checksums for this file.
	Checksums                    []*Checksum    `json:"checksums"`

	// A list of PREMIS events for this file.
	PremisEvents                 []*PremisEvent `json:"premis_events"`

	// ----------------------------------------------------
	// The fields below are for internal housekeeping.
	// We don't send this data to Pharos.
	// ----------------------------------------------------

	// IngestFileType can be one of the types defined in constants.
	// PAYLOAD_FILE, PAYLOAD_MANIFEST, TAG_MANIFEST, TAG_FILE
	IngestFileType               string         `json:"ingest_file_type"`

	// IngestLocalPath is the absolute path to this file on local disk.
	// It may be empty if we're working with a tar file.
	IngestLocalPath              string         `json:"ingest_local_path"`

	// IngestManifestMd5 is the md5 checksum of this file, as reported
	// in the bag's manifest-md5.txt file. This may be empty if there
	// was no md5 checksum file, or if this generic file wasn't listed
	// in the md5 manifest.
	IngestManifestMd5            string         `json:"ingest_manifest_md5"`

	// The md5 checksum we calculated at ingest from the actual file.
	IngestMd5                    string         `json:"ingest_md5"`

	// DateTime we calculated the md5 digest from local file.
	IngestMd5GeneratedAt         time.Time      `json:"ingest_md5_generated_at"`

	// DateTime we verified that our md5 checksum matches what's in the manifest.
	IngestMd5VerifiedAt          time.Time      `json:"ingest_md5_verified_at"`

	// The sha256 checksum for this file, as reported in the payload manifest.
	// This may be empty if the bag had no sha256 manifest, or if this file
	// was not listed in the manifest.
	IngestManifestSha256         string         `json:"ingest_manifest_sha256"`

	// The sha256 checksum we calculated when we read the actual file.
	IngestSha256                 string         `json:"ingest_sha_256"`

	// Timestamp of when we calculated the sha256 checksum.
	IngestSha256GeneratedAt      time.Time      `json:"ingest_sha_256_generated_at"`

	// Timestamp of when we verified that the sha256 checksum we calculated
	// matches what's in the manifest.
	IngestSha256VerifiedAt       time.Time      `json:"ingest_sha_256_verified_at"`

	// The UUID assigned to this file. This will be its S3 key when we store it.
	IngestUUID                   string         `json:"ingest_uuid"`

	// Timestamp of when we generated the UUID for this file. Needed to create
	// the identifier assignment PREMIS event.
	IngestUUIDGeneratedAt        time.Time      `json:"ingest_uuid_generated_at"`

	// Where this file is stored in S3.
	IngestStorageURL             string         `json:"ingest_storage_url"`

	// Timestamp indicating when this file was stored in S3.
	IngestStoredAt               time.Time      `json:"ingest_stored_at"`

	// Where this file is stored in Glacier.
	IngestReplicationURL         string         `json:"ingest_replication_url"`

	// Timestamp indicating when this file was stored in Glacier.
	IngestReplicatedAt           time.Time      `json:"ingest_replicated_at"`

	// If true, a previous version of this same file exists in S3/Glacier.
	IngestPreviousVersionExists  bool           `json:"ingest_previous_version_exists"`

	// If true, this file needs to be saved to S3.
	IngestNeedsSave              bool           `json:"ingest_needs_save"`

	// Error that occurred during ingest. If empty, there was no error.
	IngestErrorMessage           string         `json:"ingesterror_message"`

	// File User Id (unreliable)
	IngestFileUid                int            `json:"ingest_file_uid"`

	// File Group Id (unreliable)
	IngestFileGid                int            `json:"ingest_file_gid"`

	// File User Name (unreliable)
	IngestFileUname              string         `json:"ingest_file_uname"`

	// File Group Name (unreliable)
	IngestFileGname              string         `json:"ingest_file_gname"`

	// File Mode/Permissions (unreliable)
	IngestFileMode               int64          `json:"ingest_file_mode"`
}

func NewGenericFile() (*GenericFile) {
	return &GenericFile{
		Checksums: make([]*Checksum, 0),
		PremisEvents: make([]*PremisEvent, 0),
		IngestPreviousVersionExists: false,
		IngestNeedsSave: true,
	}
}

// Serializes a version of GenericFile that Fluctus will accept as post/put input.
// Note that we don't serialize the id or any of our internal housekeeping info.
func (gf *GenericFile) SerializeForPharos() ([]byte, error) {
	// We have to create a temporary structure to prevent json.Marshal
	// from serializing Size (int64) with scientific notation.
	// Without this step, Size will be serialized as something like
	// 2.706525e+06, which is not valid JSON.
	temp := struct{
		Identifier                   string         `json:"identifier"`
		IntellectualObjectId         int            `json:"intellectual_object_id"`
		IntellectualObjectIdentifier string         `json:"intellectual_object_identifier"`
		FileFormat                   string         `json:"file_format"`
		URI                          string         `json:"uri"`
		Size                         int64          `json:"size"`
		FileCreated                  time.Time      `json:"file_created"`
		FileModified                 time.Time      `json:"file_modified"`
		CreatedAt                    time.Time      `json:"created_at"`
		UpdatedAt                    time.Time      `json:"updated_at"`
		Checksums                    []*Checksum    `json:"checksums"`
	} {
		Identifier:                     gf.Identifier,
		IntellectualObjectId:           gf.IntellectualObjectId,
		IntellectualObjectIdentifier:   gf.IntellectualObjectIdentifier,
		FileFormat:                     gf.FileFormat,
		URI:                            gf.URI,
		Size:                           gf.Size,
		FileCreated:                    gf.FileCreated,
		FileModified:                   gf.FileModified,
		Checksums:                      gf.Checksums,
	}
	return json.Marshal(temp)
}

// Returns the original path of the file within the original bag.
// This is just the identifier minus the institution id and bag name.
// For example, if the identifier is "uc.edu/cin.675812/data/object.properties",
// this returns "data/object.properties"
func (gf *GenericFile) OriginalPath() (string, error) {
	parts := strings.SplitN(gf.Identifier, "/", 3)
	if len(parts) < 3 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[2], nil
}

// Returns the name of the institution that owns this file.
func (gf *GenericFile) InstitutionIdentifier() (string, error) {
	parts := strings.Split(gf.Identifier, "/")
	if len(parts) < 2 {
		return "", fmt.Errorf("GenericFile identifier '%s' is not valid", gf.Identifier)
	}
	return parts[0], nil
}

// Returns the checksum digest for the given algorithm for this file.
func (gf *GenericFile) GetChecksum(algorithm string) (*Checksum) {
	for _, cs := range gf.Checksums {
		if cs != nil && cs.Algorithm == algorithm {
			return cs
		}
	}
	return nil
}

// Returns events of the specified type
func (gf *GenericFile) FindEventsByType(eventType string) ([]PremisEvent) {
	events := make([]PremisEvent, 0)
	for _, event := range gf.PremisEvents {
		if event != nil && event.EventType == eventType {
			events = append(events, *event)
		}
	}
	return events
}

// Returns the name of this file in the preservation storage bucket
// (that should be a UUID), or an error if the GenericFile does not
// have a valid preservation storage URL.
func (gf *GenericFile) PreservationStorageFileName() (string, error) {
	if strings.Index(gf.URI, "/") < 0 {
		return "", fmt.Errorf("Cannot get preservation storage file name because GenericFile has an invalid URI")
	}
	parts := strings.Split(gf.URI, "/")
	return parts[len(parts) - 1], nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"

	"github.com/nbari/violetear"
)

func stream(w http.ResponseWriter, r *http.Request) {
	cmd := exec.Command("python", "game.py")
	rPipe, wPipe, err := os.Pipe()
	if err != nil {
		log.Fatal(err)
	}
	cmd.Stdout = wPipe
	cmd.Stderr = wPipe
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	go writeOutput(w, rPipe)
	cmd.Wait()
}

func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming not supported", http.StatusInternalServerError)
		return
	}

	// Immportant to make it work in browsers
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	in := bufio.NewScanner(input)
	for in.Scan() {
		fmt.Fprintf(w, "data: %s\n", in.Text())
		flusher.Flush()
	}
	input.Close()
}

func main() {
	router := violetear.New()
	router.HandleFunc("/", stream, "GET")
	log.Fatal(http.ListenAndServe(":8080", router))
}
<commit_msg>sync Wed Apr 11 10:40:32 CEST 2018<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"

	"github.com/nbari/violetear"
)

func stream(w http.ResponseWriter, r *http.Request) {
	cmd := exec.Command("python", "game.py")
	rPipe, wPipe, err := os.Pipe()
	if err != nil {
		log.Fatal(err)
	}
	cmd.Stdout = wPipe
	cmd.Stderr = wPipe
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	go writeOutput(w, rPipe)
	cmd.Wait()
	wPipe.Close()
}

func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming not supported", http.StatusInternalServerError)
		return
	}

	// Immportant to make it work in browsers
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	in := bufio.NewScanner(input)
	for in.Scan() {
		fmt.Fprintf(w, "data: %s\n", in.Text())
		flusher.Flush()
	}
	input.Close()
}

func main() {
	router := violetear.New()
	router.HandleFunc("/", stream, "GET")
	log.Fatal(http.ListenAndServe(":8080", router))
}
<|endoftext|>


<commit_before>package context

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"os"
	"sort"
	"strconv"
	"time"

	"github.com/wmiller848/GoGP/gene"
	"github.com/wmiller848/GoGP/program"
	"github.com/wmiller848/GoGP/util"
)

type ScoreFunction func(int) int

type ProgramInstance struct {
	*program.Program
	ID         string
	Generation int
	Score      float64
}

type Programs []*ProgramInstance

func (p Programs) Len() int           { return len(p) }
func (p Programs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Programs) Less(i, j int) bool { return p[i].Score < p[j].Score }

type Context struct {
	Population  int
	Programs    Programs
	VerboseMode bool
}

func New() *Context {
	return &Context{}
}

func (c *Context) Verbose() {
	c.VerboseMode = !c.VerboseMode
}

func (c *Context) RunWithInlineScore(pipe io.Reader, threshold, score float64, inputs, population, generations int, auto bool) (string, *ProgramInstance) {
	//os.Mkdir("./out", 0777)
	uuid := util.RandomHex(32)
	//os.Mkdir("./out/generations", 0777)
	//os.RemoveAll("./out/generations/" + uuid)
	//os.Mkdir("./out/generations/"+uuid, 0777)
	c.InitPopulation(inputs, population)
	var i int = 0
	time.Sleep(500 * time.Millisecond)
	fountain := Multiplex(pipe)
	for {
		if i >= generations && !auto {
			break
		}

		parents := c.EvalInline(fountain, i, inputs, threshold, uuid)

		children := []*ProgramInstance{}
		if len(parents) > 0 && i != generations-1 {
			for i := 0; i < c.Population-len(parents); i++ {
				pgm := &ProgramInstance{
					Program:    parents[i%len(parents)].Mutate(),
					ID:         util.RandomHex(16),
					Generation: i + 1,
					Score:      math.MaxFloat64,
				}
				children = append(children, pgm)
			}
			c.Programs = append(parents, children...)
			prgm := c.Fitest()
			if c.VerboseMode {
				//fmt.Printf(".")
				fmt.Printf("\rScore - %3.2f Generation %v", (1.0-prgm.Score)*100.0, i)
			}
			if prgm != nil && (1.0-prgm.Score) > score {
				break
			}
		}
		i++
	}
	fountain.Destroy()
	if c.VerboseMode {
		fmt.Printf("\n")
	}
	return uuid, c.Fitest()
}

func (c *Context) EvalInline(fountain *Multiplexer, generation, inputs int, threshold float64, uuid string) Programs {
	path := "./out/generations/" + uuid + "/" + strconv.Itoa(generation)
	os.Mkdir(path, 0777)

	//		* Each testBuf row ->
	//			* compute average score
	validPrograms := 0
	tap := fountain.Multiplex().Tap()
	var data []byte
	for {
		d, open := <-tap
		if open == false {
			break
		}
		data = append(data, d...)
	}
	for i, _ := range c.Programs {
		prgm := c.Programs[i]
		gns, _ := prgm.DNA.MarshalGenes()
		mathGns := gene.MathGene(gns).Heal()
		tree, _ := mathGns.MarshalTree()
		if tree == nil {
			continue
		}
		wrong := 0
		lines := bytes.Split(data, []byte("\n"))
		for i, _ := range lines {
			if len(lines[i]) > 0 {
				nums := bytes.Split(lines[i], []byte(" "))
				if len(nums) >= inputs {
					inputFloats := []float64{}
					assertFloat := math.NaN()
					for j, numByts := range nums {
						num, err := strconv.ParseFloat(string(numByts), 64)
						if err == nil {
							if j < inputs {
								inputFloats = append(inputFloats, num)
							} else {
								assertFloat = num
							}
						}
					}
					out := tree.Eval(inputFloats...)
					diff := math.Abs(out - assertFloat)
					//fmt.Println(prgm.ID, inputFloats, out, assertFloat, diff)
					if diff >= threshold || math.IsNaN(out) {
						wrong++
					}
				}
			}
		}
		prgm.Score = float64(wrong) / float64(len(lines))
		validPrograms++
	}

	sort.Sort(c.Programs)
	// Top 30%
	limit := validPrograms / 3
	variance := limit / 3
	parents := make(Programs, limit+variance)
	for i := 0; i < limit; i++ {
		parents[i] = c.Programs[i]
	}
	for i := limit; i < limit+variance; i++ {
		pgm := &ProgramInstance{
			Program:    program.New(inputs),
			ID:         util.RandomHex(16),
			Generation: generation,
			Score:      math.MaxFloat64,
		}
		parents[i] = pgm
	}
	return parents
}

func (c *Context) Fitest() *ProgramInstance {
	//if c.VerboseMode {
	//for i, _ := range c.Programs {
	//gn, err := c.Programs[i].DNA.MarshalGenes()
	//if err != nil {
	//fmt.Println(err.Error())
	//continue
	//}
	//d := gene.MathGene(gn)
	//fmt.Println("Program", c.Programs[i].ID, c.Programs[i].Score, string(d.Heal()))
	//}
	//}
	if len(c.Programs) > 0 {
		sort.Sort(c.Programs)
		return c.Programs[0]
	} else {
		return nil
	}
}

func (c *Context) InitPopulation(inputs, population int) {
	c.Population = population
	c.Programs = make(Programs, population)
	var i int
	for i = 0; i < population; i++ {
		pgm := &ProgramInstance{
			Program:    program.New(inputs),
			ID:         util.RandomHex(16),
			Generation: 0,
			Score:      math.MaxFloat64,
		}
		c.Programs[i] = pgm
	}
}
<commit_msg>fix old stuff<commit_after>package context

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"sort"
	"strconv"
	"time"

	"github.com/wmiller848/GoGP/gene"
	"github.com/wmiller848/GoGP/program"
	"github.com/wmiller848/GoGP/util"
)

type ScoreFunction func(int) int

type ProgramInstance struct {
	*program.Program
	ID         string
	Generation int
	Score      float64
}

type Programs []*ProgramInstance

func (p Programs) Len() int           { return len(p) }
func (p Programs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Programs) Less(i, j int) bool { return p[i].Score < p[j].Score }

type Context struct {
	Population  int
	Programs    Programs
	VerboseMode bool
}

func New() *Context {
	return &Context{}
}

func (c *Context) Verbose() {
	c.VerboseMode = !c.VerboseMode
}

func (c *Context) RunWithInlineScore(pipe io.Reader, threshold, score float64, inputs, population, generations int, auto bool) (string, *ProgramInstance) {
	//os.Mkdir("./out", 0777)
	uuid := util.RandomHex(32)
	//os.Mkdir("./out/generations", 0777)
	//os.RemoveAll("./out/generations/" + uuid)
	//os.Mkdir("./out/generations/"+uuid, 0777)
	c.InitPopulation(inputs, population)
	var i int = 0
	time.Sleep(500 * time.Millisecond)
	fountain := Multiplex(pipe)
	for {
		if i >= generations && !auto {
			break
		}

		parents := c.EvalInline(fountain, i, inputs, threshold, uuid)

		children := []*ProgramInstance{}
		if len(parents) > 0 && i != generations-1 {
			for i := 0; i < c.Population-len(parents); i++ {
				pgm := &ProgramInstance{
					Program:    parents[i%len(parents)].Mutate(),
					ID:         util.RandomHex(16),
					Generation: i + 1,
					Score:      math.MaxFloat64,
				}
				children = append(children, pgm)
			}
			c.Programs = append(parents, children...)
			prgm := c.Fitest()
			if c.VerboseMode {
				//fmt.Printf(".")
				fmt.Printf("\rScore - %3.2f Generation %v", (1.0-prgm.Score)*100.0, i)
			}
			if prgm != nil && (1.0-prgm.Score) > score {
				break
			}
		}
		i++
	}
	fountain.Destroy()
	if c.VerboseMode {
		fmt.Printf("\n")
	}
	return uuid, c.Fitest()
}

func (c *Context) EvalInline(fountain *Multiplexer, generation, inputs int, threshold float64, uuid string) Programs {
	//path := "./out/generations/" + uuid + "/" + strconv.Itoa(generation)
	//os.Mkdir(path, 0777)

	//		* Each testBuf row ->
	//			* compute average score
	validPrograms := 0
	tap := fountain.Multiplex().Tap()
	var data []byte
	for {
		d, open := <-tap
		if open == false {
			break
		}
		data = append(data, d...)
	}
	lines := bytes.Split(data, []byte("\n"))
	for i, _ := range c.Programs {
		prgm := c.Programs[i]
		gns, _ := prgm.DNA.MarshalGenes()
		mathGns := gene.MathGene(gns).Heal()
		tree, _ := mathGns.MarshalTree()
		if tree == nil {
			continue
		}
		wrong := 0
		for i, _ := range lines {
			if len(lines[i]) > 0 {
				nums := bytes.Split(lines[i], []byte(" "))
				if len(nums) >= inputs {
					inputFloats := []float64{}
					assertFloat := math.NaN()
					for j, numByts := range nums {
						num, err := strconv.ParseFloat(string(numByts), 64)
						if err == nil {
							if j < inputs {
								inputFloats = append(inputFloats, num)
							} else {
								assertFloat = num
							}
						}
					}
					out := tree.Eval(inputFloats...)
					diff := math.Abs(out - assertFloat)
					//fmt.Println(prgm.ID, inputFloats, out, assertFloat, diff)
					if diff >= threshold || math.IsNaN(out) {
						wrong++
					}
				}
			}
		}
		prgm.Score = float64(wrong) / float64(len(lines))
		validPrograms++
	}

	sort.Sort(c.Programs)
	// Top 30%
	limit := validPrograms / 3
	variance := limit / 3
	parents := make(Programs, limit+variance)
	for i := 0; i < limit; i++ {
		parents[i] = c.Programs[i]
	}
	for i := limit; i < limit+variance; i++ {
		pgm := &ProgramInstance{
			Program:    program.New(inputs),
			ID:         util.RandomHex(16),
			Generation: generation,
			Score:      math.MaxFloat64,
		}
		parents[i] = pgm
	}
	return parents
}

func (c *Context) Fitest() *ProgramInstance {
	//if c.VerboseMode {
	//for i, _ := range c.Programs {
	//gn, err := c.Programs[i].DNA.MarshalGenes()
	//if err != nil {
	//fmt.Println(err.Error())
	//continue
	//}
	//d := gene.MathGene(gn)
	//fmt.Println("Program", c.Programs[i].ID, c.Programs[i].Score, string(d.Heal()))
	//}
	//}
	if len(c.Programs) > 0 {
		sort.Sort(c.Programs)
		return c.Programs[0]
	} else {
		return nil
	}
}

func (c *Context) InitPopulation(inputs, population int) {
	c.Population = population
	c.Programs = make(Programs, population)
	var i int
	for i = 0; i < population; i++ {
		pgm := &ProgramInstance{
			Program:    program.New(inputs),
			ID:         util.RandomHex(16),
			Generation: 0,
			Score:      math.MaxFloat64,
		}
		c.Programs[i] = pgm
	}
}
<|endoftext|>