kotlin版贪吃蛇小游戏

效果图

TIM截图20190903095435.png

最近一直在搞小程序,之前学过的kotlin都快忘光了。借此采用kotlin写了个贪吃蛇的demo,来回顾下kotlin的语法。

分析

贪吃蛇游戏由食物(红色小块)和小蛇(绿色小块)组成。其中需要明确的问题如下:

  1. 小蛇吃到食物后,食物在新的坐标重新出现(坐标需在显示区内)

  2. 小蛇吃到食物后身体变长(长度为食物的长度)

  3. 蛇身应由一个个小正方形组成大小与食物一样

  4. 蛇头和蛇尾不能调转即蛇头方向向左则无法再向右移动

  5. 小蛇在拐弯时,蛇身跟随蛇头转向即蛇身到达蛇头所在位置时如果与蛇头方向不一直则改变蛇身方向

  6. 小蛇碰撞到墙壁(蛇头不在显示区域内)或蛇头碰撞到蛇身则游戏结束

代码


class Food() : View{
    companion object {
        val widthAndHeight = 20
    }

    var isShow: Boolean = true
    var color: Color = Color.RED
    var x: Int = 0;
    var y: Int = 0;


    override fun draw(g: Graphics?) {
        g!!.color = if(isShow) color else null
        isShow = !isShow
        g.fillRect(x,y,widthAndHeight,widthAndHeight)
    }

    /**
     * 生成食物
     */
    fun generateFood(cellList: ArrayList<Snake.Cell>){
        while(true){
            val point = GameUtils.generatePosition()
            var isSuccess = true;
            for(cell in cellList){
                if(cell.x == point.x && cell.y == point.y){ //排除小蛇的坐标
                    isSuccess = false
                }
            }
            if(isSuccess){
                x = point.x
                y = point.y
                break
            }
        }
    }

}

class Snake() : View{
    enum class Direction{ //方向枚举
        TOP,RIGHT,BOTTOM,LEFT
    }

    var cellList: ArrayList<Cell> = ArrayList() //组成蛇的小正方形列表
    var color: Color = Color.GREEN //蛇的颜色

    private var inflectionPoint: ArrayList<Cell> = ArrayList() //记录发生转向的蛇头

    init {
        initCellList()
    }

    fun initCellList(){
        cellList.clear()
        cellList.add(Cell(0,0, Direction.LEFT)) //添加蛇头
    }

    /**
     * 检查是否成功进食,是=增加蛇身长度,否则不做处理
     */
    fun tryEat(food: Food): Boolean{
        if(cellList[0].x == food.x && cellList[0].y == food.y){
            val lastCell = cellList[cellList.size - 1];
            when(lastCell.direction){
                Direction.LEFT-> cellList.add(Cell(lastCell.x + Food.widthAndHeight,lastCell.y,lastCell.direction))
                Direction.RIGHT-> cellList.add(Cell(lastCell.x - Food.widthAndHeight,lastCell.y,lastCell.direction))
                Direction.TOP-> cellList.add(Cell(lastCell.x,lastCell.y + Food.widthAndHeight,lastCell.direction))
                Direction.BOTTOM-> cellList.add(Cell(lastCell.x,lastCell.y - Food.widthAndHeight,lastCell.direction))
            }
            return true
        }
        return false
    }

    fun walk(){
        for(index in cellList.indices){
            val cell:Cell = cellList[index]
            if(index != 0){
                for(pCell in inflectionPoint){//判断蛇身的每一个正方形是否经过蛇头转向点,是改变方向
                    if(cell.x == pCell.x && cell.y == pCell.y){
                        cell.direction = pCell.direction
                        println("change direction")
                        if(index == cellList.size-1){//当蛇尾也经过蛇头转向点时,从列表删除转向信息
                            inflectionPoint.remove(pCell)
                        }
                        break;
                    }
                }
            }


            when(cell.direction){//根据方向修改坐标,进行移动操作
                Snake.Direction.TOP-> cell.y -= Food.widthAndHeight
                Snake.Direction.RIGHT-> cell.x += Food.widthAndHeight
                Snake.Direction.BOTTOM-> cell.y += Food.widthAndHeight
                Snake.Direction.LEFT-> cell.x -= Food.widthAndHeight
            }
        }
    }

    /**
     * 根据输入的#{switchDirection}进行转向操作
     * @param switchDirection 方向枚举
     */
    fun switchDirection(switchDirection:Snake.Direction){
        val cDirection = cellList[0].direction
        when(switchDirection){
            Snake.Direction.LEFT->{
                if(cDirection != Snake.Direction.RIGHT){
                    cellList[0].direction = Snake.Direction.LEFT
                }
            }

            Snake.Direction.TOP->{
                if(cDirection != Snake.Direction.BOTTOM){
                    cellList[0].direction = Snake.Direction.TOP
                }
            }

            Snake.Direction.RIGHT->{
                if(cDirection != Snake.Direction.LEFT){
                    cellList[0].direction = Snake.Direction.RIGHT
                }
            }

            Snake.Direction.BOTTOM->{
                if(cDirection != Snake.Direction.TOP){
                    cellList[0].direction = Snake.Direction.BOTTOM
                }
            }
        }
        if(cellList.size > 1){
            inflectionPoint.add(Cell(cellList[0].x,cellList[0].y,cellList[0].direction))
        }
    }

    /**
     * 获取蛇头
     */
    fun getHeaderCell(): Cell{
        return cellList[0]
    }

    fun getCellCount(): Int{
        return cellList.size
    }

    /**
     * 是否咬到自己(蛇头碰撞到蛇身)
     */
    fun biteYourself(): Boolean{
        for(index in cellList.indices){
            if(index != 0){
                val cell = cellList[index]
                if(getHeaderCell().x == cell.x && getHeaderCell().y == cell.y){
                   return true
                }
            }
        }
        return false
    }

    fun generatePosition(){
        val point = GameUtils.generatePosition()
        cellList[0].x = point.x
        cellList[0].y = point.y
        if(point.x <= Food.widthAndHeight * 2){
            cellList[0].direction = Direction.RIGHT
        }
    }

    override fun draw(g: Graphics?) {
        g!!.color = color
        for(cell in cellList){
            g.fillRect(cell.x,cell.y,cell.width,cell.height)
        }
    }

    /**
     * 蛇的每一节,每吃一个食物涨一节
     */
    data class Cell(var x:Int,var y:Int,var direction: Direction,var width: Int = Food.widthAndHeight,var height: Int = Food.widthAndHeight)
}


/**
 *  游戏主界面
 */
class MainFrame(width:Int,height:Int) : DoubleBufferFrame(width,height), KeyListener {
    var isConfirmStart = false; //是否确认开始
    var food: Food = Food()
    var snake: Snake = Snake()

    init {
        initData()
        title = "贪吃蛇"
        addKeyListener(this)
        if(JOptionPane.showOptionDialog(this,"点击确定开始游戏","提示",JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE,null,null,null) == 0){
            isConfirmStart = true
        }
    }

    fun initData(){
        food.generateFood(snake.cellList)
        snake.generatePosition()
    }

    override fun updateAttr() {
        if(!isConfirmStart){
            return
        }
        if(isOver()){
            isStopDrawThread = true;
            if(JOptionPane.showOptionDialog(this,"游戏结束,得分${(snake.getCellCount()-1) * SCORE},是否重新开始","提示",JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE,null,null,null) == 0){
                snake.initCellList()
                initData()
                isStopDrawThread = false;
            }
            return
        }
        if(snake.tryEat(food)){
            food.generateFood(snake.cellList)
        }
        snake.walk()
    }

    /**
     * 蛇撞墙或着咬到自己则返回true,游戏结束
     */
    fun isOver(): Boolean{
        return isCrashWall() || snake.biteYourself()
    }

    /**
     * 蛇是否碰撞到墙
     */
    fun isCrashWall(): Boolean{
        val cell:Snake.Cell = snake.getHeaderCell();
        return cell.x < contentPane.x || cell.x + cell.width > contentPane.width || cell.y < contentPane.y || cell.y + cell.height > contentPane.height
    }


    override fun dPaint(g: Graphics?) {
        food.draw(g)
        snake.draw(g)
    }

    override fun keyTyped(e: KeyEvent?) {

    }

    override fun keyPressed(e: KeyEvent?) {
        isStopDrawThread = true

        when(e!!.keyCode){
            37-> snake.switchDirection(Snake.Direction.LEFT)
            38-> snake.switchDirection(Snake.Direction.TOP)
            39-> snake.switchDirection(Snake.Direction.RIGHT)
            40-> snake.switchDirection(Snake.Direction.BOTTOM)
        }
        repaint()
    }

    override fun keyReleased(e: KeyEvent?) {
        isStopDrawThread = false
    }

}

DoubleBufferFrame类采用双缓冲技术解决闪烁问题,主要思路是先将需绘制的场景绘制到内存,再从内存绘制到窗体上

const val BLOCK_LENGTH:Int = 20
const val SCORE:Int = 10 //每吃一个食物的得分
const val DIFFICULTY:Int = 5 //难度 1-10

fun main(args: Array<String>){
    MainFrame(Food.widthAndHeight * BLOCK_LENGTH,Food.widthAndHeight * BLOCK_LENGTH)
}

总结

贪吃蛇的实现并不难,几个类就能实现了。估计也就只有分析中的第5点需要稍微思考下。有兴趣的朋友可以拓展下。附上源码地址: https://github.com/aii1991/GluttonousSnake

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容