♟️
Siv3D | ボードゲームのテンプレート集
1. リバーシの盤面
石を置く操作は実装されていません。
# include <Siv3D.hpp> // OpenSiv3D v0.6.3
namespace State
{
constexpr int32 Void = -1; // 石を置けないマス(特殊ルール)
constexpr int32 Empty = 0; // 石を置けるマス
constexpr int32 Black = 1; // 黒が置かれているマス
constexpr int32 White = 2; // 白が置かれているマス
}
const Array<Grid<int32>> BoardPatterns =
{
{ // Board A
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 2, 0, 0, 0 },
{ 0, 0, 0, 2, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
},
{ // Board B
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 0, 0, 2, 2, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
},
{ // Board C
{ 0, 0, 0, -1, -1, 0, 0, 0 },
{ 0, 0, 0, -1, -1, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ -1, -1, 0, 1, 2, 0, -1, -1 },
{ -1, -1, 0, 2, 1, 0, -1, -1 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, -1, -1, 0, 0, 0 },
{ 0, 0, 0, -1, -1, 0, 0, 0 },
},
};
void DrawBoard(const Grid<int32>& board, const Vec2& boardPos, double cellSize)
{
for (auto p : step(board.size()))
{
const int32 state = board[p];
if (state == State::Void)
{
continue;
}
const RectF cell{ boardPos + (p * cellSize), cellSize };
cell.draw(ColorF{ 0.2, 0.7, 0.3 })
.drawFrame(1, 0, ColorF{ 0.25 });
if (state == State::Empty)
{
continue;
}
const ColorF colorTop = (state == State::Black) ? ColorF{ 0.1 } : ColorF{ 0.9 };
const ColorF colorBottom = (state == State::Black) ? ColorF{ 0.9 } : ColorF{ 0.1 };
Circle{ cell.center(), (cellSize * 0.45) }
.drawShadow(Vec2{ 1, 3 }, 4, 0)
.draw(colorBottom)
.movedBy(0, -2)
.draw(colorTop);
}
}
Optional<Point> GetCellIndex(const Vec2& cursorPos, const Grid<int32>& board, const Vec2& boardPos, double cellSize)
{
const int32 xIndex = static_cast<int32>((cursorPos.x - boardPos.x) / cellSize);
const int32 yIndex = static_cast<int32>((cursorPos.y - boardPos.y) / cellSize);
if (not InRange(xIndex, 0, (board.size().x - 1))
|| not InRange(yIndex, 0, (board.size().y - 1)))
{
return none;
}
if (board[yIndex][xIndex] == State::Void)
{
return none;
}
return Point{ xIndex, yIndex };
}
void Main()
{
Scene::SetBackground(ColorF{ 0.8, 0.7, 0.6 });
size_t boardPatternIndex = 0;
Grid<int32> board = BoardPatterns[boardPatternIndex];
constexpr Vec2 boardPos{ 40, 40 };
constexpr double cellSize = 64;
while (System::Update())
{
DrawBoard(board, boardPos, cellSize);
if (SimpleGUI::RadioButtons(boardPatternIndex,
{ U"Board A", U"Board B", U"Board C" }, Vec2{ 600, 40 }))
{
board = BoardPatterns[boardPatternIndex];
}
if (const Optional<Point> cellIndex = GetCellIndex(Cursor::Pos(), board, boardPos, cellSize))
{
RectF{ boardPos + (*cellIndex * cellSize), cellSize }
.draw(ColorF{ 1.0, 0.5, 0.0, 0.5 })
.drawFrame(4, 0, ColorF{ 1.0, 0.5, 0.0 });
}
}
}
2. 囲碁の盤面
石を置く操作は実装されていません。
# include <Siv3D.hpp> // OpenSiv3D v0.6.3
namespace State
{
constexpr int32 Empty = 0; // 石を置けるマス
constexpr int32 Black = 1; // 黒が置かれているマス
constexpr int32 White = 2; // 白が置かれているマス
}
Image CreateGobanImage(const Size& gobanSize)
{
PerlinNoise noise;
return Image::Generate(Size{ 800, 800 }, [&](Point p)
{
const double x = Fraction(noise.octave2D0_1(p * Vec2{ 0.03, 0.0002 }, 2) * 25) * 0.15 + 0.85;
return ColorF{ x, 0.7 * Pow(x, 1.3), 0.35 * x };
}).gaussianBlurred(1).scaled(gobanSize);
}
Image CreateStoneImage(bool black, int32 cellSize)
{
const Vec4 sphere{ 0, 0, 1.5, 1.0 };
const Vec3 lightDir = Vec3{ -1, 1, -1 }.normalized();
return Image::Generate(Size{ 200, 200 }, [=](Point p)
{
const Vec3 dir = Vec3{ p.x / 100.0 - 1.0, p.y / -100.0 + 1.0, 1.0 }.normalized();
const double B = -sphere.xyz().dot(dir);
const double D = B * B - (sphere.xyz().dot(sphere.xyz()) - (sphere.w * sphere.w));
if (D > 0.0)
{
const double t = -B - Sqrt(D);
if (t > 0.0)
{
const Vec3 pos = dir * t;
const Vec3 normal = (pos - sphere.xyz()).normalized();
const Vec3 half = (lightDir - pos.normalized()).normalized();
const double diffuse = Max(normal.dot(lightDir), 0.0) * (black ? 0.02 : 1.5);
const double specular = Pow(Max(normal.dot(half), 0.0), 32);
return ColorF{ Pow(diffuse + specular, 1.0 / 2.2) };
}
}
return ColorF{ black ? 0.0 : 0.5, 0.0 };
}).scaled(cellSize * 1.1 / 200);
}
constexpr Point GetPos(const Point& xy, const Point& gobanPos, int32 padding, int32 cellSize)
{
return gobanPos + Point::All(padding) + (xy * cellSize);
}
void DrawGoban(const Texture& gobanTexture,
const Point& gobanPos, const Size& gobanSize, int32 padding, int32 cellSize)
{
RectF{ gobanPos, gobanSize }.drawShadow(Vec2{ 6, 6 }, 20, 5);
gobanTexture.draw(gobanPos);
for (auto i : step(19))
{
const Point r0 = GetPos({ 0, i }, gobanPos, padding, cellSize);
const Point c0 = GetPos({ i, 0 }, gobanPos, padding, cellSize);
Rect{ r0, (18 * cellSize), 1 }.draw(ColorF{ 0.1 });
Rect{ c0, 1, (18 * cellSize) }.draw(ColorF{ 0.1 });
}
for (auto x : { 3, 9, 15 })
{
for (auto y : { 3, 9, 15 })
{
const Point pos = GetPos({ x, y }, gobanPos, padding, cellSize);
Circle{ pos, 2.1 }.moveBy(0.5, 0.5).draw(ColorF{ 0.1 });
}
}
}
void DrawStones(const Grid<int32>& board, const Point& gobanPos, int32 padding, int32 cellSize,
const Texture& blackStoneTexture, const Texture& whiteStoneTexture)
{
for (auto p : step(board.size()))
{
const int32 state = board[p];
if (state == State::Empty)
{
continue;
}
const Vec2 pos = GetPos(p, gobanPos, padding, cellSize);
Circle{ pos, (cellSize * 0.5) }.drawShadow(Vec2{ 2.5, 2.5 }, 3);
(state == State::Black ? blackStoneTexture : whiteStoneTexture)
.drawAt(pos);
}
}
Optional<Point> GetCellIndex(const Vec2& cursorPos, const Point& gobanPos, int32 padding, int32 cellSize)
{
const int32 xIndex = static_cast<int32>((cursorPos.x - padding - gobanPos.x + (cellSize / 2)) / cellSize);
const int32 yIndex = static_cast<int32>((cursorPos.y - padding - gobanPos.y + (cellSize / 2)) / cellSize);
if (InRange(xIndex, 0, 18) && InRange(yIndex, 0, 18))
{
return Point{ xIndex, yIndex };
}
else
{
return none;
}
}
void Main()
{
Scene::SetBackground(ColorF{ 0.7, 0.7, 0.5 });
constexpr Point gobanPos{ 30, 30 };
constexpr int32 padding = 18;
constexpr int32 cellSize = 28;
constexpr Size gobanSize = Size::All((18 * cellSize) + (2 * padding));
const Texture gobanTexture{ CreateGobanImage(gobanSize), TextureDesc::Mipped };
const Texture blackStoneTexture{ CreateStoneImage(true, cellSize), TextureDesc::Mipped };
const Texture whiteStoneTexture{ CreateStoneImage(false, cellSize), TextureDesc::Mipped };
Grid<int32> board(Size{ 19, 19 }, State::Empty);
board[{16, 3}] = State::Black;
board[{2, 15}] = State::White;
board[{4, 15}] = State::Black;
board[{3, 3}] = State::White;
board[{3, 13}] = State::Black;
board[{14, 2}] = State::White;
board[{1, 14}] = State::Black;
board[{14, 16}] = State::White;
while (System::Update())
{
DrawGoban(gobanTexture, gobanPos, gobanSize, padding, cellSize);
DrawStones(board, gobanPos, padding, cellSize, blackStoneTexture, whiteStoneTexture);
if (const Optional<Point> cellIndex = GetCellIndex(Cursor::Pos(), gobanPos, padding, cellSize))
{
Rect{ gobanPos + Point::All(padding - (cellSize / 2) + 1) + (*cellIndex * cellSize), cellSize }
.stretched(2)
.draw(ColorF{ 0.5, 0.5, 1.0, 0.5 })
.drawFrame(4, 0, ColorF{ 0.5, 0.5, 1.0 });
}
}
}
3. チェスの盤面
v0.4.3 向けのコードです。v0.6.3 向けのコードは準備中です。
# include <Siv3D.hpp> // OpenSiv3D v0.4.3
enum class Player : uint8
{
Black,
White,
};
enum class PieceType : uint8
{
Pawn,
Knight,
Bishop,
Rook,
Queen,
King,
};
struct Piece
{
Player player;
PieceType type;
};
void Main()
{
Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });
constexpr int32 CellSize = 72;
constexpr Point Offset{ 112, 12 };
const Array<Texture> pieceTextures =
{
Texture{ SDF::GenerateMSDFIcon(0xf443, 4), TextureDesc::SDF },
Texture{ SDF::GenerateMSDFIcon(0xf441, 4), TextureDesc::SDF },
Texture{ SDF::GenerateMSDFIcon(0xf43a, 4), TextureDesc::SDF },
Texture{ SDF::GenerateMSDFIcon(0xf447, 4), TextureDesc::SDF },
Texture{ SDF::GenerateMSDFIcon(0xf445, 4), TextureDesc::SDF },
Texture{ SDF::GenerateMSDFIcon(0xf43f, 4), TextureDesc::SDF },
};
Grid<Optional<Piece>> grid =
{
{ Piece{ Player::Black, PieceType::Rook }, Piece{ Player::Black, PieceType::Knight }, Piece{ Player::Black, PieceType::Bishop }, Piece{ Player::Black, PieceType::Queen },
Piece{ Player::Black, PieceType::King }, Piece{ Player::Black, PieceType::Bishop }, Piece{ Player::Black, PieceType::Knight }, Piece{ Player::Black, PieceType::Rook }},
{ Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn },
Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn }, Piece{ Player::Black, PieceType::Pawn }},
{},
{},
{},
{},
{ Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn },
Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn }, Piece{ Player::White, PieceType::Pawn }},
{ Piece{ Player::White, PieceType::Rook }, Piece{ Player::White, PieceType::Knight }, Piece{ Player::White, PieceType::Bishop }, Piece{ Player::White, PieceType::Queen },
Piece{ Player::White, PieceType::King }, Piece{ Player::White, PieceType::Bishop }, Piece{ Player::White, PieceType::Knight }, Piece{ Player::White, PieceType::Rook }},
};
while (System::Update())
{
Rect{ Offset , CellSize * 8 }
.drawShadow(Vec2{ 4, 4 }, 20, 4);
for (auto&& p : step(grid.size()))
{
Rect{ p * CellSize + Offset, CellSize }
.draw(IsEven(p.x + p.y) ? ColorF{ 1.0, 0.8, 0.6 } : ColorF{ 0.6, 0.4, 0.2 });
}
{
const Point point = (Cursor::Pos() - Offset) / CellSize;
if (InRange(point.x, 0, 7) && InRange(point.y, 0, 7))
{
Cursor::RequestStyle(CursorStyle::Hand);
Rect{ point * CellSize + Offset, CellSize }
.draw(ColorF{ 1.0, 0.5, 0.0, 0.5 })
.drawFrame(4, 0, ColorF{ 1.0, 0.5, 0.0 });
}
}
for (auto&& p : step(grid.size()))
{
if (const auto& piece = grid[p])
{
const Texture& texture = pieceTextures[FromEnum(piece->type)];
Graphics2D::SetSDFParameters(16.0, 0.1);
texture
.scaled(2)
.drawAt(p * CellSize + Point::All(CellSize) / 2 + Offset, ColorF{ piece->player == Player::Black ? 0.9 : 0.25 });
Graphics2D::SetSDFParameters(16.0, 0.0);
texture
.scaled(2)
.drawAt(p * CellSize + Point::All(CellSize) / 2 + Offset, ColorF{ piece->player == Player::Black ? 0.25 : 0.9 });
}
}
}
}
Discussion