天天看點

go html5 遊戲,Go項目開發----2048小遊戲

源碼下載下傳:

項目開發詳細教程:

一. 2048遊戲設計

《2048》由19歲的意大利人Gabriele Cirulli于2014年3月開發。遊戲任務是在一個網格上滑動小方塊來進行組合,直到形成一個帶有有數字2048的方塊。《2048》使用方向鍵讓方塊上下左右移動。如果兩個帶有相同數字的方塊在移動中碰撞,則它們會合并為一個方塊,且所帶數字變為兩者之和。每次移動時,會有一個值為2或者4的新方塊出現。當值為2048的方塊出現時,遊戲即勝利。

1. 遊戲邏輯設計

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 

for y :=0; y 

if t[i%len(t)][j%len(t)]== 0 {

t[i%len(t)][j%len(t)]= 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 

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 

np := tl

n := 0 //統計每一列中非零值的個數

// 向上移動非零值,如果有零值元素,則用非零元素進行覆寫

for x :=0; x 

if t[x][i]!= 0 {

t[n][i] = t[x][i]

if n!= x {

changed = true//标示數組的元素是否有變化

}

n++

}

}

if n 

notfull = true

}

np = n

// 向上合并所有相同的元素

for x :=0; 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 

if t[x][i]!= 0 {

t[n][i] = t[x][i]

n++

}

}

for x := n; 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 

}

}()

ev :=

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

}

}

}

有疑問加站長微信聯系(非本文作者)