Golang 控制台百行代码贪吃蛇小游戏

Golang 并不适合在前端工作,缺少强大的图形图像包和硬件加速包,更适合做成后台服务程序。本文的贪吃蛇小游戏运行与控制台上,其中调用了Window系统kernel32.dll中控制台相关的函数。
项目地址:
https://gitee.com/redfire0922/snakego
可以使用如下命令下载源码

go get gitee.com/redfire0922/snakego

最终效果
Golang 控制台百行代码贪吃蛇小游戏_第1张图片
其中最关键的还是对系统函数的调用。因为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])
        }
    }
}

你可能感兴趣的:(golang记录)