Skip to content

Commit f585b9c

Browse files
committed
Implement 'new' command for clients and servers
1 parent 89cfb98 commit f585b9c

File tree

13 files changed

+746
-2
lines changed

13 files changed

+746
-2
lines changed

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
44
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
55
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
66
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
7-
github.com/code-game-project/codegame-cli v0.4.1 h1:FCZtp/coj+oHdJFvfD6rbtFqttma6GvkORgbhLfTl3A=
8-
github.com/code-game-project/codegame-cli v0.4.1/go.mod h1:oKjILTs/tR+V8adC7hben2t4zXRW+3kzdI1KbTASkBc=
97
github.com/code-game-project/codegame-cli v0.4.2 h1:2h+X0tqMmIOCr3fnGYeLeTxRP9K8ie3XzNAEGl6JRB4=
108
github.com/code-game-project/codegame-cli v0.4.2/go.mod h1:oKjILTs/tR+V8adC7hben2t4zXRW+3kzdI1KbTASkBc=
119
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=

main.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/code-game-project/codegame-cli-go/new/client"
10+
"github.com/code-game-project/codegame-cli-go/new/server"
11+
"github.com/spf13/pflag"
12+
)
13+
14+
func main() {
15+
var gameName string
16+
pflag.StringVar(&gameName, "game-name", "", "The name of the game. (required for clients)")
17+
18+
var url string
19+
pflag.StringVar(&url, "url", "", "The URL of the game. (required for clients)")
20+
21+
var cgVersion string
22+
pflag.StringVar(&cgVersion, "cg-version", "", "The CodeGame protocol version of the game, e.g. 0.6 (required for clients)")
23+
24+
var cgeVersion string
25+
pflag.StringVar(&cgeVersion, "cge-version", "", "The CGE version of the game, e.g. 0.3 (required for clients)")
26+
27+
pflag.Usage = func() {
28+
fmt.Fprintf(os.Stderr, "Usage: %s <command> [...]\n", os.Args[0])
29+
fmt.Fprintln(os.Stderr, "\nCommands:")
30+
fmt.Fprintln(os.Stderr, "\tnew \tCreate a new project.")
31+
fmt.Fprintln(os.Stderr, "\nOptions:")
32+
pflag.PrintDefaults()
33+
}
34+
35+
pflag.Parse()
36+
if pflag.NArg() < 2 {
37+
pflag.Usage()
38+
os.Exit(1)
39+
}
40+
41+
workingDir, err := os.Getwd()
42+
if err != nil {
43+
fmt.Fprintln(os.Stderr, err)
44+
}
45+
projectName := filepath.Base(workingDir)
46+
47+
command := strings.ToLower(pflag.Arg(0))
48+
49+
switch command {
50+
case "new":
51+
err = new(projectName, gameName, url, cgVersion, cgeVersion)
52+
default:
53+
err = fmt.Errorf("Unknown command: %s\n", command)
54+
}
55+
if err != nil {
56+
fmt.Fprintln(os.Stderr, err)
57+
os.Exit(1)
58+
}
59+
}
60+
61+
func new(projectName, gameName, url, cgVersion, cgeVersion string) error {
62+
projectType := strings.ToLower(pflag.Arg(1))
63+
64+
var err error
65+
switch projectType {
66+
case "client":
67+
err = client.CreateNewClient(projectName, gameName, url, cgVersion, cgeVersion)
68+
case "server":
69+
err = server.CreateNewServer(projectName)
70+
default:
71+
err = fmt.Errorf("Unknown project type: %s\n", projectType)
72+
}
73+
74+
return err
75+
}

new/client/client.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"strings"
7+
8+
_ "embed"
9+
10+
"github.com/code-game-project/codegame-cli-go/util"
11+
"github.com/code-game-project/codegame-cli/cli"
12+
"github.com/code-game-project/codegame-cli/external"
13+
)
14+
15+
//go:embed templates/main.go.tmpl
16+
var goClientMainTemplate string
17+
18+
//go:embed templates/wrappers/main.go.tmpl
19+
var goClientWrapperMainTemplate string
20+
21+
//go:embed templates/wrappers/game.go.tmpl
22+
var goClientWrapperGameTemplate string
23+
24+
//go:embed templates/wrappers/events.go.tmpl
25+
var goClientWrapperEventsTemplate string
26+
27+
func CreateNewClient(projectName, gameName, serverURL, cgVersion, cgeVersion string) error {
28+
module, err := cli.Input("Project module path:")
29+
if err != nil {
30+
return err
31+
}
32+
33+
out, err := external.ExecuteHidden("go", "mod", "init", module)
34+
if err != nil {
35+
if out != "" {
36+
cli.Error(out)
37+
}
38+
return err
39+
}
40+
41+
libraryURL, libraryTag, err := getGoClientLibraryURL(projectName, cgVersion)
42+
if err != nil {
43+
return err
44+
}
45+
46+
cgeMajor, cgeMinor, _, err := external.ParseVersion(cgeVersion)
47+
if err != nil {
48+
return cli.Error(err.Error())
49+
}
50+
51+
wrappers := false
52+
if cgeMajor > 0 || cgeMinor >= 3 {
53+
wrappers, err = cli.YesNo("Do you want to generate helper functions?", true)
54+
if err != nil {
55+
return err
56+
}
57+
}
58+
59+
cli.Begin("Installing correct go-client version...")
60+
out, err = external.ExecuteHidden("go", "get", fmt.Sprintf("%s@%s", libraryURL, libraryTag))
61+
if err != nil {
62+
if out != "" {
63+
cli.Error(out)
64+
}
65+
return err
66+
}
67+
cli.Finish()
68+
69+
cli.Begin("Creating project template...")
70+
err = createGoClientTemplate(libraryTag, projectName, module, gameName, serverURL, libraryURL, cgeVersion, wrappers)
71+
if err != nil {
72+
return err
73+
}
74+
cli.Finish()
75+
76+
cli.Begin("Installing dependencies...")
77+
78+
out, err = external.ExecuteHidden("go", "mod", "tidy")
79+
if err != nil {
80+
if out != "" {
81+
cli.Error(out)
82+
}
83+
return err
84+
}
85+
86+
cli.Finish()
87+
88+
cli.Begin("Organizing imports...")
89+
90+
if !external.IsInstalled("goimports") {
91+
cli.Warn("Failed to organize import statements: 'goimports' is not installed!")
92+
return nil
93+
}
94+
external.ExecuteHidden("goimports", "-w", "main.go")
95+
96+
cli.Finish()
97+
98+
return nil
99+
}
100+
101+
func createGoClientTemplate(libraryTag, projectName, modulePath, gameName, serverURL, libraryURL, cgeVersion string, wrappers bool) error {
102+
if !wrappers {
103+
return execGoClientMainTemplate(projectName, serverURL, libraryURL)
104+
}
105+
106+
return execGoClientWrappersTemplate(projectName, modulePath, gameName, serverURL, libraryURL, cgeVersion)
107+
}
108+
109+
func getGoClientLibraryURL(projectName, cgVersion string) (url string, tag string, err error) {
110+
clientVersion := external.ClientVersionFromCGVersion("code-game-project", "go-client", cgVersion)
111+
112+
if clientVersion == "latest" {
113+
var err error
114+
clientVersion, err = external.LatestGithubTag("code-game-project", "go-client")
115+
if err != nil {
116+
return "", "", err
117+
}
118+
clientVersion = strings.TrimPrefix(strings.Join(strings.Split(clientVersion, ".")[:2], "."), "v")
119+
}
120+
121+
majorVersion := strings.Split(clientVersion, ".")[0]
122+
tag, err = external.GithubTagFromVersion("code-game-project", "go-client", clientVersion)
123+
if err != nil {
124+
return "", "", err
125+
}
126+
path := "github.com/code-game-project/go-client/cg"
127+
if majorVersion != "0" && majorVersion != "1" {
128+
path = fmt.Sprintf("github.com/code-game-project/go-client/v%s/cg", majorVersion)
129+
}
130+
131+
return path, tag, nil
132+
}
133+
134+
func execGoClientMainTemplate(projectName, serverURL, libraryURL string) error {
135+
type data struct {
136+
URL string
137+
LibraryURL string
138+
}
139+
140+
return util.ExecTemplate(goClientMainTemplate, "main.go", data{
141+
URL: serverURL,
142+
LibraryURL: libraryURL,
143+
})
144+
}
145+
146+
func execGoClientWrappersTemplate(projectName, modulePath, gameName, serverURL, libraryURL, cgeVersion string) error {
147+
gamePackageName := strings.ReplaceAll(strings.ReplaceAll(gameName, "-", ""), "_", "")
148+
149+
gameDir := strings.ReplaceAll(strings.ReplaceAll(gameName, "-", ""), "_", "")
150+
151+
eventNames, err := external.GetEventNames(util.BaseURL(serverURL, util.IsSSL(serverURL)), cgeVersion)
152+
if err != nil {
153+
return err
154+
}
155+
156+
type event struct {
157+
Name string
158+
PascalName string
159+
}
160+
161+
events := make([]event, len(eventNames))
162+
for i, e := range eventNames {
163+
pascal := strings.ReplaceAll(e, "_", " ")
164+
pascal = strings.Title(pascal)
165+
pascal = strings.ReplaceAll(pascal, " ", "")
166+
events[i] = event{
167+
Name: e,
168+
PascalName: pascal,
169+
}
170+
}
171+
172+
data := struct {
173+
URL string
174+
LibraryURL string
175+
PackageName string
176+
ModulePath string
177+
Events []event
178+
}{
179+
URL: serverURL,
180+
LibraryURL: libraryURL,
181+
PackageName: gamePackageName,
182+
ModulePath: modulePath,
183+
Events: events,
184+
}
185+
186+
err = util.ExecTemplate(goClientWrapperMainTemplate, filepath.Join("main.go"), data)
187+
if err != nil {
188+
return err
189+
}
190+
191+
err = util.ExecTemplate(goClientWrapperGameTemplate, filepath.Join(gameDir, "game.go"), data)
192+
if err != nil {
193+
return err
194+
}
195+
196+
err = util.ExecTemplate(goClientWrapperEventsTemplate, filepath.Join(gameDir, "events.go"), data)
197+
if err != nil {
198+
return err
199+
}
200+
201+
return nil
202+
}

new/client/templates/main.go.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import "{{.LibraryURL}}"
4+
5+
func main() {
6+
socket, err := cg.NewSocket("{{.URL}}")
7+
if err != nil {
8+
log.Fatalf("failed to connect to server: %s", err)
9+
}
10+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package {{.PackageName}}
2+
3+
import "{{.LibraryURL}}"
4+
5+
{{range .Events}}
6+
// On{{.PascalName}}Event triggers the callback every time the `{{.Name}}` event is received.
7+
func (g *Game) On{{.PascalName}}Event(callback func(origin Player, data {{.PascalName}}EventData)) cg.CallbackId {
8+
return g.socket.On({{.PascalName}}Event, func(origin string, event cg.Event) {
9+
var data {{.PascalName}}EventData
10+
err := event.UnmarshalData(&data)
11+
if err == nil {
12+
username := g.socket.ResolveUsername(origin)
13+
if origin == "server" {
14+
username = "Server"
15+
}
16+
callback(Player{
17+
Id: origin,
18+
Username: username,
19+
}, data)
20+
}
21+
})
22+
}
23+
24+
// On{{.PascalName}}EventOnce triggers the callback the next time the `{{.Name}}` event is received.
25+
func (g *Game) On{{.PascalName}}EventOnce(callback func(origin Player, data {{.PascalName}}EventData)) cg.CallbackId {
26+
return g.socket.Once({{.PascalName}}Event, func(origin string, event cg.Event) {
27+
var data {{.PascalName}}EventData
28+
err := event.UnmarshalData(&data)
29+
if err == nil {
30+
username := g.socket.ResolveUsername(origin)
31+
if origin == "server" {
32+
username = "Server"
33+
}
34+
callback(Player{
35+
Id: origin,
36+
Username: username,
37+
}, data)
38+
}
39+
})
40+
}
41+
42+
// Send{{.PascalName}}Event sends a `{{.Name}}` event.
43+
func (g *Game) Send{{.PascalName}}Event(data {{.PascalName}}EventData) error {
44+
return g.socket.Send({{.PascalName}}Event, data)
45+
}
46+
{{end}}
47+
48+
// RemoveCallback removes the callback with the specified id.
49+
func (g *Game) RemoveCallback(id cg.CallbackId) {
50+
g.socket.RemoveCallback(id)
51+
}

0 commit comments

Comments
 (0)