Προς το περιεχόμενο

Παιχνίδι 2048 σε C


johnny.tifosi

Προτεινόμενες αναρτήσεις

Ένας λιτός κλώνος του 2048 υλοποιημένος σε 700 γραμμές C/C++ & Windows API.

 

 

// Simple 2048 Clone,  xdir
//  μετάφραση με C++ Builder XE ή Visual Studio C++
// 
// ** VC2010: Όρισε το "Enable Incremental Linking" σε "NO"
//	    : Όρισε το "Character Set" σε "Not Set" 

#include <windows.h>
#include <stdlib.h>
#include <time.h>

#include <vector>
#include <utility>
#include <map>

int NumArray[4][4] = { 0 },
	Score = 0,
	GenCellFlashCnt = 5;

bool GenCellPending = false,
	 GenCellFlash = true,
	 FlashTimerOn = false,
	 GameRunning  = false;

HINSTANCE gInstance;

HWND hWndGame = NULL,
	 hWndGameWin = NULL;

std::pair<int, int> LatestGenCell;
std::vector<std::pair<int, int> > ResultingCells;
std::map<int, std::pair<COLORREF, COLORREF> > NumberToColor;

#define	WIN_TITLE	"XD2048"

#define	GRID_WIDTH	325
#define	GRID_HEIGHT 312

#define	GRID_TIMER    1
#define	GRID_TIMER_MS 150

#define	MI_RESTART	WM_USER + 10

#define	WebWhite 		 RGB(0xFF, 0xFF, 0xFF)
#define WebLinen  		 RGB(0xFA, 0xF0, 0xE6)
#define WebAntiqueWhite  RGB(0xFA, 0xEB, 0xD7)
#define	WebPeachPuff	 RGB(0xFF, 0xDA, 0xB9)
#define	WebOrange		 RGB(0xFF, 0xA5, 0x00)
#define WebCoral		 RGB(0xFF, 0x7F, 0x50)
#define	WebTomato		 RGB(0xFF, 0x63, 0x47)
#define WebPaleGoldenrod RGB(0xEE, 0xE8, 0xAA)
#define WebKhaki		 RGB(0xF0, 0xE6, 0x8C)
#define WebGold			 RGB(0xFF, 0xD7, 0x00)
#define	WebLightYellow	 RGB(0xFF, 0xFF, 0xE0)
#define WebYellow		 RGB(0xFF, 0xFF, 0x00)
#define WebMaroon		 RGB(0x80, 0x00, 0x00)
#define	WebIvory		 RGB(0xFF, 0xFF, 0xF0)

void BeginNewGame(void);
void StartTimer(const int& ARow, const int& ACol);
void InvalidateScore(const HWND& hWndGame);
void EndTimer(void);
bool GameWon(void);

bool GenerateNewTile(const int& Number = -1);
bool HasEmptyCell(void);
bool EmptyCell(const int&, const int&);
bool CellInBound(const int& ARowTo, const int& AColTo);
bool MatchingCell(const int& ARowFrom, const int& AColFrom,
				  const int& ARowTo, const int& AColTo);
void MoveGrid(const WORD& Key);
void ScrollCellTo(int &ARow, int &ACol, const WORD& Dir);

LRESULT CALLBACK GameProc(HWND, UINT, WPARAM, LPARAM);

#ifdef __BORLANDC__
	#pragma argsused
#endif
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	gInstance = hInstance;

	WNDCLASS wc = { 0 };

	wc.hInstance = hInstance;
	wc.lpszClassName = WIN_TITLE;
	wc.lpfnWndProc = GameProc;
	wc.hIcon  = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor= LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground= reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));

	if(!RegisterClass(&wc))
		MessageBox(NULL, "RegisterClass failed", NULL, MB_ICONSTOP);
	else 
	{
		// Δημιουργία μενού
		const HMENU MainMenu = CreateMenu();

		AppendMenu(MainMenu, MF_STRING, MI_RESTART, "&Restart");

		// Δημιουργία παραθύρου
		hWndGame = CreateWindow(
					wc.lpszClassName,
					wc.lpszClassName,
					(WS_OVERLAPPEDWINDOW ^ WS_MAXIMIZEBOX) ^ WS_THICKFRAME,
					CW_USEDEFAULT, CW_USEDEFAULT,
					GRID_WIDTH,
					GRID_HEIGHT + GetSystemMetrics(SM_CYMENU),
					NULL,
					MainMenu,
					hInstance,
					NULL);

		if(!hWndGame) 
			MessageBox(NULL, "CreateWindow failed", NULL, MB_ICONSTOP);
		else 
		{
			// Παρουσίαση παραθύρου
			ShowWindow(hWndGame, nCmdShow);
			UpdateWindow(hWndGame);

			// Διαχείριση μηνυμάτων προγράμματος
			MSG msg;
			int nRet;

			while((nRet = GetMessage(&msg, NULL, 0, 0))) 
			{
				if(nRet == -1) 
				{
					MessageBox(NULL, "GetMessage failed", NULL, MB_ICONSTOP);
					break;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
	}

	return 0;
}
//---------------------------------------------------------------------------
// Game event driven loop
//---------------------------------------------------------------------------
LRESULT CALLBACK GameProc(HWND hWndGame, UINT Msg, WPARAM wParam, LPARAM lParam) 
{
	static HFONT fonGame = NULL;

	static HBITMAP bmpGrid = NULL;
	static HDC dcGrid = NULL;

	switch(Msg) 
	{
		case WM_CREATE:
		{
			// Προετοιμασία κατασκευής DC πλέγματος 4x4
			const HDC dcGame = GetDC(hWndGame);

			if(!(dcGrid = CreateCompatibleDC(dcGame))) {
				MessageBox(NULL, "CreateCompatibleDC failed", NULL, MB_ICONSTOP);
				return -1;
            }
			if(!(bmpGrid = CreateCompatibleBitmap(dcGame, 325, 312))) {
				MessageBox(NULL, "CreateCompatibleBitmap failed", NULL, MB_ICONSTOP);
				return -1;
			}
			SelectObject(dcGrid, bmpGrid);

			// Κατασκευή φόντου προγράμματος
			fonGame = CreateFont(
					   -18, 0, 0, 0,
					   FW_DONTCARE,
					   FALSE, FALSE, FALSE,
					   ANSI_CHARSET,
					   OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
					   DEFAULT_QUALITY,
					   FF_DONTCARE,
					   "Comic Sans MS");

			if(!fonGame) 
			{
				MessageBox(NULL, "CreateFont failed", NULL, MB_ICONSTOP);
				return -1;
			}
			SelectObject(dcGrid, fonGame);
			SetBkMode(dcGrid, TRANSPARENT);

			ReleaseDC(hWndGame, dcGame);

			// Κατασκευή Static Control νίκης
			hWndGameWin = CreateWindow(
				"STATIC", "You Win!",
				WS_CHILD | SS_CENTER,
				100, 125, 105, 25,
				hWndGame, NULL, gInstance, NULL);

			SendMessage(hWndGameWin, WM_SETFONT, WPARAM(fonGame), TRUE);

			// Ορισμός του ευρετηρίου χρωμάτων του προγράμματος
			NumberToColor[-1]  = std::make_pair(RGB(204, 192, 179), WebWhite);
			NumberToColor[2]   = std::make_pair(WebLinen        , WebMaroon);
			NumberToColor[4]   = std::make_pair(WebAntiqueWhite , WebMaroon);
			NumberToColor[8]   = std::make_pair(WebPeachPuff    , WebIvory );
			NumberToColor[16]  = std::make_pair(WebOrange	    , WebIvory);
			NumberToColor[32]  = std::make_pair(WebCoral	    , WebIvory);
			NumberToColor[64]  = std::make_pair(WebTomato	    , WebIvory);
			NumberToColor[128] = std::make_pair(WebPaleGoldenrod,WebMaroon);
			NumberToColor[256] = std::make_pair(WebKhaki		, WebMaroon);
			NumberToColor[512] = std::make_pair(WebGold		  , WebMaroon);
			NumberToColor[1024]= std::make_pair(WebLightYellow, WebMaroon);
			NumberToColor[2048]= std::make_pair(WebYellow 	  , WebMaroon);

			// Προετοιμασία RNG 
			#ifndef __BORLANDC__
			srand(static_cast<unsigned>(time(NULL)));				
			#else
			srand(time(NULL));
			#endif

			BeginNewGame();
		}
		return 0;

		case WM_PAINT:
		{
			RECT rcUpdate;
			PAINTSTRUCT ps;

			// Πρέπει να ενημερώσω το παράθυρο του προγράμματος;
			if(!GetUpdateRect(hWndGame, &rcUpdate, FALSE))
				break;
			if(!rcUpdate.left && !rcUpdate.right && !rcUpdate.top && !rcUpdate.bottom)
				break;

			// Ναι. Σχεδίαση πλέγματος 4x4
			if(!BeginPaint(hWndGame, &ps))
				break;

			// Σχεδίαση πλακιδίων 
			for(int iRow = 0; iRow < 4; ++iRow) {
				for(int iCol = 0; iCol < 4; ++iCol) {
					RECT rcTile = {
						iCol * 80,
						iRow * 70,
						iCol * 80 + 80,
						iRow * 70 + 70
					 };

					const int *ptrNumber = &NumArray[iRow][iCol];

					static LOGBRUSH lgBr = { BS_SOLID };
					lgBr.lbColor = NumberToColor[*ptrNumber].first;

					// Πλέγμα
					Rectangle(dcGrid,
							  rcTile.left, rcTile.top,
							  rcTile.right, rcTile.bottom);

					// Πλακίδια
					rcTile.left += 4;
					rcTile.right-= 4;
					rcTile.top += 4;
					rcTile.bottom -= 4;

					static HBRUSH br;
					br = CreateBrushIndirect(&lgBr);
					FillRect(dcGrid, &rcTile, br);
					DeleteObject(br);

					// Αγνόηση κενών πλακιδίων
					if(*ptrNumber == -1)
						continue;
					// Να αναβοσβήσω το πιο πρόσφατα δημιουργημένο πλακίδιο;
					if(FlashTimerOn)
						if(LatestGenCell == std::make_pair(iRow, iCol))
							if(!GenCellFlash)
								continue;

					// Εκτύπωση αριθμού πλακιδίου
					static char strTile[32];
					wsprintf(strTile, "%d", *ptrNumber);

					DrawText(dcGrid,
							 strTile, -1,
							 &rcTile,
							 DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_END_ELLIPSIS);
				}
			}

			// Σχεδίαση εικόνας προγράμματος στο παράθυρο (double buffer)
			BitBlt(ps.hdc, 0, 0, GRID_WIDTH, GRID_HEIGHT, dcGrid, 0, 0, SRCCOPY);

			// Τέλος της ενημέρωσης παράθυρου!
			EndPaint(hWndGame, &ps);
		}
		return 0;

		// Διαχείριση των πλήκτρων του προγράμματος
		case WM_KEYDOWN:
		{
			switch(wParam) 
			{
				case VK_LEFT: case VK_RIGHT:
				case VK_UP:   case VK_DOWN:
					if(GameRunning) 
					{
                    	GenCellPending = false;

						EndTimer();

						MoveGrid(wParam);
						InvalidateRect(hWndGame, NULL, FALSE);
						InvalidateScore(hWndGame);

						if(GameWon()) 
						{		      // Νικηφόρο τέλος παιχνιδιού;
							ShowWindow(hWndGameWin, SW_SHOW);
							GameRunning = false;
						} 
						else		  // Προσθήκη νέου πλακιδίου στο πλέγμα;
							if(GenCellPending)
								GenerateNewTile();
					}
				return 0;

				case VK_F5:
					BeginNewGame();
				return 0;
			}
		}
		break;

		// Διαχείριση χρονομέτρων προγράμματος
		case WM_TIMER:
			switch(wParam) 
			{
				case 1:
					if(--GenCellFlashCnt < 0) 
						EndTimer();
					else
						GenCellFlash = !GenCellFlash;

					InvalidateRect(hWndGame, NULL, FALSE);
				return 0;
			}
			break;

		// Διαχείριση μενού προγράμματος
		case WM_COMMAND:
			switch(wParam) 
			{
				case MI_RESTART:
					BeginNewGame();
				return 0;
			}
		return 0;

		// Διαχείριση εξόδου από το πρόγραμμα
		case WM_DESTROY:
			PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWndGame, Msg, wParam, lParam);
}

// Συναρτήσεις διαχείρισης & λογικής παιχνιδιού //

//---------------------------------------------------------------------------
// Έναρξη νέου παιχνιδιού
//---------------------------------------------------------------------------
void BeginNewGame(void)
{
	GameRunning = false;

	Score = GenCellPending = 0;
	InvalidateScore(hWndGame);

	EndTimer();

	ResultingCells.clear();

	memset(&NumArray, -1, sizeof(NumArray));

	const int Number = rand() % 101 <= 50 ? 2: 4;

	for(int TileCount = 2; TileCount > 0; TileCount--)
		GenerateNewTile(Number);

	InvalidateRect(hWndGame, NULL, FALSE);

	ShowWindow(hWndGameWin, SW_HIDE);

	GameRunning = true;
}
//---------------------------------------------------------------------------
// Τοποθέτηση τυχαίων πλακιδίων (με αριθμό το 2 ή το 4) στο πλέγμα. 
// Όταν Number != -1 τότε τοποθέτηση πλακιδίου με αριθμό το Number σε τυχαία 
// θέση του πλέγματος (σε αυτή την περίπτωση το πλακίδιο δεν θα αναβοσβήσει).
//
// true όταν το πλακίδιο τοποθετηθεί επιτυχώς διαφορετικά, όταν δεν υπάρχει 
// ελεύθερος χώρος στο πλέγμα, false.
//---------------------------------------------------------------------------
bool GenerateNewTile(const int& Number)
{
	short ACol,
		  ARow;

	if(HasEmptyCell()) {

		do {
			ACol = rand() % 4;
			ARow = rand() % 4;
		} while(NumArray[ARow][ACol] != -1);

		NumArray[ARow][ACol] = Number == -1 ? rand() % 101 <= 50 ? 2: 4:
							   Number;

		if(Number == -1)
			StartTimer(ARow, ACol);

		return true;
	}

	return false;
}
//---------------------------------------------------------------------------
// true όταν το πλέγμα μπορεί να δεχθεί ένα ή περισσότερα πλακίδια, 
// διαφορετικά false
//---------------------------------------------------------------------------
bool HasEmptyCell(void)
{
	for(short ARow = 0; ARow < 4; ARow++)
		for(short ACol = 0; ACol < 4; ACol++)
			if(EmptyCell(ARow, ACol))
				return true;

	return false;
}
//---------------------------------------------------------------------------
// true όταν η θέση ARow;ACol (Y, X) του πλέγματος δεν περιέχει πλακίδιο και 
// η θέση είναι εντός των διαστάσεων του πλέγματος, διαφορετικά false
//---------------------------------------------------------------------------
bool EmptyCell(const int& ARow, const int& ACol)
{
	if(ARow < 0 || ARow > 3 || ACol < 0 || ACol > 3)
		return false;

	return NumArray[ARow][ACol] == -1;
}
//---------------------------------------------------------------------------
// true όταν βρεθεί πλακίδιο με αριθμό 2048 διαφορετικά false
//---------------------------------------------------------------------------
bool GameWon(void)
{
	for(short ARow = 0; ARow < 4; ARow++)
		for(short ACol = 0; ACol < 4; ACol++)
			if(NumArray[ARow][ACol] == 2048)
				return true;

	return false;
}
//---------------------------------------------------------------------------
// true όταν τα πλακίδια στις θέσεις του πλέγματος ARowFrom, AColFrom και 
// ARowTo, AColTo περιέχουν ισότιμους αριθμούς, διαφορετικά false
//---------------------------------------------------------------------------
bool MatchingCell(const int& ARowFrom, const int& AColFrom,
				  const int& ARowTo, const int& AColTo)
{
	return CellInBound(ARowFrom, AColFrom) && CellInBound(ARowTo, AColTo) ?
		NumArray[ARowFrom][AColFrom] == NumArray[ARowTo][AColTo]: false;
}
//---------------------------------------------------------------------------
// Μετακίνηση του πλακιδίου της θέσης του πλέγματος ARowFrom, AColFrom στην 
// θέση ARowTo, AColTo του πλέγματος.
//
// true για επιτυχή μετακίνηση ή όταν οι θέσεις του πλέγματος είναι εκτός 
// ορίων τότε false
//---------------------------------------------------------------------------
bool MoveCell(const int& ARowFrom, const int& AColFrom,
							 const int& ARowTo, const int& AColTo)
{
	if(CellInBound(ARowFrom, AColFrom) && CellInBound(ARowTo, AColTo)) 
	{
		NumArray[ARowTo][AColTo] = NumArray[ARowFrom][AColFrom];
		NumArray[ARowFrom][AColFrom] = -1;
		return GenCellPending = GenCellFlash = true;
	}
	return false;
}
//---------------------------------------------------------------------------
// true όταν η θέση του πλακιδίου ARowTo, AColTo είναι εντός των ορίων του 
// πλέγματος διαφορετικά false
//---------------------------------------------------------------------------
bool CellInBound(const int& ARowTo, const int& AColTo)
{
	return ARowTo >= 0 && ARowTo < 4 && AColTo >= 0 && AColTo < 4;
}
//---------------------------------------------------------------------------
// Άθροιση των πλακιδίων των θέσεων ARowFrom;AColFrom και ARowTo;AColTo του 
// πλέγματος.
//
// true όταν η άθροιση είναι επιτυχής διαφορετικά αν κάποιο από τα πλακίδια 
// στις θέσεις αυτές προέκυψε από προηγούμενη άθροιση στον τρέχοντα γύρο του 
// παιχνιδιού false
//---------------------------------------------------------------------------
bool SumCells(const int& ARowFrom, const int& AColFrom,
			  const int& ARowTo, const int& AColTo)
{
	const std::pair<int, int> CellToSum = std::make_pair(ARowTo, AColTo);

	// Το ResultingCell περιέχει όλα τα πλακίδια που προέκυψαν από άθροιση ισότιμων 
	// πλακιδίων στον τρέχοντα γύρο του παιχνιδιού
	std::vector<std::pair<int, int> >::const_iterator iCell = ResultingCells.begin();

	for(;iCell != ResultingCells.end(); ++iCell)
		if(*iCell == CellToSum)
			return false;

	Score += NumArray[ARowTo][AColTo] += NumArray[ARowFrom][AColFrom];
	NumArray[ARowFrom][AColFrom] = -1;

	ResultingCells.push_back(std::make_pair(ARowTo, AColTo));

	return GenCellPending = GenCellFlash  = true;
}
//---------------------------------------------------------------------------
// Ενημέρωση της ένδειξης σκορ
//---------------------------------------------------------------------------
void InvalidateScore(const HWND& hWndGame)
{
	static char strScore[BUFSIZ];

	wsprintf(strScore, "%s Score: %d", WIN_TITLE, Score);

	SetWindowText(hWndGame, Score ? strScore: WIN_TITLE);
}
//---------------------------------------------------------------------------
// Μετακίνηση του πλέγματος στην κατεύθυνση ‘Key’
//---------------------------------------------------------------------------
void MoveGrid(const WORD& Key)
{
	int ARow,
		ACol;

	ResultingCells.clear();
	KillTimer(hWndGame, 1);

	switch(Key) {
		case VK_LEFT:
			for(ARow = 0; ARow < 4; ++ARow)
				for(ACol = 0; ACol < 4; ++ACol)
					ScrollCellTo(ARow, ACol, Key);
		break;
		case VK_RIGHT:
			for(ARow = 0; ARow < 4; ++ARow)
				for(ACol = 3; ACol >= 0; --ACol)
					ScrollCellTo(ARow, ACol, Key);
		break;
		case VK_UP:
			for(ACol = 0; ACol < 4; ++ACol)
				for(ARow = 0; ARow < 4; ++ARow)
					ScrollCellTo(ARow, ACol, Key);
		break;
		case VK_DOWN:
			for(ACol = 0; ACol < 4; ++ACol)
				for(ARow = 4; ARow >= 0; --ARow)
					ScrollCellTo(ARow, ACol, Key);
		break;
	}
}
//---------------------------------------------------------------------------
// Μετακίνηση του πλακιδίου στην θέση ARow;ACol του πλέγματος προς την κατεύθυνση 
// ‘Dir’ μέχρι να συγκρουστεί στα όρια του πλέγματος ή σε ένα ανισότιμο πλακίδιο.
//
// Αν το πλακίδιο συγκρουστεί με ισότιμο πλακίδιο τότε άθροιση τους και διακοπή 
// μετακίνησης!
//---------------------------------------------------------------------------
void ScrollCellTo(int &ARow, int &ACol, const WORD& Dir)
{
	#ifdef _DEBUG
	const int dbg = NumArray[0][0];
	#endif

	// Διακοπή όταν το πλακίδιο φτάνει στα όρια του πλέγματος ή η εξεταζόμενη 
	// θέση του πλέγματος είναι κενή
	if(!CellInBound(ARow, ACol) || EmptyCell(ARow, ACol))
		return;

	switch(Dir) {
		case VK_LEFT:
			// Άθροισμα ισότιμων πλακιδίων; Αν όχι τότε μετακίνηση του πλακιδίου 
			// προς την κατεύθυνση Dir, αν αυτό δεν είναι δυνατόν τότε διακοπή!
			if(MatchingCell(ARow, ACol, ARow, ACol - 1)) 
			{
				if(!SumCells(ARow, ACol, ARow, ACol - 1))
					return;
			}
			else
				if(EmptyCell(ARow, ACol - 1) && MoveCell(ARow, ACol, ARow, ACol - 1))
					ACol--;
				else
					return;
		break;
		case VK_RIGHT:			
			if(MatchingCell(ARow, ACol, ARow, ACol + 1)) 
			{
				if(!SumCells(ARow, ACol, ARow, ACol + 1))
					return;
			}
			else
				if(EmptyCell(ARow, ACol + 1) && MoveCell(ARow, ACol, ARow, ACol + 1))
					ACol++;
				else
					return;
		break;
		case VK_UP:			
			if(MatchingCell(ARow - 1, ACol, ARow, ACol)) 
			{
				if(!SumCells(ARow, ACol, ARow - 1, ACol))
					return;
			}
			else
				if(EmptyCell(ARow - 1, ACol) && MoveCell(ARow, ACol, ARow - 1, ACol))
					ARow--;
				else
					return;

		break;
		case VK_DOWN:			
			if(MatchingCell(ARow + 1, ACol, ARow, ACol)) 
			{
				if(!SumCells(ARow, ACol, ARow + 1, ACol))
					return;
			}
			else
				if(EmptyCell(ARow + 1, ACol) && MoveCell(ARow, ACol, ARow + 1, ACol))
					ARow++;
				else
					return;
		break;
	}

	// Επανάληψη μετακίνησης με αναδρομή-στοίβας
	ScrollCellTo(ARow, ACol, Dir);
}
//---------------------------------------------------------------------------
// Έναρξη αναβοσβήματος του πλακιδίου στην θέση ARow;ACol του πλέγματος
//---------------------------------------------------------------------------
void StartTimer(const int& ARow, const int& ACol) 
{
	KillTimer(hWndGame, GRID_TIMER);

	LatestGenCell  = std::make_pair(ARow, ACol);
	GenCellFlashCnt= 5;
	GenCellFlash   = FlashTimerOn = true;

	SetTimer(hWndGame, GRID_TIMER, GRID_TIMER_MS, NULL);
}
//---------------------------------------------------------------------------
// Διακοπή αναβοσβήματος πλακιδίου
//---------------------------------------------------------------------------
void EndTimer(void) 
{
	KillTimer(hWndGame, GRID_TIMER);

	LatestGenCell  = std::make_pair(-1, -1);
	GenCellFlashCnt= 5;
	GenCellFlash   = !(FlashTimerOn = false);
}

 

01. Το παιχνίδι αναπτύχθηκε σε C++ Builder XE και ο πηγαίος κώδικας του είναι συμβατός με την MS-VC++ 2010 (ή ίσως και με παλαιότερες εκδόσεις).

 

02. Έχει δοκιμασθεί σε Windows 7 και σε Large fonts ενώ παίζει σχετικά καλά και σε Small fonts (εξωτικά DPI δεν δοκίμασα).

 

03. Το πρόγραμμα φυσικά μπορεί να περιέχει bugs ή άλλες αβλεψίες (υποστήριξη λόγο χρόνου δεν υπάρχει)

 

Υ.Γ.

Δεν είμαι fan τέτοιων παιχνιδιών αλλά το 2048 μου κίνησε το ενδιαφέρον ώστε να ασχοληθώ λίγο περισσότερο μαζί του.

 

Ελπίζω κάποια τμήματα του software να βοηθήσουν στο port του φίλου migf1 από κονσόλα σε GUI (τουλάχιστον σε Windows).

 

Καλύτερη λύση πάντως για αγνωστικής πλατφόρμας GUI, το QT framework καθώς μεταξύ άλλων θετικών υλοποιεί και ένα πανίσχυρο σύστημα animation!

 

  • Like 4
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 272
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Συχνή συμμετοχή στο θέμα

Δημοσιευμένες Εικόνες

Ωραίος (ως συνήθως) φίλε Dx!

 

Για να γίνει compile με mingw-g++ ο κώδικας αρκεί να προστεθεί ένα

#include <cstdio>
ώστε να αναγνωρίζει το BUFSIZ και κατόπιν στη γραμμή εντολών:

g++ -std=c++11 xd2048.cpp -o xd2048.exe -lkernel32 -luser32 -lcomdlg32 -lgdi32 -lcomctl32 -Wl,--subsystem,windows
Στα πολύ βιαστικά, το έβαλα να ειδοποιεί με message-box τον παίκτη όταν χάνει και να τον προτρέπει να επιλέξει Restart αν θέλει να ξεκινήσει νέο παιχνίδι.

 

Επειδή τις κινήσεις τις κάνεις με αναδρομή σε void συνάρτηση και δεν έκατσα να κάνω μετατροπή με bool τιμή επιστροφής και propagation της στους διαδοχικούς callers, προσέθεσα στα γρήγορα μια απλή συνάρτηση HasAdjacentCells() που ελέγχει επιτόπου το grid και την έβαλα στο case WM_KEYDOWN της GameProc.

 

Το αποτέλεσμα είναι πως η ειδοποίηση δεν βγαίνει αμέσως μόλις γεμίσει το grid, αλλά μόλις επιχειρηθεί κίνηση ακριβώς μετά από εκεί (btw, ελπίζω να μη χάλασα κάποια άλλη λειτουργικότητα του game... αν και λογικά δεν πρέπει να χάλασα κάτι).

 

Κώδικας:

 

 

// Simple 2048 Clone, xdir
// μετάφραση με C++ Builder XE ή Visual Studio C++
//
// ** VC2010: Όρισε το "Enable Incremental Linking" σε "NO"
// : Όρισε το "Character Set" σε "Not Set"

#include <windows.h>
#include <stdlib.h>
#include <time.h>

#include <cstdio>
#include <vector>
#include <utility>
#include <map>

int       NumArray[4][4]  = { 0 },
          Score           = 0,
          GenCellFlashCnt = 5;

bool      GenCellPending = false,
          GenCellFlash   = true,
          FlashTimerOn   = false,
          GameRunning    = false;

HINSTANCE gInstance;

HWND      hWndGame    = NULL,
          hWndGameWin = NULL;

std::pair<int, int> LatestGenCell;
std::vector<std::pair<int, int> > ResultingCells;
std::map<int, std::pair<COLORREF, COLORREF> > NumberToColor;

#define	WIN_TITLE        "XD2048"

#define	GRID_WIDTH       325
#define	GRID_HEIGHT      312

#define	GRID_TIMER       1
#define	GRID_TIMER_MS    150

#define	MI_RESTART       WM_USER + 10

#define	WebWhite         RGB(0xFF, 0xFF, 0xFF)
#define WebLinen         RGB(0xFA, 0xF0, 0xE6)
#define WebAntiqueWhite  RGB(0xFA, 0xEB, 0xD7)
#define	WebPeachPuff	 RGB(0xFF, 0xDA, 0xB9)
#define	WebOrange	 RGB(0xFF, 0xA5, 0x00)
#define WebCoral	 RGB(0xFF, 0x7F, 0x50)
#define	WebTomato	 RGB(0xFF, 0x63, 0x47)
#define WebPaleGoldenrod RGB(0xEE, 0xE8, 0xAA)
#define WebKhaki	 RGB(0xF0, 0xE6, 0x8C)
#define WebGold	         RGB(0xFF, 0xD7, 0x00)
#define	WebLightYellow	 RGB(0xFF, 0xFF, 0xE0)
#define WebYellow	 RGB(0xFF, 0xFF, 0x00)
#define WebMaroon	 RGB(0x80, 0x00, 0x00)
#define	WebIvory	 RGB(0xFF, 0xFF, 0xF0)

/* MIGF1 */
#define ERRBOX(lpText)              \
	MessageBox( NULL, (lpText), NULL, MB_ICONSTOP )

/* MIGF1 */
#define INFOBOX(lpText, lpCaption)  \
	MessageBox( NULL, (lpText), (lpCaption), MB_ICONINFORMATION )

void BeginNewGame(void);
void StartTimer(const int& ARow, const int& ACol);
void InvalidateScore(const HWND& hWndGame);
void EndTimer(void);
bool GameWon(void);

bool GenerateNewTile(const int& Number = -1);
bool HasAdjacentCells( void );   /* MIGF1 */
bool HasEmptyCell(void);
bool EmptyCell(const int&, const int&);
bool CellInBound(const int& ARowTo, const int& AColTo);
bool MatchingCell(const int& ARowFrom, const int& AColFrom,
const int& ARowTo, const int& AColTo);
void MoveGrid(const WORD& Key);
void ScrollCellTo(int &ARow, int &ACol, const WORD& Dir);

LRESULT CALLBACK GameProc(HWND, UINT, WPARAM, LPARAM);

#ifdef __BORLANDC__
#pragma argsused
#endif
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	gInstance = hInstance;

	WNDCLASS wc = { 0 };

	wc.hInstance     = hInstance;
	wc.lpszClassName = WIN_TITLE;
	wc.lpfnWndProc   = GameProc;
	wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground =reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));

	if ( !RegisterClass(&wc) ) {
		ERRBOX( "RegisterClass failed" );
	}
	else {
		// Δημιουργία μενού
		const HMENU MainMenu = CreateMenu();

		AppendMenu(MainMenu, MF_STRING, MI_RESTART, "&Restart");

		// Δημιουργία παραθύρου
		hWndGame = CreateWindow(
			wc.lpszClassName,
			wc.lpszClassName,
			(WS_OVERLAPPEDWINDOW ^ WS_MAXIMIZEBOX) ^ WS_THICKFRAME,
			CW_USEDEFAULT, CW_USEDEFAULT,
			GRID_WIDTH,
			GRID_HEIGHT + GetSystemMetrics(SM_CYMENU),
			NULL,
			MainMenu,
			hInstance,
			NULL
			);

		if ( !hWndGame ) {
			ERRBOX( "CreateWindow failed" );
		}
		else {
			// Παρουσίαση παραθύρου
			ShowWindow(hWndGame, nCmdShow);
			UpdateWindow(hWndGame);

			// Διαχείριση μηνυμάτων προγράμματος
			MSG msg;
			int nRet;

			while( (nRet = GetMessage(&msg, NULL, 0,0)) )
			{
				if ( nRet == -1 ) {
					ERRBOX( "GetMessage failed" );
					break;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
	}

	return 0;
}
//---------------------------------------------------------------------------
// Game event driven loop
//---------------------------------------------------------------------------
LRESULT CALLBACK GameProc(HWND hWndGame, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	static HFONT  fonGame  = NULL;
	static HBITMAP bmpGrid = NULL;
	static HDC     dcGrid  = NULL;

	switch( Msg )
	{
		case WM_CREATE:
		{
			// Προετοιμασία κατασκευής DC πλέγματος 4x4
			const HDC dcGame = GetDC(hWndGame);

			if ( !(dcGrid = CreateCompatibleDC(dcGame)) ) {
				ERRBOX( "CreateCompatibleDC failed" );
				return -1;
			}
			if (
			!( bmpGrid = CreateCompatibleBitmap(dcGame,325,312) )
			){
				ERRBOX( "CreateCompatibleBitmap failed" );
				return -1;
			}
			SelectObject(dcGrid, bmpGrid);

			// Κατασκευή φόντου προγράμματος
			fonGame = CreateFont(
					-18, 0, 0, 0,
					FW_DONTCARE,
					FALSE, FALSE, FALSE,
					ANSI_CHARSET,
					OUT_CHARACTER_PRECIS,
					CLIP_CHARACTER_PRECIS,
					DEFAULT_QUALITY,
					FF_DONTCARE,
					"Comic Sans MS"
					);
			if ( !fonGame ) {
				ERRBOX( "CreateFont failed" );
				return -1;
			}
			SelectObject(dcGrid, fonGame);
			SetBkMode(dcGrid, TRANSPARENT);

			ReleaseDC(hWndGame, dcGame);

			// Κατασκευή Static Control νίκης
			hWndGameWin = CreateWindow(
					"STATIC", "You Win!",
					WS_CHILD | SS_CENTER,
					100, 125, 105, 25,
					hWndGame, NULL, gInstance, NULL
					);

			SendMessage(
				hWndGameWin,
				WM_SETFONT,
				WPARAM(fonGame),
				TRUE
				);

			// Ορισμός του ευρετηρίου χρωμάτων του προγράμματος
			NumberToColor[-1] = std::make_pair(
						RGB(204, 192, 179),
						WebWhite
						);
			NumberToColor[2]  = std::make_pair(
						WebLinen,
						WebMaroon
						);
			NumberToColor[4]  = std::make_pair(
						WebAntiqueWhite,
						WebMaroon
						);
			NumberToColor[8]  = std::make_pair(
						WebPeachPuff,
						WebIvory
						);
			NumberToColor[16] = std::make_pair(
						WebOrange,
						WebIvory
						);
			NumberToColor[32] = std::make_pair(
						WebCoral,
						WebIvory
						);
			NumberToColor[64] = std::make_pair(
						WebTomato,
						WebIvory
						);
			NumberToColor[128] = std::make_pair(
						WebPaleGoldenrod,
						WebMaroon
						);
			NumberToColor[256] = std::make_pair(
						WebKhaki,
						WebMaroon
						);
			NumberToColor[512] = std::make_pair(
						WebGold,
						WebMaroon
						);
			NumberToColor[1024] = std::make_pair(
						WebLightYellow,
						WebMaroon
						);
			NumberToColor[2048] = std::make_pair(
						WebYellow,
						WebMaroon
						);

			// Προετοιμασία RNG
#ifndef __BORLANDC__
			srand( static_cast<unsigned>(time(NULL)) );
#else
			srand(time(NULL));
#endif
			BeginNewGame();
		}
		return 0;

		case WM_PAINT:
		{
			RECT rcUpdate;
			PAINTSTRUCT ps;

			// Πρέπει να ενημερώσω το παράθυρο του προγράμματος;
			if ( !GetUpdateRect(hWndGame, &rcUpdate, FALSE) ) {
				break;
			}
			if ( !rcUpdate.left && !rcUpdate.right
			&& !rcUpdate.top && !rcUpdate.bottom
			){
				break;
			}

			// Ναι. Σχεδίαση πλέγματος 4x4
			if ( !BeginPaint(hWndGame, &ps) ) {
				break;
			}

			// Σχεδίαση πλακιδίων
			for (int iRow = 0; iRow < 4; ++iRow)
			{
				for (int iCol = 0; iCol < 4; ++iCol)
				{
					RECT rcTile = {
						iCol * 80,
						iRow * 70,
						iCol * 80 + 80,
						iRow * 70 + 70
					};

					const int *ptrNumber
					= &NumArray[iRow][iCol];

					static LOGBRUSH lgBr = { BS_SOLID };

					lgBr.lbColor
					= NumberToColor[*ptrNumber].first;

					// Πλέγμα
					Rectangle(dcGrid,
					rcTile.left, rcTile.top,
					rcTile.right, rcTile.bottom);

					// Πλακίδια
					rcTile.left   += 4;
					rcTile.right  -= 4;
					rcTile.top    += 4;
					rcTile.bottom -= 4;

					static HBRUSH br;
					br = CreateBrushIndirect(&lgBr);
					FillRect(dcGrid, &rcTile, br);
					DeleteObject(br);

					// Αγνόηση κενών πλακιδίων
					if ( *ptrNumber == -1 ) {
						continue;
					}

					// Να αναβοσβήσω το πιο πρόσφατα
					// δημιουργημένο πλακίδιο;
					if ( FlashTimerOn ) {
						if (
						LatestGenCell
						== std::make_pair(iRow, iCol)
						){
							if ( !GenCellFlash ) {
								continue;
							}
						}
					}

					// Εκτύπωση αριθμού πλακιδίου
					static char strTile[32];
					wsprintf(strTile, "%d", *ptrNumber);

					DrawText(dcGrid,
					strTile, -1,
					&rcTile,
					DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_END_ELLIPSIS);
				}
			}

			// Σχεδίαση εικόνας προγράμματος στο παράθυρο
			// (double buffer)
			BitBlt(
				ps.hdc,
				0, 0, GRID_WIDTH, GRID_HEIGHT,
				dcGrid, 0, 0, SRCCOPY
				);

			// Τέλος της ενημέρωσης παράθυρου!
			EndPaint( hWndGame, &ps );
		}
		return 0;

		// Διαχείριση των πλήκτρων του προγράμματος
		case WM_KEYDOWN:
		{
			switch ( wParam )
			{
				case VK_LEFT:
				case VK_RIGHT:
				case VK_UP:
				case VK_DOWN:
					if ( GameRunning ) {
						GenCellPending = false;

						EndTimer();

						MoveGrid(wParam);
						InvalidateRect(hWndGame, NULL, FALSE);
						InvalidateScore(hWndGame);

						if ( GameWon() ) {
							// Νικηφόρο τέλος παιχνιδιού;
							ShowWindow(hWndGameWin, SW_SHOW);
							GameRunning = false;
						}
						else {	// Προσθήκη νέου πλακιδίου στο πλέγμα;
							if ( GenCellPending ) {
								GenerateNewTile();
							}
							else if (
							!HasAdjacentCells()
							&& !HasEmptyCell()
							){
								INFOBOX(
								"You have lost!\n"
								"Select Restart to try again",
								"GAME OVER"
								);
							}
						}
					}
					return 0;

				case VK_F5:
					BeginNewGame();
					return 0;
			}
		}
		break;

		// Διαχείριση χρονομέτρων προγράμματος
		case WM_TIMER:
			switch(wParam)
			{
				case 1:
					if ( --GenCellFlashCnt < 0 ) {
						EndTimer();
					}
					else {
						GenCellFlash = !GenCellFlash;
					}
					InvalidateRect(hWndGame, NULL, FALSE);
					return 0;
			}
			break;

		// Διαχείριση μενού προγράμματος
		case WM_COMMAND:
			switch( wParam )
			{
				case MI_RESTART:
					BeginNewGame();
					return 0;
			}
			return 0;

		// Διαχείριση εξόδου από το πρόγραμμα
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
	}

	return DefWindowProc(hWndGame, Msg, wParam, lParam);
}

// Συναρτήσεις διαχείρισης & λογικής παιχνιδιού //

//---------------------------------------------------------------------------
// Έναρξη νέου παιχνιδιού
//---------------------------------------------------------------------------
void BeginNewGame( void )
{
	GameRunning = false;

	Score = GenCellPending = 0;
	InvalidateScore(hWndGame);

	EndTimer();

	ResultingCells.clear();

	memset(&NumArray, -1, sizeof(NumArray));

	const int Number = rand() % 101 <= 50 ? 2: 4;

	for (int TileCount = 2; TileCount > 0; TileCount-- ) {
		GenerateNewTile(Number);
	}

	InvalidateRect(hWndGame, NULL, FALSE);

	ShowWindow(hWndGameWin, SW_HIDE);

	GameRunning = true;
}

//---------------------------------------------------------------------------
// Τοποθέτηση τυχαίων πλακιδίων (με αριθμό το 2 ή το 4) στο πλέγμα.
// Όταν Number != -1 τότε τοποθέτηση πλακιδίου με αριθμό το Number σε τυχαία
// θέση του πλέγματος (σε αυτή την περίπτωση το πλακίδιο δεν θα αναβοσβήσει).
//
// true όταν το πλακίδιο τοποθετηθεί επιτυχώς διαφορετικά, όταν δεν υπάρχει
// ελεύθερος χώρος στο πλέγμα, false.
//---------------------------------------------------------------------------
bool GenerateNewTile( const int& Number) 
{
	short ACol,
	      ARow;

	if ( HasEmptyCell() ) {

		do {
			ACol = rand() % 4;
			ARow = rand() % 4;
		} while( NumArray[ARow][ACol] != -1 );

		NumArray[ARow][ACol] = Number == -1 ? rand() % 101 <= 50 ? 2: 4:
		Number;

		if ( Number == -1 ) {
			StartTimer(ARow, ACol);
		}

		return true;
	}

	return false;
}

/* ---------------------------------------------------------------------------
 * ** ADDED BY MIGF1 ***
 * ---------------------------------------------------------------------------
 */
bool HasAdjacentCells( void )
{
	/* horizontally */
	for (short i=0; i < 4; i++)
	{
		for (short j=0; j < 3; j++ ) {
			if ( !EmptyCell(i,j) && NumArray[i][j] == NumArray[i][j+1] ) {
				return true;
			}
		}
	}

	/* vertically */
	for (short j=0; j < 4; j++) {
		for (short i=0; i < 3; i++ ) {
			if ( !EmptyCell(i,j) && NumArray[i][j] == NumArray[i+1][j] ) {
				return true;
			}
		}
	}
	return false;
}

//---------------------------------------------------------------------------
// true όταν το πλέγμα μπορεί να δεχθεί ένα ή περισσότερα πλακίδια,
// διαφορετικά false
//---------------------------------------------------------------------------
bool HasEmptyCell( void )
{
	for (short ARow =0; ARow < 4; ARow++) {
		for (short ACol = 0; ACol < 4; ACol++) {
			if ( EmptyCell(ARow, ACol) ) {
				return true;
			}
		}
	}

	return false;
}

//---------------------------------------------------------------------------
// true όταν η θέση ARow;ACol (Y, X) του πλέγματος δεν περιέχει πλακίδιο και
// η θέση είναι εντός των διαστάσεων του πλέγματος, διαφορετικά false
//---------------------------------------------------------------------------
bool EmptyCell(const int& ARow, const int& ACol)
{
	if (ARow < 0 || ARow > 3 || ACol < 0 || ACol > 3 ) {
		return false;
	}
	return NumArray[ARow][ACol] == -1;
}

//---------------------------------------------------------------------------
// true όταν βρεθεί πλακίδιο με αριθμό 2048 διαφορετικά false
//---------------------------------------------------------------------------
bool GameWon(void)
{
	for (short ARow = 0; ARow < 4; ARow++) {
		for (short ACol = 0; ACol < 4; ACol++) {
			if ( NumArray[ARow][ACol] == 2048 ) {
				return true;
			}
		}
	}
	return false;
}

//---------------------------------------------------------------------------
// true όταν τα πλακίδια στις θέσεις του πλέγματος ARowFrom, AColFrom και
// ARowTo, AColTo περιέχουν ισότιμους αριθμούς, διαφορετικά false
//---------------------------------------------------------------------------
bool MatchingCell(
	const int& ARowFrom,
	const int& AColFrom,
	const int& ARowTo,
	const int& AColTo
	)
{
	return CellInBound(ARowFrom, AColFrom) && CellInBound(ARowTo, AColTo)
		? NumArray[ARowFrom][AColFrom] == NumArray[ARowTo][AColTo]
		: false
		;
}
//---------------------------------------------------------------------------
// Μετακίνηση του πλακιδίου της θέσης του πλέγματος ARowFrom, AColFrom στην
// θέση ARowTo, AColTo του πλέγματος.
//
// true για επιτυχή μετακίνηση ή όταν οι θέσεις του πλέγματος είναι εκτός
// ορίων τότε false
//---------------------------------------------------------------------------
bool MoveCell(
	const int& ARowFrom,
	const int& AColFrom,
	const int& ARowTo,
	const int& AColTo
	)
{
	if ( CellInBound(ARowFrom, AColFrom) && CellInBound(ARowTo, AColTo) )
	{
		NumArray[ARowTo][AColTo]     = NumArray[ARowFrom][AColFrom];
		NumArray[ARowFrom][AColFrom] = -1;
		return (GenCellPending = GenCellFlash = true);
	}
	return false;
}

//---------------------------------------------------------------------------
// true όταν η θέση του πλακιδίου ARowTo, AColTo είναι εντός των ορίων του
// πλέγματος διαφορετικά false
//---------------------------------------------------------------------------
bool CellInBound( const int& ARowTo, const int& AColTo )
{
	return ARowTo >= 0 && ARowTo < 4
	       && AColTo >= 0 && AColTo < 4;
}
//---------------------------------------------------------------------------
// ʼθροιση των πλακιδίων των θέσεων ARowFrom;AColFrom και ARowTo;AColTo του
// πλέγματος.
//
// true όταν η άθροιση είναι επιτυχής διαφορετικά αν κάποιο από τα πλακίδια
// στις θέσεις αυτές προέκυψε από προηγούμενη άθροιση στον τρέχοντα γύρο του
// παιχνιδιού false
//---------------------------------------------------------------------------
bool SumCells(
	const int& ARowFrom,
	const int& AColFrom,
	const int& ARowTo,
	const int& AColTo
	)
{
	const std::pair<int, int> CellToSum = std::make_pair(ARowTo, AColTo);

	// Το ResultingCell περιέχει όλα τα πλακίδια που προέκυψαν από άθροιση ισότιμων
	// πλακιδίων στον τρέχοντα γύρο του παιχνιδιού
	std::vector<std::pair<int, int> >::const_iterator iCell = ResultingCells.begin();

	for (;iCell != ResultingCells.end(); ++iCell ) {
		if ( *iCell == CellToSum ) {
			return false;
		}
	}

	Score += NumArray[ARowTo][AColTo] += NumArray[ARowFrom][AColFrom];
	NumArray[ARowFrom][AColFrom] = -1;

	ResultingCells.push_back(std::make_pair(ARowTo, AColTo));

	return GenCellPending = GenCellFlash = true;
}

//---------------------------------------------------------------------------
// Ενημέρωση της ένδειξης σκορ
//---------------------------------------------------------------------------
void InvalidateScore(const HWND& hWndGame)
{
	static char strScore[BUFSIZ];

	wsprintf(strScore, "%s Score: %d", WIN_TITLE, Score);
	SetWindowText(
		hWndGame,
		Score ? strScore: WIN_TITLE
		);
}

//---------------------------------------------------------------------------
// Μετακίνηση του πλέγματος στην κατεύθυνση Key
//---------------------------------------------------------------------------
void MoveGrid(const WORD& Key)
{
	int ARow,
	    ACol;

	ResultingCells.clear();
	KillTimer(hWndGame, 1);

	switch( Key ) {
		case VK_LEFT:
			for (ARow = 0; ARow < 4; ++ARow) {
				for (ACol = 0; ACol < 4; ++ACol) {
					ScrollCellTo(ARow, ACol, Key);
				}
			}
			break;

		case VK_RIGHT:
			for (ARow = 0; ARow < 4; ++ARow) {
				for (ACol = 3; ACol >= 0; --ACol) {
					ScrollCellTo(ARow, ACol, Key);
				}
			}
			break;

		case VK_UP:
			for (ACol = 0; ACol < 4; ++ACol) {
				for (ARow = 0; ARow < 4; ++ARow) {
					ScrollCellTo(ARow, ACol, Key);
				}
			}
			break;

		case VK_DOWN:
			for (ACol = 0; ACol < 4; ++ACol) {
				for (ARow = 4; ARow >= 0; --ARow) {
					ScrollCellTo(ARow, ACol, Key);
				}
			}
			break;
	}
}

//---------------------------------------------------------------------------
// Μετακίνηση του πλακιδίου στην θέση ARow;ACol του πλέγματος προς την κατεύθυνση
// Dir μέχρι να συγκρουστεί στα όρια του πλέγματος ή σε ένα ανισότιμο πλακίδιο.
//
// Αν το πλακίδιο συγκρουστεί με ισότιμο πλακίδιο τότε άθροιση τους και διακοπή
// μετακίνησης!
//---------------------------------------------------------------------------
void ScrollCellTo( int &ARow, int &ACol, const WORD& Dir )
{
#ifdef _DEBUG
	const int dbg = NumArray[0][0];
#endif

	// Διακοπή όταν το πλακίδιο φτάνει στα όρια του πλέγματος ή η εξεταζόμενη
	// θέση του πλέγματος είναι κενή
	if ( !CellInBound(ARow, ACol) || EmptyCell(ARow, ACol) ) {
		return;
	}

	switch( Dir )
	{
		case VK_LEFT:
			// ʼθροισμα ισότιμων πλακιδίων; Αν όχι τότε μετακίνηση του πλακιδίου
			// προς την κατεύθυνση Dir, αν αυτό δεν είναι δυνατόν τότε διακοπή!
			if ( MatchingCell(ARow, ACol, ARow, ACol - 1) ) {
				if(!SumCells(ARow, ACol, ARow, ACol - 1)) {
					return;
				}
			}
			else {
				if (
				EmptyCell(ARow, ACol - 1)
				&& MoveCell(ARow, ACol, ARow, ACol - 1)
				){
					ACol--;
				}
				else {
					return;
				}
			}
			break;

		case VK_RIGHT:	
			if ( MatchingCell(ARow, ACol, ARow, ACol + 1) ) {
				if( !SumCells(ARow, ACol, ARow, ACol + 1) ) {
					return;
				}
			}
			else {
				if ( EmptyCell(ARow, ACol + 1)
				&& MoveCell(ARow, ACol, ARow, ACol + 1)
				){
					ACol++;
				}
				else {
					return;
				}
			}
			break;

		case VK_UP:	
			if ( MatchingCell(ARow - 1, ACol, ARow, ACol) ) {
				if ( !SumCells(ARow, ACol, ARow - 1, ACol) ) {
					return;
				}
			}
			else {
				if ( EmptyCell(ARow - 1, ACol)
				&& MoveCell(ARow, ACol, ARow - 1, ACol)
				){
					ARow--;
				}
				else {
					return;
				}
			}
			break;

		case VK_DOWN:	
			if ( MatchingCell(ARow + 1, ACol, ARow, ACol) ) {
				if ( !SumCells(ARow, ACol, ARow + 1, ACol) ) {
					return;
				}
			}
			else {
				if ( EmptyCell(ARow + 1, ACol)
				&& MoveCell(ARow, ACol, ARow + 1, ACol)
				){
					ARow++;
				}
				else {
					return;
				}
			}
			break;
	}

	// Επανάληψη μετακίνησης με αναδρομή-στοίβας
	ScrollCellTo(ARow, ACol, Dir);
}

//---------------------------------------------------------------------------
// Έναρξη αναβοσβήματος του πλακιδίου στην θέση ARow;ACol του πλέγματος
//---------------------------------------------------------------------------
void StartTimer( const int& ARow, const int& ACol )
{
	KillTimer(hWndGame, GRID_TIMER);

	LatestGenCell   = std::make_pair(ARow, ACol);
	GenCellFlashCnt = 5;
	GenCellFlash    = FlashTimerOn = true;

	SetTimer(hWndGame, GRID_TIMER, GRID_TIMER_MS, NULL);
}

//---------------------------------------------------------------------------
// Διακοπή αναβοσβήματος πλακιδίου
//---------------------------------------------------------------------------
void EndTimer( void )
{
	KillTimer(hWndGame, GRID_TIMER);

	LatestGenCell   = std::make_pair(-1, -1);
	GenCellFlashCnt = 5;
	GenCellFlash    = !(FlashTimerOn = false);
}

 

ΥΓ. Για να είμαι απολύτως ειλικρινής, με χαλάσανε λιγάκι τόσες πολλές global μεταβλητές, αλλά οκ είπαμε... τον χαβαλέ μας κάνουμε :)

  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

ΥΓ. Για να είμαι απολύτως ειλικρινής, με χαλάσανε λιγάκι τόσες πολλές global μεταβλητές, αλλά οκ είπαμε... τον χαβαλέ μας κάνουμε  :) 

Yup! Το έγραψα quick & dirty ίσα - ίσα να χωράει σε μια ανάρτηση (ήταν δουλεία μερικών ωρών).

 

Υ.Γ.

 Πριν πολύ καιρό είχα γράψει ένα ακόμα παιχνίδι από ένα άλλο παρόμοιου περιεχομένου topic του forum, ήταν ένα Battleship Solitaire ξανά σε C/C++ & Windows API, μου είχε βγει περίπου 1000 γραμμές - - κάποια στιγμή λέω να το αναρτήσω.

 

 Με αυτό τον τρόπο φρεσκάρω το WinAPI μου που & που ;-)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Έκανα πριν από λίγο ένα commit, όπου μαζί με ένα προηγούμενο, έχουν πλέον προστεθεί πλήρη σχόλια σε όλα τα πηγαία αρχεία πλην των mvhist.c/mvhist.h (αυτά από αύριο).

 

Επίσης μόλις σήμερα παρατήρησα πως στο δικό μου fork, όταν είχα βάλει tag στο Version Bump commit το Github το πέρασε αυτόματα και στα Releases. Οπότε geomagas, μήπως πρέπει να κάνεις tag κι εσύ στο blessed master για να περάσει κι εκεί downloadable release;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Όταν κάνεις fetch ένα branch, από τη μάνα του το git σου φέρνει και όλες τις tags που δείχνουν στα objects που σου φέρνει.

 

Όταν όμως χρησιμοποιείς το UI του github δεν ξέρω τι διαδικασία γίνεται. Οι tags είναι πολύ σημαντικό θέμα ενός VCS οπότε υποθέτω πως σίγουρα το κάνει το github αλλά δεν το χρησιμοποιώ και δεν ξέρω πώς γίνεται.

 

Είναι δραστική αλλαγή οπότε δεν μπορώ να την προτείνω σε άλλους αλλά προσωπικά χρησιμοποιώ το github μόνο σαν hosting (και chat όταν χρειάζεται code review) facility. Οποιαδήποτε ενέργεια θέλω να κάνω την κάνω μέσω git και αν κάποια ενέργειά μου χρειάζεται να "επικοινωνήσει" με το github χρησιμοποιώ τα tokens που παρέχει. Αντί λοιπόν να κάνω κάτι merge από το ui του github, μπορώ να το κάνω από το git και απλά να γράψω στο τέλος του commit "fixes #42" και αυτόματα θα κλείσει το bug 42.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ας υποθέσουμε ότι κάνω checkout και τα δύο, σε local copies (geomagas/2048cc και migf1/2048cc), αντιγράφω το migf1/2048cc/tags στο geomagas/2048cc και μετά κάνω commit, λες να πάει κάτι πολύ στραβα; (μπορώ να το τεστάρω φτιάχνοντας ένα dummy repo, αλλά είπα να ρωτήσω πρώτα)

 

BTW, χρησιμοποιώ svn από commandline.

 

---

 

Είπσης, δεν θα ήταν πιο σκόπιμο να βάλω πχ τον migf1 στους contributors ώστε να μη χρειάζεται να συντηρεί καν δικό του fork, αλλά να κάνει commit απ' ευθείας;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Είπσης, δεν θα ήταν πιο σκόπιμο να βάλω πχ τον migf1 στους contributors ώστε να μη χρειάζεται να συντηρεί καν δικό του fork, αλλά να κάνει commit απ' ευθείας;

Εφόσον δουλεύετε σε ξεχωριστά branches και δεν υπάρχει θέμα με conflicts και χαζομάρες, λογικό μου ακούγεται.

 

 

 

Ας υποθέσουμε ότι κάνω checkout και τα δύο, σε local copies (geomagas/2048cc και migf1/2048cc), αντιγράφω το migf1/2048cc/tags στο geomagas/2048cc και μετά κάνω commit, λες να πάει κάτι πολύ στραβα; (μπορώ να το τεστάρω φτιάχνοντας ένα dummy repo, αλλά είπα να ρωτήσω πρώτα)

 

BTW, χρησιμοποιώ svn από commandline.

Εφόσον έκανες clone, λογικά θα είναι στο .git/packed-refs αλλά δεν αντιγράφεις ποτέ έτσι χύμα το αρχείο. Κάνε fetch.

% for i in migf1 geomagas ; do
> git clone -v git://github.com/${i}/2048cc ${i}  
> done
Cloning into 'migf1'...
remote: Counting objects: 222, done.
remote: Compressing objects: 100% (112/112), done.
remote: Total 222 (delta 132), reused 179 (delta 108)
Receiving objects: 100% (222/222), 972.68 KiB | 282.00 KiB/s, done.
Resolving deltas: 100% (132/132), done.
Checking connectivity... done
Cloning into 'geomagas'...
remote: Counting objects: 221, done.
remote: Compressing objects: 100% (111/111), done.
remote: Total 221 (delta 132), reused 178 (delta 108)
Receiving objects: 100% (221/221), 972.54 KiB | 289.00 KiB/s, done.
Resolving deltas: 100% (132/132), done.
Checking connectivity... done

% cd geomagas 
% git fetch --tags -v ../migf1
remote: Counting objects: 1, done.
remote: Total 1 (delta 0), reused 1 (delta 0)
Unpacking objects: 100% (1/1), done.
From ../migf1
 * [new tag]         v0.3a3     -> v0.3a3
Μετά κάνε push (με --tags για να είσαι σίγουρος) στο github και θα πρέπει να εμφανίζεται στις tags.

 

Στο παρόν repo δεν παίζει και τόσο ρόλο αλλά υπάρχει και πιο δόκιμη λύση.

% git clone -v git://github.com/geomagas/2048cc geomagas
Cloning into 'geomagas'...                                                
remote: Counting objects: 221, done.
remote: Compressing objects: 100% (111/111), done.
remote: Total 221 (delta 132), reused 178 (delta 108)
Receiving objects: 100% (221/221), 972.54 KiB | 467.00 KiB/s, done.
Resolving deltas: 100% (132/132), done.
Checking connectivity... done

% cd geomagas 
% git remote add migf1 git://github.com/migf1/2048cc.git
% git remote -v update
Fetching origin
From git://github.com/geomagas/2048cc
 = [up to date]      master     -> origin/master
Fetching migf1
remote: Counting objects: 1, done.
remote: Total 1 (delta 0), reused 1 (delta 0)
Unpacking objects: 100% (1/1), done.
From git://github.com/migf1/2048cc
 * [new branch]      master     -> migf1/master
 * [new tag]         v0.3a3     -> v0.3a3
% git remote rm migf1
Και έτσι έλαβες μόνο όσα objects δεν είχες (σε αυτή τη περίπτωση μόνο το tag) αντί να κατεβάσεις 2 φορές το σύνολο των objects.
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ας υποθέσουμε ότι κάνω checkout και τα δύο, σε local copies (geomagas/2048cc και migf1/2048cc), αντιγράφω το migf1/2048cc/tags στο geomagas/2048cc και μετά κάνω commit, λες να πάει κάτι πολύ στραβα; (μπορώ να το τεστάρω φτιάχνοντας ένα dummy repo, αλλά είπα να ρωτήσω πρώτα)

 

Αν χρειάζεσαι μόνο κάποια συγκεκριμένα commits, τότε git cherry-pick hashvalue

 

 

Είπσης, δεν θα ήταν πιο σκόπιμο να βάλω πχ τον migf1 στους contributors ώστε να μη χρειάζεται να συντηρεί καν δικό του fork, αλλά να κάνει commit απ' ευθείας;

Μπορείς φυσικά να τον προσθέσεις (δηλαδή να του δώσεις write privileges) αλλά αν θέλετε να έχετε code review, τότε ούτως η άλλως το development θα πρέπει να γίνεται σε ξεχωριστό branch. Μετά το ποιος θα κάνει το merge στο master (ή όποιο branch αποφασίσετε ότι θα είναι το stable) και πόσο pedantic θα είστε στους κανόνες που θα συμφωνήσετε, ε, αυτό ειναί απόφαση των devs :)

 

http://nvie.com/posts/a-successful-git-branching-model/

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Μπορείς φυσικά να τον προσθέσεις (δηλαδή να του δώσεις write privileges) αλλά αν θέλετε να έχετε code review, τότε ούτως η άλλως το development θα πρέπει να γίνεται σε ξεχωριστό branch. Μετά το ποιος θα κάνει το merge στο master (ή όποιο branch αποφασίσετε ότι θα είναι το stable) και πόσο pedantic θα είστε στους κανόνες που θα συμφωνήσετε, ε, αυτό ειναί απόφαση των devs :)

+1

 

Για ένα project αυτού του βεληνεκούς (και λίγο μεγαλύτερου ακόμη) δεν είναι λίγο overkill το git-flow ? Ακόμη και το github που έχει 25-30 άτομα και πολύ πιο συνεχή και γρήγορη ανάπτυξη χρησιμοποιούν κάτι πολύ πιο απλό

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@imitheos

Δεν διαβασα τώρα το link που παραθέτεις, αλλά νομίζω ότι το έχω διαβάσει παλιότερα, και αν θυμάμαι καλά αυτό που λέει είναι ότι όταν κάνεις deploy 50 φορές την ημέρα τότε φυσικά και δεν υπάρχει η έννοια του stable ή του release. Για αυτό και δεν μπορεί να δουλέψει το gitflow στο github ή στο twitter.

 

Από την άλλη όμως, και το blog που περιγράφει το git-flow αλλά και αυτό που περιγράφει το github-flow νομίζω ότι πρέπει να διαβαστoύν από όλους όσους ψάχνονται με DVCS workflows.

 

Έχω ένα φίλο πάντως που το χρησιμοποιεί στα πάντα και μου λέει ότι δεν υπάρχει θέμα. Απλά έχει και αυτό ένα initial learning curve (όπως και κάθε workflow άλλωστε), το οποίο όμως αν πρωτοξεκινάς με DVCS ανεβάζει και άλλο τον όγκο αυτών που πρέπει να μάθεις.

 

ΥΓ1. Για το git-flow υπάρχει επίσης και τo gitflow extension

ΥΓ2. Για το mercurial υπάρχει και το αντίστοιχο hg-flow extension

  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ευχαριστώ και τους δύο.

 

@imitheos, όταν κάνω git clone git://...., κατά το push μου λέει:

  You can't push to git://github.com/geomagas/2048cc.git
  Use https://github.com/geomagas/2048cc.git

Αν πάλι προσπαθήσω git clone https://..... δεν δουλεύει. Χάνω κάτι;

 

EDIT: Παράλειψη. Για το https παίρνω το url όπως το δίνει το github (κάτω δεξιά "HTTPS Clone url") και "δεν δουλεύει" σημαίνει ότι βγάζει "Cloning into ......." και μένει εκεί επ' άπειρον.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ευχαριστώ και τους δύο.

 

@imitheos, όταν κάνω git clone git://...., κατά το push μου λέει:

  You can't push to git://github.com/geomagas/2048cc.git
  Use https://github.com/geomagas/2048cc.git
Αν πάλι προσπαθήσω git clone https://..... δεν δουλεύει. Χάνω κάτι;

 

Από συνήθεια κάνω πάντα clone με git:// γιατί χρησιμοποιώ ssh κλειδί για το push οπότε σε μπέρδεψα. Για να μην κάνεις ξανά clone με https:// άλλαξε το url.

% git remote -v set-url --push https://github.com/geomagas/2048cc.git
Μετά όταν κάνεις push θα σου ζητάει username και pass.

 

Αν για url δηλώσεις https://[email protected]/κτλ τότε θα σου ζητάει μόνο pass κάθε φορά και αν δηλώσεις https://geomagas:[email protected]/κτλ δεν θα σου ζητάει ούτε pass.

 

Το σκέτο set-url αλλάζει γενικά την url ενώ έτσι που στο έχω δώσει με την --push παράμετρο θα αλλάξει μόνο την push url οπότε θα κάνεις fetch μέσω git που είναι πιο γρήγορο / έξυπνο.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...