提交 88c3d89e 编写于 作者: 独孤过's avatar 独孤过

Update Console

上级 3fbbddd6
# 黑白棋
又名翻转棋。
## 简介
基于控制台的黑白棋游戏,界面宽高可变,支持中止与继续操作。在棋局界面之时,按<kbd>Escape</kbd>可以中止游戏,此时回到菜单界面,出现继续游戏选项。
## 规则
每落一子,必须转换对手棋子为自己棋子。若无法转换对手棋子,则无子可落,跳过自己回合。
游戏结束条件如下:
1. 双方无子可落
2. 一方无子
3. 棋盘已满
达到其中之一,则游戏结束,棋子数量多者获胜。
## 界面
初始菜单界面如下图所示:
![初始菜单](image/Menu1.png)
在中止一次游戏之后,菜单增加继续游戏选项,此时菜单界面如下图所示:
![完整菜单](image/Menu2.png)
棋局界面布局如下图所示:
![棋局布局](image/Chessboard.png)
## 说明
菜单界面的按键与作用如下表所示:
按键|作用
-|-
<kbd>Enter</kbd>|确定选项
<kbd>Space</kbd>|确定选项
<kbd></kbd>|向上切换选项
<kbd></kbd>|向下切换选项
棋局界面的按键与作用如下表所示:
按键|作用
-|-
<kbd>Escape</kbd>|中止对弈
<kbd>Space</kbd>|确定落子
<kbd></kbd>|上移光标
<kbd></kbd>|下移光标
<kbd></kbd>|左移光标
<kbd></kbd>|右移光标
## 问题
此游戏在Windows 7和Windows 8之上运行无异常,而在Windows 10之上棋局界面会有问题。
解决方法:
* 右击控制台的标题栏,弹出右键菜单,选择属性,出现属性选项卡,切换至选项,勾选使用旧版控制台(需要重新启动,影响所有控制台),点击确定,重新运行程序即可。
## 结构
* bin:可执行程序文件夹
* image:游戏截图文件夹
* src:源码文件夹
## 项目
文件|说明
-|-
[Reversi.hpp](src/Reversi.hpp)|定义键值宏和命名空间宏
[main.cpp](src/main.cpp)|初始游戏
[Renderer.hpp](src/Renderer.hpp)[Renderer.cpp](src/Renderer.cpp)|定义渲染器类
[Game.hpp](src/Game.hpp)[Game.cpp](src/Game.cpp)|定义游戏类
[Menu.hpp](src/Menu.hpp)[Menu.cpp](src/Menu.cpp)|定义菜单类
[Chessboard.hpp](src/Chessboard.hpp)[Chessboard.cpp](src/Chessboard.cpp)|定义棋局类
## 作者
name:许聪
mailbox:2592419242@qq.com
CSDN:https://blog.csdn.net/xucongyoushan
gitee:https://gitee.com/solifree
github:https://github.com/xucongandxuchong
#include "Chessboard.hpp"
#include "Renderer.hpp"
#include <Windows.h>
#include <conio.h>
#include <cstring>
#include <istream>
#include <ostream>
#include <iomanip>
REVERSI_BEGIN
// 亮蓝色背景
static constexpr uint16_t SCREEN = BACKGROUND_BLUE | BACKGROUND_INTENSITY;
// 亮红色前景
static constexpr uint16_t PROMPT = FOREGROUND_RED | FOREGROUND_INTENSITY;
// 红色前景,亮黄色背景
static constexpr uint16_t BOARD[2] = { FOREGROUND_RED, BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY };
// 亮黄色背景,亮白色前景,黑色前景
static constexpr uint16_t CHESS[3] = { BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY, 0, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY };
// 下标说明符
const Chessboard::Index Chessboard::_index = Chessboard::Index();
// 坐标说明符
const Chessboard::Coordinate Chessboard::_coordinate = Chessboard::Coordinate();
std::istream& operator >>(std::istream& _input, Chessboard& _chessboard)
{
return _input.read(reinterpret_cast<char*>(_chessboard._states), sizeof _chessboard._states);
}
std::ostream& operator <<(std::ostream& _output, const Chessboard& _chessboard)
{
return _output.write(reinterpret_cast<const char*>(_chessboard._states), sizeof _chessboard._states);
}
// 转换坐标为下标
inline void Chessboard::getIndex(int16_t _x, int16_t _y, int16_t& _row, int16_t& _column) const noexcept
{
_row = (_y - (_up + 1)) >> 1;
_column = (_x - (_left + 2)) >> 2;
}
// 转换下标为坐标
inline void Chessboard::getCoord(int16_t _row, int16_t _column, int16_t& _x, int16_t& _y) const noexcept
{
_x = (_column << 2) + (_left + 2);
_y = (_row << 1) + (_up + 1);
}
// 判断指定下标是否棋子
inline bool Chessboard::isChess(Index _index, int16_t _row, int16_t _column) const noexcept
{
return _states[_row][_column] != EMPTY;
}
// 判断指定坐标是否棋子
bool Chessboard::isChess(Coordinate _coordinate, int16_t _x, int16_t _y) const noexcept
{
int16_t row, column;
getIndex(_x, _y, row, column);
return isChess(_index, row, column);
}
// 设置棋子状态
inline void Chessboard::setChess(int16_t _row, int16_t _column, State _state) noexcept
{
_states[_row][_column] = _state;
}
// 设置光标颜色
void Chessboard::setCursorColor() const
{
_renderer.setTextColor(CHESS[1], CHESS[0]);
}
// 设置棋子颜色
void Chessboard::setChessColor(int16_t _x, int16_t _y) const
{
int16_t row, column;
getIndex(_x, _y, row, column);
_renderer.setTextColor(CHESS[_states[row][column]], CHESS[0]);
}
// 绘制棋盘
void Chessboard::drawChessboard()
{
_renderer.setTextColor(BOARD[0], BOARD[1]);
_stream.seekp(0, _stream.beg);
_stream << "┏";
for (uint8_t j = COLUMN - 1; j > 0; --j)
_stream << "━┳";
_stream << "━┓";
const int16_t& x = _left;
int16_t y = _up;
_renderer.outText(x, y, _stream.str().c_str());
for (uint8_t i = ROW - 1; i > 0; --i)
{
_stream.seekp(0, _stream.beg);
for (uint8_t j = 0; j < COLUMN; ++j)
_stream << "┃ ";
_stream << "┃";
_renderer.outText(x, ++y, _stream.str().c_str());
_stream.seekp(0, _stream.beg);
_stream << "┣";
for (uint8_t j = COLUMN - 1; j > 0; --j)
_stream << "━╋";
_stream << "━┫";
_renderer.outText(x, ++y, _stream.str().c_str());
}
_stream.seekp(0, _stream.beg);
for (uint8_t j = 0; j < COLUMN; ++j)
_stream << "┃ ";
_stream << "┃";
_renderer.outText(x, ++y, _stream.str().c_str());
_stream.seekp(0, _stream.beg);
_stream << "┗";
for (uint8_t j = COLUMN - 1; j > 0; --j)
_stream << "━┻";
_stream << "━┛";
_renderer.outText(x, ++y, _stream.str().c_str());
}
// 绘制所有棋子
void Chessboard::drawChess() const
{
for (int16_t row = 0, column, x, y; row < ROW; ++row)
for (column = 0; column < COLUMN; ++column)
if (isChess(_index, row, column))
{
getCoord(row, column, x, y);
setChessColor(x, y);
_renderer.outText(x, y, "●");
}
}
// 转换单个棋子
void Chessboard::convertOne(int16_t _row, int16_t _column, State _state)
{
Sleep(40);
setChess(_row, _column, _state);
int16_t x, y;
getCoord(_row, _column, x, y);
setChessColor(x, y);
_renderer.outText(x, y, "●");
}
// 转换上方棋子
bool Chessboard::convertUp(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row <= 1)
return false;
// 向上方遍历,直到自己的棋子或者空位为止
int16_t m = _row - 1;
for (; m >= 0; --m)
if (_states[m][_column] == _states[_row][_column] \
|| _states[m][_column] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m < 0 || m == _row - 1 || _states[m][_column] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row - 1; i > m; --i)
convertOne(i, _column, _states[_row][_column]);
}
return true;
}
// 转换下方棋子
bool Chessboard::convertDown(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row >= ROW - 2)
return false;
// 向下方遍历,直到自己的棋子或者空位为止
int16_t m = _row + 1;
for (; m < ROW; ++m)
if (_states[m][_column] == _states[_row][_column] \
|| _states[m][_column] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m >= ROW || m == _row + 1 || _states[m][_column] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row + 1; i < m; ++i)
convertOne(i, _column, _states[_row][_column]);
}
return true;
}
// 转换左方棋子
bool Chessboard::convertLeft(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_column <= 1)
return false;
// 向左方遍历,直到自己的棋子或者空位为止
int16_t n = _column - 1;
for (; n >= 0; --n)
if (_states[_row][n] == _states[_row][_column] \
|| _states[_row][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (n < 0 || n == _column - 1 || _states[_row][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t j = _column - 1; j > n; --j)
convertOne(_row, j, _states[_row][_column]);
}
return true;
}
// 转换右方棋子
bool Chessboard::convertRight(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_column >= COLUMN - 2)
return false;
// 向右方遍历,直到自己的棋子或者空位为止
int16_t n = _column + 1;
for (; n < COLUMN; ++n)
if (_states[_row][n] == _states[_row][_column] \
|| _states[_row][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (n >= COLUMN || n == _column + 1 || _states[_row][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t j = _column + 1; j < n; ++j)
convertOne(_row, j, _states[_row][_column]);
}
return true;
}
// 转换左上棋子
bool Chessboard::convertLeftUp(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row <= 1 || _column <= 1)
return false;
// 向左上方遍历,直到自己的棋子或者空位为止
int16_t m = _row - 1, n = _column - 1;
for (; m >= 0 && n >= 0; --m, --n)
if (_states[m][n] == _states[_row][_column] \
|| _states[m][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m < 0 || n < 0 || m == _row - 1 || _states[m][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row - 1, j = _column - 1; i > m; --i, --j)
convertOne(i, j, _states[_row][_column]);
}
return true;
}
// 转换右上棋子
bool Chessboard::convertRightUp(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row <= 1 || _column >= COLUMN - 2)
return false;
// 向右上方遍历,直到自己的棋子或者空位为止
int16_t m = _row - 1, n = _column + 1;
for (; m >= 0 && n < COLUMN; --m, ++n)
if (_states[m][n] == _states[_row][_column] \
|| _states[m][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m < 0 || n >= COLUMN || m == _row - 1 || _states[m][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row - 1, j = _column + 1; i > m; --i, ++j)
convertOne(i, j, _states[_row][_column]);
}
return true;
}
// 转换左下棋子
bool Chessboard::convertLeftDown(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row >= ROW - 2 || _column <= 1)
return false;
// 向左下方遍历,直到自己的棋子或者空位为止
int16_t m = _row + 1, n = _column - 1;
for (; m < ROW && n >= 0; ++m, --n)
if (_states[m][n] == _states[_row][_column] \
|| _states[m][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m >= ROW || n < 0 || m == _row + 1 || _states[m][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row + 1, j = _column - 1; i < m; ++i, --j)
convertOne(i, j, _states[_row][_column]);
}
return true;
}
// 转换右下棋子
bool Chessboard::convertRightDown(int16_t _row, int16_t _column, bool _judgmental)
{
// 无转换空间
if (_row >= ROW - 2 || _column >= COLUMN - 2)
return false;
// 向右下方遍历,直到自己的棋子或者空位为止
int16_t m = _row + 1, n = _column + 1;
for (; m < ROW && n < COLUMN; ++m, ++n)
if (_states[m][n] == _states[_row][_column] \
|| _states[m][n] == EMPTY)
break;
// 超出边界或者不可转换对手棋子
if (m >= ROW || n >= COLUMN || m == _row + 1 || _states[m][n] == EMPTY)
return false;
// 若非尝试转换,则依次转换棋子
if (!_judgmental)
{
for (int16_t i = _row + 1, j = _column + 1; i < m; ++i, ++j)
convertOne(i, j, _states[_row][_column]);
}
return true;
}
// 转换棋子
bool Chessboard::convert(int16_t _row, int16_t _column, bool _judgmental)
{
// 转换上方棋子
bool result = convertUp(_row, _column, _judgmental);
bool convertible = result;
if (_judgmental && convertible)
return convertible;
// 转换下方棋子
result = convertDown(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换左方棋子
result = convertLeft(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换右方棋子
result = convertRight(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换左上棋子
result = convertLeftUp(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换右上棋子
result = convertRightUp(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换左下棋子
result = convertLeftDown(_row, _column, _judgmental);
convertible = convertible || result;
if (_judgmental && convertible)
return convertible;
// 转换右下棋子
result = convertRightDown(_row, _column, _judgmental);
convertible = convertible || result;
return convertible;
}
// 等待操作
void Chessboard::wait() const
{
do
{
// 显示光标
setCursorColor();
_renderer.outText(_x, _y, "◇");
Sleep(40);
// 光标所在位置有棋子
if (isChess(_coordinate, _x, _y))
{
// 显示棋子
setChessColor(_x, _y);
_renderer.outText(_x, _y, "●");
Sleep(40);
}
} while (!_kbhit()); // 按键退出循环
// 光标所在位置无棋子
if (!isChess(_coordinate, _x, _y))
_renderer.outText(_x, _y, " "); // 清除光标
}
// 处理事件
int Chessboard::handle()
{
// 接收按键
bool even = false;
int key = _getch();
if (key == 224)
{
key = _getch();
even = true;
}
// 若非双键则返回
if (!even)
return key;
switch (key)
{
case UP: // 上方向键
key = 0;
_y -= 2;
if (_y < _up)
_y = _down - 2;
break;
case DOWN: // 下方向键
key = 0;
_y += 2;
if (_y >= _down)
_y = _up + 1;
break;
case LEFT: // 左方向键
key = 0;
_x -= 4;
if (_x < _left)
_x = _right - 4;
break;
case RIGHT: // 右方向键
key = 0;
_x += 4;
if (_x >= _right)
_x = _left + 2;
break;
default:
break;
}
return key;
}
Chessboard::Chessboard(Renderer& _renderer) noexcept
: _renderer(_renderer)
{
// 计算棋盘的宽度和高度
_height = ROW + (ROW + 1);
_width = (COLUMN + COLUMN + 1) * 2;
// 计算棋盘边界
_up = (_renderer.getRow() - _height) / 2;
_down = _up + _height;
_left = (_renderer.getColumn() - _width) / 2;
_right = _left + _width;
// 初始光标位置
getCoord(ROW / 2 - 1, COLUMN / 2 - 1, _x, _y);
}
// 检查棋局有效性
Chessboard::operator bool() const noexcept
{
for (uint8_t row = 0; row < ROW; ++row)
for (uint8_t column = 0; column < COLUMN; ++column)
if (_states[row][column] < EMPTY || _states[row][column] > WHITE)
return false;
return true;
}
// 初始棋子
void Chessboard::init() noexcept
{
// 初始化棋子状态
memset(_states, EMPTY, sizeof _states);
uint8_t row = ROW / 2 - 1, column = COLUMN / 2 - 1;
_states[row][column + 1] = _states[row + 1][column] = BLACK;
_states[row][column] = _states[row + 1][column + 1] = WHITE;
}
// 显示棋局
void Chessboard::show()
{
// 设置窗口颜色
_renderer.setScreenColor(SCREEN);
// 绘制棋盘
drawChessboard();
// 绘制棋子
drawChess();
// 设置字体颜色
_renderer.setTextColor(PROMPT, SCREEN);
// 打印棋子数
int16_t x = (_left - 8) / 2, y = _renderer.getRow() / 2;
_renderer.outText(x, y, "黑子:");
int16_t column = _renderer.getColumn();
x = (column + _right - 8) / 2;
_renderer.outText(x, y, "白子:");
// 打印回合数和执子棋手
x = (column - 8) / 2;
y = (_up - 2) / 2;
_renderer.outText(x, y, "回合:");
_renderer.outText(x, y + 1, "执子:");
}
// 隐藏棋局
void Chessboard::hide() const
{
_renderer.clearScreen();
}
// 更新棋局
void Chessboard::update(uint8_t _round, State _state)
{
using std::setw;
// 计算双方棋子数
_numbers[State::BLACK - 1] = _numbers[State::WHITE - 1] = 0;
for (uint8_t row = 0; row < ROW; ++row)
for (uint8_t column = 0; column < COLUMN; ++column)
if (isChess(_index, row, column))
++_numbers[_states[row][column] - 1];
// 设置字体颜色
_renderer.setTextColor(PROMPT, SCREEN);
// 打印黑子数
_stream.seekp(0, _stream.beg);
_stream << setw(2) << std::to_string(_numbers[State::BLACK - 1]) << std::ends;
int16_t x = (_left - 8) / 2 + 6, y = _renderer.getRow() / 2;
_renderer.outText(x, y, _stream.str().c_str());
// 打印白子数
_stream.seekp(0, _stream.beg);
_stream << setw(2) << std::to_string(_numbers[State::WHITE - 1]) << std::ends;
int16_t column = _renderer.getColumn();
x = (column + _right - 8) / 2 + 6;
_renderer.outText(x, y, _stream.str().c_str());
// 打印回合数
_stream.seekp(0, _stream.beg);
_stream << setw(2) << std::to_string(_round) << std::ends;
x = (column - 8) / 2 + 6;
y = (_up - 2) / 2;
_renderer.outText(x, y, _stream.str().c_str());
// 打印执子棋手
_renderer.outText(x, y + 1, _state == BLACK ? "黑" : "白");
}
// 判断能否落子
bool Chessboard::operable(State _state)
{
for (int16_t row = 0; row < ROW; ++row)
for (int16_t column = 0; column < COLUMN; ++column)
{
// 当前位置有棋子
if (isChess(_index, row, column))
continue;
// 尝试转换
setChess(row, column, _state);
bool convertible = convert(row, column, true);
setChess(row, column, EMPTY);
// 是否能够转换
if (convertible)
return true;
}
return false;
}
// 无子可落
void Chessboard::skip() const
{
int16_t row = _renderer.getRow(), column = _renderer.getColumn();
_renderer.outText(0, column, _down, row, "无子可落!");
Sleep(500);
_renderer.outText(0, column, _down, row, " ");
}
// 判断棋局结束
bool Chessboard::judge() const noexcept
{
// 是否一方无子或者棋盘已满
return _numbers[0] == 0 || _numbers[1] == 0 \
|| _numbers[0] + _numbers[1] == ROW * COLUMN;
}
// 展示结局
void Chessboard::finish(bool _inoperable) const
{
// 设置字体颜色
_renderer.setTextColor(PROMPT, SCREEN);
// 双方有子且棋盘未满,但双方无子可落
if (_inoperable)
{
_renderer.outText(0, _renderer.getColumn(), _down, _renderer.getRow(), "双方无子可落!");
Sleep(500);
_renderer.outText(0, _renderer.getColumn(), _down, _renderer.getRow(), " ");
}
const char* result;
if (_numbers[State::BLACK - 1] > _numbers[State::WHITE - 1])
result = "黑棋胜!";
else if (_numbers[State::BLACK - 1] < _numbers[State::WHITE - 1])
result = "白棋胜!";
else
result = "平局!";
_renderer.outText(0, _renderer.getColumn(), _down, _renderer.getRow(), result);
Sleep(500);
}
// 落子操作
bool Chessboard::put(State _state)
{
// 计算下标
int16_t row, column;
getIndex(_x, _y, row, column);
// 当前位置有棋子
if (isChess(_index, row, column))
return false;
// 设置棋子并尝试转换
setChess(row, column, _state);
if (!convert(row, column, true))
{
// 转换失败回退操作
setChess(row, column, EMPTY);
return false;
}
// 转换成功则展示转换过程
convert(row, column, false);
return true;
}
// 操作循环
int Chessboard::loop()
{
using std::setw;
// 按键操作
int key;
do
{
wait();
key = handle();
} while (key == 0); // 键值为零,说明棋局捕获按键,继续接收按键
// 中止对弈
if (key == ESCAPE)
{
_renderer.setTextColor(PROMPT, SCREEN);
_renderer.outText(0, _renderer.getColumn(), _down, _renderer.getRow(), "中止对弈!");
Sleep(500);
}
return key;
}
REVERSI_END
#pragma once
#include <cstdint>
#include <sstream>
#include "Reversi.hpp"
class Renderer;
REVERSI_BEGIN
class Chessboard
{
struct Index {};
struct Coordinate {};
public:
enum State : char { EMPTY, BLACK, WHITE };
private:
static constexpr uint8_t ROW = 8, COLUMN = 8; // 棋盘每边格子数
static const Index _index; // 下标说明符
static const Coordinate _coordinate; // 坐标说明符
std::stringstream _stream; // 字符流
Renderer& _renderer; // 渲染器
int16_t _width, _height; // 棋盘宽度和高度
int16_t _up, _down, _left, _right; // 棋盘边界
int16_t _x, _y; // 光标位置
State _states[ROW][COLUMN]; // 棋子状态
uint8_t _numbers[2]; // 双方棋子数
private:
friend std::istream& operator >>(std::istream& _input, Chessboard& _chessboard);
friend std::ostream& operator <<(std::ostream& _output, const Chessboard& _chessboard);
// 转换坐标为下标
void getIndex(int16_t _x, int16_t _y, int16_t& _row, int16_t& _column) const noexcept;
// 转换下标为坐标
void getCoord(int16_t _row, int16_t _column, int16_t& _x, int16_t& _y) const noexcept;
// 判断指定下标是否棋子
bool isChess(Index _index, int16_t _row, int16_t _column) const noexcept;
// 判断指定坐标是否棋子
bool isChess(Coordinate _coordinate, int16_t _x, int16_t _y) const noexcept;
// 设置棋子状态
void setChess(int16_t _row, int16_t _column, State _state) noexcept;
// 设置光标颜色
void setCursorColor() const;
// 设置棋子颜色
void setChessColor(int16_t _x, int16_t _y) const;
// 绘制棋盘
void drawChessboard();
// 绘制所有棋子
void drawChess() const;
// 转换单个棋子,展示动态效果
void convertOne(int16_t _row, int16_t _column, State _state);
// 转换上方棋子
bool convertUp(int16_t _row, int16_t _column, bool _judgmental);
// 转换下方棋子
bool convertDown(int16_t _row, int16_t _column, bool _judgmental);
// 转换左方棋子
bool convertLeft(int16_t _row, int16_t _column, bool _judgmental);
// 转换右方棋子
bool convertRight(int16_t _row, int16_t _column, bool _judgmental);
// 转换左上棋子
bool convertLeftUp(int16_t _row, int16_t _column, bool _judgmental);
// 转换右上棋子
bool convertRightUp(int16_t _row, int16_t _column, bool _judgmental);
// 转换左下棋子
bool convertLeftDown(int16_t _row, int16_t _column, bool _judgmental);
// 转换右下棋子
bool convertRightDown(int16_t _row, int16_t _column, bool _judgmental);
// 转换棋子
bool convert(int16_t _row, int16_t _column, bool _judgmental);
// 等待操作
void wait() const;
// 处理事件
int handle();
public:
Chessboard(Renderer& _renderer) noexcept;
// 检查棋局有效性
explicit operator bool() const noexcept;
// 初始棋子
void init() noexcept;
// 显示棋局
void show();
// 隐藏棋局
void hide() const;
// 更新棋局
void update(uint8_t _round, State _state);
// 判断能否落子
bool operable(State _state);
// 无子可落
void skip() const;
// 判断棋局结束
bool judge() const noexcept;
// 展示结局
void finish(bool _inoperable) const;
// 落子操作
bool put(State _state);
// 操作循环
int loop();
};
REVERSI_END
#include "Game.hpp"
#include "Renderer.hpp"
#include "Menu.hpp"
#include <Windows.h>
#include <conio.h>
REVERSI_BEGIN
// 记录文件名
static const char *IMAGE = "Reversi.dat";
// 加载记录
bool Game::load()
{
// 关闭文件流
if (_stream.is_open())
_stream.close();
// 以读方式打开文件流
_stream.open(IMAGE, Stream::in);
if (!_stream.is_open())
return false;
// 读取记录
_stream >> _chessboard;
if (!_chessboard)
return false;
_stream.read(&_round, sizeof _round);
_player = (_round & 0x01) == Identity::BLACK ? &_black : &_white;
return true;
}
// 存储记录
void Game::save()
{
// 关闭文件流
if (_stream.is_open())
_stream.close();
// 以写方式打开文件流
_stream.open(IMAGE, Stream::out);
if (_stream.is_open())
{
// 写入记录
_stream << _chessboard;
_stream.write(&_round, sizeof _round);
}
}
// 重新开始
void Game::restart() noexcept
{
_round = '\1'; // 初始回合数
_player = &_black; // 初始执子者
_chessboard.init(); // 初始化棋局
}
// 轮换执子者
void Game::rotate(bool _forward) noexcept
{
// 前向或者后向切换回合
_forward ? ++_round : --_round;
switch (*_player)
{
case Identity::BLACK: // 黑子换为白子
_player = &_white;
break;
case Identity::WHITE: // 白子换为黑子
_player = &_black;
break;
default:
break;
}
}
// 判断游戏结束
bool Game::judge()
{
// 有子可落
if (_chessboard.operable(*_player))
return false;
// 前向轮换执子者
rotate(true);
// 有子可落
if (_chessboard.operable(*_player))
{
// 后向轮换执子者
rotate(false);
return false;
}
// 双方无子可落
return true;
}
// 对弈逻辑
void Game::play()
{
// 显示棋局
_chessboard.show();
_chessboard.update(_round, *_player);
int key;
do
{
// 棋局操作循环
key = _chessboard.loop();
// 中止游戏
if (key == ESCAPE)
{
save(); // 存储记录
break;
}
// 落子操作
else if (key == SPACE)
{
// 是否成功落子
if (!_chessboard.put(*_player))
continue;
// 轮换执子者
rotate(true);
// 更新棋局
_chessboard.update(_round, *_player);
/*
* 游戏结束条件之一:一方无子或者棋盘已满
* 游戏结束条件之二:双方无子可落
*/
if (bool inoperable = false; _chessboard.judge() || (inoperable = judge()))
{
// 展示结局
_chessboard.finish(inoperable);
break;
}
// 无子可落
if (!_chessboard.operable(*_player))
{
_chessboard.skip();
rotate(true);
_chessboard.update(_round, *_player);
}
}
} while (true);
// 隐藏棋局
_chessboard.hide();
}
// 游戏循环
void Game::loop()
{
int choice;
Menu menu(_renderer);
do
{
// 加载记录,根据有无记录,初始化菜单光标
menu.resume(load());
// 选择菜单项
menu.show();
choice = menu.loop();
menu.hide();
switch (choice)
{
case 2: // 继续游戏
play();
break;
case 1: // 重新开始
restart();
play();
break;
default:
break;
}
} while (choice != 0); // 选择退出游戏即退出循环
}
REVERSI_END
#pragma once
#include <fstream>
#include "Reversi.hpp"
#include "Chessboard.hpp"
class Renderer;
REVERSI_BEGIN
class Game
{
using Stream = std::fstream;
using Identity = Chessboard::State;
private:
Stream _stream; // 组合文件流
Renderer& _renderer; // 聚合渲染器
Chessboard _chessboard; // 棋局
Identity _black, _white, *_player; // 黑白棋手
char _round; // 回合数
private:
// 加载记录
bool load();
// 存储记录
void save();
// 重新开始
void restart() noexcept;
// 轮换执子者
void rotate(bool _forward) noexcept;
// 判定游戏结束
bool judge();
// 对弈逻辑
void play();
public:
Game(Renderer& _renderer) noexcept : \
_renderer(_renderer), \
_chessboard(_renderer), \
_black(Identity::BLACK), _white(Identity::WHITE), \
_round ('\1') {}
~Game()
{
if (_stream.is_open())
_stream.close();
}
// 游戏循环
void loop();
};
REVERSI_END
#include "Menu.hpp"
#include "Renderer.hpp"
#include <Windows.h>
#include <conio.h>
#include <cstring>
REVERSI_BEGIN
// 黑色背景
static constexpr uint16_t COLOR_SCREEN = 0;
// 淡黄色前景
static constexpr uint16_t COLOR_VERSE = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
// 亮白色前景
static constexpr uint16_t COLOR_FRAME = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
// 亮红色前景
static constexpr uint16_t COLOR_RESUME = FOREGROUND_RED | FOREGROUND_INTENSITY;
// 亮蓝色前景
static constexpr uint16_t COLOR_RESTART = FOREGROUND_BLUE | FOREGROUND_INTENSITY;
// 亮绿色前景
static constexpr uint16_t COLOR_EXIT = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
// 纵横黑白
static const char TEXT_VERSE[][3] = { "胸", "罗", "万", "象", "纵", "横", "有", \
"目", "悦", "群", "情", "黑", "白", "明", \
"如", "今", "纵", "横", "已", "相", "知", \
"缘", "何", "黑", "白", "难", "相", "融" };
// 菜单标题
static const char *TEXT_TITLE = "黑白棋";
// 菜单选项
static const char *TEXT_RESUME = "继续游戏";
static const char *TEXT_RESTART = "重新开始";
static const char *TEXT_EXIT = "退出游戏";
// 边框横线
static const char *TEXT_LINE = "◆◆◆◆◆◆◆◆◆◆◆";
// 边框点形
static const char *TEXT_POINT = "◆";
// 光标字形
static const char *TEXT_CURSOR = "☆";
// 获取句列间距
inline int16_t Menu::getSpace(int16_t _width) const noexcept
{
return (_width - _size * _number) / (_number + 1);
}
// 刷新光标
void Menu::refreshCursor(const char* _cursor)
{
using std::strlen;
switch (_choice)
{
case 0: // 退出游戏
_x = (_width - static_cast<decltype(_width)>(strlen(TEXT_EXIT))) / 2;
_x = _left + _x / 2 + 1;
_renderer.outText(_x, _y - 2, _cursor);
break;
case 1: // 重新开始
_x = (_width - static_cast<decltype(_width)>(strlen(TEXT_RESTART))) / 2;
_x = _left + _x / 2 + 1;
_renderer.outText(_x, _y - 4, _cursor);
break;
case 2: // 继续游戏
_x = (_width - static_cast<decltype(_width)>(strlen(TEXT_RESUME))) / 2;
_x = _left + _x / 2 + 1;
_renderer.outText(_x, _y - 6, _cursor);
break;
default:
break;
}
}
// 修改选择
void Menu::modifyChoice(int _delta) noexcept
{
// 选择编号变化
_choice += _delta;
// 范围容错处理
if (_choice < 0)
_choice = _stored ? 2 : 1;
else if (_stored ? _choice > 2 : _choice >= 2)
_choice = 0;
}
// 更新菜单
int Menu::update()
{
// 是否按键
if (_kbhit() == 0)
return 0;
// 接收按键
bool even = false;
int key = _getch();
if (key == 224)
{
key = _getch();
even = true;
}
// 隐藏光标
refreshCursor(" ");
Sleep(40);
// 修改选择
if (even) // 双键
{
if (key == UP) // 上方向键
modifyChoice(1);
else if (key == DOWN) // 下方向键
modifyChoice(-1);
}
return key;
}
Menu::Menu(Renderer& _renderer) noexcept
: _renderer(_renderer), _stored(false), _choice(1)
{
using std::strlen;
// 计算菜单边框的宽度和高度
_width = static_cast<decltype(_width)>(strlen(TEXT_LINE));
_height = 3 + 4 + 6;
// 计算菜单边界
_up = (_renderer.getRow() - _height) / 2;
_down = _up + _height;
_left = (_renderer.getColumn() - _width) / 2;
_right = _left + _width;
// 计算字宽度与句数量
_size = static_cast<decltype(_size)>(strlen(TEXT_VERSE[0]));
_number = sizeof TEXT_VERSE / sizeof TEXT_VERSE[0] / 7 / 2;
}
// 显示菜单
void Menu::show()
{
// 设置窗口颜色
_renderer.setScreenColor(COLOR_SCREEN);
// 设置字体颜色
_renderer.setTextColor(COLOR_VERSE, COLOR_SCREEN);
// 打印前一句
int16_t counter = 0;
int16_t space = getSpace(_left);
for (int16_t column = _left - (space + _size) * _number, index = 0; \
index < _number; ++index, column += space + _size)
for (int16_t row = _up; row < _down; row += 2)
{
Sleep(68); // 两字间隔68ms
_renderer.outText(column, row, TEXT_VERSE[counter++]);
}
// 打印后一句
space = getSpace(_renderer.getColumn() - _right);
for (int16_t column = _right + space, index = 0; \
index < _number; ++index, column += space + _size)
for (int16_t row = _up; row < _down; row += 2)
{
Sleep(68);
_renderer.outText(column, row, TEXT_VERSE[counter++]);
}
// 打印菜单边框
Sleep(96);
_y = _up;
_renderer.setTextColor(COLOR_FRAME, COLOR_SCREEN);
_renderer.outText(_left, _y, TEXT_LINE);
_y += 2;
_renderer.outText(_left, _right, _y, _y + 1, TEXT_TITLE);
_renderer.outText(_left, _y += 2, TEXT_LINE);
// 继续游戏
if (_stored)
{
_renderer.setTextColor(COLOR_RESUME, COLOR_SCREEN);
_y += 2;
_renderer.outText(_left, _right, _y, _y + 1, TEXT_RESUME);
}
// 重新开始
_renderer.setTextColor(COLOR_RESTART, COLOR_SCREEN);
_y += 2;
_renderer.outText(_left, _right, _y, _y + 1, TEXT_RESTART);
// 退出游戏
_renderer.setTextColor(COLOR_EXIT, COLOR_SCREEN);
_y += 2;
_renderer.outText(_left, _right, _y, _y + 1, TEXT_EXIT);
// 打印菜单边框
_renderer.setTextColor(COLOR_FRAME, COLOR_SCREEN);
_renderer.outText(_left, _y += 2, TEXT_LINE);
for (int16_t row = _up + 1; row < _y; ++row)
{
_renderer.outText(_left, row, TEXT_POINT);
_renderer.outText(_right - 2, row, TEXT_POINT);
}
// 刷新光标
refreshCursor(TEXT_CURSOR);
}
// 隐藏菜单
void Menu::hide() const
{
_renderer.clearScreen();
}
// 菜单循环
int Menu::loop()
{
int key;
do
{
refreshCursor(TEXT_CURSOR);
Sleep(40);
key = update();
} while (key != ENTER && key != SPACE); // 按Enter或Space确定选项
return _choice;
}
REVERSI_END
#pragma once
#include <cstdint>
#include "Reversi.hpp"
class Renderer;
REVERSI_BEGIN
class Menu
{
Renderer& _renderer; // 渲染器
bool _stored; // 存储标记
int16_t _width, _height; // 菜单边框宽度和高度
int16_t _up, _down, _left, _right; // 菜单边界
int16_t _size, _number; // 字宽度与句数量
int16_t _x, _y; // 光标位置
int _choice; // 选择编号
private:
// 获取句列间距
int16_t getSpace(int16_t _width) const noexcept;
// 刷新光标
void refreshCursor(const char* _cursor);
// 更新选择
void modifyChoice(int _delta) noexcept;
// 更新菜单
int update();
public:
Menu(Renderer& _renderer) noexcept;
// 继续游戏
void resume(bool _stored) noexcept
{
this->_stored = _stored;
_choice = _stored ? 2 : 1;
}
// 显示菜单
void show();
// 隐藏菜单
void hide() const;
// 菜单循环
int loop();
};
REVERSI_END
#include "Renderer.h" #include "Renderer.hpp"
#include <Windows.h> #include <Windows.h>
...@@ -8,227 +8,261 @@ ...@@ -8,227 +8,261 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
Renderer::Renderer(int16_t row, int16_t column, const char *title) // 初始化窗口
//void Renderer::init(const char *_title) const
//{
// // 设置窗口长宽
// std::string command = "mode con lines=" + std::to_string(_row) + " cols=" + std::to_string(_column);
// std::system(command.c_str());
//
// // 设置窗口标题
// if (_title)
// {
// command.assign("title ").append(_title);
// std::system(command.c_str());
// }
//
// // 隐藏光标
// hideCursor();
//}
// 初始化窗口
void Renderer::init(const char* _title) const
{ {
handle = GetStdHandle(STD_OUTPUT_HANDLE); // 设置窗口长宽
this->row = row; SMALL_RECT rect = { 0, 0, _column - 1, _row - 1 };
this->column = column; SetConsoleWindowInfo(_handle, TRUE, &rect);
init(row, column, title);
// 当屏幕缓冲区长宽与窗口长宽一致时,隐藏横纵滚动条
SetConsoleScreenBufferSize(_handle, COORD{ _column, _row });
// 固定窗口大小
SetWindowLongPtr(
GetConsoleWindow(), // 获取控制台窗口句柄
GWL_STYLE, // 指定修改窗口样式
GetWindowLongPtr(GetConsoleWindow(), GWL_STYLE) // 获取窗口原样式
& ~WS_SIZEBOX & ~WS_MAXIMIZEBOX); // 窗口大小设为无法改变,最大化按钮设为不活跃
// 设置窗口标题
if (_title)
{
#if defined UNICODE
//int length = static_cast<int>(sizeof(char) * std::strlen(_title));
//int number = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED | MB_USEGLYPHCHARS, _title, length, NULL, 0) + 1;
//TCHAR *buffer = new TCHAR[number];
//MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED | MB_USEGLYPHCHARS, _title, length, buffer, number);
//buffer[number - 1] = TEXT('\0');
//SetConsoleTitle(buffer);
//delete buffer;
SetConsoleTitleA(_title);
#else
SetConsoleTitle(_title);
#endif
}
// 隐藏光标
hideCursor();
} }
// 清空控制台窗口 // 隐藏光标
//void Renderer::clearScreen() void Renderer::hideCursor() const
{
CONSOLE_CURSOR_INFO info;
GetConsoleCursorInfo(_handle, &info);
info.bVisible = FALSE;
SetConsoleCursorInfo(_handle, &info);
}
// 隐藏光标(不兼容旧版控制台)
//void Renderer::hideCursor() const
//{ //{
// std::system("cls"); // std::cout << "\033[?25l";
//} //}
// 清空控制台窗口 // 显示光标
//void Renderer::clearScreen() void Renderer::showCursor() const
{
CONSOLE_CURSOR_INFO info;
GetConsoleCursorInfo(_handle, &info);
info.bVisible = TRUE;
SetConsoleCursorInfo(_handle, &info);
}
// 显示光标(不兼容旧版控制台)
//void Renderer::showCursor() const
//{ //{
// // 获取控制台缓冲区信息 // std::cout << "\033[?25h";
// CONSOLE_SCREEN_BUFFER_INFO info;
// GetConsoleScreenBufferInfo(handle, &info);
//
// DWORD size = info.dwSize.X * info.dwSize.Y; // 缓冲区的字符容纳量
// DWORD number;
// COORD coord = { 0, 0 };
// // 以空格填充整个控制台
// FillConsoleOutputCharacter(handle, \
// static_cast<TCHAR>(' '), size, coord, &number);
//
// // 再次获取缓冲区信息
// GetConsoleScreenBufferInfo(handle, &info);
// // 以默认颜色填充控制台
// FillConsoleOutputAttribute(handle, \
// info.wAttributes, size, coord, &number);
// // 移动光标至(0, 0)
// SetConsoleCursorPosition(handle, coord);
//} //}
// 清空控制台窗口 // 移动光标
void Renderer::clearScreen() void Renderer::moveCursor(int16_t _x, int16_t _y) const
{ {
std::cout << "\033[2J" << "\033[0;0H"; COORD coord = { _x, _y };
SetConsoleCursorPosition(_handle, coord);
} }
// 设置窗口颜色 // 移动光标(不兼容旧版控制台)
//void Renderer::setScreenColor(uint16_t background) //void Renderer::moveCursor(int16_t _x, int16_t _y) const
//{ //{
// std::ostringstream output; // std::cout << "\033[" << _y << ';' << _x << 'H';
// output << "color " << std::hex << (background >> 4 & 0xF) << 'f'; // // \033[0m 禁用所有属性
// std::system(output.str().c_str()); // // \033[1m 高亮
// // \033[4m 下划线
// // \033[7m 反显(交换前景色与背景色)
// // \033[nA 光标上移n行
// // \033[nB 光标下移n行
// // \033[nC 光标右移n列
// // \033[nD 光标左移n列
// // \033[K 清除光标至行尾的内容,即基于当前属性填充光标至行尾
// // \033[s 保存光标位置
// // \033[u 恢复光标位置
//} //}
// 设置窗口颜色 Renderer::Renderer(int16_t _row, int16_t _column, const char *_title)
//void Renderer::setScreenColor(uint16_t background) : _handle(GetStdHandle(STD_OUTPUT_HANDLE)), _row(_row), _column(_column)
{
init(_title);
}
// 清空控制台窗口
//void Renderer::clearScreen() const
//{ //{
// // 获取缓冲区信息 // std::system("cls");
// CONSOLE_SCREEN_BUFFER_INFO info;
// GetConsoleScreenBufferInfo(handle, &info);
//
// DWORD size = info.dwSize.X * info.dwSize.Y; // 缓冲区的字符容纳量
// DWORD number;
// COORD coord = { 0, 0 };
// // 以默认颜色填充控制台
// FillConsoleOutputAttribute(handle, \
// background, size, coord, &number);
// // 移动光标至(0, 0)
// SetConsoleCursorPosition(handle, coord);
//} //}
// 设置窗口颜色 // 清空控制台窗口
void Renderer::setScreenColor(uint16_t background) void Renderer::clearScreen() const
{ {
std::ostringstream output; // 获取控制台缓冲区信息
output << "\033[" << (background >> 7 & 0x1) << ";4" << (background >> 4 & 0x7) << 'm'; CONSOLE_SCREEN_BUFFER_INFO info;
std::cout << output.str(); GetConsoleScreenBufferInfo(_handle, &info);
clearScreen();
std::cout << "\033[0m"; // 以空格填充整个控制台
DWORD size = info.dwSize.X * info.dwSize.Y; // 缓冲区的字符容纳量
DWORD number;
COORD coord = { 0, 0 };
FillConsoleOutputCharacter(_handle, \
static_cast<TCHAR>(' '), size, coord, &number);
// 再次获取缓冲区信息
GetConsoleScreenBufferInfo(_handle, &info);
// 以默认颜色填充控制台
FillConsoleOutputAttribute(_handle, \
info.wAttributes, size, coord, &number);
// 移动光标至(0, 0)
SetConsoleCursorPosition(_handle, coord);
} }
// 清空控制台窗口(不兼容旧版控制台)
//void Renderer::clearScreen() const
//{
// std::cout << "\033[2J" << "\033[0;0H";
//}
/* /*
* 设置文本颜色 * 设置窗口颜色
* 参数: * 参数:
* foreground 前景色 * _background 背景色
* background 背景色 * _foreground 前景色
*/ */
//void Renderer::setTextColor(uint16_t foreground, uint16_t background) //void Renderer::setScreenColor(uint16_t _background, uint16_t _foreground) const
//{ //{
// std::ostringstream output; // std::ostringstream output;
// output << "color " << std::hex << (background >> 4 & 0xF) << (foreground & 0xF); // output << "color " << std::hex << (_background >> 4 & 0x0F) << (_foreground & 0x0F);
// system(output.str().c_str()); // system(output.str().c_str());
//} //}
// 设置文本颜色 // 设置窗口颜色
//void Renderer::setTextColor(uint16_t foreground, uint16_t background) void Renderer::setScreenColor(uint16_t _background, uint16_t _foreground) const
//{
// SetConsoleTextAttribute(handle, foreground & 0x0F | background & 0xF0);
//}
// 设置文本颜色
void Renderer::setTextColor(uint16_t foreground, uint16_t background)
{ {
std::ostringstream output; // 获取缓冲区信息
output << "\033[" << (background >> 7 & 0x1) << ";4" << (background >> 4 & 0x7) << 'm' CONSOLE_SCREEN_BUFFER_INFO info;
<< "\033[" << (foreground >> 3 & 0x1) << ";3" << (foreground & 0x7) << 'm'; GetConsoleScreenBufferInfo(_handle, &info);
std::cout << output.str();
}
// 输出文本 // 以默认颜色填充控制台
void Renderer::outText(int16_t x, int16_t y, const char *text) DWORD size = info.dwSize.X * info.dwSize.Y; // 缓冲区的字符容纳量
{ DWORD number;
moveCursor(x, y); COORD coord = { 0, 0 };
std::cout << text; FillConsoleOutputAttribute(_handle, \
} _background, size, coord, &number);
// 输出文本至指定范围 // 移动光标至(0, 0)
void Renderer::outTextRange(int16_t left, int16_t right, int16_t up, int16_t down, const char *text) SetConsoleCursorPosition(_handle, coord);
{
auto length = static_cast<decltype(left)>(std::strlen(text)); // 设置字体颜色
moveCursor((left + right - length) / 2, (up + down - 1) / 2); setTextColor(_foreground, _background);
std::cout << text;
} }
// 初始化控制台窗口 /*
//void Renderer::init(int16_t row, int16_t column, const char *title) * 交换红色位和蓝色位
* 参数:
* _color 颜色
* _offset 左向偏移
*/
//static uint16_t swapRedAndBlue(uint16_t _color, uint16_t _offset) noexcept
//{ //{
// // 设置窗口长宽 // // 获取红色位或蓝色位
// std::string command = "mode con lines=" + std::to_string(row) + " cols=" + std::to_string(column); // uint16_t temp = _color & 0x1 << _offset;
// std::system(command.c_str()); //
// // 设置窗口标题 // // 清零色位并赋值为另一色位
// if (title) // _color = _color & ~(0x1 << _offset) | (_color & 0x4 << _offset) >> 2;
// { // return _color & ~(0x4 << _offset) | temp << 2;
// command.assign("title ").append(title);
// std::system(command.c_str());
// }
// // 隐藏光标
// hideCursor();
//} //}
// 初始化控制台窗口 // 设置窗口颜色(不兼容旧版控制台)
void Renderer::init(int16_t row, int16_t column, const char *title) //void Renderer::setScreenColor(uint16_t _background, uint16_t _foreground) const
{
// 设置窗口长宽
SMALL_RECT rect = { 0, 0, column - 1, row - 1 };
SetConsoleWindowInfo(handle, TRUE, &rect);
// 当屏幕缓冲区长宽与窗口长宽一致时,隐藏横纵滚动条
SetConsoleScreenBufferSize(handle, COORD{ column, row });
// 固定窗口大小
SetWindowLongPtr(
GetConsoleWindow(), // 获取控制台窗口句柄
GWL_STYLE, // 指定修改窗口样式
GetWindowLongPtr(GetConsoleWindow(), GWL_STYLE) // 获取窗口原样式
& ~WS_SIZEBOX & ~WS_MAXIMIZEBOX); // 窗口大小设为无法改变,最大化按钮设为不活跃
// 隐藏光标
hideCursor();
// 设置窗口标题
if (title)
{
#if defined UNICODE
//int length = static_cast<int>(sizeof(char) * std::strlen(title));
//int number = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED | MB_USEGLYPHCHARS, title, length, NULL, 0) + 1;
//TCHAR *buffer = new TCHAR[number];
//MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED | MB_USEGLYPHCHARS, title, length, buffer, number);
//buffer[number - 1] = TEXT('\0');
//SetConsoleTitle(buffer);
//delete buffer;
SetConsoleTitleA(title);
#else
SetConsoleTitle(title);
#endif
}
}
// 隐藏光标
//void Renderer::hideCursor()
//{ //{
// CONSOLE_CURSOR_INFO info; // // 交换色位
// GetConsoleCursorInfo(handle, &info); // uint16_t color = swapRedAndBlue(_background, 4);
// info.bVisible = FALSE; //
// SetConsoleCursorInfo(handle, &info); // // 设置背景色
// std::ostringstream output;
// output << "\033[" << (color >> 7 & 0x01) << ";4" << (color >> 4 & 0x07) << 'm';
// std::cout << output.str();
//
// // 填充窗口
// clearScreen();
//
// // 清除设置
// std::cout << "\033[0m";
//
// // 设置字体颜色
// setTextColor(_foreground, _background);
//} //}
// 隐藏光标 // 设置文本颜色
void Renderer::hideCursor() void Renderer::setTextColor(uint16_t _foreground, uint16_t _background) const
{ {
std::cout << "\033[?25l"; SetConsoleTextAttribute(_handle, _foreground & 0x0F | _background & 0xF0);
} }
// 显示光标 // 设置文本颜色(不兼容旧版控制台)
//void Renderer::showCursor() //void Renderer::setTextColor(uint16_t _foreground, uint16_t _background) const
//{ //{
// CONSOLE_CURSOR_INFO info; // // 交换色位
// GetConsoleCursorInfo(handle, &info); // _foreground = swapRedAndBlue(_foreground, 0);
// info.bVisible = TRUE; // _background = swapRedAndBlue(_background, 4);
// SetConsoleCursorInfo(handle, &info); //
// // 设置背景色与前景色
// std::ostringstream output;
// output << "\033[" << (_background >> 7 & 0x01) << ";4" << (_background >> 4 & 0x07) << 'm'
// << "\033[" << (_foreground >> 3 & 0x01) << ";3" << (_foreground & 0x07) << 'm';
// std::cout << output.str();
//} //}
// 显示光标 // 输出文本
void Renderer::showCursor() void Renderer::outText(int16_t _x, int16_t _y, const char *_text) const
{ {
std::cout << "\33[?25h"; moveCursor(_x, _y);
std::cout << _text;
} }
// 移动光标 // 输出文本至指定范围
//void Renderer::moveCursor(int16_t x, int16_t y) void Renderer::outText(int16_t _left, int16_t _right, int16_t _up, int16_t _down, const char *_text) const
//{
// COORD coord = { x, y };
// SetConsoleCursorPosition(handle, coord);
//}
// 移动光标
void Renderer::moveCursor(int16_t x, int16_t y)
{ {
std::cout << "\033[" << y << ';' << x << 'H'; auto length = static_cast<decltype(_left)>(std::strlen(_text));
// \033[0m 禁用所有属性 moveCursor((_left + _right - length) / 2, (_up + _down - 1) / 2);
// \033[1m 高亮 std::cout << _text;
// \033[4m 下划线
// \033[7m 反显(交换前景色与背景色)
// \033[nA 光标上移n行
// \033[nB 光标下移n行
// \033[nC 光标右移n列
// \033[nD 光标左移n列
// \033[K 清除光标至行尾的内容,即基于当前属性填充光标至行尾
// \033[s 保存光标位置
// \033[u 恢复光标位置
} }
#pragma once
#include <cstdint>
class Renderer
{
void *handle;
short row, column;
public:
Renderer(int16_t row, int16_t column, const char *title = nullptr);
short getRow() { return row; }
short getColumn() { return column; }
void clearScreen();
void setScreenColor(uint16_t background);
void setTextColor(uint16_t foreground, uint16_t background);
void outText(int16_t x, int16_t y, const char *text);
void outTextRange(int16_t left, int16_t right, int16_t up, int16_t down, const char *text);
private:
void init(int16_t row, int16_t column, const char *title);
void hideCursor();
void showCursor();
void moveCursor(int16_t x, int16_t y);
};
#pragma once
#include <cstdint>
class Renderer
{
void *_handle; // 窗口句柄
int16_t _row, _column; // 窗口行数和列数
private:
// 初始化窗口
void init(const char* _title) const;
// 隐藏光标
void hideCursor() const;
// 显示光标
void showCursor() const;
// 隐藏光标
void moveCursor(int16_t _x, int16_t _y) const;
public:
Renderer(int16_t _row, int16_t _column, const char *_title = nullptr);
// 获取窗口行数
int16_t getRow() const noexcept { return _row; }
// 获取窗口列数
int16_t getColumn() const noexcept { return _column; }
// 清空控制台窗口
void clearScreen() const;
// 设置窗口颜色
void setScreenColor(uint16_t _background, uint16_t _foreground = 0x0F) const;
// 设置文本颜色
void setTextColor(uint16_t _foreground, uint16_t _background) const;
// 输出文本至指定位置
void outText(int16_t _x, int16_t _y, const char *_text) const;
// 输出文本至指定范围
void outText(int16_t _left, int16_t _right, int16_t _up, int16_t _down, const char *_text) const;
};
#ifndef REVERSI_H
#define REVERSI_H
// ¼üÖµ
#define ENTER 13
#define ESCAPE 27
#define SPACE 32
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
// ÃüÃû¿Õ¼ä
#define REVERSI_BEGIN namespace reversi {
#define REVERSI_END }
#endif
#include "Renderer.hpp"
#include "Game.hpp"
int main()
{
using namespace reversi;
Renderer renderer(25, 80, "Reversi");
Game game(renderer);
game.loop();
return 0;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册