1/*
2 * Copyright 2008 Ralf Sch��lke, ralf.schuelke@googlemail.com.
3 * Copyright 2010 Adam Smith <adamd.smith@utoronto.ca>
4 * Copyright 2014 Haiku, Inc. All rights reserved.
5 *
6 * Distributed under the terms of the MIT License.
7 *
8 * Authors:
9 *		Ralf Sch��lke, ralf.schuelke@googlemail.com
10 *		John Scipione, jscipione@gmail.com
11 *		Adam Smith, adamd.smith@utoronto.ca
12 */
13
14
15#include "PairsWindow.h"
16
17#include <Application.h>
18#include <Alert.h>
19#include <Button.h>
20#include <Catalog.h>
21#include <ObjectList.h>
22#include <Menu.h>
23#include <MenuBar.h>
24#include <MenuItem.h>
25#include <MessageRunner.h>
26#include <String.h>
27#include <StringFormat.h>
28#include <TextView.h>
29
30#include "Pairs.h"
31#include "PairsButton.h"
32#include "PairsView.h"
33
34
35#undef B_TRANSLATION_CONTEXT
36#define B_TRANSLATION_CONTEXT "PairsWindow"
37
38
39const uint32 MENU_NEW					= 'MGnw';
40const uint32 MENU_DIFFICULTY			= 'MGdf';
41const uint32 MENU_QUIT					= 'MGqu';
42const uint32 MENU_ICON_SIZE				= 'MSIs';
43
44const uint32 kMsgPairComparing			= 'pcom';
45
46
47//	#pragma mark - PairsWindow
48
49
50PairsWindow::PairsWindow()
51	:
52	BWindow(BRect(0, 0, 0, 0), B_TRANSLATE_SYSTEM_NAME("Pairs"),
53		B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE
54			| B_NOT_RESIZABLE | B_NOT_ZOOMABLE),
55	fPairComparing(NULL),
56	fIsFirstClick(true),
57	fIsPairsActive(true),
58	fPairCardPosition(0),
59	fPairCardTmpPosition(0),
60	fButtonTmpPosition(0),
61	fButtonPosition(0),
62	fButtonClicks(0),
63	fFinishPairs(0),
64	fIconSizeMenu(NULL)
65{
66	_MakeMenuBar();
67	_MakeGameView(4, 4);
68
69	CenterOnScreen();
70}
71
72
73PairsWindow::~PairsWindow()
74{
75	delete fPairComparing;
76}
77
78
79void
80PairsWindow::_MakeMenuBar()
81{
82	fMenuBar = new BMenuBar(BRect(0, 0, 0, 0), "menubar");
83	AddChild(fMenuBar);
84
85	BMenu* gameMenu = new BMenu(B_TRANSLATE("Game"));
86	fMenuBar->AddItem(gameMenu);
87
88	BMenuItem* menuItem;
89
90	BMenu* newMenu = new BMenu(B_TRANSLATE("New"));
91	newMenu->SetRadioMode(true);
92
93	BMessage* difficultyMessage = new BMessage(MENU_DIFFICULTY);
94	difficultyMessage->AddInt32("rows", 4);
95	difficultyMessage->AddInt32("cols", 4);
96	newMenu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Beginner (4x4)"),
97		difficultyMessage));
98	menuItem->SetMarked(true);
99
100	difficultyMessage = new BMessage(MENU_DIFFICULTY);
101	difficultyMessage->AddInt32("rows", 6);
102	difficultyMessage->AddInt32("cols", 6);
103	newMenu->AddItem(new BMenuItem(B_TRANSLATE("Intermediate (6x6)"),
104		difficultyMessage));
105
106	difficultyMessage = new BMessage(MENU_DIFFICULTY);
107	difficultyMessage->AddInt32("rows", 8);
108	difficultyMessage->AddInt32("cols", 8);
109	newMenu->AddItem(new BMenuItem(B_TRANSLATE("Expert (8x8)"),
110		difficultyMessage));
111
112	menuItem = new BMenuItem(newMenu, new BMessage(MENU_NEW));
113	menuItem->SetShortcut('N', B_COMMAND_KEY);
114	gameMenu->AddItem(menuItem);
115
116	gameMenu->AddSeparatorItem();
117
118	gameMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
119		new BMessage(MENU_QUIT), 'Q'));
120
121	fIconSizeMenu = new BMenu(B_TRANSLATE("Size"));
122	fIconSizeMenu->SetRadioMode(true);
123	fMenuBar->AddItem(fIconSizeMenu);
124
125	BMessage* iconSizeMessage = new BMessage(MENU_ICON_SIZE);
126	iconSizeMessage->AddInt32("size", kSmallIconSize);
127	fIconSizeMenu->AddItem(new BMenuItem(
128		B_TRANSLATE("Small"), iconSizeMessage), 0);
129
130	iconSizeMessage = new BMessage(MENU_ICON_SIZE);
131	iconSizeMessage->AddInt32("size", kMediumIconSize);
132	fIconSizeMenu->AddItem(menuItem = new BMenuItem(
133		B_TRANSLATE("Medium"), iconSizeMessage), 1);
134	menuItem->SetMarked(true);
135
136	iconSizeMessage = new BMessage(MENU_ICON_SIZE);
137	iconSizeMessage->AddInt32("size", kLargeIconSize);
138	fIconSizeMenu->AddItem(new BMenuItem(
139		B_TRANSLATE("Large"), iconSizeMessage), 2);
140}
141
142
143void
144PairsWindow::_MakeGameView(uint8 rows, uint8 cols)
145{
146	BRect viewBounds = Bounds();
147	viewBounds.top = fMenuBar->Bounds().Height() + 1;
148
149	uint8 iconSize;
150	BMenuItem* marked = fIconSizeMenu->FindMarked();
151	if (marked != NULL) {
152		switch (fIconSizeMenu->IndexOf(marked)) {
153			case 0:
154				iconSize = kSmallIconSize;
155				break;
156
157			case 2:
158				iconSize = kLargeIconSize;
159				break;
160
161			case 1:
162			default:
163				iconSize = kMediumIconSize;
164		}
165	} else {
166		iconSize = kMediumIconSize;
167		fIconSizeMenu->ItemAt(1)->SetMarked(true);
168	}
169
170	fPairsView = new PairsView(viewBounds, "PairsView", rows, cols, iconSize);
171	AddChild(fPairsView);
172	_ResizeWindow(rows, cols);
173}
174
175
176void
177PairsWindow::NewGame()
178{
179	fButtonClicks = 0;
180	fFinishPairs = 0;
181	fIsFirstClick = true;
182	fPairsView->CreateGameBoard();
183}
184
185
186void
187PairsWindow::SetGameSize(uint8 rows, uint8 cols)
188{
189	RemoveChild(fPairsView);
190	delete fPairsView;
191
192	_MakeGameView(rows, cols);
193	NewGame();
194}
195
196
197void
198PairsWindow::MessageReceived(BMessage* message)
199{
200	switch (message->what) {
201		case MENU_NEW:
202			NewGame();
203			break;
204
205		case MENU_DIFFICULTY:
206		{
207			int32 rows;
208			int32 cols;
209			if (message->FindInt32("rows", &rows) == B_OK
210				&& message->FindInt32("cols", &cols) == B_OK) {
211				SetGameSize(rows, cols);
212			}
213			break;
214		}
215
216		case MENU_ICON_SIZE:
217		{
218			int32 size;
219			if (message->FindInt32("size", &size) == B_OK) {
220				fPairsView->SetIconSize(size);
221				_ResizeWindow(fPairsView->Rows(), fPairsView->Cols());
222			}
223
224			break;
225		}
226
227		case MENU_QUIT:
228			be_app->PostMessage(B_QUIT_REQUESTED);
229			break;
230
231		case kMsgCardButton:
232		{
233			if (!fIsPairsActive)
234				break;
235
236			int32 buttonNumber;
237			if (message->FindInt32("button number", &buttonNumber) != B_OK)
238				break;
239
240			BObjectList<PairsButton>* pairsButtonList
241				= fPairsView->PairsButtonList();
242			if (pairsButtonList == NULL)
243				break;
244
245			// look at what icon is behind a button
246			int32 buttonCount = pairsButtonList->CountItems();
247			for (int32 i = 0; i < buttonCount; i++) {
248				int32 iconPosition = fPairsView->GetIconPosition(i);
249				if (iconPosition == buttonNumber) {
250					fPairCardPosition = i % (buttonCount / 2);
251					fButtonPosition = iconPosition;
252					break;
253				}
254			}
255
256			// gameplay
257			fButtonClicks++;
258			pairsButtonList->ItemAt(fButtonPosition)->Hide();
259
260			if (fIsFirstClick) {
261				fPairCardTmpPosition = fPairCardPosition;
262				fButtonTmpPosition = fButtonPosition;
263			} else {
264				delete fPairComparing;
265					// message of message runner might not have arrived
266					// yet, so it is deleted here to prevent any leaking
267					// just in case
268				BMessage message(kMsgPairComparing);
269				fPairComparing = new BMessageRunner(BMessenger(this),
270					&message,  5 * 100000L, 1);
271				fIsPairsActive = false;
272			}
273
274			fIsFirstClick = !fIsFirstClick;
275			break;
276		}
277
278		case kMsgPairComparing:
279		{
280			BObjectList<PairsButton>* pairsButtonList
281				= fPairsView->PairsButtonList();
282			if (pairsButtonList == NULL)
283				break;
284
285			delete fPairComparing;
286			fPairComparing = NULL;
287
288			fIsPairsActive = true;
289
290			if (fPairCardPosition == fPairCardTmpPosition)
291				fFinishPairs++;
292			else {
293				pairsButtonList->ItemAt(fButtonPosition)->Show();
294				pairsButtonList->ItemAt(fButtonTmpPosition)->Show();
295			}
296
297			// game end and results
298			if (fFinishPairs == pairsButtonList->CountItems() / 2) {
299				BString strAbout = B_TRANSLATE("%app%\n"
300					"\twritten by Ralf Sch��lke\n"
301					"\tCopyright 2008-2010, Haiku Inc.\n"
302					"\n");
303
304				strAbout.ReplaceFirst("%app%",
305					B_TRANSLATE_SYSTEM_NAME("Pairs"));
306
307				// Note: in english the singular form is never used, but other
308				// languages behave differently.
309				static BStringFormat format(B_TRANSLATE(
310					"You completed the game in "
311					"{0, plural, one{# click} other{# clicks}}.\n"));
312				format.Format(strAbout, fButtonClicks);
313
314				BAlert* alert = new BAlert("about",
315					strAbout.String(),
316					B_TRANSLATE("New game"),
317					B_TRANSLATE("Quit game"));
318
319				BTextView* view = alert->TextView();
320				BFont font;
321
322				view->SetStylable(true);
323
324				view->GetFont(&font);
325				font.SetSize(18);
326				font.SetFace(B_BOLD_FACE);
327				view->SetFontAndColor(0,
328					strlen(B_TRANSLATE_SYSTEM_NAME("Pairs")), &font);
329				view->ResizeToPreferred();
330				alert->SetShortcut(0, B_ESCAPE);
331
332				if (alert->Go() == 0)
333					NewGame();
334				else
335					be_app->PostMessage(B_QUIT_REQUESTED);
336			}
337			break;
338		}
339
340		default:
341			BWindow::MessageReceived(message);
342	}
343}
344
345
346//	#pragma mark - PairsWindow private methods
347
348
349void
350PairsWindow::_ResizeWindow(uint8 rows, uint8 cols)
351{
352	int32 iconSize = fPairsView->IconSize();
353	int32 spacing = fPairsView->Spacing();
354
355	ResizeTo((iconSize + spacing) * rows + spacing,
356		(iconSize + spacing) * cols + spacing + fMenuBar->Bounds().Height());
357}
358