Skip to content

Instantly share code, notes, and snippets.

@Kiterai
Created July 4, 2024 15:31
Show Gist options
  • Save Kiterai/f58dde7d817a545709941afe65fad4a7 to your computer and use it in GitHub Desktop.
Save Kiterai/f58dde7d817a545709941afe65fad4a7 to your computer and use it in GitHub Desktop.
# include <Siv3D.hpp>
constexpr int field_sz = 4;
constexpr int sz = 80;
constexpr int padding = 10;
constexpr int xBias = (800 - (sz * field_sz + padding * (field_sz - 1))) / 2;
constexpr int yBias = (600 - (sz * field_sz + padding * (field_sz - 1))) / 2;
struct Field2048 {
int field[field_sz][field_sz];
bool freeze[field_sz][field_sz];
int prev_field[field_sz][field_sz];
Array<std::pair<Point, Point>> prev_move;
Array<Point> appear;
Field2048() {
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++) {
field[i][j] = -1;
freeze[i][j] = false;
}
add_number(2);
add_number(2);
}
Point add_number(int num) {
Array<Point> blank;
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++) {
if (field[i][j] == -1) {
blank.push_back({ j, i });
}
}
const auto &target = blank[Random(blank.size() - 1)];
field[target.y][target.x] = num;
return target;
}
void move_number(int x, int y, int dx, int dy) {
if (field[y][x] == -1) return;
auto px = x, py = y;
while (0 <= x + dx && x + dx < field_sz && 0 <= y + dy && y + dy < field_sz) {
if (field[y + dy][x + dx] == -1) {
std::swap(field[y + dy][x + dx], field[y][x]);
}
else if (field[y + dy][x + dx] == field[y][x] && !freeze[y + dy][x + dx]) {
field[y + dy][x + dx] += field[y][x];
field[y][x] = -1;
freeze[y + dy][x + dx] = true;
y += dy;
x += dx;
break;
}
else {
break;
}
y += dy;
x += dx;
}
prev_move.push_back({ { px, py }, { x, y } });
}
bool operator!=(Field2048& other) const {
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++)
if (field[i][j] != other.field[i][j]) return true;
return false;
}
void apply(Field2048& after) {
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++) {
prev_field[i][j] = field[i][j];
field[i][j] = after.field[i][j];
freeze[i][j] = false;
}
prev_move = after.prev_move;
appear.clear();
if (Random() <= 0.1)
appear.push_back(add_number(4));
else
appear.push_back(add_number(2));
}
auto up() const {
auto tmp = (*this);
tmp.prev_move.clear();
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++) {
tmp.move_number(i, j, -1, 0);
}
return tmp;
}
auto down() const {
auto tmp = (*this);
tmp.prev_move.clear();
for (int i = field_sz - 1; i >= 0; i--)
for (int j = 0; j < field_sz; j++) {
tmp.move_number(i, j, +1, 0);
}
return tmp;
}
auto left() const {
auto tmp = (*this);
tmp.prev_move.clear();
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++) {
tmp.move_number(j, i, 0, -1);
}
return tmp;
}
auto right() const {
auto tmp = (*this);
tmp.prev_move.clear();
for (int i = field_sz - 1; i >= 0; i--)
for (int j = 0; j < field_sz; j++) {
tmp.move_number(j, i, 0, +1);
}
return tmp;
}
};
void Main()
{
const Font font{ 50 };
bool gameover = false;
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
Field2048 field;
Timer animTimer;
while (System::Update())
{
bool changed = false;
if (!animTimer.isRunning()) {
if (KeyUp.down()) {
if (auto tmp = field.up(); field != tmp) {
field.apply(tmp);
changed = true;
}
}
else if (KeyDown.down()) {
if (auto tmp = field.down(); field != tmp) {
field.apply(tmp);
changed = true;
}
}
else if (KeyLeft.down()) {
if (auto tmp = field.left(); field != tmp) {
field.apply(tmp);
changed = true;
}
}
else if (KeyRight.down()) {
if (auto tmp = field.right(); field != tmp) {
field.apply(tmp);
changed = true;
}
}
}
if (changed) {
animTimer.set(0.3s);
animTimer.start();
if (!(field.up() != field || field.down() != field || field.left() != field || field.right() != field)) {
gameover = true;
}
}
if (KeyR.down()) {
field = Field2048{};
gameover = false;
}
// Render
Rect{ xBias - padding, yBias - padding, sz * field_sz + padding * (field_sz + 1), sz * field_sz + padding * (field_sz + 1)}.draw(Palette::White);
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++)
Rect{ xBias + i * (sz + padding), yBias + j * (sz + padding), sz, sz }.draw(Palette::Lightgray);
if (animTimer.isRunning()) {
constexpr auto moveDur = 0.6;
constexpr auto appearDur = 1 - moveDur;
if (animTimer.progress0_1() / moveDur < 1.0) {
for (const auto& [prev, cur] : field.prev_move) {
auto p01 = EaseInOutCubic(Min(1.0, animTimer.progress0_1() / moveDur));
auto p10 = 1 - p01;
auto vx = xBias + (prev.y * p10 + cur.y * p01) * (sz + padding);
auto vy = yBias + (prev.x * p10 + cur.x * p01) * (sz + padding);
RectF{ vx, vy, sz, sz }.draw(Palette::Palegoldenrod);
font(U"{}"_fmt(field.prev_field[prev.y][prev.x])).drawAt(vx + sz / 2, vy + sz / 2, Palette::Orange);
}
} else {
for (const auto& [prev, cur] : field.prev_move) {
auto vx = xBias + cur.y * (sz + padding);
auto vy = yBias + cur.x * (sz + padding);
RectF{ vx, vy, sz, sz }.draw(Palette::Palegoldenrod);
font(U"{}"_fmt(field.field[cur.y][cur.x])).drawAt(vx + sz / 2, vy + sz / 2, Palette::Orange);
}
auto p01 = EaseOutSine((animTimer.progress0_1() - moveDur) / appearDur);
for (const auto& pos : field.appear) {
auto vx = xBias + pos.y * (sz + padding);
auto vy = yBias + pos.x * (sz + padding);
RectF{ vx, vy, sz, sz }.scaled(p01).draw(Palette::Palegoldenrod);
}
}
}
else {
for (int i = 0; i < field_sz; i++)
for (int j = 0; j < field_sz; j++)
{
if (field.field[i][j] != -1) {
Rect{ xBias + i * (sz + padding), yBias + j * (sz + padding), sz, sz }.draw(Palette::Palegoldenrod);
font(U"{}"_fmt(field.field[i][j])).drawAt(xBias + i * (sz + padding) + sz / 2, yBias + j * (sz + padding) + sz / 2, Palette::Orange);
}
}
}
if (gameover) {
Rect{ 200, 200, 400, 200 }.draw(Palette::Gray);
font(U"Game over").drawAt({ 400, 270 });
font(U"R to restart").drawAt({ 400, 330 });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment