golang实现跨平台单文件无依赖贪吃蛇游戏
- 作者:
- 淡白
- 创建时间:
- 2022-03-29 20:30:54
- Go c
摘要:该代码是一个简单的贪吃蛇游戏的实现。主要包含以下几个部分: 1. 声明了一些需要使用的常量和变量,如蛇的类型、速度等。 2. 实现了清空终端、获取终端宽高的函数。 3. 初始化蛇的位置和身体。 4. 随机食物位置的函数。 5. 按键监听的函数,根据按键设置蛇的移动方向和加速开关。 6. 计算下一次移动位置和判断蛇是否撞到边界或自己的函数。 7. 绘制地图的函数。 以上就是该代码的主要内容。完整的代码可以查看作者的GitHub仓库。
定义声明需要的数据和结构
const (
food = 3
head = 2
body = 1
baseSpeed = 100
)
var bodyArray []point //蛇点位记录数组
var integral = 0 //积分
var vector = int64(6) //方向 根据小数字键盘 2下 4左 6右 8上 方向来
var speed = 400 //附加间隔时间
var speedUp = uint32(0) //是否加速
type point struct {
x int
y int
Type int
}
var foodP point
终端的操作
每一次的画面实现更新需要清除原来的输出再输出新的。
通过使用命令来清空控制台。
unix通过clear
命令windows通过cls
命令
//清空终端
func clear() {
switch runtime.GOOS {
case "linux", "darwin":
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
_ = cmd.Run()
case "windows":
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
_ = cmd.Run()
}
}
直接使用终端的宽高来限定地图边界。
windows和unix系统都有stty
这个命令
//获取终端宽高
func getWSZ() (int, int, error) {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
return 0, 0, err
}
a := strings.ReplaceAll(strings.ReplaceAll(string(out), "\r", ""), "\n", "")
split := strings.Split(a, " ")
h, _ := strconv.Atoi(split[0])
w, _ := strconv.Atoi(split[1])
return h, w, err
}
初始化工作
初始化蛇的位置和身体
默认出生在左上角向右移动
func iniBody() {
var err error
h, w, err = getWSZ()
if err != nil {
println(err.Error())
return
}
h -= 2 //减去两行用来输出额外信息
bodyArray = []point{
{
x: 2,
y: 0,
Type: head,
},
{
x: 1,
y: 0,
Type: body,
},
{
x: 0,
y: 0,
Type: body,
},
}
}
随机食物位置
func randFood() {
x := rand.Int63n(int64(w))
y := rand.Int63n(int64(h))
flg := false
//确保不会刷新到身体上
for _, p := range bodyArray {
if int64(p.x) == x && int64(p.y) == y {
flg = true
}
}
if flg {
randFood()
speed -= 10
} else {
foodP.x = int(x)
foodP.y = int(y)
foodP.Type = food
}
}
按键监听
监听实现
原本我是直接使用的github.com/robotn/gohook
库,但后来为了去掉依赖,使用cgo重新实现了下简单的监听。
/*
#cgo windows CFLAGS: -D CGO_OS_WINDOWS=1
#cgo darwin CFLAGS: -D CGO_OS_DRWIN=1
#cgo linux CFLAGS: -D CGO_OS_LINUX=1
#if defined(CGO_OS_DRWIN)
#include <termios.h>
#elif defined(CGO_OS_LINUX)
#include <termio.h>
#endif
#if defined(CGO_OS_WINDOWS)
#include <conio.h>
char getcharGo(){
return getch();
}
#else
#include <stdio.h>
char getcharGo()
{
int in;
struct termios new_settings;
struct termios stored_settings;
tcgetattr(0,&stored_settings);
new_settings = stored_settings;
new_settings.c_lflag &= (~ICANON);
new_settings.c_cc[VTIME] = 0;
tcgetattr(0,&stored_settings);
new_settings.c_cc[VMIN] = 1;
tcsetattr(0,TCSANOW,&new_settings);
in = getchar();
tcsetattr(0,TCSANOW,&stored_settings);
return in;
}
#endif
*/
import "C"
按键处理
需要携程调用该方法,根据C.getcharGo()
返回的按键来设置蛇的下一步移动方向
以及空格键的开关加速
func listeningInput() {
for {
result := C.getcharGo()
if result == 32 {
loadUint32 := atomic.LoadUint32(&speedUp)
if loadUint32 == 0 {
atomic.StoreUint32(&speedUp, uint32(1))
} else {
atomic.StoreUint32(&speedUp, uint32(0))
}
}
switch result {
case 119, 72, 65:
if atomic.LoadInt64(&vector) != 2 {
atomic.StoreInt64(&vector, 8)
}
case 97, 75, 68:
if atomic.LoadInt64(&vector) != 6 {
atomic.StoreInt64(&vector, 4)
}
case 115, 80, 66:
if atomic.LoadInt64(&vector) != 8 {
atomic.StoreInt64(&vector, 2)
}
case 100, 77, 67:
if atomic.LoadInt64(&vector) != 4 {
atomic.StoreInt64(&vector, 6)
}
}
}
}
计算下一次移动位置和判断
func next() bool {
//蛇的移动是去掉最后一个点位 把头部点位移到新的点位
newP := point{
x: bodyArray[0].x,
y: bodyArray[0].y,
Type: bodyArray[0].Type,
}
//根据方向移动
switch atomic.LoadInt64(&vector) {
case 8:
newP.y -= 1
case 2:
newP.y += 1
case 4:
newP.x -= 1
case 6:
newP.x += 1
}
//添加到头部
bodyArray = append([]point{newP}, bodyArray...)
//如果没有吃到食物就去掉尾巴
if !(newP.x == foodP.x && newP.y == foodP.y) {
bodyArray = bodyArray[:len(bodyArray)-1]
} else {
//吃到食物就添加积分 随机新的食物
randFood()
integral++
}
//改变原来头部类型为身体
bodyArray[1].Type = body
//超过边界 结束
if newP.y >= h || newP.y < 0 {
return false
}
if newP.x >= w || newP.x < 0 {
return false
}
//撞到自己
for _, p := range bodyArray[1:] {
if bodyArray[0].x == p.x && bodyArray[0].y == p.y {
return false
}
}
return true
}
绘制地图
func drawMap() {
clear()
drawBar()
m := make(map[string]int)
m[fmt.Sprintf("%d-%d", foodP.x, foodP.y)] = food
for _, p := range bodyArray {
m[fmt.Sprintf("%d-%d", p.x, p.y)] = p.Type
}
for i := 0; i < h; i++ {
for j := 0; j < w; j++ {
k := fmt.Sprintf("%d-%d", j, i)
out := " "
if v, ok := m[k]; ok {
switch v {
case body:
out = "*"
case head:
out = "@"
case food:
out = "A"
}
}
print(out)
}
}
drawBottom()
}