You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
423 lines
8.4 KiB
423 lines
8.4 KiB
package game |
|
|
|
import ( |
|
"bytes" |
|
"embed" |
|
"fmt" |
|
"image" |
|
"image/color" |
|
_ "image/png" |
|
"log" |
|
"strings" |
|
"time" |
|
|
|
"code.rocketnine.space/tslocum/fibs" |
|
"github.com/hajimehoshi/ebiten/v2" |
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil" |
|
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts" |
|
"github.com/hajimehoshi/ebiten/v2/inpututil" |
|
"github.com/nfnt/resize" |
|
"golang.org/x/image/font" |
|
"golang.org/x/image/font/opentype" |
|
) |
|
|
|
// Copyright 2020 The Ebiten Authors |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
//go:embed assets |
|
var assetsFS embed.FS |
|
|
|
var debugExtra []byte |
|
|
|
var ( |
|
imgCheckerWhite *ebiten.Image |
|
imgCheckerBlack *ebiten.Image |
|
|
|
mplusNormalFont font.Face |
|
mplusBigFont font.Face |
|
) |
|
|
|
const defaultServerAddress = "fibs.com:4321" |
|
|
|
func init() { |
|
loadAssets(0) |
|
|
|
initializeFonts() |
|
} |
|
|
|
func loadAssets(width int) { |
|
imgCheckerWhite = loadAsset("assets/checker_white.png", width) |
|
imgCheckerBlack = loadAsset("assets/checker_black.png", width) |
|
} |
|
|
|
func loadAsset(assetPath string, width int) *ebiten.Image { |
|
f, err := assetsFS.Open(assetPath) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
img, _, err := image.Decode(f) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
if width > 0 { |
|
imgResized := resize.Resize(uint(width), 0, img, resize.Lanczos3) |
|
return ebiten.NewImageFromImage(imgResized) |
|
} |
|
return ebiten.NewImageFromImage(img) |
|
|
|
} |
|
|
|
func initializeFonts() { |
|
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
const dpi = 72 |
|
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{ |
|
Size: 24, |
|
DPI: dpi, |
|
Hinting: font.HintingFull, |
|
}) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{ |
|
Size: 32, |
|
DPI: dpi, |
|
Hinting: font.HintingFull, |
|
}) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
} |
|
|
|
type Sprite struct { |
|
image *ebiten.Image |
|
w int |
|
h int |
|
x int |
|
y int |
|
colorWhite bool |
|
} |
|
|
|
func (s *Sprite) Update() { |
|
return // TODO |
|
} |
|
|
|
type Sprites struct { |
|
sprites []*Sprite |
|
num int |
|
} |
|
|
|
func (s *Sprites) Update() { |
|
for i := 0; i < s.num; i++ { |
|
s.sprites[i].Update() |
|
} |
|
} |
|
|
|
const ( |
|
MinSprites = 0 |
|
MaxSprites = 50000 |
|
) |
|
|
|
var spinner = []byte(`-\|/`) |
|
|
|
type Game struct { |
|
touchIDs []ebiten.TouchID |
|
sprites Sprites |
|
op *ebiten.DrawImageOptions |
|
Board *board |
|
|
|
screenW, screenH int |
|
|
|
drawBuffer bytes.Buffer |
|
|
|
spinnerIndex int |
|
|
|
ServerAddress string |
|
Username string |
|
Password string |
|
loggedIn bool |
|
usernameConfirmed bool |
|
|
|
Watch bool |
|
|
|
Client *fibs.Client |
|
|
|
runeBuffer []rune |
|
inputBuffer string |
|
|
|
Debug int |
|
} |
|
|
|
func NewGame() *Game { |
|
go func() { |
|
// TODO fetch HTTP request, set debugExtra |
|
}() |
|
|
|
g := &Game{ |
|
op: &ebiten.DrawImageOptions{ |
|
Filter: ebiten.FilterNearest, |
|
}, |
|
Board: NewBoard(), |
|
|
|
runeBuffer: make([]rune, 24), |
|
} |
|
|
|
go func() { |
|
t := time.NewTicker(time.Second / 4) |
|
for range t.C { |
|
_ = g.update() |
|
} |
|
}() |
|
|
|
return g |
|
} |
|
|
|
func (g *Game) handleEvents() { |
|
for e := range g.Client.Event { |
|
switch event := e.(type) { |
|
case *fibs.EventMove: |
|
g.Board.movePiece(event.From, event.To) |
|
} |
|
} |
|
} |
|
|
|
func (g *Game) Connect() { |
|
g.loggedIn = true |
|
|
|
address := g.ServerAddress |
|
if address == "" { |
|
address = defaultServerAddress |
|
} |
|
g.Client = fibs.NewClient(address, g.Username, g.Password) |
|
|
|
go g.handleEvents() |
|
|
|
c := g.Client |
|
|
|
if g.Watch { |
|
go func() { |
|
time.Sleep(1 * time.Second) |
|
c.WatchRandomGame() |
|
|
|
go g.renderLoop() |
|
}() |
|
} |
|
|
|
go func() { |
|
err := c.Connect() |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
}() |
|
} |
|
|
|
func (g *Game) renderLoop() { |
|
t := time.NewTicker(time.Second) |
|
for range t.C { |
|
gameBoard := g.Board |
|
v := g.Client.Board.GetIntState() |
|
gameBoard.SetState(v) |
|
} |
|
} |
|
|
|
func (g *Game) leftTouched() bool { |
|
for _, id := range g.touchIDs { |
|
x, _ := ebiten.TouchPosition(id) |
|
/*if x < screenWidth/2 { |
|
return true |
|
}*/ |
|
_ = x |
|
} |
|
return false |
|
} |
|
|
|
func (g *Game) rightTouched() bool { |
|
for _, id := range g.touchIDs { |
|
x, _ := ebiten.TouchPosition(id) |
|
/*if x >= screenWidth/2 { |
|
return true |
|
}*/ |
|
_ = x |
|
} |
|
return false |
|
} |
|
|
|
// Separate update function for all normal update logic, as Update may only be |
|
// called when there is user input when vsync is disabled. |
|
func (g *Game) update() error { |
|
return nil |
|
} |
|
|
|
func (g *Game) Update() error { // Called by ebiten only when input occurs |
|
err := g.update() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if !g.loggedIn { |
|
f := func() { |
|
var clearBuffer bool |
|
defer func() { |
|
if clearBuffer { |
|
g.inputBuffer = "" |
|
|
|
if !g.usernameConfirmed { |
|
g.usernameConfirmed = true |
|
} else if g.Password != "" { |
|
g.Connect() |
|
} |
|
} |
|
}() |
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) && len(g.inputBuffer) > 0 { |
|
g.inputBuffer = g.inputBuffer[:len(g.inputBuffer)-1] |
|
} |
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) { |
|
clearBuffer = true |
|
} |
|
|
|
g.runeBuffer = ebiten.AppendInputChars(g.runeBuffer[:0]) |
|
if len(g.runeBuffer) > 0 { |
|
g.inputBuffer += string(g.runeBuffer) |
|
|
|
if strings.ContainsRune(g.inputBuffer, '\n') { |
|
g.inputBuffer = strings.Split(g.inputBuffer, "\n")[0] |
|
clearBuffer = true |
|
} |
|
if !g.usernameConfirmed { |
|
g.Username = g.inputBuffer |
|
} else { |
|
g.Password = g.inputBuffer |
|
} |
|
log.Println("INPUT BUFFER IS:" + g.inputBuffer) |
|
} |
|
} |
|
|
|
f() |
|
} |
|
|
|
g.touchIDs = ebiten.AppendTouchIDs(g.touchIDs[:0]) |
|
|
|
// Decrease the number of the sprites. |
|
if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || g.leftTouched() { |
|
g.sprites.num -= 20 |
|
if g.sprites.num < MinSprites { |
|
g.sprites.num = MinSprites |
|
} |
|
} |
|
|
|
// Increase the number of the sprites. |
|
if ebiten.IsKeyPressed(ebiten.KeyArrowRight) || g.rightTouched() { |
|
g.sprites.num += 20 |
|
if MaxSprites < g.sprites.num { |
|
g.sprites.num = MaxSprites |
|
} |
|
} |
|
|
|
if inpututil.IsKeyJustPressed(ebiten.KeyD) { |
|
g.Debug++ |
|
if g.Debug == 3 { |
|
g.Debug = 0 |
|
} |
|
g.Board.debug = g.Debug |
|
} |
|
|
|
g.Board.update() |
|
|
|
//g.sprites.Update() |
|
return nil |
|
} |
|
|
|
func (g *Game) Draw(screen *ebiten.Image) { |
|
screen.Fill(color.RGBA{0, 102, 51, 255}) |
|
|
|
// Log in screen |
|
if !g.loggedIn { |
|
const welcomeText = `Please enter your FIBS username and password. |
|
If you do not have a FIBS account yet, visit |
|
http://www.fibs.com/help.html#register` |
|
debugBox := image.NewRGBA(image.Rect(0, 0, g.screenW, g.screenH)) |
|
debugImg := ebiten.NewImageFromImage(debugBox) |
|
|
|
if !g.usernameConfirmed { |
|
ebitenutil.DebugPrint(debugImg, welcomeText+fmt.Sprintf("\n\nUsername: %s", g.Username)) |
|
} else { |
|
ebitenutil.DebugPrint(debugImg, welcomeText+fmt.Sprintf("\n\nPassword: %s", strings.Repeat("*", len(g.Password)))) |
|
} |
|
|
|
g.resetImageOptions() |
|
g.op.GeoM.Scale(2, 2) |
|
screen.DrawImage(debugImg, g.op) |
|
return |
|
} |
|
|
|
// Game screen |
|
|
|
g.Board.draw(screen) |
|
|
|
if g.Debug == 1 { |
|
debugBox := image.NewRGBA(image.Rect(10, 20, 200, 200)) |
|
debugImg := ebiten.NewImageFromImage(debugBox) |
|
|
|
g.drawBuffer.Reset() |
|
|
|
g.drawBuffer.Write([]byte(fmt.Sprintf("FPS %0.0f\nTPS %0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS()))) |
|
|
|
/* TODO enable when vsync is able to be turned off |
|
g.spinnerIndex++ |
|
if g.spinnerIndex == 4 { |
|
g.spinnerIndex = 0 |
|
}*/ |
|
|
|
scaleFactor := ebiten.DeviceScaleFactor() |
|
if scaleFactor != 1.0 { |
|
g.drawBuffer.WriteRune('\n') |
|
g.drawBuffer.Write([]byte(fmt.Sprintf("SCA %0.1f", scaleFactor))) |
|
} |
|
|
|
if debugExtra != nil { |
|
g.drawBuffer.WriteRune('\n') |
|
g.drawBuffer.Write(debugExtra) |
|
} |
|
|
|
ebitenutil.DebugPrint(debugImg, g.drawBuffer.String()) |
|
|
|
g.resetImageOptions() |
|
g.op.GeoM.Translate(3, 0) |
|
g.op.GeoM.Scale(2, 2) |
|
screen.DrawImage(debugImg, g.op) |
|
} |
|
} |
|
|
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { |
|
s := ebiten.DeviceScaleFactor() |
|
outsideWidth, outsideHeight = int(float64(outsideWidth)*s), int(float64(outsideHeight)*s) |
|
if g.screenW == outsideWidth && g.screenH == outsideHeight { |
|
return outsideWidth, outsideHeight |
|
} |
|
|
|
g.screenW, g.screenH = outsideWidth, outsideHeight |
|
g.Board.setRect(0, 0, g.screenW, g.screenH) |
|
return outsideWidth, outsideHeight |
|
} |
|
|
|
func (g *Game) resetImageOptions() { |
|
g.op.GeoM.Reset() |
|
}
|
|
|