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(), } }