柠“萌”圆 发表于 2014-8-19 15:14:44

我写的控制台贪吃蛇

http://imgsrc.baidu.com/forum/w%3D580/sign=110f7c3a710e0cf3a0f74ef33a46f23d/c765349b033b5bb548b7f1d635d3d539b600bc0c.jpg
http://imgsrc.baidu.com/forum/w%3D580/sign=2961030993ef76c6d0d2fb23ad17fdf6/62e0cb134954092311e8b2569158d109b2de49e3.jpg

#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库

elvo 发表于 2014-8-19 23:49:02

irvine726 发表于 2014-8-20 15:59:23

谢谢分享

irvine726 发表于 2014-8-21 15:19:46

谢谢分享
页: [1]
查看完整版本: 我写的控制台贪吃蛇