鱼C论坛

 找回密码
 立即注册
查看: 1741|回复: 3

[技术交流] 我写的控制台贪吃蛇

[复制链接]
发表于 2014-8-19 15:14:44 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x

                               
登录/注册后可看大图


                               
登录/注册后可看大图

#ifndef COORDINATE_H
#define COORDINATE_H

#include <cstddef>
#include <unordered_set>

namespace Screen {
    typedef unsigned char pos_type;

    struct Coordinate {
        inline bool operator==(const Coordinate &rhs) const noexcept
        { return x ==  rhs.x && y == rhs.y; }
        pos_type x, y;
    };

    inline Coordinate makeCoordinate(pos_type x, pos_type y) noexcept
    {
        Coordinate ret;
        ret.x = x;
        ret.y = y;
        return ret;
    }
}

namespace std {
    template <>
    struct hash<Screen::Coordinate>  {
        typedef size_t result_type;
        typedef Screen::Coordinate argument_type;

        inline size_t operator()(const Screen::Coordinate &c) const noexcept
        {
            return hash<Screen::pos_type>()(c.x) ^
                    hash<Screen::pos_type>()(c.y);
        }
    };

}

#endif // COORDINATE_H
#ifndef KEYBOARDLISTENER_H
#define KEYBOARDLISTENER_H

#include "map.h"
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/thread.hpp>

namespace Listener {
    class KeyboardListener : boost::noncopyable {
    public:
        KeyboardListener(Map::GameMap &map)
        noexcept : pMap(&map) { startListen(); }
        ~KeyboardListener() noexcept { stopListen(); }
    private:
        void startListen() const noexcept;
        void stopListen() const noexcept;

        Map::GameMap *pMap;
        mutable boost::scoped_ptr<boost::thread> pThread;
    };
}


#endif // KEYBOARDLISTENER_H
#ifndef MAP_H
#define MAP_H

#include "snake_basicInformation.h"
#include "screen.h"
#include <boost/noncopyable.hpp>
#include <cstddef>
#include <boost/thread/mutex.hpp>

namespace Map {
    class GameMap : boost::noncopyable {
    public:
        GameMap() noexcept;
        ~GameMap() noexcept
        {Screen::clearScreen(); Screen::hideCursor(true);}

        bool snakeMove(BasicInformation::Control) noexcept;

    private:
        bool move(BasicInformation::Control, Screen::Coordinate &) noexcept;
        void distributionFood(std::size_t = 0) noexcept;

        BasicInformation::wall_t wallCoords;
        BasicInformation::food_t foodCoords;
        BasicInformation::snake_t snakeCoords;
        BasicInformation::Control headDirection =
                BasicInformation::OTHER;
        bool paused = false;
        bool isOver = false;
    };

    template <class Map_t>
    void draw(const Map_t &, char) noexcept;
}


template <class Map_t>
void Map::draw(const Map_t &map, char ch) noexcept
{
    boost::mutex mu;
    boost::mutex::scoped_lock lock(mu);
    for (auto coord : map)
        Screen::putchar(coord.x, coord.y, ch);
}

#endif // MAP_H
#ifndef RANDOM_H
#define RANDOM_H

#include "coordinate.h"

namespace Random {
    Screen::Coordinate randomCoordinate() noexcept; // 随机生成坐标
}

#endif // RANDOM_H
#ifndef PUTCHAR_H
#define PUTCHAR_H

#include <cstdint>
#include <cstdlib>

namespace Screen {

    typedef std::uint8_t row_type;
    typedef row_type col_type;

    void putchar(row_type cols, col_type rows,
                 char data) noexcept; // 在rows行(0 - 24), cols列(0 - 79)打印字符
    inline void erase(row_type cols, col_type rows) noexcept // 清除第rows行,cols列的字符
    { putchar(cols, rows, 0); }
    inline void clearScreen() noexcept // 清屏
    { std::system("cls"); }
    void hideCursor(bool = false) noexcept;
}

#endif // PUTCHAR_H
#ifndef SNAKE_BASICINFORMATION_H
#define SNAKE_BASICINFORMATION_H

#include "coordinate.h"
#include <deque>
#include <unordered_set>

namespace BasicInformation {
    // 键盘控制枚举
    enum Control { LEFT = 'A',
                   UP = 'W',
                   DOWN = 'S',
                   RIGHT = 'D',
                   PAUSE = ' ',
                   OTHER
                 };
    enum Information { // GROW = 1, // 蛇每次吃到食物生长长度
                       TOTAL_STONE = 20, // 石头的总数
                       TOTAL_FOOD = 5,// 屏幕上最多只能同时有TOTAL_FOOD个食物
                       MINMUM_FOOD = 0, // 当食物降低至MINMUM_FOOD个时补充食物
                       SNAKE = '*',
                       WALL = '#',
                       FOOD = '@',
                       SCREEN_HIGHT = 25, // 屏幕高
                       SCREEN_WIDTH = 80 // 屏幕宽
                     };

    typedef std::deque<Screen::Coordinate> snake_t;
    typedef std::unordered_set<Screen::Coordinate> wall_t;
    typedef wall_t food_t;
}

#endif // SNAKE_BASICINFORMATION_H
#include "keyboardListener.h"
#include <conio.h>
#include <cctype>

namespace Helper {
    void listener(Map::GameMap *map) noexcept;
}

void Listener::KeyboardListener::
startListen() const noexcept
{
    pThread.reset(new boost::thread(Helper::listener, pMap));
}

void Listener::KeyboardListener::
stopListen() const noexcept
{
    try {
        pThread->interrupt();
    }
    catch (...) {}
}

void Helper::listener(Map::GameMap *map) noexcept
{
    int ctrl = getch();
    while (map->snakeMove(static_cast<BasicInformation::Control>(
                              std::toupper(ctrl)
                              )))
        ctrl = getch();
}
#include "screen.h"
#include "map.h"
#include "snake_basicInformation.h"
#include "keyboardListener.h"
#include <iostream>
#include <conio.h>
#include <windows.h>
#include <cctype>
#include <memory>
#include <cstring>

int main(int argc, char *argv[])
{
    {
        Map::GameMap gameMap;
        Listener::KeyboardListener listener(gameMap);
        while (gameMap.snakeMove(BasicInformation::OTHER)) Sleep(250);
    }


    const char *gameOver = "GameOver";
    Screen::col_type x = (BasicInformation::SCREEN_WIDTH - std::strlen(gameOver))  / 2;
    Screen::row_type y = BasicInformation::SCREEN_HIGHT / 2;

    while (*gameOver)
        Screen::putchar(x++, y, *gameOver++);


    return 0;
}
#include "map.h"
#include "random.h"
#include <algorithm>
#include <cassert>

namespace Helper {
    BasicInformation::wall_t distributionWall() noexcept;
    BasicInformation::snake_t distributionSnakePos() noexcept;
}

Map::GameMap::GameMap() noexcept :
    wallCoords(Helper::distributionWall()),
    snakeCoords(Helper::distributionSnakePos())
{
    Screen::hideCursor();
    distributionFood();

    while (wallCoords.find(snakeCoords.front()) != wallCoords.end()) // 如果蛇的初始位置刚好有墙
        snakeCoords = Helper::distributionSnakePos(); // 重新分配蛇的位置

    draw(wallCoords, BasicInformation::WALL); // 画墙
    draw(snakeCoords, BasicInformation::SNAKE); // 画蛇
    draw(foodCoords, BasicInformation::FOOD);
}

bool Map::GameMap::
snakeMove(BasicInformation::Control ctrl) noexcept
{
    boost::mutex mu;
    boost::mutex::scoped_lock lock(mu);

    if (isOver)
        return false;

    if (ctrl == BasicInformation::PAUSE)
        paused = !paused;


    if (!paused) {
        Screen::Coordinate newHead;

        if(!move(ctrl, newHead)) { // 控制值无效
        // 根据蛇头方向移动
        if (!move(headDirection, newHead)) // 蛇头方向无效
            return true; // 返回
        }
        else headDirection = ctrl; // 蛇头方向变为行进方向

        if (wallCoords.find(newHead) != wallCoords.end()) { // 撞墙
            isOver = true;
            return false;
        }

        // 没有吃到食物
        if (foodCoords.find(newHead) == foodCoords.end()) {
            Screen::erase(snakeCoords.back().x, snakeCoords.back().y); // 擦掉尾巴
            snakeCoords.pop_back();
        }
        else { // 吃到食物
            foodCoords.erase(newHead);

            if (foodCoords.size() == BasicInformation::MINMUM_FOOD) {
                distributionFood(foodCoords.size());
                draw(foodCoords, BasicInformation::FOOD);
            }
        }

        if (std::find(snakeCoords.begin(), snakeCoords.end(), newHead) // 咬到自己
                != snakeCoords.end()) {
            isOver = true;
            return false;
        }

        snakeCoords.push_front(newHead);
        draw(snakeCoords, BasicInformation::SNAKE); // 画蛇
    }

    return true;
}

bool Map::GameMap::
move(BasicInformation::Control ctrl, Screen::Coordinate &newHead) noexcept
{
    using namespace BasicInformation;

    switch (ctrl) {
    case LEFT :
        newHead = Screen::makeCoordinate(
                    snakeCoords.front().x - 1,
                    snakeCoords.front().y
                    );
        break;

    case RIGHT :
        newHead = Screen::makeCoordinate(
                    snakeCoords.front().x + 1,
                    snakeCoords.front().y
                    );
        break;

    case UP :
        newHead = Screen::makeCoordinate(
                    snakeCoords.front().x,
                    snakeCoords.front().y - 1
                    );
        break;

    case DOWN :
        newHead = Screen::makeCoordinate(
                    snakeCoords.front().x,
                    snakeCoords.front().y + 1
                    );
        break;

    default: // 控制键无效
        return false;
    }

    return true;
}

void Map::GameMap::
distributionFood(std::size_t currentFoodCount) noexcept
{
    for (std::size_t i = currentFoodCount;
         i != BasicInformation::TOTAL_FOOD; ++i) {
        Screen::Coordinate foodPos = Random::randomCoordinate();

        while (std::find(snakeCoords.begin(),
                         snakeCoords.end(), foodPos) != snakeCoords.end() ||
               wallCoords.find(foodPos) != wallCoords.end()) // 食物的位置与蛇的位置重复或与石头位置重复
            foodPos = Random::randomCoordinate();
        // 食物位置合法
        foodCoords.insert(foodPos);
    }
}

namespace Helper {
    BasicInformation::wall_t make_wall() noexcept;
    void theFirstLine(BasicInformation::wall_t &) noexcept;
    void middle(BasicInformation::wall_t &) noexcept;
    void theLastLine(BasicInformation::wall_t &) noexcept;
}

BasicInformation::wall_t
    Helper::distributionWall() noexcept
{
    static BasicInformation::wall_t w = make_wall();

    return w;
}


BasicInformation::snake_t Helper::distributionSnakePos() noexcept
{
    BasicInformation::snake_t s;

    s.push_back(Random::randomCoordinate());

    return s;
}

BasicInformation::wall_t
    Helper::make_wall() noexcept
{
    BasicInformation::wall_t w;

    theFirstLine(w); // 第一行的墙壁
    middle(w); // 中间的墙壁
    theLastLine(w); // 最后一行的墙壁

    for (std::size_t i = 0;
         i != BasicInformation::TOTAL_STONE; ++i) // 再随机生成TOTAL_STONE个石头
        w.insert(Random::randomCoordinate());

    return w;
}


void Helper::theFirstLine(BasicInformation::wall_t &wmap) noexcept
{
    for (Screen::pos_type x = 0;
         x != BasicInformation::SCREEN_WIDTH; ++x)
        wmap.insert(Screen::makeCoordinate(x, 0));
}

void Helper::middle(BasicInformation::wall_t &wmap) noexcept
{
    for (Screen::pos_type y = 1;
         y != BasicInformation::SCREEN_HIGHT - 1; ++y) {
        wmap.insert(Screen::makeCoordinate(0, y));
        wmap.insert(Screen::makeCoordinate(BasicInformation::SCREEN_WIDTH - 1, y));
    }
}

void Helper::theLastLine(BasicInformation::wall_t &wmap) noexcept
{
    for (Screen::pos_type x = 0;
         x != BasicInformation::SCREEN_WIDTH; ++x)
        wmap.insert(Screen::makeCoordinate(x,
                                            BasicInformation::SCREEN_HIGHT - 1));
}
#include "random.h"
#include "snake_basicInformation.h"
#include <random>
#include <ctime>
#include <limits>

Screen::Coordinate Random::randomCoordinate() noexcept
{
    static std::default_random_engine cols_engine(std::time(0));
    static std::default_random_engine rows_engine(std::numeric_limits<unsigned>::max());
    static std::uniform_int_distribution<Screen::pos_type> rand_rows(1,
                                               BasicInformation::SCREEN_HIGHT - 2); // 围墙除外, 行数从1 - SCREEN_HIGHT - 2
    static std::uniform_int_distribution<Screen::pos_type> rand_cols(1,
                                               BasicInformation::SCREEN_WIDTH - 2); // 围墙除外, 列数从1 - SCREEN_WIDTH - 2

    Screen::Coordinate coord;
    coord.y = rand_rows(rows_engine);
    coord.x = rand_cols(cols_engine);

    return coord;
}
#include "screen.h"
#include "snake_basicInformation.h"
#include <cassert>
#include <iostream>
#include <windows.h>
#include <boost/thread/mutex.hpp>

void Screen::putchar(row_type cols, col_type rows, char data) noexcept
{
    boost::mutex mu;
    boost::mutex::scoped_lock lock(mu);

    assert(rows < BasicInformation::SCREEN_HIGHT &&
           cols < BasicInformation::SCREEN_WIDTH);

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos;
    pos.X = cols;
    pos.Y = rows;
    SetConsoleCursorPosition(hOut,pos);
    std::cout.put(data);
}

void Screen::hideCursor(bool isVisible) noexcept
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cci;
    GetConsoleCursorInfo(hOut,&cci);
    cci.bVisible = isVisible;
    SetConsoleCursorInfo(hOut,&cci);
}

编译器需要支持C++11, 需要安装boost库
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2014-8-19 23:49:02 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2014-8-20 15:59:23 | 显示全部楼层
谢谢分享
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2014-8-21 15:19:46 | 显示全部楼层
谢谢分享
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-24 21:01

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表