Журнал LinuxFormat - перейти на главную

LXF163: Изучаем языки

Материал из Linuxformat
Перейти к: навигация, поиск

Про­грам­ми­ро­ва­ние: соз­да­ем класс­ную эму­ля­цию звезд­но­го не­ба на трех раз­ных язы­ках

Содержание

Изу­ча­ем язы­ки

Майк Сон­дерс вих­рем про­мчит вас по трем язы­кам про­грам­ми­ро­ва­ния и ин­ст­ру­мен­та­ри­ям, соз­да­вая на ка­ж­дом эф­фект не­ба в звезд­ах.

(thumbnail)
Наш эксперт. Под­на­то­рев в язы­ках от AREXX до ас­семб­ле­ра Z80, Майк Сон­дерс бо­лее все­го чув­ст­ву­ет се­бя до­ма с ко­пи­ей ас­семб­ле­ра NASM и ис­ход­ным ко­дом сво­ей ОС – http://mikeos.berlios.de.

Вот в чем во­прос: сколь­ко язы­ков про­грам­ми­ро­вания нуж­но знать, что­бы быть дей­ст­ви­тель­но хо­ро­шим про­грам­ми­стом? Точ­но­го от­ве­та нет, но мы бы ска­за­ли, три-че­ты­ре. Конеч­но, мож­но вы­учить один от кор­ки до кор­ки и стать в нем на­стоя­щим гением, но ни один язык не со­вер­шенен, по­это­му ка­ких-то воз­мож­но­стей и идей в нем все рав­но не бу­дет. Это по­хо­же на обыч­ные язы­ки: да­же ес­ли вы в со­вер­шен­ст­ве вла­дее­те анг­лий­ским, вы все рав­но мо­же­те по­черп­нуть очень мно­гое из род­ст­вен­ных ему язы­ков, на­при­мер, немец­ко­го или фран­цуз­ско­го. Вы нау­чи­тесь не толь­ко за­ме­нять сло­ва – вы нау­чи­тесь мыс­лить ина­че и по­пут­но от­крое­те для се­бя но­вую куль­ту­ру.

Итак, дей­ст­ви­тель­но хо­ро­ший про­грам­мист обыч­но зна­ет несколь­ко язы­ков про­грам­ми­ро­вания. И мы со­ве­ту­ем всем, да­же тем, у ко­го это лишь хоб­би, иногда про­бо­вать и дру­гие язы­ки, ведь из ка­ж­до­го мож­но уз­нать что-то но­вое. На­при­мер, сто­ит по­зна­ко­мить­ся с язы­ком низ­ко­го уров­ня, и вы уз­нае­те мно­го но­во­го об управ­лении па­мя­тью и ука­за­те­лях, и смо­же­те про­ще реа­ли­зо­вы­вать слож­ные ал­го­рит­мы на язы­ках вы­со­ко­го уров­ня. Зная несколь­ко язы­ков, вы всегда смо­же­те вы­брать вер­ный для ре­шения кон­крет­ной за­да­чи (слиш­ком мно­гие хо­ро­шо зна­ют толь­ко один язык, на­при­мер, C++, и по­это­му пы­та­ют­ся ре­шить с его по­мо­щью все за­да­чи).

По­это­му мы ре­ши­ли по­свя­тить эту ста­тью трем раз­лич­ным язы­кам. Но фиш­ка в том, что на ка­ж­дом из них мы бу­дем де­лать од­но и то же. Оно мо­жет по­ка­зать­ся бес­смыс­лен­ным, но на са­мом де­ле это от­лич­ный спо­соб уви­деть ка­ж­дый из них в дей­ст­вии и уз­нать, как од­на и те же цель дости­га­ет­ся с их раз­ны­ми под­хо­да­ми. Мы не бу­дем да­вать длин­ных из­ви­ли­стых опи­саний язы­ков: луч­шее, что вы смо­же­те сде­лать – про­чи­тать код и на­ши крат­кие объ­яснения и на­чать раз­би­рать­ся в них са­мо­стоя­тель­но. Итак, без дол­гих пре­ди­сло­вий...

Низ­кий уро­вень: C и SDL

Мы на­пи­шем эму­ля­цию па­рал­лак­ти­че­­ско­­го звезд­но­го неба с мно­же­ст­вом звезд, дви­жу­щих­ся по эк­ра­ну с раз­лич­ны­ми ско­ро­стя­ми. Из нее мож­но сде­лать скрин-сей­вер или фон для иг­ры shoot’em-up [«пе­ре­стре­ляй их всех» – жанр ви­део- и ком­пь­ю­тер­ных игр, – прим. пер.]. Глав­ное – эта за­да­ча по­мо­жет вам по­нять, как соз­да­вать мас­си­вы, вы­пол­нять вы­чис­ления, поль­зо­вать­ся цик­ла­ми, ри­со­вать пик­се­ли и т. д.

Начнем с до­воль­но низ­ко­го уров­ня на при­ме­ре C и SDL. C счи­та­ет­ся мно­ги­ми «пор­ти­руе­мым язы­ком ас­семб­ле­ра» и не вклю­ча­ет ком­фор­та язы­ков вы­со­ко­го уров­ня, на­по­до­бие сбо­ра му­со­ра. На C вы бо­лее тес­но взаи­мо­дей­ст­вуе­те с па­мя­тью и уст­рой­ст­ва­ми, и он иде­аль­но под­хо­дит для оп­ре­де­лен­ных за­дач. SDL (Simple DirectMedia Layer – про­стой уро­вень для досту­па к муль­ти­ме­диа) – очень по­пу­ляр­ная биб­лио­те­ка для ра­бо­ты с муль­ти­ме­диа, ко­то­рая по­зво­ля­ет ра­бо­тать с изо­бра­жения­ми, шриф­та­ми и зву­ком. Это не са­мая про­стая биб­лио­те­ка в сравнении с ин­ст­ру­мен­та­ри­ем для раз­ра­бот­ки игр, но, как и C, она да­ет вам мощ­ный кон­троль над про­ис­хо­дя­щим.

Итак, начнем с этой слад­кой па­роч­ки и рас­смот­рим «сы­рой» спо­соб соз­дания звезд­но­го неба, а за­тем пе­рей­дем к вы­со­ко­уровневым аль­тер­на­ти­вам. Ни­же при­ве­ден код – его вы най­де­те на дис­ке в фай­ле starfield.c (от­крой­те ка­та­лог c_with_sdl внут­ри starfield.tgz).

#include <stdlib.h>
#include <SDL.h>
#define MAX_STARS 100

typedef struct

{

int x, y, speed;

} star_type;

star_type stars[MAX_STARS];

int main()

{

int i;

Uint8 *p;

SDL_Surface *screen;

SDL_Init(SDL_INIT_VIDEO);

atexit(SDL_Quit);

screen = SDL_SetVideoMode(640, 480, 8, SDL_SWSURFACE);

for(i = 0; i < MAX_STARS; i++) {

stars[i].x = rand()%640;

stars[i].y = rand()%480;

stars[i].speed = 1 + rand()%16;

}

for(i = 0; i < SDL_NUMEVENTS; i++) {

if (i != SDL_QUIT)

SDL_EventState(i, SDL_IGNORE);

}

while (SDL_PollEvent(NULL) == 0) {

SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));

for(i = 0; i < MAX_STARS; i++) {

stars[i].x -= stars[i].speed;

if(stars[i].x <= 0)

stars[i].x = 640;

p = (Uint8 *) screen->pixels + stars[i].y * screen->pitch + stars[i].x * screen->format->BytesPerPixel;

  • p = 255;

}

SDL_UpdateRect(screen, 0, 0, 0, 0);

SDL_Delay(30);

}

return 0;

}

Не­пло­хо, а? Чуть боль­ше 50 строк ко­да – и мы по­лу­чи­ли яр­кое звезд­ное небо, все на чис­том C и SDL на низ­ком уровне. Для ком­пи­ля­ции ис­пол­няе­мо­го фай­ла вы­полните ко­ман­ду

gcc -o starfield starfield.c `sdl-config --cflags` `sdl-config --libs`

Об­ра­ти­те внимание на сим­во­лы об­рат­но­го апо­ст­ро­фа – со­от­вет­ст­вую­щая кла­ви­ша ско­рее все­го бу­дет в ле­вом верхнем уг­лу кла­виа­ту­ры. В об­щем слу­чае все, что внут­ри об­рат­ных апо­ст­ро­фов, счи­та­ет­ся ко­ман­дой, но эти ко­ман­ды вы­пол­ня­ют­ся до вы­полнения основ­ной ко­ман­ды (gcc). Так, на­брав sdl-config --libs, вы уви­ди­те па­ра­мет­ры ком­пи­ля­то­ра, необ­хо­ди­мые для сбор­ки про­грамм SDL. Для за­пуска ском­пи­ли­ро­ван­ной про­грам­мы на­бе­ри­те ./starfield.

Лад­но, взглянем-ка на код. Пер­вые две стро­ки го­во­рят ком­пи­ля­то­ру, что мы хо­тим под­клю­чить за­го­ло­воч­ные фай­лы стан­дарт­ной биб­лио­те­ки (для генера­ции слу­чай­ных чи­сел) и SDL (что­бы восполь­зо­вать­ся про­це­ду­ра­ми этой биб­лио­те­ки). За­тем сле­ду­ет стро­ка #define, ко­то­рая го­во­рит GCC, что все по­сле­дую­щие вхо­ж­дения MAX_STARS в ис­ход­ном ко­де нуж­но за­менить чис­лом 100. Это по­зво­ля­ет нам экс­пе­ри­мен­ти­ро­вать с ко­ли­че­­ст­вом звезд, ме­няя все­го од­ну стро­ку в ко­де.

Да­лее мы оп­ре­де­ля­ем струк­ту­ру – т. е. на­бор пе­ре­мен­ных, к ко­то­рым мож­но об­ра­щать­ся вме­сте под одним именем. Мы на­зва­ли ее star_type, и ка­ж­дая пе­ре­мен­ная это­го ти­па бу­дет иметь внут­ри пе­ре­мен­ные ко­ор­ди­нат X и Y, а так­же ско­ро­сти speed (все это це­лые чис­ла). Стро­кой

star_type stars[MAX_STARS];

мы соз­да­ем но­вый мас­сив струк­тур star_type, ко­то­рый на­зы­ва­ет­ся stars. В ко­де на дис­ке это мас­сив из 100 звезд, к эле­мен­там ко­то­ро­го мож­но об­ра­щать­ся как к star[0] – star[99].

Песня о глав­ном

По­ра пе­рей­ти к са­мо­му ко­ду – внутрь функ­ции main() (это пер­вая функ­ция, ко­то­рая вы­пол­ня­ет­ся в про­грам­ме на C – а в на­шем ко­де это во­об­ще един­ст­вен­ная функ­ция). Мы объ­яв­ля­ем це­ло­чис­лен­ную пе­ре­мен­ную i, ко­то­рая бу­дет ис­поль­зо­вать­ся в ка­че­­ст­ве счет­чи­ка. За­тем мы объ­яв­ля­ем пе­ре­мен­ную p – без­зна­ко­вый вось­ми­бит­ный це­ло­чис­лен­ный ука­за­тель; это пе­ре­мен­ная, ко­то­рая за­тем бу­дет ссы­лать­ся на гра­фи­че­­ские дан­ные. На­конец, у нас есть пе­ре­мен­ная screen – ука­за­тель, ко­то­рый бу­дет ис­поль­зо­ван в SDL.

Ес­ли вы никогда не слы­ша­ли об ука­за­те­лях, они по­хо­жи на пе­ре­мен­ные, но хра­нят не чис­ла, а ад­ре­са в па­мя­ти дру­гих пе­ре­мен­ных. Так, у вас мо­жет быть це­ло­чис­лен­ная пе­ре­мен­ная x, ко­то­рая со­дер­жит чис­ло 50 и на­хо­дит­ся по ад­ре­су 1000 в опе­ра­тив­ной па­мя­ти. Ес­ли соз­дать ука­за­тель y и на­пра­вить его на x, то y бу­дет со­дер­жать не 50, а ад­рес в па­мя­ти – 1000. Ра­бо­та с ука­за­те­ля­ми весь­ма кро­пот­ли­ва, и мно­гие язы­ки про­грам­ми­ро­вания вы­со­ко­го уров­ня их из­бе­га­ют, но знать о них сто­ит. За­тем сле­ду­ют три стро­ки, где инициа­ли­зи­ру­ют­ся под­сис­те­мы SDL, со­об­ща­ет­ся ком­пи­ля­то­ру, что мы хо­тим вы­звать про­це­ду­ру SDL_Quit по за­вер­шении про­грам­мы, и соз­да­ет­ся но­вое ок­но (640 пик­се­лей в ши­ри­ну и 480 в вы­со­ту, в 8-бит­ном ре­жи­ме цве­та для 256 цве­тов).

Вы­зов SDL_SetVideoMode воз­вра­ща­ет струк­ту­ру с вы­во­ди­мой ин­фор­ма­ци­ей, по­это­му мы на­прав­ля­ем ука­за­тель screen на нее.

Да­лее идут два цик­ла for. Пер­вый за­пол­ня­ет мас­сив со звез­да­ми, за­пи­сы­вая в ко­ор­ди­на­ты X и Y и ско­рость ка­ж­до­го эле­мен­та слу­чай­ные зна­чения. По­про­буй­те из­менить 16 в стро­ке со ско­ро­стью на боль­шее и мень­шее зна­чения и по­смот­ри­те на ре­зуль­тат. Вто­рой цикл for ве­лит SDL не до­ку­чать нам в от­вет на ка­ж­дое на­жа­тие кла­ви­ши или дви­жение мы­ши, ко­то­рые она по­лу­ча­ет – она долж­на иг­но­ри­ро­вать все, кро­ме за­кры­тия ок­на.

За­тем мы пе­ре­хо­дим к цик­лу while, где и творится вся потеха. Мы го­во­рим SDL, что хо­тим вы­пол­нять этот цикл до тех пор, по­ка ок­но не бу­дет за­кры­то, и на­ше пер­вое дей­ст­вие – за­полнить эк­ран чер­ным цве­том (RGB 0, 0, 0) с по­мо­щью функ­ции SDL_FillRect (бла­го­да­ря NULL за­пол­ня­ет­ся все ок­но). За­тем мы про­хо­дим­ся по мас­си­ву star, об­нов­ляя го­ри­зон­таль­ное по­ло­жение ка­ж­дой звез­ды, вы­чи­тая ее ско­рость (точнее, перемещение за единицу времени) из ее ко­ор­ди­на­ты x. Пик­се­ли ок­на ну­ме­ру­ют­ся от 0 до 639 сле­ва на­пра­во и от 0 до 479 свер­ху вниз. Ес­ли звез­да вы­ле­та­ет за ле­вую границу ок­на (т. е. ее ко­ор­ди­на­та x становится мень­ше ну­ля), мы пе­ре­ме­ща­ем ее впра­во, сно­ва на­чи­ная с 640.

В двух стро­ках, на­чи­наю­щих­ся с p и *p, звез­ды до­бав­ля­ют­ся на «небо». В пер­вой мы за­пи­сы­ва­ем в ука­за­тель p точ­ное рас­по­ло­жение гра­фи­че­­ских дан­ных, в ко­то­рые нуж­но раз­мес­тить пик­сель. Эти дан­ные хра­нят­ся в по­ле pixels струк­ту­ры screen, и так как это линей­ный од­но­мер­ный мас­сив бай­тов, нуж­но вы­полнить кое-ка­кие вы­чис­ления, что­бы оно со­от­вет­ст­во­ва­ло на­ше­му дву­мер­но­му изо­бра­жению. Когда p ука­зы­ва­ет на вер­ное ме­сто, мы за­пи­сы­ва­ем чис­ло 255 (бе­лый цвет) в байт, на ко­то­рый он ука­зы­ва­ет (для это­го ис­поль­зу­ет­ся звез­доч­ка).

На­конец, мы го­во­рим SDL, что за­кон­чи­ли от­ри­сов­ку и нуж­но вы­вес­ти все на эк­ран, и до­бав­ля­ем за­держ­ку в 30 мил­ли­се­кунд, что­бы звез­ды не дви­га­лись слиш­ком бы­ст­ро. Вот и все!

Ес­ли вы никогда не пи­са­ли на C, про­грам­ма мог­ла по­ка­зать­ся слож­но­ва­той, по­это­му пе­рей­дем к язы­кам бо­лее вы­со­ко­го уров­ня. Ос­во­ив их, вернитесь сю­да, и все станет яснее.

Вы­со­кий уро­вень: Python и Pygame

По сравнению с тем, что мы толь­ко что сде­ла­ли, в Python с его биб­лио­те­кой Pygame жизнь на­мно­го лег­че. Го­раз­до про­ще ра­бо­тать с гра­фи­кой, и не нуж­но во­зить­ся с ука­за­те­ля­ми. Ни­же при­ве­ден код – это файл starfield.py на LXFDVD, и ес­ли у вас уста­нов­ле­на Pygame, его мож­но за­пустить ко­ман­дой ./starfield.py. Ес­ли вы но­ви­чок в Python, код все рав­но бу­дет доста­точ­но по­нят­ным, но не забывайте об­ра­щать внимание на от­сту­пы, ко­то­рые жизнен­но важ­ны для ра­бо­ты про­грам­мы. Код, вы­де­ляе­мый в бло­ки (на­при­мер, в цик­лы), всегда нуж­но вы­де­лять от­сту­па­ми.

#!/usr/bin/env python

import pygame

from random import randrange

MAX_STARS = 100

pygame.init()

screen = pygame.display.set_mode((640, 480))

clock = pygame.time.Clock()

stars = []

for i in range(MAX_STARS):

star = [randrange(0, 639), randrange(0, 479), randrange(1, 16)]

stars.append(star)

while True:

clock.tick(30)

for event in pygame.event.get():

if event.type == pygame.QUIT:

exit(0)

screen.fill((0,0,0))

for star in stars:

star[0] -= star[2]

if star[0] < 0:

star[0] = 640

screen.set_at((star[0], star[1]), (255, 255, 255))

pygame.display.flip()

По ло­ги­ке этот язык очень по­хож на C, и их воз­мож­но­сти сравнимы. В на­ча­ле мы го­во­рим Python, что хо­тим восполь­зо­вать­ся мо­ду­лем Pygame и про­це­ду­рой генера­ции слу­чай­ных чи­сел randrange из мо­ду­ля random, и за­тем объ­яв­ля­ем пе­ре­мен­ную MAX_STARS, ана­ло­гич­ную эк­ви­ва­лен­ту в про­грам­ме на C. За­тем мы инициа­ли­зи­ру­ем Pygame и соз­да­ем ок­но, свя­зы­вая его с пе­ре­мен­ной screen, по­сле че­го уста­нав­ли­ва­ем фо­но­вый тай­мер с по­мо­щью функ­ции Clock() биб­лио­те­ки Pygame, что­бы немно­го за­мед­лить про­ис­хо­дя­щее.

За­тем мы оп­ре­де­ля­ем мас­сив star (или спи­сок – list – в тер­ми­нах Python) вы­ра­жением stars = []. Как ви­ди­те, Python бо­лее ги­бок, чем C, и раз­мер мас­си­ва в на­ча­ле за­да­вать не нуж­но. Да­лее в цик­ле мы соз­да­ем 100 объ­ек­тов star и за­пол­ня­ем ка­ж­дый из них тре­мя зна­чения­ми: ко­ор­ди­на­той X, ко­ор­ди­на­той Y и ско­ро­стью speed, как и в при­ме­ре на C. По­сле соз­дания ка­ж­до­го объ­ек­та star мы до­бав­ля­ем его в спи­сок stars ме­то­дом append.

Да­лее сле­ду­ет цикл while True, он основ­ной. Сна­ча­ла идет за­держ­ка clock.tick, за­тем мы ве­лим Pygame об­ра­бо­тать все при­шед­шие со­бы­тия от кла­виа­ту­ры и мы­ши (по за­кры­тию ок­на мы вы­хо­дим из про­грам­мы). За­тем с по­мо­щью ме­то­да fill на­ше­го объ­ек­та screen мы за­ли­ва­ем ок­но чер­ным цве­том и на­чи­на­ем пе­ре­би­рать звез­ды в цик­ле: для ка­ж­дой звез­ды вы­чи­та­ем ее тре­тий ком­понент (ско­рость, а точнее, перемещение за единицу времени) из пер­во­го (ко­ор­ди­на­та X), причем звез­да пе­ре­ме­ща­ет­ся вле­во (эле­мен­ты мас­си­ва или спи­ска ну­ме­ру­ют­ся с ну­ля). Как и в вер­сии на C, мы про­ве­ря­ем вы­ход звез­ды за ле­вую границу ок­на.

По­сле это­го мы по­ме­ща­ем бе­лый пик­сель (255, 255, 255 в фор­ма­те RGB) в точ­ку с ко­ор­ди­на­та­ми X и Y, и на этом цикл star processing за­кан­чи­ва­ет­ся. На­конец, мы вы­зы­ва­ем про­це­ду­ру display flip, ко­то­рая вы­во­дит ре­зуль­та­ты всех пре­ды­ду­щих опе­ра­ций ри­со­вания на эк­ран. В це­лом, про­грам­ма на Python ко­ро­че, про­ще и по­нятнее по сравнению с вер­си­ей на C/SDL, и, как вы мо­же­те пред­ста­вить, пи­сать иг­ры в Pygame очень ве­се­ло.

Не­обыч­ный уро­вень: Perl и Ncurses

На­конец, рас­смот­рим луч­ший язык для об­ра­бот­ки тек­ста, ко­то­рый в рав­ной ме­ре лю­бят и нена­ви­дят – Perl. И, для вя­щей пу­таницы, мы бу­дем ри­со­вать звезд­ное небо не в гра­фи­че­­ском, а в тек­сто­вом тер­ми­на­ле. Как? Вы­во­дя точ­ки в ка­че­­ст­ве звез­до­чек! Есть очень по­лез­ная биб­лио­те­ка Ncurses, доступ­ная для боль­шин­ст­ва язы­ков про­грам­ми­ро­вания; она здо­ро­во уп­ро­ща­ет ра­бо­ту с тер­ми­на­лом (пе­ре­ме­щение, от­клю­чение кур­со­ра и т. д.). Вот код про­грам­мы с LXFDVD из фай­ла starfield.pl:

  1. !/usr/bin/perl

$numstars = 100;

use Time::HiRes qw(usleep);

use Curses;

$screen = new Curses;

noecho;

curs_set(0);

for ($i = 0; $i < $numstars ; $i++) {

$star_x[$i] = rand(80);

$star_y[$i] = rand(24);

$star_s[$i] = rand(4) + 1;

}

while (1) {

$screen->clear;

for ($i = 0; $i < $numstars ; $i++) {

$star_x[$i] -= $star_s[$i];

if ($star_x[$i] < 0) {

$star_x[$i] = 80;

}

$screen->addch($star_y[$i], $star_x[$i], “.”);

}

$screen->refresh;

usleep 50000;

}

Те­перь вы долж­ны хо­ро­шо понимать струк­ту­ру это­го ко­да. Един­ст­вен­ное круп­ное от­ли­чие здесь – вме­сто мас­си­ва звезд с ко­ор­ди­на­та­ми и ско­ро­стью для ка­ж­дой (т. е. мас­си­ва мас­си­вов) мы, что­бы уп­ро­стить код, восполь­зо­ва­лись тре­мя мас­си­ва­ми. star_x со­дер­жит ко­ор­ди­на­ты X звезд, star_y – ко­ор­ди­на­ты Y, а star_s – ско­ро­сти (как минимум единицу, что­бы все звез­ды дви­га­лись). Раз­мер эк­ра­на 80 × 24 – стан­дарт­ный раз­мер тер­ми­на­ла X Window, но для тер­ми­на­лов боль­ше­го раз­ме­ра эти зна­чения мож­но из­менить.

Здесь addch – про­це­ду­ра пе­ча­ти сим­во­лов Ncurses; и, что ин­те­рес­но, ко­ор­ди­на­та Y в ней ука­зы­ва­ет­ся пер­вой. usleep приоста­нав­ли­ва­ет вы­полнение про­грам­мы на за­дан­ное чис­ло мик­ро­се­кунд. Да, а ко­ман­ды noecho и curs_set в на­ча­ле го­во­рят, что нам не нуж­ны ввод с кла­виа­ту­ры и тек­сто­вый кур­сор.

Итак, мы уз­на­ли, как реа­ли­зо­вать звезд­ное небо на трех язы­ках про­грам­ми­ро­вания с тре­мя ин­ст­ру­мен­та­рия­ми, и, на­де­юсь, это по­бу­дит вас по­про­бо­вать но­вые язы­ки и биб­лио­те­ки. Как ви­ди­те, основ­ные ал­го­рит­мы в про­грам­ме обыч­но одни и те же, но всегда мож­но уз­нать кое-что но­вое. На­сла­ж­дай­тесь, и – уда­чи вам! |

Персональные инструменты
купить
подписаться
Яндекс.Метрика