Recently I read the
topic of the user
nsinreal , who proposed the implementation of the sapper on batch file. Since I recently started to get acquainted with GWT and in general with Java, I decided to write my sapper with blackjack and other things :) Along the way, I’ll tell you about the implementation and problems I encountered.
So,
yaminesweeper.appspot.com . I made it on the weekend, so do not beat the simple view and some bugs, which I will write about below. Sources can be found here:
http://github.com/wargoth/yaminesweeper .
Key features:
- ability to check mines (right mouse button)
- ability to quickly open fields (middle mouse button)
- change field parameters (width, height, number of min)
- save the time of solving the field and watch the total user rating (you need to log in through your Google account).
Of the bugs, I note:
- total curvature in IE (solved)
- curvature in opera (problems with redefining behavior when pressing the middle and right mouse buttons)
It is planned to do:
- quick opening of the fields by simultaneously pressing the left and right mouse buttons (now only the middle key)
- optimize algorithms (now it’s still not as fast as we would like)
- improve appearance
The client part is written in GWT, the server part is for calculating statistics, in AppEngine. I was very inspired by these two technologies, as, in other matters, and the speed of development on them. Below I will focus on the main points in architecture planning and implementation. I am pleased to accept criticism on the part of the code. I did not try to create a super-duper beautiful interface, and that's not the point. As part of the code, I am interested in the opinion of experienced programmers, since I repeat, I recently started programming in Java.
')
The advantages of GWT have been written many times, but I will note once again what I discovered for myself:
- type control. You can forget about the problems with types in javascript that often arise when developing in javascript. And in general, after dynamically typed languages ​​(I am a PHP programmer), I am delighted with the strong typing of Java and the possibilities it provides
- cross-browser compatibility. Of course, there are some inconsistencies that I encountered, but you can not think about many things. GWT will compile 6 scripts that will be loaded only for its browser type - optimized and including only the code that will be executed
- code debugging in your favorite IDE
Well, enough water, let's get to the most important ...
Algorithms
To implement the game, you need to write out for yourself the rules of the game, as well as imagine how everything will work.
Minefield build a random location min. What should happen when a player clicks on the field?
- If this field is mine, then we are undermined :)
- If not, then count how many mines are around.
- If there are mines around, you need to display their number.
- If there are no mines around, then this field is considered empty and it is necessary to open all fields that are around (recursively go through points 1-4)
The player opens several fields, places flags that indicate places where it is better not to attack. It is necessary to give him the opportunity to quickly reveal the fields around the field, which correctly displays the number of flags around. This, again, is implemented by a round-robin round of the fields and opening them if they are not marked with a flag.
If the number of open fields is equal to the total number of fields minus the number of mines, the player is considered lucky and wins.
Architecture
The application consists of 4 main parts:
- playing field Minefield
- including the collection
- Mined Field
- as well as the collection bypass algorithms - CollectionIterator (bypassing the entire playing field) and AroundFieldIterator (bypassing the mined field)
The rest of the auxiliary classes are the timer, the options dialog, and more.
Because I have a problem with naming in Russian (it’s easy to get confused in the “playing fields” and “mined fields”), then I’ll rather use the class names at once - Minefield and Field.
Minefield provides a Grid widget in which all Field fields will be located. It encapsulates the logic associated with the initialization of the game.
Collection - contains a collection of objects. The collection traversal logic is encapsulated in the CollectionIterator and AroundFieldIterator iterators.
One of the requirements for the application was the ease of changing the algorithms so that they could be replaced, optimized and reworked independently of the rest of the application. For this, the iterators CollectionIterator and AroundFieldIterator were created. The first passes the playing field from start to finish, returning the corresponding Field position, and the second passes around this Field and returns all adjacent fields.
Field encapsulates the logic of a mined field behavior. It has states, such as “flagged”, “open”, reacts to user events — it opens, explodes, or opens neighboring fields. It also provides widgets corresponding to the state. For closed - the button, for open - a label with the number of min and so on.
Implementation
I will run through the main points in the implementation of the game in order to demonstrate how easy it is to program in GWT.
Minefield initialization starts with a random distribution of mines on it:
private void populateMines() {
for ( int i = 0; i < minesNum; i++) {
int col, row;
do {
col = ( int ) Math .round( Math .random() * ( double ) (cols - 1));
row = ( int ) Math .round( Math .random() * ( double ) (rows - 1));
} while (collection. get (col, row) != null );
collection. set (col, row, new Field( this , col, row, Field.MINE));
}
}
* This source code was highlighted with Source Code Highlighter .
Then we build the field itself, filling it with widgets that provide the Field fields:
private void initWidget() {
grid = getWidget();
grid.clear();
grid.resize(rows, cols);
for (CollectionIterator iterator = collection.iterator(); iterator
.hasNext();) {
Field field = iterator.next();
grid.setWidget(iterator.getRow(), iterator.getCol(), field
.getWidget());
}
}
* This source code was highlighted with Source Code Highlighter .
When the user clicks the Field button, the Field.open () event is raised. If this field is a mine, then we explode. To do this, we delegate this event to the parent Minefield, so that it goes through all the mines and "blew up" them. If the field is not a mine, then we count the number of mines around:
private void calculateMinesNum() {
for (AroundFieldIterator iterator = parent.getCollection()
.aroundFieldIterator(col, row); iterator.hasNext();) {
Field field = iterator.next();
if (field.isMine()) {
incrementMinesNum();
}
}
}
* This source code was highlighted with Source Code Highlighter .
Thus, using the same collection iterators, you can easily implement all the logic of the game.
Rake
I would like to tell you about those rakes that I stepped on. Maybe it will help someone. Some problems I still have not solved. So I will be very grateful if someone tells you how they are solved.
Redefining the right mouse click
Not all browsers work the same way. Moreover, the behavior in debug mode (the so-called hosted mode) and in a normal browser (web mode) are also different. The following code puts the event on the right-click on the button:
private Widget getButtonWidget() {
button = new Button();
button.addMouseDownHandler( new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent event ) {
switch ( event .getNativeButton()) {
case NativeEvent.BUTTON_RIGHT:
toggleFlag();
break ;
}
}
});
return button;
}
* This source code was highlighted with Source Code Highlighter .
In debug mode, everything works fine, but in conventional browsers, the context menu drops out, which you need to get rid of by redefining the onBrowserEvent method either at the button itself or at the parent widget, which I did in the Minefield class:
public Grid getWidget() {
if (grid == null ) {
grid = new Grid() {
@Override
public void onBrowserEvent(Event event ) {
event .stopPropagation();
event .preventDefault();
}
};
grid.sinkEvents(Event.ONCONTEXTMENU | Event.ONMOUSEDOWN | Event.ONDBLCLICK);
grid.addStyleName( "grid" );
}
return grid;
}
* This source code was highlighted with Source Code Highlighter .
The sinkEvents method says which events should be intercepted, and event.stopPropagation () and event.preventDefault () - prohibit them from further propagation and execution. In theory.
In practice, it works well in Chrome, FF, and in the opera it does not. Moreover, in the opera, by default, the ability to control the right mouse click is turned off, and the behavior when pressing the middle key over text is generally mysterious to me. I will still work on this.
Interception of events with the middle key is implemented in the same way as the right one.
Reinitialize Widgets
Maybe it was just a discovery for me, but when I implemented the re-initialization of the application in order to implement a “new game” or change of options, it was a surprise for me why the following code behaves differently than I thought:
private void initWidget() {
grid = new Grid();
grid.resize(rows, cols);
...
}
* This source code was highlighted with Source Code Highlighter .
I assumed that when the initWidget method was called again, the old field had to be destroyed, and a new one with new parameters was created. For a typical application, this would be fair, but one should not forget that this is just a “reflection” of the DOM object in Java (more precisely, on the contrary). Those. a new object was actually created in Java, but the old object from DOM was not deleted or replaced. And even old events remain attached to it. Therefore, it is good practice to initialize all widgets either in the class constructor or using separate methods, for example:
public class Minefield {
private Grid grid = new Grid();
...
}
* This source code was highlighted with Source Code Highlighter .
Or so:
public class Minefield {
private Grid grid;
...
public Grid getWidget() {
if (grid == null ) {
grid = new Grid();
....
}
return grid;
}
}
* This source code was highlighted with Source Code Highlighter .
Now, when reinitializing, you will use the same objects “reflected” into DOM objects.
The article was quite extensive, and there are many ideas about what to tell. So when there is a time, I can continue to pro coding in the GWT. And when it's better to deal with AppEngine, then about it. I am pleased to hear comments and suggestions.
Thanks for attention.