Golang 并不适合在前端工作,缺少强大的图形图像包和硬件加速包,更适合做成后台服务程序。本文的贪吃蛇小游戏运行与控制台上,其中调用了Window系统kernel32.dll中控制台相关的函数。
项目地址:
https://gitee.com/redfire0922/snakego
可以使用如下命令下载源码
go get gitee.com/redfire0922/snakego
最终效果
其中最关键的还是对系统函数的调用。因为golang读取用户输入都是按整行读取。也就是说用户在输入数据按下回车键之前,golang是一点点数据都读取不到。这对交互稍微丰富点的程序来说大大限制了想象力。其次是对输入位置的控制。普通情况下要录入一份类似表单的数据,golang只能一问一答录入。本文正是给出了另外一种方案。
// +bulid windows
package main
import (
"syscall"
"unsafe"
)
type (
SHORT int16
DWORD uint32
WORD uint16
BOOL int32
WCHAR uint16
COORD struct {
X SHORT
Y SHORT
}
CONSOLE_SCREEN_BUFFER_INFO struct {
Size COORD
CursorPosition COORD
Attributes WORD
Window SMALL_RECT
MaximumWindowSize COORD
}
SMALL_RECT struct {
Left SHORT
Top SHORT
Right SHORT
Bottom SHORT
}
INPUT_RECORD struct {
EventType WORD
_ [2]byte
Event [16]byte
}
KEY_EVENT_RECORD struct {
KeyDown BOOL
RepeatCount WORD
VirtualKeyCode WORD
VirtualScanCode WORD
UnicodeChar WCHAR
ControlKeyState DWORD
}
)
const (
VK_ARROW_UP = 0x26
VK_ARROW_DOWN = 0x28
VK_ARROW_LEFT = 0x25
VK_ARROW_RIGHT = 0x27
)
var (
Stdout syscall.Handle
Stdin syscall.Handle
tmp_arg DWORD
)
func (this SMALL_RECT) uintptr() uintptr {
return uintptr(*(*int32)(unsafe.Pointer(&this)))
}
func (this COORD) uintptr() uintptr {
return uintptr(*(*int32)(unsafe.Pointer(&this)))
}
//获取输入,输出handle
func InitSyscall() {
cout, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if err != nil {
panic(err)
}
Stdout = cout
cin, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
if err != nil {
panic(err)
}
Stdin = cin
}
//获取光标位置
func GetConsoleCursorPosition() COORD {
var tmp_info CONSOLE_SCREEN_BUFFER_INFO
err := GetConsoleScreenBufferInfo(&tmp_info)
if err != nil {
panic(err)
}
return tmp_info.CursorPosition
}
//获取控制台信息
func GetConsoleScreenBufferInfo(info *CONSOLE_SCREEN_BUFFER_INFO) (err error) {
r0, _, e1 := proc_get_console_screen_buffer_info.Call(uintptr(Stdout), uintptr(unsafe.Pointer(info)))
if int(r0) == 0 {
if e1 != nil {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
//设置光标位置 新光标位置必须在已有数据的控制台Buffer范围内。
func SetConsoleCursorPosition(pos COORD) (err error) {
r0, _, e1 := proc_set_console_cursor_position.Call(uintptr(Stdout), pos.uintptr())
if int(r0) == 0 {
if e1 != nil {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
//读取用户输入
func ReadConsoleInput(record *INPUT_RECORD) (err error) {
r0, _, e1 := proc_read_console_input.Call(uintptr(Stdin), uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&tmp_arg)))
if int(r0) == 0 {
if e1 != nil {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
//等待事件 WaitForMultipleObjects等待Windows中的所有的内核对象(关于该函数的描述和例子见MSDN)
func WaitForMultipleObjects(objects []syscall.Handle) (err error) {
r0, _, e1 := syscall.Syscall6(proc_wait_for_multiple_objects.Addr(),
4, uintptr(len(objects)), uintptr(unsafe.Pointer(&objects[0])),
0, 0xFFFFFFFF, 0, 0)
if uint32(r0) == 0xFFFFFFFF {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
proc_set_console_cursor_position = kernel32.NewProc("SetConsoleCursorPosition")
proc_read_console_input = kernel32.NewProc("ReadConsoleInputW")
proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects")
)
}
以下是贪吃蛇的实现,也可以说是上面系统函数的调用例子
package main
import (
"container/list"
"errors"
"fmt"
"strings"
"syscall"
"time"
"unsafe"
)
var (
height = 30 //整体高 30行
width = 30 //整体宽 30*2字符宽
origPos COORD
workArea SMALL_RECT //活动区间
food SHORT //食物占用块
empty = make(map[SHORT]bool) //空白块集合
snake = list.New() //蛇占用块
)
func main() {
InitSyscall()
StartAnimation()
snakeHead := popCell()
snake.PushFront(snakeHead)
delete(empty, snakeHead)
food = popCell()
drawCell(snakeHead, "■")
drawCell(food, "$")
err := snakeRun()
SetConsoleCursorPosition(COORD{origPos.X, origPos.Y + 2})
if err != nil {
fmt.Println(err)
}
fmt.Printf("得分:%v\n", (snake.Len()-1)*100)
}
//游戏核心实现
func snakeRun() error {
keypress := make(chan rune)
go hanldeInput(keypress)
direction := 's'
for {
select {
case <-time.Tick(time.Millisecond * 200):
break
}
newhead := snake.Front().Value.(SHORT)
var kp rune
select {
case kp = <-keypress:
default:
}
switch kp {
case 'a':
if direction != 'd' {
direction = kp
}
case 'w':
if direction != 's' {
direction = kp
}
case 's':
if direction != 'w' {
direction = kp
}
case 'd':
if direction != 'a' {
direction = kp
}
case 'q':
return nil
}
switch direction {
case 'a':
newhead = (newhead - (1 << 8))
case 'w':
newhead = newhead + 1
case 's':
newhead = newhead - 1
case 'd':
newhead = (newhead + (1 << 8))
}
_, exists := empty[newhead]
if exists {
snake.PushFront(newhead)
delete(empty, newhead)
drawCell(newhead, "■")
if food != newhead {
back := snake.Back()
snake.Remove(back)
tail := back.Value.(SHORT)
empty[tail] = true
drawCell(tail, " ")
} else {
food = popCell()
if food == -1 {
return errors.New("游戏结束")
} else {
drawCell(food, "$")
}
}
} else {
//游戏失败
return errors.New("游戏失败")
}
}
return nil
}
//开场动画
func StartAnimation() {
for _, ch := range []string{"■", " "} {
for i := 0; i < height; i++ {
for n := 0; n < width; n++ {
if i == 0 {
if n == 0 {
fmt.Print("┏")
} else if n == width-1 {
fmt.Print("┓")
} else {
fmt.Print("━")
}
} else if i == height-1 {
if n == 0 {
fmt.Print("┗")
} else if n == width-1 {
fmt.Print("┛")
} else {
fmt.Print("━")
}
} else {
if n == 0 || n == width-1 {
fmt.Print("┃")
} else {
fmt.Print(ch)
}
}
}
fmt.Println("")
}
if ch != " " {
time.Sleep(time.Second * 1)
origPos = GetConsoleCursorPosition()
origPos.X += 2
origPos.Y -= 2
SetConsoleCursorPosition(COORD{0, origPos.Y + 2 - SHORT(height)})
/* SetConsoleCursorPosition(COORD{origPos.X, origPos.Y + 1})
fmt.Print("A")
return */
}
}
workArea.Left = 0
workArea.Bottom = 0
workArea.Right = SHORT((width - 3))
workArea.Top = SHORT(height - 3)
for i := workArea.Left; i <= workArea.Right; i++ {
for n := workArea.Bottom; n <= workArea.Top; n++ {
empty[((i << 8) | (n & 127))] = true
}
}
}
//随机空白块
func popCell() SHORT {
for k := range empty {
return k
}
return -1
}
//绘制块
func drawCell(pos SHORT, ch string) {
/* if err = checkArea(pos); err != nil {
return err
} */
x := pos >> 8
y := pos & 127
err := SetConsoleCursorPosition(COORD{(x * 2) + origPos.X, origPos.Y - y})
if err != nil {
panic(err)
}
fmt.Print(ch)
}
//处理用户按键
func hanldeInput(keypress chan rune) {
for {
err := WaitForMultipleObjects([]syscall.Handle{Stdin})
if err != nil {
panic(err)
}
var r INPUT_RECORD
ReadConsoleInput(&r)
kr := (*KEY_EVENT_RECORD)(unsafe.Pointer(&r.Event))
if kr.KeyDown == 0 {
continue
}
switch kr.VirtualKeyCode {
case VK_ARROW_UP:
keypress <- 'w'
case VK_ARROW_DOWN:
keypress <- 's'
case VK_ARROW_LEFT:
keypress <- 'a'
case VK_ARROW_RIGHT:
keypress <- 'd'
}
if kr.UnicodeChar != 0 {
keypress <- rune(strings.ToLower(string(kr.UnicodeChar))[0])
}
}
}