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

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

Отступление


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

О конкурсе


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 место, но об этом, усложненных заданиях и последствиях отключения Интернета на площадках в следующей статье.

Комментарии (9)


  1. rum
    23.07.2019 00:36

    Я всегда думал, что «спортивное программирование» это больше про ACM ICPC, ну да ладно.
    Критерии оценки не выдаются на руки, это понятно, но ведь на что-то жюри обращало внимание? Или вся оценка происходит за кулисами? Если есть возможность, расскажите больше про это.


    1. dipierro
      23.07.2019 03:41

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


      1. Pingvi
        23.07.2019 06:28

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

        В этом году критерии оценок составляли те, кто создавал задания. Эксперты, а тем более эксперты-компатриоты к составлению критерий оценок не привлекаются.

        Процесс оценки условно-закрытый, да.

        Процесс оценки именно закрытый, допускается только главный эксперт и эксперты экзамена/соревнования.

        rum эксперты строго следуют критериям оценок, почти все они составлены таким образом, чтобы можно было ответить на вопрос «да» или «нет». Итоговую оценку выставляет система, после того как в неё занесут промежуточные результаты.
        Согласно правилам на 10 участников выделяется 3 эксперта для проверки работы, больше 10 участников проверить физически сложно так как критериев достаточное количество.


        1. Santa42
          23.07.2019 11:33

          Сейчас по правилам соревнований главный эксперт так же не допускается к оценке участников. Оценку проводят только эксперты-компатриоты.

          Главный эксперт может быть допущен только по согласованию с остальными экспертами(проводится голосование и если большинство не против — глав.эксперт оценивает)
          То есть тут должна быть полная демократия в оценке, а главный эксперт может как-то влиять на оценку(обычно главный эксперт и составляет задание и критерии оценки)
          Проверка проводится экспертами-компатриотами, но проверятся не свой участник. То есть ни участник, ни эксперт-компатриот не должны знать сколько балов набрал участник. Известны балы только других участников


          1. Pingvi
            23.07.2019 13:02

            В этом году был экспертом на демонстрационном экзамене, есть незначительные отличия по соревнованиям. На ДЭ главный эксперт может вмешиваться в работу экспертов если видит сговор, и влиять на оценку как раз может.

            На ДЭ нет экспертов-компатриотов, только эксперты ДЭ (приглашенные со стороны) и технический эксперт (от площадки).


            1. Santa42
              23.07.2019 13:08

              Согласен
              ДЭ на самом деле во многих деталях отличается от соревнований.
              Я не участвовал в ДЭ никак. Могу сказать только как на соревнованиях, так как неоднократно был глав. экспертом


    1. alekseyHunter Автор
      23.07.2019 06:52

      но ведь на что-то жюри обращало внимание?

      rum Да, большинство баллов ставилось за наличие компонентов на форме, которые указаны в макете. Меньше баллов за получение данных из базы данных, но можно было вписать данные и вручную. Еще оценивались комментарии, но написать приличные в стиле JavaDoc было сложно, так как функций со сложной логикой не было.


  1. nicebmw9
    23.07.2019 06:53

    Я писал подобные приложения ещё на 1 курсе, но почему то фирмы за мной не охотились =(


    1. alekseyHunter Автор
      23.07.2019 11:31

      nicebmw9 А Вы им показывали приложения?