♟️

Siv3D | ボードゲームのテンプレート集

2020/10/01に公開

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