mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
feat: migrate Remeres to 12.x assets system and modernized item handling • Migrated old Remeres .dat/.spr system to new 12.x client /assets/ appearance & sprite format • Removed OTB dependency and outdated client version structures • Integrated Protobuf-based sprite reader (credit: @nekiro) • Major refactor on graphics.cpp structure and brush icon handling • Updated brushes and tags system • Fixed top order drawing bugs and missing brushes icons • Fixed outfit color paint and correct offset handling for non-32x32 sprites • Re-enabled map saving and loading • Improved creature sprite rendering and brush previews • Added new item properties window with toggle for legacy/new layout Co-authored-by: Marcos <66353315+marcosvf132@users.noreply.github.com> Co-authored-by: Pedro Henrique Alves Cruz <phac@cin.ufpe.br>
892 lines
19 KiB
C++
892 lines
19 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
// This file is part of Remere's Map Editor
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Remere's Map Editor is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Remere's Map Editor is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "main.h"
|
|
|
|
#include "gui.h"
|
|
|
|
#include "about_window.h"
|
|
|
|
class GamePanel : public wxPanel {
|
|
public:
|
|
GamePanel(wxWindow* parent, int width, int height);
|
|
virtual ~GamePanel();
|
|
|
|
void OnPaint(wxPaintEvent &);
|
|
void OnKeyDown(wxKeyEvent &);
|
|
void OnKeyUp(wxKeyEvent &);
|
|
void OnIdle(wxIdleEvent &);
|
|
|
|
void pause() {
|
|
paused_val = true;
|
|
}
|
|
void unpause() {
|
|
paused_val = false;
|
|
}
|
|
bool paused() const {
|
|
return paused_val || dead;
|
|
}
|
|
|
|
protected:
|
|
virtual void Render(wxDC &pdc) = 0;
|
|
virtual void GameLoop(int time) = 0;
|
|
virtual void OnKey(wxKeyEvent &event, bool down) = 0;
|
|
|
|
virtual int getFPS() const = 0;
|
|
|
|
protected:
|
|
wxStopWatch game_timer;
|
|
|
|
private:
|
|
bool paused_val;
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
|
|
protected:
|
|
bool dead;
|
|
};
|
|
|
|
const int TETRIS_MAPHEIGHT = 20;
|
|
const int TETRIS_MAPWIDTH = 10;
|
|
|
|
class TetrisPanel : public GamePanel {
|
|
public:
|
|
TetrisPanel(wxWindow* parent);
|
|
~TetrisPanel();
|
|
|
|
protected:
|
|
virtual void Render(wxDC &pdc);
|
|
virtual void GameLoop(int time);
|
|
virtual void OnKey(wxKeyEvent &event, bool down);
|
|
|
|
virtual int getFPS() const {
|
|
return lines / 10 + 3;
|
|
}
|
|
|
|
enum Color {
|
|
NO_COLOR,
|
|
RED,
|
|
BLUE,
|
|
GREEN,
|
|
STEEL,
|
|
YELLOW,
|
|
PURPLE,
|
|
WHITE,
|
|
};
|
|
|
|
enum BlockType {
|
|
FIRST_BLOCK,
|
|
BLOCK_TOWER = FIRST_BLOCK,
|
|
BLOCK_SQUARE,
|
|
BLOCK_TRIANGLE,
|
|
BLOCK_L,
|
|
BLOCK_J,
|
|
BLOCK_Z,
|
|
BLOCK_S,
|
|
LAST_BLOCK = BLOCK_S
|
|
};
|
|
|
|
struct Block {
|
|
Color structure[4][4];
|
|
int x, y;
|
|
} block;
|
|
|
|
const wxBrush &GetBrush(Color color) const;
|
|
bool BlockCollisionTest(int mx, int my) const;
|
|
void RemoveRow(int row);
|
|
void NewBlock();
|
|
void MoveBlock(int x, int y);
|
|
void RotateBlock();
|
|
void NewGame();
|
|
void EndGame();
|
|
void AddScore(int lines);
|
|
|
|
int score;
|
|
int lines;
|
|
Color map[TETRIS_MAPWIDTH][TETRIS_MAPHEIGHT];
|
|
};
|
|
|
|
const int SNAKE_MAPHEIGHT = 20;
|
|
const int SNAKE_MAPWIDTH = 20;
|
|
|
|
class SnakePanel : public GamePanel {
|
|
public:
|
|
SnakePanel(wxWindow* parent);
|
|
~SnakePanel();
|
|
|
|
protected:
|
|
virtual void Render(wxDC &pdc);
|
|
virtual void GameLoop(int time);
|
|
virtual void OnKey(wxKeyEvent &event, bool down);
|
|
|
|
virtual int getFPS() const {
|
|
return 7;
|
|
}
|
|
|
|
enum {
|
|
NORTH,
|
|
SOUTH,
|
|
WEST,
|
|
EAST,
|
|
};
|
|
|
|
void NewApple();
|
|
void Move(int dir);
|
|
void NewGame();
|
|
void EndGame();
|
|
void UpdateTitle();
|
|
|
|
// -1 is apple, 0 is nothing, >0 is snake (will decay in n rounds)
|
|
int length;
|
|
int last_dir;
|
|
int map[SNAKE_MAPWIDTH][SNAKE_MAPHEIGHT];
|
|
};
|
|
|
|
//=============================================================================
|
|
// About Window - Information window about the application
|
|
|
|
BEGIN_EVENT_TABLE(AboutWindow, wxDialog)
|
|
EVT_BUTTON(wxID_OK, AboutWindow::OnClickOK)
|
|
EVT_BUTTON(ABOUT_VIEW_LICENSE, AboutWindow::OnClickLicense)
|
|
EVT_MENU(ABOUT_RUN_TETRIS, AboutWindow::OnTetris)
|
|
EVT_MENU(ABOUT_RUN_SNAKE, AboutWindow::OnSnake)
|
|
EVT_MENU(wxID_CANCEL, AboutWindow::OnClickOK)
|
|
END_EVENT_TABLE()
|
|
|
|
AboutWindow::AboutWindow(wxWindow* parent) :
|
|
wxDialog(parent, wxID_ANY, "About", wxDefaultPosition, wxSize(300, 320), wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX),
|
|
game_panel(nullptr) {
|
|
|
|
std::string compiler;
|
|
#if defined(__clang__)
|
|
compiler = fmt::format("Clang++ {}.{}.{}", __clang_major__, __clang_minor__, __clang_patchlevel__);
|
|
#elif defined(_MSC_VER)
|
|
compiler = fmt::format("Microsoft Visual Studio {}", _MSC_VER);
|
|
#elif defined(__GNUC__)
|
|
compiler = fmt::format("G++ {}.{}.{}", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
|
#else
|
|
compiler = "unknown";
|
|
#endif
|
|
|
|
wxString about;
|
|
|
|
about << "This is an OpenTibia Map Editor created by Remere and edited by Canary.\n";
|
|
about << "Version " << __W_RME_VERSION__ << " for ";
|
|
about <<
|
|
#ifdef __WINDOWS__
|
|
"Windows";
|
|
#elif __LINUX__
|
|
"Linux";
|
|
#elif __APPLE__
|
|
"macOS";
|
|
#else
|
|
"Unsupported OS";
|
|
#endif
|
|
about << "\n\n";
|
|
|
|
about << "Using " << wxVERSION_STRING << " interface\n";
|
|
about << "OpenGL version " << wxString((char*)glGetString(GL_VERSION), wxConvUTF8) << "\n";
|
|
about << "\n";
|
|
about << "This program comes with ABSOLUTELY NO WARRANTY;\n";
|
|
about << "for details see the LICENSE file.\n";
|
|
about << "This is free software, and you are welcome to redistribute it\n";
|
|
about << "under certain conditions.\n";
|
|
about << "\n";
|
|
about << "Compiled on: " << __TDATE__ << " : " << __TTIME__ << "\n";
|
|
about << "Compiled with: " << compiler << "\n";
|
|
|
|
topsizer = newd wxBoxSizer(wxVERTICAL);
|
|
|
|
topsizer->Add(newd wxStaticText(this, wxID_ANY, about), 1, wxALL, 20);
|
|
|
|
wxSizer* choicesizer = newd wxBoxSizer(wxHORIZONTAL);
|
|
choicesizer->Add(newd wxButton(this, wxID_OK, "OK"), wxSizerFlags(1).Center());
|
|
topsizer->Add(choicesizer, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM, 20);
|
|
|
|
wxAcceleratorEntry entries[3];
|
|
entries[0].Set(wxACCEL_NORMAL, WXK_ESCAPE, wxID_CANCEL);
|
|
entries[1].Set(wxACCEL_NORMAL, 't', ABOUT_RUN_TETRIS);
|
|
entries[2].Set(wxACCEL_NORMAL, 's', ABOUT_RUN_SNAKE);
|
|
wxAcceleratorTable accel(3, entries);
|
|
SetAcceleratorTable(accel);
|
|
|
|
SetSizerAndFit(topsizer);
|
|
Centre(wxBOTH);
|
|
}
|
|
|
|
AboutWindow::~AboutWindow() {
|
|
////
|
|
}
|
|
|
|
void AboutWindow::OnClickOK(wxCommandEvent &WXUNUSED(event)) {
|
|
EndModal(0);
|
|
}
|
|
|
|
void AboutWindow::OnClickLicense(wxCommandEvent &WXUNUSED(event)) {
|
|
FileName path;
|
|
try {
|
|
path = wxStandardPaths::Get().GetExecutablePath();
|
|
} catch (std::bad_cast &) {
|
|
return;
|
|
}
|
|
path.SetFullName("COPYING.txt");
|
|
std::ifstream gpl(path.GetFullPath().mb_str());
|
|
|
|
std::string gpl_str;
|
|
char ch;
|
|
while (gpl.get(ch)) {
|
|
gpl_str += ch;
|
|
}
|
|
|
|
g_gui.ShowTextBox(this, "License", wxstr(gpl_str.size() ? gpl_str : "The COPYING.txt file is not available."));
|
|
}
|
|
|
|
void AboutWindow::OnTetris(wxCommandEvent &) {
|
|
if (!game_panel) {
|
|
DestroyChildren();
|
|
game_panel = newd TetrisPanel(this);
|
|
topsizer->Add(game_panel, 1, wxALIGN_CENTER | wxALL, 7);
|
|
Fit();
|
|
game_panel->SetFocus();
|
|
SetWindowStyleFlag(wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX);
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
void AboutWindow::OnSnake(wxCommandEvent &) {
|
|
if (!game_panel) {
|
|
DestroyChildren();
|
|
game_panel = newd SnakePanel(this);
|
|
topsizer->Add(game_panel, 1, wxALIGN_CENTER | wxALL, 7);
|
|
Fit();
|
|
game_panel->SetFocus();
|
|
SetWindowStyleFlag(wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX);
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// GamePanel - Abstract class for games
|
|
|
|
BEGIN_EVENT_TABLE(GamePanel, wxPanel)
|
|
EVT_KEY_DOWN(GamePanel::OnKeyDown)
|
|
EVT_KEY_UP(GamePanel::OnKeyUp)
|
|
EVT_PAINT(GamePanel::OnPaint)
|
|
EVT_IDLE(GamePanel::OnIdle)
|
|
END_EVENT_TABLE()
|
|
|
|
GamePanel::GamePanel(wxWindow* parent, int width, int height) :
|
|
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(width, height), wxWANTS_CHARS),
|
|
paused_val(false),
|
|
dead(false) {
|
|
// Receive idle events
|
|
SetExtraStyle(wxWS_EX_PROCESS_IDLE);
|
|
// Complete redraw
|
|
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
|
|
}
|
|
|
|
GamePanel::~GamePanel() {
|
|
////
|
|
}
|
|
|
|
void GamePanel::OnPaint(wxPaintEvent &) {
|
|
wxBufferedPaintDC pdc(this);
|
|
Render(pdc);
|
|
}
|
|
|
|
void GamePanel::OnKeyDown(wxKeyEvent &event) {
|
|
switch (event.GetKeyCode()) {
|
|
case WXK_ESCAPE: {
|
|
if (dead) {
|
|
return;
|
|
}
|
|
wxDialog* dlg = (wxDialog*)GetParent();
|
|
dlg->EndModal(0);
|
|
break;
|
|
}
|
|
default: {
|
|
OnKey(event, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GamePanel::OnKeyUp(wxKeyEvent &event) {
|
|
OnKey(event, false);
|
|
}
|
|
|
|
void GamePanel::OnIdle(wxIdleEvent &event) {
|
|
int time = game_timer.Time();
|
|
if (time > 1000 / getFPS()) {
|
|
game_timer.Start();
|
|
if (!paused()) {
|
|
GameLoop(time);
|
|
}
|
|
}
|
|
if (!paused()) {
|
|
event.RequestMore(true);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// TetrisPanel - A window with a Tetris game!
|
|
|
|
TetrisPanel::TetrisPanel(wxWindow* parent) :
|
|
GamePanel(parent, 16 * TETRIS_MAPWIDTH, 16 * TETRIS_MAPHEIGHT) {
|
|
NewGame();
|
|
}
|
|
|
|
TetrisPanel::~TetrisPanel() {
|
|
////
|
|
}
|
|
|
|
const wxBrush &TetrisPanel::GetBrush(Color color) const {
|
|
static std::unique_ptr<wxBrush> yellow_brush;
|
|
static std::unique_ptr<wxBrush> purple_brush;
|
|
|
|
if (yellow_brush.get() == nullptr) {
|
|
yellow_brush.reset(newd wxBrush(wxColor(255, 255, 0)));
|
|
}
|
|
if (purple_brush.get() == nullptr) {
|
|
purple_brush.reset(newd wxBrush(wxColor(128, 0, 255)));
|
|
}
|
|
|
|
const wxBrush* brush = nullptr;
|
|
switch (color) {
|
|
case RED:
|
|
brush = wxRED_BRUSH;
|
|
break;
|
|
case BLUE:
|
|
brush = wxCYAN_BRUSH;
|
|
break;
|
|
case GREEN:
|
|
brush = wxGREEN_BRUSH;
|
|
break;
|
|
case PURPLE:
|
|
brush = purple_brush.get();
|
|
break;
|
|
case YELLOW:
|
|
brush = yellow_brush.get();
|
|
break;
|
|
case WHITE:
|
|
brush = wxWHITE_BRUSH;
|
|
break;
|
|
case STEEL:
|
|
brush = wxGREY_BRUSH;
|
|
break;
|
|
default:
|
|
brush = wxBLACK_BRUSH;
|
|
break;
|
|
}
|
|
return *brush;
|
|
}
|
|
|
|
void TetrisPanel::Render(wxDC &pdc) {
|
|
pdc.SetBackground(*wxBLACK_BRUSH);
|
|
pdc.Clear();
|
|
|
|
for (int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < TETRIS_MAPWIDTH; ++x) {
|
|
pdc.SetBrush(GetBrush(map[x][y]));
|
|
pdc.DrawRectangle(x * 16, y * 16, 16, 16);
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
if (block.structure[x][y] != NO_COLOR) {
|
|
pdc.SetBrush(GetBrush(block.structure[x][y]));
|
|
pdc.DrawRectangle((block.x + x) * 16, (block.y + y) * 16, 16, 16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TetrisPanel::OnKey(wxKeyEvent &event, bool down) {
|
|
if (!down || dead) {
|
|
return;
|
|
}
|
|
|
|
switch (event.GetKeyCode()) {
|
|
case WXK_SPACE: {
|
|
if (paused()) {
|
|
unpause();
|
|
} else {
|
|
pause();
|
|
}
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_UP:
|
|
case WXK_UP: {
|
|
if (dead) {
|
|
return;
|
|
}
|
|
unpause();
|
|
RotateBlock();
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_DOWN:
|
|
case WXK_DOWN: {
|
|
if (dead) {
|
|
return;
|
|
}
|
|
unpause();
|
|
MoveBlock(0, 1);
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_LEFT:
|
|
case WXK_LEFT: {
|
|
if (dead) {
|
|
return;
|
|
}
|
|
unpause();
|
|
MoveBlock(-1, 0);
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_RIGHT:
|
|
case WXK_RIGHT: {
|
|
if (dead) {
|
|
return;
|
|
}
|
|
unpause();
|
|
MoveBlock(1, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TetrisPanel::NewGame() {
|
|
NewBlock();
|
|
score = 0;
|
|
lines = 0;
|
|
game_timer.Start();
|
|
unpause();
|
|
dead = false;
|
|
|
|
// Clear map
|
|
for (int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < TETRIS_MAPWIDTH; ++x) {
|
|
map[x][y] = NO_COLOR;
|
|
}
|
|
}
|
|
AddScore(0); // Update title
|
|
}
|
|
|
|
void TetrisPanel::AddScore(int lines_added) {
|
|
lines += lines_added;
|
|
score += lines_added * lines_added * 10;
|
|
wxString title = "Canary Map Editor Tetris : ";
|
|
title << score << " points ";
|
|
title << lines << " lines";
|
|
((wxTopLevelWindow*)GetParent())->SetTitle(title);
|
|
}
|
|
|
|
void TetrisPanel::GameLoop(int time) {
|
|
MoveBlock(0, 1);
|
|
}
|
|
|
|
bool TetrisPanel::BlockCollisionTest(int mx, int my) const {
|
|
int nx = block.x + mx;
|
|
int ny = block.y + my;
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
if (block.structure[x][y] != NO_COLOR) {
|
|
if (nx + x < 0 || nx + x > TETRIS_MAPWIDTH - 1 || ny + y < 0 || ny + y > TETRIS_MAPHEIGHT - 1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < TETRIS_MAPWIDTH; ++x) {
|
|
if (x >= nx && x < nx + 4 && y >= ny && y < ny + 4) {
|
|
if (map[x][y] != NO_COLOR && block.structure[x - nx][y - ny] != NO_COLOR) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TetrisPanel::RemoveRow(int row) {
|
|
for (int x = 0; x < TETRIS_MAPWIDTH; ++x) {
|
|
for (int y = row; y > 0; --y) { // Move all above one step down
|
|
map[x][y] = map[x][y - 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
void TetrisPanel::NewBlock() {
|
|
block.x = TETRIS_MAPWIDTH / 2;
|
|
block.y = -1;
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
block.structure[x][y] = NO_COLOR;
|
|
}
|
|
}
|
|
|
|
switch (random(FIRST_BLOCK, LAST_BLOCK)) {
|
|
case BLOCK_TOWER: {
|
|
block.structure[1][0] = RED;
|
|
block.structure[1][1] = RED;
|
|
block.structure[1][2] = RED;
|
|
block.structure[1][3] = RED;
|
|
block.y = 0;
|
|
break;
|
|
}
|
|
default:
|
|
case BLOCK_SQUARE: {
|
|
block.structure[1][1] = BLUE;
|
|
block.structure[1][2] = BLUE;
|
|
block.structure[2][1] = BLUE;
|
|
block.structure[2][2] = BLUE;
|
|
break;
|
|
}
|
|
case BLOCK_TRIANGLE: {
|
|
block.structure[1][1] = STEEL;
|
|
block.structure[0][2] = STEEL;
|
|
block.structure[1][2] = STEEL;
|
|
block.structure[2][2] = STEEL;
|
|
break;
|
|
}
|
|
case BLOCK_Z: {
|
|
block.structure[0][1] = YELLOW;
|
|
block.structure[1][1] = YELLOW;
|
|
block.structure[1][2] = YELLOW;
|
|
block.structure[2][2] = YELLOW;
|
|
break;
|
|
}
|
|
case BLOCK_S: {
|
|
block.structure[2][1] = GREEN;
|
|
block.structure[1][1] = GREEN;
|
|
block.structure[1][2] = GREEN;
|
|
block.structure[0][2] = GREEN;
|
|
break;
|
|
}
|
|
case BLOCK_J: {
|
|
block.structure[1][1] = WHITE;
|
|
block.structure[2][1] = WHITE;
|
|
block.structure[2][2] = WHITE;
|
|
block.structure[2][3] = WHITE;
|
|
break;
|
|
}
|
|
case BLOCK_L: {
|
|
block.structure[2][1] = PURPLE;
|
|
block.structure[1][1] = PURPLE;
|
|
block.structure[1][2] = PURPLE;
|
|
block.structure[1][3] = PURPLE;
|
|
// break; missing add it?
|
|
}
|
|
}
|
|
}
|
|
|
|
void TetrisPanel::MoveBlock(int x, int y) {
|
|
if (BlockCollisionTest(x, y)) {
|
|
if (y == 1) { // moving down...
|
|
if (block.y < 1) { // Out of bounds!
|
|
dead = true;
|
|
g_gui.PopupDialog("Game Over", "You reached a score of " + i2ws(score) + "!", wxOK);
|
|
NewGame();
|
|
SetFocus();
|
|
} else {
|
|
// Freeze the old block onto the map
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
if (block.structure[x][y] != NO_COLOR) {
|
|
map[block.x + x][block.y + y] = block.structure[x][y];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Any cleared rows?
|
|
int cleared = 0;
|
|
for (int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
|
|
bool full = true;
|
|
for (int x = 0; x < TETRIS_MAPWIDTH; ++x) {
|
|
if (map[x][y] == NO_COLOR) {
|
|
full = false;
|
|
}
|
|
}
|
|
if (full) {
|
|
RemoveRow(y);
|
|
++cleared;
|
|
}
|
|
}
|
|
AddScore(cleared);
|
|
NewBlock();
|
|
}
|
|
} // If we're not moving down, we're not moving the block either
|
|
} else {
|
|
// No collision so move the block!
|
|
block.x += x;
|
|
block.y += y;
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void TetrisPanel::RotateBlock() {
|
|
Block temp;
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
temp.structure[3 - x][y] = block.structure[y][x];
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
if (temp.structure[x][y] != NO_COLOR) {
|
|
if (block.x + x < 0 || block.x + x > TETRIS_MAPWIDTH - 1 || block.y + y < 0 || block.y + y > TETRIS_MAPHEIGHT - 1) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < TETRIS_MAPWIDTH; ++y) {
|
|
for (int x = 0; x < TETRIS_MAPHEIGHT; ++x) {
|
|
if (x >= block.x && x < block.x + 4 && y >= block.y && y < block.y + 4) {
|
|
if (map[x][y] != NO_COLOR && temp.structure[x - block.x][y - block.y] != NO_COLOR) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
block.structure[x][y] = temp.structure[x][y];
|
|
}
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
//=============================================================================
|
|
// SnakePanel - A window with a Snake game!
|
|
|
|
SnakePanel::SnakePanel(wxWindow* parent) :
|
|
GamePanel(parent, 16 * SNAKE_MAPWIDTH, 16 * SNAKE_MAPHEIGHT) {
|
|
NewGame();
|
|
}
|
|
|
|
SnakePanel::~SnakePanel() {
|
|
////
|
|
}
|
|
|
|
void SnakePanel::Render(wxDC &pdc) {
|
|
pdc.SetBackground(*wxBLACK_BRUSH);
|
|
pdc.Clear();
|
|
|
|
wxBrush snakebrush(wxColor(0, 0, 255));
|
|
wxBrush applebrush(wxColor(255, 0, 0));
|
|
|
|
double lblue = 1.0;
|
|
double lred = 0.5;
|
|
double lgreen = 0.0;
|
|
|
|
for (int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < SNAKE_MAPWIDTH; ++x) {
|
|
if (map[x][y] == -1) { // Apple
|
|
pdc.SetBrush(applebrush);
|
|
pdc.DrawRectangle(x * 16, y * 16, 16, 16);
|
|
} else if (map[x][y] > 0) { // Snake
|
|
double snook = double(map[x][y]) / length;
|
|
snakebrush.SetColour(wxColor(
|
|
int(255.0 * (1.0 - abs(lred - snook))),
|
|
int(255.0 * (1.0 - abs(lgreen - snook))),
|
|
int(255.0 * (1.0 - abs(lblue - snook)))
|
|
)
|
|
);
|
|
pdc.SetBrush(snakebrush);
|
|
pdc.DrawRectangle(x * 16, y * 16, 16, 16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SnakePanel::OnKey(wxKeyEvent &event, bool down) {
|
|
if (!down) {
|
|
return;
|
|
}
|
|
|
|
int keyCode = event.GetKeyCode();
|
|
if (keyCode == WXK_SPACE) {
|
|
if (paused()) {
|
|
unpause();
|
|
} else {
|
|
pause();
|
|
}
|
|
} else if (!dead) {
|
|
switch (keyCode) {
|
|
case WXK_NUMPAD_UP:
|
|
case WXK_UP: {
|
|
unpause();
|
|
Move(NORTH);
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_DOWN:
|
|
case WXK_DOWN: {
|
|
unpause();
|
|
Move(SOUTH);
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_LEFT:
|
|
case WXK_LEFT: {
|
|
unpause();
|
|
Move(WEST);
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_RIGHT:
|
|
case WXK_RIGHT: {
|
|
unpause();
|
|
Move(EAST);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SnakePanel::NewGame() {
|
|
length = 3;
|
|
game_timer.Start();
|
|
last_dir = NORTH;
|
|
unpause();
|
|
dead = false;
|
|
|
|
// Clear map
|
|
for (int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < SNAKE_MAPWIDTH; ++x) {
|
|
map[x][y] = 0;
|
|
}
|
|
}
|
|
map[SNAKE_MAPWIDTH / 2][SNAKE_MAPHEIGHT / 2] = length;
|
|
NewApple();
|
|
UpdateTitle(); // Update title
|
|
}
|
|
|
|
void SnakePanel::UpdateTitle() {
|
|
wxString title = "Canary Map Editor Snake : ";
|
|
title << length << " segments";
|
|
((wxTopLevelWindow*)GetParent())->SetTitle(title);
|
|
}
|
|
|
|
void SnakePanel::GameLoop(int time) {
|
|
Move(last_dir);
|
|
}
|
|
|
|
void SnakePanel::NewApple() {
|
|
bool possible = false;
|
|
for (int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < SNAKE_MAPWIDTH; ++x) {
|
|
if (map[x][y] == 0) {
|
|
possible = true;
|
|
}
|
|
if (possible) {
|
|
break;
|
|
}
|
|
}
|
|
if (possible) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (possible) {
|
|
while (true) {
|
|
int x = random(0, SNAKE_MAPWIDTH - 1);
|
|
int y = random(0, SNAKE_MAPHEIGHT - 1);
|
|
if (map[x][y] == 0) {
|
|
map[x][y] = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SnakePanel::Move(int dir) {
|
|
if ((last_dir == NORTH && dir == SOUTH) || (last_dir == WEST && dir == EAST) || (last_dir == EAST && dir == WEST) || (last_dir == SOUTH && dir == NORTH)) {
|
|
return;
|
|
}
|
|
|
|
int nx = 0, ny = 0;
|
|
int head_x = 0, head_y = 0;
|
|
for (int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < SNAKE_MAPWIDTH; ++x) {
|
|
if (map[x][y] == length) {
|
|
head_x = x;
|
|
head_y = y;
|
|
}
|
|
}
|
|
}
|
|
switch (dir) {
|
|
case NORTH: {
|
|
nx = head_x;
|
|
ny = head_y - 1;
|
|
break;
|
|
}
|
|
case SOUTH: {
|
|
nx = head_x;
|
|
ny = head_y + 1;
|
|
break;
|
|
}
|
|
case WEST: {
|
|
nx = head_x - 1;
|
|
ny = head_y;
|
|
break;
|
|
}
|
|
case EAST: {
|
|
nx = head_x + 1;
|
|
ny = head_y;
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (map[nx][ny] > 0 || nx < 0 || ny < 0 || nx >= SNAKE_MAPWIDTH || ny >= SNAKE_MAPHEIGHT) {
|
|
// Crash
|
|
dead = true;
|
|
g_gui.PopupDialog("Game Over", "You reached a length of " + i2ws(length) + "!", wxOK);
|
|
NewGame();
|
|
SetFocus();
|
|
} else {
|
|
// Walk!
|
|
if (map[nx][ny] == -1) {
|
|
// Took apple!
|
|
length += 1;
|
|
UpdateTitle();
|
|
NewApple();
|
|
} else {
|
|
for (int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
|
|
for (int x = 0; x < SNAKE_MAPWIDTH; ++x) {
|
|
if (map[x][y] > 0) {
|
|
map[x][y] -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
map[nx][ny] = length;
|
|
}
|
|
last_dir = dir;
|
|
|
|
Refresh();
|
|
}
|