
Minesweeper on GWT
Recently I read the topic of the user nsinreal , who proposed the implementation of a sapper on batch files. Since I just recently started getting to know GWT and Java in general, I decided to write my sapper with blackjack and other things :) Along the way, I’ll talk about the implementation and the problems I encountered.
So yaminesweeper.appspot.com . I did it on the weekend, so do not hit for a simple look and some bugs, which I will write about below. You can find the source here: http://github.com/wargoth/yaminesweeper .
Key features:
Of the bugs I note:
Planned to do:
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 well as the speed of development on them. Below I will focus on the highlights in architecture planning and implementation. I will gladly accept criticism regarding the code. I did not try to create a super-duper beautiful interface, and that's not the point. As for the code, I’m interested in the opinion of experienced programmers, as I, again, recently started to program in Java.
The benefits of GWT have been written many times, but I will note again what I discovered:
Well, enough water, I’ll move on to the most important thing ...
To implement the game, you need to write down the rules of the game for yourself, as well as imagine how everything will work.
We build a minefield with a random arrangement of min. What should happen when a player clicks on the field?
The player opens several fields, places flags indicating places where it is better not to step. We need to provide him with the opportunity to quickly expand the fields around the field, which correctly displays the number of flags around. This is again accomplished by circularly traversing the fields and opening them if they are not flagged.
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.
The application consists of 4 main parts:
The remaining auxiliary classes are a timer, an options dialog, and more.
Because I have a problem with naming in Russian (it’s easy to get confused in “playing fields” and “mined fields”), then I’d better use the class names Minefield and Field right away.
Minefield provides a Grid widget in which all Field fields will be located. It encapsulates the logic associated with initializing the game.
Collection - contains a collection of objects. Collection traversal logic is encapsulated in 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 processed independently of the rest of the application. For this, iterators CollectionIterator and AroundFieldIterator were created. The first passes the playing field from beginning to end, returning the corresponding Field position, and the second goes around this Field and returns all neighboring fields.
Field encapsulates the logic of the behavior of a mined field. It has states, such as “flagged”, “open”, responds to user events - it opens, explodes, or opens neighboring fields. It also provides stateful widgets. For closed - a button, for open - a label with the number of mines and more.
I will go over the main points in the implementation of the game in order to demonstrate how easy it is to program in GWT.
Minefield initialization begins with a random distribution of mines on it:
Then we build the field itself, filling it with widgets that provide the Field fields:
When the user clicks on the Field button, the Field.open () event is raised. If this field is a mine, then "explode." To do this, we delegate this event to the parent Minefield so that it will go through all the mines and “blow them up”. If the field is not a mine, then calculate the number of mines around:
Thus, using the same collection iterators, you can easily implement the entire logic of the game.
I would like to talk about the rake that I stepped on. Maybe this will help someone. I still have not solved some problems. So I will be very grateful if someone tells me how they are solved.
Not all browsers work the same. Moreover, the behavior in debugging mode (the so-called hosted mode) and in a regular browser (web mode) also differ. The following code hangs an event on a right-click on a button:
In debug mode, everything works fine, but in normal browsers a context menu appears that you need to get rid of by overriding the onBrowserEvent method either on the button itself or on the parent widget, which I did in the Minefield class:
The sinkEvents method tells which events should be intercepted, and event.stopPropagation () and event.preventDefault () prevent them from further distribution and execution. In theory.
In practice, this works well in Chrome, FF, but not in opera. Moreover, in opera, by default, the ability to control the right mouse click is turned off altogether, and the behavior of pressing the middle key over the text is generally mysterious to me. I will still work on this.
Interception of events by the middle key is implemented similarly to the right one.
Maybe this was just a revelation for me, but when I implemented the initialization of the application in order to implement a “new game” or change options, it was a surprise to me why the following code behaves differently from what I thought:
I assumed that when the initWidget method was called again, the old field had to be destroyed, and the new one with new parameters was created. For a typical application, this would be true, but do not forget that this is just a “reflection” of the DOM object in Java (more precisely, vice versa). Those. indeed, a new object was created in Java, but the old object from the DOM was not deleted or replaced. And on it even the old events remain attached. Therefore, it is good practice to initialize all widgets either in the class constructor or in separate methods, for example like this:
Or so:
Now during reinitialization, you will use the same objects that are “reflected” into DOM objects.
The article turned out to be quite extensive, but there are a lot of ideas about what to tell. So when there is time, I can continue about coding in GWT. And when I’ll deal better with AppEngine, then about him. I am pleased to hear comments and suggestions.
Thanks for attention.
So yaminesweeper.appspot.com . I did it on the weekend, so do not hit for a simple look and some bugs, which I will write about below. You can find the source here: http://github.com/wargoth/yaminesweeper .
Key features:
- the ability to flag mines (with the right mouse button)
- the ability to quickly open fields (middle mouse button)
- change field parameters (width, height, number of minutes)
- save the time of solving the field and watch the general rating of users (you need to log in through your Google account).
Of the bugs I note:
- general curvature in IE (solved)
- curvature in the opera (problems with redefining behavior when pressing the middle and right mouse buttons)
Planned to do:
- quick opening of fields by simultaneously pressing the left and right mouse buttons (now only the middle key)
- optimize algorithms (now it still doesn’t work 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 well as the speed of development on them. Below I will focus on the highlights in architecture planning and implementation. I will gladly accept criticism regarding the code. I did not try to create a super-duper beautiful interface, and that's not the point. As for the code, I’m interested in the opinion of experienced programmers, as I, again, recently started to program in Java.
The benefits of GWT have been written many times, but I will note again what I discovered:
- type control. You can forget about the problems with types in javascript that often arise when developing in javascript. In general, after dynamically typed languages (I'm a PHP programmer), I am delighted with the strict typing of Java and the possibilities that it provides
- cross-browser compatibility. Of course, there are some inconsistencies here that I have encountered, but you can not think about many things. GWT will compile 6 scripts that will be loaded only for its type of browser - optimized and including only the code that will be executed
- code debugging - in your favorite IDE
Well, enough water, I’ll move on to the most important thing ...
Algorithms
To implement the game, you need to write down the rules of the game for yourself, as well as imagine how everything will work.
We build a minefield with a random arrangement of min. What should happen when a player clicks on the field?
- If this field is a mine, then we are undermining :)
- If not, then consider 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 you need to open all the fields that are around (we recursively go through points 1-4)
The player opens several fields, places flags indicating places where it is better not to step. We need to provide him with the opportunity to quickly expand the fields around the field, which correctly displays the number of flags around. This is again accomplished by circularly traversing the fields and opening them if they are not flagged.
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:
- Minefield playing field
- including collection
- mined fields
- as well as collection traversal algorithms - CollectionIterator (bypassing the entire playing field) and AroundFieldIterator (traversing around the minefield)
The remaining auxiliary classes are a timer, an options dialog, and more.
Because I have a problem with naming in Russian (it’s easy to get confused in “playing fields” and “mined fields”), then I’d better use the class names Minefield and Field right away.
Minefield provides a Grid widget in which all Field fields will be located. It encapsulates the logic associated with initializing the game.
Collection - contains a collection of objects. Collection traversal logic is encapsulated in 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 processed independently of the rest of the application. For this, iterators CollectionIterator and AroundFieldIterator were created. The first passes the playing field from beginning to end, returning the corresponding Field position, and the second goes around this Field and returns all neighboring fields.
Field encapsulates the logic of the behavior of a mined field. It has states, such as “flagged”, “open”, responds to user events - it opens, explodes, or opens neighboring fields. It also provides stateful widgets. For closed - a button, for open - a label with the number of mines and more.
Implementation
I will go over the main points in the implementation of the game in order to demonstrate how easy it is to program in GWT.
Minefield initialization begins 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 on the Field button, the Field.open () event is raised. If this field is a mine, then "explode." To do this, we delegate this event to the parent Minefield so that it will go through all the mines and “blow them up”. If the field is not a mine, then calculate 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 the entire logic of the game.
Rake
I would like to talk about the rake that I stepped on. Maybe this will help someone. I still have not solved some problems. So I will be very grateful if someone tells me how they are solved.
Right click override
Not all browsers work the same. Moreover, the behavior in debugging mode (the so-called hosted mode) and in a regular browser (web mode) also differ. The following code hangs an event on a right-click on a 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 normal browsers a context menu appears that you need to get rid of by overriding the onBrowserEvent method either on the button itself or on 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 tells which events should be intercepted, and event.stopPropagation () and event.preventDefault () prevent them from further distribution and execution. In theory.
In practice, this works well in Chrome, FF, but not in opera. Moreover, in opera, by default, the ability to control the right mouse click is turned off altogether, and the behavior of pressing the middle key over the text is generally mysterious to me. I will still work on this.
Interception of events by the middle key is implemented similarly to the right one.
Reinitialize widgets
Maybe this was just a revelation for me, but when I implemented the initialization of the application in order to implement a “new game” or change options, it was a surprise to me why the following code behaves differently from what 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 the new one with new parameters was created. For a typical application, this would be true, but do not forget that this is just a “reflection” of the DOM object in Java (more precisely, vice versa). Those. indeed, a new object was created in Java, but the old object from the DOM was not deleted or replaced. And on it even the old events remain attached. Therefore, it is good practice to initialize all widgets either in the class constructor or in separate methods, for example like this:
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 during reinitialization, you will use the same objects that are “reflected” into DOM objects.
The article turned out to be quite extensive, but there are a lot of ideas about what to tell. So when there is time, I can continue about coding in GWT. And when I’ll deal better with AppEngine, then about him. I am pleased to hear comments and suggestions.
Thanks for attention.