剛接觸go語言不久,前段時間看到一個2048的項目開發教程,因而就試着練了下手。個人環境採用的是Ubuntu Linux環境。git
×××:github
https://github.com/shiyanlou/golang2048_game.gitgolang
http://download.csdn.net/detail/hzy305365977/8067803數組
項目開發詳細教程:框架
http://www.shiyanlou.com/courses/type/1ide
一. 2048 遊戲設計ui
《2048》由19歲的意大利人Gabriele Cirulli於2014年3月開發。遊戲任務是在一個網格上滑動小方塊來進行組合,直到造成一個帶有有數字2048的方塊。《2048》使用方向鍵讓方塊上下左右移動。若是兩個帶有相同數字的方塊在移動中碰撞,則它們會合併爲一個方塊,且所帶數字變爲二者之和。每次移動時,會有一個值爲2或者4的新方塊出現。當值爲2048的方塊出現時,遊戲即勝利。spa
1. 遊戲邏輯設計.net
2048遊戲使用4x4的格子來表示須要移動的數字,這不難想到可使用一個矩陣來表示這些數字,咱們使用type G2048 [4][4]int來表示。每一次使用方向鍵來移動數字時,對應方向上的數字須要進行移動和合並,也就是移動和合並矩陣中的非零值。當按下不一樣的方向鍵時,移動的數字也不一樣。咱們一共會向上、向下、向左、向右四個方向移動數字,能夠經過旋轉矩陣將向下、向左、向右的移動都轉換爲向上的移動,這樣能必定程度上簡化遊戲邏輯。大體流程圖以下:設計
2. 界面設計
開發的2048遊戲將運行在console下。在console中,咱們能夠控制每個字符單元的背景色,以及顯示的字符。咱們能夠根據這一點,在console中繪製中圖形,也就是2048遊戲的框架:4x4的空白格子,而後每個格子是4個字符單元,也就是最多能顯示四位數字。咱們將使用包github.com/nsf/termbox-go進行界面的繪製,termbox-go能很方便的設置字符單元的屬性。
三. 2048遊戲的實現
2048遊戲中的難點有兩個地方,一個是矩陣中數字的移動合併,另外一個則是矩陣的變換,之因此須要對矩陣進行變換,是爲了將2048遊戲中向下的移動,向左的移動和向右的移動都轉換成向上的移動操做。
1. 矩陣的旋轉
矩陣的旋轉操做是爲了將其餘三個方向的移動都轉換爲向上的移動操做。向下(↓)、向左(←)、向右(→)轉換爲向上(↑)的操做時,數組須要進行的翻轉操做以下所示:
· ↓ → ↑ 此類轉換能夠有多種方法作到:
o 上下翻轉矩陣,而後向上移動合併,再次上下翻轉矩陣上下翻轉後:martix_new[n-1-x][y]= martix_old[x][y]
o 順時針翻轉180度矩陣,而後向上移動合併,接着逆時針旋轉180度此時martix_new[n-1-x]n-1-y]= martix_old[x][y]
· ← → ↑ 此類轉換能夠將矩陣向右旋轉90度後,向上移動合併,接着向左旋轉90度完成向右旋轉90度後:martix_new[y][n-x-1] = martix_old[x][y] 向左旋轉90度後:martix_new[n-y-1][x]= martix_old[x][y]
· → → ↑ 此類轉換能夠將矩陣向左旋轉90度後,向上移動合併,接着向右旋轉90度完成
主要代碼:
package main
import"fmt"
type g2048 [4][4]int
func (t *g2048)MirrorV() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(t)-i-1][j] =num
}
}
*t = *tn
}
func (t *g2048)Right90() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[j][len(t)-i-1] = num
}
}
*t = *tn
}
func (t *g2048)Left90() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-j-1][i] =num
}
}
*t = *tn
}
func (g *g2048)R90() {
tn := new(g2048)
for x, line := range g {
for y, _ := range line {
tn[x][y] = g[len(line)-1-y][x]
}
}
*g = *tn
}
func (t *g2048)Right180() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-i-1][len(line)-j-1] = num
}
}
*t = *tn
}
func (t *g2048)Print() {
for _, line := range t {
for _, number := range line {
fmt.Printf("%2d ", number)
}
fmt.Println()
}
fmt.Println()
tn := g2048{{1, 2, 3, 4}, {5, 8}, {9, 10, 11}, {13, 14, 16}}
*t = tn
}
func main() {
fmt.Println("origin")
t := g2048{{1, 2, 3, 4}, {5, 8}, {9, 10, 11}, {13, 14, 16}}
t.Print()
fmt.Println("mirror")
t.MirrorV()
t.Print()
fmt.Println("Left90")
t.Left90()
t.Print()
fmt.Println("Right90")
t.R90()
t.Print()
fmt.Println("Right180")
t.Right180()
t.Print()
}
2. 2048的實現
package g2048
import (
"fmt"
"github.com/nsf/termbox-go"
"math/rand"
"time"
)
var Score int
var step int
//輸出字符串
func coverPrintStr(x,y int, str string, fg, bg termbox.Attribute) error {
xx := x
for n, c := rangestr {
if c == '\n' {
y++
xx = x - n - 1
}
termbox.SetCell(xx+n, y,c, fg, bg)
}
termbox.Flush()
return nil
}
//遊戲狀態
type Status uint
const (
Win Status = iota
Lose
Add
Max = 2048
)
//2048遊戲中的16個格子使用4x4二維數組表示
type G2048 [4][4]int
//檢查遊戲是否已經勝利,沒有勝利的狀況下隨機將值爲0的元素
//隨機設置爲2或者4
func (t *G2048)checkWinOrAdd() Status {
// 判斷4x4中是否有元素的值大於(等於)2048,有則獲勝利
for _, x := range t {
for _, y := range x {
if y >= Max {
return Win
}
}
}
// 開始隨機設置零值元素爲2或者4
i := rand.Intn(len(t))
j := rand.Intn(len(t))
for x := 0; x < len(t); x++{
for y := 0; y < len(t); y++{
if t[i%len(t)][j%len(t)] == 0 {
t[i%len(t)][j%len(t)] = 2 <<(rand.Uint32() % 2)
return Add
}
j++
}
i++
}
// 所有元素都不爲零(表示已滿),則失敗
return Lose
}
//初始化遊戲界面
func (t G2048)initialize(ox, oy int) error {
fg := termbox.ColorYellow
bg := termbox.ColorBlack
termbox.Clear(fg, bg)
str := " SCORE: " + fmt.Sprint(Score)
for n, c := rangestr {
termbox.SetCell(ox+n, oy-1, c, fg, bg)
}
str = "ESC:exit" + "Enter:replay"
for n, c := rangestr {
termbox.SetCell(ox+n, oy-2, c, fg, bg)
}
str = " PLAY withARROW KEY"
for n, c := rangestr {
termbox.SetCell(ox+n, oy-3, c, fg, bg)
}
fg = termbox.ColorBlack
bg = termbox.ColorGreen
for i := 0; i <=len(t); i++{
for x := 0; x < 5*len(t); x++{
termbox.SetCell(ox+x,oy+i*2, '-', fg, bg)
}
for x := 0; x <=2*len(t); x++{
if x%2 == 0 {
termbox.SetCell(ox+i*5, oy+x, '+', fg, bg)
} else {
termbox.SetCell(ox+i*5, oy+x, '|', fg, bg)
}
}
}
fg = termbox.ColorYellow
bg = termbox.ColorBlack
for i := range t {
for j := range t[i] {
if t[i][j] > 0 {
str := fmt.Sprint(t[i][j])
for n, char := rangestr {
termbox.SetCell(ox+j*5+1+n, oy+i*2+1, char, fg, bg)
}
}
}
}
return termbox.Flush()
}
//翻轉二維切片
func (t *G2048)mirrorV() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(t)-i-1][j] =num
}
}
*t = *tn
}
//向右旋轉90度
func (t *G2048)right90() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[j][len(t)-i-1] = num
}
}
*t = *tn
}
//向左旋轉90度
func (t *G2048)left90() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-j-1][i] =num
}
}
*t = *tn
}
func (t *G2048)right180() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-i-1][len(line)-j-1] = num
}
}
*t = *tn
}
//向上移動併合並
func (t *G2048)mergeUp() bool {
tl := len(t)
changed := false
notfull := false
for i := 0; i <tl; i++ {
np := tl
n := 0 // 統計每一列中非零值的個數
// 向上移動非零值,若是有零值元素,則用非零元素進行覆蓋
for x := 0; x <np; x++ {
if t[x][i] != 0 {
t[n][i] = t[x][i]
if n != x {
changed = true //標示數組的元素是否有變化
}
n++
}
}
if n < tl {
notfull = true
}
np = n
// 向上合併全部相同的元素
for x := 0; x <np-1; x++ {
if t[x][i] == t[x+1][i] {
t[x][i] *= 2
t[x+1][i] = 0
Score += t[x][i] *step // 計算遊戲分數
x++
changed = true
}
}
// 合併完相同元素之後,再次向上移動非零元素
n = 0
for x := 0; x <np; x++ {
if t[x][i] != 0 {
t[n][i] = t[x][i]
n++
}
}
for x := n; x <tl; x++ {
t[x][i] = 0
}
}
return changed || !notfull
}
//向下移動合併的操做能夠轉換向上移動合併:
//1.向右旋轉180度矩陣
//2.向上合併
//3.再次向右旋轉180度矩陣
func (t *G2048)mergeDwon() bool {
//t.mirrorV()
t.right180()
changed := t.mergeUp()
//t.mirrorV()
t.right180()
return changed
}
//向左移動合併轉換爲向上移動合併
func (t *G2048)mergeLeft() bool {
t.right90()
changed := t.mergeUp()
t.left90()
return changed
}
///向右移動合併轉換爲向上移動合併
func (t *G2048)mergeRight() bool {
t.left90()
changed := t.mergeUp()
t.right90()
return changed
}
//檢查按鍵,作出不一樣的移動動做或者退出程序
func (t *G2048)mrgeAndReturnKey() termbox.Key {
var changed bool
Lable:
changed = false
//ev := termbox.PollEvent()
event_queue := make(chan termbox.Event)
go func() { // 在其餘goroutine中開始監聽
for {
event_queue <- termbox.PollEvent()// 開始監聽鍵盤事件
}
}()
ev := <-event_queue
switch ev.Type {
case termbox.EventKey:
switch ev.Key {
case termbox.KeyArrowUp:
changed = t.mergeUp()
case termbox.KeyArrowDown:
changed = t.mergeDwon()
case termbox.KeyArrowLeft:
changed = t.mergeLeft()
case termbox.KeyArrowRight:
changed = t.mergeRight()
case termbox.KeyEsc, termbox.KeyEnter:
changed = true
default:
changed = false
}
//若是元素的值沒有任何更改,則重新開始循環
if !changed {
goto Lable
}
case termbox.EventResize:
x, y := termbox.Size()
t.initialize(x/2-10, y/2-4)
goto Lable
case termbox.EventError:
panic(ev.Err)
}
step++ // 計算遊戲操做數
return ev.Key
}
//重置
func (b *G2048)clear() {
next :=new(G2048)
Score = 0
step = 0
*b = *next
}
//開始遊戲
func (b *G2048)Run() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()
rand.Seed(time.Now().UnixNano())
A:
b.clear()
for { // 進入無限循環
st := b.checkWinOrAdd()
x, y := termbox.Size()
b.initialize(x/2-10, y/2-4) // 初始化遊戲界面
switch st {
case Win:
str := "Win!!"
strl := len(str)
coverPrintStr(x/2-strl/2, y/2, str, termbox.ColorMagenta,termbox.ColorYellow)
case Lose:
str := "Lose!!"
strl := len(str)
coverPrintStr(x/2-strl/2, y/2, str, termbox.ColorBlack,termbox.ColorRed)
case Add:
default:
fmt.Print("Err")
}
// 檢查用戶按鍵
key := b.mrgeAndReturnKey()
// 若是按鍵是 Esc 則退出遊戲
if key == termbox.KeyEsc{
return
}
// 若是按鍵是 Enter 則重新開始遊戲
if key == termbox.KeyEnter{
goto A
}
}
}