Skip to content

Commit

Permalink
Add scripting
Browse files Browse the repository at this point in the history
  • Loading branch information
elgopher committed Aug 6, 2023
1 parent 0f0e765 commit fbe0232
Show file tree
Hide file tree
Showing 31 changed files with 1,310 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
go: [ 1.18 ]
go: [ 1.20 ]
env:
DISPLAY: ':99.0'
steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Pi is under development. Only limited functionality is provided. API is not stab
## How to get started?

1. Install dependencies
* [Go 1.18+](https://go.dev/dl/)
* [Go 1.20+](https://go.dev/dl/)
* If not on Windows, please install additional dependencies for [Linux](docs/install-linux.md) or [macOS](docs/install-macos.md).
2. Try examples from [examples](examples) directory.
3. Create a new game using provided [Github template](https://github.com/elgopher/pi-template).
Expand Down
6 changes: 6 additions & 0 deletions devtools/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package devtools

import (
"fmt"

"github.com/elgopher/pi"
"github.com/elgopher/pi/devtools/internal/snapshot"
"github.com/elgopher/pi/devtools/internal/terminal"
)

var (
Expand All @@ -17,10 +20,13 @@ func pauseGame() {
gamePaused = true
timeWhenPaused = pi.TimeSeconds
snapshot.Take()
terminal.StartReadingCommands()
}

func resumeGame() {
gamePaused = false
pi.TimeSeconds = timeWhenPaused
snapshot.Draw()
fmt.Println("Game resumed")
terminal.StopReadingCommandsFromStdin()
}
2 changes: 1 addition & 1 deletion devtools/devtools.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func MustRun(runBackend func() error) {
}

inspector.BgColor, inspector.FgColor = BgColor, FgColor
fmt.Println("Press F12 to pause the game and show devtools.")
fmt.Println("Press F12 in the game window to pause the game and activate devtools with terminal.")

pi.Update = func() {
updateDevTools()
Expand Down
2 changes: 1 addition & 1 deletion devtools/internal/inspector/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (m *Measure) Update() {
case pi.MouseBtnp(pi.MouseLeft) && !distance.measuring:
distance.measuring = true
distance.startX, distance.startY = x, y
fmt.Printf("Measuring started at (%d, %d)\n", x, y)
fmt.Printf("\nMeasuring started at (%d, %d)\n", x, y)
case !pi.MouseBtn(pi.MouseLeft) && distance.measuring:
distance.measuring = false
dist, width, height := calcDistance()
Expand Down
5 changes: 3 additions & 2 deletions devtools/internal/inspector/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ var helpShown bool
func Update() {
if !helpShown {
helpShown = true
fmt.Println("Press right mouse button to show toolbar.")
fmt.Println("Press P to take screenshot.")
fmt.Println("\nPress right mouse button in the game window to show the toolbar.")
fmt.Println("Press P in the game window to take screenshot.")
fmt.Println("Terminal activated.")
}

if !toolbar.visible {
Expand Down
199 changes: 199 additions & 0 deletions devtools/internal/interpreter/interpreter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package interpreter

import (
"bufio"
"errors"
"fmt"
"go/build"
"os"
"os/exec"
"reflect"
"runtime"
"strings"

"github.com/traefik/yaegi/interp"

"github.com/elgopher/pi/devtools/internal/interpreter/lib"
"github.com/elgopher/pi/devtools/internal/snapshot"
"github.com/elgopher/pi/devtools/internal/terminal"
)

func ExportFunc(name string, f any) {
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: reflect.ValueOf(f),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting function %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`) // TODO Redeclaration?
if err != nil {
panic(fmt.Sprintf("problem exporting function %s: %s", name, err))
}
}

func ExportVar(name string, v any) {
value := reflect.ValueOf(v)
if value.Kind() != reflect.Ptr {
panic(fmt.Sprintf("ExportVar: you must pass pointer to variable '%s', but you passed %T. Please add '&' in front.", name, v))
}
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: value.Elem(),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting variable %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`)
if err != nil {
panic(fmt.Sprintf("problem exporting variable %s: %s", name, err))
}
}

// TODO Better read name from T
func ExportType[T any](name string) {
var nilValue *T
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: reflect.ValueOf(nilValue),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting type %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`)
if err != nil {
panic(fmt.Sprintf("problem exporting type %s: %s", name, err))
}
}

var interpreter *interp.Interpreter

func init() {
interpreter = interp.New(interp.Options{
GoPath: gopath(), // if GoPath is set then Yaegi does not complain about setting GOPATH.
})
err := interpreter.Use(lib.Symbols)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("problem using Go interpreter symbols: %s", err))
}

_, err = interpreter.Eval(`
import (
"bytes"
"fmt"
"math"
"math/rand"
"sort"
"strconv"
"strings"
"github.com/elgopher/pi"
"github.com/elgopher/pi/font"
"github.com/elgopher/pi/image"
"github.com/elgopher/pi/key"
"github.com/elgopher/pi/snap"
"github.com/elgopher/pi/state"
)
`)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("importing interpreter packages failed: %s", err))
}
}

func gopath() string {
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = build.Default.GOPATH
}
return gopath
}

func EvaluateNextCommand() {
select {
case cmd := <-terminal.Commands:
defer func() {
terminal.CommandExecuted <- struct{}{}
}()
defer func() {
err := recover()
if err != nil {
fmt.Println("panic when running Yaegi", err)
}
}()
if helpCmd := strings.Trim(cmd, " "); strings.HasPrefix(helpCmd, "help ") || helpCmd == "help" {
printHelp(strings.Trim(strings.TrimLeft(helpCmd, "help"), " "))
} else {
runGoCode(cmd)
}
default:
return
}
}

func printHelp(topic string) {
switch topic {
case "":
println("This is interactive terminal. You can write Go code here, which will run immediately in the paused game. You can use all Pi packages and selection of standard packages: bytes, fmt, math, rand, sort, strconv and strings.\n\nType help topic for more information. For example: help pi or help pi.Spr")
default:
if goDoc(topic) {
return
}
}
}

func goDoc(symbol string) bool {
// TODO PREPEND OTHER PACKAGES TOO
if strings.HasPrefix(symbol, "pi.") {
symbol = "github.com/elgopher/" + symbol
}

var args []string
args = append(args, "doc")

isPackageName := !strings.Contains(symbol, ".")
if !isPackageName {
args = append(args, "-all")
}
args = append(args, symbol)
command := exec.Command("go", args...)
command.Stdout = bufio.NewWriter(os.Stdout)
if err := command.Run(); err != nil {
var exitErr *exec.ExitError
if isExitErr := errors.As(err, &exitErr); isExitErr && exitErr.ExitCode() == 1 {
fmt.Println("no help found")
return true
}
fmt.Println("problem getting help:", err)
}
return false
}

func runGoCode(source string) {
res, err := interpreter.Eval(source)
if err != nil {
fmt.Println(err)
}
snapshot.Take()

if res.IsValid() {
if res.Type().Kind() != reflect.Func {
kind := res.Type().Kind()
if kind == reflect.Struct {
structName := res.Type().PkgPath() + "." + res.Type().Name()
structName = structName[strings.LastIndex(structName, "/")+1:]
fmt.Printf("%s: %+v\n", structName, res)
} else {
fmt.Printf("%s: %+v\n", kind, res)
}
} else {
name := runtime.FuncForPC(res.Pointer()).Name()
printHelp(name)
}
}
}
15 changes: 15 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-font.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-image.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fbe0232

Please sign in to comment.