Compare commits
11 Commits
e6a7b1342a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
c36d278a31
|
|||
|
|
9fe289a0ab
|
||
|
|
42b295413c
|
||
|
|
7dc26cf28a
|
||
|
|
88d58abc29
|
||
|
|
2c7b50e018
|
||
|
|
6f88306941
|
||
|
|
5d8fe93ffb | ||
|
|
d6495310c4
|
||
|
|
c01083f9a9
|
||
|
|
0e211e15be
|
19
.github/workflows/go.yml
vendored
Normal file
19
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
24
.github/workflows/release.yml
vendored
Normal file
24
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Build and Release Go Binary
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
# TODO: enable for macos and windows
|
||||
|
||||
jobs:
|
||||
release-linux-amd64:
|
||||
name: release linux/amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@
|
||||
# Dependency directories
|
||||
/Godeps/
|
||||
|
||||
go-mines
|
||||
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Syed
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
54
README.md
Normal file
54
README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Minesweeper (Terminal Edition)
|
||||
|
||||
A terminal-based Minesweeper game written in Go, using the `tcell` library for handling terminal UI and events.
|
||||
|
||||
## Features
|
||||
- Classic Minesweeper gameplay
|
||||
- Intuitive keyboard controls
|
||||
- Terminal-based UI
|
||||
- Real-time updates with `tcell`
|
||||
|
||||
## Some screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
### Prerequisites
|
||||
- Go 1.18+ installed
|
||||
|
||||
### Steps
|
||||
1. Clone this repository:
|
||||
```sh
|
||||
git clone https://github.com/Toprun123/go-mines.git
|
||||
cd go-mines
|
||||
```
|
||||
2. Install dependencies:
|
||||
```sh
|
||||
go get ./...
|
||||
```
|
||||
3. Build the project:
|
||||
```sh
|
||||
go build -o go-mines
|
||||
```
|
||||
4. Run the game:
|
||||
```sh
|
||||
./go-mines
|
||||
```
|
||||
|
||||
## Controls
|
||||
- `Arrow Keys` - Move cursor
|
||||
- `Enter` - Reveal cell
|
||||
- `F` - Flag/Unflag cell
|
||||
- `Q` - Quit game
|
||||
|
||||
Or Mouse Clicks (Left Click to Reveal, Right Click to Flag/Unflag)
|
||||
|
||||
## Contributing
|
||||
Feel free to open issues or submit pull requests!
|
||||
|
||||
## Author
|
||||
Syed - https://github.com/Toprun123
|
||||
4
go.mod
4
go.mod
@@ -1,10 +1,10 @@
|
||||
module github.com/Toprun123/go-mines
|
||||
|
||||
go 1.23.4
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.7.4 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.7.4 // direct
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
|
||||
BIN
images/1.png
Normal file
BIN
images/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
images/2.png
Normal file
BIN
images/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
images/3.png
Normal file
BIN
images/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
85
main.go
85
main.go
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
var start, pline int
|
||||
var offx, offy int
|
||||
var fx, fy, sx, sy, mines int
|
||||
var fx, fy, sx, sy, ax, ay, mines int
|
||||
var screen tcell.Screen
|
||||
var gameTime time.Time
|
||||
var timr int
|
||||
@@ -27,6 +27,7 @@ var mouseThing string
|
||||
var asciiDigits map[rune][]string
|
||||
var gameOver bool
|
||||
var flags int
|
||||
var board [][]cell
|
||||
|
||||
type cell struct {
|
||||
err bool
|
||||
@@ -35,6 +36,7 @@ type cell struct {
|
||||
reveal bool
|
||||
isMine bool
|
||||
color string
|
||||
sel bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -116,6 +118,8 @@ func main() {
|
||||
pline = 7
|
||||
fx = -1
|
||||
fy = -1
|
||||
ax = 0
|
||||
ay = 0
|
||||
mines = *m_o
|
||||
sx = *sx_o
|
||||
sy = *sy_o
|
||||
@@ -132,7 +136,7 @@ func main() {
|
||||
}
|
||||
offx = width/2 - (sx*5)/2
|
||||
offy = height/2 - (sy*3-3)/2
|
||||
board, _ := gen_board(sx, sy, mines, fx, fy)
|
||||
board, _ = gen_board(sx, sy, mines, fx, fy)
|
||||
refresh(board)
|
||||
screen.Show()
|
||||
go func() {
|
||||
@@ -145,15 +149,54 @@ func main() {
|
||||
ev := screen.PollEvent()
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventKey:
|
||||
if ev.Rune() == 'l' || ev.Rune() == 'q' {
|
||||
switch ev.Rune() {
|
||||
case 'l', 'q':
|
||||
return
|
||||
case 'f':
|
||||
if !gameOver {
|
||||
_ = flag_it(board, ax, ay, sx, sy)
|
||||
}
|
||||
if ev.Key() == tcell.KeyCtrlC {
|
||||
case 'r', ' ':
|
||||
if !gameOver {
|
||||
_ = reveal(board, ax, ay, sx, sy)
|
||||
}
|
||||
}
|
||||
switch ev.Key() {
|
||||
case tcell.KeyCtrlC:
|
||||
return
|
||||
case tcell.KeyLeft:
|
||||
board[ay][ax].sel = false
|
||||
ax--
|
||||
board[ay][ax].sel = true
|
||||
refresh(board)
|
||||
case tcell.KeyRight:
|
||||
board[ay][ax].sel = false
|
||||
ax++
|
||||
board[ay][ax].sel = true
|
||||
refresh(board)
|
||||
case tcell.KeyUp:
|
||||
board[ay][ax].sel = false
|
||||
ay--
|
||||
board[ay][ax].sel = true
|
||||
refresh(board)
|
||||
case tcell.KeyDown:
|
||||
board[ay][ax].sel = false
|
||||
ay++
|
||||
board[ay][ax].sel = true
|
||||
refresh(board)
|
||||
case tcell.KeyEnter:
|
||||
if fx == -1 && fy == -1 {
|
||||
fx = ax
|
||||
fy = ay
|
||||
board, _ = gen_board(sx, sy, mines, fx, fy)
|
||||
gameTime = time.Now()
|
||||
}
|
||||
if !gameOver {
|
||||
_ = reveal(board, ax, ay, sx, sy)
|
||||
}
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
x, y := ev.Position()
|
||||
doit := true
|
||||
button := buttonify(ev.Buttons())
|
||||
if x >= offx+(sx*5/2)-3 && x <= offx+(sx*5/2)+3 && y >= offy-4 && y <= offy-3 && button == "L" {
|
||||
fx = -1
|
||||
@@ -164,14 +207,13 @@ func main() {
|
||||
board, _ := gen_board(sx, sy, mines, fx, fy)
|
||||
refresh(board)
|
||||
screen.Show()
|
||||
doit = false
|
||||
continue
|
||||
}
|
||||
x = x - offx
|
||||
y = y - offy
|
||||
boardX := int(math.Floor(float64(x) / 5))
|
||||
boardY := int(math.Floor(float64(y) / 3))
|
||||
if fx == -1 && fy == -1 && (button == "L" || button == "R") && doit {
|
||||
if fx == -1 && fy == -1 && (button == "L" || button == "R") {
|
||||
fx = boardX
|
||||
fy = boardY
|
||||
board, _ = gen_board(sx, sy, mines, fx, fy)
|
||||
@@ -346,12 +388,12 @@ func flag_it(board [][]cell, x, y, width, height int) error {
|
||||
}
|
||||
|
||||
func gen_board(width, height, mines, fx, fy int) ([][]cell, error) {
|
||||
board := make([][]cell, height)
|
||||
for i := range board {
|
||||
board[i] = make([]cell, width)
|
||||
board2 := make([][]cell, height)
|
||||
for i := range board2 {
|
||||
board2[i] = make([]cell, width)
|
||||
}
|
||||
if mines >= width*height {
|
||||
return board, errors.New("too many mines")
|
||||
return board2, errors.New("too many mines")
|
||||
}
|
||||
source := rand.NewSource(time.Now().UnixNano())
|
||||
rand := rand.New(source)
|
||||
@@ -359,17 +401,20 @@ func gen_board(width, height, mines, fx, fy int) ([][]cell, error) {
|
||||
for mineCount < mines {
|
||||
x := rand.Intn(width)
|
||||
y := rand.Intn(height)
|
||||
if (x == fx && y == fy) || board[y][x].isMine {
|
||||
if (x == fx && y == fy) || board2[y][x].isMine {
|
||||
continue
|
||||
}
|
||||
board[y][x].isMine = true
|
||||
board2[y][x].isMine = true
|
||||
mineCount++
|
||||
}
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
if board[y][x].isMine {
|
||||
if board2[y][x].isMine {
|
||||
continue
|
||||
}
|
||||
if ax == x && ay == y {
|
||||
board2[y][x].sel = true
|
||||
}
|
||||
directions := []struct{ dx, dy int }{
|
||||
{-1, -1}, {0, -1}, {1, -1},
|
||||
{-1, 0}, {1, 0},
|
||||
@@ -378,11 +423,11 @@ func gen_board(width, height, mines, fx, fy int) ([][]cell, error) {
|
||||
count := 0
|
||||
for _, dir := range directions {
|
||||
nx, ny := x+dir.dx, y+dir.dy
|
||||
if nx >= 0 && nx < width && ny >= 0 && ny < height && board[ny][nx].isMine {
|
||||
if nx >= 0 && nx < width && ny >= 0 && ny < height && board2[ny][nx].isMine {
|
||||
count++
|
||||
}
|
||||
}
|
||||
board[y][x].mines = count
|
||||
board2[y][x].mines = count
|
||||
colors := map[int]string{
|
||||
0: "#41e8b0",
|
||||
1: "#7cc7ff",
|
||||
@@ -394,10 +439,10 @@ func gen_board(width, height, mines, fx, fy int) ([][]cell, error) {
|
||||
7: "#999999",
|
||||
8: "#d0d8e0",
|
||||
}
|
||||
board[y][x].color = colors[board[y][x].mines]
|
||||
board2[y][x].color = colors[board2[y][x].mines]
|
||||
}
|
||||
}
|
||||
return board, nil
|
||||
return board2, nil
|
||||
}
|
||||
|
||||
func print_board(board [][]cell, solved int) {
|
||||
@@ -461,6 +506,10 @@ func print_board(board [][]cell, solved int) {
|
||||
style = tcell.StyleDefault.Background(tcell.GetColor("#384048")).Foreground(tcell.GetColor("#1f272f")).Bold(true)
|
||||
style2 = tcell.StyleDefault.Background(tcell.GetColor("#384048")).Foreground(tcell.GetColor(board[i][j].color)).Bold(true)
|
||||
}
|
||||
// Chaange color of border if selected
|
||||
if board[i][j].sel {
|
||||
style = style.Foreground(tcell.GetColor("#ff7d7d"))
|
||||
}
|
||||
print_at(j*5+offx, i*3+1+offy, fmt.Sprint(" ", symbol, " "), style2)
|
||||
draw_box(j*5+offx, i*3+offy, j*5+4+offx, i*3+2+offy, style, chars_i)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user