Created
July 4, 2024 15:31
-
-
Save Kiterai/f58dde7d817a545709941afe65fad4a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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