├── .gitignore ├── main.go ├── go.mod ├── Dockerfile ├── README.md ├── go.sum └── game ├── manage.go └── component ├── monitorApp.go ├── gameService.go ├── screenApp.go ├── game.go └── initScreen.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "snake/game" 5 | ) 6 | 7 | func main() { 8 | game.Start() 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module snake 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/mattn/go-runewidth v0.0.13 7 | github.com/nsf/termbox-go v1.1.1 8 | ) 9 | 10 | require github.com/rivo/uniseg v0.2.0 // indirect 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | ENV GO111MODULE="on" GOPROXY=https://goproxy.cn,https://goproxy.io,direct 4 | 5 | WORKDIR /app 6 | 7 | COPY . /app 8 | 9 | RUN go build . 10 | 11 | 12 | 13 | ENTRYPOINT ["./snake"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang_snake 2 | 3 | Golang version snake game ^_^ 4 | 5 | ## Demonstration 6 | 7 | 8 | 9 | ## Operation 10 | `keyboard: ↑:up, ↓:down, ←:left, →:right,esc:closing page` 11 | 12 | `键盘: ↑:蛇向上, ↓:蛇向下, ←:蛇向左, →:蛇向右,esc:关闭当前页面` 13 | 14 | ## Run 15 | `go run main.go` 16 | 17 | ## Docker run 18 | `step 1 : ./build.sh` 19 | 20 | `step 2 : docker run -ti --rm snake:v1` -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 2 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 3 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 4 | github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= 5 | github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= 6 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 7 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 8 | -------------------------------------------------------------------------------- /game/manage.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | "snake/game/component" 8 | ) 9 | 10 | // Start starting snake program 11 | // 12 | // @Description: 13 | func Start() { 14 | 15 | //捕获异常 16 | defer recoverFailed() 17 | 18 | //运行游戏 19 | component.NewGameService().Start() 20 | } 21 | 22 | // recoverFailed 23 | // 24 | // @Description: 25 | func recoverFailed() { 26 | if pM := recover(); pM != nil { 27 | fmt.Println(pM) 28 | fmt.Println(PanicTrace()) 29 | } 30 | } 31 | 32 | func PanicTrace() string { 33 | buf := new(bytes.Buffer) 34 | for i := 1; ; i++ { 35 | pc, file, line, ok := runtime.Caller(i) 36 | if !ok { 37 | break 38 | } 39 | fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 40 | } 41 | return buf.String() 42 | } 43 | -------------------------------------------------------------------------------- /game/component/monitorApp.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import "github.com/nsf/termbox-go" 4 | 5 | //main 6 | const ( 7 | RIGHT = 1 + iota 8 | LEFT 9 | UP 10 | DOWN 11 | QUIT 12 | ) 13 | 14 | // Monitor 15 | // 16 | // @Description: 17 | type Monitor interface { 18 | // start 19 | // 20 | // @Description: 启动监控 21 | // @return *monitorApp 22 | start() *monitorApp 23 | } 24 | 25 | // monitorApp 26 | // @Description: 27 | type monitorApp struct { 28 | move chan int 29 | quit chan int 30 | } 31 | 32 | //newMonitorApp 初始化监控应用 33 | func newMonitorApp() Monitor { 34 | return &monitorApp{make(chan int), make(chan int)} 35 | } 36 | 37 | //handle 启动监听 38 | func (s *monitorApp) start() *monitorApp { 39 | //异步监控 40 | go s.initMonitor() 41 | return s 42 | } 43 | 44 | // initMonitor 45 | // 46 | // @Description: 47 | // @receiver s 48 | // @param monitorChan 49 | // @param quit 50 | func (s *monitorApp) initMonitor() { 51 | termbox.SetInputMode(termbox.InputEsc) 52 | 53 | for { 54 | switch ev := termbox.PollEvent(); ev.Type { 55 | case termbox.EventKey: 56 | switch ev.Key { 57 | case termbox.KeyArrowLeft: 58 | s.move <- LEFT 59 | case termbox.KeyArrowDown: 60 | s.move <- DOWN 61 | case termbox.KeyArrowRight: 62 | s.move <- RIGHT 63 | case termbox.KeyArrowUp: 64 | s.move <- UP 65 | case termbox.KeyEsc: 66 | s.quit <- QUIT 67 | } 68 | case termbox.EventError: 69 | panic(ev.Err) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /game/component/gameService.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "github.com/nsf/termbox-go" 5 | ) 6 | 7 | // Game 游戏服务接口 8 | // 9 | // @Description: 10 | type Game interface { 11 | Start() 12 | } 13 | 14 | //gameService 定义游戏实体 15 | type gameService struct { 16 | // 17 | // screenApp 18 | // @Description: 屏幕 19 | // 20 | screenApp Screen 21 | 22 | // 23 | // monitorApp 24 | // @Description: 控制 25 | // 26 | monitorApp Monitor 27 | } 28 | 29 | //NewGameService 实例化游戏服务 30 | func NewGameService() Game { 31 | return &gameService{screenApp: newScreenApp(), monitorApp: newMonitorApp()} 32 | } 33 | 34 | //Start 开始游戏 35 | func (g *gameService) Start() { 36 | //初始化插件 37 | if initErr := termbox.Init(); initErr != nil { 38 | panic(initErr) 39 | } 40 | 41 | //函数退出时, 关闭包 rules 42 | defer termbox.Close() 43 | 44 | //游戏启动 45 | g.run(g.monitorApp.start()) 46 | } 47 | 48 | //run 游戏启动 49 | func (g *gameService) run(m *monitorApp) { 50 | //退出户标识 51 | CloseGame: 52 | for { 53 | select { 54 | 55 | //键盘移动事件 56 | case operator := <-m.move: 57 | g.screenApp.setDirection(operator) 58 | 59 | //点击ECS事件 60 | case <-m.quit: 61 | //退出刷新 62 | break CloseGame 63 | 64 | //游戏角色状态 65 | case status := <-g.screenApp.getSnakeStatusChan(): 66 | //设置人物状态 67 | g.screenApp.setGameOver(status) 68 | 69 | default: 70 | //如果蛇还没死 71 | if g.screenApp.getActivity() { 72 | //进行渲染界面 73 | if err := g.screenApp.start(); err != nil { 74 | panic(err.Error()) 75 | } 76 | } 77 | 78 | g.screenApp.flush() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /game/component/screenApp.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import "time" 4 | 5 | type Screen interface { 6 | // start 7 | // 8 | // @Description: 启动屏幕 9 | // @return error 10 | start() error 11 | 12 | // setDirection 13 | // 14 | // @Description:设置蛇的方向 15 | // @param operator 16 | setDirection(operator int) 17 | 18 | // getActivity 19 | // 20 | // @Description: 获取蛇是否还活着 21 | // @return bool 22 | getActivity() bool 23 | 24 | // getSnakeStatusChan 25 | // 26 | // @Description: 获取当前蛇的状态 27 | // @return <-chan 28 | getSnakeStatusChan() <-chan bool 29 | 30 | // setGameOver 31 | // 32 | // @Description: 设置游戏的状态 33 | // @param status 34 | setGameOver(status bool) 35 | 36 | // flush 37 | // 38 | // @Description: 刷新屏幕 39 | flush() 40 | } 41 | 42 | //screenApp 屏幕应用 43 | type screenApp struct { 44 | // 45 | // screen 46 | // @Description: 屏幕 47 | // 48 | screen screenFunType 49 | // 50 | // role 51 | // @Description: 角色 52 | // 53 | role *game 54 | } 55 | 56 | //newScreenApp 初始化屏幕应用 57 | func newScreenApp() Screen { 58 | return &screenApp{screen: initScreenHandle(), role: newGameData()} 59 | } 60 | 61 | //start 启动屏幕渲染 62 | func (s *screenApp) start() error { 63 | return s.screen(s.role) 64 | } 65 | 66 | // setDirection 67 | // 68 | // @Description: 69 | // @receiver s 70 | // @param operator 71 | func (s *screenApp) setDirection(operator int) { 72 | //给游戏控制设置方向 73 | s.role.getControl().setDirection(operator) 74 | } 75 | 76 | // getSnakeStatusChan 77 | // 78 | // @Description: 79 | // @receiver s 80 | // @return <-chan 81 | func (s *screenApp) getSnakeStatusChan() <-chan bool { 82 | return s.role.getControl().getSnakeStatusChan() 83 | } 84 | 85 | // setGameOver 86 | // 87 | // @Description: 88 | // @receiver s 89 | // @param status 90 | func (s *screenApp) setGameOver(status bool) { 91 | s.role.getControl().setGameOver(status) 92 | } 93 | 94 | // getActivity 95 | // 96 | // @Description: 97 | // @receiver s 98 | // @return bool 99 | func (s *screenApp) getActivity() bool { 100 | return s.role.getControl().getActivity() 101 | } 102 | 103 | // flush 104 | // 105 | // @Description: 106 | // @receiver s 107 | func (s *screenApp) flush() { 108 | time.Sleep(time.Duration(150-(*s.role.getScreen().getScore()/10)) * time.Millisecond) 109 | } 110 | -------------------------------------------------------------------------------- /game/component/game.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | //scope 4 | type scope struct { 5 | x int 6 | y int 7 | } 8 | 9 | //snake 10 | type snake struct { 11 | snakeBody []scope 12 | direction int 13 | len int 14 | } 15 | 16 | //setLen 蛇的长度 17 | func (s *snake) setLen(len int) { 18 | s.len = len 19 | } 20 | 21 | //setSnakeBody 设置蛇的身体 22 | func (s *snake) setSnakeBody(snakeBody []scope) { 23 | s.snakeBody = snakeBody 24 | } 25 | 26 | //setDirection 设置蛇要走的方向 27 | func (s *snake) setDirection(direction int) { 28 | s.direction = direction 29 | } 30 | 31 | //getSnakeBody 获取蛇的身体 32 | func (s snake) getSnakeBody() []scope { 33 | return s.snakeBody 34 | } 35 | 36 | //getDirection 方向 37 | func (s snake) getDirection() int { 38 | return s.direction 39 | } 40 | 41 | //getLen 长度 42 | func (s snake) getLen() int { 43 | return s.len 44 | } 45 | 46 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 47 | //screen 屏幕相关参数 48 | type screen struct { 49 | snakes *snake 50 | foodPoint *scope 51 | width int 52 | height int 53 | score int 54 | } 55 | 56 | //newScreen 实例化屏幕 57 | func newScreen() *screen { 58 | return &screen{snakes: new(snake), foodPoint: new(scope), width: 60, height: 30, score: 0} 59 | } 60 | 61 | //getScore 获取分数 62 | func (s *screen) getScore() *int { 63 | return &s.score 64 | } 65 | 66 | //setScore 设置得分 67 | func (s *screen) setScore(score int) { 68 | s.score = score 69 | } 70 | 71 | //setWidth 设置边框的宽度 72 | func (s *screen) setWidth(width int) { 73 | s.width = width 74 | } 75 | 76 | //setHeight 设置边框的高度 77 | func (s *screen) setHeight(height int) { 78 | s.height = height 79 | } 80 | 81 | //initScreenSize 实时获取盒子尺寸 82 | func (s *screen) initScreenSize() { 83 | 84 | //设置边框的宽 85 | s.setWidth(50) 86 | 87 | //设置边框的高 88 | s.setHeight(20) 89 | } 90 | 91 | //getSnakes 获取蛇 92 | func (s screen) getSnakes() *snake { 93 | return s.snakes 94 | } 95 | 96 | //getFoodPoint 获取食物的坐标 97 | func (s screen) getFoodPoint() *scope { 98 | return s.foodPoint 99 | } 100 | 101 | //getWidth 获取屏幕的狂赌 102 | func (s screen) getWidth() int { 103 | return s.width 104 | } 105 | 106 | //getHeight 获取屏幕的高度 107 | func (s screen) getHeight() int { 108 | return s.height 109 | } 110 | 111 | //运行时数据 112 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 113 | 114 | //control 控制 115 | type control struct { 116 | snakeStatusChannel chan bool 117 | gameOver bool 118 | direction int 119 | } 120 | 121 | //newControl 实例化控制器 122 | func newControl() *control { 123 | return &control{snakeStatusChannel: make(chan bool, 1), gameOver: false, direction: UP} 124 | } 125 | 126 | //setGameOver 设置蛇已死亡 127 | func (r *control) setGameOver(gameOver bool) { 128 | r.gameOver = gameOver 129 | } 130 | 131 | //setDirection 设置玩家方向指令 132 | func (r *control) setDirection(direction int) { 133 | r.direction = direction 134 | } 135 | 136 | //getSnakeStatusChan 游戏状态管道 137 | func (r *control) getSnakeStatusChan() chan bool { 138 | return r.snakeStatusChannel 139 | } 140 | 141 | //getActivity 如果蛇还在活跃中 142 | func (r *control) getActivity() bool { 143 | return r.gameOver == false 144 | } 145 | 146 | //getDirection 获取玩家的方向的指令 147 | func (r *control) getDirection() int { 148 | return r.direction 149 | } 150 | 151 | //游戏数据总览数据 152 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 153 | //game control 游戏数据结构 154 | type game struct { 155 | //控制 156 | control *control 157 | //屏幕 158 | screen *screen 159 | } 160 | 161 | // getControl 控制信息 162 | func (g *game) getControl() *control { 163 | return g.control 164 | } 165 | 166 | //getScreen 屏幕信息 167 | func (g *game) getScreen() *screen { 168 | return g.screen 169 | } 170 | 171 | //newGameData 实例化 172 | func newGameData() *game { 173 | return &game{control: newControl(), screen: newScreen()} 174 | } 175 | -------------------------------------------------------------------------------- /game/component/initScreen.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "errors" 5 | "github.com/mattn/go-runewidth" 6 | "github.com/nsf/termbox-go" 7 | "math/rand" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | //screenFunType 启动屏幕展示 13 | type screenFunType func(*game) error 14 | 15 | //snakeFunType 16 | type snakeFunType func(*game) 17 | 18 | //foodFunType 19 | type foodFunType func(int, int, *scope) 20 | 21 | //moveFunType 22 | type moveFunType func(int, int, chan bool, *snake, *int, *scope) 23 | 24 | //initFood 25 | func initFood() func(width int, height int, foodPoint *scope) { 26 | return func(width int, height int, foodPoint *scope) { 27 | if foodPoint.x == 0 && foodPoint.y == 0 { 28 | genFood(width, height, foodPoint) 29 | } 30 | } 31 | } 32 | 33 | //genFood 34 | func genFood(width int, height int, foodPoint *scope) { 35 | foodPoint.x = generateRandInt(1, width-1) 36 | foodPoint.y = generateRandInt(4, height-1) 37 | } 38 | 39 | //handle 40 | func handle(initSnake snakeFunType, initFood foodFunType, move moveFunType) screenFunType { 41 | return func(game *game) error { 42 | 43 | //游戏屏幕数据 44 | screen := game.getScreen() 45 | 46 | //动态设置表格(游戏中, 边框变动会进行动态调整) 47 | screen.initScreenSize() 48 | 49 | //初始化蛇 50 | initSnake(game) 51 | 52 | //初始化食物 53 | initFood(screen.getWidth(), screen.getHeight(), screen.getFoodPoint()) 54 | 55 | //初始化移动 56 | move(screen.getWidth(), screen.getHeight(), game.getControl().getSnakeStatusChan(), screen.getSnakes(), screen.getScore(), screen.getFoodPoint()) 57 | 58 | //拿到数据后,进行渲染界面 59 | return render(screen.getWidth(), screen.getHeight(), screen.getSnakes(), screen.getScore(), screen.getFoodPoint()) 60 | } 61 | } 62 | 63 | //render 64 | func render(width int, height int, snakes *snake, score *int, foodPoint *scope) error { 65 | 66 | if err := termbox.Clear(termbox.ColorDefault, termbox.ColorBlack); err != nil { 67 | return errors.New(err.Error()) 68 | } 69 | 70 | //middle number 71 | var midWidth = width/2 - 8 72 | 73 | //setting the title 74 | for _, s := range "Snake games" { 75 | termbox.SetCell(midWidth, 1, s, termbox.ColorLightRed, termbox.ColorBlack) 76 | midWidth += runewidth.RuneWidth(s) 77 | } 78 | 79 | //setting the score 80 | x := 2 81 | for _, si := range "score:" { 82 | termbox.SetCell(x, 2, si, termbox.ColorLightRed, termbox.ColorBlack) 83 | x += runewidth.RuneWidth(si) 84 | } 85 | for _, sii := range strconv.Itoa(*score) { 86 | termbox.SetCell(x, 2, sii, termbox.ColorLightRed, termbox.ColorBlack) 87 | x += runewidth.RuneWidth(sii) 88 | } 89 | 90 | //setting quit tips 91 | wTip := width - 14 - 2 92 | for _, si := range "press esc quit" { 93 | termbox.SetCell(wTip, 2, si, termbox.ColorLightRed, termbox.ColorBlack) 94 | wTip += runewidth.RuneWidth(si) 95 | } 96 | 97 | //set frame 98 | w := 0 99 | for w <= width { 100 | termbox.SetCell(w, 3, ' ', termbox.ColorGreen, termbox.ColorLightGreen) 101 | termbox.SetCell(w, height, ' ', termbox.ColorGreen, termbox.ColorLightGreen) 102 | w += runewidth.RuneWidth(' ') 103 | } 104 | h := 0 105 | for h <= height { 106 | termbox.SetCell(0, h, ' ', termbox.ColorGreen, termbox.ColorLightGreen) 107 | termbox.SetCell(width, h, ' ', termbox.ColorGreen, termbox.ColorLightGreen) 108 | h += runewidth.RuneWidth(' ') 109 | } 110 | 111 | //setting snake 112 | for _, body := range snakes.snakeBody { 113 | termbox.SetCell(body.x, body.y, ' ', termbox.ColorLightRed, termbox.ColorLightRed) 114 | } 115 | 116 | //setting food 117 | termbox.SetCell(foodPoint.x, foodPoint.y, '@', termbox.ColorLightRed, termbox.ColorDefault) 118 | 119 | return termbox.Flush() 120 | } 121 | 122 | // initMove initMonitor for user keyboard 123 | func initMove() moveFunType { 124 | return func(width int, height int, runtimeChan chan bool, snakes *snake, score *int, foodPoint *scope) { 125 | move(width, height, runtimeChan, snakes, score, foodPoint) 126 | } 127 | } 128 | 129 | //isDeath 130 | func isDeath(width int, height int, snakes *snake) bool { 131 | 132 | s := head(snakes) 133 | 134 | return s.x >= width || s.y >= height || s.x <= 0 || s.y <= 3 135 | } 136 | 137 | //move 138 | func move(width int, height int, runtimeChan chan bool, snakes *snake, score *int, foodPoint *scope) { 139 | scopes := head(snakes) 140 | 141 | switch snakes.direction { 142 | case RIGHT: 143 | scopes.x++ 144 | case LEFT: 145 | scopes.x-- 146 | case UP: 147 | scopes.y-- 148 | case DOWN: 149 | scopes.y++ 150 | } 151 | 152 | if isDeath(width, height, snakes) { 153 | runtimeChan <- true 154 | return 155 | } 156 | 157 | if scopes.x == foodPoint.x && scopes.y == foodPoint.y { 158 | snakes.len++ 159 | *score++ 160 | 161 | genFood(width, height, foodPoint) 162 | } 163 | 164 | if snakes.len > len(snakes.snakeBody) { 165 | snakes.snakeBody = append(snakes.snakeBody, scopes) 166 | } else { 167 | snakes.snakeBody = append(snakes.snakeBody[1:], scopes) 168 | } 169 | } 170 | 171 | //head 172 | func head(snakes *snake) scope { 173 | return snakes.snakeBody[len(snakes.snakeBody)-1] 174 | } 175 | 176 | //generateRandInt 177 | func generateRandInt(min, max int) int { 178 | rand.Seed(time.Now().Unix()) 179 | 180 | n := max - min 181 | if n > 0 { 182 | return rand.Intn(n) + min 183 | } 184 | 185 | return 1 186 | } 187 | 188 | //initSnake snake 189 | func initSnake() snakeFunType { 190 | return func(gameData *game) { 191 | 192 | //屏幕信息 193 | g := gameData.getScreen() 194 | 195 | //蛇的信息 196 | s := g.getSnakes() 197 | 198 | //如果蛇不存在, 需要初始化 199 | if len(s.getSnakeBody()) == 0 { 200 | 201 | //进行初始化body 202 | s.setSnakeBody(append(s.getSnakeBody(), scope{5, g.getHeight() - 2})) 203 | s.setSnakeBody(append(s.getSnakeBody(), scope{5, g.getHeight() - 3})) 204 | 205 | //初始化蛇的长度 206 | s.setLen(2) 207 | } 208 | 209 | //设置蛇跑的方向 210 | s.setDirection(gameData.getControl().getDirection()) 211 | } 212 | } 213 | 214 | //initScreenHandle 初始化屏幕 215 | func initScreenHandle() screenFunType { 216 | return handle(initSnake(), initFood(), initMove()) 217 | } 218 | --------------------------------------------------------------------------------