483 lines
11 KiB
Go
483 lines
11 KiB
Go
package views
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/theme"
|
|
"fyne.io/fyne/v2/widget"
|
|
"github.com/gin-gonic/gin"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"net/http"
|
|
"slices"
|
|
"sync"
|
|
"work_cation/global"
|
|
"work_cation/models"
|
|
"work_cation/pkg/utils"
|
|
"work_cation/service"
|
|
)
|
|
|
|
type GameConfig struct {
|
|
Name string
|
|
X int
|
|
Y int
|
|
Win int
|
|
}
|
|
|
|
var GameConfigList = []GameConfig{
|
|
{"3*3 井字棋", 3, 3, 3},
|
|
{"10*10 五子棋", 10, 10, 5},
|
|
{"19*19 五子棋", 19, 19, 5},
|
|
}
|
|
|
|
func TenChinaGameView() {
|
|
game := NewTenGame(nil)
|
|
game.Show()
|
|
}
|
|
|
|
func StartGameListen() {
|
|
router := service.Server.Router
|
|
|
|
// 推送游戏邀请
|
|
service.Server.Router.POST("/send_game", func(c *gin.Context) {
|
|
user, msg, is := service.PublicPostCheck(c)
|
|
if !is {
|
|
return
|
|
}
|
|
w := fyne.CurrentApp().NewWindow("游戏邀请")
|
|
w.SetContent(container.NewVBox(
|
|
widget.NewLabel(fmt.Sprintf("%s向你发起了 %s 游戏邀请 ", user.Name, msg["name"])),
|
|
container.NewHBox(
|
|
widget.NewButton("同意", func() {
|
|
game := NewTenGame(nil)
|
|
game.itemX = float32(msg["x"].(float64))
|
|
game.itemY = float32(msg["y"].(float64))
|
|
game.winSum = int(msg["win"].(float64))
|
|
online := &models.Online{ID: user.ID, Ip: user.Ip, Port: user.Port}
|
|
err := game.AddNet(utils.Uuid.CreateUUID(), 0, online, &user.Users)
|
|
if err != nil {
|
|
dialog.ShowInformation("错误", err.Error(), w)
|
|
return
|
|
}
|
|
game.Show()
|
|
w.Close()
|
|
}),
|
|
widget.NewButton("婉拒", w.Close),
|
|
)))
|
|
w.CenterOnScreen()
|
|
w.RequestFocus()
|
|
w.Show()
|
|
c.JSON(http.StatusOK, gin.H{})
|
|
})
|
|
|
|
// 开始游戏
|
|
router.POST("/start_game", func(c *gin.Context) {
|
|
user, msg, is := service.PublicPostCheck(c)
|
|
if !is {
|
|
return
|
|
}
|
|
game := NewTenGame(nil)
|
|
game.itemX = float32(msg["x"].(float64))
|
|
game.itemY = float32(msg["y"].(float64))
|
|
game.winSum = int(msg["win"].(float64))
|
|
online := &models.Online{ID: user.ID, Ip: user.Ip, Port: user.Port}
|
|
game.AddNet(utils.Uuid.CreateUUID(), 1, online, &user.Users)
|
|
game.Show()
|
|
c.JSON(200, gin.H{"message": "ok"})
|
|
})
|
|
router.POST("/play_game", func(c *gin.Context) {
|
|
_, msg, is := service.PublicPostCheck(c)
|
|
if !is {
|
|
return
|
|
}
|
|
g := global.GetGameInfo(msg["game_uuid"].(string))
|
|
if g != nil {
|
|
game := g.Obj.(*TenGame)
|
|
game.Play(game.userIndex, int(msg["pos"].(float64)))
|
|
}
|
|
c.JSON(200, gin.H{"message": "ok"})
|
|
})
|
|
|
|
router.POST("/close_game", func(c *gin.Context) {
|
|
_, msg, is := service.PublicPostCheck(c)
|
|
if !is {
|
|
return
|
|
}
|
|
g := global.GetGameInfo(msg["game_uuid"].(string))
|
|
if g != nil {
|
|
dialog.NewConfirm("提示", "对方已退出", func(b bool) {
|
|
g.Obj.(*TenGame).w.Close()
|
|
}, g.Obj.(*TenGame).w).Show()
|
|
//dialog.ShowInformation("提示", "对方已退出", g.Obj.(*TenGame).w)
|
|
}
|
|
c.JSON(200, gin.H{"message": "ok"})
|
|
})
|
|
}
|
|
|
|
// 格子棋 Checkered-Chess
|
|
type TenGame struct {
|
|
lock sync.Mutex // 玩家操作锁
|
|
|
|
playerMax int // 玩家总人数
|
|
playerID2Image map[int]fyne.Resource // 各个玩家对应的图标
|
|
defaultImage fyne.Resource // 默认图标
|
|
|
|
currentRoundPlayer int // 当前行动的玩家
|
|
players [][]int // 各个玩家已完成下棋数据
|
|
allMaps []int // 所有下棋数据
|
|
|
|
winCallback func(int) // 胜利返回
|
|
|
|
Items []*canvas.Image // 各个板块对象
|
|
w fyne.Window
|
|
|
|
itemSize fyne.Size //单个格子大小
|
|
itemX float32 // 格子总数x
|
|
itemY float32 // 格子总数y
|
|
|
|
winSum int // 胜利连线数
|
|
|
|
Ctx context.Context
|
|
Cancel func()
|
|
|
|
isNet bool // 是否是网络模式
|
|
uuid string
|
|
myIndex int
|
|
user *models.Users //
|
|
userIndex int
|
|
online *models.Online
|
|
}
|
|
|
|
func NewTenGame(winCallback func(int)) *TenGame {
|
|
playerImageMap := map[int]fyne.Resource{
|
|
0: theme.RadioButtonIcon(),
|
|
1: theme.CancelIcon(),
|
|
//2: theme.DownloadIcon(),
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
game := &TenGame{
|
|
playerID2Image: playerImageMap,
|
|
defaultImage: nil, //theme.ViewFullScreenIcon(),
|
|
|
|
currentRoundPlayer: 0,
|
|
players: make([][]int, len(playerImageMap)),
|
|
winCallback: winCallback,
|
|
itemSize: fyne.NewSize(45, 45),
|
|
itemX: 19,
|
|
itemY: 19,
|
|
winSum: 5,
|
|
Ctx: ctx,
|
|
Cancel: cancel,
|
|
isNet: false,
|
|
}
|
|
return game
|
|
}
|
|
|
|
func (t *TenGame) AddNet(uuid string, myIndex int, online *models.Online, user *models.Users) error {
|
|
t.isNet = true
|
|
t.online = online
|
|
t.myIndex = myIndex
|
|
t.user = user
|
|
t.uuid = uuid
|
|
info := &global.RunGameInfo{
|
|
Uuid: t.uuid,
|
|
Type: global.GameType1,
|
|
Obj: t,
|
|
}
|
|
global.CreateGame(info)
|
|
|
|
// 发起人
|
|
if myIndex == 0 {
|
|
t.userIndex = 1
|
|
req := gin.H{
|
|
"type": global.GameType1,
|
|
"game_uuid": t.uuid,
|
|
"x": t.itemX,
|
|
"y": t.itemY,
|
|
"win": t.winSum,
|
|
}
|
|
_, err := service.PostSend("/start_game", online, req)
|
|
if err != nil {
|
|
t.isNet = false
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *TenGame) Show() {
|
|
myApp := fyne.CurrentApp()
|
|
myWindow := myApp.NewWindow("")
|
|
t.w = myWindow
|
|
t.setTitle()
|
|
myWindow.SetContent(t.mainCanvasObject())
|
|
myWindow.SetOnClosed(func() {
|
|
if t.isNet {
|
|
global.DeleteGame(t.uuid)
|
|
service.Client.CloseGame(t.online, t.uuid)
|
|
}
|
|
})
|
|
myWindow.CenterOnScreen()
|
|
myWindow.RequestFocus()
|
|
myWindow.Show()
|
|
}
|
|
|
|
// 主界面
|
|
func (t *TenGame) mainCanvasObject() fyne.CanvasObject {
|
|
return container.NewBorder(nil, nil, nil, nil, t.chessCanvasObject())
|
|
}
|
|
|
|
// 棋盘界面
|
|
func (t *TenGame) chessCanvasObject() fyne.CanvasObject {
|
|
// 计算大小
|
|
wSize := fyne.NewSize(t.itemX*(t.itemSize.Width), t.itemY*(t.itemSize.Height))
|
|
|
|
var items []fyne.CanvasObject
|
|
|
|
for i := 0; i < int(t.itemX*t.itemY); i++ {
|
|
var itemIndex = i
|
|
|
|
image := canvas.NewImageFromResource(t.defaultImage)
|
|
image.FillMode = canvas.ImageFillOriginal
|
|
image.Resize(t.itemSize) //fy
|
|
|
|
t.Items = append(t.Items, image)
|
|
|
|
toggle := widget.NewButton("", func() {
|
|
err := t.Play(t.myIndex, itemIndex)
|
|
if err != nil {
|
|
dialog.ShowError(err, t.w)
|
|
}
|
|
})
|
|
toggle.Resize(t.itemSize)
|
|
|
|
iViews := container.NewWithoutLayout(image, toggle)
|
|
iViews.Resize(t.itemSize)
|
|
//gridWrap.Add()
|
|
items = append(items, iViews)
|
|
}
|
|
gridWrap := container.NewGridWithRows(int(t.itemX), items...)
|
|
|
|
scroll := container.NewScroll(gridWrap)
|
|
scroll.SetMinSize(wSize)
|
|
|
|
// 绘制背景
|
|
background := canvas.NewImageFromResource(drawABackgroundImage(wSize, int(t.itemX), int(t.itemY)))
|
|
background.SetMinSize(wSize)
|
|
|
|
return container.New(layout.NewCenterLayout(),
|
|
background,
|
|
scroll,
|
|
)
|
|
}
|
|
|
|
func (t *TenGame) Play(userIndex int, pos int) error {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
if t.isNet && t.currentRoundPlayer != userIndex {
|
|
return errors.New("你急个der")
|
|
}
|
|
if slices.Contains(t.allMaps, pos) {
|
|
return errors.New("已经下过了")
|
|
}
|
|
|
|
if t.isNet && t.myIndex == userIndex {
|
|
// todo 广播数据
|
|
_, err := service.Client.PlayGame(t.online, t.uuid, pos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// 下棋
|
|
t.allMaps = append(t.allMaps, pos)
|
|
t.play(pos)
|
|
// 推进
|
|
t.advance()
|
|
return nil
|
|
}
|
|
|
|
// 推进
|
|
func (t *TenGame) advance() {
|
|
t.currentRoundPlayer++
|
|
if t.currentRoundPlayer+1 > len(t.playerID2Image) {
|
|
t.currentRoundPlayer = 0
|
|
}
|
|
t.setTitle()
|
|
}
|
|
|
|
func (t *TenGame) setTitle() {
|
|
title := fmt.Sprintf("让我们来下棋 %0.f*%0.f win:%d", t.itemX, t.itemY, t.winSum)
|
|
if t.isNet {
|
|
title += fmt.Sprintf(" | 我 vs %s", t.user.Name)
|
|
if t.currentRoundPlayer == t.myIndex {
|
|
title += " | 我的回合"
|
|
} else {
|
|
title += " | 对手回合"
|
|
}
|
|
}
|
|
t.w.SetTitle(title)
|
|
}
|
|
|
|
/*
|
|
1, 2, 3
|
|
4, 5, 6
|
|
7, 8, 9
|
|
*/
|
|
func (t *TenGame) play(pos int) {
|
|
// 更新玩家数据
|
|
playerIntList := t.players[t.currentRoundPlayer]
|
|
playerIntList = append(playerIntList, pos)
|
|
t.players[t.currentRoundPlayer] = playerIntList
|
|
|
|
// 刷新展示
|
|
t.Items[pos].Resource = t.playerID2Image[t.currentRoundPlayer]
|
|
t.Items[pos].Refresh()
|
|
|
|
// check 胜利 判断
|
|
if t.winCheck(playerIntList, pos) {
|
|
endView := widget.NewButton("关闭", func() {
|
|
t.w.Close()
|
|
})
|
|
|
|
img := canvas.NewImageFromResource(t.playerID2Image[t.currentRoundPlayer])
|
|
img.FillMode = canvas.ImageFillOriginal
|
|
|
|
v := container.NewBorder(nil, endView, nil, nil,
|
|
container.NewBorder(nil, nil, img, nil, widget.NewLabel("胜利")))
|
|
dialog.NewCustomWithoutButtons("游戏结束", v, t.w).Show()
|
|
if t.winCallback != nil {
|
|
t.winCallback(t.currentRoundPlayer)
|
|
}
|
|
} else if len(t.allMaps) == int(t.itemX*t.itemY) {
|
|
endView := widget.NewButton("关闭", t.w.Close)
|
|
|
|
img := canvas.NewImageFromResource(t.playerID2Image[t.currentRoundPlayer])
|
|
img.FillMode = canvas.ImageFillOriginal
|
|
|
|
v := container.NewBorder(nil, endView, nil, nil,
|
|
container.NewBorder(nil, nil, img, nil, widget.NewLabel("平局")))
|
|
dialog.NewCustomWithoutButtons("游戏结束", v, t.w).Show()
|
|
}
|
|
}
|
|
|
|
func (t *TenGame) winCheck(playerIntList []int, pos int) bool {
|
|
//slices.Sort(playerIntList)
|
|
|
|
return IsArithmeticSequence2(playerIntList, pos, t.winSum, int(t.itemX), int(t.itemY))
|
|
|
|
}
|
|
|
|
/*
|
|
0, 1, 2
|
|
3, 4, 5
|
|
6, 7, 8
|
|
*/
|
|
|
|
type pos struct {
|
|
x int
|
|
y int
|
|
}
|
|
|
|
/*
|
|
{0, 0}, {1, 0}, {2, 0}
|
|
{0, 1}, {1, 1}, {2, 1}
|
|
{0, 2}, {1, 2}, {2, 2}
|
|
|
|
0 1 2
|
|
3 4 5
|
|
6 7 8
|
|
*/
|
|
func int3pos(maxX, i int) pos {
|
|
return pos{x: i % maxX, y: i / maxX}
|
|
}
|
|
|
|
func pos2int(maxX int, pos pos) int {
|
|
return pos.x + pos.y*maxX
|
|
}
|
|
|
|
var lineDirection = [][]pos{
|
|
{{1, 1}, {-1, -1}},
|
|
{{1, -1}, {-1, 1}},
|
|
{{1, 0}, {-1, 0}},
|
|
{{0, 1}, {0, -1}},
|
|
}
|
|
|
|
func IsArithmeticSequence2(numbers []int, in int, diff int, mapX, mapY int) bool {
|
|
if len(numbers) <= diff-1 {
|
|
return false
|
|
}
|
|
inPos := int3pos(mapX, in)
|
|
for _, direction := range lineDirection {
|
|
var sum = 0
|
|
for _, idirec := range direction {
|
|
sum += lineSum(inPos, mapX, mapY, numbers, idirec)
|
|
}
|
|
if sum >= diff-1 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func lineSum(pos pos, mapX, mapY int, numbers []int, direction pos) (sum int) {
|
|
for {
|
|
pos.x += direction.x
|
|
pos.y += direction.y
|
|
if pos.x >= mapX || pos.y >= mapY || pos.x < 0 || pos.y < 0 {
|
|
return
|
|
}
|
|
if !slices.Contains(numbers, pos2int(mapX, pos)) {
|
|
return
|
|
}
|
|
sum++
|
|
}
|
|
}
|
|
|
|
// 画背景图
|
|
func drawABackgroundImage(size fyne.Size, itemX, itemY int) fyne.Resource {
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, int(size.Width), int(size.Height)))
|
|
draw.Draw(img, img.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
|
|
|
|
//itemX = itemX * 2
|
|
//itemY = itemY * 2
|
|
|
|
// 画横线
|
|
for ix := 1; ix < itemX; ix++ {
|
|
i1 := int(size.Height * (float32(ix) / float32(itemX)))
|
|
for x := 1; x < int(size.Width); x++ {
|
|
img.Set(x, i1, color.Black)
|
|
}
|
|
//ix++
|
|
}
|
|
|
|
// Draw vertical line
|
|
for ix := 1; ix < itemY; ix++ {
|
|
i2 := int(size.Width * (float32(ix) / float32(itemY)))
|
|
for y := 0; y < int(size.Height); y++ {
|
|
img.Set(i2, y, color.Black)
|
|
}
|
|
//ix++
|
|
}
|
|
|
|
buffer := new(bytes.Buffer)
|
|
err := png.Encode(buffer, img)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &fyne.StaticResource{
|
|
StaticName: "",
|
|
StaticContent: buffer.Bytes(),
|
|
}
|
|
}
|