add basic viewer
This commit is contained in:
parent
d41e310aa3
commit
274cda86bb
14
go.mod
14
go.mod
@ -1,3 +1,17 @@
|
||||
module git.saintnet.tech/freego
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec // indirect
|
||||
github.com/hajimehoshi/ebiten v1.12.12 // indirect
|
||||
github.com/hajimehoshi/ebiten/v2 v2.2.5 // indirect
|
||||
github.com/jezek/xgb v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||
golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
)
|
||||
|
206
input.go
Normal file
206
input.go
Normal file
@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
// Dir represents a direction.
|
||||
type Dir int
|
||||
|
||||
// represents the valid directions
|
||||
const (
|
||||
DirUp Dir = iota
|
||||
DirRight
|
||||
DirDown
|
||||
DirLeft
|
||||
)
|
||||
|
||||
type mouseState int
|
||||
|
||||
const (
|
||||
mouseStateNone mouseState = iota
|
||||
mouseStatePressing
|
||||
mouseStateSettled
|
||||
)
|
||||
|
||||
type touchState int
|
||||
|
||||
const (
|
||||
touchStateNone touchState = iota
|
||||
touchStatePressing
|
||||
touchStateSettled
|
||||
touchStateInvalid
|
||||
)
|
||||
|
||||
// String returns a string representing the direction.
|
||||
func (d Dir) String() string {
|
||||
switch d {
|
||||
case DirUp:
|
||||
return "Up"
|
||||
case DirRight:
|
||||
return "Right"
|
||||
case DirDown:
|
||||
return "Down"
|
||||
case DirLeft:
|
||||
return "Left"
|
||||
}
|
||||
panic("not reach")
|
||||
}
|
||||
|
||||
// Vector returns a [-1, 1] value for each axis.
|
||||
func (d Dir) Vector() (x, y int) {
|
||||
switch d {
|
||||
case DirUp:
|
||||
return 0, -1
|
||||
case DirRight:
|
||||
return 1, 0
|
||||
case DirDown:
|
||||
return 0, 1
|
||||
case DirLeft:
|
||||
return -1, 0
|
||||
}
|
||||
panic("not reach")
|
||||
}
|
||||
|
||||
// Input represents the current key states.
|
||||
type Input struct {
|
||||
mouseState mouseState
|
||||
mouseInitPosX int
|
||||
mouseInitPosY int
|
||||
mouseDir Dir
|
||||
|
||||
touches []ebiten.TouchID
|
||||
touchState touchState
|
||||
touchID ebiten.TouchID
|
||||
touchInitPosX int
|
||||
touchInitPosY int
|
||||
touchLastPosX int
|
||||
touchLastPosY int
|
||||
touchDir Dir
|
||||
}
|
||||
|
||||
// NewInput generates a new Input object.
|
||||
func NewInput() *Input {
|
||||
return &Input{}
|
||||
}
|
||||
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func vecToDir(dx, dy int) (Dir, bool) {
|
||||
if abs(dx) < 4 && abs(dy) < 4 {
|
||||
return 0, false
|
||||
}
|
||||
if abs(dx) < abs(dy) {
|
||||
if dy < 0 {
|
||||
return DirUp, true
|
||||
}
|
||||
return DirDown, true
|
||||
}
|
||||
if dx < 0 {
|
||||
return DirLeft, true
|
||||
}
|
||||
return DirRight, true
|
||||
}
|
||||
|
||||
// Update updates the current input states.
|
||||
func (i *Input) Update() {
|
||||
switch i.mouseState {
|
||||
case mouseStateNone:
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
i.mouseInitPosX = x
|
||||
i.mouseInitPosY = y
|
||||
i.mouseState = mouseStatePressing
|
||||
}
|
||||
case mouseStatePressing:
|
||||
if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
dx := x - i.mouseInitPosX
|
||||
dy := y - i.mouseInitPosY
|
||||
d, ok := vecToDir(dx, dy)
|
||||
if !ok {
|
||||
i.mouseState = mouseStateNone
|
||||
break
|
||||
}
|
||||
i.mouseDir = d
|
||||
i.mouseState = mouseStateSettled
|
||||
}
|
||||
case mouseStateSettled:
|
||||
i.mouseState = mouseStateNone
|
||||
}
|
||||
|
||||
i.touches = ebiten.AppendTouchIDs(i.touches[:0])
|
||||
switch i.touchState {
|
||||
case touchStateNone:
|
||||
if len(i.touches) == 1 {
|
||||
i.touchID = i.touches[0]
|
||||
x, y := ebiten.TouchPosition(i.touches[0])
|
||||
i.touchInitPosX = x
|
||||
i.touchInitPosY = y
|
||||
i.touchLastPosX = x
|
||||
i.touchLastPosX = y
|
||||
i.touchState = touchStatePressing
|
||||
}
|
||||
case touchStatePressing:
|
||||
if len(i.touches) >= 2 {
|
||||
break
|
||||
}
|
||||
if len(i.touches) == 1 {
|
||||
if i.touches[0] != i.touchID {
|
||||
i.touchState = touchStateInvalid
|
||||
} else {
|
||||
x, y := ebiten.TouchPosition(i.touches[0])
|
||||
i.touchLastPosX = x
|
||||
i.touchLastPosY = y
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(i.touches) == 0 {
|
||||
dx := i.touchLastPosX - i.touchInitPosX
|
||||
dy := i.touchLastPosY - i.touchInitPosY
|
||||
d, ok := vecToDir(dx, dy)
|
||||
if !ok {
|
||||
i.touchState = touchStateNone
|
||||
break
|
||||
}
|
||||
i.touchDir = d
|
||||
i.touchState = touchStateSettled
|
||||
}
|
||||
case touchStateSettled:
|
||||
i.touchState = touchStateNone
|
||||
case touchStateInvalid:
|
||||
if len(i.touches) == 0 {
|
||||
i.touchState = touchStateNone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dir returns a currently pressed direction.
|
||||
// Dir returns false if no direction key is pressed.
|
||||
func (i *Input) Dir() (Dir, bool) {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) {
|
||||
return DirUp, true
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) {
|
||||
return DirLeft, true
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) {
|
||||
return DirRight, true
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) {
|
||||
return DirDown, true
|
||||
}
|
||||
if i.mouseState == mouseStateSettled {
|
||||
return i.mouseDir, true
|
||||
}
|
||||
if i.touchState == touchStateSettled {
|
||||
return i.touchDir, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
120
main.go
120
main.go
@ -1,16 +1,128 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func dummyGame() (*Game, error) {
|
||||
g := &Game{
|
||||
board: NewBoard(4),
|
||||
state: gameSetup,
|
||||
}
|
||||
//Setup terrain
|
||||
terrain := []struct {
|
||||
x, y, t int
|
||||
}{
|
||||
{1, 1, 1},
|
||||
{2, 2, 1},
|
||||
}
|
||||
for _, tt := range terrain {
|
||||
res, err := g.board.AddTerrain(tt.x, tt.y, tt.t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !res {
|
||||
return nil, errors.New("Error creating terrain")
|
||||
}
|
||||
}
|
||||
pieces := []struct {
|
||||
x, y int
|
||||
p *Piece
|
||||
}{
|
||||
{0, 0, NewPiece(Flag, Blue)},
|
||||
{3, 0, NewPiece(Spy, Blue)},
|
||||
{2, 0, NewPiece(Captain, Blue)},
|
||||
{3, 1, NewPiece(Marshal, Blue)},
|
||||
{0, 1, NewPiece(Bomb, Blue)},
|
||||
|
||||
{1, 2, NewPiece(Flag, Red)},
|
||||
{3, 2, NewPiece(Spy, Red)},
|
||||
{2, 3, NewPiece(Captain, Red)},
|
||||
{0, 2, NewPiece(Marshal, Red)},
|
||||
{0, 3, NewPiece(Bomb, Red)},
|
||||
}
|
||||
for _, tt := range pieces {
|
||||
res, err := g.SetupPiece(tt.x, tt.y, tt.p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Piece %v,%v:%v", tt.x, tt.y, err)
|
||||
}
|
||||
if !res {
|
||||
return nil, errors.New("error placing dummy piece")
|
||||
}
|
||||
}
|
||||
_, err := g.SetupPiece(0, 0, NewPiece(Flag, Blue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
func main() {
|
||||
//red := NewDummyPlayer(Red)
|
||||
//blue := NewDummyPlayer(Blue)
|
||||
g := NewGame()
|
||||
g.state = gameSetup
|
||||
printboardcolours(g)
|
||||
g, err := dummyGame()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go func(g *Game) {
|
||||
r := NewDummyPlayer(Red)
|
||||
b := NewDummyPlayer(Blue)
|
||||
g.Start()
|
||||
var moves = []struct {
|
||||
input string
|
||||
player Player
|
||||
res bool
|
||||
}{
|
||||
{"c3-b3", r, true},
|
||||
{"c0-c1", b, true},
|
||||
{"d2xd1", r, true},
|
||||
{"c1-d1", b, true},
|
||||
{"b3-c3", r, true},
|
||||
{"d1xd2", b, true},
|
||||
}
|
||||
for i, tt := range moves {
|
||||
time.Sleep(3 * time.Second)
|
||||
log.Printf("playing move %v\n", i)
|
||||
raw, err := NewRawCommand(tt.input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
parsed, err := g.Parse(tt.player, raw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := g.Mutate(parsed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if res {
|
||||
log.Printf("move %v successful\n", i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}(g)
|
||||
viewer(g)
|
||||
return
|
||||
}
|
||||
|
||||
func viewer(g *Game) {
|
||||
v, err := NewViewer(g)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ebiten.SetWindowSize(800, 640)
|
||||
ebiten.SetWindowTitle("Freego")
|
||||
if err := ebiten.RunGame(v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func addpiece(game *Game, rank int, c Colour, x int, y int) {
|
||||
res, err := game.SetupPiece(x, y, NewPieceFromInt(rank, c))
|
||||
if err != nil {
|
||||
|
91
view_board.go
Normal file
91
view_board.go
Normal file
@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
var errorTaskTerminated = errors.New("freego: task terminated")
|
||||
|
||||
type task func() error
|
||||
|
||||
// ViewBoard represents the game board.
|
||||
type ViewBoard struct {
|
||||
size int
|
||||
tiles map[*ViewTile]struct{}
|
||||
tasks []task
|
||||
}
|
||||
|
||||
// NewViewBoard generates a new ViewBoard with giving a size.
|
||||
func NewViewBoard(g *Game) (*ViewBoard, error) {
|
||||
size := g.board.size
|
||||
b := &ViewBoard{
|
||||
size: size,
|
||||
tiles: map[*ViewTile]struct{}{},
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
for j := 0; j < size; j++ {
|
||||
b.tiles[NewViewTile(i, j, g.board.board[i][j])] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
//func (b *ViewBoard) tileAt(x, y int) *Tile {
|
||||
// return tileAt(b.tiles, x, y)
|
||||
//}
|
||||
|
||||
// Update updates the board state.
|
||||
func (b *ViewBoard) Update(input *Input) error {
|
||||
for t := range b.tiles {
|
||||
if err := t.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if 0 < len(b.tasks) {
|
||||
t := b.tasks[0]
|
||||
if err := t(); err == errorTaskTerminated {
|
||||
b.tasks = b.tasks[1:]
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if dir, ok := input.Dir(); ok {
|
||||
//if err := b.Move(dir); err != nil {
|
||||
// return err
|
||||
//}
|
||||
log.Println(dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw draws the board to the given boardImage.
|
||||
func (b *ViewBoard) Draw(boardImage *ebiten.Image) {
|
||||
boardImage.Fill(color.RGBA{0xbb, 0xad, 0xa0, 0xff})
|
||||
for j := 0; j < b.size; j++ {
|
||||
for i := 0; i < b.size; i++ {
|
||||
//v := 0
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
x := i*tileSize + (i+1)*tileMargin
|
||||
y := j*tileSize + (j+1)*tileMargin
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
//op.ColorM.ScaleWithColor(tileBackgroundColor(v))
|
||||
boardImage.DrawImage(tileImage, op)
|
||||
}
|
||||
}
|
||||
for t := range b.tiles {
|
||||
t.Draw(boardImage)
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the board size.
|
||||
func (b *ViewBoard) Size() (int, int) {
|
||||
x := b.size*tileSize + (b.size+1)*tileMargin
|
||||
y := x
|
||||
return x, y
|
||||
}
|
129
view_tile.go
Normal file
129
view_tile.go
Normal file
@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/examples/resources/fonts"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/text"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
)
|
||||
|
||||
//ViewTile is a tile in the viewer
|
||||
type ViewTile struct {
|
||||
gameTile *Tile
|
||||
|
||||
// next represents a next tile information after moving.
|
||||
// next is empty when the tile is not about to move.
|
||||
//next TileData
|
||||
|
||||
//movingCount int
|
||||
//startPoppingCount int
|
||||
//poppingCount int
|
||||
}
|
||||
|
||||
var (
|
||||
tileImage = ebiten.NewImage(tileSize, tileSize)
|
||||
)
|
||||
|
||||
var (
|
||||
mplusSmallFont font.Face
|
||||
mplusNormalFont font.Face
|
||||
mplusBigFont font.Face
|
||||
)
|
||||
|
||||
const (
|
||||
tileSize = 80
|
||||
tileMargin = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const dpi = 72
|
||||
mplusSmallFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 24,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 32,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 48,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
//NewViewTile creates a new view tile
|
||||
func NewViewTile(x, y int, t *Tile) *ViewTile {
|
||||
return &ViewTile{
|
||||
gameTile: t,
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the tile's animation states.
|
||||
func (t *ViewTile) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw draws the current tile to the given boardImage.
|
||||
func (t *ViewTile) Draw(boardImage *ebiten.Image) {
|
||||
j, i := t.gameTile.x, t.gameTile.y
|
||||
v := t.gameTile.entity
|
||||
if v == nil && t.gameTile.Passable() {
|
||||
return
|
||||
}
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
x := i*tileSize + (i+1)*tileMargin
|
||||
y := j*tileSize + (j+1)*tileMargin
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
//op.ColorM.ScaleWithColor(tileBackgroundColor(v))
|
||||
boardImage.DrawImage(tileImage, op)
|
||||
str := "NA"
|
||||
if v == nil {
|
||||
str = "river"
|
||||
} else {
|
||||
str = v.Rank.String()
|
||||
}
|
||||
|
||||
f := mplusBigFont
|
||||
switch {
|
||||
case 3 < len(str):
|
||||
f = mplusSmallFont
|
||||
case 2 < len(str):
|
||||
f = mplusNormalFont
|
||||
}
|
||||
|
||||
bound, _ := font.BoundString(f, str)
|
||||
w := (bound.Max.X - bound.Min.X).Ceil()
|
||||
h := (bound.Max.Y - bound.Min.Y).Ceil()
|
||||
x = x + (tileSize-w)/2
|
||||
y = y + (tileSize-h)/2 + h
|
||||
pieceColor := color.RGBA{0xf9, 0xf6, 0xf2, 0xff}
|
||||
if v != nil {
|
||||
if t.gameTile.entity.Owner == Red {
|
||||
pieceColor = color.RGBA{0xff, 0x0, 0x0, 0xff}
|
||||
} else if t.gameTile.entity.Owner == Blue {
|
||||
pieceColor = color.RGBA{0x0, 0x0, 0xff, 0xff}
|
||||
}
|
||||
}
|
||||
text.Draw(boardImage, str, f, x, y, pieceColor)
|
||||
}
|
60
viewer.go
Normal file
60
viewer.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
//Viewer is a graphical representation of a freego game
|
||||
type Viewer struct {
|
||||
input *Input
|
||||
board *ViewBoard
|
||||
boardImage *ebiten.Image
|
||||
gameState *Game
|
||||
}
|
||||
|
||||
//NewViewer creates a new viewer
|
||||
func NewViewer(g *Game) (*Viewer, error) {
|
||||
v := &Viewer{
|
||||
input: NewInput(),
|
||||
gameState: g,
|
||||
}
|
||||
var err error
|
||||
v.board, err = NewViewBoard(g)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Layout implements ebiten.Game's Layout.
|
||||
func (v *Viewer) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
|
||||
return 800, 640
|
||||
}
|
||||
|
||||
// Update updates the current game state.
|
||||
func (v *Viewer) Update() error {
|
||||
v.input.Update()
|
||||
if err := v.board.Update(v.input); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw draws the current game to the given screen.
|
||||
func (v *Viewer) Draw(screen *ebiten.Image) {
|
||||
if v.boardImage == nil {
|
||||
w, h := v.board.Size()
|
||||
v.boardImage = ebiten.NewImage(w, h)
|
||||
}
|
||||
screen.Fill(color.RGBA{0xfa, 0xf8, 0xef, 0xff})
|
||||
v.board.Draw(v.boardImage)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
sw, sh := screen.Size()
|
||||
bw, bh := v.boardImage.Size()
|
||||
x := (sw - bw) / 2
|
||||
y := (sh - bh) / 2
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
screen.DrawImage(v.boardImage, op)
|
||||
}
|
Loading…
Reference in New Issue
Block a user