mostly works but somehow pressing R to restart doesnt
This commit is contained in:
parent
d99d0690e2
commit
4f3fd8e038
191
_examplemouse.go
191
_examplemouse.go
@ -1,191 +0,0 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/awesome-gocui/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, true)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = false
|
||||
g.Mouse = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
var initialMouseX, initialMouseY, xOffset, yOffset int
|
||||
var globalMouseDown, msgMouseDown, movingMsg bool
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if _, err := g.View("msg"); msgMouseDown && err == nil {
|
||||
moveMsg(g)
|
||||
}
|
||||
if v, err := g.SetView("global", -1, -1, maxX, maxY, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
}
|
||||
if v, err := g.SetView("but1", 2, 2, 22, 7, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.SelBgColor = gocui.ColorGreen
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, "Button 1 - line 1")
|
||||
fmt.Fprintln(v, "Button 1 - line 2")
|
||||
fmt.Fprintln(v, "Button 1 - line 3")
|
||||
fmt.Fprintln(v, "Button 1 - line 4")
|
||||
if _, err := g.SetCurrentView("but1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v, err := g.SetView("but2", 24, 2, 44, 4, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.SelBgColor = gocui.ColorGreen
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, "Button 2 - line 1")
|
||||
}
|
||||
updateHighlightedView(g)
|
||||
return nil
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range []string{"but1", "but2"} {
|
||||
if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, globalDown); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, msgDown); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
if _, err := g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
l = ""
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2, 0); err == nil || errors.Is(err, gocui.ErrUnknownView) {
|
||||
v.Clear()
|
||||
v.SelBgColor = gocui.ColorCyan
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateHighlightedView(g *gocui.Gui) {
|
||||
mx, my := g.MousePosition()
|
||||
for _, view := range g.Views() {
|
||||
view.Highlight = false
|
||||
}
|
||||
if v, err := g.ViewByPosition(mx, my); err == nil {
|
||||
v.Highlight = true
|
||||
}
|
||||
}
|
||||
|
||||
func moveMsg(g *gocui.Gui) {
|
||||
mx, my := g.MousePosition()
|
||||
if !movingMsg && (mx != initialMouseX || my != initialMouseY) {
|
||||
movingMsg = true
|
||||
}
|
||||
g.SetView("msg", mx-xOffset, my-yOffset, mx-xOffset+20, my-yOffset+2, 0)
|
||||
}
|
||||
|
||||
func msgDown(g *gocui.Gui, v *gocui.View) error {
|
||||
initialMouseX, initialMouseY = g.MousePosition()
|
||||
if vx, vy, _, _, err := g.ViewPosition("msg"); err == nil {
|
||||
xOffset = initialMouseX - vx
|
||||
yOffset = initialMouseY - vy
|
||||
msgMouseDown = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func globalDown(g *gocui.Gui, v *gocui.View) error {
|
||||
mx, my := g.MousePosition()
|
||||
if vx0, vy0, vx1, vy1, err := g.ViewPosition("msg"); err == nil {
|
||||
if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 {
|
||||
return msgDown(g, v)
|
||||
}
|
||||
}
|
||||
globalMouseDown = true
|
||||
maxX, _ := g.Size()
|
||||
msg := fmt.Sprintf("Mouse down at: %d,%d", mx, my)
|
||||
x := mx - len(msg)/2
|
||||
if x < 0 {
|
||||
x = 0
|
||||
} else if x+len(msg)+1 > maxX-1 {
|
||||
x = maxX - 1 - len(msg) - 1
|
||||
}
|
||||
if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.WriteString(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mouseUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if msgMouseDown {
|
||||
msgMouseDown = false
|
||||
if movingMsg {
|
||||
movingMsg = false
|
||||
return nil
|
||||
} else {
|
||||
g.DeleteView("msg")
|
||||
}
|
||||
} else if globalMouseDown {
|
||||
globalMouseDown = false
|
||||
g.DeleteView("globalDown")
|
||||
}
|
||||
return nil
|
||||
}
|
13
go.mod
13
go.mod
@ -1,3 +1,16 @@
|
||||
module gitea.boner.be/bdnugget/minesweeper
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require github.com/awesome-gocui/gocui v1.1.0
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.8.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
||||
|
89
go.sum
Normal file
89
go.sum
Normal file
@ -0,0 +1,89 @@
|
||||
github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII=
|
||||
github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
398
main.go
398
main.go
@ -1,9 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/awesome-gocui/gocui"
|
||||
)
|
||||
|
||||
type gameState struct {
|
||||
@ -13,6 +18,9 @@ type gameState struct {
|
||||
numMines int
|
||||
field [][]point
|
||||
gameStartedAt time.Time
|
||||
firstMove bool
|
||||
cursorX int
|
||||
cursorY int
|
||||
}
|
||||
|
||||
type point struct {
|
||||
@ -34,47 +42,385 @@ func (gs *gameState) applyToNeighbours(x, y int, do func(x, y int)) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *gameState) printField() {
|
||||
for _, row := range gs.field {
|
||||
for _, cell := range row {
|
||||
if cell.isMine {
|
||||
fmt.Print("*")
|
||||
} else {
|
||||
fmt.Print(cell.numNeighbouringMines)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
// Colors using gocui's attributes
|
||||
var numberColors = []gocui.Attribute{
|
||||
gocui.ColorBlue, // 1
|
||||
gocui.ColorGreen, // 2
|
||||
gocui.ColorRed, // 3
|
||||
gocui.ColorMagenta, // 4
|
||||
gocui.ColorYellow, // 5
|
||||
gocui.ColorCyan, // 6
|
||||
gocui.ColorWhite, // 7
|
||||
gocui.ColorDefault, // 8
|
||||
}
|
||||
|
||||
func (gs *gameState) initField() {
|
||||
// Restore emoji cell representations
|
||||
var (
|
||||
emojiUnopened = "🟦 " // Note the trailing space for consistent width
|
||||
emojiMarked = "🚩 " // Note the trailing space
|
||||
emojiMine = "💣 " // Note the trailing space
|
||||
emojiEmpty = " " // Three spaces
|
||||
)
|
||||
|
||||
func (gs *gameState) printField(v *gocui.View) {
|
||||
v.Clear()
|
||||
|
||||
// Build the entire board string first to avoid partial rendering issues
|
||||
var board strings.Builder
|
||||
|
||||
for i := range gs.field {
|
||||
for j := range gs.field[i] {
|
||||
cell := gs.field[i][j]
|
||||
|
||||
if !cell.isOpen {
|
||||
if cell.isMarked {
|
||||
board.WriteString(emojiMarked) // Fixed-width emoji + space
|
||||
} else {
|
||||
board.WriteString(emojiUnopened) // Fixed-width emoji + space
|
||||
}
|
||||
} else {
|
||||
if cell.isMine {
|
||||
board.WriteString(emojiMine) // Fixed-width emoji + space
|
||||
} else if cell.numNeighbouringMines > 0 {
|
||||
num := cell.numNeighbouringMines
|
||||
// Format numbers to take exactly the same width as emojis (3 chars)
|
||||
board.WriteString(fmt.Sprintf(" %d ", num))
|
||||
} else {
|
||||
board.WriteString(emojiEmpty) // Three spaces
|
||||
}
|
||||
}
|
||||
}
|
||||
board.WriteString("\n") // End of row
|
||||
}
|
||||
|
||||
// Write the entire board at once
|
||||
fmt.Fprint(v, board.String())
|
||||
}
|
||||
|
||||
func (gs *gameState) initField(safeX, safeY int) {
|
||||
gs.field = make([][]point, gs.rows)
|
||||
for i := range gs.field {
|
||||
gs.field[i] = make([]point, gs.cols)
|
||||
}
|
||||
|
||||
for i := range gs.numMines {
|
||||
ranX := rand.IntN(gs.rows -1)
|
||||
ranY := rand.IntN(gs.cols-1)
|
||||
if !gs.field[ranX][ranY].isMine {
|
||||
gs.field[ranX][ranY].isMine = true
|
||||
gs.applyToNeighbours(ranX, ranY, func(x, y int) {
|
||||
gs.field[x][y].numNeighbouringMines++
|
||||
})
|
||||
} else {
|
||||
i--
|
||||
// Create a list of all possible mine positions except the first clicked cell and its neighbors
|
||||
possiblePositions := make([]struct{ x, y int }, 0, gs.rows*gs.cols)
|
||||
for i := 0; i < gs.rows; i++ {
|
||||
for j := 0; j < gs.cols; j++ {
|
||||
// Skip the safe cell and its neighbors
|
||||
if (i == safeX && j == safeY) || isCellNeighbor(i, j, safeX, safeY) {
|
||||
continue
|
||||
}
|
||||
possiblePositions = append(possiblePositions, struct{ x, y int }{i, j})
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle the positions
|
||||
for i := range possiblePositions {
|
||||
j := rand.IntN(len(possiblePositions))
|
||||
possiblePositions[i], possiblePositions[j] = possiblePositions[j], possiblePositions[i]
|
||||
}
|
||||
|
||||
// Place mines using the first numMines positions
|
||||
minesPlaced := 0
|
||||
for i := 0; i < len(possiblePositions) && minesPlaced < gs.numMines; i++ {
|
||||
pos := possiblePositions[i]
|
||||
gs.field[pos.x][pos.y].isMine = true
|
||||
gs.applyToNeighbours(pos.x, pos.y, func(x, y int) {
|
||||
gs.field[x][y].numNeighbouringMines++
|
||||
})
|
||||
minesPlaced++
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *gameState) layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
|
||||
// Calculate game view dimensions based on the grid size
|
||||
// Each cell is exactly 3 characters wide (emoji + space)
|
||||
gameWidth := gs.cols*3 + 2 // +2 for the frame
|
||||
gameHeight := gs.rows + 2 // +2 for the frame (reduced padding)
|
||||
|
||||
// Center the game view precisely
|
||||
gameX := (maxX - gameWidth) / 2
|
||||
gameY := (maxY - gameHeight - 3) / 2 // Adjust position to center vertically, leaving room for restart button
|
||||
|
||||
// Game view - centered with proper spacing
|
||||
if v, err := g.SetView("game", gameX, gameY, gameX+gameWidth, gameY+gameHeight, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.Title = "Minesweeper"
|
||||
v.Clear()
|
||||
// Remove the blank line padding that was causing extra space
|
||||
gs.printField(v)
|
||||
if _, err := g.SetCurrentView("game"); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Editor = gocui.EditorFunc(gs.gameEditor)
|
||||
v.Editable = true
|
||||
}
|
||||
|
||||
// Restart button - positioned below the game view
|
||||
buttonWidth := 14
|
||||
buttonY := gameY + gameHeight + 1 // Position right below the game view
|
||||
if v, err := g.SetView("restart", maxX/2-buttonWidth/2, buttonY, maxX/2+buttonWidth/2, buttonY+2, 0); err != nil {
|
||||
if !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
v.Title = "Restart"
|
||||
fmt.Fprintln(v, " [R] ")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gs *gameState) keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, gs.quit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Left click to open cells
|
||||
if err := g.SetKeybinding("game", gocui.MouseLeft, gocui.ModNone, gs.handleMouseClick); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Right click to mark cells
|
||||
if err := g.SetKeybinding("game", gocui.MouseRight, gocui.ModNone, gs.handleRightClick); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 'R' key to restart the game - making it lowercase and uppercase
|
||||
if err := g.SetKeybinding("", 'r', gocui.ModNone, gs.restart); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'R', gocui.ModNone, gs.restart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mouse binding for restart button
|
||||
if err := g.SetKeybinding("restart", gocui.MouseLeft, gocui.ModNone, gs.restart); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gs *gameState) quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
// Use the MousePosition method from gocui instead of view cursor
|
||||
func (gs *gameState) handleMouseClick(g *gocui.Gui, v *gocui.View) error {
|
||||
// Get global mouse position
|
||||
mx, my := g.MousePosition()
|
||||
|
||||
// Get view's position
|
||||
vx0, vy0, _, _, err := g.ViewPosition("game")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate local position within the view (accounting for frame)
|
||||
localX := mx - vx0 - 1 // -1 for left frame
|
||||
localY := my - vy0 - 1 // -1 for top frame
|
||||
|
||||
// Calculate cell coordinates
|
||||
cellX := localY
|
||||
cellY := localX / 3
|
||||
|
||||
// Proceed with the game logic
|
||||
if cellX >= 0 && cellX < gs.rows && cellY >= 0 && cellY < gs.cols {
|
||||
// If it's the first move, initialize the field after the click
|
||||
if gs.firstMove {
|
||||
gs.initField(cellX, cellY) // Pass click coordinates to initField
|
||||
gs.firstMove = false
|
||||
gs.gameStartedAt = time.Now() // Reset the start time to when the game actually begins
|
||||
}
|
||||
|
||||
if !gs.field[cellX][cellY].isOpen && !gs.field[cellX][cellY].isMarked {
|
||||
if gs.field[cellX][cellY].isMine {
|
||||
gs.revealAllMines()
|
||||
gs.isGameOver = true
|
||||
} else {
|
||||
gs.openCell(cellX, cellY)
|
||||
}
|
||||
gs.printField(v)
|
||||
|
||||
gs.updateGameState(g)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle keyboard input for the game
|
||||
func (gs *gameState) gameEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
|
||||
// Handle keyboard input for the game
|
||||
if key == gocui.KeyArrowUp {
|
||||
// Move cursor up
|
||||
} else if key == gocui.KeyArrowDown {
|
||||
// Move cursor down
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
|
||||
func (gs *gameState) openCell(x, y int) {
|
||||
if x < 0 || y < 0 || x >= gs.rows || y >= gs.cols || gs.field[x][y].isOpen {
|
||||
return // Bounds check and already opened check
|
||||
}
|
||||
|
||||
gs.field[x][y].isOpen = true
|
||||
|
||||
if gs.field[x][y].numNeighbouringMines == 0 {
|
||||
gs.applyToNeighbours(x, y, gs.openCell) // Recursively open neighbours if no adjacent mines
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *gameState) revealAllMines() {
|
||||
for i := 0; i < gs.rows; i++ {
|
||||
for j := 0; j < gs.cols; j++ {
|
||||
if gs.field[i][j].isMine {
|
||||
gs.field[i][j].isOpen = true // Reveal all mines
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *gameState) isGameWon() bool {
|
||||
openedCells := 0
|
||||
for i := 0; i < gs.rows; i++ {
|
||||
for j := 0; j < gs.cols; j++ {
|
||||
if gs.field[i][j].isOpen && !gs.field[i][j].isMine {
|
||||
openedCells++
|
||||
}
|
||||
}
|
||||
}
|
||||
return openedCells == (gs.rows*gs.cols - gs.numMines) // Check if all non-mine cells are opened
|
||||
}
|
||||
|
||||
// Helper function to check if a cell is a neighbor of another cell
|
||||
func isCellNeighbor(x1, y1, x2, y2 int) bool {
|
||||
return abs(x1-x2) <= 1 && abs(y1-y2) <= 1
|
||||
}
|
||||
|
||||
// Helper function for absolute value
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (gs *gameState) restart(g *gocui.Gui, v *gocui.View) error {
|
||||
// Reset game state
|
||||
gs.isGameOver = false
|
||||
gs.firstMove = true
|
||||
gs.gameStartedAt = time.Now()
|
||||
gs.field = make([][]point, gs.rows) // Clear field
|
||||
for i := range gs.field {
|
||||
gs.field[i] = make([]point, gs.cols)
|
||||
}
|
||||
|
||||
// Delete any message view
|
||||
g.DeleteView("message")
|
||||
|
||||
// Find the game view and refresh it
|
||||
if gameView, err := g.View("game"); err == nil {
|
||||
gs.printField(gameView)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a new function to handle right-clicks for marking cells
|
||||
func (gs *gameState) handleRightClick(g *gocui.Gui, v *gocui.View) error {
|
||||
// Get global mouse position
|
||||
mx, my := g.MousePosition()
|
||||
|
||||
// Get view's position
|
||||
vx0, vy0, _, _, err := g.ViewPosition("game")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate local position within the view (accounting for frame)
|
||||
localX := mx - vx0 - 1 // -1 for left frame
|
||||
localY := my - vy0 - 1 // -1 for top frame
|
||||
|
||||
// Calculate cell coordinates
|
||||
cellX := localY
|
||||
cellY := localX / 3
|
||||
|
||||
// Proceed with the game logic
|
||||
if cellX >= 0 && cellX < gs.rows && cellY >= 0 && cellY < gs.cols {
|
||||
// Can only mark cells that aren't already open
|
||||
if !gs.field[cellX][cellY].isOpen {
|
||||
// Toggle the marked state
|
||||
gs.field[cellX][cellY].isMarked = !gs.field[cellX][cellY].isMarked
|
||||
gs.printField(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a dedicated function for game state changes
|
||||
func (gs *gameState) updateGameState(g *gocui.Gui) {
|
||||
if gs.isGameWon() {
|
||||
gs.revealAllMines()
|
||||
gs.isGameOver = true
|
||||
gs.showMessage(g, "YEET DAB!", "Congratulation! You're winRAR!\nR to restart or Ctrl+C to ragequit")
|
||||
} else if gs.isGameOver {
|
||||
gs.showMessage(g, "OH SHIET!", "You dun goofed!\nR to restart or Ctrl+C to ragequit")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for message display
|
||||
func (gs *gameState) showMessage(g *gocui.Gui, title, message string) error {
|
||||
maxX, maxY := g.Size()
|
||||
width, height := 40, 5 // Slightly larger for better appearance
|
||||
|
||||
msgView, err := g.SetView("message", maxX/2-width/2, maxY/2-height/2,
|
||||
maxX/2+width/2, maxY/2+height/2, 0)
|
||||
if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
|
||||
return err
|
||||
}
|
||||
|
||||
msgView.Title = title
|
||||
msgView.Clear()
|
||||
|
||||
// Add padding for better appearance
|
||||
fmt.Fprintln(msgView, "")
|
||||
fmt.Fprintln(msgView, " "+message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, true)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = false
|
||||
g.Mouse = true
|
||||
|
||||
gs := &gameState{
|
||||
rows: 10,
|
||||
cols: 10,
|
||||
numMines: 5,
|
||||
numMines: 10,
|
||||
gameStartedAt: time.Now(),
|
||||
firstMove: true,
|
||||
}
|
||||
|
||||
gs.initField()
|
||||
gs.printField()
|
||||
gs.initField(0, 0)
|
||||
|
||||
g.SetManagerFunc(gs.layout)
|
||||
|
||||
if err := gs.keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user