Дипломная (вкр): Розробка ігрової програми для мобільних пристроїв з сенсорним екраном

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

5.3 Отримані результати


Програма розроблялася в інтегрованому сереловищі NetBeans IDE 7.0. [6]

В одержаній реалізації, гра матиме такий вигляд.


Скріншот зроблено у програмі MicroEmulator 2.0.4.62.

Дана версія розрахована на дисплеї з роздільною здатністю 640х360 пікселів. Програма тестувалась на телефоні Sony Ericsson U1, де успішно працювала.

Висновки


В ході виконання роботи було запропоновано реалізацію гри, для мобільних пристроїв з сенсорним екраном на базі обраної платформи Java Micro Edition. Отримано досвід роботи з інструментами розробки програм для мобільних пристроїв, зокрема в середовищі NetBeans IDE 7.0.

Важливими особливостями такого програмування є розміри графічних елементів інтерфейсу - адже це значним чином впливає на зручність користування продуктом. При цьому потрібно враховувати роздільну здатність екрану і точність сенсору, а також користуватиметься людина стилусом, чи пальцем. Можливий варіант виходу з ситуації - розробка різних версій продукту для пристроїв з кардинально різними характеристиками. Завдяки універсальності платформи Java ME цей процес зводиться до заміни графічних файлів і констант, а не повного переписування коду.

Варто зазначити, що розробка ігрових програм для портативних пристроїв - доволі перспективна сфера, адже кількість мобільних телефонів з сенсорним екраном постійно зростає, а людству ніколи не набридне грати в цікаві ігри, що зумовлює попит на відповідне програмне забезпечення.

Список використаних джерел


1.       Горнаков С.Г. "Програмирование мобильних телефонов на Java 2 Micro Edition" - М.: ДМК Пресс, 2004

2.       David Fox, Roman Verhosek "Micro Java Game Development" - Addison Wesley, 2002

3.       MIDP Programming with J2ME: http://www.developer.com/java/j2me/article. php/10934_1561591_8/MIDP-Programming-with-J2ME. htm <http://www.developer.com/java/j2me/article.php/10934_1561591_8/MIDP-Programming-with-J2ME.htm>

.        Мухин И.А. Сенсорные экраны - решение проблем (10 технологий). "BROADCASTING Телевидение и радиовещение": 1 часть - № 3 (55) май 2006, с.50-52; 2 часть - № 4 (56) июнь-июль 2006, с.40-41; 3 часть - № 7 (59) ноябрь 2006, с.64-66.

.        Офіційний сайт Java Platform, Micro Edition: <http://www.oracle.com/technetwork/java/javame/index.html>

.        Офіційний сайт середовища розробки NetBeans IDE: <http://netbeans.org/>

Додатки


Додаток: Код програми

// GameField. java

import java. io. IOException;javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. TiledLayer;class GameField {final int GRASS = 1;final int ROAD = 2;final int HOME = 3;[] [] field;tl;GameField (int [] [] field) {. field = field;(field. length, field [0]. length,100,0);

}

// генерує поле по замовчуваннюGameField () {n = 10;m = 15;= new int [n] [m];(int i=0; i<n; i++)(int j=0; j<m; j++)[i] [j] =GRASS;[0] [0] = HOME;[0] [1] = ROAD;[0] [2] = ROAD;[1] [2] = ROAD;[2] [2] = ROAD;[3] [2] = ROAD;[4] [2] = ROAD;[4] [3] = ROAD;[1] [3] = ROAD;[1] [4] = ROAD;[2] [4] = ROAD;[3] [4] = ROAD;[4] [4] = ROAD;[4] [5] = ROAD;[4] [6] = ROAD;[4] [7] = ROAD;[5] [7] = ROAD;[6] [7] = ROAD;[7] [7] = ROAD;[8] [7] = ROAD;[8] [8] = ROAD;[8] [9] = ROAD;[8] [10] = ROAD;[8] [11] = ROAD;[8] [12] = ROAD;[8] [13] = ROAD;[9] [13] = ROAD;[9] [14] = ROAD;(n, m,100,0);

}void createLayer (int n, int m, int x0, int y0) {{= new TiledLayer (m, n, Image. createImage ("/field. png"), 36, 36);

} catch (IOException ex) {. printStackTrace ();

}. setPosition (x0, y0);(int i=0; i<n; i++)(int j=0; j<m; j++). setCell (j, i, field [i] [j]);

}void paint (Graphics g) {. paint (g);

}point getCellij (int x, int y) {- = tl. getX ();- = tl. getY ();new point (x / tl. getCellWidth (),y /tl. getCellHeight ());

}TrekBuilder genTrekBuilder () {tb = new TrekBuilder (36,36,tl. getX (),tl. getY ());. buildTreks (field, 9, 14);tb;

}point getCellXY (point p) {new point (p. x*tl. getCellWidth () +tl. getX (), p. y*tl. getCellHeight () +tl. getY ());

}boolean isCellAvailable (int i, int j) {( (j >=0 && j<field. length && i>=0 && i<field [j]. length) && field [j] [i] == GRASS);

}void checkCell (point p) {= getCellij (p. x, p. y);s = (field [p. x] [p. y] == GRASS)?"Grass": "";= (field [p. x] [p. y] == ROAD)?"road": s;= (field [p. x] [p. y] == HOME)?"home": s;. out. println (s + " ("+p. x + " " + p. y + ")");

}

}

// Menu. java

import java. io. IOException;javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;class Menu {bgimg,changeimg,sellimg;pc;sell = false;Menu (TDCanvas pc) {. pc = pc;{= Image. createImage ("/menu. png");= Image. createImage ("/change. png");= Image. createImage ("/sell. png");

} catch (IOException ex) {. printStackTrace ();

}

}void changeState (boolean sell) {. sell = sell;

}void paint (Graphics g) {. drawImage (bgimg, 0, 0, Graphics. TOP|Graphics. LEFT);(sell) g. drawImage (sellimg, 7, 188, Graphics. TOP|Graphics. LEFT);g. drawImage (changeimg, 7, 188, Graphics. TOP|Graphics. LEFT);

}void pressed (int x, int y) {(x>7 && x<83 && y>188 && y<264) {(sell) {. removeCur ();= false;

}pc. changeCurTower ();

}

}

}

// MenuCanvas. java

import javax. microedition. lcdui. Canvas;javax. microedition. lcdui. Display;javax. microedition. lcdui. Graphics;class MenuCanvas extends Canvas{cenX = 0;cenY = 0;selected;[] options = {"Campaign","Survival","Exit"};tdg;MenuCanvas (TDGame tdg) {();. tdg = tdg;(true);= getWidth () /2;= getHeight () /2;

}void pointerPressed (int x, int y) {(x > cenX)(y >cenY) select ();cancel ();(y >cenY) down ();up ();

}void paint (Graphics g) {. setGrayScale (255);. setColor (10, 200, 100);. fillRect (0, 0, getWidth (), getHeight ());. setGrayScale (0);(int i=0; i<options. length; i++) {(i == selected) g. setColor (200, 20,10);. drawString (options [i], cenX - cenX/2, i*15,Graphics. TOP|Graphics. LEFT);. setColor (0,0,0);

}. drawString ("ok", 5+ cenX + cenX/2, 5+ cenY + cenY/2,Graphics. TOP|Graphics. LEFT);. drawString ("cancel", 5+ cenX + cenX/2,cenY/2 - 5,Graphics. TOP|Graphics. LEFT);. drawString ("up", 2, cenY/2 - 5, Graphics. TOP|Graphics. LEFT);. drawString ("down", 2, 5+ cenY + cenY/2, Graphics. TOP|Graphics. LEFT);

}void up () {= (selected>0)? selected-1: options. length-1;();

}void down () {= (selected<options. length-1)? selected+1: 0;();

}void select () {(selected) {0:. getDisplay (tdg). setCurrent (new TDCanvas (tdg, TDCanvas. CAMPAIGN));;1:. getDisplay (tdg). setCurrent (new TDCanvas (tdg, TDCanvas. SURVIVAL));;2:. destroyApp (false);. notifyDestroyed ();;

}

}void cancel () {(tdg. canContinue ()) tdg. continueGame ();

}

}

// Monster. java

import javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. Sprite;class Monster {sp;speed;maxspeed;minspeed = 1;hp;dmg;gold;tr;Monster (int speed, int hp, int dmg, int gold, Image img) {. speed = speed;. hp = hp;. dmg = dmg;. gold = gold;= new Sprite (img,36,36);

}void SetXY (int x, int y) {. setPosition (x, y);

}void SetXY (point p) {. setPosition (p. x, p. y);

}void paint (Graphics g) {. paint (g);(sp. getFrame ()! = 0) sp. setFrame (0);sp. setFrame (1);

}void setTrek (Trek tr) {. tr = tr;(tr. getNext ());

}boolean move () {();res = moveWith (speed);res;

}boolean moveWith (int step) {(tr. isEmty ()) {

// видалити монстра= 0;false;

}{pto = tr. getNext ();cur = new point (sp. getX (),sp. getY ());(cur. equals (pto)) {. delNext ();();

}{step1 = 0;(pto. x > cur. x) {= changeStep (cur. x,pto. x,step);. x+=step1;= step1;

}(pto. x < cur. x) {= changeStep (cur. x,pto. x,step);. x-=step;= step1;

}(pto. y > cur. y) {= changeStep (cur. y,pto. y,step);. y+=step;= step1;

}(pto. y < cur. y) {= changeStep (cur. y,pto. y,step);. y-=step;= step1;

}(cur);(step! =0) moveWith (step);

}true;

}

}int changeStep (int f, int t, int step) {dist = Math. abs (f-t);(dist<step) return dist;return step;

}point getCentPosition () {new point (sp. getX () +sp. getWidth () /2,sp. getY () +sp. getHeight () /2);

}point getPosition () {new point (sp. getX (),sp. getY ());

}void hit (int d) {- = d;(! isAlive ()) {. st. removeAllElements ();

}. setFrame (2);

}void hitSpeed (int d) {- = d;(speed < minspeed) speed = minspeed;

}void Speed () {(speed < maxspeed) speed += 1;

}boolean isAlive () {(hp>0);

}int getHp () {hp;

}void setHp (int hp) {. hp = hp;

}

}

// MonsterFactory. java

public interface MonsterFactory {Monster genNext ();

}

// Monsters. java

import java. util. Vector;javax. microedition. lcdui. Graphics;class Monsters {mar = new Vector ();pc;Monsters (TDCanvas pc) {. pc = pc;

}void add (Monster m) {. addElement (m);

}void move () {(int i=0; i<mar. size (); i++) {m = ( (Monster) mar. elementAt (i));(m. isAlive ()) m. move ();{. removeElementAt (i);p = m. getPosition ();(p. x == 100 && p. y == 0) pc. hp - = m. dmg;pc. gold += m. gold;

}

}

}Monster [] getArray () {[] oar = new Monster [mar. size ()];. copyInto (oar);oar;

}void paint (Graphics g) {(int i=0; i<mar. size (); i++) {

( (Monster) mar. elementAt (i)). paint (g);

}

}

}

// SimpleFactory. java

import java. util. Random;javax. microedition. lcdui. Image;class SimpleFactory implements MonsterFactory{tb;img;start;SimpleFactory (TrekBuilder tb, Image img) {. tb = tb;. img = img;

}Monster genNext () {r = new Random ();speed = r. nextInt (8) +2;m = new Monster (speed, 30, 7, 3, img);. setTrek (tb. randTrek ());m;

}

}

// SimpleTower. java

import javax. microedition. lcdui. Image;class SimpleTower extends Tower{SimpleTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"damages\n one");

}boolean attack (Monster [] m) {. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hit (dmg);. setFrame (1);true;

}false;

}void upgrade () {(xp % 20 == 0) dmg ++;(xp % 30 == 0) range ++;

}

}

// SpeedTower. java

import javax. microedition. lcdui. Image;class SpeedTower extends Tower{SpeedTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"damages\n speed");

}boolean attack (Monster [] m) {. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hitSpeed (dmg);. setFrame (1);true;

}

}false;

}void upgrade () {(xp % 20 == 0 && cooldown>0) cooldown--;(xp % 30 == 0) range++;

}

}

// SplashTower. java

import javax. microedition. lcdui. Image;class SplashTower extends Tower{SplashTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"splash\n damage");

}boolean attack (Monster [] m) {hit = false;. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hit (dmg);. setFrame (1);= true;

}

}hit;

}void upgrade () {(xp % 20 == 0 && cooldown>0) cooldown--;(xp % 30 == 0) dmg++;

}

}

// TDGame. java

import java. util. Random;javax. microedition. midlet. *;javax. microedition. lcdui. *;javax. microedition. lcdui. game. GameCanvas;class TDGame extends MIDlet

{Display display;void startApp () {= Display. getDisplay (this);. setCurrent (new MenuCanvas (this));

}void pauseApp () {}void destroyApp (boolean forced) {}TDCanvas PausedGame = null;void setToPause (TDCanvas tdc) {= tdc;

}boolean canContinue () {(PausedGame! = null);

}void continueGame () {. getDisplay (this). setCurrent (PausedGame);. continueGame ();= null;

}

}TDCanvas extends GameCanvas implements Runnable{img, im1, im2, im3,mimg;t;gf = new GameField ();tb;tws;mf;ms;static int hp = 100;pe;m = new Menu (this);startTime = System. currentTimeMillis ();gold = 100;static final int SURVIVAL = 0;static final int CAMPAIGN = 1;TDCanvas (TDGame pe, int mode) {(true);(true);. pe = pe;{= Image. createImage ("/monstr_fastt. png");= Image. createImage ("/tower_simple. png");= Image. createImage ("/tower_speed. png");= Image. createImage ("/tower_splash. png");

} catch (Exception ex) {. out. println (ex. getMessage ());

}= gf. genTrekBuilder ();= new SimpleFactory (tb, img);= new Towers ();= new Monsters (this);. add (mf. genNext ());= new Thread (this);. start ();

}curTower = - 1;void removeCur () {+= tws. remove (curTower);

}void pointerPressed (int x, int y) {(x>100) {= tws. check (new point (x,y));(curTower == - 1) {. moveNew (x, y);. changeState (false);. changeState (false);

}{. changeState (true);. changeState (true);

}

}{(y<90) {= true;. setToPause (this);. getDisplay (pe). setCurrent (new MenuCanvas (pe));

}. pressed (x, y);

}

}simple = 0;void changeCurTower () {++;(simple > 2) simple =0;(simple);

}void setCurTower (int simple) {(simple == 0) tws. tryNew (new SimpleTower (0, 7, 36, 10, 5, im1));if (simple == 1) tws. tryNew (new SpeedTower (0, 2, 36, 20, 5, im2));tws. tryNew (new SplashTower (0, 4, 36, 15, 10, im3));

}void pointerReleased (int x, int y) {(! m. isSell ()) {res = tws. put (gf,gold);(res! =-1) {= res;(simple);

}

}

}void pointerDragged (int x, int y) {. moveNew (x, y);

}void painting (Graphics g) {. setGrayScale (255);. fillRect (0, 0, getWidth (), getHeight ());. setGrayScale (0);. drawRect (0, 0, getWidth (), getHeight ());. paint (g);. paint (g);. paint (g);. paint (g);(m. isSell ()) tws. paintInfo (g, 7,101, curTower);c = g. getColor ();. setColor (0, 230, 25);. drawString (" hp: "+ hp, 6, 10,Graphics. TOP|Graphics. LEFT);. setColor (255, 245, 48);. drawString ("Gold: "+ gold, 6, 25,Graphics. TOP|Graphics. LEFT);. setColor (255, 10,10);. drawString ("Mons: "+ 25, 6, 40,Graphics. TOP|Graphics. LEFT);. setColor (10, 10, 210);tm = (int) ( (System. currentTimeMillis () - startTime) /1000);min = (tm/60) %60;sec = tm%60;. drawString ("Time: "+ min+": "+sec, 5, 55,Graphics. TOP|Graphics. LEFT);. setColor (c);();

}void continueGame () {= false;

}boolean pause = false;void run () {k = 0;g = getGraphics ();addgoldtime = 0;(true) {(! pause) {add = (int) (System. currentTimeMillis () /1000);(add%2 == 0 && add! = addgoldtime) {= add;++;[] mar = ms. getArray ();. act (mar);. move ();++;r = new Random ();(k % (r. nextInt (5) +5) == 0) {= 0;. add (mf. genNext ());

}(g);

}

}{. sleep (80);

} catch (InterruptedException ex) {. printStackTrace ();

}(hp <= 0) break;

}. setColor (255, 0, 120);. drawString ("G A M E O V E R", 30, 30,Graphics. TOP|Graphics. LEFT);();{. sleep (200);

} catch (InterruptedException ex) {. printStackTrace ();

}. destroyApp (false);. notifyDestroyed ();

}

}

// Tower. java

import javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. Sprite;abstract class Tower {public boolean attack (Monster [] m);protected void upgrade ();sp;xp;dmg;range;gold;cooldown;lastactiontime = 0;info;img;Tower (int xp, int dmg, int range, int gold, int cooldown, Image img,String info) {. xp = xp;. dmg = dmg;. range = range;. gold = gold;. cooldown = cooldown;. img = Image. createImage (img, 0, 0, 36, 36, Sprite. TRANS_NONE);= new Sprite (img, 36, 36);. info = info;

}void setXY (int x, int y) {. setPosition (x, y);

}void paint (Graphics g) {. paint (g);

}void paintInfo (Graphics g, int x, int y) {. drawImage (img, x, y, Graphics. TOP|Graphics. LEFT);(g);tp = getCentPosition ();color = g. getColor ();. setColor (255, 0, 0);. drawRect (tp. x - range, tp. y - range, range*2, range*2);. drawString ("Dmg: \n"+ dmg, x+37, y, Graphics. TOP|Graphics. LEFT);. setColor (0, 30, 200);. drawString ("Range: "+ range, x, y+36, Graphics. TOP|Graphics. LEFT);. setColor (255, 245, 48);. drawString ("Gold: "+ gold, x, y+51, Graphics. TOP|Graphics. LEFT);. setColor (200, 200, 200);. drawString ("Cldn: "+ cooldown, x, y+66, Graphics. TOP|Graphics. LEFT);. setColor (0, 0, 0);. drawString (info, x-1, y-28, Graphics. TOP|Graphics. LEFT);. setColor (color);

}boolean isCooledDown () {time = System. currentTimeMillis ();(time - lastactiontime > cooldown*100);

}boolean isInRange (Monster m) {p = m. getCentPosition ();tp = getCentPosition ();( (p. x <= tp. x + range && p. x >= tp. x - range) &&

(p. y <= tp. y + range) && (p. y >= tp. y - range));

}void act (Monster [] m) {(isCooledDown ()) {(attack (m)) {++;();= (xp > 1000)? xp%1000: xp;

}= System. currentTimeMillis ();

}

}point getCentPosition () {new point (sp. getX () +sp. getWidth () /2,sp. getY () +sp. getHeight () /2);

}point getPosition () {new point (sp. getX (),sp. getY ());

}int getPrice () {gold;

}

// Towers. java

import javax. microedition. lcdui. Graphics;class Towers {[] arr;ntw = null;sell;Towers () {= new Tower [148];

}void act (Monster [] mar) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {[i]. act (mar);

}

}

}void tryNew (Tower ntw) {. ntw = ntw;. setXY (-100, - 100);

}void cancelNew () {= null;

}void moveNew (int x, int y) {(ntw! = null) ntw. setXY (x, y);

}void paint (Graphics g) {(int i=0; i<arr. length; i++) {(arr [i]! = null) arr [i]. paint (g);

}(! sell && ntw! = null) ntw. paintInfo (g, 7,101);

}void paintInfo (Graphics g, int x, int y, int ind) {(arr [ind]! = null) arr [ind]. paintInfo (g,x,y);

}int put (GameField gf, int gold) {(ntw! = null) {(gold < ntw. getPrice ()) return - 1;p = ntw. getCentPosition ();= gf. getCellij (p. x, p. y);(! gf. isCellAvailable (p. x, p. y)) return - 1;= gf. getCellXY (p);(! isFree (p)) return - 1;. setXY (p. x, p. y);(int i=0; i<arr. length; i++) {(arr [i] == null) {[i] = ntw;();gold - arr [i]. gold;

}

}

}- 1;

}boolean isFree (point p) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {(p. equals (arr [i]. getPosition ())) return false;

}

}true;

}int check (point p) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {cur = arr [i]. getPosition ();(p. x > cur. x && p. x < cur. x + arr [i]. sp. getWidth () &&. y > cur. y && p. y < cur. y + arr [i]. sp. getHeight ()) return i;

}

}- 1;

}int remove (int i) {gold = arr [i]. gold;[i] =null;= false;gold;

}void changeState (boolean sell) {. sell = sell;

}

}

// Trek. java

import java. util. Stack;class Trek {st;Trek (Stack st) {. st = st;

}point getNext () {(point) st. peek ();

}boolean isEmty () {st. isEmpty ();

}void delNext () {. pop ();

}Trek copy () {st1 = new Stack ();. setSize (st. size ());(int i=0; i<st. size (); i++). setElementAt (st. elementAt (i), i);new Trek (st1);

}

}

// TrekBuilder. java

import java. util. Random;java. util. Stack;java. util. Vector;class TrekBuilder {[] treks = null;[] [] map = null;h,w,x0,y0;TrekBuilder (int h, int w, int x0, int y0) {. h = h;. w = w;. x0 = x0;. y0 = y0;

}Stack convertToXY (Stack st) {(int i=0; i<st. size (); i++) {x = ( (point) st. elementAt (i)). x;y = ( (point) st. elementAt (i)). y;

( (point) st. elementAt (i)). x = y*w + x0;

( (point) st. elementAt (i)). y = x*h + y0;

}. out. println ();st;

}boolean buildTreks (int [] [] map, int si, int sj) {. map = map;from = new Stack ();. push (new point (si, sj));v = findTrek (from);(v. isEmpty ()) return false;= new Trek [v. size ()];(int i =0; i<v. size (); i++) {s = (Stack) v. elementAt (i);. addElement (new point (si,sj));[i] = new Trek (convertToXY (s));

}true;

}boolean ok (int i, int j) {(map [i] [j] == GameField. ROAD);

}boolean finish (int i, int j) {(map [i] [j] == GameField. HOME);

}

// повертає вектор стеків шляху (шлях в клітинках, а не координатах)Vector findTrek (Stack from) {ret = new Vector ();[] neib = new point [4];f = (point) from. peek ();[0] = new point (f. x,f. y-1);[1] = new point (f. x,f. y+1);[2] = new point (f. x-1,f. y);[3] = new point (f. x+1,f. y);(int i=0; i<4; i++) {(neib [i]. x < map. length && neib [i]. y < map [0]. length && neib [i]. x>=0 && neib [i]. y>=0) {(finish (neib [i]. x,neib [i]. y)) {st = new Stack ();. push (neib [i]. copy ());. addElement (st);

}(ok (neib [i]. x,neib [i]. y) &&! from. contains (neib [i])) {. push (neib [i]);v = findTrek (from);. pop ();(int j = 0; j<v. size (); j++) {cur = (Stack) v. elementAt (j);. push (neib [i]. copy ());. addElement (cur);

}

}

}

}ret;

}Trek randTrek () {(treks == null) return null;{r = new Random ();i = r. nextInt (treks. length);treks [i]. copy ();

}

}Trek [] getTreks () {treks;

}

}

// point. java

public class point {int x;int y;point (int x, int y) {. x = x;. y = y;

}boolean equals (Object obj) {(obj instanceof point) {p = (point) obj;(x == p. x && y == p. y);

}super. equals (obj);point copy () {new point (x,y);

}

}

Источник: https://www.bibliofond.ru/detail.aspx?id=774913