Add tutorial

Resolves #14.
This commit is contained in:
Trevor Slocum 2024-01-04 20:13:49 -08:00
parent 70c2f97d4d
commit 2aa4c96f4c
5 changed files with 194 additions and 3 deletions

View file

@ -1,4 +1,5 @@
1.2.1:
- Add tutorial
- Change lobby button labels
1.2.0:

View file

@ -499,6 +499,8 @@ func NewBoard() *board {
b.frame.AddChild(f)
}
b.frame.AddChild(game.tutorialFrame)
b.fontUpdated()
for i := range b.Sprites.sprites {

View file

@ -130,6 +130,8 @@ var (
historyContainer *etk.Grid
listGamesContainer *etk.Grid
tutorialFrame *etk.Frame
createGameFrame *etk.Frame
joinGameFrame *etk.Frame
historyFrame *etk.Frame
@ -600,6 +602,9 @@ type Game struct {
resetInfo *etk.Text
resetInProgress bool
tutorial *tutorialWidget
tutorialFrame *etk.Frame
pressedKeys []ebiten.Key
cursorX, cursorY int
@ -664,6 +669,8 @@ func NewGame() *Game {
TouchInput: AutoEnableTouchInput,
tutorialFrame: etk.NewFrame(),
debugImg: ebiten.NewImage(200, 200),
volume: 1,
scaleFactor: 1,
@ -671,6 +678,7 @@ func NewGame() *Game {
Mutex: &sync.Mutex{},
}
g.keyboard.SetScheduleFrameFunc(scheduleFrame)
g.tutorialFrame.SetPositionChildren(true)
game = g
loadImageAssets(0)
@ -982,6 +990,7 @@ func NewGame() *Game {
createGameFrame.SetPositionChildren(true)
createGameFrame.AddChild(createGameContainer)
createGameFrame.AddChild(etk.NewFrame(g.lobby.showKeyboardButton))
createGameFrame.AddChild(g.tutorialFrame)
}
{
@ -1014,6 +1023,7 @@ func NewGame() *Game {
joinGameFrame.SetPositionChildren(true)
joinGameFrame.AddChild(joinGameContainer)
joinGameFrame.AddChild(etk.NewFrame(g.lobby.showKeyboardButton))
joinGameFrame.AddChild(g.tutorialFrame)
}
{
@ -1116,6 +1126,7 @@ func NewGame() *Game {
listGamesFrame.SetPositionChildren(true)
listGamesFrame.AddChild(listGamesContainer)
listGamesFrame.AddChild(g.tutorialFrame)
}
g.needLayoutConnect = true
@ -1254,6 +1265,10 @@ func (g *Game) handleEvent(e interface{}) {
matchesPlural = ""
}
l(fmt.Sprintf("*** Welcome, %s. There %s %d client%s playing %d match%s.", ev.PlayerName, areIs, ev.Clients, clientsPlural, ev.Games, matchesPlural))
if strings.HasPrefix(g.Client.Username, "Guest_") {
g.tutorialFrame.AddChild(NewTutorialWidget())
}
case *bgammon.EventHelp:
l(fmt.Sprintf("*** Help: %s", ev.Message))
case *bgammon.EventNotice:
@ -1968,6 +1983,9 @@ func (g *Game) Connect() {
username := g.Username
go c.Connect()
go saveUsername(username)
// TODO
}
func (g *Game) ConnectLocal(conn net.Conn) {

View file

@ -37,6 +37,12 @@ msgstr ""
msgid "Available"
msgstr ""
msgid "Bearing Off"
msgstr ""
msgid "Board"
msgstr ""
msgid "Boxcars is a client for playing backgammon via bgammon.org, a free and open source backgammon service."
msgstr ""
@ -55,6 +61,12 @@ msgstr ""
msgid "Connecting..."
msgstr ""
msgid "Create Match"
msgstr ""
msgid "Create a match if you would like to play against someone else. Backgammon and acey-deucey games are supported."
msgstr ""
msgid "Create match"
msgstr ""
@ -70,6 +82,9 @@ msgstr ""
msgid "Double"
msgstr ""
msgid "Double click a checker to bear it off. Bear off all 15 checkers to win."
msgstr ""
msgid "Download replay"
msgstr ""
@ -79,9 +94,6 @@ msgstr ""
msgid "Email"
msgstr ""
msgid "Failed to connect to server."
msgstr ""
msgid "Failed to download replay: %s"
msgstr ""
@ -100,6 +112,9 @@ msgstr ""
msgid "Failed to submit moves: %s"
msgstr ""
msgid "Good Luck, Have Fun"
msgstr ""
msgid "Hide Keyboard"
msgstr ""
@ -136,6 +151,9 @@ msgstr ""
msgid "Match Name"
msgstr ""
msgid "Matches List"
msgstr ""
msgid "Name"
msgstr ""
@ -235,12 +253,21 @@ msgstr ""
msgid "Submit"
msgstr ""
msgid "This concludes the tutorial. Join the community via Matrix/Discord/IRC at %s"
msgstr ""
msgid "This screen lists the matches that are currently available. A few bots are always available to play against."
msgstr ""
msgid "To download this replay visit"
msgstr ""
msgid "To log in as a guest, enter a username (if you want) and do not enter a password."
msgstr ""
msgid "Tutorial"
msgstr ""
msgid "Type %s to offer a rematch."
msgstr ""
@ -265,12 +292,18 @@ msgstr ""
msgid "Warning: Received unrecognized event from server."
msgstr ""
msgid "Welcome to the guided tutorial. Click anywhere outside of this message box to close the tutorial. Click anywhere inside of this message box to view the next page."
msgstr ""
msgid "Write error"
msgstr ""
msgid "Yes"
msgstr ""
msgid "You have the black checkers. You can move a checker by either clicking it or dragging it."
msgstr ""
msgid "You may need to upgrade your client."
msgstr ""

137
game/tutorial.go Normal file
View file

@ -0,0 +1,137 @@
package game
import (
"image"
"image/color"
"time"
"code.rocket9labs.com/tslocum/etk"
"github.com/hajimehoshi/ebiten/v2"
"github.com/leonelquinteros/gotext"
)
type tutorialWidget struct {
*etk.Frame
grid *etk.Grid
page int
lastClick time.Time
}
func NewTutorialWidget() *tutorialWidget {
w := &tutorialWidget{
Frame: etk.NewFrame(),
grid: etk.NewGrid(),
}
w.Frame.SetPositionChildren(true)
w.Frame.AddChild(w.grid)
w.setPage(0)
return w
}
func (w *tutorialWidget) hide() {
game.lobby.showCreateGame = false
game.setRoot(listGamesFrame)
setViewBoard(false)
w.grid.Clear()
}
func (w *tutorialWidget) dialogText(message string) *tutorialDialog {
t := etk.NewText(message)
t.SetBackgroundColor(bufferBackgroundColor)
return &tutorialDialog{
Text: t,
handler: func() {
w.setPage(w.page + 1)
},
}
}
func (w *tutorialWidget) newTutorialBox() *tutorialBox {
return &tutorialBox{
Box: etk.NewBox(),
handler: w.hide,
}
}
func (w *tutorialWidget) setPage(page int) {
if time.Since(w.lastClick) < 250*time.Millisecond {
return
}
w.lastClick = time.Now()
w.page = page
w.grid.Clear()
var title string
var message string
switch w.page {
case 0:
title = gotext.Get("Tutorial")
message = gotext.Get("Welcome to the guided tutorial. Click anywhere outside of this message box to close the tutorial. Click anywhere inside of this message box to view the next page.")
case 1:
title = gotext.Get("Matches List")
message = gotext.Get("This screen lists the matches that are currently available. A few bots are always available to play against.")
case 2:
game.lobby.showCreateGame = true
game.setRoot(createGameFrame)
etk.SetFocus(game.lobby.createGameName)
title = gotext.Get("Create Match")
message = gotext.Get("Create a match if you would like to play against someone else. Backgammon and acey-deucey games are supported.")
case 3:
game.lobby.showCreateGame = false
game.setRoot(listGamesFrame)
setViewBoard(true)
title = gotext.Get("Board")
message = gotext.Get("You have the black checkers. You can move a checker by either clicking it or dragging it.")
case 4:
title = gotext.Get("Bearing Off")
message = gotext.Get("Double click a checker to bear it off. Bear off all 15 checkers to win.")
case 5:
title = gotext.Get("Good Luck, Have Fun")
message = gotext.Get("This concludes the tutorial. Join the community via Matrix/Discord/IRC at %s", "bgammon.org/community")
case 6:
w.hide()
return
}
message = title + "\n\n" + message
w.grid.SetColumnSizes(-1, -1, -1, -1)
w.grid.SetRowSizes(-1, -1, -1, -1)
w.grid.AddChildAt(w.newTutorialBox(), 0, 0, 4, 1)
w.grid.AddChildAt(w.newTutorialBox(), 0, 1, 1, 2)
w.grid.AddChildAt(w.dialogText(message), 1, 1, 2, 2)
w.grid.AddChildAt(w.newTutorialBox(), 3, 1, 1, 2)
w.grid.AddChildAt(w.newTutorialBox(), 0, 3, 4, 1)
}
type tutorialDialog struct {
*etk.Text
handler func()
}
func (d *tutorialDialog) Draw(screen *ebiten.Image) error {
screen.SubImage(d.Text.Rect().Inset(-2)).(*ebiten.Image).Fill(color.RGBA{0, 0, 0, 255})
return d.Text.Draw(screen)
}
func (d *tutorialDialog) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
if !cursor.In(d.Rect()) || !clicked {
return false, nil
}
d.handler()
return true, nil
}
type tutorialBox struct {
*etk.Box
handler func()
}
func (b *tutorialBox) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
if !cursor.In(b.Rect()) || !clicked {
return false, nil
}
b.handler()
return true, nil
}