Go项目开发----2048小游戏

305365977 · · 168 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

刚接触go语言不久,前段时间看到一个2048的项目开发教程,于是就试着练了下手。我的环境采用的是Ubuntu Linux环境。


源码下载:

https://github.com/shiyanlou/golang2048_game.git

http://download.csdn.net/detail/hzy305365977/8067803

项目开发详细教程:

http://www.shiyanlou.com/courses/type/1


一. 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聽<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

聽聽聽聽聽聽聽 }

聽聽聽 }

}


本文来自:51CTO博客

感谢作者:305365977

查看原文:Go项目开发----2048小游戏

168 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传