WorldSkills: Обзор от участника олимпиады

Sport programmingDevelopment for AndroidKotlinDevelopment for Windows

Привет, Хабр!

Каждый программист хочет прокачать свои навыки и каждая компания хочет видеть у себя квалифицированных специалистов, но как этого достичь? На помощь приходят олимпиады, об участии в одной из них и будет эта статья.

Отступление


Статья получилась объемная, поэтому весь код и большая часть изображений находятся под спойлерами. Все еще тестирую формат подачи статей. Если такой формат покажется Вам неудобным, просьба написать об этом. Также все картинки кликабельны.

О конкурсе


WorldSkills — это соревнование, целью которого является выявление профессионалов в конкретной области. Конкурс берет свои истоки с WorldSkills International (WSI), международной некоммерческой ассоциации.

Участие в конкурсе бесплатное. Возраст участников от 18 до 28 лет, учащиеся в коллежах или ВУЗах.

Конкурс в ВУЗе длится 5 дней: в первый день открытие и проверка рабочих мест, затем три дня соревнований, в заключительный день — подведение итогов и закрытие.

О выборе компетенции и подготовке


Об олимпиаде я узнал случайно во время одной из практик в университете на 2 курсе. Меня пригласили поучаствовать в компетенции «Разработка программных решений для бизнеса». Для решения задач требовались знания в C# или Java, работа с базой данных, и как я узнал в процессе первой олимпиады Android. Открытие олимпиады было запланировано на начало июня, а за окном был уже конец апреля. В этот момент я не знал абсолютно ничего, что требовалось.

Моему удивлению не было предела, когда всех потенциальных участников собрали для подготовки. В аудитории сидело 7 человек, 6 из которых были уже третьекурсниками, седьмым был я. Почему же я был так удивлен? На третьем курсе студентам читают курс по Базам данных, который длится 2 семестра, а значит у всех было на год практики больше. Отказываться не хотелось, поэтому я попросил у преподавателей книги по БД, нашел курс по C# в Интернете и начал готовиться, периодически появляясь на подготовительных занятия.

За две недели до начала олимпиады мы узнали, что от нашей кафедры могут участвовать не более трех человек. Прошел отборочный тур. За 1,5 часа мы должны были создать базу данных в СУБД MSSQL по ER-модели, импортировать данные из файла Excel и показать их в приложении. Одним словом, мне разрешили участвовать в олимпиаде от нашей кафедры.

Теперь о конкурсе


Первый день соревнования или «C -1»


В этот день прошло долгожданное открытие. Всех участников собрали в актовом зале, где рассказали о истории WorldSkills, компетенциях и представили всех экспертов с участниками.
После этой церемонии все разошлись по своим площадкам проверять оборудование. Был проведен «ритуал посвящения», на котором мы выбирали свои рабочие места и расписывались за технику безопасности. На тот момент я не знал что можно проверять в ПО, поэтому сразу после жеребьевки ушел готовиться к следующему дню.

Второй день соревнования или начало соперничества


Что же представляла из себя олимпиада?

Для нашей компетенции за 2 дня (три для других компетенций) по 6 часов с перерывами от нас требовалось написать клиент-серверное приложение на C#/Java с запросами к БД. Точнее сказать «все что успеем написать», поскольку принципом олимпиады является «делай что можешь и как можешь».

По словам экспертов, критериев было на несколько десятков страниц A4. Критерии выдаются только после окончания сессии и только экспертам, поэтому о них ничего сказать не могу.

Перейдем к событиям второго дня. За первую сессию требовалось реализовать следующее:

  • Создать базу данных по известной ER-диаграмме
  • Импортировать данные из файлов Excel в базу
  • Создать 4 экрана по макетам в презентации

А за вторую сверстать еще пять экранов по макетам.

Если кратко, все задания были решены, кроме отображения картинок из базы данных и создания кастомного списка элементов.

Немного скриншотов:




Интерфейс позволяет найти игроков по имени (даже по первой букве), сезону и команде. По двойному клику на изображение, которое не было реализовано, открывается подробная информация о игроке. Сейчас бы реализовал это через DataGridViewImageColumn.




Как я сохранял изображения
        private void download_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFile = new SaveFileDialog();
            saveFile.DefaultExt = ".jpg";
            saveFile.AddExtension = true;
            //Папка по умолчанию - Библиотеки/Изображения
            saveFile.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures);
            saveFile.Filter = "Bitmap files (*.bmp)|*.bmp|Image files (*.jpg;*.png)|*.jpg;*.png";

            if (saveFile.ShowDialog() == DialogResult.OK)
            {
                if (!saveFile.FileName.Equals(""))
                {

                    String[] arr = saveFile.FileName.Split('.');

                    //Получаем путь для сохранения от начала до "\" включительно
                    String filepath = arr[0].Substring(0, arr[0].LastIndexOf('\\') + 1);

                    for (int i = 1; i <= imageList1.Images.Count; i++)
                    {
                        Image image = Image.FromFile("..\\Pictures\\" + (i + offset) + ".jpg");

                        image.Save(filepath + i + "." + arr[1]);
                    }
                }
            }
        }


Больше скриншотов,текста и кода



Интерфейс является главным экраном, позволяющий выбрать роль «Пользователь» или «Администратор», а также посмотреть лучшие моменты с матчей. Как разместить элементы по центру не смог найти.

Реализация загрузки изображений

        private int countImages()
        {
            sqlConnection = new SqlConnection(connectionString);

            using (sqlConnection)
            {
                sqlConnection.Open();

                String sqlcomm = "SELECT Count(*) FROM [Pictures$]";

                SqlCommand command = new SqlCommand(sqlcomm, sqlConnection);

                int result = (int)command.ExecuteScalar();

                return result;
            }
        }

        private void loadPage()
        {
            sqlConnection = new SqlConnection(connectionString);

            using (sqlConnection)
            {
                sqlConnection.Open();

                String sqlcomm = "SELECT [Img] , [CreateTime] FROM [Pictures] P" +
                    " Order By P.CreateTime Desc" +
                    " Offset @click Rows FETCH NEXT @svm ROWS ONLY";
                SqlCommand cmd = new SqlCommand(sqlcomm, sqlConnection);
                cmd.Parameters.AddWithValue("@click", click);
                cmd.Parameters.AddWithValue("@svm", svm);

                dataAdapter.SelectCommand = cmd;
                DataSet data = new DataSet();
                dataAdapter.Fill(data);

                imageList1.Images.Clear();
                listView1.Clear();

                for (int i = 0; i < data.Tables[0].Rows.Count; i++)
                {
                    imageList1.Images.Add(Image.FromFile("..\\Pictures\\" + data.Tables[0].Rows[i].ItemArray[0].ToString()));
                }

                listView1.LargeImageList = imageList1;

                for (int i = 0; i < imageList1.Images.Count; i++)
                    listView1.Items.Add("").ImageIndex = i;

            }
        }

        private void left_Click(object sender, EventArgs e)
        {
            if (click > 0)
            {
                if (!right.Enabled)
                {
                    right.Enabled = true;
                }

                click -= svm;

                loadPage();
            }
            else
            {
                left.Enabled = false;
            }

        }

        private void right_Click(object sender, EventArgs e)
        {
            if (click + svm < totalPhotos)
            {
                if (!left.Enabled)
                {
                    left.Enabled = true;
                }
                click += svm;

                loadPage();
            }
            else
            {
                right.Enabled = false;
            }
        }





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

Проверка существования введенного логина и пароля в БД
        private int checkData(string jobnumber, string password)
        {
            //Защита от SQL-Injection при помощи параметризованных команд
            String sqlchecked = "if (Isnull((Select RoleId From Admin$ Where Jobnumber like(@Jobnumber) and Passwords like(@Password)) , 0) = 0)" +
                " Select 0 " +
                " else " +
                " Select RoleId From Admin$  Where Jobnumber like(@Jobnumber) and Passwords like(@Password)";

            sqlConnection = new SqlConnection(connectionString);
            int result = 0;
            using (sqlConnection)
            {
                sqlConnection.Open();

                SqlCommand command = new SqlCommand(sqlchecked, sqlConnection);
                //Автоматическое определение типа вводимых данных
                command.Parameters.AddWithValue("@Jobnumber", jobnumber);
                command.Parameters.AddWithValue("@Password", password);
                //Получение данных из первого столбца первой строки таблицы
                result = (int)command.ExecuteScalar();
            }
            return result;
        }


Если Вы знаете лучший способ проверить данные, просьба написать в комментариях.




А так выглядит недоделанный экран, здесь я просто накидал компоненты на форму согласно макету. Можете, пожалуйста, подсказать как сделать такой кастомный список на C#?




Самый сложный для меня экран, так как здесь была работа с графиками, к которым я не готовился.

Третий день соревнования или неожиданная встреча с Android


Предыдущий день можно было считать разогревом по сравнению с третьим. На 3 сессии предстояло доверстать еще 8 экранов. А на заключительной сессии произошла смена планов и вместо презентации по разработанному продукту мы начали делать упрощенную версию для Android. А именно галерею с картинками, подгружаемых из базы данных. Сейчас это звучит легко, но в тот момент я был рад, что на сессию давали 15 минут для выхода в Интернет. За 3 часа был создан GridView с элементами в виде ImageView, в Adapter передан массив id картинок и переопределен интерфейс OnItemClickListener для создания новой Activity с картинкой.

Еще немного скриншотов:




Интерфейс для администратора, позволяющий посмотреть информацию по игрокам. Самый бесполезный экран, по моему мнению, так как вносить изменения нельзя.

| | |

Моя «идеальная» галерея за 3 часа с использованием Интернета и без навыков программирования под Android.

Больше изображений, текста и кода
Код для создания галереи, когда не знаешь как работать с БД
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        //список id изображений
        var images = ArrayList<Int>()

        images.add(R.drawable.a1)
        //...
        images.add(R.drawable.a12)

        listView.adapter = CustomAdapter(images.toTypedArray())

        listView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>, _: View, i: Int, _: Long ->
            var intent = Intent(this, FullscreenActivity::class.java)
            //передаем id изображения, которое нужно посмотреть
            intent.putExtra("id",images[i])
            startActivity(intent)
        }
    }
}

class CustomAdapter internal constructor(var images: Array<Int>) :
    BaseAdapter() {

    override fun getCount(): Int {
        return images.size
    }

    // элемент по позиции
    override fun getItem(position: Int): Int {
        return images[position]
    }

    // id по позиции
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    // пункт списка
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //используем уже созданное представление
        var view: View? = convertView
        //если нет, создаем новое
        if (view == null) {
            val inflater = LayoutInflater.from(parent.context)
            view = inflater.inflate(R.layout.item, parent, false)
        }

        view!!.findViewById<ImageView>(R.id.imageView).setImageResource(images[position])

        return view
    }

}

class FullscreenActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fullscreen)

        //Получаем id изображения
        var id = intent.getIntExtra("id",-1)

        imageView2.setImageResource(id)
    }
}






По клику на сезон(правая таблица) открывается подробная информация о всех матчах.




Интерфейс для администраторов, позволяющий вносить изменения в список команд и экспортировать данные в Excel.

Как я экспортировал данные в Excel

        private void exportExcel_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.Filter = "Excel (*.xls)|*.xls|All files (*.*)|*.*";
            saveFileDialog.DefaultExt = ".xls";

            if (saveFileDialog.ShowDialog() != DialogResult.OK)
                return;

            if (saveFileDialog.FileName.Equals(""))
                return;

            DataTable dt = (DataTable)dataGridView2.DataSource;
            //Открываем файл
            StreamWriter wr = new StreamWriter(saveFileDialog.FileName);

            try
            {
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    wr.Write(dt.Columns[i].ToString().ToUpper() + "\t");
                }

                wr.WriteLine();

                //Записываем строки в файл Excel
                for (int i = 0; i < (dt.Rows.Count); i++)
                {
                    for (int j = 0; j < dt.Columns.Count; j++)
                    {
                        if (dt.Rows[i][j] != null)
                        {
                            wr.Write(Convert.ToString(dt.Rows[i][j]) + "\t");
                        }
                        else
                        {
                            wr.Write("\t");
                        }
                    }
                    //Переходим на следующую строку
                    wr.WriteLine();
                }
                //Закрываем файл
                wr.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }



Подведение итогов


В 12 часов состоялась церемония закрытия, где всем участникам вручались сертификаты, а победителей и призеров ждали медальки с дипломами. В нашей компетенции я занял 2 место. Между награждением компетенций выступали студенты с песнями и танцами.

Заключение


За месяц подготовки я освоил DDL и DML команды SQL, что значительно упростило работу на парах по БД на третьем курсе. Полученные знания в C# и Windows Form оставляют желать лучшего, кроме работы с базами данных и пользовательским интерфейсом ни с чем работать не пришлось.

В этом году я также принимал участие в олимпиаде WorldSkills и занял 1 место, но об этом, усложненных заданиях и последствиях отключения Интернета на площадках в следующей статье.
Tags:спортивное программированиеолимпиадаworldskillsworldskills russia
Hubs: Sport programming Development for Android Kotlin Development for Windows
+8
3.1k 15
Comments 9

Popular right now